How to listen to multiple databases in google cloud functions - node.js

I want to listen to two databases in my google cloud functions so I tried this
const app1 = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://honey.firebaseio.com"
});
const app2 = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://honey-d8.firebaseio.com/"
}, 'app2');
// Get the default database instance for an app1
var OperationDB = admin.database().ref();
// Get a database instance for app2
var userDB = admin.database(app2).ref();
then I tried to call the second database
exports.onBoardNewUser = functions.userDB.ref('/Users/').onCreate((snapshot, context) => {
})
whenever I attempt to deploy the file it gives this error
Cannot read property 'ref' of undefined
The two databases are in the same project
I have tried different variations but no luck. what am I doing wrong an how can I fix this ?

You can't attach a Cloud Function to some arbitrary database reference. Cloud Functions database triggers are not like listeners that you get with the client SDKs. It receives events from Realtime Database as they are generated.
You have to use the provided API to build a function that references a particular database and path to receive updates.
You can't reference a database outside of the project where you deployed the function.
You can reference multiple shards of a database within the same project. To do that, you have to specify the name of the shard instance using the provided API. If you don't name the instance in the function builder using the instance() method, you will only receive events from the primary/default shard.
If you want to handle events for all of your shards at the same location in each one, you have to export one function for each shard, and they can each delegate to the same helper function to handle the event.

Related

Firebase cloud pubsub subscriptions stops listening for messages

I'm trying to connect my firebase cloud functions project to a third party pub/sub (a separate project). According to this thread this is not possible, so there's no traditional way to make this work. However, I've tried to manually subscribe to certain topics using the #google-cloud/pubsub client on my firebase cloud functions. I need to react to pub/sub messages and write/update certain documents.
Example (minimal):
I have a pubsub subscription on sub.ts:
const pubSubClient = new PubSub({
projectId: config.project_id,
credentials: {
client_email: config.client_email,
private_key: config.private_key
}
});
I subscribe to a certain topic to do some business logic
const subscription = pubSubClient.subscription('my-subscription');
this.subscription.on('message', async (message) => {
try {
message.ack();
const event = parseData(message.data);
await admin.firestore().collection('my-collection').add(event);
} catch (e) {
console.error(e);
}
});
Then this file is imported within the index.js where I declare most CF functions.
import * as admin from 'firebase-admin';
admin.initializeApp();
import './sub';
export { myFunction } from './modules/my-module';
export { myOtherFunction } from './modules/other-module';
It appears that my subscriptions die out after a time and messages won't go through. If I redeploy my functions it appears to be working for a time, but then it stops listening to messages. I've read that firebase cloud functions are stateless, so in this case I need a "stateful" module within my firebase project. Is this possible? Or should I manage this on another server?.
Thanks!
What you're trying to do here (subscribe to a pubsub topic from code running in Cloud Functions) won't work for two reasons:
Cloud Functions server instances scale up and down automatically. There could be 0 instances or 1000 instances concurrently running your triggers, depending on the current load.
Cloud Functions shuts down running code when the function has terminated, and there is a maximum timeout of 9 minutes for any function invocation.
So, even if you manage to subscribe to a topic, that subscription doesn't have a guaranteed duration. It will eventually be destroyed, and you will lose messages.
If you want to handle messages using Cloud Functions in "project A", but the messages come from "project B", you should consider sending them from A to B, perhaps by using pubsub function in B that does nothing other that publish each message to a topic in A. You can then write another function to handle them in A.

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)

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:

AWS Lambda Dynamic DB Switching Singelton (Node)

I'm trying to take advantage of db connection reuse in Lambda, by keeping the code outside of the handler.
For example - something like:
import dbconnection from './connection'
const handler(event, context, callback){
//use dbconnection
}
The issue is I don't decide what database to connect to until I do a lookup to see where they should be connecting. In my specific case I have 'customer=foo' in a query param then I can look to see that foo should connect to database1.
So what I need to do is something like this :
const dbconnection = require('./connection)('database1')
The way it is now I need to do this in every handler method which is expensive.
Is there some way I can pull the query parameter, look up my database and set it / switch it globally within the Lambda execution context?
I've tried this:
import dbconnection from './connection'
const handler(event, context, callback){
const client = dbconnection.setDatabase('database1')
}
....
./connection.js
setDatabase(database) {
if(this.currentDatabase !== database) {
// connect to different database
this.currentDatabase = database;
}
}
Everything works locally with sls offline but doesn't work through the AWS Lambda execution context. Thoughts?
You can either hardcode (or provide it via environment variable) it or not. If you can, then pull it out of then handler and it will not be executed each time. If you can't, as you have mentioned, then what you are trying to do is to make lambda stateful. Lambda was designed to be stateless and AWS intentionally doesn't expose specific informations about the underlying containers so that you don't start doing something like what you are trying to do now - introducing state to it.

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

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([]).

Resources