Multiple Firebase notifications with Node.js - node.js

I created a Node.js file to send notifications with the help of FCM, I uploaded the file to the "Functions" section of Firebase, it works correctly, when it finds a change in the database it sends a notification to the devices.
The problem is that it sends the same notification too many times (between 3 and 10).
this is my code Node.js:
var admin = require("firebase-admin");
var request = require('request');
const functions = require('firebase-functions');
var API_KEY = "<KEY>";
admin.initializeApp({
credential: admin.credential.cert({
projectId: "projectId",
clientEmail: "clientEmail",
privateKey: "privateKey"
}),
databaseURL: "https://database.firebaseio.com/"
});
exports.backendDeNotificaciones = functions.database.ref('/solicitudDeNotificaciones').onWrite(event => {
ref = admin.database().ref();
function EsperandoNotificaciones()
{
console.log("Esperando Notificaciones");
var requests = ref.child('solicitudDeNotificaciones');
requests.on('child_added', function(requestSnapshot)
{
var request = requestSnapshot.val();
enviarNotificacion(
request.username,
request.message,
function()
{
requestSnapshot.ref.remove();
});
}, function(error)
{
console.error(error);
});
};
function enviarNotificacion(username, message, onSuccess) {
request({
url: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: {
'Content-Type' :' application/json',
'Authorization': 'key='+API_KEY
},
body: JSON.stringify({
notification: {
title: message
},
to : '/topics/TOPIC_NAME'
})
}, function(error, response, body) {
if (error) { console.error(error); }
else if (response.statusCode >= 400) {
console.error('Error de HTTP: '+response.statusCode+' — '+response.statusMessage);
}
else {
onSuccess();
console.log("Notificación Enviada :)");
}
});
}
EsperandoNotificaciones();
});

Every time someone writes a notification request to the database, you read all notifications requests from the database, send them, and delete them. This may lead to race conditions when you receive a new notification request while processing another one.
You should instead simply respond to each individual notification request. This also nicely simplifies your code:
exports.backendDeNotificaciones =
functions.database.ref('/solicitudDeNotificaciones/{messageId}').onCreate(event => {
var request = event.data.val();
enviarNotificacion(
request.username,
request.message,
function() {
event.data.ref.remove();
});
}, function(error) {
console.error(error);
});
};
});

Related

Firebase 401 Error https://fcm.googleapis.com/fcm/send works with admin().messaging().send() but does not work with admin().messaing().sendTopic()

