Skip to content

Commit

Permalink
Add GCM client
Browse files Browse the repository at this point in the history
  • Loading branch information
wangmengyan95 committed Feb 5, 2016
1 parent 7cc77c7 commit 872e690
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 3 deletions.
82 changes: 82 additions & 0 deletions GCM.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
var Parse = require('parse/node').Parse;
var gcm = require('node-gcm');
var randomstring = require('randomstring');

var GCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // GCM allows a max of 4 weeks
var GCMRegistrationTokensMax = 1000;

function GCM(apiKey) {
this.sender = new gcm.Sender(apiKey);
}

/**
* Send gcm request.
* @param {Object} data The data we need to send, the format is the same with api request body
* @param {Array} registrationTokens A array of registration tokens
* @returns {Object} A promise which is resolved after we get results from gcm
*/
GCM.prototype.send = function (data, registrationTokens) {
if (registrationTokens.length >= GCMRegistrationTokensMax) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
'Too many registration tokens for a GCM request.');
}
var pushId = randomstring.generate({
length: 10,
charset: 'alphanumeric'
});
var timeStamp = Date.now();
var expirationTime;
// We handle the expiration_time convertion in push.js, so expiration_time is a valid date
// in Unix epoch time in milliseconds here
if (data['expiration_time']) {
expirationTime = data['expiration_time'];
}
// Generate gcm payload
var gcmPayload = generateGCMPayload(data.data, pushId, timeStamp, expirationTime);
// Make and send gcm request
var message = new gcm.Message(gcmPayload);
var promise = new Parse.Promise();
this.sender.send(message, { registrationTokens: registrationTokens }, 5, function (error, response) {
// TODO: Use the response from gcm to generate and save push report
// TODO: If gcm returns some deviceTokens are invalid, set tombstone for the installation
promise.resolve();
});
return promise;
}

/**
* Generate the gcm payload from the data we get from api request.
* @param {Object} coreData The data field under api request body
* @param {String} pushId A random string
* @param {Number} timeStamp A number whose format is the Unix Epoch
* @param {Number|undefined} expirationTime A number whose format is the Unix Epoch or undefined
* @returns {Object} A promise which is resolved after we get results from gcm
*/
var generateGCMPayload = function(coreData, pushId, timeStamp, expirationTime) {
var payloadData = {
'time': new Date(timeStamp).toISOString(),
'push_id': pushId,
'data': JSON.stringify(coreData)
}
var payload = {
priority: 'normal',
data: payloadData
};
if (expirationTime) {
// The timeStamp and expiration is in milliseconds but gcm requires second
var timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
if (timeToLive < 0) {
timeToLive = 0;
}
if (timeToLive >= GCMTimeToLiveMax) {
timeToLive = GCMTimeToLiveMax;
}
payload.timeToLive = timeToLive;
}
return payload;
}

