Firebase CLI for one to one device notification - firebase

I am trying to send a one to one device specific notification using FCM and Firebase CLI. For this I am sending the token from android to Firebase realtime database and trying to capture this token in CLI using an onwrite event. Here is the structure of the realtime database:
Here is the code where I am trying to capture the onwrite event and the token:
exports.sendNotification = functions.database.ref('/Notification/{notification_id}').onWrite((data, context) => {
const notification_id = context.params.notification_id;
const receiver_token = data.ref.parent.child(notification_id).child("token");
})
But I get the following error in the log:
sendNotification
TypeError: Cannot read property 'parent' of undefined at exports.sendNotification.functions.database.ref.onWrite
I am writing CLI code for the first time and hence any help would be appreciated

The onWrite trigger gets a change parameter, which contains the snapshots before and after the change that triggered the code.
So you'll need to get the change.before or change.after to get the actual data.
exports.sendNotification = functions.database.ref('/Notification/{notification_id}').onWrite((change, context) => {
const notification_id = context.params.notification_id;
const receiver_token = change.after.ref.parent.child(notification_id).child("token");
})
See the Firebase documentation on onWrite triggers.
Note that it's much more common to use onCreate for this scenario, as you're typically deleting the notification after you've handled it.

Related

Why doesn't firestore onWrite trigger get invoked on firebase cloud functions emulator?

I have a firestore with a collection called "chats"; I use the firestore emulator to insert a new document and I am expecting the onWrite trigger to get called while I am running index.js locally on my firebase cloud functions emulator (by running firebase emulators:start), but it never does.
I know that the emulator is connected to the right firestore emulator since I can read the data (see below), I just can't get the trigger to be invoked.
// My setup:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// We are able to fetch a document correctly, meaning at least my
// cloud functions emulator is hooked up to the firestore emulator!
admin.firestore().collection("chats").doc("eApXKLLXA4X6tIJtYpOx").get()
.then(doc => {
if (doc.exists) {
console.log("Document data:", doc.data()); // <- this works!!
} else {
throw new Error("No sender document!");
}
})
// This never gets called when I insert a new document through the emulator UI!
exports.myFunction = functions.firestore
.document('chats/{chat-id}')
.onWrite((change, context) => { console.log("on write") });
Try to change the document selector from chats/{chat-id} to chats/{chatId}. Looks like - symbol is forbidden here. If I use it, I get the following error in firebase-debug.log:
[debug] [2020-06-01T12:17:29.924Z] Jun 01, 2020 3:17:29 PM com.google.cloud.datastore.emulator.impl.util.WrappedStreamObserver onError
INFO: operation failed: Invalid pattern. Reason: [69:-] Expected '}' at end of capture expression.
Another reason might be using a wrong project id, but it seems it's not your case.
See: Firestore triggers of cloud functions in emulator are ignored

How can scheduled Firebase Cloud Messaging notifications be made outside of the Firebase Console?