I have created my backend in nodejs like shown below. When calling code example 1, code executes without problem. Example 1 calls admin().messaging().send() and works; however, when calling code example 2 (sending topic message) there is 401 error (like shown below)
An error occurred when trying to authenticate to the FCM servers.
Make sure the credential used to authenticate this SDK has the proper permissions.
See https://firebase.google.com/docs/admin/setup for setup instructions.
PROJECT_NOT_PERMITTED
Error 401
Is there authorization setting when sending topic message? What needs to be done to resolve issue with 401 Error? Thank you
Example 1
// Load the AWS SDK for Node.js
var AWS = require("aws-sdk");
// Set the region
AWS.config.update({ region: "ap-northeast-2" });
var admin = require("firebase-admin");
var serviceAccount = require("../firebadeCredentialInformation.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
module.exports.handler = async (event) => {
console.log(event);
var body = JSON.parse(event.body);
console.log(body);
var topic;
var topicPayload;
try {
//body, title, data, token
const message = {
data: {
type: "VOTE",
senderName: body.senderName,
question: body.question,
questionRangeKey: body.questionRangeKey,
senderToken: body.senderToken,
gender: body.gender,
schoolGrade: body.schoolGrade,
schoolName: body.schoolName,
},
token: body.token,
};
//? Make sure the message is sent
//TODO REMOVE BELOW AWAIT TO IMPROVE SPEED
var result = await admin.messaging().send(message);
console.log(result);
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Sucessfully sent response",
input: event,
},
null,
2
),
};
} catch (e) {
console.log("Error", e);
return {
statusCode: 500,
body: JSON.stringify(
{
message: e,
input: event,
},
null,
2
),
};
}
};
Example 2
// Load the AWS SDK for Node.js
var AWS = require("aws-sdk");
// Set the region
AWS.config.update({ region: "ap-northeast-2" });
var admin = require("firebase-admin");
var serviceAccount = require("../firebadeCredentialInformation.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
module.exports.handler = async (event) => {
console.log(event);
var body = JSON.parse(event.body);
console.log(body);
var topic;
var topicPayload;
try {
//body, title, data, token
const message = {
data: {
type: "VOTE",
senderName: body.senderName,
question: body.question,
questionRangeKey: body.questionRangeKey,
senderToken: body.senderToken,
gender: body.gender,
schoolGrade: body.schoolGrade,
schoolName: body.schoolName,
},
token: body.token,
};
//? Make sure the message is sent
//TODO REMOVE BELOW AWAIT TO IMPROVE SPEED
var result = await admin.messaging().send(message);
console.log(result);
if (body.schoolId != "") {
//* Ff not missing school id
const topicPayload = {
data: {
type: "TOPIC",
senderName: body.senderName,
question: body.question,
questionRangeKey: body.questionRangeKey,
senderToken: body.senderToken,
gender: body.gender,
schoolGrade: body.schoolGrade,
schoolName: body.schoolName,
},
};
const schoolId = body.schoolId;
const topic = "/topics/" + schoolId;
console.log(topic);
//? Make sure the message is sent
//TODO REMOVE BELOW AWAIT TO IMPROVE SPEED
// Send a message to devices subscribed to the provided topic.
var topicResult = await admin
.messaging()
.sendToTopic(topic, topicPayload);
console.log(topicResult);
}
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Sucessfully sent response",
input: event,
},
null,
2
),
};
} catch (e) {
console.log("Error", e);
return {
statusCode: 500,
body: JSON.stringify(
{
message: e,
input: event,
},
null,
2
),
};
}
};
Have figured out the issue for those who are stuck as well. Instead of using admin().message.sendTopic(). I have used the command below which is also same as sending a topic message, which worked.
//* if not missing school id
const schoolId = body.schoolId;
const topic = "/topics/" + schoolId;
const topicPayload = {
data: {
type: "TOPIC",
senderName: body.senderName,
},
topic: topic,
};
console.log(topic);
// Send a message to devices subscribed to the provided topic.
var topicResult = await admin.messaging().send(topicPayload);

Firebase functions take MINUTES to write in Firestore

