Conditionals within Firebase Functions - javascript

This is for using conditionals with Firebase Functions (node.js). I am a total noob when it comes to Javascript so please bear with me. I am trying to send push notifications with different payload (sound) depending on the category of the food ordered. I tried the following code but no notifications are sent. If I removed the if-else conditionals then notifications are sent. Not sure what I am doing wrong. The code compiled fine. Thanks guys. The log showed "ReferenceError: payload is not defined
at admin.database.ref.once.then.allToken (/user_code/index.js:60:58)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)"
exports.sendRequestNotification = functions.database.ref('/Requests/{id}').onCreate((snap, context) => {
const snapShot = snap.val();
console.log(snapShot);
if (snap.child("RequestItemCategory").val() === 1) {
const payload = {
notification: {
title: 'Food Ordered',
body: 'Seat Number: ' + snap.child("Seat").val() + ' Request Item: ' + snap.child("RequestItem").val(),
sound: 'food.wav'
}
};
} else {
const payload = {
notification: {
title: 'New Request Received',
body: 'Seat Number: ' + snap.child("Seat").val() + ' Request Item: ' + snap.child("RequestItem").val(),
sound: 'drinks.wav'
}
};
}
return admin.database().ref('/Tokens').once('value').then(allToken => {
const token = Object.keys(allToken.val());
console.log(token);
return admin.messaging().sendToDevice(token, payload)
});
});

A const is only visible within the block where it was declared. You're declaring two different const payload vars embedded in a block that's not accessible to where you're trying to use it. You'll need to declare it like this instead:
let payload;
if (...) {
payload = ...
} else {
payload = ...
}
Then you can use payload immediately after the conditional, because it will be in scope for your call to the admin SDK.

Related

Send notifications conditionally using Firebase cloud functions?

I am writing an android application where I need to send a notification based on some condition.
For example, when notiType = home then send other message in notification. If notiType = inBetween then send another message
I have written the cloud function for this but getting an error while deploying.
Here is the cloud function :
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/* Listens for new messages added to /messages/:pushId and sends a notification to users */
exports.pushNotification = functions.database.ref('/Notifications/{user_id}/{notification_id}').onWrite(event => {
console.log('Push notification event triggered');
/* Grab the current value of what was written to the Realtime Database
*/
const userId = event.params.user_id;
const notificationId = event.params.notification_id;
const deviceToken = admin.database().ref(`Notifications/${userId}/${notificationId}/deviceToken`).once('value');
const childName = admin.database().ref(`Notifications/${userId}/${notificationId}/childName`).once('value');
const notificationType = admin.database().ref(`Notifications/${userId}/${notificationId}/type`).once('value');
return Promise.all([deviceToken, childName, notificationType]).then(result => {
const token = result[0].val();
const name = result[1].val();
const type = result[2].val();
/* Create a notification and data payload. They contain the notification information, and message to be sent respectively */
const payload;
switch (type) {
case "home":
payload = {
notification: {
title: 'App Name',
body: `${name} is reached at home`,
sound: "default"
}
};
break;
case "between":
payload = {
notification: {
title: 'App Name',
body: `${name} stuck on the way for some reason`,
sound: "default"
}
};
break;
case "school":
payload = {
notification: {
title: 'App Name',
body: `${name} reached at school`,
sound: "default"
}
};
break;
};
return admin.messaging().sendToDevice(token, payload).then(response => {
return null;
});
});
});
Getting this error :
Please correct me where I am going wrong. Using Firebase -tools version 5.0.1
JavaScript is telling you this line is invalid:
const payload;
You can't declare a const variable without also giving it a value immediately. Since you are conditionally giving it a value later, perhaps you should use let payload; instead.

Undefined username in cloud functions?

