Database Structure
Notification Structure
After successfully deploying my function into fire-base I am having these error in firebase function logs
1: Function execution took 1724 ms, finished with status: 'error'
2:TypeError: Cannot read property 'uid' of undefined
at exports.sendNotification.functions.firestore.document.onWrite.event
(/user_code/index.js:9:30)
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:733:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
3:{"#type":"type.googleapis.com/google.cloud.audit.AuditLog","status":
{"code":3,"message":"The request has errors"},"authenticationInfo":
{"principalEmail":"info.jap.123#gmail.com"},"requestMetadata"{"callerIp":"39.50.37.100",
Index.js
'use-strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
//admin.initializeApp(functions.config().firebase);
exports.sendNotification =
functions.firestore.
document("Users/{uid}/Notification/{nid}").onWrite(event=>
{
const user_id = event.params.uid
const notification_id = event.params.nid;
});
Notification Class in this class i am storing the notification id and message into Firebase-Firestore.
private void sendNotification()
{
String number = edTxtDevId.getText().toString();
if (TextUtils.isEmpty(number))
{
edTxtDevId.setError("Please Provide the dev id thank you");
return;
}
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String time = String.valueOf(cal.getTime());
final String message = "Notification has been sent to this number "+number+" by this number "+user_id+" at time = "+time;
Map<String , Object> notificationMessage = new HashMap<>();
notificationMessage.put("From" , mUserId);
notificationMessage.put("message" , message);
for (int i = 0 ; i < userList.size() ; i++)
{
if (userList.get(i).equals(number))
{
Log.e(TAG, "sendNotification: "+number+" found " +userList.get(i) + " id = "+i);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
mFirestore.collection("Users/"+mUserId+"/Notification").add(notificationMessage).addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(DocumentReference documentReference) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}
}
Please help me if you know how to solve these
Thank you in advance!
Edited:
First of all, just as written here,
You need to pass a function with 2 arguments to onWrite().
Here is an example.
In your case,
document("Users/{uid}/Notification/{nid}").onWrite(event=> should be
document("Users/{uid}/Notification/{nid}").onWrite((change, context) =>
So, your code should look like:
'use strict'
const functions = require('firebase-functions');
// const admin = require('firebase-admin');
// admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.firestore
.document("Users/{uid}/Notification/{nid}")
.onWrite((change, context) => { // <- changed arguments here
// console.log(uid)
// console.log(nid)
// you can try these, but i think these console.log() will not work.
});
Second, if you want to retrieve uid and nid from the document path, you can use functions.database.DataSnapshot.ref or
functions.EventContext.resource
so your code should look like:
'use strict'
const functions = require('firebase-functions');
// const admin = require('firebase-admin');
// admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.firestore
.document("Users/{uid}/Notification/{nid}")
.onWrite((change, context) => {
// console.log(change.ref)
// the output will be like: "Users/PfUyNf.../Notification/0Ujdt..."
// or
// console.log(context.resource)
// the output will be like:
// "projects/_/instances/<databaseInstance>/refs/Users/PfUyNf.../Notification/0Ujdt..."
});
then use regexp or something to process the output.
I think there will be better solutions, but hope it helps.
Related
I'm trying to get an emphemoral key from stripe using firebase functions. Here is the error I see in my firebase logs:
getStripeEphemeralKeysUnhandled error TypeError: Cannot read property 'create' of undefined at exports.getStripeEphemeralKeys.functions.https.onCall (/workspace/index.js:40:42) at func (/workspace/node_modules/firebase-functions/lib/providers/https.js:273:32) at process._tickCallback (internal/process/next_tick.js:68:7)
Here is the error that shows in the xcode console:
Error creating ephenmeral key: Error Domain=com.firebase.functions Code=13 "INTERNAL" UserInfo={NSLocalizedDescription=INTERNAL} INTERNAL
firebase function written in Javascript:
const functions = require('firebase-functions');
const stripe_key = "sk_test_LT2Wc4v9kUD6XxG8Nq2LNh4P00thTqtiSa"
const admin = require('firebase-admin');
var stripe = require('stripe')(stripe_key);
admin.initializeApp(functions.config().firebase);
exports.createStripeCustomer = functions.https.onCall(async (data, context) => {
const full_name = data.full_name;
const email = data.email;
const customer = await stripe.customers.create({
email: email,
name: full_name,
description: full_name
});
console.log('new customer created: ', customer.id)
return {
customer_id: customer.id
}
});
exports.getStripeEphemeralKeys = functions.https.onCall(async (data, context) => {
const api_version = data.api_version;
const customer_id = data.customer_id;
const key = await stripe.ephemeralKeys.create (
{customer: customer_id},
{apiVersion: api_version}
);
return key;
});
iOS call using swift
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let stripe_customer_id = MyDefaults.getDefaultsForCustID()
functions.httpsCallable("getStripeEphemeralKeys").call(["api_version" : apiVersion, "customer_id" : stripe_customer_id]) { (response, error) in
if let error = error {
completion(nil, error)
}
if let response = (response?.data as? [String: Any]) {
completion(response, nil)
}
}
}
I'm not versed enough in Javascript to know what the error means except there seems to be a problem with the word create. Any thoughts are much appreciated!
OK Thanks #floatingLomas #Rafael Lemos. You guys were on the right track. It was the stripe version. I was having some pod errors that I was pushing to fix later so cleaned up and updated cocoa pods ruby and npm... now when I firebase deploy - I finally got the right version of stripe up to firebase...
I"M GOOD NOW -- THANKS!!!
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've deployed this code to my firebase functions project:
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp()
export const getEmail = functions.https.onRequest((request, response) => {
var from = request.body.sender;
admin.auth().getUserByEmail(from)
.then(snapshot => {
const data = snapshot.toJSON()
response.send(data)
})
.catch(error => {
//Handle error
console.log(error)
response.status(500).send(error)
})
})
Which takes in a email parameter that it gets from the user's input on my app. My app's code looks like this:
Functions.functions().httpsCallable("https://us-central1-projectname.cloudfunctions.net/getEmail").call(email) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
//email isnt taken
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
print(code, message, details)
}
// ...
}
if let text = (result?.data as? [String: Any])?["text"] as? String {
// email taken
}
}
When I run the app and when that function is called, it seems to do nothing, no error message is shown and no data has been sent back. What am I missing?
Update: I went to the logs and nothing has happened in there as if the function was never called.
You are actually mixing up HTTP Cloud Functions and Callable Cloud Functions:
You Cloud Function code corresponds to an HTTP one but the code in your front-end seems to call a Callable one.
You should adapt one or the other, most probably adapt your Cloud Function to a Callable one, along the following lines:
exports.getEmail = functions.https.onCall((data, context) => {
const from = data.sender;
return admin.auth().getUserByEmail(from)
.then(userRecord => {
const userData = userRecord.toJSON();
return { userData: userData };
})
});
Have a look at the doc for more details, in particular how to handle errors. The doc is quite detailed and very clear.
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);
})
});