Firebase Cloud Functions "admin.messaging(...).send is not a function" - firebase

I have a function in Firebase Functions service that send any FCM.
I would to use admin.messaging().send() function, like this reference guide, but I got this error while function is triggered, not during deploy:
TypeError: admin.messaging(...).send is not a function
at exports.sendChatNotification.functions.database.ref.onCreate.event (/user_code/lib/index.js:113:30)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:59:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:53:36)
at /var/tmp/worker/worker.js:700:26
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
I can see this error in Functions->Log inside Firebase console.
That's my function code:
exports.sendChatNotification = functions.database.ref('/messages').onCreate(event => {
var message = {
data: {
title: 'title',
body: 'body',
},
apns: {
header: {
'apns-priority': '10',
'apns-expiration':'0'
},
payload: {
aps: {
sound: 'default',
'content-available':'1'
}
}
},
android: {
ttl: 60*1000,
priority: 'high'
},
topic: 'mytopic'
};
return admin.messaging().send(message);
});
If I use admin.messaging().sendToTopic() and (changing the message structure) it works fine. It seems Firebase doesn't support its own API.
I deploy this using Firebase tools, with command line "firebase deploy".
I have updated firebase-tools and firebase-functions and firebase-admin in my functions project.

The send() function was added in firebase-admin 5.9.0. If you want to use it, you should run npm install firebase-admin#latest in your functions folder to install the latest version. At the time of this writing, the latest version is 5.9.1.

Related

Firebase Functions does not initialize admin on different class

