Firebase Cloud Functions: Cannot pass the token retrieved from Realtime Database - firebase

I'm having issues in retrieving a token saved in realtime database using cloud function's admin.database(). There is only one token to read from the child.
Firebase Database structure
Here's my code in Index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification = functions.database
.ref('/Logs/{LogsID}')
.onWrite( (change, context) => {
const notificationSnapshot = change.after.val();
const status = notificationSnapshot.Status;
const time = notificationSnapshot.Time;
const payload = {
notification: {
title : status,
body : time
}
}
console.info(notificationSnapshot);
const pushToken = admin.database().ref('/Tokens').once('child_added').then( (data) => {
const tokenSnapshot = data.val();
const finaltoken = tokenSnapshot.token;
console.info(finaltoken);
})
// Need help down here.
admin.messaging().sendToDevice(finaltoken, payload)
.then( () => {
console.log('Notification sent');
})
.catch( () =>{
console.log('Notification failed');
})
return null;
});
finalToken shows the correct token in log as expected. Log Showing the token
But I'm getting error while I'm passing the same token to admin.messaging(). Console is logging 'Notification sent' but not receiving a notification.
ReferenceError: finaltoken is not defined
at exports.sendNotification.functions.database.ref.onWrite (/user_code/index.js:43:36)
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:827:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
It works when I directly pass the token like,
var finalToken = 'ephrj1........kndji'
so the admin.messaging() works, only passing the token is not working.
I'm new to Cloud Functions and javascript, so any help is much appreciated.

Final token is being retrieved in callback / async function.
That means that when you add it to .sendToDevice() the token is undefined because the async function has not retrieved the token from the database... yet.
const pushToken = admin.database().ref('/Tokens').once('child_added').then( (data) => {
const tokenSnapshot = data.val();
const finaltoken = tokenSnapshot.token;
console.info(finaltoken);
admin.messaging().sendToDevice(finaltoken, payload)
.then( () => {
console.log('Notification sent');
})
.catch( () =>{
console.log('Notification failed');
})
// I moved admin.messaging above this bracket
})
// It used to be here
return null;
Try putting the admin.messaging code within the code block of (data) => {}
By doing this we ensure that whenever we call sendToDevice() the token is defined.

Related

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client | Express & Firestore

exports.addAdvert = async (req, res) => {
try {
const advert = req.body;
const newAdvert = await db.collection("adverts").add({...advert,date: firebase.firestore.FieldValue.serverTimestamp()});
const id = newAdvert.id;
return res.status(200).json({ id });
} catch (e) {
return res.status(400).json({ message: `Llamada fallida: ${e}` });
}
};
Hi!! I'm developing a backend using express and Firebase. Actually done others calls to backend and firebase and works. I don't know when I call this one returns this error but in firebase creates the new advert.

Better code to push notification: firestore - cloud function

