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

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...
});

Related

Push notification on Firebase Functions - Iterate onWrite returns

So I am trying to send push notifications to user with using Firebase Functions & FCM. I referred to the sample code to populate my pushes and when I use the hardcoded token, it works and the push gets sent correctly. Below is my working cloud function code.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendChatNotification = functions.database.ref('/user_pushes/{uid}').onWrite((change, context) => {
const afterData = change.after.val();
//let tokens = "My_hardcoded_device_token"
console.log('tokens',afterData.fcm_tokens)
let tokens = afterData.fcm_tokens
const payload = {
notification: {
title: afterData.sender_name,
body: afterData.message,
sound: "default"
}
};
return admin.messaging().sendToDevice(tokens, payload);
});
And below is my json of RTDB of '/user_pushes/{uid}'.
{ fcm_tokens:
{ 'token1': 0,
'token2': 0,
'token3': 0, },
message: '👍🏼',
sender_name: 'sender name'
}
Of course I want to get tokens from my database. I expected to use like for in loop to get my tokens from the node named 'fcm_tokens' but no luck yet to find proper snippet. I want my tokens to be in a kind of an array form so that the pushes can be sent to multiple receivers. I am an inexperienced self taught iOS developer and not yet learnt server side programming so if my approach to get this is not appropriate please do advise! Many thanks in advance!
###EDIT###
I tried to retrieve the tokens with below a line of code and it throws an another error saying that the token should not be empty string or an array although it is likely not empty. Maybe my token(s) are keys not the values and it is the reason why? If it is the case can anybody advise how to get the keys into an array?
let tokens = afterData.fcm_tokens
console.log('tokens',afterData.fcm_tokens)
tokens { 'fWgm79ifOUVSofW2XXhoKt:APA91bHovn04k5lwnkzfQD74VmekV8FoTSUh8pyr9d_I0EMDPuzFdKpD7Y4OU_AKHVjalGQaGE_I6A5m6livf8QrxHTSTmn9h6EB9qyBde_reQRcjU6cZLHLLXx2cO0w6f3MOZOFnYDv': 0 }
Error: Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.
at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:254:16)
at Messaging.validateRegistrationTokensType (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:729:19)
at Messaging.sendToDevice (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:328:14)
at exports.sendChatNotification.functions.database.ref.onWrite (/srv/index.js:27:26)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:134:23)
at /worker/worker.js:825:24
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
Finally I got it work after a few of workarounds. It turns out that it is quite simple. Just catch keys with forEach syntax to iterate through the json.
const tokens = Object.keys(afterData.fcm_tokens)
For more detail, please see below.
Error with registration token in FCM with Cloud Functions

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

Firebase CLI for one to one device notification

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.

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.

Unit test Actions on Google Dialogflow locally

I'm trying to unit test a DialogflowApp locally by using the firebase shell environment. (in a cli do firebase experimental:functions:shell and then call my methods)
I have followed this guide by google https://firebase.google.com/docs/functions/local-emulator but they don't use the DialogflowApp where the invoked function tries to bind a request object containing intents and parameters like this ->
exports.myFunction = functions.https.onRequest((request, response) => {
const app = new App({ request, response });
function myMethod(app) {
let myArgument = app.getArgument(MY_ARGUMENT);
app.tell('Here we are responding');
}
let actionMap = new Map();
actionMap.set(MYMETHOD_ACTION, myMethod);
app.handleRequest(actionMap);
});
Regardless of what request object I send in the CLI, like this myFunction(require("../test/testdata.json")), the request body object is empty, like this body: {} which means I can't do app.handleRequest() or app.getArgument(). The error message I get is
RESPONSE RECEIVED FROM FUNCTION: 400, Action Error: no matching intent
handler for: null
I thought that if I populated testdata.json with the json request data shown in Actions on Google -> console.actions.google.com -> Simulator it would be valid data but no.
My question is, how can i mock my request data so that I can start unit testing my fullfillment methods locally?
EDIT 1:
firebase > myMethod.post("/").form(require("../test/testdata.json"))
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property `parameters` of 'undefined' or 'null'.
if we look in dialogflow_app.js we can see this code for fetching an argument value
getArgument (argName) {
debug('getArgument: argName=%s', argName);
if (!argName) {
error('Invalid argument name');
return null;
}
const { parameters } = this.body_.result;
if (parameters && parameters[argName]) {
return parameters[argName];
}
return this.getArgumentCommon(argName);
}
this.body_ is always just empty {}, regardless of how and what I send into the method when running locally.
EDIT 3
firebase > myMethod({method: "post",json: true, body: require("../test/testdata.json")})
Sent request to function.
firebase > info: User function triggered, starting execution
info: Function crashed
info: TypeError: Cannot destructure property parameters of 'undefined' or 'null'.
Invoking a Firebase HTTPS function using the shell requires a different form. It takes the parameters that the request module does, so in order to emulate a webhook, it will be something like this:
myfunction({
method: 'POST',
json: true,
body: require("../test/testdata.json")
});
These three parameters are important:
You need to specify that this is a POST operation
You need to indicate that the body will be JSON. This will send the correct header and won't try to send the body as x-www-form-urlencoded
You need to include the body. As an object is ok because you've set the json parameter to true.

Resources