Not receiving notifications with firebase functions - javascript

I have been trying to implement Cloud Messaging for my app so that every user of the app would receive notification automatically when a new child is added to the Realtime Database.
In my MainActivity I am subscribing each user to a topic with this method.
FirebaseMessaging.getInstance().subscribeToTopic("latest_events").addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Toast.makeText(MainActivity.this, "Successfully subscribed", Toast.LENGTH_SHORT).show();
}
});
I have also installed firebase functions for my backend and deployed my javascript code.
index.js
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref("/Users").onWrite(event => {
var payload = {
notification: {
title: "A new user has been added!",
body: "Click to see"
}
};
if (event.data.previous.exists()) {
if (event.data.previous.numChildren() < event.data.numChildren()) {
return admin.messaging().sendToTopic("latest_events", payload);
} else {
return;
}
}
if (!event.data.exists()) {
return;
}
return admin.messaging().sendToTopic("latest_events", payload);
});
I have not been getting the desired notification when a user gets added. Can't seem to get what am doing wrong.
Firebase Functions Log Cat
sendNotification
TypeError: Cannot read property 'previous' of undefined at exports.sendNotification.functions.database.ref.onWrite.event (/user_code/index.js:15:19) at cloudFunctionNewSignature (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:105:23) at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:135:20) at /var/tmp/worker/worker.js:730:24 at process._tickDomainCallback (internal/process/next_tick.js:135:7)