I made a cloud function (using google :() that sends push notifications when adding a document in firestore, but I have the error that you see in the image and the notifications do not arrive but I do not understand what may be wrong in my code, can someone help me?
mi code:
exports.cambiaColeccion = functions.firestore
.document('sendMessage/{docId}')
.onCreate((snap, context) => {
const nuevoMensaje= snap.data();
console.log('id', nuevoMensaje);
console.log('titulo', nuevoMensaje.titulo)
enviaMensage();
});
async function enviaMensage() {
console.log('en enviaMensaje');
const payload ={
notification: {
title: "Titulo del mensaje",
body: "Texto del mensaje ... ",
sound: 'default',
badge: '1',
}
// Get the list of device tokens.
const allTokens = await admin.firestore().collection('FCMTokens').get();
const tokens = [];
allTokens.forEach((tokenDoc) => {
tokens.push(tokenDoc.id);
});
if (tokens.length > 0) {
// Send notifications to all tokens.
const response = await admin.messaging().sendToDevice(tokens, payload);
//await cleanupTokens(response, tokens);
console.log('Notifications have been sent and tokens cleaned up.');
}
return true
}
// Cleans up the tokens that are no longer valid.
function cleanupTokens(response, tokens) {
// For each notification we check if there was an error.
const tokensDelete = [];
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') {
const deleteTask = admin.firestore().collection('FCMTokens').doc(tokens[index]).delete();
tokensDelete.push(deleteTask);
}
}
});
return Promise.all(tokensDelete);
}
You need to return a Promise in your Cloud Function, in such a way the Cloud Functions instance running your function does not shut down before your function successfully reaches its terminating condition or state. See the doc for more details.
In your case you are not returning anything in the Cloud Function itself. Since async functions always return a Promise, you can adapt your code as follows:
exports.cambiaColeccion = functions.firestore
.document('sendMessage/{docId}')
.onCreate((snap, context) => {
const nuevoMensaje = snap.data();
console.log('id', nuevoMensaje);
console.log('titulo', nuevoMensaje.titulo)
return enviaMensage();
});
async function enviaMensage() {
console.log('en enviaMensaje');
const payload = {
notification: {
title: "Titulo del mensaje",
body: "Texto del mensaje ... ",
sound: 'default',
badge: '1',
}
}
// Get the list of device tokens.
const allTokens = await admin.firestore().collection('FCMTokens').get();
if (allTokens.size > 0) { // allTokens is a QuerySnapshot
const tokens = allTokens.docs.map(tokenDoc => tokenDoc.id);
await admin.messaging().sendToDevice(tokens, payload);
}
}
Note that it would be good to add some try/catch block in order to capture and debug potential errors.
Update following your comment on the cleanupTokens function.
Your cleanupTokens function is correct. It is asynchronous since it returns a Promise (returned by Promise.all(tokensDelete);).
The way you call it should work correctly:
const allTokens = await admin.firestore().collection('FCMTokens').get();
if (allTokens.size > 0) { // allTokens is a QuerySnapshot
const tokens = allTokens.docs.map(tokenDoc => tokenDoc.id);
const response = await admin.messaging().sendToDevice(tokens, payload);
await cleanupTokens(response, tokens);
}

Firebase function featuring val undefined

I am trying to do a push notification function whenever the database update sends a notification but the function gets an error at first
Error:
TypeError: Cannot read property 'val' of undefined
at exports.fcmSend.functions.database.ref.onCreate.event (/srv/index.js:9:31)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:131:23)
at /worker/worker.js:825:24
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
my function
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
exports.fcmSend = functions.database.ref('/messages/{userId}/{messageId}').onCreate(event => {
console.log('event', event)
const message = event.after.val()
const userId = event.params.userId
const payload = {
notification: {
title: message.title,
body: message.body,
icon: "https://placeimg.com/250/250/people"
}
};
admin.database()
.ref(`/fcmTokens/${userId}`)
.once('value')
.then(token => token.val() )
.then(userFcmToken => {
return admin.messaging().sendToDevice(userFcmToken, payload)
})
.then(res => {
console.log("Sent Successfully", res);
})
.catch(err => {
console.log('err', err);
});
});
The onCreate() listener doesn't have an "after". Only onUpdate() and onWrite() have before and after. It should work just to do:
event.val();

Firebase Functions ReferenceError with SendGrid function

I'm trying to get started with SendGrid and a Firestore database, using Firebase functions. I've gone through tutorials and set up according to the latest notation, (snap, context) instead of (event). I can't figure out what's wrong with this script:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
//admin.initializeApp(functions.config().firebase);
const SENDGRID_API_KEY = my-api-key-is-here;
const sendgridemail = require('#sendgrid/mail');
sendgridemail.setApiKey(SENDGRID_API_KEY);
exports.confEmail = functions.firestore
.document('clients/{clientId}/projects/{projectId}/form-data/{docId}') //any write to this node will trigger email
.onCreate((snap, context) => {
const clientId = context.params.clientId;
const projectId = context.params.projectId;
const docId = context.params.docId;
const fsdb = admin.firestore();
return fsdb.collection('clients/'+clientId+'/projects/'+projectId+'/form-data').doc(docId)
.get()
.then(doc => {
const docData = doc.data()
const msgbody = {
to: docData.EMAIL,
from: 'xxxxx#gmail.com',
subject: 'Form Submission Confirmation',
templateId: 'd-07bf6a2b89084951a30ceddcd9c8915f',
substitutionWrappers: ['{{', '}}'],
substitutions: {
formdata: "Message Body\n<br>"+docData.CONF_MSG
}
};
return confEmail.send(msgbody)
})
.then(() => console.log('confimration mail sent success') )
.catch(err => console.log(err) )
});
The error message generated in the Firebase console is mysterious, and I'm not even sure how to interpret it.
ReferenceError: confEmail is not defined
at fsdb.collection.doc.get.then.doc (/user_code/index.js:48:13)
at process._tickDomainCallback (internal/process/next_tick.js:135:7
)
My best guess just that my 'confEmail' function is not defined because there's an error in it, but I can't figure out what. Or does it mean something else?
It looks like most of the tutorial scripts are over-complicating things. and a simpler script like this seems to work.
const sendgrid = require('#sendgrid/mail');
sendgrid.setApiKey(SENDGRID_API_KEY);
exports.confEmail = functions.firestore
.document('clients/{clientId}/projects/{projectId}/form-data/{docId}') //any write to this node will trigger email
.onCreate((snap, context) => {
const docData = snap.data();
const msgbody = {
to: docData.EMAIL,
from: 'xxxxxxx#gmail.com',
subject: 'Form Submission Confirmation',
templateId: 'd-07bf6a2b89084951a30ceddcd9c8915f',
substitutionWrappers: ['{{', '}}'],
substitutions: {
formdata: docData.CONF_MSG
}
};
return sendgrid.send(msgbody)
});

