Firestore cloud functions with version 9 modular - firebase

I am trying to trigger cloud functions from firestore events (onWrite) but I don't find the correct way to implement it with the version 9 modular that I am using for this project. The whole documentation is with version 8 (named space).
Here is what I am trying to do (version 8) :
export const documentWriteListener = functions.firestore
.document('collection/{documentUid}')
.onWrite((change, context) => {
if (!change.before.exists) {
// New document Created : add one to count
db.doc(docRef).update({ numberOfDocs: FieldValue.increment(1) });
} else if (change.before.exists && change.after.exists) {
// Updating existing document : Do nothing
} else if (!change.after.exists) {
// Deleting document : subtract one from count
db.doc(docRef).update({ numberOfDocs: FieldValue.increment(-1) });
}
return;
});
Here is my version 9 firebase initialization file :
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { getFunctions } from "firebase/functions"
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
const functions = getFunctions(app);
export { db, auth, functions }
and here is what I've tried for the cloud function (that I put in an independent file actionsCount.js) :
import { db, functions } from '../../firebase/initFirebase';
import { updateDoc, doc } from "firebase/firestore";
import * as functions from 'firebase-functions';
export const documentWriteListeners = functions.firestore
.document('actions/{documentUid}')
.onWrite((change, context) => {
const actionsCounter = doc(db, "actionsCount", "counter")
if (!change.before.exists()) {
// New document Created : add one to count
await updateDoc(actionsCounter, { numberOfDocs: FieldValue.increment(1) });
} else if (change.before.exists() && change.after.exists()) {
// Updating existing document : Do nothing
} else if (!change.after.exists()) {
// Deleting document : subtract one from count
await updateDoc(actionsCounter, { numberOfDocs: FieldValue.increment(-1) });
}
return;
})
When I deploy using firebase deploy --only functions, I get the error : Cannot understand what targets to deploy/serve. No targets in firebase.json match '--only functions'.
Should I put the function somewhere else ? Function is wrong ?
Thanks a lot for your help !

You need to initialize Firebase Functions with the command firebase init. When you select functions and proceed with the setup, it'll create a new functions directory containing an index.js/ts file by default. That's where you add your Cloud Functions.
The firebase-functions SDK is not meant to be used on client side.
The directory structure would look like this:
firebase deploy --only functions will deploy the functions.

Finally fixed this! Half a day haha
So I obviously ran the functions firebase init as suggested by Dharmaraj.
As per the modular (version 9), it looks like we can do it. So I basically tried to initialize the firebase sdk in the functions/index.js file with the version 8 (named-space) methods and it worked.
Here is the code :
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const fieldValue = admin.firestore.FieldValue;
// Updating the Actions document count
exports.documentWriteListeners = functions.firestore.document('actions/{documentUid}').onWrite((change, context) => {
if (!change.before.exists) {
// New document Created : add one to count
db.doc('actionsCount/counter').update({ numberOfDocs: fieldValue.increment(1) });
} else if (change.before.exists && change.after.exists) {
// Updating existing document : Do nothing
} else if (!change.after.exists) {
// Deleting document : subtract one from count
db.doc('actionsCount/counter').update({ numberOfDocs: fieldValue.increment(-1) });
}
return;
})

Related

cant able to retrieve data from firestore after ejecting expo

im looking to retrieve data from the firestore.first it worked well when i ejected from expo i cant able to retrieve data from the servers. when i read some documentation they suggested to use
let myApp = initializeApp(firebaseConfig);
myApp.firestore().settings({ experimentsalForceLongPolling: true });
const firestore = getFirestore(myApp);
but im ettin error myApp.firestore() is not a function.
here is my code:
import { initializeApp } from "firebase/app";
const firebaseConfig = {
};
let myApp = initializeApp(firebaseConfig);
myApp.firestore().settings({ experimentsalForceLongPolling: true });
const firestore = getFirestore(myApp);
im thinking there is a problem in import can someone help me please
The settings to enable experimentalAutoDetectLongPolling should be enabled through initializeFirestore there you will find settings: FirestoreSettings argument refer FirestoreSettings which you can set as follows :
import { initializeApp } from "firebase/app";
import { initializeFirestore, getFirestore } from "firebase/firestore";
const firebaseConfig = {
};
let app = initializeApp(firebaseConfig);
let db;
try {
db = initializeFirestore(app, { experimentalAutoDetectLongPolling: true }); // <= settings
} catch (e) {
if (e.code === 'failed-precondition') {
// Multiple app instances detected
db = getFirestore(app);
} else {
throw e;
}
}
And as initializeFirestore is equivalent to getFirestore method so no need to reinitialize firestore again as shown in docs:
Initializes a new instance of Firestore with the provided settings. Can only be called before any other function, including (getFirestore:1). If the custom settings are empty, this function is equivalent to calling (getFirestore:1).

