Error with registration token in FCM with Cloud Functions - javascript

I have deployed the following JavaScript to Firebase Cloud Function but I am constantly getting the following error even though I know that there is a token saved under the path specified.
The idea is to trigger a notification to device when a new message is written to the database, it then fetches the registration token which is then used to try and send a notification to the user.
Error: Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.
JS Function:
exports.notifyNewMessage = functions.database.ref('/messages/{pushId}/{message}').onCreate((snap, context) => {
const message = snap.val();
const fromId = message['fromId'];
const toId = message['toId'];
const messageTxt = message ['message'];
const imageUrl = message ['imageUrl'];
return admin.database().ref('/fcmtokens/' + toId + '/registration-tokens').once('value').then((userTok) => {
const registrationTokens = userTok.val()
console.log(registrationTokens);
return admin.database().ref('/users' + fromId).once('value').then((userDoc) => {
const senderName = userDoc.firstName //get('firstName')
const notificationBody = (imageUrl === "") ? "You received a new image message." : messageTxt
//build media messages notification
const payload = {
notification: {
title: senderName + " sent you a message",
body: messageTxt
},
data: {
SENDER_NAME: senderName,
SENDER_ID: fromId
}//end data
}//end payload
//send message
return admin.messaging().sendToDevice(registrationTokens, payload).then( response => {
const stillRegisteredTokens = registrationTokens
response.results.forEach((result, index) => {
const error = result.error
if (error) {
const failedRegistrationToken = registrationTokens[index]
console.error('blah', failedRegistrationToken, error)
if (error.code === 'messaging/invalid-registration-token'
|| error.code === 'messaging/registration-token-not-registered') {
const failedIndex = stillRegisteredTokens.indexOf(failedRegistrationToken)
if (failedIndex > -1) {
stillRegisteredTokens.splice(failedIndex, 1)
}
}
}
})//end forEach
return admin.database().ref("fcmtokens/" + recipientId).update({
registrationTokens: stillRegisteredTokens
})//end update
})//end sendToDevice
})//end return-then
})//end return-then
});
This is my fcmtokens database structure node:
"fcmtokens" : {
"dBQdpR7l1WT2utKVxdX2" : {
"registration-tokens" : {
"c4PSCAUAg5s:Yw95DyVxwElE88LwX7" : true
}
}
}
Question update:
The update database part of my function above is creating a separate branch called registrationTokens of all active tokens. I want to overwrite and update the current token under registration-tokens instead?
return admin.database().ref("fcmtokens/" + recipientId).update({
registrationTokens: stillRegisteredTokens
})//end update
Image showing the new registrationTokens branch being created.