Inside the Firebase Console, under the Cloud Messaging view, users are able to create test notifications. This functionality also allows you to schedule the time at which the notification will send to a device or set of devices.
Is it possible to create and send scheduled FCM notifications to specific devices by using firebase cloud functions and the Firebase Admin SDK? Is there an alternative way to solving this?
The current way that I send scheduled messages to users is like so:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const schedule = require('node-schedule');
admin.initializeApp();
exports.setScheduledNotification = functions.https.onRequest(async (req, res) => {
const key = req.query.notification_key;
const message = {
notification: {
title: 'Test Notification',
body: 'Test Notification body.'
}
};
var currentDate = new Date();
var laterDate = new Date(currentDate.getTime() + (1 * 60000));
var job = schedule.scheduleJob(key, laterDate, () => {
const snapshot = admin.messaging().sendToDevice(key, message);
});
return res.status(200).send(`Message has been scheduled.`);
});
First of all, I am unsure how node-schedule interacts with firebase cloud functions. The logs appear that the function terminates very quickly which I would think would be correct. The longer the operation runs the more costly it is in our firebase bills. The notification does still run on a scheduled time though. I'm confused on how that all is working behind the scenes.
Secondly, I am having issues canceling these scheduled notifications. The notifications will most likely be on a 2hr timed schedule from the point it gets created. Before the 2hrs is up, I'd like the have the ability to cancel/overwrite the notification with an updated scheduled time.
I tried this to cancel the notification and it failed to find the previously created notification. Here is the code for that:
exports.cancelScheduledNotification = functions.https.onRequest(async (req, res) => {
const key = req.query.notification_key;
var job = schedule.scheduledJobs[key];
job.cancel();
return res.status(200).send(`Message has been canceled.`);
});
Is it possible to tap into the scheduling functionality of firebase cloud messaging outside of the firebase console? Or am I stuck with hacking my way around this issue?
A Cloud Function can run for a maximum of 9 minutes. So unless you're using node-schedule for periods shorter than that, your current approach won't work. Even if it would work, or if you are scheduling for less than 9 minutes in advance, using this approach is very uneconomic as you'll be paying for the Cloud Functions for all this time while it's waiting.
A more common approach is to store information about what message you want to be delivered to whom at what time in a database, and then use regular scheduled functions to periodically check what messages to send. For more on this, see these previous questions:
Firebase scheduled notification in android
How to schedule push notifcations for react native expo?
Schedule jobs in Firebase
Ionic: Is it possible to delay incoming push FCM push notification from showing to my device until a specific time
Cloud Functions for Firebase trigger on time?
How to create cron jobs dynamically in firebase
A recent improvement on this is to use the Cloud Tasks API to programmatically schedule Cloud Functions to be called at a specific time with a specific payload, and then use that to send the message through FCM. Doug Stevenson wrote a great blog post about this here: How to schedule a Cloud Function to run in the future with Cloud Tasks (to build a Firestore document TTL). While the post is about deleting documents at a certain time, you can combine it with the previous approach to schedule FCM messages too.
Scheduling of tasks is now also described in the documentation on enqueueing functions with Cloud Tasks
A final option, and one I'd actually recommend nowadays, is to separate the delivery of the message from the display of the notification.
Display of data messages (unlike notification messages) is never handled by the system, and always left to your application. So you can deliver the FCM data message straight away that then contains the time to display the message, and then wake the device up to display the message (often called a local notification) at that time.
To make Frank's answer more tangible, I am including some sample code below for scheduled cloud functions, that can help you achieve the 'scheduled FCM notifications'.
You should store the information required to send your notification(s) in Firestore (e.g. the when-to-notify parameter and the FCM token(s) of the users you want to send the notification to) and run a cloud function every minute to evaluate if there is any notification that needs to be delivered.
The function checks what Firestore documents have a WhenToNofity parameter that is due, and send the notifications to the receiver tokens immediately. Once sent, the function sets the boolean 'notificationSent' to true, to avoid that the users receive the same notification again on the next iteration.
The code below achieves just that:
const admin = require('firebase-admin');
admin.initializeApp();
const database = admin.firestore();
exports.sendNotification = functions.pubsub.schedule('* * * * *').onRun(async (context) => {
//check whether notification should be sent
//send it if yes
const query = await database.collection("experiences")
.where("whenToNotify", '<=', admin.firestore.Timestamp.now())
.where("notificationSent", "==", false).get();
query.forEach(async snapshot => {
sendNotification(snapshot.data().tokens);
await database.doc('experiences/' + snapshot.id).update({
"notificationSent": true,
});
});
function sendNotification(tokens) {
let title = "INSERT YOUR TITLE";
let body = "INSERT YOUR BODY";
const message = {
notification: { title: title, body: body},
tokens: tokens,
android: {
notification: {
sound: "default"
}
},
apns: {
payload: {
aps: {
sound: "default"
}
}
}
};
admin.messaging().sendMulticast(message).then(response => {
return console.log("Successful Message Sent");
}).catch(error => {
console.log(error);
return console.log("Error Sending Message");
});
}
return console.log('End Of Function');
});
If you're unfamiliar with setting up cloud functions, you can check how to set them up here. Cloud functions require a billing account, but you get 1M cloud invocations per month for free, which is more than enough to cover the costs of this approach.
Once done, you can insert your function in the index.js file.

Is there cloud functions that trigger if database changes and send notification to users subscriber to 'topics'