Is there any downsides of requiring the files inside firebase cloud functions and initializingApp inside each of them?

I am using the cloud functions as following:
index.js (current)
exports.addVehicle = functions.https.onRequest(async (req, res) => {
cors(req, res, async () => {
await require("./src/vehicles/addVehicle").addVehicle(req, res);
});
});
addVehicle.js (current)
const admin = require("firebase-admin");
const Vehicle = require("../../models/Vehicle");
const app = admin.initializeApp();
const db = app.firestore();
exports.addVehicle = async (req, res) => {
try{
const vehicleInfo = new Vehicle(req.body);
const addedVehicle = await db.collection("vehicles").add(vehicleInfo);
console.log(addedVehicle);
res.json({data: "Succesfully added vehicle"});
}
catch(err){
if(err){
res.json(err);
}
}
};
But before I was using it like
index.js (previous)
const app = admin.initializeApp();
const db = app.firestore();
const { addVehicle } = require("./src/vehicles/addVehicle");
exports.addVehicle = functions.https.onRequest(async (req, res) => {
cors(req, res, async () => {
await addVehicle(req, res, db);
});
});
addVehicle.js (previous)
exports.addVehicle = async (req, res) => {
try{
const vehicleInfo = new Vehicle(req.body);
const addedVehicle = await db.collection("vehicles").add(vehicleInfo);
console.log(addedVehicle);
res.json({data: "Succesfully added vehicle"});
}
catch(err){
if(err){
res.json(err);
}
}
};
To summarize,previously, I was initializing the app inside index.js and passing the db as parameter to functions that I directly require and invoke. But now, I am not initializing the app in the index file, rather I initializeApp in every seperate cloud function itself, and also I do not require the files beforehand and invoke but rather both require and invoke them inside (firebase does not allow to initializeApp in different files with the previous method but when i require them inside it allows me to initializeApp in multiple different files).
The current version seems much more organized and clean, but my question is that if there is any other down or upsides of calling initializeApp multiple times across cloud functions ? Also I wonder why before it was not allowing to initializeApp in different files but now when I require it inside function directly, it allows?
You only need to call initializeApp one time globally, and then it will be initialized for all of your functions. So this is the accepted way of doing so:
// index.js
const admin = require("firebase-admin")
// Other imports
admin.initializeApp()
// Your exports
With this, you can now require("firebase-admin") in other files and your default app will already be initialized. And you don't have to pass db around. So your old addVehicle.js would be generally accepted and look like this:
// addVehicle.js
const admin = require("firebase-admin")
export default async function addVehicle(req, res) {
const db = admin.firestore()
try{
const vehicleInfo = new Vehicle(req.body);
const addedVehicle = await db.collection("vehicles").add(vehicleInfo);
console.log(addedVehicle);
res.json({data: "Succesfully added vehicle"});
}
catch(err){
if(err){
res.json(err);
}
}
}
And then back in your index, you could import addVehicle.js like before and export it as a https function.
// index.js
const admin = require("firebase-admin")
const addVehicle = require("./vehicles/addVehicle")
// Other imports
admin.initializeApp()
exports.addVehicle = functions.https.onRequest(addVehicle);
// Other exports
The downside to how you're doing it not in the new way - requiring the files in the body of the function - is that you're making the function work harder by requiring the files every time the function is invoked instead of one time when your cloud function runs. Because your function's (and firebase's) dependency on your addVehicle file never changes, you only need to load it once at the beginning of your functions index.
There's a time and place to import inline like you're doing, but there's no benefit and some performance issues doing it how you are doing it in the new way.

Read from firebase storage and write to firestore using firebase functions