Given your data structure, this code:
admin.database().ref('/fcmtokens/' + toId + '/registration-tokens').once('value').then((userTok) => {
const registrationTokens = userTok.val()
Leads to registrationTokens being:
{
"c4PSCAUAg5s:Yw95DyVxwElE88LwX7" : true
}
You then call sendToDevice with:
return admin.messaging().sendToDevice(registrationTokens, payload).then( response => {
But if you look at the reference documentation for `sendToDevice, it says:
registrationTokens
Array of string
An array of registration tokens for the devices to which to send the message.
And clearly your registrationTokens is not an array, but just an object.
To convert it to an array use Object.keys():
const registrationTokens = Object.keys(userTok.val())

Related

How do I parse an object coming back from AWS SES SDK in JavaScript?

I'm trying to use AWS SES getIdentityVerificationAttributes via code, and I'm having trouble parsing the object coming back.
I've made a mess out of the code below, but AWS says it is coming back as a map, so I tried doing a .get() and it threw an error that .get() is not a function. So I tried working with Object.entries but still I can't find a good way of returning the VerificationStatus of this object. I expected JSON.Parse(identityVerify) to work, but it doesn't (getting "ERROR SyntaxError: Unexpected token o in JSON at position 1").
const isEmailAWSVerified = async (Email) => {
try {
console.log('[isEmailAWSVerified] Email: ', Email);
/*
data = {
VerificationAttributes: {
"myEmail#example.com": {
VerificationStatus: "Success", // Could be "Pending", "Failed"...
VerificationToken: "EXAMPLE3VYb9EDI2nTOQRi/Tf6MI/6bD6THIGiP1MVY="
}
}
}
*/
let identityVerify=
await SES.getIdentityVerificationAttributes(
{Identities: [`${Email}`]})
.promise();
console.log('[isEmailAWSVerified] - identityVerify', identityVerify);
const parsedResponse = JSON.Parse(identityVerify);
const ret = Object.entries(identityVerify.VerificationAttributes).map(([key, val]) => {
let status = '';
if (key === Email) {
const tmp = JSON.Parse(val);
console.log('[isEmailAWSVerified -- Inside Map] tmp: ', tmp);
// status = Object.entries(val).map(([key, val]) => {
// return val;
// });
return status;
}
return '';
})
console.log('[isEmailAWSVerified] - ret', ret);
return ret === 'Success' ? 1 : 0;
} catch ( err) {
console.log('[isEmailAWSVerified] - ERROR', err);
return 0;
}
}
I found a good example in PHP -- How can I translate that to JS?
try:
response = self.ses_client.get_identity_verification_attributes(
Identities=[identity])
status = response['VerificationAttributes'].get(
identity, {'VerificationStatus': 'NotFound'})['VerificationStatus']
logger.info("Got status of %s for %s.", status, identity)
except ClientError:
logger.exception("Couldn't get status for %s.", identity)
raise
else:
return status
Actually, we don't need JSON.Parse, because is an object already.
We can get verification status just by using const status = identityVerify.VerificationAttributes[${Email}].VerificationStatus;

Advice converting an onCreate firebase cloud function trigger with FCM Messaging to support async/await and database reads

I initially had a simple firebase cloud function that sent out a push notification to a topic when a new message child was created in my real-time database. But I wanted to add message filtering where notifications for messages from some filtered users would be sent only to admin users. For this, I have created user groups in my real-time database of the format {userName: FIRToken}, which gets written to from my iOS App every time it launches and I get a FIRToken. So now I will have to load 2 lists 1) Admin Users, 2) Filtered Users before I can actually decide where to send the notification.
So I looked into ways to do this and async/await seemed better than doing a promise inside a promise for loading my 2 user lists. I then saw a firestore video tutorial where a similar usecase function was converted to use async/await instead of promises in promises. Following that, I refactored my code to await on the 2 snapshots for admin and filtered users, before going on to decide where to send the notification and return a promise. My refactoring seems correct. But unfortunately, my old iPhone is stuck on <DeviceName> is busy: Copying cache files from device. Hence I can't physically login from 2 different devices and test if the notifications are going only to my admin user account. Which is why I am posting my function here to see if I have refactored my code correctly or missed something. Please let me know if I will get the intended results or I should fix something in the code.
Edit: Updated code to fix these issues:
Also, the methods to send messages are very confusing. send needs topic name to be defined in the payload but does not support apns. sendToTopic needs a topic name as an argument with the payload. sendMulticast fails to send messages to users whereas sendToDevice sends properly.
Finally sendToDevice supports sound field in notification field, but send does not.
functions.database
.ref("/discussionMessages/{autoId}/")
.onCreate(async (snapshot, context) => {
// console.log("Snapshot: ", snapshot);
try {
const groupsRef = admin.database().ref("people/groups");
const adminUsersRef = groupsRef.child("admin");
const filteredUsersRef = groupsRef.child("filtered");
const filteredUsersSnapshot = await filteredUsersRef.once("value");
const adminUsersSnapshot = await adminUsersRef.once("value");
var adminUsersFIRTokens = {};
var filteredUsersFIRTokens = {};
if (filteredUsersSnapshot.exists()) {
filteredUsersFIRTokens = filteredUsersSnapshot.val();
}
if (adminUsersSnapshot.exists()) {
adminUsersFIRTokens = adminUsersSnapshot.val();
}
// console.log(
// "Admin and Filtered Users: ",
// adminUsersFIRTokens,
// " ",
// filteredUsersFIRTokens
// );
const topicName = "SpeechDrillDiscussions";
const message = snapshot.val();
// console.log("Received new message: ", message);
const senderName = message.userName;
const senderCountry = message.userCountryEmoji;
const title = senderName + " " + senderCountry;
const messageText = message.message;
const messageTimestamp = message.messageTimestamp.toString();
const messageID = message.hasOwnProperty("messageID")
? message.messageID
: undefined;
const senderEmailId = message.userEmailAddress;
const senderUserName = getUserNameFromEmail(senderEmailId);
const isSenderFiltered = filteredUsersFIRTokens.hasOwnProperty(
senderUserName
);
console.log(
"Will attempt to send notification for message with message id: ",
messageID
);
var payload = {
notification: {
title: title,
body: messageText,
},
data: {
messageID: messageID,
messageTimestamp: messageTimestamp,
},
apns: {
payload: {
aps: {
sound: "default",
},
},
},
};
console.log("Is sender filtered? ", isSenderFiltered);
if (isSenderFiltered) {
adminFIRTokens = Object.values(adminUsersFIRTokens);
console.log("Sending filtered notification with sendMulticast()");
payload.tokens = adminFIRTokens; //Needed for sendMulticast
return admin
.messaging()
.sendMulticast(payload)
.then((response) => {
console.log(
"Sent filtered message (using sendMulticast) notification: ",
JSON.stringify(response)
);
if (response.failureCount > 0) {
const failedTokens = [];
response.responses.forEach((resp, idx) => {
if (!resp.success) {
failedTokens.push(adminFIRTokens[idx]);
}
});
console.log(
"List of tokens that caused failures: " + failedTokens
);
}
return true;
});
} else {
console.log("Sending topic message with send()");
payload.topic = topicName;
return admin
.messaging()
.send(payload)
.then((response) => {
console.log(
"Sent topic message (using send) notification: ",
JSON.stringify(response)
);
return true;
});
}
} catch (error) {
console.log("Notification sent failed:", error);
return false;
}
});

Each then() should return a value or throw

I am trying to send push notifications using cloud functions for a group chat system, but i keep getting this error in my terminal: Each then() should return a value or throw
Why is this happening?
Here's my code:
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/chatrooms/{chatroomId}/chatroom_messages/{chatmessageId}')
.onWrite((snap, context) => {
console.log("System: starting");
console.log("snapshot: ", snap);
console.log("snapshot.after: ", snap.after);
console.log("snapshot.after.val(): ", snap.after.val());
//get the message that was written
let message = snap.after.val().message;
let messageUserId = snap.after.val().user_id;
console.log("message: ", message);
console.log("user_id: ", messageUserId);
//get the chatroom id
let chatroomId = context.params.chatroomId;
console.log("chatroom_id: ", chatroomId);
return snap.after.ref.parent.parent.once('value').then(snap => {
let data = snap.child('users').val();
console.log("data: ", data);
//get the number of users in the chatroom
let length = 0;
for(value in data){
length++;
}
console.log("data length: ", length);
//loop through each user currently in the chatroom
let tokens = [];
let i = 0;
for(var user_id in data){
console.log("user_id: ", user_id);
//get the token and add it to the array
let reference = admin.database().ref("/users/" + user_id);
return reference.once('value').then(snap => {
//get the token
let token = snap.child('messaging_token').val();
console.log('token: ', token);
tokens.push(token);
i++;
//also check to see if the user_id we're viewing is the user who posted the message
//if it is, then save that name so we can pre-pend it to the message
let messageUserName = "";
if(snap.child('user_id').val() === messageUserId){
messageUserName = snap.child('name').val();
console.log("message user name: " , messageUserName);
message = messageUserName + ": " + message;
}
//Once the last user in the list has been added we can continue
if(i === length){
console.log("Construction the notification message.");
let payload = {
data: {
data_type: "data_type_chat_message",
title: "Tabian Consulting",
message: message,
chatroom_id: chatroomId
}
};
return admin.messaging().sendToDevice(tokens, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
return response;
})
.catch(function(error) {
console.log("Error sending message:", error);
});
}
});
}
});
});
The message is caused by eslint detecting that you have a then() handler that can finish without returning a value or throwing an error.
It is caused by your for loop on the following line because you do not return a value or throw if data is empty:
for (var user_id in data) {
As others have commented, your for-loop won't execute correctly because you are returning a promise and finishing the handler on only the first iteration.
for (var user_id in data) {
// ...
return reference.once('value').then(snap => {
// ...
}
Arrays in Firebase RTDB
Based on your code, you encountered some of the issues with working with arrays in the RTDB as detailed in this blog post.
Instead of using an array to keep track of a chat room's members (below), it would be best to use a key-value pair approach instead. The value stored in the key-value pair can be a simple true value; or it can be given meaning (true for admins, false for others).
// Array-based list
"chatrooms/chatroomId1": {
"chatroom_messages": { ... },
"users": [
"userId1",
"userId2",
"userId3"
]
}
// RTDB stores above data as:
"chatrooms/chatroomId1": {
"chatroom_messages": { ... },
"users": {
"0": "userId1",
"1": "userId2",
"2": "userId3"
}
}
// Recommeneded: key-value pairs
"chatrooms/chatroomId1": {
"chatroom_messages": { ... },
"users": {
"userId1": true,
"userId2": false,
"userId3": false
}
}
The main benefit of such an approach is that delete a user from a room is simpler which will help with cleaning spam users/messages. To delete a user, you just call
firebase.database().ref("chatrooms/chatroomId1/users/userId1").delete();
rather than
firebase.database().ref("chatrooms/chatroomId1/users").orderByValue().equalTo("userId1").once('value')
.then((snap) => snap.delete());
Furthermore, sending a notification/message that a user was added or removed could be easily implemented using Cloud Functions defined using:
functions.database.ref('/chatrooms/{chatroomId}/users/{userId}').onCreate(...)
functions.database.ref('/chatrooms/{chatroomId}/users/{userId}').onDelete(...)
Chaining promises
When working with asynchronous tasks, avoid using for loops entirely because they are prone to causing undetectable mistakes and modern Javascript provides better alternatives. One such method is using the Promise.all(someArray.map(value => {...})) idiom covered in this answer.
It was also suggested in the question comments to flatten your promise chain, due to the number of changes needed to do the task efficiently, I decided to just make them and note each change in the code itself. The code below relies on the restructure of the chatroom members list discussed above.
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(); // CHANGED: Cloud Functions provides the needed environment variables to initialize this for you when called without arguments.
exports.sendNotification = functions.database.ref('/chatrooms/{chatroomId}/chatroom_messages/{chatMessageId}') // CHANGED: renamed 'chatmessageId' to 'chatMessageId' (consistent camelCaseStyling)
.onWrite((change, context) => { // CHANGED: renamed 'snap' to 'change' (matches actual type & less ambiguous below)
if (!change.after.exists()) { // CHANGED: Handle when message was deleted
// message deleted. abort
console.log(`Message #${context.params.chatMessageId} in Room #${context.params.chatroomId} deleted. Aborting.`);
return;
}
let messageData = change.after.val(); // CHANGED: avoid calling change.after.val() multiple times
// console.log("New data written: ", messageData); // CHANGED: Removed verbose log commands.
let message = messageData.message;
let messageAuthorId = messageData.user_id; // CHANGED: renamed 'messageUserId' to 'messageAuthorId' (less ambiguous)
let chatroomId = context.params.chatroomId;
console.log("New message:", { // CHANGED: merged log commands (less StackDriver API overhead when deployed)
user_id: messageAuthorId,
chatroom_id: chatroomId,
message: message
});
let chatroomMembersRef = change.after.ref.parent.parent.child('users'); // CHANGED: only got needed data
return chatroomMembersRef.once('value')
.then(snap => {
// DATABASE STRUCTURE CHANGE: "/chatrooms/{chatroomId}/users" - change array (["userId1", "userId2", "userId3"]) to a userId keyed OBJECT (e.g. {"userId1": true, "userId2": true, "userId3": true})
let chatroomMemberList = Object.keys(snap.val()); // CHANGED: renamed 'data' to 'chatroomMemberList' (less ambiguous)
// console.log("Chatroom Members: ", {
// count: chatroomMemberList.length,
// members: chatroomMemberList
// });
// Asyncronously, in parallel, retrieve each member's messaging token
let chatroomMemberTokenPromises = chatroomMemberList.map((memberId) => { // CHANGED: renamed 'user_id' to 'memberId' (less ambiguous, consistent camelCaseStyling)
let memberDataRef = admin.database().ref("/users/" + memberId); // CHANGED: renamed 'reference' to 'memberDataRef' (less ambiguous)
// CHANGED: For each member, get only their registration token (rather than all of their user data)
let getMessagingTokenPromise = memberDataRef.child('messaging_token').once('value').then((memberTokenSnap) => {
console.log("Got messaging token for member #", memberId);
return memberTokenSnap.val();
});
// If this member is the message author, also get their name to prepend to the notification message.
if (memberId === messageAuthorId) {
let prependUserNamePromise = memberDataRef.child('name').once('value')
.then((memberNameSnap) => {
let messageAuthorName = memberNameSnap.val();
console.log("Message author's name: " , messageAuthorName);
message = messageAuthorName + ": " + message;
});
return Promise.all([getMessagingTokenPromise, prependUserNamePromise])
.then(results => results[0]); // only return result of getMessagingTokenPromise
} else {
return getMessagingTokenPromise;
}
});
// Wait for all of the messaging tokens
return Promise.all(chatroomMemberTokenPromises);
})
.then((chatroomMemberTokensArray) => {
console.log("Constructing the notification message...");
let payload = {
data: {
data_type: "data_type_chat_message",
title: "Tabian Consulting",
message: message,
chatroom_id: chatroomId
}
};
return admin.messaging().sendToDevice(chatroomMemberTokensArray, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
console.log("Successfully sent message:", response);
return response;
})
.catch(function(error) {
console.log("Error sending message:", error);
});
})
.catch((error) {
console.log("Unexpected error:", error)
});
});

