onCreate handler's DocumentSnapshot undefined - node.js

I'm trying to set a trigger for an onCreate event in a subcollection. I can get the event to trigger fine, but for some reason the DocumentSnapshot is undefined when I try to access it.
My function is based on the sample here: https://github.com/firebase/functions-samples/blob/master/child-count/functions/index.js
exports.onCreateReview = functions.firestore
.document('spots/{spotId}/reviews/{reviewId}')
.onCreate(
(snapshot, _) => {
console.log(snapshot.exists())
const collectionRef = snapshot.ref.parent
const countRef = collectionRef.parent.child('num_reviews');
const promise = countRef.transaction((current) => {
return current + 1;
});
console.log('num_reviews incremented');
return promise;
});
When I check the logs in my Firebase project, I get a TypeError: Cannot read property of undefined

A snapshot is not a promise as it can fire multiple times whereas a promise resolves once.
Here's a snippet of one of my .onCreate functions that you might find helpful as a guide.
exports.newFeedback = functions.firestore.document("feedback/{feedback}").onCreate(function (snap, context) {
var feedbackObj = snap.data();
var msg = feedbackObj.message;
var subject = "New Feedback: " + msg.substring(0,36);
var feedbackMsg = {
to: "ron#hightechtele.com",
from: "noreply#hightechtele.com",
subject: subject,
templateId: "----------------------------",
substitutionWrappers: ["{{", "}}"],
substitutions: {
message: msg,
senderDisplayName: feedbackObj.sender,
senderEmail: feedbackObj.senderEmail,
senderUid: feedbackObj.senderUid
}
};
return sendgrid.send(feedbackMsg);
});

Related

Firebase function TypeError .once is not a function and onCreate not working

I was trying to deploy a function to Firebase to send notifications to all admin accounts when a new user signs up to the app, this is my current code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.newDoctorNotification = functions.database.ref("/doctors/{pushId}")
.onCreate((snapshot, context) => {
const newDoctorID = context.params.pushId;
const notificationContent = {
notification: {
title: "New Doctor",
body: "A new doctor just signed up! uid: " + newDoctorID,
icon: "default",
sound: "default",
},
};
const adminTokensRef = functions.database.ref("device_tokens/admin");
const tokens = [];
adminTokensRef.once("value", (querySnapshot) => {
querySnapshot.forEach((adminToken) => {
tokens.push(adminToken.val());
});
});
if (tokens.length > 0) {
return admin.messaging().sendToDevice(tokens, notificationContent)
.then(function(result) {
console.log("Notification sent");
return null;
})
.catch(function(error) {
console.log("Notification failed ", error);
return null;
});
}
});
I have tried many variations such as the get() function and on(), but all give me the same error, I was trying to check the docs on this but they only talked about database triggers so I'm not sure if normal retrieval can work or not.
EDIT:
I updated my code to reach the database node through the snapshot given in the onCreate event, and now it works, although I am facing another problem now, if I push a new doctor under the node "doctors" it doesn't call the function.. but if I hit "test" in the Google Cloud console and it calls the function I get "null" in my snapshot.val() and "undefined" in the newDoctorID above, whereas the snapshot.key gives "doctors", why is it not calling the onCreate function?

Firebase realtime database get wildcard data

I'm trying to send a notification to users whenever their message receives a new reply. However, in the firebase cloud functions logs it is returning errors and not sending a notification. Here is the error:
TypeError: Cannot read properties of undefined (reading 'uid')
Here is my function:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports
.sendNewTripNotification = functions
.database
.ref("messagepool/{uid}/responses/")
.onWrite((event)=>{
const messageid = event.params.uid;
// console.log('User to send notification', uuid);
const ref = admin.database().ref(`messagepool/${messageid}/author`);
return ref.once("value", function(snapshot) {
const ref2 = admin.database().ref(`users/${snapshot.val()}/token`);
return ref2.once("value", function(snapshot2) {
const payload = {
notification: {
title: "💌 New Reply",
body: "You have received a new reply to your message!",
},
};
admin.messaging().sendToDevice(snapshot2.val(), payload);
}, function(errorObject) {
console.log("The read failed: " + errorObject.code);
});
}, function(errorObject) {
console.log("The read failed: " + errorObject.code);
});
});
Am I reading the wildcard uid incorrectly? Why is this happening?
The function in onWrite() takes 2 parameters - change that is a DataSnapshot and context which contains the params you are looking for. Try refactoring the code as shown below:
exports
.sendNewTripNotification = functions
.database
.ref("messagepool/{uid}/responses/")
.onWrite((change, context) => {
const { uid } = context.params;
console.log('UID:', uid);
})

