Creating a Push Notification Server with Node.js

Creating a Push Notification Server with Node.js

At Shockoe we’re used to integrating our clients’ complicated backend systems into our apps, and this often includes push notifications. However, to get that initial proof that our app is playing nicely with GCM and APNS we’ll sometimes rig up a sandbox server of our own. Since we’re used to JavaScript, Node is an obvious go-to. Here are the steps for rigging up a simple push notification server in Node.js.

A push notification server consists of two parts: storing deviceIds and sending push notifications. To achieve this we’ll create two endpoints: register and send. Register will utilize the mongodb node module mongoose and Send will leverage two platform-specific node modules: apns and node-gcm. We’ll also be using restify to set up our endpoints.

Register

For a basic implementation we only need two pieces of information: the deviceId and platform (Android or iOS). Technically–in some mongoose schema code golf scenario–we just need the deviceId, and could make both calls with one always failing.

So our mongoose schema, with the proper requirements, would look something like this:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var Device = new Schema({
    deviceId : String,
    platform : String
});

var DeviceSchema = mongoose.model('Device', Device);

Mongoose is generally a lot more powerful than this implementation, with validations and enumerations that allow for easy error checking, but for the purposes of this demo we’ll use a basic schema. After making sure mongodb is installed and running on your machine, our restify endpoint would look something like this:

var restify = require('restify');

var db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));

db.once('open', function(){
    console.log('db open');
});

mongoose.connect('mongodb://localhost/pushserver');

var server = restify.createServer({
    name : 'pushServer'
});

server.use(restify.plugins.bodyParser());

