Firebase Functions Backend Error: Cannot Import Library - firebase

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.

Related

Why can't I access .env variables when unit testing Firebase (Cloud) Functions with firebase-functions-test, mocha, and Emulator Suite?

I have a Firebase Project using Functions, Storage, and Auth that I would like to do some rudimentary unit testing using mocha and the Emulator Suite (already working for local development).
I've read the documentation front to back several times and I don't seem to understand how to set this up. My specific challenge currently is that I cannot seem to get a simple function that returns a defined environment variable to return properly due to the environment variables not loading.
My setup is as follows:
functions/index.js
const functions = require("firebase-functions")
const admin = require('firebase-admin')
admin.initializeApp()
const { getFunctions } = require('firebase-admin/functions')
...
exports.requiresEnvVariable = functions.https.onCall((data, context) => {
return process.env.MYENV_VARIABLE || ':('
})
functions/tests/test.spec.js
const { expect } = require("chai")
const sinon = require("sinon")
const admin = require("firebase-admin")
console.log(process.env.GCLOUD_PROJECT)
const test = require("firebase-functions-test")({
projectId: process.env.GCLOUD_PROJECT
}, 'serviceAccount.json')
// I've played with calling this here and stubbing, asmentioned in the docs. Nothing seems to work.
//admin.initializeApp()
//adminInitStub = sinon.stub(admin, 'initializeApp')
const myFunctions = require("../index")
describe("Unit tests", () => {
it("A good and cool test description", async () => {
const wrapped = test.wrap(myFunctions.getTToken)
const data = {}
const result = await wrapped(data)
expect('yayitworked').to.eql(result)
})
})
functions/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",
"tests": "mocha --exit tests/*.spec.js"
},
"engines": {
"node": "18"
},
"main": "index.js",
"dependencies": {
"axios": "^1.1.3",
"cors": "^2.8.5",
"firebase-admin": "^11.4.1",
"firebase-functions": "^4.1.1",
},
"devDependencies": {
"chai": "^4.3.7",
"firebase-functions-test": "^3.0.0",
"mocha": "^10.2.0",
"sinon": "^15.0.1"
},
"private": true
}
functions/.env
MYENV_VARIABLE=yayitworked
And I run from the functions folder:
firebase emulators:exec 'npm run tests'
giving AssertionError: expected ':(' to deeply equal 'yayitworked'
Why can I not access the environment variables?
Short answer is that firebase-functions-test package doesn't load environment variables stored in .env file. You'd need to load the .env file manually as part of your mocha test setup by using packages like https://www.npmjs.com/package/dotenv.
Longer answer is that the setup described in the question - combining the Firebase Emulator and firebase-functions-test package - probably isn't working as you expect them to work.
When running
firebase emulators:exec 'npm run tests'
we get 2 separate instances of requiresEnvVariable function:
One spun up by the Firebase Emulator. This function would have loaded the .env variables because Firebase Emulator includes logic to load environment variables in appropriate .env files before spinning up a function.
One spun up by your mocha test. This "function" is really a plain JS function and will inherit environment variables in the parent process.
Tests written using firebase-functions-test are testing (2) function, not (1). In order to invoke the function (1) spun up by the Firebase Emulator, you'd need to make HTTP request to the URL exposed by the Firebase Emulator (see sample code)
(Side note: firebase-functions-test package was released years before Firebase Emulator so isn't "Firebase emulator-aware" so to speak.)

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);

Not able to integrate DialogFlow with Firestore