You're using syntax from the beta version of the Cloud Functions for Firebase. Since it was updated to 1.0 the syntax changed, and you will need to update your code to match as described in the upgrade documentation.
Applying that to your code leads to something like this:
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification = functions.database.ref("/Users").onWrite((change, context) => {
var payload = {
notification: {
title: "A new user has been added!",
body: "Click to see"
}
};
if (change.before.exists()) {
if (change.before.numChildren() < change.after.numChildren()) {
return admin.messaging().sendToTopic("latest_events", payload);
} else {
return;
}
}
if (!change.after.exists()) {
return;
}
return admin.messaging().sendToTopic("latest_events", payload);
});
The changes are that:
There are two parameters passed into the function, where previously there was only one.
You no longer need to pass configuration into initializeApp.
The before and after snapshots are now available from change.
Also see these questions (which are easy to find by searching for the error message:
Firebase functions: cannot read property 'user_id' of undefined
Firebase TypeError: Cannot read property 'val' of undefined
Firebase Notifications using node.js

Related

Firebase Function onDelete from database and storage

I want to be able to delete a folder in firebase storage while onDelete in functions is triggered.
here is my firebase node code, once deleted, it will trigger functions to delete the corresponding folder in firebase storage. I am allowing user to delete their message conversion that includes images. I was able to delete the folder without using the {friendId} but {friendId} is needed in case the user have conversions with two different users.
My Firebase storage is as follow
messages_image_from_friends/
iLJ6nGJodeat2HRi5Q2xdTUmZnw2/
MXGCZv96aVUkSHZeU8kNTZqTQ0n2/
image.png
and Firebase Functions
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const firebase = admin.initializeApp();
exports.deletePhotos = functions.database.ref('/messagesFriends/{userId}/{friendId}')
.onDelete((snap, context) => {
const { userId } = context.params;
<---- const { friendId } = context.params.friendId; ????? ---- >
const bucket = firebase.storage().bucket();
return bucket.deleteFiles({
prefix: `messages_image_from_friends/${userId}/{friendId}`
}, function(err) {
if (err) {
console.log(err);
} else {
console.log(`All the Firebase Storage files in
messages_image_from_friends/${userId}/{friendId} have been deleted`);
}
});
});
Log states that {friendId} is undefined. How do i get {friendId} from exports into prefix.
I have tried "snapshot" and "then()" but do not really know how to implement it as I am new to functions. Please help.
Update!!! 9/12/2020
I was able to get this working by changing onDelete to functions.https.onCall to use hashmap instead.. hope this help others
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const firebase = admin.initializeApp();
exports.deletePhotos = functions.https.onCall((data, context) => {
const userId = data.userId;
const friendId = data.friendId;
console.log(userId, friendId);
const bucket = firebase.storage().bucket();
return bucket.deleteFiles({
prefix: `messages_image_from_friends/`+userId+`/`+friendId+`/`
}, function(err) {
if (err) {
console.log(err);
} else {
console.log(`messages_image_from_friends/`+userId+`/`+friendId);
}
});
// return {response:"This means success"};
});
and the code to call the function from your android app
private FirebaseFunctions mFunctions;
protected void onCreate(Bundle savedInstanceState) {
mFunctions = FirebaseFunctions.getInstance();
////String userId is current firebase user id
////String friendId is from getIntent(), etc
deletePhotos(userId, friendId);
}
private Task<String> deletePhotos(String userId, String friendId) {
// Create the arguments to the callable function.
Map<String, Object> data = new HashMap<>();
data.put("userId", userId);
data.put("friendId", friendId);
return mFunctions
.getHttpsCallable("deletePhotos")
.call(data)
.continueWith(new Continuation<HttpsCallableResult,
String>() {
#Override
public String then(#NonNull Task<HttpsCallableResult>
task) throws Exception {
// This continuation runs on either success or
failure, but if the task
// has failed then getResult() will throw an
Exception which will be
// propagated down.
String result = (String)
task.getResult().getData();
return result;
}
});
}
MAKE SURE YOU MAKE A NEW FIREBASE INIT FOLDER..
I MADE THE MISTAKE OF REDEPLOYING THIS DIRECTLY IN CLOUD FUNCTION CONSOLE WHILE IT WAS CONNECTED AS onDelete and IT WAS UPDATING THE index.js ONLY INSTEAD OF THE WHOLE FUNCTION FOLDER. SO DON'T DO WHAT I DID BECAUSE YOU WILL GET A TypeError: Cannot read property 'origin' of undefined at /srv/node_modules/cors/lib/
HOPE THIS HELPS OTHERS!!!
Update 9/18/20
I was able to make it work with onDelete with this
'use-strict'
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const firebase = admin.initializeApp();
exports.deletePhotos =
functions.database.ref('/messagesFriends/{userId}/{friendId}')
.onDelete((snap, context) => {
const userId = context.params.userId;
const friendId = context.params.friendId;
const bucket = firebase.storage().bucket();
console.log(userId + ' ' + friendId + " found");
return bucket.deleteFiles({
prefix: `messages_image_from_friends/`+userId+`/`+friendId
}, function(err) {
if (err) {
console.log(`messages_image_from_friends/`+userId+`/`+friendId + `
remove error`);
} else {
console.log(`messages_image_from_friends/`+userId+`/`+friendId + `
removed`);
}
});
});
context.params is an object whose properties are populated with each of the wildcards from the trigger path. You're not using it correctly.
const userId = context.params.userId;
const friendId = context.params.friendId;
I suggest reviewing the documentation for database triggers, especially the part on specifying the path:
You can specify a path component as a wildcard by surrounding it with curly brackets; ref('foo/{bar}') matches any child of /foo. The values of these wildcard path components are available within the EventContext.params object of your function. In this example, the value is available as event.params.bar.

Error: Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string

I spend too much time on trying to figure out on how FCM works so I'm looking for some wisdom. I'm trying to add push notifications for my app using Firebase Cloud messaging (FCM). I read the docs but I'm don't fully understand how it works. My app is written in Java and for the messaging I want to use javascript. In my app I have a chat rooms where users can communicate. I want to set a push notification once he gets a new message. In order to get to the chat room, the path in the database:
groups (collection) -> <group name> (document) -> chatRooms (collection) -> <chat ID> (document) -> roomMessages (collection) -> <message ID> (document)
Each document in the chatRooms collection contains the following fields:
name - name of the room.
lastMeesage - The last message that was sent in the chat room (updated once the message is sent).
lastMessageTimestamp - The timestamp of the last message.
users - Array of users (paths) that are talking in the chat room.
Each document in the roomMessages collection contains the following fields:
message - The message that was sent.
userName - The user name.
userPath - The path to the user document in the firebase.
timestamp - The timestamp of the message.
I was following this tutorial on how to do it. I wrote the following code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = functions.firestore;
exports.onMessageSent = db.document(`groups/{groupName}/chatRooms/{chatId}/roomMessages/{messageId}`).onCreate((messageDoc, context) => {
const message = messageDoc.message;
const senderName = messageDoc.senderName;
const senderPath = messageDoc.senderPath;
const chatId = context.params.chatId;
const groupName = context.params.groupName;
const groupPath = 'groups/' + groupName;
return admin.firestore().doc(senderPath).get().then(senderDoc => {
var senderPhoto = null;
if (typeof senderDoc.data().image !== 'undefined') {
senderPhoto = senderDoc.data().image;
}
return admin.firestore().doc(`groups/${groupName}/chatRooms/${chatId}`).get().then(chatRoomDoc => {
return chatRoomDoc.data().users.forEach((userPath, index) => {
return admin.firestore().doc(userPath).get().then(receiverDoc => {
console.log('Creating a chat notification for: ', receiverDoc.data().full_name);
let tokens = receiverDoc.data().tokens;
const payload = {
data: {
display_status: "admin_broadcast",
notification_type: "CHAT",
title: "New message from " + senderName,
body: message,
group_path: groupPath,
sender_path: senderPath,
sender_photo_url: senderPhoto,
chat_id: chatId
}
};
console.log("Sending notification");
return admin.messaging().sendToDevice(tokens, payload);
});
});
});
});
});
Then I ran (It does not let me run locally because - function ignored because the firestore emulator does not exist or is not running.):
firebase deploy --only functions
In android I implemented the following class:
public class MessagingService extends FirebaseMessagingService {
#Override
public void onMessageReceived(#NonNull RemoteMessage remoteMessage) {
Log.d(TAG, "onMessageReceived: started");
Log.d(TAG, "onMessageReceived: message is: " + remoteMessage.getData());
// code
}
#Override
public void onNewToken(#NonNull String token) {
Log.d(TAG, "Refreshed user token: " + token);
sendRegistrationToServer(token);
}
// code
}
But in the log section of the functions tab of the firebase console I get the following error:
onMessageSent
Error: Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.
at Object.validateResourcePath (/srv/node_modules/#google-cloud/firestore/build/src/path.js:406:15)
at Firestore.doc (/srv/node_modules/#google-cloud/firestore/build/src/index.js:427:16)
at exports.onMessageSent.db.document.onCreate (/srv/index.js:19:30)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:132:23)
at /worker/worker.js:825:24
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
What does it mean and how to fix it?
It's mean that you try get document. but the path isn't String.
If you look in error you see that happen in line 30.
I think that admin.firestore().doc(userPath) is the problem.
userPath is object and don't string. try to convert toString()

Firebase Callable Function context is undefined

I have written a firebase Http callable cloud function based on the tutorial here: https://www.youtube.com/watch?v=3hj_r_N0qMs from the firebase team. However, my function is unable to verify the custom claims on a user (me) as 'context.auth' is undefined
I've updated firebase, firebase tools, firebase-functions and admin SDK to the latest versions.
My functions/Index.ts file
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
export const addAdmin = functions.https.onCall((data, context) => {
if (context.auth.token.admin !== true) {
return {
error: 'Request not authorized'
};
}
const uid = data.uid
return grantAdminRole(uid).then(() => {
return {
result: `Request fulfilled!`
}
})
})
async function grantAdminRole(uid: string): Promise<void> {
const user = await admin.auth().getUser(uid);
if (user.customClaims && (user.customClaims as any).admin === true) {
console.log('already admin')
return;
}
return admin.auth().setCustomUserClaims(user.uid, {
admin: true,
}).then(() => {
console.log('made admin');
})
}
My app.component.ts code
makeAdmin() {
var addAdmin = firebase.functions().httpsCallable('addAdmin');
addAdmin({ uid: '[MY-USER-ID]' }).then(res => {
console.log(res);
})
.catch(error => {
console.log(error)
})
}
The function executes well if I don't try to access 'context' and I can add a custom claim to this user. However if I try to access context.auth I find the error:
Unhandled error TypeError: Cannot read property 'token' of undefined"
The error message is telling you that context.auth doesn't have a value. As you can see from the API documentation, auth will be null if there is no authenticated user making the request. This suggests to me that your client app does not have a signed-in user at the time of the request to the callable function, so make sure that is the case before invoking the function. If you allow the case where a callable function can be invoked without a signed in user, you will need to check for that case in your function code by checking context.auth before doing work on behalf of that user.
Turns out I wasn't properly integrating AngularFire Functions. I found the solution to my problem here: https://github.com/angular/angularfire2/blob/master/docs/functions/functions.md
I changed my client component code to the following:
import { AngularFireFunctions } from '#angular/fire/functions';
//other component code
makeAdmin() {
const callable = this.fns.httpsCallable('addAdmin');
this.data$ = callable({ uid: '[USERID]' })
.subscribe(resp => {
console.log({ resp });
}, err => {
console.error({ err });
});
}

Resolving a "TypeError: Cannot read property 'data' of undefined" in Cloud Functions

Sorry if this seems like a really basic question, the concept of cloud functions is extremely new to me and i'm still highly in the learning process.
However, whilst trying to execute this cloud function i get the following error
TypeError: Cannot read property 'data' of undefined
Full log can be seen here
For reference as well, I didnt make this function, im just trying to get it working, i used this video.
The actual cloud function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const firestore = admin.firestore();
const settings = { timestampInSnapshots: true };
firestore.settings(settings);
const stripe = require('stripe')(functions.config().stripe.token);
exports.addStripeSource =
functions.firestore.document('cards/{userid}/tokens/{tokenid}')
.onCreate(async (tokenSnap, context) => {
var customer;
const data = tokenSnap.after.data();
if (data === null) {
return null
}
const token = data.tokenId;
const snapchat = await
firestore.collection('cards').doc(context.params.userId).get();
const customerId = snapshot.data().custId;
const customerEmail = snpashot.data().email;
if (customerId === 'new') {
customer = await stripe.customers.create({
email: customerEmail,
source: token
});
firestore.collection('cards').doc(context.params.userId).update({
custId: customer.id
});
}
else {
customer = await stripe.customers.retrieve(customerId)
}
const customerSource = customer.sources.data[0];
return firestore.collection('cards').doc(context.params.userId).collection('sources').doc(customerSource.card.fingerprint).set(customersource, { merge: true });})
The dart code im using for writing a payment service:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class PaymentService {
addCard(token) {
FirebaseAuth.instance.currentUser().then((user) {
print("Found User");
Firestore.instance
.collection('cards')
.document(user.uid)
.collection('tokens')
.add({'tokenId': token}).then((val) {
print('saved');
});
});
}
}
And finally, what executes when i push the button:
StripeSource.addSource().then((String token) {
print("Stripe!");
PaymentService().addCard(token);
});
As you can see the code is clearly being triggered, but i guess there is some sort of error with the data var, JavaScript is brand new to me so im sure its some sort of very dumb syntax issue.
From the log image attached the error is context is not defined
functions.firestore.document('cards/{userid}/tokens/{tokenid}')
.onCreate(async (tokenSnap, conetxt) => {
In the above function, you have passed parameter as conetxt and later in the function context is used, because of which it is giving undefined error.
Change the parameter name conetxt to context.
As your provided log output explains : you need to define a reference for your firestore.document function :
functions.firestore.document('cards/{userid}/tokens/{tokenid}')
modify it to :
functions.firestore.documentReference(){
}

Error: Registration token(s) provided to sendToDevice()

Now im working for my final project. I try to send notification using firebase cloud function when its trigger the onUpdate but i got an error. I have follow tutorial on youtube and website but i dont get it. By the way, im new to firebase. below Here is my index.js code :-
const functions = require('firebase-functions');
//Firebase function and handling notification logic
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.pushNotification = functions.database.ref('/Sensor').onWrite(( change,context) => {
const sensor = change.after.val();
const payload = {
notification: {
Title: "Alert",
Body: "Open pipe detect !",
icon: "default"
}
};
return admin.messaging().sendToDevice(sensor.token, payload)
.then((response)=> {
return console.log("Successfully sent message:", response);
});
});
the project structure is like this:
**water-system**
+--Sensor
+---Pipe
+---pipeName
+---solenoid
+---status // trigger on this update
+---User
+---Id1
+---email
+---name
+---token // token store by this user
+---Id2
+---Id3
+---token // also store token
So when the child node of Sensor have been update it will send notification to User who have store the token(user id1 and id3). Glad if anyone could help me to solve this problem
Try storing the tokens in this format:
"tokens" : {
"cXyVF6oUGuo:APA91bHTSUPy31JjMVTYK" : true,
"deL50wnXUZ0:APA91bGAF-kWMNxyP6LGH" : true,
"dknxCjdSQ1M:APA91bGFkKeQxB8KPHz4o" : true,
"eZunoQspodk:APA91bGzG4J302zS7sfUW" : true
}
Whenever you want to write a new token just do a set:
firebase.app().database().ref(`/user/${uid}/tokens/${token}`).set(true);
And to create an array for sendToDevice:
const tokensList = Object.keys(tokens.val());
return admin.messaging().sendToDevice(tokensList, payload);

Categories

Resources