Issue
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.3Cordova:
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.
Solution
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);
Answered By - samthecodingman




0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.