How to get value inside transaction result Firebase via node js

I'm building an iOS messenger app using Swift, Firebase and Nodejs.
My Goal:
Whenever a user sends a message and writes message data (such as senderId, receiverId, messageText) into a Firebase database inside node (/messages/{pushId}/), I want to make a message count increment by 1 using a transaction method that Firebase provides and display a notification to a receiver user.
Progress I've made so far and Problem I'm facing:
I've successfully increment message count (totalCount) using transaction method but I can't get value inside transaction result (Here's image of functions log )
I want to get "value_: 1"( this is the incremented message count) inside snapshot and put it to a badge parameter.
exports.observeMessages = functions.database.ref('/messages/{pushId}/')
.onCreate((snapshot, context) => {
const fromId = snapshot.val().fromId;
const toId = snapshot.val().toId;
const messageText = snapshot.val().messageText;
console.log('User: ', fromId, 'is sending to', toId);
return admin.database().ref('/users/' + toId).once('value').then((snap) => {
return snap.val();
}).then((recipientId) => {
return admin.database().ref('/users/' + fromId).once('value').then((snaps) => {
return snaps.val();
}).then((senderId) => {
return admin.database().ref('/user-messages/' + toId + '/totalCount').transaction((current) => {
return (current || 0) + 1
}).then((readCount) => {
console.log('check readCount:', readCount);
var message = {
data: {
fromId: fromId,
badge: //I want to display the message count here
},
apns: {
payload: {
aps: {
alert: {
title: 'You got a message from ' + senderId.username,
body: messageText
},
"content-available": 1
}
}
},
token: recipientId.fcmToken
};
return admin.messaging().send(message)
}).then((response) => {
console.log('Successfully sent message:', response);
return response;
})
.catch((error) => {
console.log('Error sending message:', error);
//throw new error('Error sending message:', error);
})
})
})
})
Does anyone know how to do this?
Thanks in advance.
The API documentation for transaction() suggests that the promise from the transaction will receive an object with a property snapshot with the snapshot of the data that was written at the location of the transaction. So:
admin.database.ref("path/to/count")
.transaction(current => {
// do what you want with the value
})
.then(result => {
const count = result.snapshot.val(); // the value of the count written
})

Cloud Functions for Firebase onWrite trigger: snapshot.val is not a function

I created few functions in the same index.js file, which is sendEmail, sendEmailByDbStatusChange and sendEmailConfirmation.
sendEmail- To be call via HTTP/API
sendEmailByDbStatusChange - listening to DB while value change, but the action is hardcoded
sendEmailConfirmation- Listing to DB while value change, the action subject to the snapshot.
Below is my codes:
const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: gmailEmail,
pass: gmailPassword,
},
});
// Sends an email confirmation when a user changes his mailing list subscription.
exports.sendEmail = functions.https.onRequest((req, res) => {
if (req.body.subject === undefined || req.body.recipient === undefined) {
// This is an error case, as "message" is required.
//res.status(400).send('subject/body/recipient is missing!');
return false
} else {
const mailSubject = req.body.subject;
const mailHtmlBody = req.body.htmlBody;
const mailRecipient = req.body.recipient;
const mailOptions = {
from: '"Food Ninja." <foodninjaapp#gmail.com>',
to: mailRecipient,
subject: mailSubject,
html: mailHtmlBody
};
//res.status(200).send('Success: ' + mailSubject + ' to ' + mailRecipient);
return mailTransport.sendMail(mailOptions)
.then(() => {
console.log(`${mailSubject}subscription confirmation email sent to: `, mailRecipient)
return res.status(200).send('Success: ' + mailSubject + ' to ' + mailRecipient)
})
.catch((error) => console.error('There was an error while sending the email:', error));
}
});
exports.sendEmailByDbStatusChange = functions.database.ref('/users/{uid}').onWrite((event) => {
//const snapshot = event.data;
//const val = snapshot.val();
//if (!snapshot.changed('subscribedToMailingList')) {
// return null;
//}
const mailSubject = 'Sending email with Cloud Function - by DB onWrite Trigger';
const mailHtmlBody = '<h1>Hello Jerry</h1><p>If you receiving this means that you have successfully deployed a customized firebase function</p><p>Be Happy!<br><br>Food Ninja Team</p>';
const mailRecipient = 'admin#phd.com.my';
const mailOptions = {
from: '"Food Ninja." <foodninjaapp#gmail.com>',
to: mailRecipient,
subject: mailSubject,
html: mailHtmlBody
};
//const subscribed = val.subscribedToMailingList;
// Building Email message.
//mailOptions.subject = subscribed ? 'Thanks and Welcome!' : 'Sad to see you go :`(';
//mailOptions.text = subscribed ? 'Thanks you for subscribing to our newsletter. You will receive our next weekly newsletter.' : 'I hereby confirm that I will stop sending you the newsletter.';
return mailTransport.sendMail(mailOptions)
.then(() =>
console.log(`${mailSubject}subscription confirmation email sent to: `, mailRecipient)
//return res.status(200).send('Success: ' + mailSubject + ' to ' + mailRecipient)
)
.catch((error) => console.error('There was an error while sending the email:', error));
});
exports.sendEmailConfirmation = functions.database.ref('/users/{uid}').onWrite((event2) => {
console.log(event2)
console.log(event2.val())
console.log(event2.val().data)
console.log(event2.data)
console.log(event2.data.val())
const snapshot = event2.data;
console.log(snapshot)
const val = snapshot.val();
console.log(val)
if (!snapshot.changed('subscribedToMailingList')) {
return null;
}
const mailOptions = {
from: '"Spammy Corp." <noreply#firebase.com>',
to: val.email,
};
const subscribed = val.subscribedToMailingList;
// Building Email message.
mailOptions.subject = subscribed ? 'Thanks and Welcome!' : 'Sad to see you go :`(';
mailOptions.text = subscribed ? 'Thanks you for subscribing to our newsletter. You will receive our next weekly newsletter.' : 'I hereby confirm that I will stop sending you the newsletter.';
return mailTransport.sendMail(mailOptions)
.then(() => console.log(`New ${subscribed ? '' : 'un'}subscription confirmation email sent to:`, val.email))
.catch((error) => console.error('There was an error while sending the email:', error));
});
My problem is, after i deploy the code to firebase function, the console shows that the sendEmailConfirmation unable to execute smoothly due to event2.val is not a function.
My current code combined with my customize code and the original code, which sendEmailConfirmation is the original code. When run the original code independently it did work (original was event instead of event2 for the snapshot).
Please advise.
It looks like you updated to v1.0 of the Firebase SDK for Cloud Functions, but didn't upgrade your code to match.
The entire process is explained in this documentation page. Right now you're being hit by the changes in database triggers, which shows that:
Event data now a DataSnapshot
In earlier releases, event.data was a DeltaSnapshot; now in v 1.0 it is a DataSnapshot.
For onWrite and onUpdate events, the data parameter has before and after fields. Each of these is a DataSnapshot with the same methods available in admin.database.DataSnapshot. For example:
Before (<= v0.9.1)
exports.dbWrite = functions.database.ref('/path').onWrite((event) => {
const beforeData = event.data.previous.val(); // data before the write
const afterData = event.data.val(); // data after the write
});
Now (v1.0.0)
exports.dbWrite = functions.database.ref('/path').onWrite((change, context) => {
const beforeData = change.before.val(); // data before the write
const afterData = change.after.val(); // data after the write
});
According to that example, you'll need something along these lines:
exports.sendEmailConfirmation = functions.database.ref('/users/{uid}').onWrite((change, context) => {
const snapshot = change.after;
const val = snapshot.val();
console.log(val)
if (!snapshot.changed('subscribedToMailingList')) {
return null;
}
const mailOptions = {
from: '"Spammy Corp." <noreply#firebase.com>',
to: val.email,
};
const subscribed = val.subscribedToMailingList;
// Building Email message.
mailOptions.subject = subscribed ? 'Thanks and Welcome!' : 'Sad to see you go :`(';
mailOptions.text = subscribed ? 'Thanks you for subscribing to our newsletter. You will receive our next weekly newsletter.' : 'I hereby confirm that I will stop sending you the newsletter.';
return mailTransport.sendMail(mailOptions)
.then(() => console.log(`New ${subscribed ? '' : 'un'}subscription confirmation email sent to:`, val.email))
.catch((error) => console.error('There was an error while sending the email:', error));
});
Since version 1.0.0 of the firebase-functions module, database onWrite events now deliver a Change object rather than a DataSnapshot object as the first parameter. You can read about all the breaking changes in 1.0.0 in the documentation. You should use this change object instead to choose if you want to examine the contents of the database before or after the change that invoked it.

Categories