I have a ionic 3 app that was working fine some days ago. The app receive notifications from FCM after the trigger 'onWrite', from Realtime database.
The Firebase functions npm package was obsollet, so I updated using the following command:
npm install --save firebase-functions#latest
Now, the console shows me an warning saying that my admin is with the wrong credentials. My code has some classes and every class has a admin instance. The 'main' admin is initialized on index.js very start:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const serviceController = require('./controllers/serviceController');
exports.notifyWorks = functions.database.ref('/notificationsAll/{State_}/{notificationYear}/{notificationMonth}/{notificationId}')
.onWrite((change, context) => {
return serviceController.notifyWorks(change, admin)
});
The 'serviceController' class constructor:
class serviceController {
constructor() {
this.admin = require('firebase-admin');
this.notificationIcon = ''
this.notificationSound = "ding"
}
notifyWorks(change, context){
if (!change.after.exists()) {
return new Promise(function(resolve){
resolve(console.log("Não foi possível identificar tipo de app"))
});
} else {
const snapshot = change.after
const data = snapshot.val()
if (!data) {
return new Promise(function(resolve){
resolve(console.log("Não foi possível identificar os dados da mensagem"))
});
} else {
resolve(console.log("Here we continue with the logic"))
}
}
}
}
exports.notifyWorks = function(change, context){
const helper = new serviceController();
return helper.notifyWorks(change, context)
};
The functions package.json:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint --init .",
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"babel-eslint": "^10.0.1",
"exceljs": "^3.9.0",
"firebase-admin": "~5.12.0",
"firebase-functions": "^3.14.1",
"geolib": "^2.0.24",
"google-distance-matrix": "^1.1.1",
"moment": "^2.24.0",
"moment-range": "^4.0.2",
"request": "^2.88.0",
"xml-js": "^1.6.11"
},
"devDependencies": {
"eslint": "^4.12.0",
"eslint-plugin-promise": "^3.6.0"
},
"rules": {},
"engines": {
"node": "10"
},
"private": true
}
The ionic info output:
ionic info
Ionic:
Ionic CLI : 5.4.16 (/usr/local/lib/node_modules/ionic)
Ionic Framework : ionic-angular 3.9.2 #ionic/app-scripts : 3.2.3
Cordova:
Cordova CLI : 8.1.2 (cordova-lib#8.1.1) Cordova Platforms
: android 8.1.0 Cordova Plugins : cordova-plugin-ionic-webview
1.2.1, (and 29 other plugins)
Utility:
cordova-res (update available: 0.15.3) : 0.8.1 native-run
(update available: 1.4.0) : 1.3.0
System:
Android SDK Tools : 26.1.1 (/home/diego/Android/Sdk) NodeJS
: v14.17.1 (/home/diego/.nvm/versions/node/v14.17.1/bin/node) npm
: 6.14.13 OS : Linux 5.4
This is the warning:
Sometimes the functions works just fine:
I tried to initialize the configuration before the classes initialization, like this:
But give me another error:
Looks like the credentials are not 'global' in this new version. What can I do in this case? It's possible to use an 'global' instance? I appreciate any help. Thank you.
Ultimately, the problem you are experiencing is not the cause of an issue, but the symptom of improper Cloud Function lifecycle management. To understand what I mean, I'll give you a quick walkthrough of what is happening.
Contrary to popular belief, while admin.initializeApp(); is a synchronous line of code, it starts an asynchronous process that makes it ready for use later.
When you call initializeApp(), it parses the configuration object that you passed to it or the one that it assembles from the environment variables GOOGLE_APPLICATION_DEFAULT and FIREBASE_CONFIG which are prepopulated by Cloud Functions.
Once the configuration is parsed, it starts the process of negotiating its access tokens using the credentials from the configuration object in the background. If this process fails in any way, you will usually end up with a "app/invalid-credential" error that looks like:
Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: ...
When a Cloud Function completes its body of work, it is marked inactive, any resources are severely throttled and any network requests may be disconnected or blocked. The local Cloud Functions emulators don't simulate this behaviour so you should make sure you are completing all the work your Cloud Function needs to do before saying your function has completed (by chaining Promises properly or only calling res.end()/res.send()/etc once everything has finished).
A tell-tale sign that this is occurring is if you see this log message before other messages:
Function execution took ... ms, finished with ...
Importantly, when in this "inactive" state, even the requests used to negotiate the Admin SDK's access token will be interrupted which will lead to a "app/invalid-credential" error.
How to fix
You need to work through your notifyWorks function and look for any function calls that returns a Promise that you don't use await with or return to the caller.
To start out, here are a few things that I can see in the code you shared.
This Promise constructor call is confusing (as console.log() returns void (undefined)):
return new Promise(function(resolve) {
resolve(console.log("Não foi possível identificar tipo de app"))
});
should be rewritten as:
console.log("Não foi possível identificar tipo de app")
return Promise.resolve();
or (if inside a async function):
console.log("Não foi possível identificar tipo de app")
return;
Next, you make use of else statements unnecessarily. In your code, you use the following structure:
if (condition) {
return "not-exists";
} else {
if (condition) {
return "falsy-data";
} else {
return "done-stuff";
}
}
this can be flattened to just:
if (condition) {
return "not-exists";
}
if (condition) {
return "falsy-data";
}
return "done-stuff";
If you are checking change.after.exists(), there is no need to check if data is falsy if you are only checking if it is null or not.
function notifyWorks(change, context)
if (!change.after.exists()) {
console.log("Os dados foram excluídos, abortados.")
return;
}
// data won't be `null`
const data = change.after.val();
console.log("Here we continue with the logic")
// do stuff
Next, in your HTTP Event handler, you incorrectly (based on the variable names) pass through admin as context instead of context.
exports.notifyWorks = functions.database.ref('/notificationsAll/{State_}/{notificationYear}/{notificationMonth}/{notificationId}')
.onWrite((change, context) => {
return serviceController.notifyWorks(change, admin)
});
should be
exports.notifyWorks = functions.database.ref('/notificationsAll/{State_}/{notificationYear}/{notificationMonth}/{notificationId}')
.onWrite((change, context) => {
return serviceController.notifyWorks(change, context)
});
or alternatively it can be written as
exports.notifyWorks = functions.database.ref('/notificationsAll/{State_}/{notificationYear}/{notificationMonth}/{notificationId}')
.onWrite(serviceController.notifyWorks);

Firebase Functions Backend Error: Cannot Import Library

Yesterday Night I deployed my functions and the deployment was successfull without any errors.
But when i tried to execute/call the function it throwed following error and logged in firebase console :
{ Error: Failed to import the Cloud Storage client library for Node.js. Make sure to install the "#google-cloud/storage" npm package. Original error: SyntaxError: Unexpected token {
at new FirebaseError (/srv/node_modules/firebase-admin/lib/utils/error.js:43:28)
at new Storage (/srv/node_modules/firebase-admin/lib/storage/storage.js:65:19)
at /srv/node_modules/firebase-admin/lib/firebase-app.js:255:20
at FirebaseApp.ensureService_ (/srv/node_modules/firebase-admin/lib/firebase-app.js:376:23)
at FirebaseApp.storage (/srv/node_modules/firebase-admin/lib/firebase-app.js:253:21)
at FirebaseNamespace.fn (/srv/node_modules/firebase-admin/lib/firebase-namespace.js:292:45)
at Object.exports.processCard (/srv/Files/process.js:157:24)
at Busboy.bus.on (/srv/index.js:44:13)
at emitNone (events.js:106:13)
at Busboy.emit (events.js:208:7)
errorInfo:
{ code: 'storage/missing-dependencies',
message: 'Failed to import the Cloud Storage client library for Node.js. Make sure to install the "#google-cloud/storage" npm package. Original error: SyntaxError: Unexpected token {' } }
after that i tried on more time ,to check if the error has gone, but then it throwed following:
Error: The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.
at FirebaseAppError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:43:28)
at FirebaseAppError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:89:28)
at new FirebaseAppError (/srv/node_modules/firebase-admin/lib/utils/error.js:124:28)
at FirebaseNamespaceInternals.initializeApp (/srv/node_modules/firebase-admin/lib/firebase-namespace.js:68:23)
at FirebaseNamespace.initializeApp (/srv/node_modules/firebase-admin/lib/firebase-namespace.js:423:30)
at Object.exports.processCard (/srv/Files/process.js:140:15)
at Busboy.bus.on (/srv/index.js:44:13)
at emitNone (events.js:106:13)
at Busboy.emit (events.js:208:7)
at Busboy.emit (/srv/node_modules/busboy/lib/main.js:37:33)
errorInfo:
{ code: 'app/duplicate-app',
message: 'The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.' },
codePrefix: 'app' }
I executed the function locally and everything was good function executed without any errors.
The weired part is that i never actully used google-storage library i use firebase-admin sdk to generate a signedURL and this is the function of generation of singedURL:
admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "urlToDatabase"
});
function getSignedUrl(file) {
console.time('URLGenerated ');
let options = { action: 'read', expires: Date.now() + 5 * 60 * 1000 }; // 5min Expiration Time
let bucketFileName = path.basename(file);
return bucket.upload(file, { destination: `public/${bucketFileName}`})
.then(() => {
return bucket.file(`public/${bucketFileName}`).getSignedUrl(options)
.then((urls) => {
fs.unlinkSync(file);
console.timeEnd('URLGenerated ');
return urls[0];
})
.catch((e) => {
console.log('Link Generation Error' + e);
});
}).catch((e) => console.log(e));
}
In the second error it says that i've initialized admin twice but i don't think i've done that. I double checked evything and also tried emulator as i mentioned before.
and this is package.json:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "8"
},
"main": "index.js",
"dependencies": {
"#google-cloud/storage": "^5.3.0",
"busboy": "^0.3.1",
"docx": "^5.3.0",
"firebase-admin": "^9.2.0",
"firebase-functions": "^3.11.0",
"gs4fb": "^1.1.0"
},
"devDependencies": {
"firebase-functions-test": "^0.2.0"
},
"private": true
}
It is 20 Hours now and my function is down. The functions is heart of my website.
Where is the problem ?
I've updated to latest version of firebase-admin, firebase-functions library and firebaseCLI not because i needed just because it was warning me everytime.
Latest version of firebase-Admin was causing the error. Rolled back to 8.13 and everything was on track.
Seriously Never Update to latest if NOT REQUIRED.

