-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7cc77c7
commit 872e690
Showing
4 changed files
with
222 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |