So Today I updated the firebase cli and after that deployed a new function. Although the firebase log shows that notifications has been sent to this many tokens, no notification occurs. An error shows in the log
Function returned undefined, expected Promise or value
I searched for answers in stack overflow but nothing helped.
Also I would like to add that before it was showing some different error
TypeError: Cannot read property 'description' of null
and now suddenly it is showing function returned undefined.
Not sure what is wrong. Any help is appreciated.
Index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
function token_send(admin,title_input,body_input,getBody,getDeviceTokensPromise,change){
// Only edit data when it is first created.
if (change.before.val()) {
return 0;
}
// Exit when the data is deleted.
if (!change.after.val()) {
return 0;
}
return Promise.all([getDeviceTokensPromise,getBody]).then(results => {
const tokensSnapshot = results[0];
const notify=results[1];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
var contentAlert = change.after.val();
// Notification details.
const payload = {
'data': {
'title': title_input,
'body': body_input
}
};
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
console.log("Successfully sent message:", response);
console.log("content alert",contentAlert);
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
}
exports.sendNotificationCouncil = functions.database.ref(`path/Post/{pushId}`).onWrite((change,context) => {
const getDeviceTokensPromise = admin.database().ref(`/Token/token_no`).once('value');
const getBody=admin.database().ref(`/Post`).once('value');
var title_input='You have new Post';
var contentAlert = change.after.val();
var body_input=contentAlert.description; //showing error here
token_send(admin,title_input,body_input,getBody,getDeviceTokensPromise,change);
});
You should return the promise (returned by token_send()) in the sendNotificationCouncil Cloud Function, as follows:
exports.sendNotificationCouncil = functions.database.ref(`path/Post/{pushId}`).onWrite((change,context) => {
const getDeviceTokensPromise = admin.database().ref(`/Token/token_no`).once('value');
const getBody=admin.database().ref(`/Post`).once('value');
var title_input='You have new Post';
var contentAlert = change.after.val();
var body_input=contentAlert.description; //showing error here
return token_send(admin,title_input,body_input,getBody,getDeviceTokensPromise,change);
});
Note that it is also a best practice to catch the errors in your Function and in this case return false.
Related
I'm a beginner in Next.js and Firebase. I was trying to make a log in system that has roles. I used Firebase Authentication and stored the account's roles on firestore. I connected the authentication account and firestore data by using the UID (from authentication) as the Firestore Document ID.
Ps. I'm not sure if this is the best way to do this but I am able to fetch data from a field by looking for the Document ID fetched using the UID.
What I need to happen is to get the role of the Account from the Firestore and use that for a function to push the correct page for that account type. The problem is, my pushToPage() fires first before my getData() gets the information it asks for from the firestore.
This is the LogIn function. I used auth.onAuthStateChanged to get the UID of the user.
var firebaseDocument = ' ';
var accountType = '';
var databaseRef = '';
function LogIn() {
signInWithEmailAndPassword(auth, email, password)
.then((response) => {
sessionStorage.setItem('Token', response.user.accessToken);
auth.onAuthStateChanged((user) => {
if (user) {
firebaseDocument = user.uid;
databaseRef = doc(database, 'users', firebaseDocument);
} else {
console.log('user logged out');
}
});
})
.catch((err) => {
console.log(err.code);
});
accountType = getData();
pushToPage(accountType);
}
This is the getData function where it fetches the account role
const getData = async () => {
try {
const docSnap = await getDoc(databaseRef);
if (docSnap.exists()) {
return docSnap.data().account_type;
} else {
console.log('Document does not exist');
}
} catch (e) {
console.log(e);
}
};
this is the pushToPage function that reads the var accountType value to decide on what page to go.
const pushToPage = (accountType) => {
if (accountType == 'management') {
router.push('/accounts/management');
} else if (accountType == 'dean') {
router.push('/accounts/deans');
} else {
router.push('/accounts/faculty');
}
};
Running this code does not only make my Program wait for the firebase response but also displays this error
Unhandled Runtime Error
FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
I didn't use collection() though.
Also, I searched for similar problems and I think I need to place a .then() function somewhere or somekind of Asynchronous Programming technique. Unfortunately, I struggle in understanding these.
I hope I can find help here. Thanks guys.
I don't think I need the .then() function anymore. I just called the pushToPage() inside the getData.
const getData = async () => {
try {
const docSnap = await getDoc(databaseRef);
if (docSnap.exists()) {
accountType = docSnap.data().account_type;
pushToPage(accountType);
} else {
console.log('Document does not exist');
}
} catch (e) {
console.log(e);
}
};
I am using the source code from a security rules tutorial to attempt to do integration testing with Jest for my Javascript async function async_create_post, used for my firebase HTTP function create_post The files involved has a directory structure of the following:
Testing file: root/tests/handlers/posts.test.js
File to be tested: root/functions/handlers/posts.js
Helper code from the tutorial: root/tests/rules/helpers.js
And here is the source code that is involved:
posts.test.js
const { setup, teardown} = require("../rules/helpers");
const {
async_get_all_undeleted_posts,
async_get_post,
async_delete_post,
async_create_post
} = require("../../functions/handlers/posts");
describe("Post Creation", () => {
afterEach(async () => {
await teardown();
});
test("should create a post", async () => {
const db = await setup();
const malloryUID = "non-existent uid";
const firstPost = {
body: "First post from Mallory",
author_id: malloryUID,
images: ["url1", "url2"]
}
const before_post_snapshot = await db.collection("posts").get();
expect(before_post_snapshot.docs.length).toBe(0);
await async_create_post(firstPost); //fails at this point, expected to create a new post, but instead threw an error
const after_post_snapshot = await db.collection("posts").get();
expect(after_post_snapshot.docs.length).toBe(1);
});
});
posts.js
const {admin, db } = require('../util/admin');
//admin.initializeApp(config); //my credentials
//const db = admin.firestore();
const { uuid } = require("uuidv4");
const {
success_response,
error_response
} = require("../util/validators");
exports.async_create_post = async (data, context) => {
try {
const images = [];
data.images.forEach((url) => {
images.push({
uid: uuid(),
url: url
});
})
const postRecord = {
body: data.body,
images: images,
last_updated: admin.firestore.FieldValue.serverTimestamp(),
like_count: 0,
comment_count: 0,
deleted: false,
author_id: data.author_id
};
const generatedToken = uuid();
await db
.collection("posts")
.doc(generatedToken)
.set(postRecord);
// return success_response();
return success_response(generatedToken);
} catch (error) {
console.log("Error in creation of post", error);
return error_response(error);
}
}
When I run the test in Webstorm IDE, with 1 terminal running Firebase emulators:start , I get the following error message.
console.log
Error in creation of post TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Object
at validateString (internal/validators.js:120:11)
at Object.basename (path.js:1156:5)
at GrpcClient.loadProto (/Users/isaac/Desktop/project/functions/node_modules/google-gax/src/grpc.ts:166:23)
at new FirestoreClient (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/v1/firestore_client.js:118:38)
at ClientPool.clientFactory (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/index.js:330:26)
at ClientPool.acquire (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/pool.js:87:35)
at ClientPool.run (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/pool.js:164:29)
at Firestore.request (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/index.js:961:33)
at WriteBatch.commit_ (/Users/isaac/Desktop/project/functions/node_modules/#google-cloud/firestore/build/src/write-batch.js:485:48)
at exports.async_create_post (/Users/isaac/Desktop/project/functions/handlers/posts.js:36:5) {
code: 'ERR_INVALID_ARG_TYPE'
}
at exports.async_create_post (/Users/isaac/Desktop/project/functions/handlers/posts.js:44:13)
Error: expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
<Click to see difference>
at Object.<anonymous> (/Users/isaac/Desktop/project/tests/handlers/posts.test.js:59:45)
Error in creation of post comes from the console.log("Error in creation of post", error); in posts.js, so the error is shown in the title of this post.
I want to know why calling the async_create_post from posts.test.js will cause this error and does not populate my database with an additional record as expected behaviour. Do inform me if more information is required to solve the problem.
Here are some code snippets that may give more context.
helpers.js [Copied from the repository]
const firebase = require("#firebase/testing");
const fs = require("fs");
module.exports.setup = async (auth, data) => {
const projectId = `rules-spec-${Date.now()}`;
const app = firebase.initializeTestApp({
projectId,
auth
});
const db = app.firestore();
// Apply the test rules so we can write documents
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync("firestore-test.rules", "utf8")
});
// write mock documents if any
if (data) {
for (const key in data) {
const ref = db.doc(key); // This means the key should point directly to a document
await ref.set(data[key]);
}
}
// Apply the actual rules for the project
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync("firestore.rules", "utf8")
});
return db;
// return firebase;
};
module.exports.teardown = async () => {
// Delete all apps currently running in the firebase simulated environment
Promise.all(firebase.apps().map(app => app.delete()));
};
// Add extensions onto the expect method
expect.extend({
async toAllow(testPromise) {
let pass = false;
try {
await firebase.assertSucceeds(testPromise);
pass = true;
} catch (error) {
// log error to see which rules caused the test to fail
console.log(error);
}
return {
pass,
message: () =>
"Expected Firebase operation to be allowed, but it was denied"
};
}
});
expect.extend({
async toDeny(testPromise) {
let pass = false;
try {
await firebase.assertFails(testPromise);
pass = true;
} catch (error) {
// log error to see which rules caused the test to fail
console.log(error);
}
return {
pass,
message: () =>
"Expected Firebase operation to be denied, but it was allowed"
};
}
});
index.js
const functions = require('firebase-functions');
const {
async_get_all_undeleted_posts,
async_get_post,
async_delete_post,
async_create_post
} = require('./handlers/posts');
exports.create_post = functions.https.onCall(async_create_post);
The error message means that a method of the path module (like path.join) expects one of its arguments to be a string but got something else.
I found the offending line by binary search commenting the program until the error was gone.
Maybe one of your modules uses path and you supply the wrong arguments.
I'm trying to deploy a Firebase Cloud Function that sends a text message to its associated recipient for x number of text messages. The function is triggered in my iOS app when an update is made to the 'send' Realtime Database reference, indicating that the user has pressed the 'send' button.
My Firebase structure is
{
"user1uid": {
"send": false
"messagesToSend": {
"messageuid1": {
"messageText": "What's for dinner?",
"recipientNumber": "+18017378888",
}
"messageuid2:
"messageText": "Who won the Cowboys game?",
"recipientNumber": "+18017377787",
}
}
"user2uid": {
"send": false
"messagesToSend": {
"messageuid1": {
"messageText": "What's for dinner?",
"recipientNumber": "+18017378888",
}
"messageuid2:
"messageText": "Who won the Cowboys game?",
"recipientNumber": "+18017377787",
}
}
}
My code currently only sends one message, and I'm not sure how I can properly iterate through the messagesToSend node for each user and send all the messages in it.
I've been trying to follow the tutorial located here. I have looked at the following Stack Overflow responses but am unable to decipher or derive a solution from them:
Firebase cloud function promises
Am I using ForEach correctly?
My index.js code that sends one message is as follows:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
const twilio = require('twilio')
const accountSid = functions.config().twilio.sid;
const authToken = functions.config().twilio.token;
const client = new twilio(accountSid, authToken);
const twilioNumber = functions.config().twilio.number;
// Start cloud function
exports.sendSecrets = functions.database
.ref('/{uid}/send')
.onUpdate((change,context) => {
const uid = context.params.uid;
return admin.database().ref(uid+'/messagesToSend').once('value').then(snapshot => {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var messageData = childSnapshot.val();
**if (messageData.sanitized) return true;**
var message = messageData.messageText;
var phoneNumber = messageData.recipientNumber;
const textMessage = {
body: `From My App - ${message}`,
from: twilioNumber, // From Twilio number
to: phoneNumber // Text to this number
}
return client.messages.create(textMessage)
})
**return snapshot.ref.toString();**
});
});
Please note that the lines marked with ** at either end indicate that I know I need to return something based on error messages I received indicating that 'Each then() should return a value or throw'.
I make the assumption that you are using the twilio-node library that use promises: https://www.npmjs.com/package/twilio.
Since you want to send several messages in parallel, you have to use Promise.all(), as follows:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
const twilio = require('twilio')
const accountSid = functions.config().twilio.sid;
const authToken = functions.config().twilio.token;
const client = new twilio(accountSid, authToken);
const twilioNumber = functions.config().twilio.number;
// Start cloud function
exports.sendSecrets = functions.database
.ref('/{uid}/send')
.onUpdate((change,context) => {
const uid = context.params.uid;
return admin.database().ref(uid+'/messagesToSend').once('value')
.then(snapshot => {
const promises = [];
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var messageData = childSnapshot.val();
//**if (messageData.sanitized) return true;**
var message = messageData.messageText;
var phoneNumber = messageData.recipientNumber;
const textMessage = {
body: `From My App - ${message}`,
from: twilioNumber, // From Twilio number
to: phoneNumber // Text to this number
}
promises.push(client.messages.create(textMessage));
})
return Promise.all(promises);
})
// Edits made below to parentheses/brackets
.then(results => {
//Do whatever you want !!
// e.g. print the results which will be an array of messages
// (see https://www.twilio.com/docs/libraries/node#testing-your-installation)
})
});
You can also simply return Promise.all() as follows:
....
return Promise.all(promises);
})
});
Possible duplicate. Not sure.
connections: {
connectionID : {
userID: true,
anotherUserID: true
},
users: {
userID : {
deviceToken : "tokenID",
name : "Display Name"
},
anotherUserID : {
deviceToken : "tokenID",
name : "Display Name"
}
}
and so on and so forth.
This is my index.js:
exports.sendConnectionNotification = functions.database.ref('/connections/{connectionID}/{userID}').onWrite(event => {
const parentRef = event.data.ref.parent;
const userID = event.params.userID;
const connectionID = event.params.connectionID;
// If un-follow we exit the function.
if (!event.data.val()) {
return console.log('Connection', connectionID, 'was removed.');
}
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database().ref('/users/${userID}/deviceToken').once('value');
// Get the user profile.
const getUserProfilePromise = admin.auth().getUser(userID);
and it continues. I am getting this error in my logcat:
Error: Firebase.child failed: First argument was an invalid path: "/users/${userID}/deviceToken". Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"
at Error (native)
at Ge (/user_code/node_modules/firebase-admin/lib/database/database.js:111:59)
at R.h.n (/user_code/node_modules/firebase-admin/lib/database/database.js:243:178)
at Fd.h.gf (/user_code/node_modules/firebase-admin/lib/database/database.js:91:631)
at exports.sendConnectionNotification.functions.database.ref.onWrite.event (/user_code/index.js:31:51)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
I do not understand why Firebase is not able to reach the node. Clearly, my path is valid. Where am I going wrong? Sorry, I happen to start learning Firebase Functions just today.
**EDIT 1: **
After replacing:
const getDeviceTokensPromise = admin.database().ref('/users/${userID}/deviceToken').once('value');
with
const getDeviceTokensPromise = admin.database().ref(`/users/${userID}/deviceToken`).once('value');
I have gotten a new error. My console log displays:
There are no notification tokens to send to.
Here is my full index.js:
// // Create and Deploy Your First Cloud Functions
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/**
* Triggers when a user gets a new follower and sends a notification.
*
* Followers add a flag to `/followers/{followedUid}/{followerUid}`.
* Users save their device notification tokens to `/users/{followedUid}/notificationTokens/{notificationToken}`.
*/
exports.sendConnectionNotification = functions.database.ref('/connections/{connectionID}/{userID}').onWrite(event => {
const parentRef = event.data.ref.parent;
const userID = event.params.userID;
const connectionID = event.params.connectionID;
// If un-follow we exit the function.
if (!event.data.val()) {
return console.log('Connection', connectionID, 'was removed.');
}
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database().ref(`/users/${userID}/deviceToken`).once('value');
// Get the user profile.
const getUserProfilePromise = admin.auth().getUser(userID);
return Promise.all([getDeviceTokensPromise, getUserProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const user = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
console.log('Fetched user profile', user);
// Notification details.
const payload = {
notification: {
title: `${user.userNickName} is here!`,
body: 'You can now talk to each other.'
}
};
// Listing all tokens.
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
You can do use (`) instead of (') as i was also having same problem and solved by using this.
thanks
Change
const getDeviceTokensPromise = admin.database().ref('/users/${userID}/deviceToken').once('value');
to
const getDeviceTokensPromise = admin.database().ref('/users/' + userID + '${userID}/deviceToken').once('value');
'/users/${userID}/deviceToken' is not a valid path.
but '/users/123456/deviceToken' where 123456 represents the user ID, is.
maybe you are using single quote instead of back-ticks.
https://developers.google.com/web/updates/2015/01/ES6-Template-Strings
so the path is not concatenated in a right way.
I recently developed an application for a school and one of its features is principal's updates. For this, I'm using the Firebase database for this (on Android studio and Xcode). I heard that there is a new Firebase feature that calls cloud functions and I heard that I can integrate Database and FCM. For now, I have this code in index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.sendNotifications = functions.database.ref('/messageS/{id}').onWrite(event => {
const snapshot = event.data;
// Only send a notification when a message has been created.
if (snapshot.previous.val()) {
return;
}
// Notification details.
const text = snapshot.val().text;
const payload = {
notification: {
title: 'new message recived',
body: text ? (text.length <= 100 ? text : text.substring(0, 97) + '...') : '',
icon: '/app/src/main/res/drawable/logo_he_digita_homepage.png'
}
};
// Get the list of device tokens.
return admin.database().ref('fcmTokens').once('value').then(allTokens => {
if (allTokens.val()) {
// Listing all tokens.
const tokens = Object.keys(allTokens.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(allTokens.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
}
});
});
I receive the log into Firebase console, but I don't see fcmTokens in the database and the device doesn't get the notification. what should I do?
Thanks for the help.