How to use PubSub to get one message at time - firebase

I need my onPublish method (firebase side) to get only one message at time.
My code is like this:
Google Cloud (AppEngine):
const pubsub = new PubSub({
projectId: config.project,
});
const topicName = 'projects/'+config.project+'/topics/user_assigner';
const dataBuffer = Buffer.from(dataToSend);
pubsub
.topic(topicName)
.publisher()
.publish(dataBuffer, (err, messageId)=> {
console.log("call back")
console.log("err",err)
console.log("messageId",messageId)
resolve(messageId)
})
Firebase
var functions = require('firebase-functions');
module.exports =
functions.pubsub.topic('user_assigner').onPublish((event) => {
return someAsyncCode() //return promise;
});
I need the code someAsyncCode to run messages one by one, is there a way to do that?
I have seen on the documentation of the publisher the batch maxMessages but I think that is to set the limit when the publisher push new messages to the queue of messages. So, if I set that attibute to one is the same, the publisher will push one buy one(gcloud side), and the onPublish (firebase side) will get all the messages no matter if it is processing one.

Related

Firebase Cloud Function does not terminate in functions:shell

I'm trying to import a JSON into a collection. For testing purpose, I'm using emulators and document creation trigger.
The workflow is :
start emulators with firebase emulators:start
start functions shell in another terminal with firebase functions:shell (tells me that no emulator is running btw)
call my function with tempoCF()
it runs and add documents to collection but it seems that the function does not terminate. I cannot call another function and need to press CTRL+C to be able to write again in the shell.
Here is the function I use :
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const fetch = require('node-fetch');
admin.initializeApp();
const db = admin.firestore();
exports.tempoCF = functions
.firestore.document('/tempo/{docId}')
.onCreate(async (snap, context) => {
console.log("onCreate");
let settings = { method: "Get" };
let url = "https://opendata.paris.fr/api/records/1.0/search/?dataset=sanisettesparis&q=&rows=-1";
try {
let response = await fetch(url, settings);
let json = await response.json();
// TODO for each json object, add new document
return Promise.all(json["records"].map(toiletJsonObject => {
console.log(toiletJsonObject);
return db.collection('toilets').doc(toiletJsonObject["recordid"]).set({});
}));
}
catch(error) {
console.log(error);
return null;
}
}
);
I know I can use the emulator UI to create a new document that trigger the tempoCF function and it works as well but I fear that my function isn't correct and could generate bugs in production.
Here is the screenshot of the terminal. It prints logs and at the end, there is no way to write anything on the last empty line in the screenshot. I run it in Android Studio but I don't think that it matters.
I'm not on Windows but on a Mac, and I can reproduce your problem in the Terminal by calling the function with tempoCF(). Somehow, by doing that, you are simulating the creation a Firestore document without data.
But if I pass some data when calling the Cloud Function, e.g. tempoCF({foo: "bar"}) (i.e. providing new test data for the onCreate operation) I'm able to write to the Terminal after the CF has completed. See the doc for more details.

How to use scheduler for Firebase Cloud Functions with Realtime Database/Analytics triggers?