How can I get the value of children in Firebase database using Javascript?

How do you get the value of a specific key-value pair in firebase using javascript? I am creating a function for firebase cloud messaging. My function looks like this:
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/notifications/{receiver_user_id}/{notification_key}').onWrite((event, context)=>{
const receiver_user_id = context.params.receiver_user_id;
const notification_key = context.params.notification_key;
console.log('We have a notification to send to : ', receiver_user_id);
// Grab the current value of what was written to the Realtime Database.
const snapshot = event.after.val();
console.log('Uppercasing', context.params.notification_key, snapshot);
console.log('original value : ', snapshot);
if(!event.after.val()){
console.log('A notification has been deleted: ', notification_key);
return null;
}
const sender_fullname = admin.database().ref(`/notifications/${receiver_user_id}/{notification_key}/notifying_user_fullname`).once('value').toString();
console.log('full name value : ', sender_fullname);
const DeviceToken = admin.database().ref(`/tokens/${receiver_user_id}/device_token`).once('value');
return DeviceToken.then(result=>{
const token_id = result.val();
console.log('token id value : ', token_id);
const payload = {
notification: {
title: sender_fullname.toString(),
body: "You have a new message!",
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response=>{
console.log('Message has been sent');
});
});
});
Right now sender_fullname produces [object Promise] in the console log and the notification that is sent. I am uncertain how to get the exact value. An example entry in my realtime database looks like this:
original value : { date_created: '02-21-2020T17:50:32',
my_id: '0ntpUZDGJnUExiaJpR4OdHSNPkL2',
notification_key: '-M0dwVL3w1rKyPYbzUtL',
notification_type: 'liked',
notifying_user: 'OiBmjJ7yAucbKhKNSHtYHsawwhF2',
notifying_user_fullname: 'Captain Proton',
post_key: '-LzSJrOq9Y7hGgoECHRK',
read: 'false' }
Is there any way to get the exact value of say, "notifying_user_fullname"? Any help would be appreciated.
To get the value of sender_fullname, you have to do exactly the way you do for DeviceToken!
The once() method returns a promise which resolves with a DataSnapshot, so you need to use the then() method in order to get the DataSnapshot and then, use the val() method.
So the following should do the trick (untested):
exports.sendNotification = functions.database.ref('/notifications/{receiver_user_id}/{notification_key}')
.onWrite((event, context) => {
const receiver_user_id = context.params.receiver_user_id;
const notification_key = context.params.notification_key;
console.log('We have a notification to send to : ', receiver_user_id);
// Grab the current value of what was written to the Realtime Database.
const snapshot = event.after.val();
console.log('Uppercasing', context.params.notification_key, snapshot);
console.log('original value : ', snapshot);
if (!event.after.val()) {
console.log('A notification has been deleted: ', notification_key);
return null;
}
let sender_fullname;
return admin.database().ref(`/notifications/${receiver_user_id}/${notification_key}/notifying_user_fullname`).once('value')
.then(dataSnapshot => {
sender_fullname = dataSnapshot.val();
return admin.database().ref(`/tokens/${receiver_user_id}/device_token`).once('value');
})
.then(dataSnapshot => {
const token_id = dataSnapshot.val();
console.log('token id value : ', token_id);
const payload = {
notification: {
title: sender_fullname,
body: "You have a new message!",
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload)
})
.then(() => {
console.log('Message has been sent');
return null; // <-- Note the return null here, to indicate to the Cloud Functions platform that the CF is completed
})
.catch(error => {
console.log(error);
return null;
})
});
Note how we chain the different promises returned by the asynchronous methods, in order to return, in the Cloud Function, a Promise, which will indicate to the platform that the Cloud Function work is complete.
I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series which explains the importance of this point.

Async does not seem to wait for await

I use node js and postgres as well as chai and mocha for tdd, and now I have encountered a problem when I try to update an item in my database with a wrong foreign key. When this happens I want to basically get the old item from the database with the valid values.
this is the update method in the Item class
async update() {
if (this.description.length === 0) {
throw new Error("Description can not be deleted");
}
try {
const updateItem = await this.tryUpdate();
this.copyToThis(updateItem);
} catch (e) {
const oldItem = await Item.getById(this.id);
this.copyToThis(oldItem);
console.log(this);
throw new Error("Updating did not work");
}
}
This is the test that fails
it('should throw an error if you update with wrong category or project id and get the old values from the server', async function () {
const newProject = "3b4e092e-1dd9-40a5-8357-69696b3e35ba";
const newCategory = "3cf87368-9499-4af1-9af0-10ccf1e84088";
const item = await Item.getById(updateId);
expect(item).to.exist;
const oldProjectId = item.projectId;
const oldCategoryId = item.categoryId;
item.projectId = newProject;
expect(item.update()).to.be.rejectedWith(Error);
item.categoryId = newCategory;
expect(item.update()).to.be.rejectedWith(Error);
expect(item.categoryId).to.equal(oldCategoryId);
expect(item.projectId).to.equal(oldProjectId);
});
this is the AssertionError
-3cf87368-9499-4af1-9af0-10ccf1e84088
+3cf87368-9499-4af1-9af0-10ccf1e84087
As you can see the item still has the wrong categoryId and not the one from the server. Eventhough the log has the correct item.
I solved it myself
I needed to add an await in the test
it('should throw an error if you update with wrong category or project id and get the old values from the server', async function () {
const newProject = "3b4e092e-1dd9-40a5-8357-69696b3e35ba";
const newCategory = "3cf87368-9499-4af1-9af0-10ccf1e84088";
const item = await Item.getById(updateId);
expect(item).to.exist;
const oldProjectId = item.projectId;
const oldCategoryId = item.categoryId;
item.projectId = newProject;
await expect(item.update()).to.be.rejectedWith(Error);
item.categoryId = newCategory;
await expect(item.update()).to.be.rejectedWith(Error);
expect(item.categoryId).to.equal(oldCategoryId);
expect(item.projectId).to.equal(oldProjectId);
});

TypeError: Cannot read property ‘sendMessage’ of null

I am trying to test my new Slackbot by sending it a greeting with the expected behavior that it replies with 'this is a test message' in accordance with my code here in slackClient.js:
'use strict'
const RtmClient = require('#slack/client').RtmClient;
const CLIENT_EVENTS = require('#slack/client').CLIENT_EVENTS;
const RTM_EVENTS = require('#slack/client').RTM_EVENTS;
let rtm = null;
function handleOnAuthenticated(rtmStartData) {
console.log(`Logged in as ${rtmStartData.self.name} of team ${rtmStartData.team.name}, but not yet connected to a channel`);
}
function handleOnMessage(message) {
console.log(message);
// This will send the message 'this is a test message' to the channel identified by id 'C0CHZA86Q'
rtm.sendMessage('this is a test message', message.channel, function messageSent() {
// optionally, you can supply a callback to execute once the message has been sent
});
}
function addAuthenticatedHandler(rtm, handler) {
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, handler);
}
module.exports.init = function slackClient(bot_token, logLevel){
const rtm = new RtmClient(bot_token);
addAuthenticatedHandler(rtm, handleOnAuthenticated);
rtm.on(RTM_EVENTS.MESSAGE, handleOnMessage)
return rtm;
}
module.exports.addAuthenticatedHandler = addAuthenticatedHandler;
This is the error I get:
rtm.sendMessage('this is a test message', message.channel, function messageSent() {
^
TypeError: Cannot read property 'sendMessage' of null
If I understand this correctly, it's telling me I cannot give rtm a value of null, but then what kind of boilerplate value can I give it just so that I can test this out?
I found my error, I had rtm as a const and then a let. So I removed the const from rtm = new RtmClient(bot_token); and moved on to add nlp like so:
'use strict'
const RtmClient = require('#slack/client').RtmClient;
const CLIENT_EVENTS = require('#slack/client').CLIENT_EVENTS;
const RTM_EVENTS = require('#slack/client').RTM_EVENTS;
let rtm = null;
let nlp = null;
function handleOnAuthenticated(rtmStartData) {
console.log(`Logged in as ${rtmStartData.self.name} of team ${rtmStartData.team.name}, but not yet connected to a channel`);
}
function handleOnMessage(message) {
// This will send the message 'this is a test message' to the channel identified by id 'C0CHZA86Q'
rtm.sendMessage('this is a test message', message.channel, function messageSent() {
// optionally, you can supply a callback to execute once the message has been sent
});
}
function addAuthenticatedHandler(rtm, handler) {
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, handler);
}
module.exports.init = function slackClient(bot_token, logLevel, nlpClient){
rtm = new RtmClient(bot_token);
nlp = nlpClient;
addAuthenticatedHandler(rtm, handleOnAuthenticated);
rtm.on(RTM_EVENTS.MESSAGE, handleOnMessage)
return rtm;
}
module.exports.addAuthenticatedHandler = addAuthenticatedHandler;

Resources