Send push notifications in nodejs using fcm node module - node.js

I want to send push notifications on a device I have device ID , how can I send notifications on that device.

I have used FCM to send the notifications to the users. I used Mongodb,Nodejs and fcm-push node module.
Please see the codes, Hope it will help you.
deviceModel.js :
var mongoose = require('mongoose');
var Schema = require('mongoose').Schema;
var schema = new Schema({
userId: { type: Schema.Types.ObjectId },
userType: { type: String },
deviceType: { type: String, required: true, enum: ['apn', 'gcm'] },
deviceId: { type: String, required: true },
timestamp: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Device', schema);
push.js:
var pushModule = require('./push');
var DeviceModel = require('./DeviceModel');
var highland = require('highland');
var FCM = require('fcm-push');
var serverKey = config.get('fcmServerKey');
var fcm = new FCM(serverKey);
var _ = require('lodash');
/**
* Handle fcm push results and updates/invalidates device ids.
*
* #param {String[]} deviceIds - array of device ids used in send.
* #param {Object} fcmResponse - fcm send response.
*/
function handleFcmSendResult(deviceIds, fcmResponse) {
// fcm results
var results = fcmResponse.results;
// changes for deviceIds
var fcmUpdated = [];
var fcmDeleted = [];
// process results
for (var i = 0; i < results.length; i++) {
var oldId = deviceIds[i];
var deviceResult = results[i];
var msgId = deviceResult.message_id;
var newId = deviceResult.registration_id;
if (_.isString(msgId) && _.isString(newId)) {
// If registration_id is set, replace the original ID with the new value
fcmUpdated.push({ oldId: oldId, newId: newId });
} else {
// Otherwise, get the value of error
var e = deviceResult.error;
if (e === 'Unavailable') {
winston.warn('Push: FCM: Feedback: device unavailable: %s.', oldId);
} else if (e === 'NotRegistered' || e === 'InvalidRegistration') {
// delete invalid devices
fcmDeleted.push(oldId);
}
}
}
// apply changes, in bulk
var bulkOp = DeviceModel.collection.initializeUnorderedBulkOp();
if (fcmUpdated.length > 0) {
fcmUpdated.forEach(function (upd) {
bulkOp.find({ deviceId: upd.oldId }).update({ deviceId: upd.newId, timestamp: Date.now() });
});
// those old ids that are updated, need not be deleted
fcmDeleted = _.difference(fcmDeleted, _.map(fcmUpdated, _.property('oldId')));
}
if (fcmDeleted.length > 0) {
bulkOp.find({ deviceId: { '$in': fcmDeleted } }).remove();
}
console.log(bulkOp);
// run bulk op
bulkOp.execute();
}
/**
* Dispatch FCM push to device ids.
*
* #param {String[]} deviceIds - array of apn device ids.
* #param {String} eventName - event name.
* #param {*} eventData - event data.
*/
function sendFcm(deviceIds, eventName, eventData) {
// payload
var msgOpts = {
priority: 'high',
registration_ids: deviceIds,
data: _.set(eventData, 'eventName', eventName),
notification: eventData,
content_available: true,
mutable_content: true
};
fcm.send(msgOpts)
.then(function (response) {
console.log("SENT :",response);
// handleFcmSendResult(deviceIds, JSON.parse(response));
})
.catch(function (err) {
winston.error('Push: FCM: Error sending push.', err);
})
}
/**
* Sends push notifications to Device docs emitted from stream.
*
* #param {Stream.Readable} docStream - Stream of device docs.
* #param {String} eventName - event name.
* #param {*} eventData - event data.
*/
function streamSend(docStream, eventName, eventData) {
// stream for fcm
var fcmStream = highland();
// batch device ids from sub stream and sent to gcm
fcmStream.batch(1000).each(function (fcmIds) {
sendFcm(fcmIds, eventName, eventData);
});
// split source to sub streams
highland(docStream).each(function (doc) {
fcmStream.write(doc.deviceId);
}).done(function () {
// end sub streams when doc source is done
fcmStream.end();
});
}
/**
* Sends the event via push to all registered devices.
* #param {String} eventName - event name.
* #param {Object} eventData - event data. Can contain a "notification" object with: title, description and icon.
*/
var pushToPublic = function (eventName, eventData) {
var str = DeviceModel.find().cursor();
streamSend(str, eventName, eventData);
}
/**
* Sends the event via push to devices that are mapped to given user ids.
* #param {ObjectId[]} userIds - array of user ids.
* #param {String} eventName - event name.
* #param {Object} eventData - event data. Can contain a "notification" object with: title, description and icon.
*/
var pushToUserIds = function (userIds, eventName, eventData) {
var str = DeviceModel.find({ userId: { '$in': userIds } }).cursor();
streamSend(str, eventName, eventData);
}
// Send notification test function
var sendNotification = function () {
var payload = {
"updatedAt": "2017-06-17T06:12:42.975Z",
"message": "this is notification message",
"typeId": "591452ecad4c6b71bed61089",
"userId": "5912d45f29945b6d649f287e",
"_id": "5913f90d08b4d213f1ded021",
"isRead": false,
"isPublic": true,
// ORDER DELIVERD
"type": "order",
"title_loc_key": "title_order_delivered",
"title_loc_args": ["OrderValue"],
"body_loc_key": "body_order_delivered",
"body_loc_args": ["reminderValue"],
};
// pushToPublic("testEvent", payload);
pushToUserIds(['59562201a544614d47845eef'], "testEvent", payload)
}
sendNotification();
Result:
SENT:{ "multicast_id":1234567891234567890,
"success":1,
"failure":0,
"canonical_ids":0,
"results":[{
"message_id": "0:12345678912345678912345678912345678"
}]
}

you can use nodejs package node-gcm
for more please check this link will you more and solve your problem .
node package1
or
node package2

Related

Issue with notification hub

I am new to azure notification hub. I tried from the documentation. But not able to do the name . Need some help on this .
Tried from the below link
How to register devices to Azure Notification Hub from server side(with NodeJS sdk) ?
Not sure about the params.
var azure = require('azure-sb');
var notificationHubService = azure.createNotificationHubService('<Hub Name>','<Connection String>');
var payload={
alert: 'Hello!'
};
notificationHubService.createRegistrationId(function(error, registrationId, response){
if(!error){
console.log(response);
console.log(registrationId);
//RegistrationDescription registration = null;
//registration.RegistrationId = registrationId;
//registration.DeviceToken = req.body.token;
notificationHubService.apns.createOrUpdateNativeRegistration(registrationId, req.body.token, req.token.upn, function(error, response){
if(!error){
console.log('Inside : createOrUpdateNativeRegistration' + response);
notificationHubService.apns.send(null, payload, function(error){
if(!error){
// notification sent
console.log('Success: Inside the notification send call to Hub.');
}
});
}
else{
console.log('Error in registering the device with Hub' + error);
}
});
}
else{
console.log('Error in generating the registration Id' + error);
}
});
While creating registrationID which registration id i have to pass there. What is request.body.token and what is request.token.upn. I need it for apns
While creating registrationId , you dont have to pass any id. **createRegistrationId(callback)** takes callback as a parameter which creates a registration identifier.
As per the overall implemetation:
/**
* Creates a registration identifier.
*
* #param {Function(error, response)} callback `error` will contain information
* if an error occurs; otherwise, `response`
* will contain information related to this operation.
*/
NotificationHubService.prototype.createRegistrationId = function (callback) {
validateCallback(callback);
var webResource = WebResource.post(this.hubName + '/registrationids');
webResource.headers = {
'content-length': null,
'content-type': null
};
this._executeRequest(webResource, null, null, null, function (err, rsp) {
var registrationId = null;
if (!err) {
var parsedLocationParts = url.parse(rsp.headers.location).pathname.split('/');
registrationId = parsedLocationParts[parsedLocationParts.length - 1];
}
callback(err, registrationId, rsp);
});
};
Once you are done with RegistrationID Creation then you can call createOrUpdateRegistration(registration, optionsopt, callback) and here is the overall implementation for the same:
/**
* Creates or updates a registration.
*
* #param {string} registration The registration to update.
* #param {object} [options] The request options or callback function. Additional properties will be passed as headers.
* #param {object} [options.etag] The etag.
* #param {Function(error, response)} callback `error` will contain information
* if an error occurs; otherwise, `response`
* will contain information related to this operation.
*/
NotificationHubService.prototype.createOrUpdateRegistration = function (registration, optionsOrCallback, callback) {
var options;
azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { options = o; callback = c; });
validateCallback(callback);
if (!registration || !registration.RegistrationId) {
throw new Error('Invalid registration');
}
var webResource = WebResource.put(this.hubName + '/registrations/' + registration.RegistrationId);
registration = _.clone(registration);
var registrationType = registration[Constants.ATOM_METADATA_MARKER]['ContentRootElement'];
delete registration[Constants.ATOM_METADATA_MARKER];
delete registration.ExpirationTime;
delete registration.ETag;
if (!registration.Expiry) {
delete registration.Expiry;
}
registration.BodyTemplate = '<![CDATA[' + registration.BodyTemplate + ']]>';
var registrationXml = registrationResult.serialize(registrationType, registration);
this._executeRequest(webResource, registrationXml, registrationResult, null, callback);
};
You can find the complete implementation of NotificationHubService.js here.
Hope it helps.

Realtime database function triggers constantly

I have deployed a JS function for Firebase realtime database triggers. In its operations, it should send a push notification just on value update in the database, which is dead simple:
{
"rollo" : "yes"
}
If value changes to yes it should trigger notification. If it goes to "no" then it should do nothing. Here is the JS function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendNewPostNotif = functions.database.ref('/rollo').onUpdate((change, context) => {
console.log('Push notification event triggered');
const beforeData = change.before.val();
const payload = {
notification: {
title: 'Push triggered!',
body: "Push text",
sound: "default"
}
};
const options = {
priority: "high",
timeToLive: 60 * 10 * 1
};
return admin.messaging().sendToTopic("notifications", payload, options);
});
Also even though I have set TTL, each value change sends another push notification.
Any ideas?
I would try something like this:
exports.sendNewPostNotif = functions.database.ref('/rollo').onWrite((change, context) => {
const newData = change.after.val();
const oldData = change.before.val();
const payload = {
notification: {
title: 'Push triggered!',
body: "Push text",
sound: "default"
}
};
const options = {
priority: "high",
timeToLive: 60 * 10 * 1
};
if (newData != oldData && newData == 'yes') {
return admin.messaging().sendToTopic("notifications", payload, options);
}
});
onUpdate():
triggers when data is updated in the Realtime Database.
When you update it to "no" it will send a notification and when you update it to "yes" it will also send a notification.
https://firebase.google.com/docs/functions/database-events

How to get the key of a child based of the object in Firebase

I was wondering if there is a way to retrieve the _random number in firebase using node.js and encapsulate it in my notifications body section.? I tried to create an array and read everything from firebase into the array but it didn't help
exports.sendFigureNotification = functions.database.ref('_random').onWrite(event => {
var rootRef = admin.database().ref();
rootRef.once("value", function(snapshot) {
});
const payload = {
notification: {
title: 'Title',
body: 'The Cloud Function works', //use _random to get figure at index key
badge: '1',
sound: 'default'
}
};
const options = {
priority:"high",
timeToLive: 60 * 60 * 24, //24 hours
content_available: true
};
const topic = "HBF"
console.log('Sending notifications');
return admin.messaging().sendToTopic(topic, payload, options);
});
https://i.stack.imgur.com/t22Ou.png

Alexa-SDK Audio Issue

I've been trying to make an alexa skill that involves audio. I found a great guide here.
Here is their example code:
var stateByUser = {};
var podcastURL = "https://feeds.soundcloud.com/stream/309340878-user-652822799-episode-010-building-an-alexa-skill-with-flask-ask-with-john-wheeler.mp3";
// Entry-point for the Lambda
exports.handler = function(event, context) {
var player = new SimplePlayer(event, context);
player.handle();
};
// The SimplePlayer has helpful routines for interacting with Alexa, within minimal overhead
var SimplePlayer = function (event, context) {
this.event = event;
this.context = context;
};
// Handles an incoming Alexa request
SimplePlayer.prototype.handle = function () {
var requestType = this.event.request.type;
var userId = this.event.context ? this.event.context.System.user.userId : this.event.session.user.userId;
var response = null;
// On launch, we tell the user what they can do (Play audio :-))
if (requestType === "LaunchRequest") {
this.say("Welcome to the Simple Audio Player. Say Play to play some audio!", "You can say Play");
// Handle Intents here - Play, Pause and Resume is all for now
} else if (requestType === "IntentRequest") {
var intent = this.event.request.intent;
if (intent.name === "Play") {
this.play(podcastURL, 0);
} else if (intent.name === "AMAZON.PauseIntent") {
// When we receive a Pause Intent, we need to issue a stop directive
// Otherwise, it will resume playing - essentially, we are confirming the user's action
this.stop();
} else if (intent.name === "AMAZON.ResumeIntent") {
var lastPlayed = this.load(userId);
var offsetInMilliseconds = 0;
if (lastPlayed !== null) {
offsetInMilliseconds = lastPlayed.request.offsetInMilliseconds;
}
this.play(podcastURL, offsetInMilliseconds);
}
} else if (requestType === "AudioPlayer.PlaybackStopped") {
// We save off the PlaybackStopped Intent, so we know what was last playing
this.save(userId, this.event);
}
};
/**
* Creates a proper Alexa response using Text-To-Speech
* #param message
* #param repromptMessage
*/
SimplePlayer.prototype.say = function (message, repromptMessage) {
var response = {
version: "1.0",
response: {
shouldEndSession: false,
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
},
reprompt: {
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
}
}
}
}
this.context.succeed(response);
};
/**
* Plays a particular track, from specific offset
* #param audioURL The URL to play
* #param offsetInMilliseconds The point from which to play - we set this to something other than zero when resuming
*/
SimplePlayer.prototype.play = function (audioURL, offsetInMilliseconds) {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Play",
playBehavior: "REPLACE_ALL", // Setting to REPLACE_ALL means that this track will start playing immediately
audioItem: {
stream: {
url: audioURL,
token: "0", // Unique token for the track - needed when queueing multiple tracks
expectedPreviousToken: null, // The expected previous token - when using queues, ensures safety
offsetInMilliseconds: offsetInMilliseconds
}
}
}
]
}
}
this.context.succeed(response);
};
// Stops the playback of Audio
SimplePlayer.prototype.stop = function () {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Stop"
}
]
}
}
this.context.succeed(response);
};
// Saves information into our super simple, not-production-grade cache
SimplePlayer.prototype.save = function (userId, state) {
console.log("Save: " + userId);
stateByUser[userId] = state;
};
// Load information from our super simple, not-production-grade cache
SimplePlayer.prototype.load = function (userId) {
console.log("Load: " + userId);
var state = null;
if (userId in stateByUser) {
state = stateByUser[userId];
console.log("Loaded " + userId + " State: " + state);
}
return state;
};
I am trying to refactor this code so that it follows a similar format to the trivia skills example that amazon provides. However, when I run my refactored code I get an error saying
TypeError: Cannot set property 'say' of undefined
at Object.<anonymous> (/Users/Rob/Desktop/super-simple-audio-player/index.js:47:28)
Here is my attempt at refactoring
"use strict";
var stateByUser = {};
var podcastURL = "https://p.scdn.co/mp3-preview/2385471a5d35709ad90e368dacabe4082af4541a?cid=null";
var Alexa = require("alexa-sdk");
// Entry-point for the Lambda
exports.handler = function(event, context) {
var alexa = Alexa.handler(event, context);
alexa.registerHandlers(SimplePlayer);
alexa.execute();
};
// The SimplePlayer has helpful routines for interacting with Alexa, within minimal overhead
var SimplePlayer = {
"LaunchRequest": function () {
this.emit(":tell","Welcome to the Simple Audio Player. Say play to begin.");
},
"Play": function() {
this.play(podcastURL, 0);
},
"AMAZON.PauseIntent": function() {
this.stop();
},
"AMAZON.ResumeIntent": function () {
var lastPlayed = this.load(userId);
var offsetInMilliseconds = 0;
if (lastPlayed !== null) {
offsetInMilliseconds = lastPlayed.request.offsetInMilliseconds;
}
this.play(podcastURL, offsetInMilliseconds);
},
"AudioPlayer.PlaybackStopped": function () {
this.save(userId, this.event);
}
};
// Handles an incoming Alexa request
SimplePlayer.prototype.say = function (message, repromptMessage) {
var response = {
version: "1.0",
response: {
shouldEndSession: false,
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
},
reprompt: {
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
}
}
}
}
this.context.succeed(response);
};
/**
* Plays a particular track, from specific offset
* #param audioURL The URL to play
* #param offsetInMilliseconds The point from which to play - we set this to something other than zero when resuming
*/
SimplePlayer.prototype.play = function (audioURL, offsetInMilliseconds) {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Play",
playBehavior: "REPLACE_ALL", // Setting to REPLACE_ALL means that this track will start playing immediately
audioItem: {
stream: {
url: audioURL,
token: "0", // Unique token for the track - needed when queueing multiple tracks
expectedPreviousToken: null, // The expected previous token - when using queues, ensures safety
offsetInMilliseconds: offsetInMilliseconds
}
}
}
]
}
}
this.context.succeed(response);
};
// Stops the playback of Audio
SimplePlayer.prototype.stop = function () {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Stop"
}
]
}
}
this.context.succeed(response);
};
// Saves information into our super simple, not-production-grade cache
SimplePlayer.prototype.save = function (userId, state) {
console.log("Save: " + userId);
stateByUser[userId] = state;
};
// Load information from our super simple, not-production-grade cache
SimplePlayer.prototype.load = function (userId) {
console.log("Load: " + userId);
var state = null;
if (userId in stateByUser) {
state = stateByUser[userId];
console.log("Loaded " + userId + " State: " + state);
}
return state;
};
I've added alexa-sdk and changed the exports.handler and the simplePlayer.prototype.handler(). Any thoughts as to why it is not working?
Thanks in advance
I actually created the project you reference. Glad you are finding it useful.
In re-factoring the project, you changed it from prototype-style JS object to an object literal. Both are viable approaches, but the object literal becomes a problem when holding the state for a particular request (the event and context fields in particular).
It also means that the prototype methods defined in the project are not available from the object literal definition. You need to instantiate SimplePlayer (by calling new SimplePlayer(event, context)) before you will get those.
If you want to understand the trade-off between these approaches better, you can read here:
Object literal vs constructor+prototype
Here is an example of working with the Alexa SDK consistent with my project. It defines the "LaunchRequest" function as a prototype function rather than simply a property:
SimplePlayer.prototype.LaunchRequest = function () {
this.emit(":tell", "Welcome to the Simple Audio Player. Say play to begin.");
};
You also need to make sure to instantiate the SimplePlayer (not just reference it). When registering it, it should look like this:
alexa.registerHandlers(new SimplePlayer(event, context));
Hope that makes sense, and good luck with it! Let me know how it goes (I can always be reached at https://gitter.im/bespoken/bst)

how to define error message in sails.js

It's my first time using sails and it looks like it's good but I run into a problem, is it possible to define a custom error message in sails model validation because it looks like the error message being returned is to technical and not user friendly.
Thanks
Update: https://gist.github.com/mikermcneil/8366092
Here's another alternative:
/**
* Takes a Sails Model object (e.g. User) and a ValidationError object and translates it into a friendly
* object for sending via JSON to client-side frameworks.
*
* To use add a new object on your model describing what validation errors should be translated:
*
* module.exports = {
* attributes: {
* name: {
* type: 'string',
* required: true
* }
* },
*
* validation_messages: {
* name: {
* required: 'you have to specify a name or else'
* }
* }
* };
*
* Then in your controller you could write something like this:
*
* var validator = require('sails-validator-tool');
*
* Mymodel.create(options).done(function(error, mymodel) {
* if(error) {
* if(error.ValidationError) {
* error_object = validator(Mymodel, error.Validation);
* res.send({result: false, errors: error_object});
* }
* }
* });
*
* #param model {Object} An instance of a Sails.JS model object.
* #param validationErrors {Object} A standard Sails.JS validation object.
*
* #returns {Object} An object with friendly validation error conversions.
*/
module.exports = function(model, validationError) {
var validation_response = {};
var messages = model.validation_messages;
validation_fields = Object.keys(messages);
validation_fields.forEach(function(validation_field) {
if(validationError[validation_field]) {
var processField = validationError[validation_field];
//console.log(processField);
processField.forEach(function(rule) {
if(messages[validation_field][rule.rule]) {
if(!(validation_response[validation_field] instanceof Array)) {
validation_response[validation_field] = new Array();
}
var newMessage={};
newMessage[rule.rule] = messages[validation_field][rule.rule];
validation_response[validation_field].push(newMessage);
}
});
}
});
return validation_response;
};
Credits to: sfb_
Here is an ugly fix:
make a folder named my-validation-utils. create a index.js file there. and put the following content there:
var user = {
email:{
required:'Email Required',
email:'Should be an email'
},
name:{
required:'name required'
}
};
var product={
name:{
required:'Product name is required'
}
}
var validationMessages = {
user:user,
product:product
};
/**
* This function expects the name of the model and error.validationError
* and puts the user defined messages in error.validationError
*/
module.exports = function(model,validationError){
var messages = validationMessages[model];
for(key in messages){
var element = messages[key];
if(validationError[key]){
for(i in validationError[key]){
var err = validationError[key][i];
err.message = element[err.rule];
}
}
}
return validationError;
};
Now in your controller do something like this:
User.create(user).done(function (error, user) {
if (error) {
if (error.ValidationError) {
var validator = require('path/to/my-validation-utils');
var errors = validator('user',error.ValidationError);// puts the messages for model user
//now errors contains the validationErrors with user defined messages
}
} else {
//user is saved
}
});

Resources