How to get Google cloud function execution event in node.js using firestore - node.js

Below is google cloud function , deployed properly and is working fine
path to function - functions/index.js
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
exports.createUser = functions.firestore
.document('users/{userId}')
.onCreate((snap, context) => {
const newValue = snap.data();
console.log(newValue);
});
how can i access this function's event on successful invocation in node.js app
something like
const myFunctions = require("./functions/index");
myFunctions.createUser().then((data) => {
console.log(data)
})
.catch((err) => {
console.log(err);
})
As of now getting below error

Your createUser Cloud Function is triggered by a Firestore onCreate() event type and therefore will be "triggered when a document is written for the first time", as per the documentation.
The doc also adds the following:
In a typical lifecycle, a Cloud Firestore function does the following:
Waits for changes to a particular document. (In this case when the document is written for the first time)
Triggers when an event occurs and performs its tasks
Receives a data object that contains a snapshot of the data stored in the specified document.
Therefore, if you want to trigger this Cloud Function from "the outside world", e.g. from a node.js app, you need to create a new Firestore document at the corresponding location, i.e. under the users collection. To this end you would use the Node.js Server SDK, see https://cloud.google.com/nodejs/docs/reference/firestore/0.14.x/
Note that you could also trigger it from a client application (web, android, iOS) by creating a new user doc with the corresponding client SDK.
Update following your comments:
You cannot directly "port" and run your code written for Cloud Functions to a Node.js app. You will have to re-develop your solution for Node.js.
In your case you should use the Node.js Server SDK (as mentionned in my comment) and you could use the onSnapshot method of a CollectionReference. See https://cloud.google.com/nodejs/docs/reference/firestore/0.14.x/CollectionReference#onSnapshot

I will try to answer your question, but it's a bit unclear. You asked:
How to get Google cloud function execution event
Well, the event has started when the funcion triggers and your code is running, i.e your line const newValue = snap.data()
Maybe you are looking for a way to do certain tasks, when the trigger has run? You simply just do that from inside the function, and return a promise. If you for example had multiple async tasks to run, you could use a Promise.all([]).

Related

Firestore onUpdate function not triggering

The problem
I'm using Firebase cloud functions with the emulator, and for some reason, the onUpdate trigger is not triggering, yet the onCreate function does trigger.
All code is TypeScript, and it's transpiled to JS to work on cloud functions.
// functions/src/music.ts
// this function runs
export const onMusicCreated = functions.firestore
.document('music/{musicId}')
.onCreate(async (snapshot) => {
console.log('on create is running')
})
// this function doesn't run
export const onMusicUpdated = functions.firestore
.document('music/{musicId}')
.onUpdate(async (change) => {
console.log('on update is running')
})
Both functions are async because in the final code,
On the front-end, when I run the add function on the front-end, the onCreate function fires.
const { id } = await firebase.firestore().collection('music').add({ title: 'hello world' })
The console log runs as expected and the emulator outputs this into the console:
i functions: Beginning execution of "onMusicCreated"
i functions: Finished "onMusicCreated" in ~1s
Yet when I update that same document, the onUpdate function doesn't run.
// "id" is the same id as above
await firebase.firestore().doc(`music/${id}`).update({ title: 'new title' })
Nothing happens. I can confirm that the document is actually updated when I look in the firestore emulator. Am I missing something obvious? The front-end code is simplified as compared to my actual use, but the functions code isn't simplified at all. And I can confirm that the firestore document is created and updated as I'd expect.
No error messages in the console or the logs.
Debug steps
I've checked to make sure the functions code was correctly transpiled to JS. I bothed looked at the code output, as well as updated the onCreate code multiple times to ensure that function updated
I hollowed out all my code inside the functions (as shown above), so I can confirm that the function itself isn't running
The onUpdate function technically accepts two parameters. Same results with both parameters.
I have not tried the functions in production, only with the emulator.
Related posts
Why doesn't firestore onWrite trigger get invoked on firebase cloud functions emulator?
I'm not using any forbidden characters in the document selector, or at least I'm not getting that error message.
Firebase Cloud Functions for Firestore are not triggering
Using functions 3.11.0, and the functions are async, hence they should implicitly return Promise. Results are the same when explicetely returning a value (e.g., return 0)
https://firebase.google.com/docs/functions/firestore-events#trigger_a_function_when_a_document_is_updated
That's the official docs. As far as I can tell, I'm doing what the docs say. I could just be missing something blindingly obvious, though.
Other details
macOS Big Sur 11.1 (20C69)
Firebase CLI 9.1.0
The emulator should be up-to-date
Any ideas? Thanks!
Turns out I just forgot to import onMusicUpdated in the index.ts functions file. Hence the emulator never knew it existed!
Changed
// functions/src/index.ts
export { onMusicCreated } from './music'
to
// functions/src/index.ts
export { onMusicCreated, onMusicUpdated } from './music'

Firebase test auth cloud functions locally