Firebase Cloud Messaging: Internal error encountered

I'm trying to send push notifications via a Firebase Cloud Function, but getting an internal error.
Error: Internal error encountered.
at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:253:16)
at Function.FirebaseMessagingError.fromServerError (/srv/node_modules/firebase-admin/lib/utils/error.js:283:16)
at Object.createFirebaseError (/srv/node_modules/firebase-admin/lib/messaging/messaging-errors.js:34:47)
at FirebaseMessagingRequestHandler.buildSendResponse (/srv/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:119:47)
at /srv/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:94:30
at Array.map (<anonymous>)
at /srv/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:93:30
at <anonymous> errorInfo: [Object], codePrefix: 'messaging'
My function is simple enough:
sendPushNotification.js
const admin = require('firebase-admin');
const messaging = admin.messaging();
module.exports = function(title, deepLink, deviceTokens) {
var message = {
notification: {
title: title
},
data: {
deepLink: deepLink,
},
tokens: deviceTokens
};
console.log(`Sending notification ${title} with Deep Link ${deepLink} to ${deviceTokens.length} devices`);
console.log(deviceTokens);
return messaging.sendMulticast(message).then(response => {
console.log(`Success: ${response.successCount}, failure: ${response.failureCount}`);
if (response.failureCount > 0) {
console.log(response.responses)
}
});
}
The weird thing is that sometimes it does work, but maybe one in 10? The other times I get this less-than-helpful error. The APNs Authentication Key is uploaded in the Firebase Console in the project settings. The App Bundle ID is correct. I'm at a loss for what else could be going on.
(Yes, I am giving the function an array of valid deviceTokens.)
None of the other questions on StackOverflow seem to be related to this internal error, the answers on those questions don't apply here.