if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
GCM.generateGCMPayload = generateGCMPayload;
}
module.exports = GCM;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"mongodb": "~2.1.0",
"multer": "^1.1.0",
"parse": "^1.7.0",
"moment": "^2.11.1",
"randomstring": "^1.1.3",
"node-gcm": "^0.14.0",
"request": "^2.65.0"
},
"devDependencies": {
Expand Down
3 changes: 1 addition & 2 deletions push.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

var Parse = require('parse/node').Parse,
PromiseRouter = require('./PromiseRouter'),
rest = require('./rest'),
moment = require('moment');
rest = require('./rest');

var validPushTypes = ['ios', 'android'];

Expand Down
137 changes: 137 additions & 0 deletions spec/GCM.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
var GCM = require('../GCM');

describe('GCM', () => {
it('can generate GCM Payload without expiration time', (done) => {
//Mock request data
var data = {
'alert': 'alert'
};
var pushId = 1;
var timeStamp = 1454538822113;
var timeStampISOStr = new Date(timeStamp).toISOString();

var payload = GCM.generateGCMPayload(data, pushId, timeStamp);

expect(payload.priority).toEqual('normal');
expect(payload.timeToLive).toEqual(undefined);
var dataFromPayload = payload.data;
expect(dataFromPayload.time).toEqual(timeStampISOStr);
expect(dataFromPayload['push_id']).toEqual(pushId);
var dataFromUser = JSON.parse(dataFromPayload.data);
expect(dataFromUser).toEqual(data);
done();
});

it('can generate GCM Payload with valid expiration time', (done) => {
//Mock request data
var data = {
'alert': 'alert'
};
var pushId = 1;
var timeStamp = 1454538822113;
var timeStampISOStr = new Date(timeStamp).toISOString();
var expirationTime = 1454538922113

var payload = GCM.generateGCMPayload(data, pushId, timeStamp, expirationTime);

expect(payload.priority).toEqual('normal');
expect(payload.timeToLive).toEqual(Math.floor((expirationTime - timeStamp) / 1000));
var dataFromPayload = payload.data;
expect(dataFromPayload.time).toEqual(timeStampISOStr);
expect(dataFromPayload['push_id']).toEqual(pushId);
var dataFromUser = JSON.parse(dataFromPayload.data);
expect(dataFromUser).toEqual(data);
done();
});

it('can generate GCM Payload with too early expiration time', (done) => {
//Mock request data
var data = {
'alert': 'alert'
};
var pushId = 1;
var timeStamp = 1454538822113;
var timeStampISOStr = new Date(timeStamp).toISOString();
var expirationTime = 1454538822112;

var payload = GCM.generateGCMPayload(data, pushId, timeStamp, expirationTime);

expect(payload.priority).toEqual('normal');
expect(payload.timeToLive).toEqual(0);
var dataFromPayload = payload.data;
expect(dataFromPayload.time).toEqual(timeStampISOStr);
expect(dataFromPayload['push_id']).toEqual(pushId);
var dataFromUser = JSON.parse(dataFromPayload.data);
expect(dataFromUser).toEqual(data);
done();
});

it('can generate GCM Payload with too late expiration time', (done) => {
//Mock request data
var data = {
'alert': 'alert'
};
var pushId = 1;
var timeStamp = 1454538822113;
var timeStampISOStr = new Date(timeStamp).toISOString();
var expirationTime = 2454538822113;

var payload = GCM.generateGCMPayload(data, pushId, timeStamp, expirationTime);

expect(payload.priority).toEqual('normal');
// Four week in second
expect(payload.timeToLive).toEqual(4 * 7 * 24 * 60 * 60);
var dataFromPayload = payload.data;
expect(dataFromPayload.time).toEqual(timeStampISOStr);
expect(dataFromPayload['push_id']).toEqual(pushId);
var dataFromUser = JSON.parse(dataFromPayload.data);
expect(dataFromUser).toEqual(data);
done();
});

it('can send GCM request', (done) => {
var gcm = new GCM('apiKey');
// Mock gcm sender
var sender = {
send: jasmine.createSpy('send')
};
gcm.sender = sender;
// Mock data
var expirationTime = 2454538822113;
var data = {
'expiration_time': expirationTime,
'data': {
'alert': 'alert'
}
}
// Mock registrationTokens
var registrationTokens = ['token'];

var promise = gcm.send(data, registrationTokens);
expect(sender.send).toHaveBeenCalled();
var args = sender.send.calls.first().args;
// It is too hard to verify message of gcm library, we just verify tokens and retry times
expect(args[1].registrationTokens).toEqual(registrationTokens);
expect(args[2]).toEqual(5);
done();
});

it('can throw on sending when we have too many registration tokens', (done) => {
var gcm = new GCM('apiKey');
// Mock gcm sender
var sender = {
send: jasmine.createSpy('send')
};
gcm.sender = sender;
// Mock registrationTokens
var registrationTokens = [];
for (var i = 0; i <= 2000; i++) {
registrationTokens.push(i.toString());
}

expect(function() {
gcm.send({}, registrationTokens);
}).toThrow();
done();
});
});

0 comments on commit 872e690

Please sign in to comment.