I'm building a mobile app and on some actions (like userCreate), I trigger some Firebase functions to perform an API call to a third service, and then write something in the Firestore database.
Everything works in theory, but in practice, the API calls are quite fast (even with cold start scenarios), but the database writes can take several MINUTES to complete (if they do at all, I suspect that sometimes it takes too long and times out).
Since the API call work just fine, and so does the DB write on some occasion, I suspect that this is simply due to very poor async management from me since I know about nothing in JS.
Here are two of many example functions which are concerned by this issue, just to showcase that it happens whereas I'm triggering functions onCreate or on HTTPS.
onCreate function
const functions = require("firebase-functions");
const axios = require('axios')
// The Firebase Admin SDK to access the Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// Third party service credentials generation during onCreate request
exports.
buildCredentials = functions.auth.user().onCreate((user) => {
// Request to Third party to generate an Client Access Token
axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/oauth/token",
data: "client_id=xxx&client_secret=yyy&grant_type=client_credentials&scope=all:all",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then(function (response) {
//handle success
console.log('new user created: ')
console.log(user.id);
console.log(response.data);
// We write a new document in the users collection with the user ID and the Client Access Token
const db = admin.firestore();
const newUser = {
uid: user.uid,
clientAccessToken: response.data.access_token
};
db.collection('users').doc(user.uid).set(newUser)
.catch(function (error) {
//handle error
console.log(error);
});
})
.catch(function (response) {
//handle error
console.log(response);
});
})
HTTPS onCall function
exports.paymentRequest = functions.https.onCall(async (data, context) => {
const clientAccessToken = data.clientAccessToken;
const recipientIban = data.recipientIban;
const recipientName = data.recipientName;
const paymentDescription = data.paymentDescription;
const paymentReference = data.paymentReference;
const productPrice = parseInt(data.productPrice);
const uid = data.uid;
const jsonData = {
"destinations": [
{
"accountNumber": recipientIban,
"type": "iban"
}
],
"amount": productPrice,
"currency": "EUR",
"market": "FR",
"recipientName": recipientName,
"sourceMessage": paymentDescription,
"remittanceInformation": {
"type": "UNSTRUCTURED",
"value": paymentReference
},
"paymentScheme": "SEPA_INSTANT_CREDIT_TRANSFER"
};
(async function(){
response = await axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/pay",
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${clientAccessToken}`,},
data: jsonData,
})
// We write a the payment request ID in the user's document
const db = admin.firestore();
const paymentRequestID = response.data.id;
db.collection('users').doc(uid).set({
paymentRequestID: paymentRequestID
}, { merge: true })
.catch(function (error) {
//handle error
console.log(error);
});
console.log(response.data)
return response.data
})()
})
Am I on the right track thinking that this is an async problem?
Or is it a Firebase/Firestore issue?
Thanks
You are not returning the promises returned by the asynchronous methods (axios() and set()), potentially generating some "erratic" behavior of the Cloud Function.
As you will see in the three videos about "JavaScript Promises" from the official Firebase video series you MUST return a Promise or a value in a background triggered Cloud Function, to indicate to the platform that it has completed, and to avoid it is terminated before the asynchronous operations are done or it continues running after the work has been completed.
The following adaptations should do the trick (untested):
onCreate Function:
buildCredentials = functions.auth.user().onCreate((user) => {
// Request to Third party to generate an Client Access Token
return axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/oauth/token",
data: "client_id=xxx&client_secret=yyy&grant_type=client_credentials&scope=all:all",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then(function (response) {
//handle success
console.log('new user created: ')
console.log(user.id);
console.log(response.data);
// We write a new document in the users collection with the user ID and the Client Access Token
const db = admin.firestore();
const newUser = {
uid: user.uid,
clientAccessToken: response.data.access_token
};
return db.collection('users').doc(user.uid).set(newUser)
})
.catch(function (response) {
//handle error
console.log(response);
return null;
});
})
Callable Function:
exports.paymentRequest = functions.https.onCall(async (data, context) => {
try {
const clientAccessToken = data.clientAccessToken;
const recipientIban = data.recipientIban;
const recipientName = data.recipientName;
const paymentDescription = data.paymentDescription;
const paymentReference = data.paymentReference;
const productPrice = parseInt(data.productPrice);
const uid = data.uid;
const jsonData = {
// ...
};
const response = await axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/pay",
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${clientAccessToken}`,
},
data: jsonData,
})
// We write a the payment request ID in the user's document
const db = admin.firestore();
const paymentRequestID = response.data.id;
await db.collection('users').doc(uid).set({
paymentRequestID: paymentRequestID
}, { merge: true })
console.log(response.data)
return response.data
} catch (error) {
// See https://firebase.google.com/docs/functions/callable#handle_errors
}
})

Convert AJAX request into HTTP request in nodejs