I want to use Firestore in my DialogFlow bot but it shows certain errors.
This is my index.js code:
function writeToDb () {
console.log(`Inside writeToDb`);
// Get parameter from Dialogflow with the string to add to the database
const databaseEntry = {
'eMail':'shashank#gmail.com'
};
console.log(databaseEntry);
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
const dialogflowAgentRef = db.collection('dialogflow').doc('agent');
return db.runTransaction(t => {
t.set(dialogflowAgentRef, {entry: databaseEntry});
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`Wrote "${databaseEntry}" to the Firestore database.`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
This is my package.json:
{
"name": "dialogflowFirebaseFulfillment",
"description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
"version": "0.0.1",
"private": true,
"license": "Apache Version 2.0",
"author": "Google Inc.",
"engines": {
"node": "8"
},
"scripts": {
"start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
"deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
},
"dependencies": {
"actions-on-google": "^2.2.0",
"firebase-functions": "^2.0.2",
"dialogflow": "^0.6.0",
"dialogflow-fulfillment": "^0.5.0",
"i18n" : "^0.8.4",
"#google-cloud/firestore": "^0.16.1",
"firebase-admin": "^6.0.0"
}
}
And these are the errors I am getting :
Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail
Billing account not configured. External network is not accessible and quotas are severely limited.
Configure billing account to remove these restrictions
I have already made a 'dialogflow' collection and an 'agent' document in it in my firestore.
Neither of these are errors, and shouldn't be preventing your webhook fulfillment from working.
The "estimating Firebase Config" error just says that you haven't explicitly set a Firebase configuration, so it is making some assumptions based on the environment - most notably, that you're working in a Cloud Function, so it is assuming the same project and access to other project default settings. If you're using the admin to access the database in the same project, this should be fine.
The message about "Billing account not configured" means just that - you're using the Spark Plan by default which has limitations on how many calls it can make per day and can only access Google's network. Since it looks like your code is just using Firestore, this shouldn't be a problem, but if you needed to access outside the network (or when your usage gets very high), you would need to upgrade to the Blaze Plan, which includes the free tier of the Spark Plan, but allows external network access.

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

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.

Error Deploying Firestore Function with a space in the name of a collection

I'm trying to deploy a cloud function that updates an Algolia Search index whenever a new document is created in my Firestore. Everything seems to start well but then at the very end, it errors out. I've been through the docs and it feels like my syntax is correct so I'm not sure what to do at this point. The Cloud Functions log don't point to anything and just echo the error that I received below
Error Message:
=== Deploying to 'test-ffdbb'...
i deploying functions
i functions: ensuring necessary APIs are enabled...
i runtimeconfig: ensuring necessary APIs are enabled...
+ runtimeconfig: all necessary APIs are enabled
+ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (1.25 KB) for uploading
+ functions: functions folder uploaded successfully
i functions: updating function onProductCreated...
! functions[onProductCreated]: Deployment error.
Failed to configure trigger providers/cloud.firestore/eventTypes/document.create#firestore.googleapis.com (onProductCreated)
My Code:
const functions = require('firebase-functions');
const algoliasearch = require('algoliasearch');
// App ID and API Key are stored in functions config variables
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const ALGOLIA_INDEX_NAME = "Firestore";
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
// Update the search index every time a blog post is written.
exports.onProductCreated = functions.firestore
.document("Product Catalog/{id}")
.onCreate(event => {
const product = event.data.data();
product.objectID = event.params.postId;
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.saveObject(product);
});
package.json:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase experimental:functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"#google-cloud/firestore": "^0.8.2",
"algoliasearch": "^3.24.5",
"firebase-admin": "~5.4.2",
"firebase-functions": "^0.7.1"
},
"private": true
}
The problem appears to be the space in path segment Product Catalog. When I remove it, the function deploys successfully:
exports.onProductCreated = functions.firestore
.document("ProductCatalog/{id}") // <== space removed
.onCreate(event => {...});
Also note that the wildcard in your path is id and in the code you reference postId:
product.objectID = event.params.postId;
I believe this is a bug in the Firebase CLI deployment. Spaces in collection ids and document ids are perfectly valid in Firestore, and the deploy shouldn't fail if there's a space present. I've logged a bug internally with the Firebase team on this, and you should feel free to log anything that seems like a bug yourself at this page.

Resources