server.post('/register', (req, res, next) => {
    let body = JSON.parse(req.body);

    if (body) {
        let newDevice = new DeviceSchema(body);
        newDevice.save(err => {
            if (!err) {
                res.send(200);
            } else {
                res.send(500);
            }
        }
    }
}

Ideally we would perform a find on DeviceSchema to make sure we’re not adding duplicate devices.

Send

For Send we’ll implement a basic endpoint that will send a push notification to every device in the database. We can use a simple GET, grab the devices from mongo, and call functions to send the push, based on whether the target is an iOS or an Android device. The Android push module can take an array of deviceId’s instead of one at a time, so we’ll push those ids onto an array and call the sendAndroid function on all of them.

server.get('/send', (req, res) => {
    DeviceSchema.find( (err, devices) => {
        if (!err && devices) {
            let androidDevices = [];
            devices.forEach(device => {
                if (device.platform === 'ios') {
                    sendIos(device.deviceId);
                } else if (device.platform === 'android') {
                    androidDevices.push(device.deviceId);
                }
            });
            sendAndroid(androidDevices);
            res.send(200);
        } else {
            res.send(500);
        }
    });
});

Ideally we would only send the 200 response once we were sure that every push had been sent successfully, but for now it will indicate that the send functions were called.

Our sendIos function will leverage the apns module, which takes an options object. Make sure to include your push key and certificate .pem files at the root of your project:

var apns = require('apns');

var options = {
    keyFile  : 'key.pem',
    certFile : 'cert.pem',
    debug    : true,
    gateway  : 'gateway.sandbox.push.apple.com',
    errorCallback : function(num, err) {
        console.error(err);
    }
};

function sendIos(deviceId) {
    let connection = new apns.Connection(options);

    let notification = new apns.Notification();
    notification.device = new apns.Device(deviceId);
    notification.alert = 'Hello World !';

    connection.sendNotification(notification);
}

For android we’ll use node-gcm. Make sure you have your api key:

var gcm = require('node-gcm');

function sendAndroid(devices) {
    let message = new gcm.Message({
        notification : {
            title : 'Hello, World!'
        }
    });

    let sender = new gcm.sender('<YOUR_API_KEY_HERE>');

    sender.send(message, {
        registrationTokens : devices
    }, function(err, response) {
        if (err) {
            console.error(err);
        } else {
            console.log(response);
        }
    });
}

After that it’s just a simple matter of running your project and you’re ready to hit these endpoints and test push notifications on your app!

 

Enabling Push Notifications Part 3 of 3

Enabling Push Notifications Part 3 of 3

Android Code Setup

Android is set up the same way with just a few minor differences.  On Android we use the ti.cloudpush module and ti.cloud forPush Notifications.  After registering with the server, the function should return a device token.  Save this device token in app properties.

Example : Ti.App.Properties.setString(‘deviceToken’, e.deviceToken);

 However make sure you set the application properties on the callback and not inside the common JS library.  These lib files can sometimes act strangely when it comes to saving app properties.

//require the notifications common js lib file
var notifications = require('notifications');
//check for a device token
var deviceToken = Ti.App.Properties.getString('deviceToken');

//check to see if deviceToken exists
if(deviceToken === '') {
	//get the devices token
	deviceToken = notifications.getToken();
	//set the deviceToken app property
	Ti.App.Properties.setString('deviceToken', deviceToken);
}

In the index js file we set up the device to look for a token and if there is not one to register the user for Push Notifications.

//require ti.cloudpush module
var CloudPush = require('ti.cloudpush');
//require ti.cloud module
var Cloud = require('ti.cloud');

//get device token
exports.getToken = function() {
  // Initialize the module
  CloudPush.retrieveDeviceToken({
      success: deviceTokenSuccess,
      error: deviceTokenError
  });
  // Enable push notifications for this device
  function deviceTokenSuccess(e) {
      CloudPush.enabled = true;
      return e.deviceToken;
  }
  function deviceTokenError(e) {
      alert('Failed to register for push notifications! ' + e.error);
  }
  // Process incoming push notifications
  CloudPush.addEventListener('callback', function (evt) {
      alert(evt.payload);
  });
};

The device token that is generated will allow the application to receive notifications.  You will be able to register the device with ‘Channels’.   The server can push information to certain Channels.  Say you want sports news.  Then a Channel called ‘sports_updates’ could be created.  Channels are not done  server side.  Creation is done on the user side.  There is not a way to manually add a channel on the ACS Dashboard.  Once a user subscribes to a channel for the first time then it is created.

Subscribing a device token to a channel is a function in the common JS library you created for Push Notifications.  In this function we pass two variables. One being the channel and the other being the device token that was generated earlier.  Each one is needed for the subscription.  Now the module ti.cloud will be required for this project as well.  The first module, ti.cloudpush, is just for registering a device token, and ti.cloud does the rest.  So we add ti.cloud to the notifications lib file.

//subscribe to a specific channel
exports.subscribe = function(channel, token) {
    Cloud.PushNotifications.subscribeToken({
        device_token: token,
        channel: channel
    }, function (e) {
        if (e.success) {
            alert('Subscribed');
        } else {
            alert('Error:\n' + ((e.error && e.message) || JSON.stringify(e)));
        }
    });
};

Subscribing a device token to a channel is a function in the common JS library you created for Push Notifications.  In this function we pass two variables. One being the channel and the other being the device token that was generated earlier.  Each one is needed for the subscription.

You will also need to allow the user to unsubscribe from Channel based Pushed Notifications, which can be done using the unsubscribe function.

//unsubsribe from a specific channel
exports.unsubscribe = function(channel, token) {
    Cloud.PushNotifications.unsubscribeToken({
        device_token: token,
        channel: channel,
    }, function (e) {
        if (e.success) {
            alert('Unsubscribed');
        } else {
            alert('Error:\n' + ((e.error && e.message) || JSON.stringify(e)));
        }
    });
};

//device token
var deviceToken = Ti.App.Properties.getString('deviceToken');
//require notificiations lib file
var notifications = require('notifications');
//create view to house label and buttons
var container = Ti.UI.createView({
	width           : Ti.UI.FILL,
	height          : Ti.UI.FILL,
	top             : 30,
	layout          : 'vertical',
	backgroundColor : '#FFFFFF'
});
//create label for title
var channelLabel = Ti.UI.createLabel({
	width     : Ti.UI.FILL,
	height    : Ti.UI.SIZE,
	text      : 'Select a Channel',
	textAlign : 'center'
});
//create sports channel button
var sportsButton = Ti.UI.createButton({
	width     : Ti.UI.FILL,
	height    : Ti.UI.SIZE,
	top       : 10,
	title     : 'Sports'
});
//sports button event listener
sportsButton.addEventListener('click', function(e){
	if(deviceToken !== '') {
		notifications.subscribe('sports_updates', deviceToken);
	} else {
		alert('Error, device not registered for Push Notifications');
	}
});
//add elements together
container.add(channelLabel);
container.add(sportsButton);
$.win.add(container);

Now we need to create a sample page to test this all out with.  It will have a view, label, and button.  Clicking this button will subscribe the user to sports updates from the ACS server.

 This will create a basic view with a label and button on top of the window.  It will say ‘Sports’ and upon click will call the register function from the notifications lib file.

notifications.js

exports.userLogin = function() {
    // Log in to ACS
    Cloud.Users.login({
        login: 'push_notifications',
        password: 'pushy'
    }, function (e) {
        if (e.success) {
            alert('Login successful');
        } else {
            alert('Error:\n' +
                ((e.error && e.message) || JSON.stringify(e)));
        }
    });
};

You can also have users set up on the ACS server and log them in and subscribe to channels.  However the user account must exist.  Generally one user is created for the public, such as the ‘push_notifications’ user we see in the code.   This is perfect for just a basic app with notifications.  If you want user sessions and give the ability for people to create their own accounts you will have to look into Titanium.Cloud.Users on the documentation.

Sending Push Notifications using the Dashboard (http://dashboard.appcelerator.com)

This will cover three different ways for notifications to be sent.

Sending to Specific Channel/Users
Sending to All Channels
Setting up Recurring Notifications

Screen Shot 2014-03-04 at 1.19.11 PM.png

Go to the Dashboard, then Cloud, then Push Notifications.  You will see a button that says ‘Send Push Notification’, click on it.

Sending to Specific Channel/Users

  • Channel – Which channel to send the notification to

    • Choose ‘Specific Channel’

  • Choose ‘Specific Channel’

  • Users – You can choose to enter a username if you have created any to notify just users who are subscribe to that channel.

  • Alert – The message body you want the user to see

  • Send When – The time you would like to send the notification

    • Choose ‘Send Now’

  • Title – The title of the notification

Lets say we notify the ‘sports’ channel, then we need to choose ‘Specific Channel’ under the Channel.  Then under ‘Channel Name’ you would put ‘sports’.  The ACS server is Case Sensitive, so stick with lowercase or camel case.  For the ‘Alert’ section just come up with something such as ‘Richmond, Virginia to get an Major NFL Team’.  Under ‘Send When’ choose ‘Send Now’.  Then for ‘Title’ enter ‘WZYB Sports’ or something like that.

Screen Shot 2014-03-04 at 1.38.40 PM.png

Click ‘Save Changes’ which will send the notification to the subscribed devices.

Sending to All Channels

  • Channel – Which channel to send the notification to

    • Choose ‘All Channels’

  • Alert – The message body you want the user to see

  • Send When – The time you would like to send the notification

    • Choose ‘Send Now’

  • Title – The title of the notification

Click ‘Save Changes’ which will send the notification to the subscribed devices.

Setting up Recurring Notifications

  • Channel – Which channel to send the notification to

    • Choose ‘All Channels’ or ‘Specific Channel’

  • Alert – The message body you want the user to see

  • Send When – The time you would like to send the notification

    • Choose ‘Recurring’

    • Enter the Start Date, End Date, Cycle and Time of Day

  • Title – The title of the notification

Click ‘Save Changes’ which will send the notification to the subscribed devices.

That concludes this tutorial on Push Notifications.  Please feel free to leave a comment and let us know if this worked for you.

 

Enabling Push Notifications Part 2 of 3

Enabling Push Notifications Part 2 of 3

This document is intended for the average Titanium Developer to enable push notifications.  It is assumed that the reader already has a basic knowledge of Titanium and the APIs.

Today we will talk about Part 2 of 3 of Push Notifications, which begins with Google App Registration.

Register your application with Google

Android Setup

  1. Create a Google API Project

  2. Register with Appcelerator Cloud Services

Step 1 : Create a Google API Project

Screen Shot 2014-02-27 at 4.28.18 PM.png

You will need to open the Google Developers Console by visiting https://cloud.google.com/console. From there you will need to sign in with your Google ID that you want associated with this project.  Once signed in you will see a basic screen and a big red button that says ‘CREATE PROJECT’.

Enter a Project Name, and ignore Project ID.  Project ID is randomly generated by Google to avoid duplication of IDs.  Click the ‘Create’ button and the Google Developer.

Console should refresh.  Now you should be on the projects Overview page.  At the top of this page you will see Project ID and Project Number.

Screen Shot 2014-02-27 at 4.16.34 PM.png

Copy the Project Number, since this will be used at a later point as the GCM Sender ID.  On the left corner of the page there is an APIs & Auth section.  Go to that page and a list of API items will populate.  Turn On the Google Cloud Messaging for Android.

Again under APIs & Auth you will see ‘Credentials’, click there.  Now you will see a section called ‘Public API Access’.  Click the ‘CREATE NEW KEY’ button to generate an API Access Key.

A popup will appear, on that select ‘Server Key’.  Another popup will appear asking you to enter IP Addresses in the text field, just click ‘Create’.

DO NOT ENTER ANY IP ADDRESSES

Now under the Public API access section there is a section for API key with a rather strange combination of letters & numbers.  Copy that text and hang onto it.  Now go to the Appcelerator Dashboard https://dashboard.appcelerator.com/.

As before select the app you are working with and click the ‘Cloud’ tab.  On the left menu click ‘Settings & Configurations’.  Then on the tabbed menu click ‘Android Push’.  It will ask you for the GCM API Key and GCM Sender ID, which you should have saved.  Enter those values in and click ‘Save Changes’.

Implement the code into a Common JS library

Ensure that you are using a Common JS library for notifications.  Create a ‘notifications.js’ file inside the ‘lib’ folder.  If the folder does not exists then create it.

iOS Code Setup

With iOS you must use Ti.Network.registerForPushNotifications the first time the user enters the application.  After registering with the server, the function should return a device token.  Save this device token in app properties.

Example : Ti.App.Properties.setString(‘deviceToken’, e.deviceToken);

However make sure you set the application properties on the callback and not inside the common JS library.  These lib files can sometimes act strangely when it comes to saving app properties.

index.js

//require the notifications common js lib file
var notifications = require('notifications');
//check for a device token
var deviceToken = Ti.App.Properties.getString('deviceToken');

//check to see if deviceToken exists
if(deviceToken === '') {
	//create a device token by registering for notifications
	deviceToken = notifications.register();
	//set the deviceToken app property
	Ti.App.Properties.setString('deviceToken', deviceToken);
}
//open the index window
$.index.open();

//controller
var mainWindow = Alloy.createController('Main').getView();
//open the mainWindow
mainWindow.open();

notifications.js

//require ti.cloud for iOS Notifications
var Cloud = require('ti.cloud');

//register the device and receive a token
exports.register = function() {
    Ti.Network.registerForPushNotifications({
        // Specifies which notifications to receive
        types: [
            Ti.Network.NOTIFICATION_TYPE_BADGE,
            Ti.Network.NOTIFICATION_TYPE_ALERT,
            Ti.Network.NOTIFICATION_TYPE_SOUND
        ],
        success: deviceTokenSuccess,
        error: deviceTokenError,
        callback: receivePush
    });
    // Process incoming push notifications
    function receivePush(e) {
        alert('Received push: ' + JSON.stringify(e));
    }
    // Save the device token for subsequent API calls
    function deviceTokenSuccess(e) {
        return e.deviceToken;
    }
    // Display an error upon failure to register
    function deviceTokenError(e) {
        alert('Failed to register for push notifications! ' + e.error);
    }
};

The device token that is generated will allow the application to receive notifications.  You will be able to register the device with ‘Channels’.   The server can push information to certain Channels.  Say you want sports news updates.  Then a Channel called ‘sports_updates’ could be created.  Channels are not done on the server side.  Creation is done on the user side.  There is not a way to manually add a channel on the ACS Dashboard.  Once a user subscribes to a channel for the first time then it is created.


//subscribe to a specific channel
exports.subscribe = function(channel, token) {
    Cloud.PushNotifications.subscribeToken({
        device_token: token,
        channel: channel
    }, function (e) {
        if (e.success) {
            alert('Subscribed');
        } else {
            alert('Error:\n' + ((e.error && e.message) || JSON.stringify(e)));
        }
    });
};

Subscribing a device token to a channel is a function in the common JS library you created for Push Notifications.  In this function we pass two variables. One being the channel and the other being the device token that was generated earlier.  Each one is needed for the subscription.


//unsubsribe from a specific channel
exports.unsubscribe = function(channel, token) {
    Cloud.PushNotifications.unsubscribeToken({
        device_token: token,
        channel: channel,
    }, function (e) {
        if (e.success) {
            alert('Unsubscribed');
        } else {
            alert('Error:\n' + ((e.error && e.message) || JSON.stringify(e)));
        }
    });
};

You will also need to allow the user to unsubscribe from Channel based Pushed Notifications, which can be done using the unsubscribe function.

Main.js

//device token
var deviceToken = Ti.App.Properties.getString('deviceToken');
//require notificiations lib file
var notifications = require('notifications');
//create view to house label and buttons
var container = Ti.UI.createView({
	width           : Ti.UI.FILL,
	height          : Ti.UI.FILL,
	top             : 30,
	layout          : 'vertical',
	backgroundColor : '#FFFFFF'
});
//create label for title
var channelLabel = Ti.UI.createLabel({
	width     : Ti.UI.FILL,
	height    : Ti.UI.SIZE,
	text      : 'Select a Channel',
	textAlign : 'center'
});
//create sports channel button
var sportsButton = Ti.UI.createButton({
	width     : Ti.UI.FILL,
	height    : Ti.UI.SIZE,
	top       : 10,
	title     : 'Sports'
});
//sports button event listener
sportsButton.addEventListener('click', function(e){
	if(deviceToken !== '') {
		notifications.subscribe('sports_updates', deviceToken);
	} else {
		alert('Error, device not registered for Push Notifications');
	}
});
//add elements together
container.add(channelLabel);
container.add(sportsButton);
$.win.add(container);

Now we need to create a sample page to test this all out with.  It will have a view, label, and button.  Clicking this button will subscribe the user to sports updates from the ACS server.

This will create a basic view with a label and button on top of the window.  It will say ‘Sports’ and upon click will call the register function from the notifications lib file.

notifications.js Again


exports.userLogin = function() {
    // Log in to ACS
    Cloud.Users.login({
        login: 'push_notifications',
        password: 'pushy'
    }, function (e) {
        if (e.success) {
            alert('Login successful');
        } else {
            alert('Error:\n' +
                ((e.error && e.message) || JSON.stringify(e)));
        }
    });
};

You can also have users set up on the ACS server and log them in and subscribe to channels.  However the user account must exist.  Generally one user is created for the public, such as the ‘push_notifications’ user we see in the code.   This is perfect for just a basic app with notifications.  If you want user sessions and give the ability for people to create their own accounts you will have to look into Titanium.Cloud.Users on the documentation.

 

Try it and leave a comment below to let us know if you had any problems following these steps.

We will continue next week with part 3.