Actually i am using ajax call to connect to a 3rd party api when i am using the api on browser and i am able to get the data from the api.
Here is the AJAX code:-
var settings = {
async: true,
//crossDomain: true,
url: "https://rest.cricketapi.com/rest/v2/schedule/?access_token=XXX"
//"url": "https://rest.cricketapi.com/rest/v2/match/indaus_2020_one-day_02/?access_token=XXX"
//bblt20_2019_g28
};
//create token for the api for 24hours
let token;
function getToken() {
$.ajax({
type: "POST",
url: "https://rest.cricketapi.com/rest/v2/auth/",
data: {
access_key: "********************************",
secret_key: "********************************",
app_id: "http://localhost:8000/",
device_id: "developer"
},
success: function(data) {
console.log(data);
token = data.auth.access_token;
console.log(token,);
//do something when request is successfull
createUpcomingMatchesSchedule();
},
dataType: "json"
});
}
function createUpcomingMatchesSchedule() {
var urlJson = settings.url;
urlJson = urlJson.replace(/XXX/g, token);
settings.url = urlJson;
$.ajax(settings).done(function(response) {
console.log(response);
toGetUpcomingMatches(response);
});
}
Now i want to convert this ajax method into http request so that i can use the data on the server using nodejs.
Here is the code:-
const { db } = require("../../utility/admin");
var request = require("request");
// //*************************** API INITIALIZATION ********************************
var settings = {
async: true,
//crossDomain: true,
url: "https://rest.cricketapi.com/rest/v2/schedule/?access_token=XXX"
//"url": "https://rest.cricketapi.com/rest/v2/match/indaus_2020_one-day_02/?access_token=XXX"
//bblt20_2019_g28
};
var options = {
method: "POST",
// hostname: "https://rest.cricketapi.com",
uri: "https://rest.cricketapi.com/rest/v2/auth/",
access_key: "********************************",
secret_key: "********************************",
app_id: "http://localhost:8000/",
device_id: "developer"
};
let token;
function getToken() {
request(options, function(error, response, body) {
if (error) {
console.log(error);
} else {
console.log(body);
token = body.auth.access_token;
console.log(token);
// console.log(response);
}
});
}
module.exports = { getToken };
But i am not able to access the data from the api when i am using the nodejs code.
Thanks in advance
👨‍🏫 For an example, you can look at this code below: 👇
var options = {
method: "POST",
// hostname: "https://rest.cricketapi.com",
uri: "https://rest.cricketapi.com/rest/v2/auth/",
// make sure, on this API, use body or headers.
// If body, you can change this `headers` to `form`.
headers: {
access_key: "********************************",
secret_key: "********************************",
app_id: "http://localhost:8000/",
device_id: "developer"
}
};
let token;
function getToken() {
return new Promise((resolve, reject) => {
request(options, function(error, response, body) {
if (error) return reject(error)
body = JSON.parse(body);
console.log(body);
token = body.auth.access_token;
return resolve(token);
});
})
}
I hope it can help you 🙏.

Got error when trying to get access token in nodejs using azure, AADSTS50058: A silent sign-in request was sent but no user is signed in

