Send web push notifications to specific users conditionally - node.js

I am willing to use web-push notifications on my web app. I have already setup serviceWorkers on the front-end(React) and implemented web-push notifications on my backend(NodeJS). Now I just need to send notifications which are user specific, means only specific users should receive those notifications.
For e.g. In my web app's backend I will be receiving some live values. Say, there is a collection named
"users" where all the user's data will be stored. Now these users will have a field named "device" where the user will receive numeric values which will be updated within 40-50 seconds.
Now, their will be a threshold for these values. Say, for e.g. if the value reaches above 200 then that specific user should receive a push notification, letting them know that the device has reached it's limit.
How is it possible for me to create such user specific push notifications where the notification will be sent to only that user who's device value has reached above 200 ?. P.S I am using Mongoose for the database.
FrontEnd code(react.js)
sw.js:
self.addEventListener("notificationclick", function (event) {
// on Notification click
let notification = event.notification;
let action = event.action;
console.log("Notification====>", notification);
if (action === "confirm") {
console.log("Confirm clicked");
notification.close(); // Closes the notifcation
} else {
event.waitUntil(
clients.matchAll().then(function (clis) {
var client = clis.find(function (c) {
return c.visibilityState === "visible";
});
if (client !== undefined) {
// found open window
client.navigate("http://localhost:3000"); // means website opens on the same tab where user is on
client.focus();
} else {
// if client's window was not open
clients.openWindow("http://localhost:3000"); // when browser window is closed, open website
}
notification.close();
})
);
console.log(action); // name of action, basically id
}
});
self.addEventListener("notificationclose", function (event) {
console.log("Notification closed", event);
});
// triggers when we get an incoming push message
self.addEventListener("push", function (event) {
console.log("Push notifications recieved from eventListner", event);
var data = { title: "New!", content: "New things" };
if (event.data) {
// check if payload exists(from backend)
data = JSON.parse(event.data.text()); // recieve payload & store
}
var options = {
body: data.content,
icon: "https://iconarchive.com/download/i90141/icons8/windows-8/Cinema-Avengers.ico",
tag: "id1",
renotify: true,
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
swReg.js:
if ("serviceWorker" in navigator) {
console.log("Registering service worker");
navigator.serviceWorker
.register("/sw.js")
.then(() => {
console.log("Service Worker has been registered");
})
.catch((err) => console.error(err));
}
function urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function displayConfirmNotification() {
if ("serviceWorker" in navigator) {
const options = {
body: "After subscription managing done",
// icon: "/src/assets/img/pattern_react.png",
// tag:"" ==> in advanced options.
vibrate: [100, 50, 200],
// badge:""
tag: "confirm",
renotify: true,
actions: [
{ action: "confirm", title: "okay" }, // optnl icon:""
{ action: "cancel", title: "cancel" },
],
};
navigator.serviceWorker.ready.then(function (swreg) {
swreg.showNotification("Successfully subscribed sW", options);
});
}
}
function configPushSub() {
if (!("serviceWorker" in navigator)) {
return;
}
var reg;
navigator.serviceWorker.ready
.then(function (swreg) {
// access to sW registration
reg = swreg;
return swreg.pushManager.getSubscription(); // returns any existing subscription
})
.then(function (sub) {
// sub holds the current subscription, if subscription doesn't exist then it returns null
if (sub === null) {
// Create a new subscription
var vapidPublicKey = KEY;
var convertedPublicKey = urlBase64ToUint8Array(vapidPublicKey);
return reg.pushManager.subscribe({
userVisibleOnly: true, // for security
applicationServerKey: convertedPublicKey, // for security & server storage
}); // create new subscription
} else {
// We already have a subscription
}
})
.then(function (newSub) {
// have to pass this subscription(new one) to backend
console.log("New subb =======>", newSub);
return fetch("http://localhost:8000/subscribeToPushNotifications", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
subscriptionObj: newSub,
}),
});
})
.then(function (res) {
if (res.ok) {
displayConfirmNotification();
}
})
.catch(function (e) {
console.log("err while subbing====>", e);
});
}
function askForNotificationPermission() {
Notification.requestPermission(function (result) {
console.log("User's choice", result);
if (result !== "granted") {
console.log("Permission rights not granted");
} else {
configPushSub();
// displayConfirmNotification();
}
});
}
if ("Notification" in window) {
askForNotificationPermission();
}
Backend:
API to subscribe:
exports.subscribeToPushNotifications = async (req, res) => {
const { subscriptionObj } = req.body;
// console.log("Subscription object=====>", subscriptionObj);
if (subscriptionObj != undefined || subscriptionObj != null) {
let newSubscription = new Subscription({
pushSubscription: subscriptionObj,
});
await newSubscription.save();
if (newSubscription) {
console.log(newSubscription);
return res.status(200).send("Subscription made");
} else {
console.log("Not subbed");
return res.status(400).send("Subscription not made");
}
} else {
console.log("Sub obj is null");
return res.status(400).send("Sub obj was null");
}
};
Checking if values are more than the threshold and then sending notification:(For example purposes). This is an example for single user only.
exports.checkStatus = async () => {
schedule.scheduleJob("*/10 * * * * *", async () => {
let subscriptions = await Subscription.find({});
let Email = "james#mail.com";
let findUser = await User.findOne({ Email });
if (findUser) {
if (findUser.device > 200) // findUser.device contains the value
{
for (let i = 0; i < subscriptions.length; i++) { //Notification will be sent to all users which I don't want.
webpush.sendNotification(
subscriptions[i].pushSubscription,
JSON.stringify({
title: "Alert",
content: "Value has reached it's limit",
})
);
}
}
}
});
};
How can I make this work such that only those users who's device's value has gone above 200 will only receive the notification and not all the subscribed users.