I want to send a notification to a specific device so I write this function and its work right but I got undefined in the username
Logs output:
Get this
after: { '-LhjfwZeu0Ryr6jYRq5r': { Price: '888', date: '2019-6-19', description: 'Ghh', id: 50, nameOfProblem: 'Vbh', providerName: 'Loy', providerService: 'Carpenter', statusInfo: 'Incomplete', time: '15:22', username:"devas" }}
And the username is undefined
Here is the function
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders')
.onWrite(async (snapshot, context) => {
const registrationTokens = "------";
const providerId = context.params.pid;
const userId = context.params.uid;
const event = context.params;
console.log("event", event);
console.log(`New Order from ${userId} to ${providerId}`);
const afterData = snapshot.after.val(); // data after the write
const username = snapshot.after.val().username;
console.log(afterData);
console.log(username);
const payload = {
notification: {
title: 'Message received',
body: `You received a new order from ${username} check it now! `,
sound: "default",
icon: "default",
}
};
try {
const response = await admin.messaging().sendToDevice(registrationTokens, payload);
console.log('Successfully sent message:', response);
}
catch (error) {
console.log('Error sending message:', error);
}
return null;
});
It looks like the code you wrote is meant to run when a new order is added to the database. But you've declared it to trigger like this:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders')
.onWrite(async (snapshot, context) => {
This means that the code instead triggers whenever anything is written under the orders node for a user. To trigger only when an order is written under that orders node, define your trigger as:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders/{orderid}')
.onWrite(async (snapshot, context) => {
The difference above is that the path now includes {orderid} meaning that it triggers one level lower in the tree, and your snapshot.after will no longer contain the -L level.
Since you actually only seem to care about when an order gets created, you can also only trigger on that (meaning your function won't get called when an order gets updated or deleted). That'd be something like this:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders/{orderid}')
.onCreate(async (snapshot, context) => {
...
const afterData = snapshot.val();
const username = snapshot.val().username;
console.log(afterData);
console.log(username);
...
});
Here we again trigger on the lower-level in the JSON. But since we now trigger onCreate, we no longer have a before and after snapshot, and instead just do snapshot.val() to get the data that was just created.
Since the object you are retrieving has a generated member you could use a for-in loop to retrieve the value.
const object = snapshot.after.val()
for(const key in object) {
if (object.hasOwnProperty(key)) {
const element = object[key];
if(element.username) {
console.log(element.username);
break;
}
}
}

How to trouble shoot "NaN" error when the call to a function and assigning to let is the only visible issue

I'm testing an Alexa skill locally and getting an error that just says NaN. I have figured out that line let recipe = getRecipe() is the problem through console.log() statements. It doesn't appear to be in the the getRecipe() function itself because a console.log() statement at the very beginning of the try block in that function does not run, but the one at the beginning of the catch does. Thanks in advance for any suggestions.
Handler:
handle(handlerInput){
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
switch (attributes.previousIntent){
case "FoodIntent":
if(request.intent.slots.answer.resolutions.resolutionsPerAuthority[0].values[0].value.name === 'yes'){
let randomFood = Helpers.suggestFood(handlerInput);
let queryFood = randomFood.replace(/\s+/g, '-').toLowerCase(); event
attributes.currentSuggestedFood = queryFood;
const speechText = 'Great! In the future I will be able to look up the ingredients for you.'
console.log('before call getRecipe()')
let recipe = getRecipe(handlerInput)
console.log('After call getRecipe()')
return handlerInput.responseBuilder
.speak(speechText + " "+ recipe)
.reprompt(speechText)
.withShouldEndSession(true)
.withSimpleCard('Cheer Up - YesNo', speechText)
.getResponse();
} else {
let randomFood = Helpers.suggestFood(handlerInput);
let speechText = ResponseToUsersNo[Math.floor(Math.random() * ResponseToUsersNo.length)]+
FoodPrefixes[Math.floor(Math.random() * FoodPrefixes.length)] +
randomFood + FoodSuffixes[Math.floor(Math.random() * FoodSuffixes.length)];
let repromptText = 'Did the last suggestion work for you?'
handlerInput.attributesManager.setSessionAttributes(attributes);
if (attributes.FoodsAlreadySuggested.length >= 10) {
speechText = 'I feel like you don\'t actually want anything. So I\'m leaving for now, talk to you later.'
return handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(true)
.withSimpleCard('Cheer Up - YesNo', speechText)
.getResponse();
}
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(repromptText)
.withSimpleCard('Cheer Up - YesNo', speechText)
.getResponse();
}
case "HobbyIntent":
if(request.intent.slots
And the getRecipe() function:
async function getRecipe(handlerInput) {
try{
console.log('before attributes')
const attributes = handlerInput.attributesManager.getSessionAttributes();
console.log('attributes: '+ attributes)
console.log('before url')
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`; //&from=0&to=3&calories=591-722&health=alcohol-free this was on the end of the uri
console.log(url)
console.log('after url')
request.get(url, (error, response, body) => {
// let json = JSON.parse(body);
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the body
//const theRecipe = await body;
const payload = JSON.parse(body)
console.log("The ingredients for "+ payload.q + " is: ")
console.log(payload.hits[0].recipe.ingredientLines)
return (payload.hits[0].recipe.ingredientLines);
});
}
catch(err){
console.log('before error statement in catch')
console.error('There was an error: ', + err)
}
};
Here is my output:
before call getRecipe()
before attributes
attributes: [object Object]
before url
https://api.edamam.com/search?q=rellenos-de-papa&app_id=b4dbea92&app_key=8d916c99b930b77c8cbb4615f0800df7
after url
before error statement in catch
There was an error: NaN
After call getRecipe()
{ version: '1.0',
response:
{ outputSpeech:
{ type: 'SSML',
ssml: '<speak>Great! In the future I will be able to look up the ingredients for you. The ingredients are [object Promise]</speak>' },
reprompt: { outputSpeech: [Object] },
shouldEndSession: true,
card:
{ type: 'Simple',
title: 'Cheer Up - YesNo',
content: 'Great! In the future I will be able to look up the
ingredients for you.' } },
userAgent: 'ask-node/2.3.0 Node/v8.12.0',
sessionAttributes:
{ foodType: 'PuertoRican',
FoodsAlreadySuggested: [ 'Platanos Maduros', 'Rellenos de Papa' ],
previousIntent: 'FoodIntent',
state: '_YES_NO',
currentSuggestedFood: 'rellenos-de-papa' } }
UPDATE:
#Shilly. So I'm still confused... An aside, I had to edit your function a bit to make the code inside the catch reachable... but anyway I think what I did still retains the core logic you were trying to impart.
My problem is that I get an error when I parse that says unexpected token o in JSON at position 1. I think this usually means I don't need to parse it because it's already a valid js object. Cool. So I remove the parse, but then I get Cannot read property '0' of undefined., referring of course to my return payload.hits[0].recipe.ingredientLines. Can't seem to wrap my head around why. Thanks a bunch for your help.
function getRecipe(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`; //&from=0&to=3&calories=591-722&health=alcohol-free this was on the end of the uri
return get( url, ( response, body ) => {
const payload = JSON.parse(body)
console.log(payload)
return payload.hits[0].recipe.ingredientLines;
}).catch( error => {
console.error( `failed GET request for: ${ url }` );
console.error( error );
});
};
Also here is the beginning of the body in the response, which doesn't look parsed to me... body: '{\n "q" : "tostones",\n "from" : 0,\n "to" : 10,\n "params" : {\n
Finally figured it out. Many thanks to #Shilly for guiding me in the proper direction. My understanding of async and await was wrong. These sources were helpful:
Returning handler.ResponseBuilder from promise.then() method
https://medium.com/#tkssharma/writing-neat-asynchronous-node-js-code-with-promises-async-await-fa8d8b0bcd7c
Here is my updated code:
The async handler relies on a function that I created to use Promises with #Shilly's help. It's probably not the most concise way, but it works!
Handler:
async handle(handlerInput){
const attributes = handlerInput.attributesManager.getSessionAttributes();
const request = handlerInput.requestEnvelope.request;
switch (attributes.previousIntent){
case "FoodIntent":
if(request.intent.slots.answer.resolutions.resolutionsPerAuthority[0].values[0].value.name === 'yes'){
let randomFood = Helpers.suggestFood(handlerInput);
let queryFood = randomFood.replace(/\s+/g, '-').toLowerCase();
attributes.currentSuggestedFood = queryFood;
const speechText = 'Great! Here are the ingredients!'
let recipe = await getRecipe(handlerInput)
let recipeIngredients = recipe.hits[0].recipe.ingredientLines;
return handlerInput.responseBuilder
.speak(speechText+ 'The ingredients are '+ recipeIngredients)
.reprompt(speechText)
.withShouldEndSession(true)
.withSimpleCard('Cheer Up - YesIntentFood', recipeIngredients)
.getResponse();
function:
async function getRecipe(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`;
console.log(url)
return new Promise (function(resolve, reject) {
request.get(url, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve(JSON.parse(body))
}
});
})
};
Output:
https://api.edamam.com/search?q=pernil&app_id=b4dbea92&app_key=8d916c99b930b77c8cbb4615f0800df7
{ version: '1.0',
response:
{ outputSpeech:
{ type: 'SSML',
ssml: '<speak>Great! In the future I will be able to look up the ingredients for you.The ingredients are 2 1/2 pounds pork shoulder, boston butt, pernil,2 garlic cloves,1 small onion,1 bunch cilantro,1 jalapeƱo,1 cup orange juice,1 cup pineapple juice,1 lemon,Handfuls salt,Pepper to taste,Ground cumin</speak>' },
reprompt: { outputSpeech: [Object] },
shouldEndSession: true,
card:
{ type: 'Simple',
title: 'Cheer Up - YesIntentFood',
content: [Array] } },
userAgent: 'ask-node/2.3.0 Node/v8.12.0',
sessionAttributes:
{ foodType: 'PuertoRican',
FoodsAlreadySuggested: [ 'Platanos Maduros', 'Pernil' ],
previousIntent: 'FoodIntent',
state: '_YES_NO',
currentSuggestedFood: 'pernil' } }
const attributes = handlerInput.attributesManager.getSessionAttributes() throws an error NaN for reasons still unknown.
The catch() handler catches this error and logs it.
But since the function is async, and you don't resolve the promise inside the catch clause, you get this [object Promise] stringified version of that promise instead of the actual ingredient list.
Edit:
Have you considered using util.promisify() so you don't have to mix callbacks and promises?
const { promisify } = require( 'util' );
const request = require( 'request' );
const get = promisify( request.get );
function getRecipe(handlerInput) {
const attributes = handlerInput.attributesManager.getSessionAttributes();
const url = `https://api.edamam.com/search?q=${attributes.currentSuggestedFood}&app_id=${FOOD_APP_ID}&app_key=${FOOD_APP_KEY}`; //&from=0&to=3&calories=591-722&health=alcohol-free this was on the end of the uri
return get( url, ( response, body ) => {
const payload = JSON.parse(body)
return payload.hits[0].recipe.ingredientLines;
}).catch( error ) {
console.error( `failed GET request for: ${ url }` );
console.error( error );
});
};
Same can be written with async/await style, but I'm not fluent enough in it to get it 100% correct without being able to test the code myself.

How to increment app badge number for each push notification received

I am using firebase cloud functions to send a user push notifications. I dont understand JS well but I would like to be able to auto-increment the apps badge number through the notification payload and increase the number by 1 for each notification recieved. This is what I have now. I have read the documentation for firebase but I dont think I have enough JS understanding to figure out what they are describing.
exports.sendPushNotificationLikes = functions.database.ref('/friend-like-push-notifications/{userId}/{postId}/{likerId}').onWrite(event => {
const userUid = event.params.userId;
const postUid = event.params.postId;
const likerUid = event.params.likerId;
if (!event.data.val()) {
return;
}
// const likerProfile = admin.database().ref(`/users/${likerUid}/profile/`).once('value');
const getDeviceTokensPromise = admin.database().ref(`/users/${userUid}/fcmToken`).once('value');
// Get the follower profile.
const getLikerProfilePromise = admin.auth().getUser(likerUid);
return Promise.all([getDeviceTokensPromise, getLikerProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const user = results[1];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'New Like!',
body: '${user.username} liked your post!',
sound: 'default',
badge: += 1.toString()
}
};
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
Thanks in advance for any help
I'm guessing this is what the issue is:
const payload = {
notification: {
title: 'New Like!',
body: '${user.username} liked your post!',
sound: 'default',
badge: += 1.toString()
}
};
Assume you have a notification count property available in your schema say notificationCount then you can do this:
const payload = {
notification: {
title: 'New Like!',
body: `${user.username} liked your post!`,
sound: 'default',
badge: Number(notificationCount++) // => notificationCount + 1
}
};
Also on this body: '${user.username} liked your post!', this will be saved as "user.username like your post!". This is not the behaviour you want, what you should be doing is this:
body: `${user.username} liked your post!`
Assuming this is the line in question:
badge: += 1.toString()
Careful of type conversion assumptions. Adding "1" + "1" will give you "11", not "2". Why not try something like:
badge: `${targetUser.notificationCount + 1}`
This is assuming the notificationCount is a key in your schema, and that it is typed as a string. You will need to persist the target user's notification count somewhere so it can be incremented when a new notification comes in. It could also be an integer and then the string interpolation is unnecessary, i.e.:
badge: targetUser.notificationCount + 1
Also, be aware that your string interpolation here needs to be wrapped in backticks instead of single quotes, i.e.:
body: `${user.username} liked your post!`
I can't tell how the interactions are mapped in your database. This approach requires persisting and updating the notification count of the target user.

Firebase functions FCM

I'm building an app with Firebase and as they added functions I wanted to try this out but ran into a few errors as I am unfamiliar with this language... I'm trying to send an FCM to every user of a group (when a new one is added to the database) and I used the example I found online but still ran into some trouble.
exports.sendPush = functions.database.ref('/groups/{groupId}').onWrite(event => {
const groupId = event.params.groupId;
... // defining constants like msg
const participators = admin.database().ref('/groups/' + groupId + '/users').once('value');
let getDeviceTokensPromise = []
for (let part in participators) {
getDeviceTokensPromise.push(admin.database().ref('/users/' + part + '/notificationtoken')).once('value');
}
return Promise.all([getDeviceTokensPromise, participators]).then(results => {
const tokensSnapshot = results[0];
const follower = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
console.log('Fetched follower profile', follower);
// Notification details.
const payload = {
notification: {
title: 'New meeting!',
body: msg
}
};
// Listing all tokens.
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
...
So I guess my mistake must be in the first few lines as all the rest follows this code (I left out the unimportant bits)... Here is my firebase architecture:
The groups branch of the firebase database
One user under the branch users
Regards
Your code is fine. Just change the following
const participators = admin.database().ref('/groups/' + groupId + '/users').once('value');
and
getDeviceTokensPromise.push(admin.database().ref('/users/' + part + '/notificationtoken')).once('value');
to these :-
const participators = admin.database().ref(`/groups/${groupId}/users`).once('value');
and
getDeviceTokensPromise.push(admin.database().ref(`/users/${part}/notificationtoken`)).once('value');
Also, make sure that you use `` and not ' ' inside the ref part.

Categories

Resources