Serverless notifications with Cloud Functions for Firebase - node.js

I have used firebase chat notification cloud function but when
notification triggered I am getting this error in firebase function
console
Cannot read property 'current' of undefined
at exports.sendNotification.functions.database.ref.onWrite.event (/user_code/index.js:8:35)
Here is my function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/notifications/messages/{pushId}').onWrite(event => {
console.log('Push notification event triggered for testing');
console.log(event);
const message = event.data.current.val();
const senderUid = message.from;
const receiverUid = message.to;
console.log(receiverUid);
const promises = [];
if (senderUid === receiverUid) {
//if sender is receiver, don't send notification
promises.push(event.data.current.ref.remove());
return Promise.all(promises);
}
const getInstanceIdPromise = admin.database().ref(`/usersnew/${receiverUid}`).once('value');
const getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results => {
const instanceId = results[0].val();
const sender = results[1];
console.log('notifying ' + receiverUid + ' about ' + message.body + ' from ' + senderUid);
const payload = {
notification: {
title: sender.displayName,
body: message.body,
icon: sender.photoURL
}
};
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
return console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
return console.log('This is the notify feature');
});
});
Does anyone know how to solve this?
When I log event it shows like below in console
{ before:
DataSnapshot {
app:
FirebaseApp {
firebaseInternals_: [Object],
services_: {},
isDeleted_: false,
name_: '__admin__',
options_: [Object],
INTERNAL: [Object] },
instance: 'https://teleport-24f52.firebaseio.com',
_path: '/notifications/messages/-LIlFNd2spo_V1rM-G-f',
_data: null },
after:
DataSnapshot {
app:
FirebaseApp {
firebaseInternals_: [Object],
services_: {},
isDeleted_: false,
name_: '__admin__',
options_: [Object],
INTERNAL: [Object] },
instance: 'https://teleport-24f52.firebaseio.com',
_path: '/notifications/messages/-LIlFNd2spo_V1rM-G-f',
_data:
{ body: 'abc',
dayTimestamp: 1532975400000,
from: 'q8gtwtwXqbV2DtpsrbYajFsWzSr2',
negatedTimestamp: -1533056068309,
timestamp: 1533056068309,
to: 'Cmpu7mbIENTYyoHZjCjZnbnBMbl2' } } }
10:22:44.625 PM

In your code, event.data.current should be event.after.val() , event.after.ref, etc...
There was a change of API in cloud functions 1.0.
Read:
https://firebase.google.com/docs/functions/database-events#reading_the_previous_value
https://firebase.google.com/docs/functions/beta-v1-diff

Maybe the problem is in the SDK version. From v1.0 things change a bit. Try updating SDK and follow these instructions for miragation:
https://firebase.google.com/docs/functions/beta-v1-diff

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);

DiscordJS v14 - Command Handler