Related

Gpay payment-gateway integration in NodeJS

I tried it in Html and Javascript it's working perfect in web (mobile's browser).I am using angular for frontend and node for backend but not getting any proper solution for redirect Playstore or Gpay for mobile browser. Basically I want to implement for mobile browser.
this is my node for backend code & for frontend I already paste static. So, request you that please check & get back to me proper resolution or tutorial. Thanks & Regards--
const canMakePaymentCache = 'canMakePaymentCache';
onBuyClicked();
function readSupportedInstruments() {
let formValue = {};
formValue['pa'] = keys.GPAY_MERCHANT_ID;//merchantId
formValue['pn'] = `Test_Gpay_Name`;//transactionId
formValue['tn'] = 'Testing Messages ';//message
formValue['mc'] = 'merchant Code';//
formValue['tr'] = 'Transaction Reference';
formValue['tid'] = 'Transaction id';
formValue['url'] = 'http://localhost.co/';
return formValue;
}
function readAmount() {
//const pay_amount = parseInt(req.body.amount) * 100;
return parseInt(req.body.amount) * 100;
}
function onBuyClicked() {
// if (!window.PaymentRequest) {
// console.log('Web payments are not supported in this browser.');
// return;
// }
let formValue = readSupportedInstruments();
const supportedInstruments = [
{
supportedMethods: ['https://pwp-server.appspot.com/pay-dev'],
data: formValue,
},
{
supportedMethods: ['https://tez.google.com/pay'],
data: formValue,
},
];
const details = {
total: {
label: 'Total',
amount: {
currency: 'INR',
value: readAmount(),
},
},
displayItems: [
{
label: 'Original amount',
amount: {
currency: 'INR',
value: readAmount(),
},
},
],
};
const options = {
requestShipping: false,
requestPayerName: false,
requestPayerPhone: false,
requestPayerEmail: false,
shippingType: 'shipping',
};
let request = null;
try {
//const PaymentRequest = {};
//request = PaymentRequest(supportedInstruments, details, options);
request ={supportedInstruments, details, options};
} catch (e) {
return;
}
if (!request) {
console.log('Web payments are not supported in this browser.');
return;
}
var canMakePaymentPromise = checkCanMakePayment(request);
canMakePaymentPromise
.then((result) => {
showPaymentUI(request, result);
})
.catch((err) => {
console.log('Error calling checkCanMakePayment: ' + err);
});
}
function checkCanMakePayment(request) {
//if (sessionStorage.hasOwnProperty(canMakePaymentCache)) {
//return Promise.resolve(JSON.parse(sessionStorage[canMakePaymentCache]));
//}
var canMakePaymentPromise = Promise.resolve(true);
if (request.canMakePayment) {
canMakePaymentPromise = request.canMakePayment();
}
return canMakePaymentPromise
.then((result) => {
canMakePaymentCache = result;
return result;
})
.catch((err) => {
console.log('Error calling canMakePayment: ' + err);
});
}
function showPaymentUI(request, canMakePayment) {
if (!canMakePayment) {
redirectToPlayStore();
return;
}
let paymentTimeout = window.setTimeout(function () {
window.clearTimeout(paymentTimeout);
request.abort()
.then(function () {
console.log('Payment timed out after 20 minutes.');
})
.catch(function () {
console.log('Unable to abort, user is in the process of paying.');
});
}, 20 * 60 * 1000); /* 20 minutes */
request.show()
.then(function (instrument) {
window.clearTimeout(paymentTimeout);
processResponse(instrument); // Handle response from browser.
})
.catch(function (err) {
console.log(err);
});
}
function processResponse(instrument) {
var instrumentString = instrumentToJsonString(instrument);
console.log(instrumentString);
fetch('/buy', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: instrumentString,
credentials: 'include',
})
.then(function (buyResult) {
if (buyResult.ok) {
return buyResult.json();
}
console.log('Error sending instrument to server.');
})
.then(function (buyResultJson) {
completePayment(
instrument, buyResultJson.status, buyResultJson.message);
})
.catch(function (err) {
console.log('Unable to process payment. ' + err);
});
}
function completePayment(instrument, result, msg) {
instrument.complete(result)
.then(function () {
console.log('Payment completes.');
console.log(msg);
document.getElementById('inputSection').style.display = 'none'
document.getElementById('outputSection').style.display = 'block'
document.getElementById('response').innerHTML =
JSON.stringify(instrument, undefined, 2);
})
.catch(function (err) {
console.log(err);
});
}
console.log(`in line 448....`);
function redirectToPlayStore() {
//if (confirm('Tez not installed, go to play store and install?')) {
//window.location.href = 'https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user.alpha'
//res.writeHead( "https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user.alpha" );
const response_data = axios
.post(
'https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user.alpha',
)
console.log(`in line no. 459... ${response_data}`);
return response_data;
//};
}
function instrumentToJsonString(instrument) {
var instrumentDictionary = {
methodName: instrument.methodName,
details: instrument.details,
shippingAddress: addressToJsonString(instrument.shippingAddress),
shippingOption: instrument.shippingOption,
payerName: instrument.payerName,
payerPhone: instrument.payerPhone,
payerEmail: instrument.payerEmail,
};
return JSON.stringify(instrumentDictionary, undefined, 2);
}

