Skip to content

Commit a821a7b

Browse files
committed
Add GCM client
1 parent 72fa1f2 commit a821a7b

File tree

3 files changed

+218
-1
lines changed

3 files changed

+218
-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': timeStamp.toString(),
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

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"mongodb": "~2.1.0",
2020
"multer": "^1.1.0",
2121
"parse": "^1.7.0",
22+
"randomstring": "^1.1.3",
23+
"node-gcm": "^0.14.0",
2224
"request": "^2.65.0"
2325
},
2426
"devDependencies": {
@@ -30,7 +32,7 @@
3032
},
3133
"scripts": {
3234
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
33-
"test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
35+
"test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
3436
"posttest": "mongodb-runner stop",
3537
"start": "./bin/parse-server"
3638
},

spec/GCM.spec.js

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

0 commit comments

Comments
 (0)