I am writing a Cloud Function in Firebase that sends a notification to devices with tokens that are stored as documents in a collection:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification = functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}')
.onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientName = newMessage.recipientName;
const recipientID = newMessage.recipientUID;
const timestamp = newMessage.timestamp;
console.log(senderName + " " + senderID + " " + messageText + " " + recipientName + " " + recipientID + " " + timestamp.toString());
let deviceTokenQuery = admin.firestore().collection(`/Users/${recipientID}/device_tokens/`);
return deviceTokenQuery.get().then(querySnapshot => {
console.log('deviceTokenQuery returned');
let tokens = querySnapshot.docs;
console.log('1');
const notificationPromises = tokens.map(token => {
console.log('2');
let token_id = token.tokenID.toString();
console.log('3');
const payload = {
notification: {
title: senderName,
body: messageText,
icon: "default"
}
};
return admin.messaging().sendToDevice(token_id, payload)
});
console.log('4');
return Promise.all(notificationPromises);
});
});
Only "deviceTokenQuery returned", "1", and "4" log statements appear. Do I have a syntax problem or am I not using these these methods properly?
Related
I am attempting to retrieve the boolean child (notificationsOn) of an object stored as a Firestore document to see if the rest of a function should be executed.
The overall function works to completion without this portion, but adding the portion from let threadDoc to the if statement presents a "threadDoc.get is not a function" error. I think my syntax is wrong but I don't know how, as a similar function works in a later part of the function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification =functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}').onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientID = newMessage.recipientUID;
var notificationsOn = null;
let deviceTokenQuery = admin.firestore().collection(`/users/${recipientID}/device_tokens/`);
var idsToBeSorted = [senderID, recipientID];
idsToBeSorted.sort();
var threadID = idsToBeSorted[0] + idsToBeSorted[1];
console.log(recipientID);
console.log(threadID);
let threadDoc = admin.firestore().document(`users/${recipientID}/threads/${threadID}/`);
return threadDoc.get().then(doc => {
let notificationsOn = doc.data.notificationsOn;
console.log(notificationsOn);
if (notificationsOn !== false){
return deviceTokenQuery.get().then(querySnapshot => {
let tokenShapshot = querySnapshot.docs;
const notificationPromises = tokenShapshot.map(doc => {
let token_id = doc.data().tokenID;
const payload = {
data: {
title: senderName,
body: messageText,
senderID: senderID,
senderName: senderName
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response => {
console.log("Notification sent: ", response);
})
.catch(error => {
console.log("Error sending message: ", error);
});
});
return Promise.all(notificationPromises);
});
}
return;
});
});
admin.firestore().document() was supposed to be admin.firestore().collection(...).doc(...)
This fixed my problem
I think you meant to say admin.firestore() instead of functions.firestore.
Hi I'm stuck at the part where I get error when getting documents. I want to get the fields for users name.
This is the result i get from the console log: From user id: PGIfPp8wmnR1PB1XeiBfUgSnGO62From user: [object Promise] To user id [object Promise].
What am i doing wrong ?
index.js
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.sendNotification = functions.firestore.document('users/{userID}/notifications/{notificationID}')
.onWrite((change, context) => {
const user_id = context.params.userID;
const notification_id = context.params.notificationID;
console.log('We have notification from: ' + user_id + ' The notification id is: ' + notification_id);
return db.collection('users').doc(user_id)
.collection('notifications').doc(notification_id).get().then(queryResult => {
if (!queryResult.exists) {
console.log('No documents');
}else{
console.log("Document data: " ,queryResult.data());
const from_user_id = queryResult.data().from;
const from_data = admin.firestore().collection('users').doc(from_user_id).get();
const to_data = admin.firestore().collection('users').doc(user_id).get();
console.log("From user id: " + from_user_id + "From user: " + from_data + " To user id " + to_data);
}
return Promise.all([from_data, to_data]).then(result => {
const from_name = result[0].data().name;
const to_name = result[1].data().name;
return console.log("FROM: " + from_name + " TO: " + to_name);
});
}).catch(err =>{
console.error('Error getting document', err);
})
});
Error i get
Error getting document ReferenceError: from_data is not defined
at db.collection.doc.collection.doc.get.then.queryResult (/user_code/index.js:31:37)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
In these two lines:
const from_data = admin.firestore().collection('users').doc(from_user_id).get();
const to_data = admin.firestore().collection('users').doc(user_id).get();
You're assinging values to promises that will resolve with document data. You haven't got actual document data yet. You need to use the promises returned by get() to find the actual data, just like you're doing earlier in your function.
I want to test the jobs I'm running with agenda. I have written the following test but then I realized that all the expect()... functions are not getting called before it determines that the test had passed or failed. I put some console.log() by all the expect() functions and they do get called just not before it determines if the tests result. I tried adding expect.assertions(6); at the top of the test but it didn't make the test wait longer, it just failed saying it didn't find all 6.
How do I make jest wait for the whole test to finish?
This is the code I want to test:
rules.js
const Account = require("./../models/user.account");
const Listing = require("./../models/user.account.listing");
const Message = require("./../models/user.account.listing.message");
const MessageRule = require("./../models/user.account.listing.messageRule");
const Reservation = require("./../models/user.account.listing.reservation");
const Core = require("./../core");
const moment = require("moment-timezone");
require("colors");
module.exports = function(agenda) {
agenda.define("send message", {priority: "highest"}, async (job, done) => {
try {
console.time("send message " + job.attrs._id);
const userID = job.attrs.data.userID;
const listingID = job.attrs.data.listingID;
const reservationID = job.attrs.data.reservationID;
const messageRuleID = job.attrs.data.messageRuleID;
const sendDate = job.attrs.data.sendDate;
//console.log("Rule Job:".cyan + " send message - listingID: " + listingID);
let messageQuery = {
userID,
listingID,
reservationID,
messageRuleID
};
let message = await Message.findOne(messageQuery);
if (message) {
throw new Error(
"Message has already been sent. userID: " +
userID +
" listingID: " +
listingID +
" reservationID: " +
reservationID +
" messageRuleID: " +
messageRuleID +
" message._id: " +
message._id +
" message.sendDate: " +
message.sendDate +
" message.sentDate: " +
message.sentDate
);
} else {
const isLastMinuteMessage = false;
let listing = await Listing.findById(listingID);
let account = await Account.findById(listing.accountID);
let messageRule = await MessageRule.findById(messageRuleID);
let reservation = await Reservation.findById(reservationID);
// Check that it found all of the required documents
if (!listing || !listing._id) {
throw new Error("Missing the listing document. userID: " + userID + " listingID: " + listingID);
}
if (!account || !account._id) {
throw new Error(
"Missing the account document. userID: " + userID + " accountID: " + listing.accountID
);
}
if (!messageRule || !messageRule._id) {
throw new Error(
"Missing the messageRule document. userID: " + userID + " messageRuleID: " + messageRuleID
);
}
if (!reservation || !reservation._id) {
throw new Error(
"Missing the reservation document. userID: " + userID + " reservationID: " + reservationID
);
}
// Double check the send date by recalculating it then checking comparing them
if (messageRule.event == "checkin" || messageRule.event == "checkout") {
let sendDateCheck = moment.tz(
reservation.startDate + " " + messageRule.time,
"YYYY-MM-DD H",
listing.timeZone
);
if (messageRule.event == "checkout") {
sendDateCheck = sendDateCheck.add(reservation.nights, "day");
}
sendDateCheck = sendDateCheck.add(messageRule.days, "day");
if (!sendDateCheck.isSame(sendDate)) {
throw new Error(
"Message send date and calculated send date don't match. userID: " +
userID +
" listingID: " +
listingID +
" reservationID: " +
reservationID +
" messageRuleID: " +
messageRuleID +
" sendDate: " +
moment(sendDate).format() +
" sendDateCheck: " +
moment(sendDateCheck).format()
);
}
}
await Core.buildMessage(account, listing, messageRule, reservation, isLastMinuteMessage);
}
console.timeEnd("send message " + job.attrs._id);
done();
} catch (error) {
console.error("Rule Job: send message", error);
return done(error);
}
});
};
And here is my test:
rules.test.js
const Account = require("./../models/user.account");
const Listing = require("./../models/user.account.listing");
const Message = require("./../models/user.account.listing.message");
const MessageRule = require("./../models/user.account.listing.messageRule");
const Reservation = require("./../models/user.account.listing.reservation");
const Core = require("./../core");
const Rules = require("./rules");
const Docs = require("./../tests/docs");
describe("Tests for rules.js", () => {
// Mock moment()
Date.now = jest.fn(() => 1538794832371); //October 5th 2018 8pm PST
beforeEach(() => {
jest.clearAllMocks();
});
describe("rules()", () => {
it("should send a message, message exists but is enabled", async () => {
// Mock Message
// Message.findOne()
const MessageFindOneMock = jest.spyOn(Message, "findOne");
const MessageFindOneResult = Docs.messageReviewReminderDisabledThenEnabled;
const MessageFindOne = jest.fn(() => MessageFindOneResult);
MessageFindOneMock.mockImplementation(MessageFindOne);
const userID = Docs.userID;
const reservation = Docs.reservationInTheFuture;
const messageRule = Docs.messageRuleCheckUp;
const accountID = Docs.listing.accountID;
const listingID = Docs.listing._id;
const reservationID = reservation._id;
const messageRuleID = messageRule._id;
// Mock Listing
// Listing.findById()
const ListingFindByIdMock = jest.spyOn(Listing, "findById");
const ListingFindByIdResult = Docs.listing;
const ListingFindById = jest.fn(() => ListingFindByIdResult);
ListingFindByIdMock.mockImplementation(ListingFindById);
// Mock Account
// Account.findById()
const AccountFindByIdMock = jest.spyOn(Account, "findById");
const AccountFindByIdResult = {_id: accountID};
const AccountFindById = jest.fn(() => AccountFindByIdResult);
AccountFindByIdMock.mockImplementation(AccountFindById);
// Mock MessageRule
// MessageRule.findById()
const MessageRuleFindByIdMock = jest.spyOn(MessageRule, "findById");
const MessageRuleFindByIdResult = messageRule;
const MessageRuleFindById = jest.fn(() => MessageRuleFindByIdResult);
MessageRuleFindByIdMock.mockImplementation(MessageRuleFindById);
// Mock Reservation
// Reservation.findById()
const ReservationFindByIdMock = jest.spyOn(Reservation, "findById");
const ReservationFindByIdResult = reservation;
const ReservationFindById = jest.fn(() => ReservationFindByIdResult);
ReservationFindByIdMock.mockImplementation(ReservationFindById);
// Mock Core
// Core.buildMessage()
const CoreBuildMessageMock = jest.spyOn(Core, "buildMessage");
const CoreBuildMessage = jest.fn((account, listing, messageRule, reservation, isLastMinuteMessage) => {
expect(account._id).toBe(accountID);
expect(listing._id).toBe(listingID);
expect(messageRule._id).toBe(messageRuleID);
expect(reservation._id).toBe(reservationID);
});
CoreBuildMessageMock.mockImplementation(CoreBuildMessage);
// Run test
const sendDate = "2018-11-08T10:00:00-06:00";
const done = jest.fn(error => {
expect(error).toBeFalsy();
expect(CoreBuildMessage).toHaveBeenCalledTimes(10);
});
const job = {
attrs: {
data: {
userID,
listingID,
reservationID,
messageRuleID,
sendDate
}
}
};
let agenda = {
define: jest.fn((name, options, callback) => {
if (name == "send message") {
callback(job, done);
}
})
};
Rules(agenda);
});
});
});
In your rules.js you have defined your callback as an async function:
agenda.define("send message", {priority: "highest"}, async (job, done) // <--- this
However you are calling it with:
define: jest.fn((name, options, callback) => {
if (name == "send message") {
callback(job, done);
}
})
So there is no await there ...
Which explains why you get stuff running but not awaiting for it to finish.
Should be something like:
define: await jest.fn((name, options, callback) => {
if (name == "send message") {
await callback(job, done);
}
})
I want to obtain an array of all the users of my database in the second function that I use ""return admin.database().ref("/Usuarios").once('value')
" so that we can send a notification to all these users.How could all users get in the second function? Thank you
I have this code:
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotificationNewAd = functions.database.ref('/Alertas/{notiId}').onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the
data is deleted.
if (!change.after.exists()) {
return null;
}
//te escribe el json de el mensaje nuevo
const afterData = change.after.val();
console.log("afterData: ", afterData);
//get lat and lng of Ad
const name = afterData.name;
console.log("Name: "+name);
//get lat and lng of Ad
const lat = afterData.lat;
const lng = afterData.lng;
console.log("Lat y Lng", "lat: "+lat+" lng: "+lng);
//get lat and lng of Ad
const adType = afterData.typeAd;
console.log("Tipo: "+adType);
//get the user id of the ad
const notiId = context.params.notiId;
console.log("notiId: ", notiId);
const userId = afterData.userId;
console.log("userId: ", userId);
return admin.database().ref("/Usuarios").once('value')
.then(snap => {
const userName = snap.child("name").val();
return console.log("userName: ", userName);
});
});
It looks like this is what you are asking:
exports.sendNotificationNewAd =
functions.database.ref('/Alertas/{notiId}')
.onCreate((noti_snap, context) => {
//te escribe el json de el mensaje nuevo
const notif = noti_snap.val();
console.log("notif: ", notif);
//get lat and lng of Ad
const name = notif.name;
console.log("Name: " + name);
//get lat and lng of Ad
const lat = notif.lat;
const lng = notif.lng;
console.log("Lat y Lng", "lat: " + lat + " lng: " + lng);
//get lat and lng of Ad
const adType = notif.typeAd;
console.log("Tipo: " + adType);
//get the user id of the ad
const notiId = context.params.notiId;
console.log("notiId: ", notiId);
const userId = notif.userId;
console.log("userId: ", userId);
return admin.database().ref("/Usuarios").once('value')
.then(snap => {
let children = [];
snap.forEach(child_snap => {
children.push(child_snap.val()); // build children
});
return children;
})
.then(children => {
children.map(child => {
let message = {
notification: {
title: "message",
body: "body"
},
token: child.device_token
}
admin.messaging().send(message).catch(console.log);
});
return null;
})
.then( () => {
return notif.ref.remove(); // consume send request
})
.catch(console.log);
});
Thats my code where i first get the laterrides. It checks that the any ride is schedule and if any ride is scheduled then it goes inside and get the list of drivers and sends them notification. But the issue is it send notification repeatedly again and again.Kindly help.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.testing = functions.https.onRequest((req, res) => {
console.log("start");
var newArray = [];
let date = new Date();
var hours = date.getHours() + 5;
var minute = date.getMinutes() ;
var year = date.getFullYear() ;
var month = date.getUTCMonth() + 1 ;
var day = date.getDate() ;
var total_date = day + "/" + month + "/" + year;
var total_time = hours + ":" + minute;
var ridelater = admin.database().ref("ridelater");
ridelater.orderByValue().on("value", function(snapshot) {
snapshot.forEach(function(data) {
var str = data.val().time;
const senderUid = data.val().client_key;
if(data.val().date == total_date){
var arr = str.split(":");
var start_minute = arr[0] * 60;
var val = parseInt(arr[1], 10);
var timing = val - 30;
var start = start_minute + timing ;
var end_minute = hours *60;
var end = end_minute + minute;
var diff = end - start;
if (diff <= 30){
var driver = admin.database().ref("user/driver");
driver.orderByValue().on("value", function(snapshot) {
snapshot.forEach(function(instnaceIds) {
//if(instnaceIds.val().status == "free"){
const promises = [];
const receiverUid = instnaceIds.key;
const getInstanceIdPromise =
admin.database().ref(`/user/driver/${receiverUid}/instanceId`)
.once('value');
const getSenderUidPromise = admin.auth().getUser(senderUid);
Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results =>
{
const instanceId = results[0].val();
//const sender = results[1];
console.log('notifying ' + receiverUid +' from ' + senderUid);
var data1={
'users_key': senderUid,
'org_address' : data.val().org_address,
'dest_address' : data.val().dest_address,
'status' : "request",
'Nodekey' : data.key,
'user_name' : data.val().user_name,
'org_lat' : data.val().org_lat,
'org_long' : data.val().org_long,
'dest_lat' : data.val().dest_lat,
'dest_long' : data.val().dest_long,
'payment' : data.val().payment_method,
'category' : data.val().category,
'Nodekey' : data.val().node_key,
};
var data2 = JSON.stringify(data1);
console.log(JSON.stringify(data1));
const payload = {
notification: {
title: "Qandeel Haider",
body: data2
}
};
console.log(payload);
return admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Successfully sent message:" + instanceId);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
//}
});
});
}
else{
console.log("Not found" + data.val().time);
}
}
else{
console.log("Not found" + data.val().date);
}
console.log("IN");
});
});
console.log("end");
res.status(200).end();
});