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);
})
});
Related
I'm trying to write to a subset within my database and I get one console error and another error in the google cloud functions saying:
Error: Unauthorized
Your client does not have permission to the requested URL /updateFirestore.
&
Error: Value for argument "collectionPath" is not a valid resource path. Path must be a non-empty string.
at undefined. ( /workspace/index.js:18 )
at .processTicksAndRejections ( node:internal/process/task_queues:96 )
This is how my firestore is set up:
Here is where I'm trying to call my firestore database in my index.js function:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.updateFirestore = functions.database
.ref("studiopick/studio/users/{uid}")
.onWrite((change, context) => {
const uid = context.params.uid;
if (!change.after.exists()) {
return null;
}
// Grab the current value of the Realtime Database.
const data = change.after.val();
const firestoreDb = admin.firestore();
const docReference = firestoreDb.collection("studiopick/studios/" + uid);
return docReference.set(
{
TransmitterError: data.TransmitterError,
},
{merge: true},
);
});
I work with google cloud functions and cloud messaging on Firebase. However, when I try to use Async/Await functions, I have an error: error Parsing error: Unexpected token =>
I work with Node.js v16.
Here is my code:
const functions = require("firebase-functions");
// const mess = require("firebase/messaging");
const admin = require("firebase-admin");
exports.sendListenerPushNotificationProductUpdate = functions.database
.ref("Products/{product}/type/")
.onUpdate(async (snapshot, context) => {
...
const tokensSnapshot = await Promise.resolve(getDeviceTokensPromise);
console.log("TOKEN: " + JSON.stringify(tokensSnapshot));
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return functions.logger.log(
"There are no notification tokens to send to."
);
}
...
// Listing all tokens as an array.
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
const response = await admin.messaging().sendToDevice(tokens, payload);
// For each message check if there was an error.
const tokensToRemove = [];
return Promise.all(tokensToRemove);
});
Does getDeviceTokensPromise return a Promise? If so, that line should just be
const tokensSnapshot = await getDeviceTokensPromise()
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.
Just starting to use Firebase functions and have the sample working, but confused because the update event doesn't occur if I change the 'messages' collection to a different name, eg 'listings'. I change the word 'messages' in two places, on the 'add' and the 'makeUppercase' line. I get the response OK, it writes the data to the collection, but doesn't fire the event. Must be simple, but can't google it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the location parameter.
const inputcode = req.query.code || 'blank';
// Push the new message into Cloud Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('messages').add({inputcode: inputcode});
// Send back a message that we've succesfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Cloud Firestore.
const inputcode = snap.data().inputcode;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, inputcode);
const areacode = inputcode.toUpperCase();
const written = new Date();
return snap.ref.set({written, areacode}, {merge: true});
});
I'm using the local firebase emulator to do this test, by the way.
This is the new version, ony changing 'messages' to 'vvvv' in two places.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the location parameter.
const inputcode = req.query.code || 'blank';
// Push the new message into Cloud Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('vvvvv').add({inputcode: inputcode});
// Send back a message that we've succesfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
exports.makeUppercase = functions.firestore.document('/vvvvv/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Cloud Firestore.
const inputcode = snap.data().inputcode;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, inputcode);
const areacode = inputcode.toUpperCase();
const written = new Date();
return snap.ref.set({written, areacode}, {merge: true});
});
OK. Doug, your suggestion sank in after an hour or so! I've restarted everything and think that I understand. If I change the name in those two places, without restarting, the collection.add function takes place and I can see the record in the new collection, but the onCreate event didn't fire. I had to restart the whole service to restart buth parts. I was getting confused because one part was working and not the other. Thanks for your patience.
I am pretty new to DialogFlow. I am wondering how can I retrieve data from Firebase through the Inline Editor of DialogFlow. Hope you can help me!
Thats how you can communicate with firebase from dialogflow
const functions = require('firebase-functions');
const firebaseAdmin = require('firebase-admin');
const DialogflowApp = require('actions-on-google').DialogflowApp;
Initialize Firebase Admin SDK.
firebaseAdmin.initializeApp(functions.config().firebase);
Interaction with firebase Collection users in fulfillment function
let userId = app.getUser().userId;
admin.firestore().collection('users').where('userId', '==', userId).limit(1).get()
.then(snapshot => {
let user = snapshot.docs[0]
if (!user) {
// If user is not in DB, its their first time, Welcome them!
app.ask('Welcome to my app for the first time!');
// Add the user to DB
firebaseAdmin.firestore().collection('users').add({
userId: userId
}).then(ref => {
console.log('Added document with ID: ', ref.id);
});
} else {
// User in DB
app.ask('Welcome back!')
}
});
}
// Map function hanlder to Dialogflow's welcome intent action 'input.welcome'
const actionMap = new Map('input.welcome', start)
app.handleRequest(actionMap);