Skip to content

Instantly share code, notes, and snippets.

Created November 19, 2012 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/0f3c17f6fcc079090256 to your computer and use it in GitHub Desktop.
Save anonymous/0f3c17f6fcc079090256 to your computer and use it in GitHub Desktop.
// -------------------------------------------------------------------------------------------
// PNS Class for Push Notification
//
// Note: this class also acts as a Node.js module to be imported and used in server.js file
// -------------------------------------------------------------------------------------------
var eventEmitter = require('events').EventEmitter;
var util = require('util');
var instance;
/**
* Note: dev_cert_path param is a path to the single file pem file that
* embeds the ios PNS certificate and private key
*/
var PNS = function(dev_cert_path, prod_cert_path) {
// dev_cert param is a
this.dev_cert_path = dev_cert_path;
this.prod_cert_path = prod_cert_path;
this.dev_host = 'gateway.sandbox.push.apple.com';
this.prod_host = 'gateway.push.apple.com';
// port to Apple push notification server
this.port = 2195;
// push notification socket stream variable
this.stream = null;
this.crypto = require('crypto');
this.tls = require('tls');
this.fs = require('fs');
this.path = require('path');
this.querystring = require('querystring');
this.http = require('http');
instance = this;
}
// allowing this class to emit events
util.inherits(PNS, eventEmitter);
// -------------------------------------------------------------------------------------------
// Class methods
// -------------------------------------------------------------------------------------------
/**
* Helper constructor method
*/
var init = function(dev_cert_path, prod_cert_path) {
return new PNS(dev_cert_path, prod_cert_path);
};
/**
* Method to connect to the push notification server
*/
var connect = function() {
// read certificate and key file
// synchronously
var privateKey = this.fs.readFileSync(this.dev_cert_path, encoding='ascii');
var certificate = this.fs.readFileSync(this.dev_cert_path, encoding='ascii');
var options = { key: privateKey, cert: certificate };
this.stream = this.tls.connect(this.port, this.dev_host, options, function() {
// connected
console.log('Apple Push Notification socket stream connected');
instance.emit('pns_connected');
});
};
/**
* Makes a POST request to Symfony web server to get the necessary Notification objects
* then start parsing the notification objects, sending any notification out
*/
var getNotifications = function() {
console.log('getting push notifications now');
var options = {
host: 'www.symfony.local',
port: 80,
path: '/app_dev.php/api/pnsqueue',
method: 'POST'
};
var data = '';
// make a HTTP Post request to the Symfony web server
var request = this.http.request(options, function(response) {
response.on('data', function(chunk) {
data += chunk;
//console.log('data = ' + data + '\n');
});
response.on('end', function() {
parseNotifications(data);
});
});
// end of request
request.end();
};
/**
* Builds the final array needed to send push notifications
* and calls the push notification method for each notifications
*/
function parseNotifications(rawData) {
var obj = JSON.parse(rawData);
//var strJSON = JSON.stringify(rawData);
//console.log(obj);
// array of notification objects
var dataObj = obj.data;
//console.log('dataObj');
//console.log(dataObj);
var arrNotifications = dataObj.notification;
//console.log('arrNotifications');
//console.log(arrNotifications);
// ---------------------------------------------------
// go through each notification and
// parse get the message and array of tokens
// ---------------------------------------------------
for(var i = 0; i < arrNotifications.length; i++)
{
//console.log('parsing notification');
var notificationObj = arrNotifications[i];
//console.log(notificationObj);
//console.log('message = ' + notificationObj.message);
var arrTokens = new Array();
var arrNotifObjTokens = notificationObj.tokens;
if(arrNotifObjTokens != null)
{
// add all raw tokens
for(var j = 0; j < arrNotifObjTokens.length; j++)
{
var tokenObj = notificationObj.tokens[j];
if(tokenObj.active)
{
// add the token string from the token object
// to our final array
arrTokens.push(tokenObj.token);
//console.log('raw token added');
}
}
}
var arrNotifObjTokenGroups = notificationObj.tokengroups;
if(arrNotifObjTokenGroups != null)
{
// add all tokens from all token groups
//for(var tokengroup in notificationObj.tokengroups)
for(var k = 0; k < arrNotifObjTokenGroups.length; k++)
{
var tokengroupObj = arrNotifObjTokenGroups[k];
var arrTGTokens = tokengroupObj.tokens;
if(arrTGTokens != null)
{
//for(var token in tokengroupObj.tokens)
for(var l = 0; l < arrTGTokens.length; l++)
{
var tokenObj = arrTGTokens[l];
arrTokens.push(tokenObj.token);
//console.log('token group token added');
}
}
}
}
// clean up final array, removing all duplicates
arrTokens = arrTokens.filter(function(elem, pos) {
return arrTokens.indexOf(elem) == pos;
});
//console.log('arrTokens = ' + arrTokens + '\n\n');
// send the final push notification
pushNotification(notificationObj.message, arrTokens);
}
// start downloading notification again
//instance.emit('pns_queue_end');
};
/**
* sends a push notification for all tokens attached to the notification
*/
function pushNotification(message, arrTokens)
{
console.log('sending push notification...');
// for each token, send the notification message to that token
var pushObj = { aps: { alert:message, sound:'default' } };
var payload = JSON.stringify(pushObj);
var tokenlen = 32;
var payloadlen = Buffer.byteLength(payload, 'utf-8');
var buffer = new Buffer(1 + 4 + 4 + 2 + tokenlen + 2 + payloadlen);
var i = 0;
var msgid = 0xbeefcace; // message identifier, can be left at 0
//console.log('arrTokens = ' + arrTokens);
console.log('arrTokens.length = ' + arrTokens.length);
for(var i = 0; i < arrTokens.length; i++)
{
var strToken = arrTokens[i];
console.log('strToken = ' + strToken + '\n');
var hextoken = strToken;
buffer[i++] = 1; // command
buffer[i++] = msgid >> 24 & 0xFF;
buffer[i++] = msgid >> 16 & 0xFF;
buffer[i++] = msgid >> 8 & 0xFF;
buffer[i++] = msgid & 0xFF;
// expiry in epoch seconds (1 hour)
var seconds = Math.round(new Date().getTime() / 1000) + 1*60*60; // expire in epoch seconds (1 hour)
buffer[i++] = seconds >> 24 & 0xFF;
buffer[i++] = seconds >> 16 & 0xFF;
buffer[i++] = seconds >> 8 & 0xFF;
buffer[i++] = seconds & 0xFF;
buffer[i++] = tokenlen >> 8 & 0xFF; // token length
buffer[i++] = tokenlen & 0xFF;
var token = hextobin(hextoken);
token.copy(buffer, i, 0, tokenlen);
i += tokenlen;
buffer[i++] = payloadlen >> 8 & 0xFF; // payload length
buffer[i++] = payloadlen & 0xFF;
payload = Buffer(payload);
payload.copy(buffer, i, 0, payloadlen);
var writable = instance.stream.write(buffer);
if(!writable)
{
console.log('unable to write to stream');
}
// reset buffer
//console.log('a push notification was sent for token ' + strToken);
}
}
/**
* Helper function to convert hexidecimal to binary
* Apple push notification expects a binary stream
*/
function hextobin(hexstr)
{
buf = new Buffer(hexstr.length / 2);
for(var i = 0; i < hexstr.length/2; i++)
{
buf[i] = (parseInt(hexstr[i * 2], 16) << 4) + (parseInt(hexstr[i * 2 + 1], 16));
}
return buf;
}
// -------------------------------------------------------------------------------------------
// Attaching methods to this class
// -------------------------------------------------------------------------------------------
PNS.prototype.init = init;
PNS.prototype.connect = connect;
PNS.prototype.getNotifications = getNotifications;
// -------------------------------------------------------------------------------------------
// Exporting the class
// -------------------------------------------------------------------------------------------
module.exports.PNS = PNS;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment