I've been trying to push notifications with .onUpdate() trigger but It doesn't work. I'm not sure what is wrong since anything I find on docs is useless pretty much and it's my first time working with Node.js.
I want to notify the user (with Firebase Messaging) when any product gets updated (in Firebase Realtime Database) using Firebase Cloud Functions, which is after submitting an order, and the requirement is that the product stock is <= 5.
Structure of the collection is like this:
products (collection) -> {productID} (document) -> attributes: {name, barcode, price, stock, sold}
//import firebase
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/products/{product}')
.onUpdate((change, context) => {
const prodName = context.data.child('name');
const numProd = context.data.child('stock');
if(numProd<=5){
const payload = {
notification: {
title: 'Low stock!',
body: `Product ${prodName} is running out.`
}
}
const registrationToken = 'token';
return admin.messaging().sendToDevice(registrationToken,payload)
.then(function(response){
console.log('Notification sent successfully:',response);
return 1;
})
.catch(function(error){
console.log('Notification sent failed:',error);
});
}
});
Apparently you are mixing up the two Firebase's database services: Firestore and the Realtime Database.
As a matter of fact, you indicate that your data is organised in collections ("Structure of the collection is like this: products (collection) -> {productID} (document)") which means that you are using Firestore (Realtime Database doesn't have collections).
But your background trigger is corresponding to a Realtime Database trigger, see https://firebase.google.com/docs/functions/database-events.
If the assumption that you are mixing up the two database services is right, you need to use a background trigger for Firestore, see https://firebase.google.com/docs/functions/firestore-events, in particular the onUpdate() one, as follows:
exports.updateUser = functions.firestore
.document('/products/{productId}')
.onUpdate((change, context) => {
// Get an object representing the document
const newValue = change.after.data();
const prodName = newValue.name;
const numProd = newValue.stock;
// ...
});
Note that it seems that you are not handling correctly the case when numProd > 5. You may throw an error or just do return null;
It is a also a good idea to watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/.
Related
I have a google cloud bucket and firebase writes my app data there. I would like to monitor my data, and have any new update (write) to my firebase database it sent via a text or email to me. I currently have Twilio set up on Nodejs to send texts on Firebase and my code is:
const functions = require('firebase-functions');
var twilio = require('twilio');
const admin = require('firebase-admin');
admin.initializeApp();
var accountSid = 'account id'; // Account SID from www.twilio.com/console
var authToken = 'account token'; // Auth Token from www.twilio.com/console
var client = new twilio(accountSid, authToken);
exports.useWildcard = functions.firestore
.document('comments/{commentContent}')
.onWrite((change, context) => {
client.messages.create({
body: context.params.commentContent,
to: '+15555555555', // Text this number
from: '+15555555556' // From a valid Twilio number
})
.then((message) => console.log(message.sid));
});
Currently, I would like to build it out for just the comments document, which are organized inside firebase through comments/{commentContent}. Later, I would like to expand to other trees. I am however, unsure if the above will run each time there is a write to my comments tree. Does it require the firebase-admin module as I have put above? Thanks!
Yes, the onWrite method will not only run when there is a write to the comments tree, but will also be triggered by any change in any document and on the deletion of a document. This means that right now your code will responde in the same way to any of the above cases, and this could cause problems, especially in the case of a document being deleted since it will try to send a comment that doesent exist and will likely get some null exceptions.
Said that you have different solutions.
If you only want the function to react to a new comment, but not to an update or deletion you should use onCreate trigger instead of onWrite.
If you also want to handle a comment update notification you can use both onCreate and onUpdate, but sending different messages by doing something like:
exports.useWildcardCreate = functions.firestore
.document('comments/{commentContent}')
.onCreate((change, context) => {
client.messages.create({
body: context.params.commentContent,
to: '+15555555555', // Text this number
from: '+15555555556' // From a valid Twilio number
})
.then((message) => console.log(message.sid));
});
exports.useWildcardUpdate = functions.firestore
.document('comments/{commentContent}')
.onUpdate((change, context) => {
const newComment = change.after.data();
const previuosComment = change.before.data();
client.messages.create({
body: 'The comment ${previuosComment} has been changed to ${newComment}',
to: '+15555555555', // Text this number
from: '+15555555556' // From a valid Twilio number
})
.then((message) => console.log(message.sid));
});
At last if you also need to notify when a comment has been deleted you should use onWrite method but differentiating between the 3 different cases as shown below:
exports.useWildcard = functions.firestore
.document('comments/{commentContent}')
.onWrite((change, context) => {
var textBody;
const oldComment = change.before.data();
const newComment = change.after.data();
if (change.after.exists == false) { // comment has been deleted
textBody = 'The comment ${oldComment} has been deleted';
}
else if (oldComment != newComment) { // comment has been updated
textBody = 'The comment ${oldComment} has been changed to ${newComment}';
}
else { // if its not an update or a deletion its a new comment
textBody = newComment;
}
client.messages.create({
body: textBody,
to: '+15555555555', // Text this number
from: '+15555555556' // From a valid Twilio number
})
.then((message) => console.log(message.sid));
});
Finally require('firebase-admin') is needed since it will allow you to interact with Firebase from privileged environments. Here you can find all the information to the Firebase Admin SDK
There is an integer value in the my real time database that I'd like to have sync'd with an integer value in my firestore database. The realtime database is fed through an external source and when it gets an update, I'd like that pushed to the firestore database
Here's what I have so far, I am able to access the value in the realtime database but not the firestore database.
=============== Data Structure===================
Real Time database
user1:
{ meter : 20 }
Firestore database
Collection: Users
{Document : user1
{ meter : 20 }}
/// =============Code Sample ======================================
const functions = require('firebase-functions');
// Initialize the Firebase application with admin credentials
const admin = require('firebase-admin');
admin.initializeApp();
// Define user sync method
exports.meterSync = functions.database.ref('/user1/meter').onUpdate( (change, context) => {
// Get a reference to the Firestore document of the changed user
var userDoc = admin.firestore().doc(`user/${context.params.user1}`);
const meterReading = change.after.val();
console.log(meterReading);
console.log(userDoc); /// Not able to access this
return null
});
My expectation is that user doc will give me the document, so I can update the fields within it. But I am getting a documentReference object, not sure how to access the meter field.
By doing
var userDoc = admin.firestore().doc(`user/${context.params.user1}`);
You actually defined a DocumentReference.
You have to use this DocumentReference to write to the Firestore database, using the set() or update() methods.
Here is a code using the set() method:
exports.meterSync = functions.database
.ref('/{userId}/meter')
.onUpdate((change, context) => {
const meterReading = change.after.val();
console.log(meterReading);
// Get a reference to the Firestore document of the changed user
const userDoc = admin.firestore().doc(`user/${context.params.userId}`);
return userDoc.set(
{
meter: meterReading
},
{ merge: true }
);
});
You will need to use cloud functions for this, lets say, you have one background function:
export const one = functions.firestore.document('users').onWrite((change, context) => {
// ... Your code here
})
export const two = functions.database.ref('/users/{userId}')
.onCreate((snapshot, context) => {
// ... you code here
})
Since you have access to the firebase sdk admin in there you basically can achieve your goal. You can read more about it here
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
We are using firebase functions and firebase real time DB for our mobile app. We do send emails when someone place an order which is implemented using firebase DB triggers like below:
exports.on_order_received = functions.database.ref("/orders/{id}")
.onCreate((change, context) => {
console.log("start of on_order_received")
...
Above trigger works just fine for us. Now, we have some requirement where we do not have a DB trigger in the picture. It's a http request like below
exports.daily_sales_report = functions.https.onRequest((req, res) => {
//query data from firebase
The question is how do we access the real time db objects here? or in other words how do i get the access to the /orders node ? I tried like below
exports.daily_sales_report = functions.https.onRequest((req, res) => {
//query data from firebase
var ref = functions.database.ref('orders')
ref.orderByValue().limitToLast(3).on("value", function(snapshot) {
snapshot.forEach(function(data) {
console.log("The " + data.key + " dinosaur's score is " + data.val());
});
})
but this does not work. I get error "orderByValue() is not a function"
You should use the Firebase Admin SDK. It has the ability to read and write your database. In fact, when you write a database trigger, the refs it gives you to work with actually come from the Admin SDK, so it's the same API. You just need to initialize it yourself when using HTTP type functions:
// at the top of the file:
const admin = require('firebase-admin');
admin.initializeApp();
// in your function:
const root = admin.database().ref();
// root is now a Reference to the root of your database.
You'll have to use the admin and not the functions to access the database() to read the data.
(Please ensure you've access to firebase-admin sdk, use import or require as appropriate depending on whether you are using TypeScript or JavaScript)
// The Firebase Admin SDK to access the Firebase Realtime Database.
import * as admin from 'firebase-admin';
or
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
Try this:
exports.daily_sales_report = functions.https.onRequest((req, res) => {
//query data from firebase
/* Do not use functions */
// var ref = functions.database.ref('orders')
/* Instead use the admin */
var ref = admin.database().ref('orders')
ref.orderByValue().limitToLast(3).on("value", function(snapshot) {
snapshot.forEach(function(data) {
console.log("The " + data.key + " dinosaur's score is " + data.val());
});
})
orderByValue() is not defined in functions.database - but is actually available in admin.database().ref()
I have the following Firestore DB structure:
users
$USER_ID
notifications
$DOC1
$DOC2
$DOC3
I want to push a new notification when a document is created at the user notification collection.
It should be something like this, but I don't know of any way to this for each $UID:
exports.newSubscriberNotification = functions.firestore
.document('users/$UID/notifications')
.onCreate(async event => {
How can I use Firebase Functions to do this? If there is no way, any suggestions for a workaround?
You should use the following code to trigger your Cloud Function:
exports.newSubscriberNotification = functions.firestore
.document('users/{userId}/notifications/{docId}')
.onCreate((snap, context) => {
//You get the values of the newly created doc as follows:
const newValue = snap.data();
console.log(newValue);
//You get the parameters as follows:
const userId = context.params.userId;
//console.log(userId);
const docId = context.params.docId;
//console.log(docId);
// You perform here the notification sending
});
For the code for the notification sending, have a look at this official Firebase Cloud Function sample: https://github.com/firebase/functions-samples/blob/master/fcm-notifications/functions/index.js