I had tried this typescript code 👇
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import serviceAccount from "/Users/300041370/Downloads/serviceKey.json";
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
const buckObj = functions.storage.bucket("myBucket").object();
export const onWikiWrite = buckObj.onFinalize(async (object) => {
const filePath = object.name ?? "test.json";
const bucket = admin.storage().bucket("myBucket");
bucket.file(filePath).download().then((data) => {
const contents = data[0];
data = {"key": "value"};
const doc = admin.firestore().collection("myCollection").doc();
doc.set(data);
});
});
but this gave me following error
"status":{"code":7,"message":"Insufficient permissions to (re)configure a trigger (permission denied for bucket myBucket). Please, give owner permissions to the editor role of the bucket and try again.
I had asked this question here but it got closed as duplicate of this question. It basically said, storage.bucket("myBucket") feature is not supported and that I'll have to instead use match for limiting this operation to files in this specific bucket/folder. Hence, I tried this 👇
const buckObj = functions.storage.object();
export const onWikiWrite = buckObj.onFinalize(async (object) => {
if (object.name.match(/myBucket\//)) {
const fileBucket = object.bucket;
const filePath = object.name;
const bucket = admin.storage().bucket(fileBucket);
bucket.file(filePath).download().then((data) => {
const contents = data[0];
const doc = admin.firestore().collection("myCollection").doc();
const data = {content: contents}
doc.set(data);
});
}
});
I am still facing the same issue. I'll repeat that here:
"status":{"code":7,"message":"Insufficient permissions to (re)configure a trigger (permission denied for bucket myBucket). Please, give owner permissions to the editor role of the bucket and try again.
Since version 1.0 of the Firebase SDK for Cloud Functions, firebase-admin shall be initialized without any parameters within the Cloud Functions runtime.
The following should work (I've removed the check on filePath):
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
export const onWikiWrite = functions.storage
.object()
.onFinalize(async (object) => {
const fileBucket = object.bucket;
const filePath = object.name;
const bucket = admin.storage().bucket(fileBucket);
return bucket
.file(filePath)
.download()
.then((data) => {
const contents = data[0];
return admin
.firestore()
.collection('myCollection')
.add({ content: contents });
});
});
Note that we return the chain of promises returned by the asynchronous Firebase methods. It is key, in a Cloud Function which performs asynchronous processing (also known as "background functions") to return a JavaScript promise when all the asynchronous processing is complete.
We also use the add() method instead of doing doc().set().
Finally, when checking the value of the filePath, be aware of the fact that there is actually no concept of folder or subdirectory in Cloud Storage (See this answer).

Reference error firestore is not defined in firebase cloud function when using firebase admin sdk

I have a cloud function in which I am listening to the onCreate event of a firestore collection. When the cloud function is triggered I am getting a reference error. Below are the cloud function code and the error.
const functions = require('firebase-functions')
const serviceAccount = require('./serviceAccountKey.json')
const admin = require('firebase-admin')
const { firestore } = require('firebase-admin')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://my-app-id.firebaseio.com', // Here I have replaced my real id for privacy
})
const db = admin.firestore()
exports.handleDiscussionReplyAdded = functions.firestore
.document('/discussions/{discussionId}/replies/{replyId}')
.onCreate(async (snap, context) => {
try {
// 1. Creating a ref to parent chat doc
const discussionRef = db
.collection('discussions')
.doc(context.params.discussionId)
// 2. Updating the data
await discussionRef.update({
totalReplies: firestore.FieldValue.increment(1),
})
// Return true if everything went fine
return true
} catch (error) {
// Return error if something went wrong
functions.logger.error(`Error: ${JSON.stringify(error)}`)
return error
}
})
{
"severity": "ERROR",
"message": "ReferenceError: firestore is not defined\n at /Users/syedalirazabokhari/Desktop/Development/React/omanshopping/functions/index.js:23:23\n at cloudFunction (/Users/syedalirazabokhari/Desktop/Development/React/omanshopping/functions/node_modules/firebase-functions/lib/cloud-functions.js:134:23)\n at /usr/local/lib/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:590:16\n at runFunction (/usr/local/lib/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:577:15)\n at runBackground (/usr/local/lib/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:589:11)\n at processBackground (/usr/local/lib/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:572:11)\n at invokeTrigger (/usr/local/lib/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:647:19)\n at handleMessage (/usr/local/lib/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:734:15)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)"
}
I tried to debug the issue but unable to resolve. I have also updated the firebase-admin to the latest version 9.5.0 as of now.
Removing the unnecessary import const { firestore } = require('firebase-admin') and then changing firestore.FieldValue.increment(1) to admin.firestore.FieldValue.increment(1) fixed the error.

Getting "firebase.functions(app)" arg expects a FirebaseApp instance or undefined in React-Native Firebase

I am triggering an httpscallable function in GoogleCloud but receiving this error back which I could not find anywhere in documentation what is it:
"firebase.functions(app)" arg expects a FirebaseApp instance or undefined.
Ensure the arg provided is a Firebase app instance; or no args to use
the default Firebase app.
Here is my code in RN app:
import { firebase } from '#react-native-firebase/functions';
...
try {
await firebase.functions('europe-west1').httpsCallable('createUserTest')();
}
catch (httpsError) {
console.log(httpsError.message);
}
And my Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUserTest = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
const callerUid = context.auth.uid;
const callerUserRecord = await admin.auth().getUser(callerUid);
return { result: callerUserRecord.customClaims };
} catch (error) {
return { error: error };
}
});
I am using this function for testing purposes just to see if I can receive back the current user custom claims or not, however, its returning that error.
It looks like you're not using the provided client API correctly. I suggest reviewing the documentation, especially example 3. You'll want to do this instead:
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
await functionsForRegion.httpsCallable("createUserTest")()

Resources