I'm trying to create a command handle function, and this is my code, however it gives me an error in the console.
Sometimes when I go and reload the bot, the error doesn't populate, but it won't show the slash command. Other times, I get the error listed below.
const chalk = require("chalk");
const fs = require("fs");
const { REST } = require('#discordjs/rest');
const { Routes } = require('discord-api-types/v9');
module.exports = (client) => {
client.handleCommands = async () => {
const commandFolders = fs.readdirSync("./src/commands");
for (const folder of commandFolders) {
const commandFiles = fs
.readdirSync(`./src/commands/${folder}`)
.filter((file) => file.endsWith(".js"));
const { commands, commandArray } = client;
for (const file of commandFiles) {
const command = require(`../../commands/${folder}/${file}`);
commands.set(command.data.name, command);
commandArray.push(command, command.data.toJSON());
console.log(
chalk.white("Command: ") +
chalk.cyan.bold`${command.data.name} ` +
chalk.white("has successfully loaded.")
);
}
}
const clientId = "(Client_ID)";
const rest = new REST({ version: "10" }).setToken(
process.env.DISCORD_DEV_TOKEN
);
try {
console.log("Started refreshing application (/) commands.");
await rest.put(Routes.applicationCommands(clientId), {
body: client.commandArray,
});
console.log("Successfully reloaded application (/) commands.");
} catch (error) {
console.error(error);
}
};
};
Here's the error.
DiscordAPIError[50035]: Invalid Form Body
0.name[BASE_TYPE_REQUIRED]: This field is required
at SequentialHandler.runRequest (/home/**/Discord Bot/node_modules/#discordjs/rest/dist/index.js:659:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async SequentialHandler.queueRequest (/home/**/Discord Bot/node_modules/#discordjs/rest/dist/index.js:458:14)
at async REST.request (/home/**/Discord Bot/node_modules/#discordjs/rest/dist/index.js:902:22)
at async Client.client.handleCommands (/home/**/Discord Bot/src/functions/handlers/handleCommands.js:35:7) {
requestBody: { files: undefined, json: [ [Object], [Object] ] },
rawError: {
code: 50035,
errors: { '0': [Object] },
message: 'Invalid Form Body'
},
code: 50035,
status: 400,
method: 'PUT',
url: 'https://discord.com/api/v10/applications/(Client_ID)/commands'
}
I've attempted to debug it and follow the documentation from the Discord.JS website, but that doesn't even resolve the issue.
I currently have just the typical ping command, and here's the code for that.
const { SlashCommandBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName("ping")
.setDescription("Return's the bot's ping!"),
async execute(interaction, client) {
const message = await interaction.deferReply({
fetchReply: true,
});
const newMessage = `API Latency: ${client.ws.ping}\n Client Ping: ${
message.createdTimestamp - interaction.createdTimestamp
}`;
await interaction.editReply({
content: newMessage,
});
},
};
Is anyone able to share where I'm making the mistake?
Turns out my issue was within commandArray.push.
I had it written out as
commandArray.push(command, command.data.toJSON());
When it needed to be
commandArray.push(command.data.toJSON());

Keep getting the 'cannot set headers' with Axios on Firebase Functions which still fully executes regardless of the error

I have a Firebase function that executes on a Stripe webhooks via express. The function executes fine but the sending of the email (using Axios) keeps resulting in an error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
It does work fine on localhost but the error appears when pushed to Firebase staging server. Weirdly the whole function executes fully including the Axios call where I'm getting the header issue (sending the email). It does take about 2-3 minutes to fully execute due to the error.
I've tried a number of different methods using return, and then() promises but it's still flagging this error. My code is as follows:
index.js
// Controllers
const stripeWebhookSubscription = require("./src/controllers/stripe/webhooks/subscription");
// Firebase
const admin = require("firebase-admin");
const functions = require("firebase-functions");
// Express
const express = require("express");
const cors = require("cors");
// Stripe
const stripe = require("stripe")(functions.config().stripe.key_secret);
const serviceAccount = require(functions.config().project.service_account);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: functions.config().project.database_url,
storageBucket: functions.config().project.storage_bucket,
});
const database = admin.firestore();
// -------------------------
// Stripe
// -------------------------
const stripeFunction = express();
stripeFunction.use(cors({origin: true}));
stripeFunction.post("/webhooks", express.raw({type: "application/json"}), (req, res) => {
const sig = req.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody,
sig,
functions.config().stripe.webhook_secret
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case "customer.subscription.created":
stripeWebhookSubscription.createSubscription(database, event.data.object, res);
break;
(...)
default:
console.log(`Unhandled event type ${event.type}`);
break;
}
res.json({received: true});
});
exports.stripe = functions.https.onRequest(stripeFunction);
subscription.js
const functions = require("firebase-functions");
const stripe = require("stripe")(functions.config().stripe.key_secret);
const axios = require("axios");
class stripeWebhookSubscription {
static createSubscription(database, subscription, res) {
let barcode;
let plan;
let merchant;
let customer;
database.collection("subscriptions").add({
id: subscription.id,
customer: subscription.customer,
status: subscription.status,
price: {
amount: (subscription.items.data[0].price.unit_amount / 100).toFixed(2),
interval: subscription.items.data[0].price.recurring.interval,
interval_count: subscription.items.data[0].price.recurring.interval_count,
},
product: subscription.items.data[0].price.product,
created: subscription.created,
current_period_start: subscription.current_period_start,
current_period_end: subscription.current_period_end,
cancel_at: subscription.cancel_at,
cancel_at_period_end: subscription.cancel_at_period_end,
payment_gateway: "stripe",
current_usage: 1,
})
.then((doc) => {
barcode = doc.id;
return database.collection("plans").where("stripe.product", "==", subscription.items.data[0].price.product).limit(1).get();
})
.then((docs) => {
docs.forEach((doc) => {
return plan = doc.data();
});
})
.then(() => {
return database.collection("subscriptions").doc(barcode).set({
merchant: plan.merchant,
}, {merge: true});
})
.then(() => {
return database.collection("merchants").doc(plan.merchant).get();
})
.then((doc) => {
return merchant = doc.data();
})
.then(() => {
async function stripeCustomer() {
const stripeData = await stripe.customers.retrieve(subscription.customer);
customer = stripeData;
}
return stripeCustomer().then(() => {
return customer;
});
})
.then(() => {
return database.collection("customers").doc(subscription.customer).set({
name: customer.name,
email: customer.email,
phone: customer.phone,
delinquent: customer.delinquent,
created: customer.created,
livemode: customer.livemode,
merchant: plan.merchant,
subscriptions: [barcode],
}, {merge: true});
})
.then((doc) => {
return axios.request({
url: "https://api.sendinblue.com/v3/smtp/email",
method: "post",
headers: {
"api-key": functions.config().sendinblue.key,
"Content-Type": "application/json",
},
data: {
"to": [
{
"email": customer.email,
"name": customer.name,
},
],
"replyTo": {
"email": "support#scanable.com.au",
"name": "Scanable",
},
"templateId": 2,
"params": {
"plan_name": plan.name,
"interval_count": plan.interval_count,
"interval": plan.interval,
"subscription": barcode,
"merchant_name": merchant.name,
"merchant_email": merchant.email,
},
},
})
.then((response) => {
return console.log("Membership email sent to " + customer.email);
});
})
.then(() => {
res.status(200).send("✅ Subscription " + subscription.id + " created!");
})
.catch((err) => {
res.status(400).send("⚠️ Error creating subscription (" + subscription.id + "): " + err);
});
}
}
module.exports = stripeWebhookSubscription;
In index.js, you call this line:
stripeWebhookSubscription.createSubscription(database, event.data.object, res);
immediately followed by this line:
res.json({received: true});
By the time the createSubscription path has finished, the response has already been sent. When deployed to Cloud Functions, this will also terminate your function before it's done any of its workload (Note: this termination behaviour is not simulated by the local functions emulator).
Depending on what you are trying to achieve, you can probably just add the missing return to this line so that the res.json({received: true}) never gets called:
return stripeWebhookSubscription.createSubscription(database, event.data.object, res);
Additionally, on this line in subscription.js:
database.collection("subscriptions").add({
you need to add the missing return statement so the asynchronous tasks are properly chained:
return database.collection("subscriptions").add({

webpush.sendNotification - NotificationEvent.notification.data null

Setup
Operating System: Linux
Node Version: 10.15.3
web-push Version: 3.3.4
[X] Chrome
[ ] Firefox
[ ] Opera for Android
[ ] Samsung Internet Browser
[ ] Other
Chrome: Version 74.0.3729.131 (Official Build) (64-bit)
Problem
I am trying to implement example step 5.2 from google pwa lab
If I push a notification from my node/main.js script I am getting the visual notification on my screen. If I click the notification I can observe from the console the following message:
Uncaught TypeError: Cannot read property 'primaryKey' of null
at sw.js:30
If I check the content of the NotificationEvent.notification.data object it is null.
Expected
If I click the notification sent from my node server I should be able to see the data from the NotificationEvent.notification object.
Features Used
[x] VAPID Support
[ ] GCM API Key
[x] Sending with Payload
Example / Reproduce Case
This is my node/main.js that is in charge of the web push notifications.
const webPush = require('web-push');
const pushSubscription = {
endpoint: 'https://fcm.googleapis.com/fcm/send/flcWNQgYeVw:APA91bHgAC5M-WeyN5RNdSprIDUalAxSMznny3-QwPf2T8whtWwmtZ6Q546Pry-JCZqjSh9bbEIS778lJD7Hq-t4KLS35X6-T7dJGCnc_ECzKvsdOAQ50wlzQ5Tcm1WNvgOOwXV',
expirationTime: null,
keys: {
p256dh: 'BL-Rz9Nejsd7ipsYQsQ9YB6tUc-qldkBg28y7G9LBFGdKe52sSeF0FStiKmdBp5BRFf3HazlOICBAVnY',
auth: 'hOPxUEqXsdfFjB-MmFxaTw'
}
};
// TODO 4.3a - include VAPID keys
const vapidPublicKey = 'MY VAPID PUBLIC KEY';
const vapidPrivateKey = 'MY VAPID PRIVATE KEY';
const payload = 'Here is a payload!';
const options = {
//gcmAPIKey: 'NOT IN USE',
TTL: 60,
vapidDetails: {
subject: 'mailto: myemail#gmail.com',
publicKey: vapidPublicKey,
privateKey: vapidPrivateKey
},
contentEncoding: 'aes128gcm'
};
webPush.sendNotification(
pushSubscription,
payload,
options
).catch( error => {
console.log(error);
}) ;
This is the code from my service worker that handles the notifications:
self.addEventListener('notificationclose', event => {
console.log(event);
const notification = event.notification;
const primaryKey = notification.data.primaryKey;
console.log('Closed notification: ' + primaryKey);
});
self.addEventListener('notificationclick', event => {
const notification = event.notification;
console.log(notification.data);
const primaryKey = notification.data.primaryKey;
const action = event.action;
if (action === 'close') {
notification.close();
} else {
event.waitUntil(
clients.matchAll().then(clis => {
const client = clis.find(c => {
return c.visibilityState === 'visible';
});
if (client !== undefined) {
client.navigate('samples/page' + primaryKey + '.html');
client.focus();
} else {
// there are no visible windows. Open one.
clients.openWindow('samples/page' + primaryKey + '.html');
notification.close();
}
})
);
}
self.registration.getNotifications().then(notifications => {
notifications.forEach(notification => {
notification.close();
});
});
});
self.addEventListener('push', event => {
let body;
if (event.data) {
body = event.data.text();
} else {
body = 'Default body';
}
const options = {
body: body,
icon: 'images/notification-flat.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{action: 'explore', title: 'Go to the site',
icon: 'images/checkmark.png'},
{action: 'close', title: 'Close the notification',
icon: 'images/xmark.png'},
]
};
event.waitUntil(
clients.matchAll().then(c => {
console.log(c);
if (c.length === 0) {
// Show notification
self.registration.showNotification('Push Notification', options);
} else {
// Send a message to the page to update the UI
console.log('Application is already open!');
}
})
);
});
Other
I have tried changing the contentEncoding from aesgmc to aes128gcm but I am still getting the same error.
If I run the web-push command I see the same error

Push notification returns ECONNRESET in Google Cloud Functions

I am having a function in Firebase Cloud Functions that is retrieves the user's device group id in order to send a push notification, and after sends a push notification. This works well if the function gets called only once, but if I have an array of users I want to send a push notification too, the sendPushNotification function returns error : FAILED err= { RequestError: Error: read ECONNRESET
at new RequestError (/user_code/node_modules/request-promise/node_modules/request-promise-core/lib/errors.js:14:15) for every try to send push
From what i understand ECONNRESET means that the connection gets closed at one end before finishing the operation, can some help/explain me why this is:
here is my code:
function sendFollowNotification(snapshot) {
const notificationMsg = getFollowNotificationMsg() //returns a string
snapshot.forEach(function(singleUser, index) {
const userId = singleUser.key;
const userObject = singleUser.val();
console.log("will get device group")
if (index + 1 == snapshot.numChildren()) {
return getDeviceGroupNotificationKey(userId, "Discover new artists", notificationMsg, "", true);
} else {
getDeviceGroupNotificationKey(userId, "Discover new artists", notificationMsg, "", false);
}
}
function getDeviceGroupNotificationKey(groupId, notificationTitle, notificationBody, notificationSubject, shouldReturn) {
const pathToDeviceGroup = admin.database().ref('deviceGroups').child(groupId);
pathToDeviceGroup.once("value").then( function(snapshot) {
const deviceGroupObj = snapshot.val();
const notification_key = deviceGroupObj.notification_key;
console.log("got notification key")
console.log(notification_key)
if (notification_key !== undefined) {
return sendPushToDeviceGroupOld(notification_key, notificationTitle, notificationBody, "notificationKeyOld2", notificationSubject, shouldReturn);
} else {
return
}
}).catch(reason => {
console.log("user device group not there")
return
})
}
function sendPushToDeviceGroupOld(notification_key, title, body, subject, message, shouldReturn) {
console.log('sending push to ' + notification_key)
const serverKey = '-';
const senderId = '-';
const options = {
method: 'POST',
uri: 'https://fcm.googleapis.com/fcm/send',
headers: {
'Authorization': 'key=' + serverKey,
'project_id': senderId
},
body: {
to: notification_key,
data: {
subject: message
},
notification: {
title: title,
body: body,
badge: 1,
sound: "default",
},
priority : 'high',
content_available: true
},
json: true
};
return rqstProm(options)
.then((parsedBody) => {
console.log('SUCCESS response=', parsedBody);
return
})
.catch((err) => {
console.log('FAILED', err);
return
});
}

Resources