Flutter notifications from firebase messaging is delayed?

sometime the notification is delayed for no reason and then comes all together iam using
https://pub.dev/packages/flutter_local_notifications
https://pub.dev/packages/firebase_messaging
here is my code from cloud functions ( nodejs )
exports.messageTrigger = functions.firestore.document('/Messages/{messageID}').onCreate(
async (snapshot, context) => {
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
var currentRoomUsers = snapshot.data().members;
currentRoomUsers.forEach( (userID) => {
db.collection('Users').doc(userID).get().then(async(doc)=>{
if(doc.exists && doc.id != snapshot.data().senderID){
const message = {
data: {
title: `New message from ${capitalizeFirstLetter(snapshot.data().room)}`,
body: snapshot.data().type == 'm.text' ? 'Sent a new message' : snapshot.data().type == 'm.start'? 'Invited you for a call' : snapshot.data().type == 'm.image' ? 'Sent an image' : 'Sent a new message'
},
tokens: doc.data()['Device_token'],
android: {
priority: 'high',
},
priority: "high",
}
await admin.messaging().sendMulticast(message);
}else {
console.log("No such document!");
}
}).catch((error)=>{
console.log("Error getting document:", error);
});
}
);
}
);
Firestore documents are fetched asynchronously and the sendMulticast() statement here may run before all the documents are fetched. Also, you must terminate Cloud Functions by returning a Promise. Since you are using an async function, try refactoring the code with async-await syntax as shown below:
exports.messageTrigger = functions.firestore
.document("/Messages/{messageID}")
.onCreate(async (snapshot, context) => {
try {
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const currentRoomUsers = snapshot.data().members;
// use Promise.all() to fetch all documents
const customRoomUsersSnap = await Promise.all(
currentRoomUsers.map((user) =>
db.collection("Users").doc(user).get()
)
);
const messagePromises = [];
// Iterate over all documents and add a message for every existing document
customRoomUsersSnap.forEach((userSnap) => {
if (userSnap.exists) {
const message = {
data: {
title: `New message from ${capitalizeFirstLetter(
snapshot.data().room
)}`,
body:
snapshot.data().type == "m.text"
? "Sent a new message"
: snapshot.data().type == "m.start"
? "Invited you for a call"
: snapshot.data().type == "m.image"
? "Sent an image"
: "Sent a new message",
},
tokens: userSnap.data()["Device_token"],
android: {
priority: "high",
},
priority: "high",
};
messagePromises.push(admin.messaging().sendMulticast(message));
} else {
console.log(`User ${userSnap.ref.id} doc does not exist`);
}
});
// Send all messages simultaneously.
return Promise.all(messagePromises);
} catch (error) {
console.log(error);
// return Promise<null> in case of an error
return null;
}
});