I am working in an android app project for my college minor project. Everything is working but now i want to add a notification feature, i.e whenever a admin posts a notice every user subscriber to that topic gets notification, i tried to follow different tutorials and documents but since i have no programming background in js/nodejs/php i couldn't understand the cloud functions.
Can anyone write the functions or lead me to the answer?
i want the function to be triggered when a new notice is added inside /Notice and send notification to all users subscribe to Notice..
i wrote the following code, after some study,
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotices =
functions.database.ref('/Notices/{nID}').onCreate((event) => {const data =
event.data;
if(!data.changed()){
console.log('Nothing changed');
return;
}else{
console.log(data.val());
}
const payLoad = {
notification:{
title: 'Message received',
body: 'You received a new message',
sound: "default"
}
};
const options = {
priority: "high",
timeToLive: 60*60*2
};
return admin.messaging().sendToTopic("bctb", payLoad, options);});
and got the error in console of firebase,what am i doing wrong here,
TypeError: Cannot read property 'changed' of undefined
at exports.sendNotices.functions.database.ref.onCreate
(/user_code/index.js:8:13)
at cloudFunctionNewSignature (/user_code/node_modules/firebase-
functions/lib/cloud-functions.js:105:23)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-
functions.js:135:20)
at /var/tmp/worker/worker.js:770:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
Since you are not familiar with the Firebase Cloud Functions, I recommend you first go through official docs here, because without going through the basics you won't understand how they work and then go through Firebase Cloud Messaging (FCM) docs here. Once you get to know how both the service work it'll be a lot easier for you to understand and write your own cloud function. For your ease here is how your function should be like.
You can do this by simply creating an onCreate trigger function. So it will look something like:
exports.SendNotification = functions.database.ref('/Notice/{nid}')
.onCreate((snapshot, context) => {
//Your notification code here
}
Here nid is the notice id that is just created. Firebase will automatically get this id. And for sending the notification you can use Firebase cloud messaging (FCM). In this cloud function you can create a notification payload.
//send notification
const payload = {
data:{
title: "New notice has been added!",
}
};
Now you can send this notification to the app using:
admin.messaging().sendToDevice(instID, payload);
Here, instID is the instance ID. Each app installed has a unique instance ID. For sending to multiple devices you'll have to wrap the code line above in an loop to send notifications to all of the subscribed users. For this you need instance IDs of all the subscribed users.
"I hear and I forget, I see and I remember, I do and I understand"
Best of luck.

Firebase Pub/Sub trigger function is called but event is null

I successfully publish to a topic:
gcloud pubsub topics publish my-topic --message '{"value":"data"}' --attribute value=myValue
and can succesfuly view the entry using the gcloud command:
gcloud beta pubsub subscriptions pull --auto-ack my-subcription
I then created a firebase pubsub onPublish trigger function as follows:
exports.myfunction = functions.pubsub.topic('my-topic').onPublish(event => {
const attributes = event.data.attributes;
const message = event.data.json;
const value= attributes['value'];
const data = {
key: value,
key2: message.value
};
let ref= db.collection('devices').doc(value);
return updateTheDataInMyFirestore(ref, data);
});
What i aim to do is update the data in my firestore to maintain real time data. The function is called but the event is always null and i get the error 'TypeError: Cannot read property 'value' of undefined' when i try to access the attributes. I don't understand why this happens.
PubSub handler functions receive a first argument which is a Message type object. (You're calling it event.) According to the API docs that I linked to, the Message object has a property called json, which is the parsed JSON data from the payload of the message.
It looks like you're assuming that data.attributes in the Message contains the payload. It doesn't. Use the Message json property instead:
exports.myfunction = functions.pubsub.topic('my-topic').onPublish(message => {
const payload = message.json
console.log(payload)
const value = payload.value
// continue processing...
});

Can you trigger a Google Cloud Functions via firebase event without a server?

I will be implementing an elastic search index alongside my firebase application so that it can better support ad-hoc full text searches and geo searches. Thus, I need to sync firebase data to the elastic search index and all the examples require a server process that listens for firebase events.
e.g. https://github.com/firebase/flashlight
However, it would be great if I can just have a google cloud function triggered by an insert in a firebase node. I see that google cloud functions has various triggers: pub sub, storage and direct... can any of these bridge to a firebase node event without an intermediate server?
firebaser here
We just released Cloud Functions for Firebase. This allows you to run JavaScript functions on Google's servers in response to Firebase events (such as database changes, users signing in and much more).
I believe Cloud Functions for Firebase are what you are looking for.
Here are a few links:
Official Documentation
Intro video
Google Cloud Functions and Firebase (Google Cloud Next '17)
yes, you can trigger a Google Cloud Functions via firebase event without a server.
As per documents,Firebase allows for example you can send notifications using a cloud function when a user write into firebase database.
For that, I had to write a javascript as below
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/articles/{articleId}')
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
var eventSnapshot = event.data;
var str1 = "Author is ";
var str = str1.concat(eventSnapshot.child("author").val());
console.log(str);
var topic = "android";
var payload = {
data: {
title: eventSnapshot.child("title").val(),
author: eventSnapshot.child("author").val()
}
};
// Send a message to devices subscribed to the provided topic.
return admin.messaging().sendToTopic(topic, payload)
.then(function (response) {
// See the MessagingTopicResponse reference documentation for the
// contents of response.
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});

Resources