Cannot Deploy cloud functions - firebase

I tried deploying a sample function,but it shows an error
my code for the function is below
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// Listens for new messages added to /message/:pushId/original and creates an
// uppercase version of the message to /message/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/message/{pushId}/original')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const original = snapshot.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return snapshot.ref.parent.child('uppercase').set(uppercase);
});
my firebase.json file
{
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint"
],
"source": "functions"
}
}

Related

NextJS Firebase onRequest Function Rewrites

I am working on a project using the Vercel Next.js firebase-with-hosting template found here.
The template uses a Firebase Function to allow for Firebase Hosting. I am trying to add additional Firebase onRequest functions so that I can send request to the server.
I've added the printTest function and set the rewrites in the firebase.json file but keep getting 404 errors. I've successfully implemented this before with Express and without NextJS. I think I have an issue with my rewrites.
I've also tested onCallable, and pubSub functions and these worked great. My guess is I have a rewrite issue. Any help with understanding why this does now work would be greatly appreciated.
const { join } = require('path'); // From NextJS Vercel Base Build
const { default: next } = require('next'); // From NextJS Vercel Base Build
const isDev = process.env.NODE_ENV !== 'production'; // From NextJS Vercel Base Build
const nextjsDistDir = join('src', require('./src/next.config.js').distDir); // From NextJS Vercel Base Build
const admin = require('firebase-admin'); // Firebase Admin SDK for NodeJS.
const functions = require('firebase-functions'); // For NextJS + Firebase Functions + Firebase Hosting.
const serviceAccount = require('./firebaesAdminServiceAccountKey.json'); // Service account key for Firebase Admin SDK.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++ Firebase Admin Initialization ++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++ Firebase Firestore Initialization ++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
const dba = admin.firestore();
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++ Nextjs Configuration ++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
const nextjsServer = next({
dev: isDev,
conf: {
distDir: nextjsDistDir,
},
});
const nextjsHandle = nextjsServer.getRequestHandler();
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++ Cloud Functions ++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Nextjs Cloud Function to allow for Firebase Hosting.
exports.nextjsFunc = functions.https.onRequest((req, res) => {
return nextjsServer.prepare().then(() => nextjsHandle(req, res));
});
exports.printTest = functions.https.onRequest((req, res) => {
console.log('THIS WORKS!');
res.status(200).send();
});
Here is the firebase.json file with the rewrite.
{
"hosting": {
"public": "public",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"function": "nextjsFunc"
},
{
"source": "/printTest",
"function": "printTest"
}
]
},
"functions": {
"source": ".",
"predeploy": [
"npm --prefix \"$PROJECT_DIR\" install",
"npm --prefix \"$PROJECT_DIR\" run build"
],
"runtime": "nodejs10"
}
}
The URL pattern ** means that all of your requests are sent to nextjsFunc() and that includes /printTest. Since that is the first rewrite config rule, anything below is ignored.
From the doc, here's an important note:
Hosting applies the rewrite defined by the first rule with a URL pattern that matches the requested path. So, you need to deliberately order the rules within the rewrites attribute.
Fix the issue by changing the order and putting the least-strict rule at the end:
"rewrites": [
{
"source": "/printTest",
"function": "printTest"
},
{
"source": "**",
"function": "nextjsFunc"
}
]

'functions' is assigned a value but never used

I am getting an error in terminal when trying to run 'firebase deploy'. This brings the error
1:7 error 'functions' is assigned a value but never used no-unused-vars
here is my index.js file
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// Take the text parameter passed to this HTTP endpoint and insert it into
// Firestore under the path /messages/:documentId/original
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the text parameter.
const original = req.query.text;
// Push the new message into Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Firestore.
const original = snap.data().original;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to Firestore.
// Setting an 'uppercase' field in Firestore document returns a Promise.
return snap.ref.set({uppercase}, {merge: true});
});
this is directly from the firebase functions documentation
Your index file is outside the functions folder.
In my case i just added warning instead of error in the rules at the
.eslintrc.js file
rules: {
"no-unused-vars": "warn"
},
You can also wrap any block of code with eslint specific comments to disable a rule for that block:
/* eslint-disable no-unused-vars */
(....Your Code....)
/* eslint-enable no-unused-vars */
this way you need not to comment your pre-deploy script checks
and you can disable the enlint properties for the selected block
In firebase.json, delete these 3 lines :
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint"
]
In functions folder/package.json, delete these lines :
"devDependencies": {
"eslint": "^8.9.0", // delete
"eslint-config-google": "^0.14.0", // delete
}
"scripts": {
"lint": "eslint .", // delete
},
Delete node_modules in function folder then reinstall all with npm install (you must be in the function folder)
Then : firebase deploy will work fine