Microsoft bot framework save separately conversations or sessions

I got a Microsoft bot framework chatbot deployed on Azure and I´m using Tedious to save my conversations, thing is, bot it's being used on a web and many persons can open it to interact simultaneosly, but when I save a conversation from an user, it saves all the other interactions that have been made by other users at the same time, I need that each user has it's own conversation saved separately even if they are interacting with the chatbot at the same time...
Here's my code, maybe I'm missing something:
Bot.js
//SQL Connection
var Connection = require('tedious').Connection;
var config = {
server: 'myserver',
authentication: {
type: 'default',
options: {
userName: 'myuser',
password: 'mypass'
}
},
options: {
encrypt: true,
database: 'mydatabase'
}
};
const connection = new Connection(config);
connection.on('connect', function(err) {
console.log("Connected");
});
var Request = require('tedious').Request
var TYPES = require('tedious').TYPES;
// Function to save the conversation and bot ids
function executeConversationStatement(bot, cid, ulg ) {
request = new Request("INSERT INTO table (bot_id, conversationID, conversation_date, userlogged) VALUES (#bot, #cid, CURRENT_TIMESTAMP, #ulg); SELECT ##IDENTITY AS ID",function(err) {
if (err) {
console.log(err);}
});
request.addParameter('bot', TYPES.Int, bot);
request.addParameter('cid', TYPES.NVarChar, cid);
request.addParameter('ulg', TYPES.NVarChar, ulg);
request.on('row', function(columns) {
insertedcid = columns[0].value; // This is the id I pass later
columns.forEach(function(column) {
if (column.value === null) {
console.log('NULL');
} else {
console.log("Conversation id of inserted item is " + column.value);
}
});
});
connection.execSql(request);
}
// Here on members added I save the conversation id generated by the framework
class BOT extends ActivityHandler {
constructor(conversationState,userState,telemetryClient) {
super();
this.conversationState = conversationState;
this.userState = userState;
this.dialogState = conversationState.createProperty("dialogState");
this.previousIntent = this.conversationState.createProperty("previousIntent");
this.conversationData = this.conversationState.createProperty('conservationData');
const qnaMaker = new QnAMaker({
knowledgeBaseId: process.env.QnAKnowledgebaseId,
endpointKey: process.env.QnAEndpointKey,
host: process.env.QnAEndpointHostName
});
this.qnaMaker = qnaMaker;
this.onMessage(async (context, next) => {
await this.dispatchToIntentAsync(context);
await next();
});
this.onDialog(async (context, next) => {
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
await next();
});
this.onMembersAdded(async (context, next) => {
const { channelId, membersAdded } = context.activity;
actid = context._activity.id;
if (channelId === 'directline' || channelId === 'webchat') {
for (let member of membersAdded) {
if (member.id === context.activity.recipient.id) {
await context.sendActivity("Hi, I´m a chatbot to guide You");
try{
var saveqna = new executeConversationStatement(context._activity.id , 'Invitado');
}
catch{
console.log('Not saved');
}
}
}
}
await next();
});
}
//Finally, here I save the interaction:
async dispatchToIntentAsync(context) {
var result = await this.qnaMaker.getAnswers(context);
// Statement to save interaction with the insertedcid obtained above
var saveqnaint = new executeInteractionStatement(insertedcid, context._activity.text, result);
}
No matter if I use thet generated Id or the databse pk, I always keep the same identifier when multiple users are chatting, how can I got a separately Id for each session ?

use async - await with socket.io nodejs

I'm developing a web application using nodejs socket.io and angular 9. In my backend code I have written sockets in socket.connect.service.
Follows is a socket I'm using
socket.on('request-to-sit-on-the-table', async function (data, callback) { //Previously Register
let table = persistence.getTable(tableToken);
if (typeof table === 'undefined') {
let errMsg = 'This table does not exists or already closed.'
callback(prepareResponse({}, errMsg, new Error(errMsg)));
return;
}
//TODO: Get the displayName from the token.
let guest = await guestUserService.getGuestUserByPlayerToken(JSON.parse(data.userToken));***//Here is the issue***
// let displayName = 'DisplayName-' + guest;
let displayName = 'DisplayName-' + Math.random();
//TODO: Check whether the seat is available
// If the new screen name is not an empty string
let isPlayerInCurrentTable = persistence.isPlayerInCurrentTable(tableToken, userToken);
if (displayName && !isPlayerInCurrentTable) {
var nameExists = false;
let currentPlayersTokenArr = persistence.getTableObjectPlayersToken(table)
for (var token in currentPlayersTokenArr) {
let gamePlayer = persistence.getPlayerPlayer(currentPlayersTokenArr[token])
if (typeof gamePlayer !== "undefined" && gamePlayer.public.name === displayName) {
nameExists = true;
break;
}
}
if (!nameExists) {
//Emit event to inform the admin for requesting to sit on the table.
let ownerToken = persistence.getTableObjectOwnerToken(table);
let ownerSocket = persistence.getPlayerSocket(ownerToken);
ownerSocket.emit('requested-to-sit', {
seat: data.seat,
secondaryUserToken: userToken,
displayName,
numberOfChips: envConfig.defaultNumberOfChips
});
callback(prepareResponse({userToken}, 'Player connected successfully.'));
} else {
callback(prepareResponse({}, 'This name is already taken'));
}
} else {
callback(prepareResponse({}, 'This user has already joined to a game. Try clear caching'));
}
});
In my code I'm getting data from another code in guest.user.service. But I get undefined to the value of "guest"
Follows are the methods I have used in guest.user.service
exports.findById = (id) => {
return new Promise(function(resolve, reject) {
guestUserModel.findById(id, (err, data) =>{
if(err){
reject(err);
} else {
resolve(data);
}
});
});
};
exports.getGuestUserByPlayerToken = (playerToken) => {
var player = playerService.findOne({ token: playerToken })
.then(function (data) {
return self.findById(data.guestUser._id.toString());
})
.then(function (guestUser) {
return guestUser.displayName;
})
.catch(function (err) {
throw new Error(err);
})
};
Although I get my displayName for the return value It is not passed to the "guest" in my socket.Is there any syntax issue to get data as I'm using promises.please help
exports.getGuestUserByPlayerToken = async playerToken => {
try {
let player = await playerService.findOne({token:playerToken});
return playerService.findById(player.guestUser._id)
} catch(error) {
console.log(error);
return null;
}
};
This is just handle error on awaited promise not returned one. You need to handle that in caller side.

Why am I Getting Duplicate Notifications?

I'm trying to use Firebase functions to automatically send push notifications to iOS devices. More specifically, right now when a user creates a post, it will contain an ID, which is actually the user's FCM token, from which the push notification will be sent to that user.
Why does it happen that, upon creating a post, my iOS device doesn't necessarily receive a single push notification, but many? Why is the getResult function being triggered potentially more than once for a given post?
Please see my code below. Thanks!
const functions = require('firebase-functions');
var admin = require('firebase-admin');
var firebase = require('firebase');
admin.initializeApp(functions.config().firebase);
var config = {
apiKey: "XXXXX",
authDomain: "YYYYY",
databaseURL: "ZZZZZ"
}
firebase.initializeApp(config);
firebase.database().ref('posts').on('child_added', function(snapshot1) {
snapshot1.forEach((child) => {
if (child.key == 'id') {
var token = child.val();
admin.database().ref('users').on('value', function(snapshot2) {
snapshot2.forEach(function(user) {
if (user.val().fcmToken == token) {
var newBadgeCount = user.val().badge + 1;
const payload = {
notification: {
title: 'Hello, World!',
body: 'Test Message!',
badge: '' + newBadgeCount,
sound: 'default'
}
};
function getResult(token) {
return result = admin.database().ref('fcmToken/' + token).once('value').then(allToken => {
if (allToken.val()) {
const token = Object.keys(allToken.val());
return admin.messaging().sendToDevice(token, payload).then(function (response) {
console.log("Successfully sent message: ", response.results[0].error);
}).catch(function (error) {
console.log("Error sending message: ", error);
});
};
});
}
function updateBadgeCount(badgeCount, userID) {
firebase.database().ref('users/' + userID + '/badge').set(badgeCount);
}
Promise.all([getResult(token)]).then(function(snapshots) {
updateBadgeCount(newBadgeCount, user.key);
});
};
});
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
};
});
});
Firebase triggers for each record of all records with 'child_added' option once and will re-trigger after each new child added. So, If you like to trigger a function on new write options, you need use other ways.
exports.sendNotifycation = functions.database.ref('/posts/{postId}')
.onWrite(event => {
Your new re-designed codes. (in case I will complete remain)
})

Resources