exports.sendWelcomeEmail = functions.auth.user().onCreate((user) => {
console.log(user.uid);
console.log(user.email);
console.log(user.displayName);
});
exports.getUserInfo = functions.https.onCall(async (data, context) => {
// get array of user IDs and returns information (from Users collection)
const userIDs = data.userIDs;
const result = [];
const querySnapData = await admin.firestore().collection("Users").get();
querySnapData.forEach((ele) => {
if (userIDs.indexOf(ele.id) !== -1 && ele.id !== context.auth.uid) {
result.push(ele.data());
}
});
return { res: result };
});
I've got these two functions in my project - one is callable function and the other one is auth trigger functions.
So in my client app, I run
firebase.functions().useFunctionsEmulator('http://localhost:5001');
let getUserInfo = functions.httpsCallable('getUserInfo');
getUserInfo({userIDs: data}).then(res => doSomething);
And to run the cloud functions locally
firebase emulators:start
But it says
functions[sendWelcomeEmail]: function ignored because the auth emulator does not exist or is not running.
So in the client App, getUserInfo works pretty well but can't trigger onCreate.
But I was not able to find any document about auth emulator.
Any link/article/video or answer is appreciated.
The Firebase Emulator Suite currently Cloud Firestore, Realtime Database, Cloud Functions, and Cloud Pub/Sub. It does not yet emulate Firebase Authentication APIs. So any auth calls you make will be executed against the real project that is associated with the emulators.
This also means that your functions.auth.user().onCreate((user) => { Cloud Function will not be triggered in the emulators at the moment. You'll have to deploy it to the servers to test this trigger.
To learn when an auth emulator is available, I recommend keeping an eye on Firebase's release notes, and on the main documentation page for the emulator suite that lists the supported products. You can also follow along more closely on Github, either in the commits, or in this feature request.
firebase recently released the Authentication Emulator, you can check it via the release notes here: https://firebase.google.com/support/releases#october_26_2020, and further guide here: https://firebase.google.com/docs/emulator-suite/connect_auth
So as #Franek van Puffelen wrote above, it is not done yet.
Was able to test auth functions locally like below.
function sendWelcomeEmail(user) {
console.log(user.uid);
console.log(user.email);
console.log(user.displayName);
}
exports.sendWelcomeEmail = functions.auth.user().onCreate((user) => sendWelcomeEmail(user));

Is it possible to have a Firebase Function that is triggered by changes to a Firestore that lives in a seperate Firebase project to the Function?

Let's say I have a Firebase project named "A". Within this project, I have a Cloud Firestore triggered Firebase function that needs to run when a document within Firestore changes. By default, the Firebase Function will listen to changes within Firestore on project A.
However, let's say I have a particular use case where there is a second Firebase project named "B". I need the Firebase Function within Project A to be triggered on Firestore changes that happen to Firestore within project B.
Is this possible? Firebase docs do show initializing multiple projects, which would allow me to connect to multiple databases as such:
const admin = require("firebase-admin");
const serviceAccount = require("path/to/serviceAccountKey.json");
const secondaryAppConfig = {
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://<DATABASE_NAME>.firebaseio.com"
};
// Initialize another app with a different config
const secondary = firebase.initializeApp(secondaryAppConfig, "secondary");
// Retrieve the database.
const secondaryDatabase = secondary.database();
But this doesn't allow me to trigger a Firestore Triggered Firebase Function on my secondary project. Firebase functions call the firebase-functions methods directly, whereas calling a database calls the initialized project.
const functions = require('firebase-functions');
exports.myFunction = functions.firestore
.document('...')
.onWrite((change, context) => { /* ... */ });
Is what I would like to do possible? Or does anyone have a workaround (other than creating this Firebase Function within project B)?
It's not possible. Cloud Functions triggers can only fire in response to changes in the resources of the project where they are deployed. This is true for all types of triggers, including Firestore.
If you want code to run in response to changes in another project, the function will have to be deploy to that project.
Currently it is only possible for writes to Cloud Firestore to trigger Cloud Functions that are part of the same project. It is not possible to trigger Cloud Functions that are defined in another project.
The typical solution is for example to call a HTTP Function in the secondary project, for which you can then configure the complete URL.
I'm not sure it can be done all in one codebase - that's from a lack of experience though. I'd say, given your setup, your calling function can trigger your callee function via HTTP call (documentation)
This might require a paid Firebase plan, but I'm not certain of it (source)

How do i reference Firebase's realtime database from within a cloud function?

In a Firebase Cloud function i am attempting to retrieve data from my Realtime Database and write an update to it as well. This is where i am having trouble.
The function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
...
api.post('/messages/add', (request: any, response: any) => {
const dbRef:any = functions.database.ref(`count/${request.body.chatRoomId}`);
dbRef.once('value').then((snapshot:any) => {
let messageCount:number = (snapshot.val() && snapshot.val().messages) || 0;
messageCount = Number(messageCount + 1);
dbRef.update({
messages: messageCount,
updated: admin.database().database.ServerValue.TIMESTAMP
});
});
});
When i call this function from the client, in the firebase cloud console logs, i am seeing the following error:
TypeError: dbRef.once is not a function
I have tried referencing the database in each of the following ways, all of which fail:
functions.database.ref(`count/${request.body.chatRoomId}`).once('value').then((snapshot)) =>
functions.database.ref(`count/${request.body.chatRoomId}`).once('value', (snapshot) =>
admin.database().ref(`count/${request.body.chatRoomId}`).once('value').then((snapshot)) =>
admin.database().ref(`count/${request.body.chatRoomId}`).once('value', (snapshot) =>
When attempting the reference using the Admin SDK via admin.database() i get a different error:
TypeError: Cannot read property 'ServerValue' of undefined
at dbRef.once.then
Lastly, i can confirm that the value for ${request.body.chatRoomId} is resolving correctly, i had tested this by printing its value to the console.
The answer here shows how to reference the realtime database from the 'event' response within a realtime database listener like onWrite() for example, however my function is not triggered by realtime database changes, but rather is an endpoint reachable from the client.
UPDATE
The answer proposed by Doug Stevenson below is correct but there was also an additional error due to attempting to write a timestamp incorrectly to a field that i had initially excluded which was causing my function to fail despite having tried his solution, thus I have updated the code to reflect this.
First, the answer is to reference the Realtime Database with admin.database()
Second, even though i had tried this, i was still seeing an error, but the error was due to attempting to set a timestamp incorrectly. Thanks to Frank van Puffelen for pointing this out.
This: admin.database().database.ServerValue.TIMESTAMP
Needs to be: admin.database.ServerValue.TIMESTAMP
And with that it works.
You're making the mistake of trying to use the functions SDK to query the database. This is not actually creating a database reference:
functions.database.ref(`count/${request.body.chatRoomId}`);
The functions SDK is just used for declaring and configuring Cloud Functions.
What you should be doing instead is using the Admin SDK to create a reference, then query it:
admin.database().ref(...).once(...)
You also need to initialize the Admin SDK exactly once before you use it:
admin.initializeApp();

Why is Google Cloud Functions throwing a "Invalid value for config firebase.databaseURL" error when I try to initialize a firebase app?

I have a Google Cloud Function that syncs presence information from a Firebase realtime database to a Firestore database (as explained here). This is the relevant Cloud Functions code from the linked example:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// Since this code will be running in the Cloud Functions enviornment
// we call initialize Firestore without any arguments because it
// detects authentication from the environment.
const firestore = admin.firestore();
// Create a new function which is triggered on changes to /status/{uid}
// Note: This is a Realtime Database trigger, *not* Cloud Firestore.
exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
(change, context) => {
// Get the data written to Realtime Database
const eventStatus = change.after.val();
// Then use other event data to create a reference to the
// corresponding Firestore document.
const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);
// It is likely that the Realtime Database change that triggered
// this event has already been overwritten by a fast change in
// online / offline status, so we'll re-read the current data
// and compare the timestamps.
return change.after.ref.once('value').then((statusSnapshot) => {
const status = statusSnapshot.val();
console.log(status, eventStatus);
// If the current timestamp for this data is newer than
// the data that triggered this event, we exit this function.
if (status.last_changed > eventStatus.last_changed) {
return null;
}
// Otherwise, we convert the last_changed field to a Date
eventStatus.last_changed = new Date(eventStatus.last_changed);
// ... and write it to Firestore.
return userStatusFirestoreRef.set(eventStatus);
});
});
I recently received an email from Google informing me that I will need to update from NodeJS 6 to NodeJS 8 or 10. As this particular function isn't in production yet, I went ahead and made the configuration change in the Google Cloud Console. I now get the error below. I tried switching back to NodeJS 6, recreating the function from scratch, checking Github issues and other online forums. It appears that my Google Cloud Function is no longer being provided with the necessary environment variables to connect with Firebase/Firestore. However, I'm unsure why that would be the case.
Error: Invalid value for config firebase.databaseURL: undefined
at resourceGetter (/srv/node_modules/firebase-functions/lib/providers/database.js:101:19)
at cloudFunctionNewSignature (/srv/node_modules/firebase-functions/lib/cloud-functions.js:102:13)
at /worker/worker.js:825:24
at <anonymous> at process._tickDomainCallback (internal/process/next_tick.js:229:7)
This error also shows up in the Stackdriver logs for the Cloud Function:
Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail
You should redeploy using the Firebase CLI. It does some special things in the environment to help the Firebase Admin SDK initialize correctly without any parameters (adding FIREBASE_CONFIG). It sounds like when you changed the runtime in the console, you also lost this special configuration.
For me, I use firestore, and I was getting the same error as you, so I had to create a real-time database without any record then I set the credentials for the admin like so:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp({
databaseURL: "your realtime database url"
});
When you are done, run firebase deploy --only functions to deploy your functions.
Here is your Realtime database URL:

Resources