Skip to content

Commit 354d328

Browse files
committed
Add GCM client
1 parent 7cc77c7 commit 354d328

File tree

3 files changed

+221
-1
lines changed

3 files changed

+221
-1
lines changed

GCM.js

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
var Parse = require('parse/node').Parse;
2+
var gcm = require('node-gcm');
3+
var randomstring = require('randomstring');
4+
5+
var GCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // GCM allows a max of 4 weeks
6+
var GCMRegistrationTokensMax = 1000;
7+
8+
function GCM(apiKey) {
9+
this.sender = new gcm.Sender(apiKey);
10+
}
11+
12+
/**
13+
* Send gcm request.
14+
* @param {Object} data The data we need to send, the format is the same with api request body
15+
* @param {Array} registrationTokens A array of registration tokens
16+
* @returns {Object} A promise which is resolved after we get results from gcm
17+
*/
18+
GCM.prototype.send = function (data, registrationTokens) {
19+
if (registrationTokens.length >= GCMRegistrationTokensMax) {
20+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
21+
'Too many registration tokens for a GCM request.');
22+
}
23+
var pushId = randomstring.generate({
24+
length: 10,
25+
charset: 'alphanumeric'
26+
});
27+
var timeStamp = Date.now();
28+
var expirationTime;
29+
// We handle the expiration_time convertion in push.js, so expiration_time is a valid date
30+
// in Unix epoch time in milliseconds here
31+
if (data['expiration_time']) {
32+
expirationTime = data['expiration_time'];
33+
}
34+
// Generate gcm payload
35+
var gcmPayload = generateGCMPayload(data.data, pushId, timeStamp, expirationTime);
36+
// Make and send gcm request
37+
var message = new gcm.Message(gcmPayload);
38+
var promise = new Parse.Promise();
39+
this.sender.send(message, { registrationTokens: registrationTokens }, 5, function (error, response) {
40+
// TODO: Use the response from gcm to generate and save push report
41+
// TODO: If gcm returns some deviceTokens are invalid, set tombstone for the installation
42+
promise.resolve();
43+
});
44+
return promise;
45+
}
46+
47+
/**
48+
* Generate the gcm payload from the data we get from api request.
49+
* @param {Object} coreData The data field under api request body
50+
* @param {String} pushId A random string
51+
* @param {Number} timeStamp A number whose format is the Unix Epoch
52+
* @param {Number|undefined} expirationTime A number whose format is the Unix Epoch or undefined
53+
* @returns {Object} A promise which is resolved after we get results from gcm
54+
*/
55+
var generateGCMPayload = function(coreData, pushId, timeStamp, expirationTime) {
56+
var payloadData = {
57+
'time': new Date(timeStamp).toISOString(),
58+
'push_id': pushId,
59+
'data': JSON.stringify(coreData)
60+
}
61+
var payload = {
62+
priority: 'normal',
63+
data: payloadData
64+
};
65+
if (expirationTime) {
66+
// The timeStamp and expiration is in milliseconds but gcm requires second
67+
var timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
68+
if (timeToLive < 0) {
69+
timeToLive = 0;
70+
}
71+
if (timeToLive >= GCMTimeToLiveMax) {
72+
timeToLive = GCMTimeToLiveMax;
73+
}
74+
payload.timeToLive = timeToLive;
75+
}
76+
return payload;
77+
}
78+
79+
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
80+
GCM.generateGCMPayload = generateGCMPayload;
81+
}
82+
module.exports = GCM;

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"mongodb": "~2.1.0",
2121
"multer": "^1.1.0",
2222
"parse": "^1.7.0",
23-
"moment": "^2.11.1",
23+
"randomstring": "^1.1.3",
24+
"node-gcm": "^0.14.0",
2425
"request": "^2.65.0"
2526
},
2627
"devDependencies": {

spec/GCM.spec.js

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
var GCM = require('../GCM');
2+
3+
describe('GCM', () => {
4+
it('can generate GCM Payload without expiration time', (done) => {
5+
//Mock request data
6+
var data = {
7+
'alert': 'alert'
8+
};
9+
var pushId = 1;
10+
var timeStamp = 1454538822113;
11+
var timeStampISOStr = new Date(timeStamp).toISOString();
12+
13+
var payload = GCM.generateGCMPayload(data, pushId, timeStamp);
14+
15+
expect(payload.priority).toEqual('normal');
16+
expect(payload.timeToLive).toEqual(undefined);
17+
var dataFromPayload = payload.data;
18+
expect(dataFromPayload.time).toEqual(timeStampISOStr);
19+
expect(dataFromPayload['push_id']).toEqual(pushId);
20+
var dataFromUser = JSON.parse(dataFromPayload.data);
21+
expect(dataFromUser).toEqual(data);
22+
done();
23+
});
24+
25+
it('can generate GCM Payload with valid expiration time', (done) => {
26+
//Mock request data
27+
var data = {
28+
'alert': 'alert'
29+
};
30+
var pushId = 1;
31+
var timeStamp = 1454538822113;
32+
var timeStampISOStr = new Date(timeStamp).toISOString();
33+
var expirationTime = 1454538922113
34+
35+
var payload = GCM.generateGCMPayload(data, pushId, timeStamp, expirationTime);
36+
37+
expect(payload.priority).toEqual('normal');
38+
expect(payload.timeToLive).toEqual(Math.floor((expirationTime - timeStamp) / 1000));
39+
var dataFromPayload = payload.data;
40+
expect(dataFromPayload.time).toEqual(timeStampISOStr);
41+
expect(dataFromPayload['push_id']).toEqual(pushId);
42+
var dataFromUser = JSON.parse(dataFromPayload.data);
43+
expect(dataFromUser).toEqual(data);
44+
done();
45+
});
46+
47+
it('can generate GCM Payload with too early expiration time', (done) => {
48+
//Mock request data
49+
var data = {
50+
'alert': 'alert'
51+
};
52+
var pushId = 1;
53+
var timeStamp = 1454538822113;
54+
var timeStampISOStr = new Date(timeStamp).toISOString();
55+
var expirationTime = 1454538822112;
56+
57+
var payload = GCM.generateGCMPayload(data, pushId, timeStamp, expirationTime);
58+
59+
expect(payload.priority).toEqual('normal');
60+
expect(payload.timeToLive).toEqual(0);
61+
var dataFromPayload = payload.data;
62+
expect(dataFromPayload.time).toEqual(timeStampISOStr);
63+
expect(dataFromPayload['push_id']).toEqual(pushId);
64+
var dataFromUser = JSON.parse(dataFromPayload.data);
65+
expect(dataFromUser).toEqual(data);
66+
done();
67+
});
68+
69+
it('can generate GCM Payload with too late expiration time', (done) => {
70+
//Mock request data
71+
var data = {
72+
'alert': 'alert'
73+
};
74+
var pushId = 1;
75+
var timeStamp = 1454538822113;
76+
var timeStampISOStr = new Date(timeStamp).toISOString();
77+
var expirationTime = 2454538822113;
78+
79+
var payload = GCM.generateGCMPayload(data, pushId, timeStamp, expirationTime);
80+
81+
expect(payload.priority).toEqual('normal');
82+
// Four week in second
83+
expect(payload.timeToLive).toEqual(4 * 7 * 24 * 60 * 60);
84+
var dataFromPayload = payload.data;
85+
expect(dataFromPayload.time).toEqual(timeStampISOStr);
86+
expect(dataFromPayload['push_id']).toEqual(pushId);
87+
var dataFromUser = JSON.parse(dataFromPayload.data);
88+
expect(dataFromUser).toEqual(data);
89+
done();
90+
});
91+
92+
it('can send GCM request', (done) => {
93+
var gcm = new GCM('apiKey');
94+
// Mock gcm sender
95+
var sender = {
96+
send: jasmine.createSpy('send')
97+
};
98+
gcm.sender = sender;
99+
// Mock data
100+
var expirationTime = 2454538822113;
101+
var data = {
102+
'expiration_time': expirationTime,
103+
'data': {
104+
'alert': 'alert'
105+
}
106+
}
107+
// Mock registrationTokens
108+
var registrationTokens = ['token'];
109+
110+
var promise = gcm.send(data, registrationTokens);
111+
expect(sender.send).toHaveBeenCalled();
112+
var args = sender.send.calls.first().args;
113+
// It is too hard to verify message of gcm library, we just verify tokens and retry times
114+
expect(args[1].registrationTokens).toEqual(registrationTokens);
115+
expect(args[2]).toEqual(5);
116+
done();
117+
});
118+
119+
it('can throw on sending when we have too many registration tokens', (done) => {
120+
var gcm = new GCM('apiKey');
121+
// Mock gcm sender
122+
var sender = {
123+
send: jasmine.createSpy('send')
124+
};
125+
gcm.sender = sender;
126+
// Mock registrationTokens
127+
var registrationTokens = [];
128+
for (var i = 0; i <= 2000; i++) {
129+
registrationTokens.push(i.toString());
130+
}
131+
132+
expect(function() {
133+
gcm.send({}, registrationTokens);
134+
}).toThrow();
135+
done();
136+
});
137+
});

0 commit comments

Comments
 (0)