I have an Android app To organize events
I am trying to send a Notification to subscribers about the changes in time or date of the event.
When I used the following example, the function worked fine
//Cloud Functions Modules
const functions = require('firebase-functions');
//Firebase Admin SDK Modules (it will send the Notifications to the user)
const admin = require('firebase-admin');
//init Admin SDK
admin.initializeApp(functions.config().firebase);
exports.changeventTime = functions.database.ref('/user-
event/{authUid}/{key}/eventTime/')
.onWrite(event => {
var eventKey = event.params.key;
var authUid = event.params.authUid;
var eventSnapshot = event.data;
var newTime = eventSnapshot.val();
var eventTopic = "notifications_"+eventKey;
var payload = {
data: {
pushTyp: 'changTime',
time: newTime,
key: eventKey,
authuid: authUid
}
};
// Send a message to devices subscribed to the provided topic.
return admin.messaging().sendToTopic(eventTopic, 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);
});
});
But when I tried to use "return Promise.all()" The function did not work!
As in the following example:
//Cloud Functions Modules
const functions = require('firebase-functions');
//Firebase Admin SDK Modules (it will send the Notifications to the user)
const admin = require('firebase-admin');
//init Admin SDK
admin.initializeApp(functions.config().firebase);
exports.changeventTime = functions.database.ref('/uesr-
event/{authUid}/{eventKey}/eventTime/')
.onWrite(event => {
const eventKey = event.params.eventKey;
const authUid = event.params.authUid;
const eventTopic = "notifications_"+eventKey;
const eventSnapshot = event.data;
const newTime = eventSnapshot.val();
const getevent = admin.database().ref(`user-event/${authUid}/${eventKey}/`).once('value');
return Promise.all(getevent).then(results => {
const eventSnapshot = results[0];
const eventNumber = eventSnapshot.val().eventNumber;
const eventDescription = eventSnapshot.val().eventDescription;
const eventTime = eventSnapshot.val().eventTime;
const payload = {
data: {
pushTyp: 'changeTime',
time: eventTime,
key: eventKey,
authuid: authUid,
number: eventNumber,
dscr: eventDescription
}
};
// Send a message to devices subscribed to the provided topic.
return admin.messaging().sendToTopic(eventTopic, 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);
});
});
});
Related
I am trying to use a Firebase callable function to create a Twilio token for a React project. The project should allow video calls using Twilio's webRTC service. The code is based on the example here https://www.twilio.com/blog/video-chat-react-hooks. I am attempting to move the server code to obtain the Twilio token for the video service into the Firebase callable function.
My console gives the following output and errors:
error check 1: handleSubmit called
error check 2: getToken function retrieved from functions ƒ (r){return n.call(e,r,t||{})}
error check 4: token set
POST https://us-central1-vid-chat-app.cloudfunctions.net/getToken 500
Uncaught (in promise) Error: INTERNAL
at new m (error.ts:66)
at b (error.ts:175)
at P.<anonymous> (service.ts:244)
at tslib.es6.js:100
at Object.next (tslib.es6.js:82)
at a (tslib.es6.js:71)
Here is the error from the Firebase console
Unhandled error Error: accountSid is required
at new AccessToken (/srv/node_modules/twilio/lib/jwt/AccessToken.js:213:28)
at generateToken (/srv/index.js:9:12)
at videoToken (/srv/index.js:23:19)
at exports.getToken.functions.https.onCall (/srv/index.js:42:19)
at func (/srv/node_modules/firebase-functions/lib/providers/https.js:273:32)
at corsHandler (/srv/node_modules/firebase-functions/lib/providers/https.js:293:44)
at cors (/srv/node_modules/cors/lib/index.js:188:7)
at /srv/node_modules/cors/lib/index.js:224:17
at originCallback (/srv/node_modules/cors/lib/index.js:214:15)
at /srv/node_modules/cors/lib/index.js:219:13
These are the two main files I believe are relevant
React file
import React, { useState, useCallback } from 'react';
import firebase from './firebase'
import Lobby from './Lobby';
import Room from './Room';
const VideoChat = () => {
const [username, setUsername] = useState('');
const [roomName, setRoomName] = useState('');
const [token, setToken] = useState(null);
const handleUsernameChange = useCallback(event => {
setUsername(event.target.value);
}, []);
const handleRoomNameChange = useCallback(event => {
setRoomName(event.target.value);
}, []);
const handleSubmit = useCallback(async event => {
console.log("error check 1: handleSubmit called")
event.preventDefault();
const getToken = firebase.functions().httpsCallable('getToken')
console.log("error check 2: getToken function retrieved from functions", getToken)
getToken({ identity: username, room: roomName })
.then(result => {
console.log("error check 3: calling .then")
setToken(result.data)
})
console.log("error check 4: token set")
}, [username, roomName]);
const handleLogout = useCallback(event => {
setToken(null);
}, []);
let render;
if (token) {
render = (
<Room roomName={roomName} token={token} handleLogout={handleLogout} />
);
} else {
render = (
<Lobby
username={username}
roomName={roomName}
handleUsernameChange={handleUsernameChange}
handleRoomNameChange={handleRoomNameChange}
handleSubmit={handleSubmit}
/>
);
}
return render;
};
export default VideoChat;
Firebase function
const twilio = require("twilio");
const functions = require('firebase-functions');
const config = require('./config');
const AccessToken = twilio.jwt.AccessToken;
const { VideoGrant } = AccessToken;
const generateToken = config => {
return new AccessToken(
config.twilio.accountSid,
config.twilio.apiKey,
config.twilio.apiSecret
);
};
const videoToken = (identity, room, config) => {
let videoGrant;
if (typeof room !== "undefined") {
videoGrant = new VideoGrant({ room });
} else {
videoGrant = new VideoGrant();
}
const token = generateToken(config);
token.addGrant(videoGrant);
token.identity = identity;
return token;
};
const sendTokenResponse = (token, res) => {
res.set('Content-Type', 'application/json');
res.send(
JSON.stringify({
token: token.toJwt()
})
);
};
exports.getToken = functions.https.onCall((data, context)=>{
console.log('Peter error check getToken function called')
const identity = data.identity;
const room = data.room;
const token = videoToken(identity, room, config);
sendTokenResponse(token, res);
console.log('here is the token', token)
return token
});
Config file also contained in Firebase cloud function folder
module.exports = {
twilio: {
accountSid: process.env.TWILIO_ACCOUNT_SID,
apiKey: process.env.TWILIO_API_KEY,
apiSecret: process.env.TWILIO_API_SECRET,
chatService: process.env.TWILIO_CHAT_SERVICE_SID,
outgoingApplicationSid: process.env.TWILIO_TWIML_APP_SID,
incomingAllow: process.env.TWILIO_ALLOW_INCOMING_CALLS === "true"
}
};
my approach to do this is:
1) Environment Configuration: store your environment data.
firebase functions:config:set twilio.sid="The Account SID" twilio.apikey="The api key" twilio.apisecret="The api secret"
2) Firebase Function: use https.onCall
const functions = require('firebase-functions');
const AccessToken = require('twilio').jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
*//call environment data*
const accountSid = functions.config().twilio.accountsid;
const apiKey = functions.config().twilio.apikey;
const apiSecret = functions.config().twilio.secret;
*//function to generate token*
const generateToken = () => {
return new AccessToken(accountSid, apiKey, apiSecret);
};
*//function to generate video grant*
const videoToken = (identity, room) => {
videoGrant = new VideoGrant({ room });
const token = generateToken();
token.addGrant(videoGrant);
token.identity = identity;
return token;
};
*//firebase function*
exports.twilio = functions.https.onCall((data, context) => {
const identity = data.identity;
const room = data.room;
const token = videoToken(identity, room);
return token.toJwt();
}});
3) React code:
const handleSubmit = useCallback( async () => {
try {
const twilioRequest = firebase
.functions()
.httpsCallable('twilio');
const response = await twilioRequest({
identity: username,
room: roomName,
});
setToken(response.data);
} catch (err) {
console.log(err);
}
},[username, roomName]);
4) Allowing unauthenticated HTTP function invocation
Be sure you have permissions granted https://cloud.google.com/functions/docs/securing/managing-access-iam#allowing_unauthenticated_function_invocation
How I can save a payload after sending them to specific Token in the specific node " Notifications/" to retrieve it in single screen later,
and it saves very well,
but when I got a notification I see providerName as a undefined when I declare a variable "providerName"
const functions = require("firebase-functions");
const admin = require("firebase-admin");
var serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://khadamatiapp-42657.firebaseio.com"
});
exports.acceptedOrder = functions.database
.ref("/AcceptedOrders/{pid}/{orderid}")
.onCreate(async (snapshot, context) => {
const registrationTokens = snapshot.val().userToken;
// const event = context.params;
const pid = context.params.pid;
console.log("#pid", pid);
const username = snapshot.val().username;
const userUid = snapshot.val().userUid;
var providerName;
admin
.database()
.ref(`providers/${pid}`)
.once("value")
.then(snapshot => {
providerName = snapshot.val().username;
console.log("pName", providerName); // here i got ProviderOne
});
console.log("#providerName", providerName); //here i got undefined
const payload = {
notification: {
from: pid,
to: userUid,
title: "New Order",
body: `Hi ${username}, You Order is Accepted from ${providerName}, check it now! `
//Hi userOne, You Order is Accepted from ***Undefined***, check it now!
}
};
try {
let notification = payload.notification;
const response = await admin
.messaging()
.sendToDevice(registrationTokens, payload)
.then(() => {
admin
.database()
.ref(`Notifications/${userUid}`)
.push({ notification });
});
console.log("Successfully sent message:", response);
} catch (error) {
console.log("Error sending message:", error);
}
return null;
});
Update
I have three functions and it's a trigger in the same root,
now acceptedOrderFromProvider that's invoked when I create new Element in the "AcceptedOrders" Root and send a push notification
and another function is CompletedOrderFromProvider that's trigger if the status changed, send a notification I use an onUpdate rigger but doesn't work well,
it's invoked when every element created or updated,
so how to force it to invoke just when some field "status" changed?
check here image
exports.acceptedOrderFromProvider = functions.database
.ref("/AcceptedOrders/{pid}/{orderid}")
.onCreate(async (snapshot, context) => {
const registrationTokens = snapshot.val().userToken;
// const event = context.params;
const pid = context.params.pid;
// console.log("#pid", pid);
const username = snapshot.val().username;
const userUid = snapshot.val().userUid;
var providerName;
admin
.database()
.ref(`providers/${pid}`)
.once("value")
.then(async snapshot => {
providerName = snapshot.val().username;
const payload = {
notification: {
from: pid,
to: userUid,
title: "Accepted Order",
body: `Hi ${username}, You Order is Accepted from ${providerName}, check it now! `
}
};
try {
let notification = payload.notification;
const response = await admin
.messaging()
.sendToDevice(registrationTokens, payload)
.then(() => {
admin
.database()
.ref(`Notifications/${userUid}`)
.push({ notification });
});
console.log("Successfully sent message:", response);
} catch (error) {
console.log("Error sending message:", error);
}
});
return null;
});
exports.cancelledOrderFromProvider = functions.database
.ref("/AcceptedOrders/{pid}/{orderid}")
.onDelete(async (snapshot, context) => {
const registrationTokens = snapshot.val().userToken;
// const event = context.params;
const pid = context.params.pid;
// console.log("#pid", pid);
const afterData = snapshot.val();
// console.log(afterData);
const username = snapshot.val().username;
const userUid = snapshot.val().userUid;
const nameOfProblem = snapshot.val().nameOfProblem;
var providerName;
admin
.database()
.ref(`providers/${pid}`)
.once("value")
.then(async snapshot => {
providerName = snapshot.val().username;
const payload = {
notification: {
from: pid,
to: userUid,
title: "Order Cancelled",
body: `Hi ${username}, ${providerName} Cancelled your Order "${nameOfProblem}"!`
}
};
try {
let notification = payload.notification;
const response = await admin
.messaging()
.sendToDevice(registrationTokens, payload)
.then(() => {
admin
.database()
.ref(`Notifications/${userUid}`)
.push({ notification });
});
console.log("Successfully sent message:", response);
} catch (error) {
console.log("Error sending message:", error);
}
});
return null;
});
exports.CompletedOrderFromProvider = functions.database
.ref("/AcceptedOrders/{pid}/{orderid}")
.onUpdate(async (snapshot, context) => {
console.log(snapshot.after.val());
const registrationTokens = snapshot.after.val().userToken;
const pid = context.params.pid;
const username = snapshot.after.val().username;
const userUid = snapshot.after.val().userUid;
const nameOfProblem = snapshot.after.val().nameOfProblem;
var providerName;
admin
.database()
.ref(`providers/${pid}`)
.once("value")
.then(async snapshot => {
providerName = snapshot.val().username;
const payload = {
notification: {
from: pid,
to: userUid,
title: "Order Completed",
body: `Hi ${username}, ${providerName} Completed your Order "${nameOfProblem}"! Check it Now`
}
};
try {
let notification = payload.notification;
const response = await admin
.messaging()
.sendToDevice(registrationTokens, payload)
.then(() => {
admin
.database()
.ref(`Notifications/${userUid}`)
.push({ notification });
});
console.log("Successfully sent message:", response);
} catch (error) {
console.log("Error sending message:", error);
}
});
return null;
});
Code inside a .then() is run asynchronously, so even though it appears above the rest of the code in the function, it may not be called until much later. Put all code that works with providerName inside the .then() callback to ensure it is called only after providerName has been retrieved:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
var serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://khadamatiapp-42657.firebaseio.com"
});
exports.acceptedOrder = functions.database
.ref("/AcceptedOrders/{pid}/{orderid}")
.onCreate(async (snapshot, context) => {
const registrationTokens = snapshot.val().userToken;
// const event = context.params;
const pid = context.params.pid;
console.log("#pid", pid);
const username = snapshot.val().username;
const userUid = snapshot.val().userUid;
var providerName;
admin
.database()
.ref(`providers/${pid}`)
.once("value")
.then(snapshot => {
providerName = snapshot.val().username;
const payload = {
notification: {
from: pid,
to: userUid,
title: "New Order",
body: `Hi ${username}, You Order is Accepted from ${providerName}, check it now! `
}
};
try {
let notification = payload.notification;
const response = await admin
.messaging()
.sendToDevice(registrationTokens, payload)
.then(() => {
admin
.database()
.ref(`Notifications/${userUid}`)
.push({ notification });
});
console.log("Successfully sent message:", response);
} catch (error) {
console.log("Error sending message:", error);
}
});
return null;
});
Im trying to write a function with firebase cloud function which will send an email as soon as a new message is added to my "contactMessages" realtime database. so i did this but here i got an undefined snapshot :
const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const gmailEmail =
encodeURIComponent(functions.config().gmail.email);
const gmailPassword =
encodeURIComponent(functions.config().gmail.password);
const mailTransport = nodemailer.createTransport(
`smtps://${gmailEmail}:${gmailPassword}#smtp.gmail.com`
);
exports.sendContactMessage = functions.database
.ref("/contactMessages/{pushKey}")
.onWrite((change, context) => {
// Only send email for new messages.
if (snapshot.previous.val() || !snapshot.val().name) {
return;
}
const val = snapshot.val();
const mailOptions = {
to: "test#example.com",
subject: `Information Request from ${val.name}`,
html: val.html
};
return mailTransport.sendMail(mailOptions).then(() => {
return console.log("Mail sent to: test#example.com");
});
});
Change this:
exports.sendContactMessage = functions.database
.ref("/contactMessages/{pushKey}")
.onWrite((change, context) => {
// Only send email for new messages.
if (snapshot.previous.val() || !snapshot.val().name) {
return;
}
into this:
exports.sendContactMessage = functions.database
.ref("/contactMessages/{pushKey}")
.onWrite((change, context) => {
// Only send email for new messages.
if (change.before.val() || !change.after.val().name) {
return;
}
From the docs:
For onWrite and onUpdate events, the change parameter has before and after fields. Each of these is a DataSnapshot with the same methods available in admin.database.DataSnapshot.
I wrote this to detect a docment change,when it changes i want to send notifications to all the users who all are inside the Collection "users"
the problem is How to choose all docments inside a collection??
/*eslint-disable */
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification23 = functions.firestore.document("student/anbu").onWrite(event => {
//now i'm returning to my personal document and fetched my username only because i don't want to send a notification to myself,
const fromUser = admin.firestore().collection("users").doc("iamrajesh#gmail.com").get();
//here i want to fetch all documents in the "users" collection
const toUser = admin.firestore().collection("users").document.get();//if i replace "docmument" with "doc("xxxxxxx#gmail.com")" it works it fetches his FCM but how to fetch all documents??
//All documents has a "username",and a fcm "token"
return Promise.all([fromUser, toUser]).then(result => {
const fromUserName = result[0].data().userName;
const toUserName = result[1].data().userName;
const tokenId = result[1].data().tokenId;
const notificationContent = {
notification: {
title: fromUserName + " is shopping",
body: toUserName,
icon: "default",
sound : "default"
}
};
return admin.messaging().sendToDevice(tokenId, notificationContent).then(result => {
console.log("Notification sent!");
//admin.firestore().collection("notifications").doc(userEmail).collection("userNotifications").doc(notificationId).delete();
});
});
});
The following should do the trick.
See the explanations within the code
/*eslint-disable */
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification23 = functions.firestore.document("student/anbu").onWrite((change, context) => {
// Note the syntax has change to Cloud Function v1.+ version (see https://firebase.google.com/docs/functions/beta-v1-diff?0#cloud-firestore)
const promises = [];
let fromUserName = "";
let fromUserId = "";
return admin.firestore().collection("users").doc("iamrajesh#gmail.com").get()
.then(doc => {
if (doc.exists) {
console.log("Document data:", doc.data());
fromUserName = doc.data().userName;
fromUserId = doc.id;
return admin.firestore().collection("users").get();
} else {
throw new Error("No sender document!");
//the error is goinf to be catched by the catch method at the end of the promise chaining
}
})
.then(querySnapshot => {
querySnapshot.forEach(function(doc) {
if (doc.id != fromUserId) { //Here we avoid sending a notification to yourself
const toUserName = doc.data().userName;
const tokenId = doc.data().tokenId;
const notificationContent = {
notification: {
title: fromUserName + " is shopping",
body: toUserName,
icon: "default",
sound : "default"
}
};
promises.push(admin.messaging().sendToDevice(tokenId, notificationContent));
}
});
return Promise.all(promises);
})
.then(results => {
console.log("All notifications sent!");
return true;
})
.catch(error => {
console.log(error);
return false;
});
});
I am using cloud functions as a means to send users push notifications for my app. I am following the template set by google Here. The problem I have is that I dont really understand Javascript well and I have my user profile data stored within a profile node. When I call the getUser() function I am not able to access the user info within the profile node. How can I access the data within the users profile node so that I can display their "username" and "profileImage" in the push notifications.
Data Structure
users/uid/profile/Dictionary(key/val pairings here for data).
Google Data Structure
users/uid/displayName
Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendPushNotificationRep = functions.database.ref('/recieved-friend-requests/{userId}/{friendId}').onWrite(event => {
const userID = event.params.userId;
const friendID = event.params.friendId;
if (!event.data.val()) {
return;
}
const getDeviceTokensPromise = admin.database().ref(`/users/${userID}/fcmToken`).once('value');
// Get the follower profile.
const getRepProfilePromise = admin.auth().getUser(friendID);
return Promise.all([getDeviceTokensPromise, getRepProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const friend = results[1];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'Rep Request!',
body: `${friend.username} sent you a request`,
badge: '1',
sound: 'default',
icon: 'logo3'
}
};
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);
});
});
});