I'm working on a Firebase Cloud Function, to send triggered push notifications.
Right now my function sends a push as soon as an user triggers the "IAP" event in my app.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendIAPAnalytics = functions.analytics.event('IAP').onLog((event) => {
const user = event.user;
const uid = user.userId; // The user ID set via the setUserId API.
sendPushToUser();
return true;
});
function sendPushToUser(uid) {
// Fetching all the user's device tokens.
var ref = admin.database().ref(`/users/${uid}/tokens`);
return ref.once("value", function(snapshot){
const payload = {
notification: {
title: 'Hello',
body: 'Open the push'
}
};
console.log("sendPushToUser ready");
admin.messaging().sendToDevice(snapshot.val(), payload)
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
}
This functions works, push are sent and received.
I read some news about scheduling for Firebase Cloud Functions:
https://medium.com/#pascalluther/scheduling-firebase-cloud-functions-with-cloud-scheduler-b5ec22ace683
https://firebase.googleblog.com/2019/04/schedule-cloud-functions-firebase-cron.html
I understood, it's only for HTTP triggers ou PUB/SUB triggers.
So for now it's always impossible to trigger functions with delays, by writing in realtime database or when analytics events are triggered.
Am I right? or is there a trick?
I read nothing about this.
EDIT: Official documentation
https://firebase.google.com/docs/functions/schedule-functions
My syntax is wrong but I need something like this:
function sendPushToUser(uid) {
var ref = admin.database().ref(`/users/${uid}/tokens`);
return ref.once("value", function(snapshot){
const payload = {
notification: {
title: 'Hello',
body: 'Open the push'
}
};
functions.pubsub.schedule('at now + 10 mins').onRun((context) => {
admin.messaging().sendToDevice(snapshot.val(), payload)
})
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
}
There is no built-in way to retrigger Cloud Functions with a delay. If you want such functionality you will have to build that yourself, for example by scheduling a function to run periodically and then see what tasks need to be triggered. See my answer here: Delay Google Cloud Function
As Doug commented, you can use Cloud Tasks to schedule individual invocations. You'd dynamically create the task, and then have it call a HTTP function.

How to trigger function when date stored in firestore database is todays date?

I am creating an app where I need to send push notification when today's date matches with the date stored in database in order to send push notification.
How to achieve this?
Update:
You can use a scheduled Cloud Function, instead of writing an HTTPS Cloud Function that is called via n online CRON Job service. The Cloud Function code stays exactly the same, just the trigger changes.
Scheduled Cloud Functions were not available at the time of writing the initial anwser.
Without knowing your data model it is difficult to give a precise answer, but let's imagine, to simplify, that you store in each document a field named notifDate with format DDMMYYY and that those documents are store in a Collection named notificationTriggers.
You could write an HTTPS Cloud Function as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({ origin: true });
const moment = require('moment');
admin.initializeApp();
exports.sendDailyNotifications = functions.https.onRequest((request, response) => {
cors(request, response, () => {
const now = moment();
const dateFormatted = now.format('DDMMYYYY');
admin.firestore()
.collection("notificationTriggers").where("notifDate", "==", dateFormatted)
.get()
.then(function(querySnapshot) {
const promises = [];
querySnapshot.forEach(doc => {
const tokenId = doc.data().tokenId; //Assumption: the tokenId is in the doc
const notificationContent = {
notification: {
title: "...",
body: "...", //maybe use some data from the doc, e.g doc.data().notificationContent
icon: "default",
sound : "default"
}
};
promises
.push(admin.messaging().sendToDevice(tokenId, notificationContent));
});
return Promise.all(promises);
})
.then(results => {
response.send(data)
})
.catch(error => {
console.log(error)
response.status(500).send(error)
});
});
});
You would then call this Cloud Function every day with an online CRON job service like https://cron-job.org/en/.
For more examples on how to send notifications in Cloud Functions, have a look at those SO answers Sending push notification using cloud function when a new node is added in firebase realtime database?, node.js firebase deploy error or Firebase: Cloud Firestore trigger not working for FCM.
If you are not familiar with the use of Promises in Cloud Functions I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
You will note the use of Promise.all() in the above code, since you are executing several asynchronous tasks (sendToDevice() method) in parallel. This is detailed in the third video mentioned above.
Use Google Cloud Functions Scheduled Triggers
https://cloud.google.com/scheduler/docs/tut-pub-sub
Using a scheduled trigger you can specify how many times to invoke your function by specifying the frequency using the unix-cron format. Then within the function you can do date check and other needed logic

Firebase cloud function that writes to database writes multiple times and never returns

I'm trying to create a cloud function to write to a firebase db. It's supposed to be really simple. I want to write and return a 200. The example they give does a redirect in the then callback, but I don't want to redirect. Unfortunately, my function hangs indefinitely and writes the same message to the db 3,4,5,6 times.
I'm pretty sure I am doing the return incorrectly. I tried to return res.status = 200 from the then callback, but that didn't work either.
This is what I have currently:
exports.createEvent = functions.https.onRequest((req, res) => {
const name = req.query.name;
const description = req.query.description;
const location = req.query.location; //json? lat/lng, public,
const date = req.query.date;
// Push the new message into the Realtime Database using the Firebase Admin SDK.
return admin.database().ref('/Event')
.push({name : name, description : description, location : location, date : date});
});
I would suggest you watch the official Video Series on Cloud Functions (https://firebase.google.com/docs/functions/video-series/) and in particular the first video on Promises titled "Learn JavaScript Promises (Pt.1) with HTTP Triggers in Cloud Functions".
You will see that for an HTTTS Cloud Function you have to send a response to the client (watch the video at 8:50).
Therefore the following modifications to your code should do the trick:
exports.createEvent = functions.https.onRequest((req, res) => {
const name = req.query.name;
const description = req.query.description;
const location = req.query.location; //json? lat/lng, public,
const date = req.query.date;
// Push the new message into the Realtime Database using the Firebase Admin SDK.
admin.database().ref('/Event')
.push({name : name, description : description, location : location, date : date})
.then(ref => {
res.send('success');
})
.catch(error => {
res.status(500).send(error);
})
});

Botbuilder doesn't respond after sending 'accepted' code when using cloud functions

I'm using Dialogflow as the handler for my botbuilder endpoint, which is a Azure Bot Service bot, and the handler is deployed on a Firebase Cloud Function, but for every botframework request I make, the function returns a 202 (that's the default behaviour of botbuilder I believe), and the function stops working in the middle of the code.
I saw in this response from Frank van Puffelen that the functions may halt if there's a response from the function.
Cloud Functions for Firebase: serializing Promises
Is this enough to stop my function from executing? If so, is there a way to stop this from happening?
Edit: I'm using the Universal Bot to setup the callback for the messages.
const bot = new builder.UniversalBot(this.connector, botFrameworkCallback)
.set('storage', new builder.MemoryBotStorage());
And here's the botFrameworkCallback:
const botFrameworkCallback = (session) => {
const message = session.message.text;
const userRef = new UserRef('user');
let userInfo;
userRef
.get()
.then((userInformation) => {
console.log('user information', userInformation);
userInfo = userInformation;
const userData: IUser = {
...userInfo,
ref: userRef
};
return makeDialogflowRequest(userData, message);
})
.then((intentResult: any) => {
console.log('intent result', intentResult);
const response = intentResult.answer;
session.send(response);
})
.catch((err) => {
console.log('Error on BotFramework', err);
const response = 'Sorry. An error happened while getting your response.';
session.send(response);
});
}
The whole integration part is there to give user specific responses, so this code does a lot of API requests, like Firestore ones, the Dialogflow one, and because of that we've set it up this way.

Resources