I wrote a cloud function, to listen for document creation in a collection, in my database
here is the function,
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().functions);
var newData;
exports.myTrigger = functions.firestore.document('FCM/{id}').onCreate(async (snapshot, context) => {
//
if (snapshot.empty) {
console.log('No Devices');
return;
}
newData = 'hello';
const deviceIdTokens = await admin
.firestore()
.collection('FCM')
.get();
var tokens = [];
var i=0;
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().ar1[i]);
i++;
}
var payload = {
notification: {
title: 'push title',
body: 'push body',
sound: 'default',
},
data: {
push_key: 'Push Key Value',
key1: newData,
},
};
try {
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log('Notification sent successfully');
} catch (err) {
console.log(err);
}
});
This function works weirdly,
For example, sometimes it sends notification, and sometimes it does not.
It throws errors like " TypeError: Cannot read property '0' of undefined".
I don't know how to resolve this issue,
In my arr1 field, i have an array of device tokens, to whom i want to send notifications to,
i want the function to send notifications only to the devices(using tokens) which are just created(in the newly created document ),then delete the document.
I think it's sending notifications to all the documents at once.
I'm pretty new at node..
please help me out.
UPDATE:-
Here is my document structure
Type error coming from this line:
tokens.push(token.data().arr1[i]);
So all I can say is that sometimes token.data() doesn't have an arr1 attribute.
Related
i am building chat app with flutter and I try to send notification to specific device using the cloud function so when a user send message to his friend then his friend get notification with the message but I get that error
note : I don not have any knowledge with javascript or node js
Unhandled error Error: Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.
at Object.validateResourcePath (/workspace/node_modules/#google-cloud/firestore/build/src/path.js:446:15)
at CollectionReference.doc (/workspace/node_modules/#google-cloud/firestore/build/src/reference.js:2061:20)
at /workspace/index.js:14:12
at fixedLen (/workspace/node_modules/firebase-functions/lib/providers/https.js:72:41)
at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:407:32
at processTicksAndRejections (node:internal/process/task_queues:96:5)
first I try to get the device token and save it to firebase
void getToken() async {
await fcm.getToken().then((value) {
tokens = value;
print('my token22 is $tokens');
saveToken(tokens: tokens);
});
}
void saveToken({String? tokens}) async {
FirebaseFirestore.instance.collection('userToken').doc(userphone).set({
'token': tokens,
});
}
then I try to call this token at index.js file at function function
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.addMessage = functions.https.onCall(
(data, context) => {
const friendPhone = data.text;
const userDoc = admin
.firestore()
.collection("userToken")
.doc(friendPhone)
.get();
const deviceTokens = userDoc.data();
console.log(deviceTokens);
const title = data.title;
const body = data.body;
try {
if (deviceTokens) {
exports.myFunction = functions.firestore
.document("chats/{chatId}/messegeId/{messageId}")
.onWrite((snap, context) => {
console.log(snap.data());
admin.messaging().sendToDevice(deviceTokens,
{
notification: {title: title,
body: body,
clickAction: "FLUTTER_NOTIFICATION_CLICK",
},
});
});
}
} catch (error) {
console.log(error);
throw new functions.https.
HttpsError("invalid-argument", "some message");
}
}
);
after that I call the function at sendMessege button so that when the user send messgege it work
Future<void> writeMessage({
String? message,
String? title,
String? friendPhone,
}) async {
HttpsCallable callable =
FirebaseFunctions.instance.httpsCallable("addMessage");
final resp = await callable.call(<String, dynamic>{
"text": friendPhone,
"title": title,
"body": message,
});
print("result: ${resp.data}");
}
oare you sure that the path for the friendPhone is a valid path in firebase?
Is there maybe a spelling mistake in messageId?
document("chats/{chatId}/messegeId/{messageId}")
You wrote messegeId instead of messageId
Hope that helps.
I've been trying to figure this out for hours and I just can't. I'm still a beginner with Node.js and Firebase. I need your help to be able to retrieve the tokens array in my "userdata" collection to Node.js and be able to use it to send notifications in the Cloud Function. So far this is what I've been working on. Here is what my database looks like:
The receiverId is gathered from when I have an onCreate function whenever a user sends a new message. Then I used it to access the userdata of a specific user which uses the receiverId as their uid.
In the cloud function, I was able to start the function and retrieve the receiverId and print the userToken[key]. However, when I try to push the token it doesnt go through and it results in an error that says that the token is empty. See the image:
Your help would mean a lot. Thank you!
newData = snapshot.data();
console.log("Retrieving Receiver Id");
console.log(newData.receiverId); //uid of the user
const tokens = [];
const docRef = db.collection('userdata').doc(newData.receiverId);
docRef.get().then((doc) => {
if (doc.exists) {
console.log("DocRef exist");
const userToken = doc.data().tokens;
for(var key in userToken){
console.log(userToken[key]);
tokens.push(userToken[key]);
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
//Notification Payload
var payload = {
notification: {
title: newData.sendBy,
body: 'Sent you a message',
sound: 'default',
},
data: {
click_action : 'FLUTTER_NOTIFICATION_CLICK',
route: '/telconsultinbox',
}
};
console.log("Sending Notification now.");
console.log(tokens);
try{
//send to device
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log('Notification sent successfully');
console.log(newData.sendBy);
}catch(err){
console.log(err);
}
I think you should avoid using for..in to iterate through an array (you can read more about it in this answer). Try one of these 2 options:
You could use forEach(), which is more elegant:
userToken.forEach((token) => {
console.log(token);
tokens.push(token);
});
for-of statement:
for(const token of userToken){
console.log(token);
tokens.push(token);
}
Also, I would consider renaming userToken to userTokens, since it should contain multiple values. Makes the code a bit more readable.
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;
}
});
I have firestore document with 5000+ users token but FCM limit is 1000 how can I send
notification to all.
how can I send 1000-1000 using loop anyone can help me to figure out this.
var newData;
exports.articlenotification = functions.firestore
.document("Articles/{id}")
.onCreate(async (snapshot, context) => {
//
if (snapshot.empty) {
console.log("No Devices");
return;
}
newData = snapshot.data();
const deviceIdTokens = await admin
.firestore()
.collection("Tokens")
.where("article", "==", true)
.get();
var tokens = [];
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().token);
}
var payload = {
notification: {
title: "New Article",
body: newData.title,
image: newData.image,
sound: "default"
}
};
try {
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log("Notification sent successfully");
} catch (err) {
console.log(err);
}
});
There are two ways to do it. first way to sent 1000 then 1000 ..etc. and second way by send to specific topic and all clients who subscribe to this topic will receive your notification.
device-group
topic-messaging
This code to send 1000 then 1000 ..etc. but I don't prefer it. you should use topic-messaging instead of it.
for (let i = 0; i < listOf5000Tokens.length; i += 1000) {
const listOf1000Tokens = listOf5000Tokens.slice(i, i + 1000);
// using await to wait for sending to 1000 token
await doSomeThing(listOf1000Tokens);
}
you'll need to send the message in batches.
For example:
// Create a list containing up to 500 messages.
const messages = [];
messages.push({
notification: {title: 'Price drop', body: '5% off all electronics'},
token: registrationToken,
});
messages.push({
notification: {title: 'Price drop', body: '2% off all books'},
topic: 'readers-club',
});
admin.messaging()
.sendAll(messages)
.then((response) => console.log(response.successCount + ' messages were sent successfully'));
In my Firestore database I store DocumentReferences to users so that I am always using up-to-date user data such as username, profile pictures, and auth tokens.
I am also implementing Cloud Functions to listen for database triggers so that I can send notifications to those specific users about activity related to their posts.
This is where I run into trouble, because I do not know how to use the stored reference object properly inside the Node.js function when I access it like all other database information.
The following is my function code:
exports.countNameChanges = functions.firestore
.document('posts/{postId}')
.onUpdate((change, context) => {
// Retrieve the current and previous value
const data = change.after.data();
const previousData = change.before.data();
var registrationToken = '';
var notification = '';
var postTitle = data.statement;
var userRef = data.userRef; //This is my `DocumentReference` object
if (data.interactionCount > previousData.interactionCount && data.postTypeId == 2131165321) notification = 'You recieved a new comment!';
if (data.interactionCount > previousData.interactionCount && data.postTypeId == 2131165335) notification = 'You recieved a new vote!';
if (data.likes > previousData.likes) notification = 'You have a new post like!' ;
if (data.dislikes > previousData.dislikes) notification = 'You have a new post dislike!' ;
admin.firestore()
.doc(userRef) //This is my `DocumentReference` object
.get()
.then(doc => {
registrationToken = doc.data().token;
var payload = {
data: {
title: postTitle,
body: notification
},
token: registrationToken
};
admin.messaging().send(payload)
.then((response) => {
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
})
});
});
});
My Function Log
I would assume that the DocumentReference object would be easy to work with
inside a Cloud Function since the object is supported for direct storage into Firestore, but I can't figure it out.
If userRef is a DocumentReference type object, then just call get() on it directly. Don't pass it to doc(). You're only supposed to pass string type objects to doc().
userRef.get().then(...)