I am trying to implement azure login in nodejs scheduler app, and then want to upload file to share point.
First i need to login, then get access token,refresh token, admin access token etc.
When i try to get access token , i got error like this.
Here no use of any front end.
URL= 'https://login.microsoftonline.com/' + TENANT_ID + '/oauth2/token',
Status Code Error: 400 -
"{"error":"invalid_grant","error_description":"AADSTS50058: A silent sign-in request was sent but no user is signed in.\r\nTrace ID: 05db5c6a-155c-4870-9bca-a518b5931900\r\nCorrelation ID: 1e8372d0-c1ba-4070-88d7-597e9cb5cb2c\r\nTimestamp: 2019-08-14 12:04:42Z","error_codes":[50058],"timestamp":"2019-08-14 12:04:42Z","trace_id":"05db5c6a-155c-4870-9bca-a518b5931900","correlation_id":"1e8372d0-c1ba-4070-88d7-597e9cb5cb2c","error_uri":"https://login.microsoftonline.com/error?code=50058\"}"
Here the code
async function init(parsedBody) {
var jwtToken = await sharepointAuth.getJWTToken(parsedBody);
console.log("jwtToken:",jwtToken)
const config = {
JWK_URI: appConstants.JWK_URI,
ISS: appConstants.ISS,
AUD: appConstants.conf.AUD,
};
console.log(config)
await azureJWT.verify(jwtToken, config).then(async () => {
console.log("----------------------------------")
var fileName = 'analytics.min.js';
var filePath = './public/analytics.min.js';
var userAccessToken = await getAccessToken(jwtToken);
console.log("userAccessToken:", userAccessToken);
var accessTokenObj = await sharepointAuth.getAdminAccessToken();
accessToken = accessTokenObj.access_token;
console.log("accessToken:", accessToken)
fs.readFile(filePath, { encoding: null }, function (err, data) {
const relativeUrl = web/GetFolderByServerRelativeUrl('${selectedFolderName}');
const SHAREPOINT_HEADER = {
'Authorization': Bearer ${accessToken},
"Content-Type": application/json;odata=verbose,
'Accept': 'application/json;odata=verbose',
}
const options = {
method: "POST",
uri: ${SHAREPOINT_URI}${relativeUrl}/Files/add(url='${fileName}',overwrite=true),
headers: SHAREPOINT_HEADER,
body: data
};
console.log(options)
rp(options)
.then(() => {
// POST succeeded...
console.log('File uploaded!');
})
.catch((error) => {
// POST failed...
console.log("File Upload Error: ", error.toString());
});
});
});
}
const request = require("request");
const endpoint = "https://login.microsoftonline.com/tenentId/oauth2/token";
const requestParams = {
grant_type: "client_credentials",
client_id: "ClientId",
client_secret: "Secret",
resource: "ClientId"
};
request.post({ url: endpoint, form: requestParams }, function (err, response, body) {
if (err) {
console.log("error");
}
else {
console.log("Body=" + body);
let parsedBody = JSON.parse(body);
if (parsedBody.error_description) {
console.log("Error=" + parsedBody.error_description);
}
else {
console.log("parsedBody : " + parsedBody);
console.log("Access Token=" + parsedBody.access_token);
init(parsedBody);
}
}
});
function getAccessToken(jwtToken) {
return new Promise(async (resolve) => {
try {
const options = {
method: 'POST',
uri: URL,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
formData: {
grant_type: appConstants.OTB_GRANT_TYPE,
client_id: appConstants.conf.AUD,
client_secret: appConstants.conf.CLIENT_SECRET,
resource: appConstants.OTB_RESOURCE_URI2,
client_assertion_type: appConstants.OTB_CLIENT_ASSERTION_TYPE,
requested_token_use: appConstants.OTB_REQ_TOKEN_USE,
scope: appConstants.OTB_SCOPE,
assertion: jwtToken,
},
};
console.log("options:", options)
await rp(options)
.then(async (parsedBody) => {
// POST succeeded...
const result = JSON.parse(parsedBody);
console.log("****************************************** result", result)
refreshToken = result.refresh_token;
resolve(result.access_token);
})
.catch((error) => {
// POST failed...
console.log('getAccessTokenRequestError: ', error.toString());
resolve(appConstants.ACCESS_TOKEN_ERROR);
});
} catch (error) {
console.log('getAccessTokenRequestPromiseError: ', error.toString());
resolve(appConstants.MIDDLEWARE_ERROR);
}
});
}
I have no idea about azure login without front end. I want to login in azure and upload file to share point in scheduler app in node.
First i need to login by using client id and secret. then i got bearer token. then i want to get access token by using bearer token. At that time i get error like this.
AADSTS50058: A silent sign-in request was sent but no user is signed in
Why don't you get the access token this way(client credentials flow)?
const request = require("request");
const endpoint =
"https://login.microsoftonline.com/{tenant}/oauth2/token";
const requestParams = {
grant_type: "client_credentials",
client_id: "",
client_secret: "",
resource: "https://mydomain.sharepoint.com"
};
request.post({ url: endpoint, form: requestParams }, function(
err,
response,
body
) {
if (err) {
console.log("error");
} else {
console.log("Body=" + body);
let parsedBody = JSON.parse(body);
if (parsedBody.error_description) {
console.log("Error=" + parsedBody.error_description);
} else {
console.log("Access Token=" + parsedBody.access_token);
}
}
});
If you need the access token which contains login user message, you can use ROPC flow.
const request = require("request");
const endpoint =
"https://login.microsoftonline.com/{tenant}/oauth2/token";
const requestParams = {
grant_type: "password",
username: "",
password: "",
client_id: "",
resource: "https://mydomain.sharepoint.com"
};
request.post({ url: endpoint, form: requestParams }, function(
err,
response,
body
) {
if (err) {
console.log("error");
} else {
console.log("Body=" + body);
let parsedBody = JSON.parse(body);
if (parsedBody.error_description) {
console.log("Error=" + parsedBody.error_description);
} else {
console.log("Access Token=" + parsedBody.access_token);
}
}
});