I can't replace the sk_test key with the sk_live key on Stripe using Firebase cloud functions

I have a React Native application, running on a firebase backend. I have integrated with Stripe. The token is created by the client, and the firebase cloud function creates the charge with that token. I have built the app and tested payments using the test keys in Stripe.
I have now replaced the test keys with the live keys.
The live public key is working in the React Native application, and is creating a token successfully.
here is the function for creating the token code in the React Native application
import Stripe from 'react-native-stripe-api';
async payByCard() {
const { user } = this.props;
const uid = user.uid;
const { number, exp_month, exp_year, cvc } = this.state;
this.setState({ loading: true });
const apiKey = 'pk_live_#######################';
const client = new Stripe(apiKey);
try {
const token = await client.createToken({
number,
exp_month,
exp_year,
cvc,
});
this.props.addToken({ token }, uid);
} catch (error) {
this.setState({ error: error.message, loading: false });
}
}
The firebase cloud functions, however, is still using the secret test key.
here is the loud function for creating a charge.
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.testkey)
export const stripeCharge = functions.database
.ref('/payments/{userUid}/{paymentUid}')
.onWrite((change, context) => {
const payment = change.after.val();
const userUid = context.params.userUid;
const paymentUid = context.params.paymentUid;
if (!payment || payment.charge || !payment.pendingBasket) return;
return admin.database()
.ref(`/users/${userUid}`)
.once('value')
.then(snapshot => {
return snapshot.val();
})
.then(customer => {
const amount = Number(payment.pendingBasket.total * 100).toFixed(0)
const idempotency_key = paymentUid;
const source = payment.token.id;
const currency = 'gbp';
const description = `Athalens ${customer.address.FirstName} ${customer.address.LastName} - ${customer.address.PostCode}`
const charge = {amount, currency, description, source};
return stripe.charges.create(charge, { idempotency_key });
}).catch((error) => {
console.log('error 1 =' + error.message);
admin.database()
.ref(`/payments/${userUid}/${paymentUid}/status`)
.set(error.message)
})
.then(charge => {
admin.database()
.ref(`/payments/${userUid}/${paymentUid}/charge`)
.set(charge)
if (charge.status === "succeeded") {
customerOrders(userUid, paymentUid)
photographerUid(userUid, paymentUid)
clearBasket(userUid)
confirmation(userUid, paymentUid);
} else {
decline(userUid, paymentUid)
}
}).catch((error) => {
console.log('error 2 =' + error.message);
})
})
The process I am doing to upload the Secret key to firebase:
1. Williams-MBP:~ williamgoodhew$ cd /Users/williamgoodhew/projects/athalens/athalens_server_code/basket/functions
2. Williams-MBP:functions williamgoodhew$ firebase functions:config:set stripe.token=“sk_live_#################”
3. Williams-MBP:functions williamgoodhew$ firebase deploy --only functions
When I test the live payment system, a token is created, but no charge is created. and I receive the following error in the cloud functions log:
No such token: tok_############; a similar object exists in live mode, but a test mode key was used to make this request.
I have got in contact with Firebase and it was a silly error my end.
In my cloud function, I had initialized my test key "
const stripe = require('stripe')(functions.config().stripe.testkey)
" instead of using "stripe.token".
I changed stripe.testkey to stripe.token.and everything worked out fine.

Resources