Cannot get req.path and req.query.abc with firebase functions

I'm trying to get the request query params and url in firebase functions.
Here is the code I'm using
firebase.json
{
"hosting": {
"public": "build",
"rewrites": [{
"source": "/getCoins",
"function": "getCoins"
}]
}
}
Using "firebase-functions": "^2.3.1" in package.json
functions/index.js
'use strict';
const functions = require('firebase-functions');
exports.getCoins = functions.https.onRequest((req, res) => {
console.log(req.query); // [object Object]
console.log(req.query.repeat); // empty
console.log(req.url); // '/'
console.log(req.originalUrl); // '/'
res.sendStatus(200);
});
Started the firebase functions in my windows command prompt using firebase serve --only functions. As it starts serving data from http://localhost:5000, I'm trying to request http://localhost:5000/coins-app/us-central1/getCoins?repeat=4
I'm not getting any error in the command prompt, but could only see the commented lines from the above functions/index.js code.
You should run firebase serve and request to http://localhost:5000/getCoins?repeat=4 .
functions.https.onRequest() can't accept query string parameter directly.

Firebase functions :ApiError: Not Found at Object.parseHttpRespBod : when removing from firebase storage

I'm trying to remove an item from my firebase storage by firebase cloud functions.
But its giving me this error..
Error { ApiError: Not Found
at Object.parseHttpRespBody (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/common/src/util.js:193:30)
at Object.handleResp (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/common/src/util.js:131:18)
at /user_code/node_modules/firebase-admin/node_modules/#google-cloud/common/src/util.js:496:12
at Request.onResponse [as _callback] (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/common/node_modules/retry-request/index.js:198:7)
at Request.self.callback (/user_code/node_modules/firebase-admin/node_modules/request/request.js:185:22)
at emitTwo (events.js:106:13)
at Request.emit (events.js:191:7)
at Request.<anonymous> (/user_code/node_modules/firebase-admin/node_modules/request/request.js:1161:10)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
code: 404,
errors: [ { domain: 'global', reason: 'notFound', message: 'Not Found' } ],
response: undefined,
message: 'Not Found' }
And this is my code :
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var db = admin.firestore();
var storage = admin.storage().bucket('visa_cop');
exports.deletingVisaCop = functions.firestore.document('users/{user_Id}/info/visa_cop').onUpdate((change,context) =>{
var data = change.after.data().checked;
if(data === true)
{
return storage.delete().then(function(data) {
return console.log("DataIs",data);
}).catch(function(error){
return console.log("Error",error);
});
} else
{
}
});
And I added for Google APIs Service Agent and App Engine default service account storage admin roles from the I am & admin page.
Thank You.
the problem is here:
functions.firestore.document('users/{user_Id}/info/visa_cop').onUpdate((change,context)
at the moment, the function listens to a document called "visa_cop" in the folder "info". you need to add the token at the end, to tell the function to listen to update of any file in this folder (or you can specify a file if needed).
Just append e.g. /{visaId} after visa_cop, like so:
functions.firestore.document('users/{user_Id}/info/visa_cop/{visaId}').onUpdate((change,context)
Ps. "visaId" can be anything, however it must match the Document Path that you define at function deploy.
in your example, the function listens to any doc in "visa_cop" folder, so if you use:
Console:
Trigger is "Cloud Firestore"
Event Type is "update"
Document Path is "students/{studentId}/visa_cop/{visaId}"
CLI:
gcloud functions deploy [FUNCTION_NAME] \
--runtime [RUNTIME] \
--trigger-event providers/cloud.firestore/eventTypes/document.update \
--trigger-resource "projects/[PROJECT_ID]/databases/(default)/documents/users/{userId}/info/visa_cop/{visaId}"

Resources