Firebase Cloud Messaging with Node.js server

I'm trying to send push notifications with FCM between individual devices using a node.js server and Swift 2.2 (based on Frank van Puffelen's Sending notifications between Android devices with Firebase Database and Cloud Messaging).
The notification request is successfully being handled by both Firebase Database and the Node.js server (adding request to database, fetching data from database, sending notification to topic) but I'm not getting any alert on my device.
When I launch the app, func application(application: UIApplication, didReceiveRemoteNotification) gets called and I'm able to print the notification but, compared to sending a notification through Firebase's interface, unfortunately no alert.
userInfo from Node.js notification (No Alert):
[aps: {
alert = {
title = "This should be the text";
};
}, gcm.message_id: 0:1475766502047698%d4d04c12d4d04c12]
userInfo from sending a notification through Firebase's interface (Alert works):
[gcm.notification.sound2: default, google.c.a.e: 1, aps: {
alert = This should be the text;
sound = default;
}, gcm.n.e: 1, google.c.a.c_id: ##Some Id###, google.c.a.udt: 0, gcm.message_id: 0:1475766412557820%d4d04c12d4d04c12, google.c.a.ts: ##Some Id 2##]
index.js:
var express = require('express');
var firebase = require("firebase");
var request = require('request');
var app = express();
var path = require("path");
var API_KEY = "Some Key"; // Your Firebase Cloud Server API key
app.set('port', (process.env.PORT || 5000));
app.use(express.static(__dirname + '/public'));
// views is directory for all template files
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.get('/', function(request, response) {
response.render('pages/index')
});
app.listen(app.get('port'), function() {
console.log('Node app is running on port', app.get('port'));
});
firebase.initializeApp({
serviceAccount: path.resolve(__dirname, './credentials/someCredentials.json'),
databaseURL: "https://someURL.firebaseio.com"
});
ref = firebase.database().ref();
function listenForNotificationRequests() {
var requests = ref.child('notificationRequests');
requests.on('child_added', function(requestSnapshot) {
var request = requestSnapshot.val();
sendNotificationToUser(
request.username,
request.message,
function() {
requestSnapshot.ref.remove();
}
);
}, function(error) {
console.error(error);
});
};
function sendNotificationToUser(username, message, onSuccess) {
request({
url: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: {
'Content-Type' :' application/json',
'Authorization': 'key='+API_KEY
},
body: JSON.stringify({
notification: {
title: message
},
to : '/topics/user_'+username
})
}, function(error, response, body) {
if (error) { console.error(error); }
else if (response.statusCode >= 400) {
console.error('HTTP Error: '+response.statusCode+' - '+response.statusMessage);
}
else {
onSuccess();
}
});
}
// start listening
listenForNotificationRequests();
I would really appreciate it if someone could help me out with this :)
Found the answer: I had to change the sendNotificationToUser function by replacing title to body in my notification object and set the priority to high.
function sendNotificationToUser(username, message, onSuccess) {
request({
url: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: {
'Content-Type' :' application/json',
'Authorization': 'key='+API_KEY
},
body: JSON.stringify({
notification: {
body: message, // Send your message in 'body'
sound: 'default'
},
to : '/topics/user_'+username,
priority: 'high' // Set the priority to high
})
}, function(error, response, body) {
if (error) { console.error(error); }
else if (response.statusCode >= 400) {
console.error('HTTP Error: '+response.statusCode+' - '+response.statusMessage);
}
else {
onSuccess();
}
});
}

Resources