How to write to a cloud storage bucket with a firebase cloud function triggered from firestore?

To make sure this is as clean and isolated as possible, I created a new test project with nothing in it other than these steps.
I then enabled cloud firestore in the console. I created a new collection called "blarg" (great name, eh!). I added a document to it, and used the auto-id, then added a field named "humpty" with the string dumpty. It looks like this in the console:
I created a directory called signatures in the storage section of the firebase console. I want to write a file into this directory when my function (below) triggers.
I then added a cloud function with the following code. It shows up (I assume) correctly in the functions section, with the name testItOut and triggering on the event document.update for /blarg/{eventId}.:
const functions = require('firebase-functions');
const os = require('os');
const fs = require('fs');
const path = require('path');
require('firebase-admin').initializeApp();
exports.testItOut = functions.firestore
.document('blarg/{docId}')
.onUpdate((change, context) => {
console.log( "Inside #testItOut" );
const projectId = 'testing-60477';
const Storage = require('#google-cloud/storage');
const storage = new Storage({
projectId,
});
let bucketName = 'signatures';
let fileName = 'temp.txt';
const tempFilePath = path.join(os.tmpdir(), fileName);
console.log( `Writing out to ${tempFilePath}` );
fs.writeFileSync(tempFilePath, "something!" );
return storage
.bucket( bucketName )
.upload( tempFilePath )
.then( () => fs.unlinkSync(tempFilePath) )
.catch(err => console.error('ERROR inside upload: ', err) );
});
The package.json looks like this:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"#google-cloud/storage": "^1.7.0",
"firebase-admin": "~5.12.1",
"firebase-functions": "^1.0.3"
},
"devDependencies": {
"eslint": "^4.12.0",
"eslint-plugin-promise": "^3.6.0"
},
"private": true
}
If I change the value of the key "humpty" I see the function invocation. But, I get the error inside the logs.
ERROR inside upload: { ApiError: testing-60477#appspot.gserviceaccount.com does not have storage.objects.create access to signatures/temp.txt.
at Object.parseHttpRespBody (/user_code/node_modules/#google-cloud/storage/node_modules/#google-cloud/common/src/util.js:193:30)
at Object.handleResp (/user_code/node_modules/#google-cloud/storage/node_modules/#google-cloud/common/src/util.js:131:18)
...
This is as simple as it can get, I'd assume. What am I doing wrong? I thought calling initializeApp() gave my function rights to write to the storage bucket for the account automatically?
The only strange error I see is that billing is not setup. I thought this was necessary only if you use external APIs. Is Google Storage an "external API?" The log message does not indicate that's the issue.
The issue was that I mistakenly thought the call to bucket was to set the subdirectory inside the bucket. Instead of bucket('signatures') I should have made it an empty call like bucket() and then provided an options parameter (like { destination: '/signatures/temp.txt' }) for the upload call:
const functions = require('firebase-functions');
const os = require('os');
const fs = require('fs');
const path = require('path');
const admin = require('firebase-admin');
admin.initializeApp();
exports.testItOut = functions.firestore
.document('blarg/{docId}')
.onUpdate((change, context) => {
console.log( "Inside #testItOut" );
const storage = admin.storage()
let fileName = 'temp.txt';
let destination = '/signatures/temp.txt';
const tempFilePath = path.join(os.tmpdir(), fileName);
console.log( `Writing out to ${tempFilePath}` );
fs.writeFileSync(tempFilePath, "something!" );
return storage
.bucket()
.upload( tempFilePath, { destination } )
.then( () => fs.unlinkSync(tempFilePath) )
.catch(err => console.error('ERROR inside upload: ', err) );
});
I got confused because I saw lots of examples in firebase docs where there are calls to bucket("albums") (like this: https://cloud.google.com/nodejs/docs/reference/storage/1.6.x/Bucket#getFiles) and assumed this meant the bucket can be used to set a subdirectory for uploads. It's obvious to me now the difference, but I was thrown off because the calls to bucket did not look like bucketname-12345.appspot.com as I see in the firebase storage console.
Don't bother trying to use the Google Cloud Storage SDK like this:
const Storage = require('#google-cloud/storage');
const storage = new Storage({
projectId,
});
Instead, just use the Admin SDK after you initialize it. The Admin SDK wraps the Google Cloud Storage SDK. When you initialize admin, you are also implicitly initializing all of the components it wraps.
const admin = require('firebase-admin')
admin.initializeApp()
const storage = admin.storage()
// Now use storage just like you would use #google-cloud/storage
The default service account that's used when you init admin with no args like this should have write access to your project's default storage bucket.

Firebase app not accessing functions (e.g. firebase.auth())

I'm having this problem that I've spent hours looking into but can't seem to figure out. I'm trying to use Firebase with Node.js, but am unable to access the different Firebase functions (e.g. firebase.auth() and firebase.firestore()). However, when I initialize via the admin SDK, those end up working. Below is my simple code as well as the error:
const functions = require('firebase-functions');
const firebase = require('firebase');
const app = express();
firebase.initializeApp(functions.config().firebase);
const auth = firebase.auth();
const db = firebase.firestore();
And the resulting error: TypeError: firebase.auth is not a function
Note I also had a few lines initializing express and handlebars but doubt those would have had any impact.
I've also tried including "require("firebase/auth")" but that hasn't worked. Below is also my package.json, specifically the modules I have installed:
"dependencies": {
"consolidate": "^0.15.1",
"express": "^4.16.3",
"firebase": "^5.0.4",
"firebase-admin": "~5.12.1",
"firebase-functions": "^1.0.4",
"firebase-tools": "^3.18.6",
"firebaseauth": "^1.0.0",
"handlebars": "^4.0.11"
},
Any help please? I'm a bit of a novice web developer so any advice would be much appreciated. Thank you!
Cloud Functions triggers have a format like this:
exports.myFunctionName = functions.firestore
.document('users/marie').onWrite((change, context) => {
// ... Your code here
});
You cannot access the client-side SDKs from inside a Cloud Function. Instead, if there are features you want to access inside a Cloud Function, you can use the Admin SDKs.
var admin = require('firebase-admin');
var app = admin.initializeApp();
Short answer: Use the admin SDK, that's what it's for - Cloud Functions to interact with the rest of your backend.
Longer answer:
To help you along, here is an example of a working index.js file for a Firebase Cloud Function that accesses both the database (not firestore) and the auth portion of Firebase... with some comments to provide context. It's an API endpoint that accepts a user's token, verifies that the user is an 'admin' in my system, and then creates a new user account before responding with the new UID.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.createUser = functions.https.onRequest( (req, res) => {
// Look at the accessToken provided in the request, and have Firebase verify whether it's valid or not
admin.auth().verifyIdToken(req.body.accessToken).then(decodedIdToken => {
// Token was valid, pull the UID out of the token for the user making this request
let requestorUid = decodedIdToken.uid;
// Make a query to the database to see what that user's role is
admin.database().ref(`userBasicInfo/${requestorUid}/role`).once('value').then( dataSnapshot => {
if (dataSnapshot.val() === 'admin') {
// If their role is "admin" then let's create a user!
let userEmail = `${req.body.newuserid}#company.org`;
admin.auth().createUser({
email: userEmail,
password: req.body.password
}).then(userRecord => {
// See the UserRecord reference doc for the contents of userRecord.
res.send({uid: userRecord.uid});
}).catch( error => {
console.log("Error creating user:");
console.log(error)
res.send(false);
});
} else {
console.log('Checked the database for the UID provided in the access token, role was not "admin".');
res.send(false);
}
}).catch( err => {
console.log('Error finding user in database:');
console.log(err);
res.send(false);
})
}).catch( error => {
console.error('Error while verifying Firebase ID token:');
console.log(error);
res.send(false);
});
});
And the package.json would be:
{
"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": {
"firebase-admin": "^5.11.0",
"firebase-functions": "^1.0.2"
},
"private": true
}

Functions do not appear/run on Firebase Cloud Functions

I can successfully deploy the HelloWord application fine as per the video guide here.
But now I was following the instructions here in order to link Algolia and Firebase with Cloud Functions, I can run the code on my computer and it updates, but somehow it does not auto-update when I make changes on Firebase.
Secondly, when I deploy the application I get the "deploy complete" message but nothing appears in Firebase>>Functions and Algolia is not updated when I make changes to Firebase.
index.js code
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const firebase = require('firebase');
// load values from the .env file in this directory into process.env
dotenv.load();
// configure firebase
firebase.initializeApp({
databaseURL: process.env.FIREBASE_DATABASE_URL,
});
const database = firebase.database();
// configure algolia
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);
//synchronize firebase database with algolia index
const contactsRef = database.ref('/contactDetail/botswana');
contactsRef.on('child_added', addOrUpdateIndexRecord);
contactsRef.on('child_changed', addOrUpdateIndexRecord);
contactsRef.on('child_removed', deleteIndexRecord);
function addOrUpdateIndexRecord(contact) {
// Get Firebase object
const record = contact.val();
// Specify Algolia's objectID using the Firebase object key
record.objectID = contact.key;
// Add or update object
index
.saveObject(record)
.then(() => {
console.log('Firebase object indexed in Algolia', record.objectID);
})
.catch(error => {
console.error('Error when indexing contact into Algolia', error);
process.exit(1);
});
}
function deleteIndexRecord(contact) {
// Get Algolia's objectID from the Firebase object key
const objectID = contact.key;
// Remove the object from Algolia
index
.deleteObject(objectID)
.then(() => {
console.log('Firebase object deleted from Algolia', objectID);
})
.catch(error => {
console.error('Error when deleting contact from Algolia', error);
process.exit(1);
});
}
package.json code
{
"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": {
"algoliasearch": "^3.24.9",
"dotenv": "^4.0.0",
"firebase": "^4.8.1",
"firebase-admin": "~5.4.2",
"firebase-functions": "^0.7.1"
},
"private": true
}
Did you check the logs in the Functions section ? In order to see changes in Algolia, you should first see the function appearing in your firebase console.
I had to make few changes to the code before it runs
functions needed to be exported using "exports"
functions does not use .on() as listener instead use e.g. onCreate() with one parameter, more here. See below
exports.childAdded=contactsRef.onCreate(addOrUpdateIndexRecord);
Solution:
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const functions = require('firebase-functions');
// load values from the .env file in this directory into process.env
dotenv.load();
// configure algolia
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);
//synchronize firebase database with algolia index
const contactsRef = functions.database.ref('/contactDetail/locationA/{contactID}');
exports.childAdded=contactsRef.onCreate(addOrUpdateIndexRecord);
exports.childChanged=contactsRef.onUpdate(addOrUpdateIndexRecord);
exports.childRemoved=contactsRef.onDelete(deleteIndexRecord);
function addOrUpdateIndexRecord(contact) {
console.log("The function addOrUpdateIndexRecord is running!");
// Get Firebase object
const record = contact.data.val();
// Specify Algolia's objectID using the Firebase object key
record.objectID = contact.data.key;
// Add or update object
return index
.saveObject(record)
.then(() => {
console.log('Firebase object indexed in Algolia', record.objectID);
})
.catch(error => {
console.error('Error when indexing contact into Algolia', error);
process.exit(1);
});
}
function deleteIndexRecord(contact) {
// Get Algolia's objectID from the Firebase object key
const objectID = contact.data.key;
// Remove the object from Algolia
return index
.deleteObject(objectID)
.then(() => {
console.log('Firebase object deleted from Algolia', objectID);
})
.catch(error => {
console.error('Error when deleting contact from Algolia', error);
process.exit(1);
});
}

Resources