Merge pull request #1195 from ParsePlatform/flovilmart.moduleParseServerPush
Push adapters are provided by external packages
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
"parse": "^1.8.0",
|
"parse": "^1.8.0",
|
||||||
"parse-server-fs-adapter": "^1.0.0",
|
"parse-server-fs-adapter": "^1.0.0",
|
||||||
"parse-server-gcs-adapter": "^1.0.0",
|
"parse-server-gcs-adapter": "^1.0.0",
|
||||||
|
"parse-server-push-adapter": "^1.0.0",
|
||||||
"parse-server-s3-adapter": "^1.0.0",
|
"parse-server-s3-adapter": "^1.0.0",
|
||||||
"parse-server-simple-mailgun-adapter": "^1.0.0",
|
"parse-server-simple-mailgun-adapter": "^1.0.0",
|
||||||
"redis": "^2.5.0-1",
|
"redis": "^2.5.0-1",
|
||||||
|
|||||||
@@ -1,307 +0,0 @@
|
|||||||
var APNS = require('../src/APNS');
|
|
||||||
|
|
||||||
describe('APNS', () => {
|
|
||||||
|
|
||||||
it('can initialize with single cert', (done) => {
|
|
||||||
var args = {
|
|
||||||
cert: 'prodCert.pem',
|
|
||||||
key: 'prodKey.pem',
|
|
||||||
production: true,
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
}
|
|
||||||
var apns = new APNS(args);
|
|
||||||
|
|
||||||
expect(apns.conns.length).toBe(1);
|
|
||||||
var apnsConnection = apns.conns[0];
|
|
||||||
expect(apnsConnection.index).toBe(0);
|
|
||||||
expect(apnsConnection.bundleId).toBe(args.bundleId);
|
|
||||||
// TODO: Remove this checking onec we inject APNS
|
|
||||||
var prodApnsOptions = apnsConnection.options;
|
|
||||||
expect(prodApnsOptions.cert).toBe(args.cert);
|
|
||||||
expect(prodApnsOptions.key).toBe(args.key);
|
|
||||||
expect(prodApnsOptions.production).toBe(args.production);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can initialize with multiple certs', (done) => {
|
|
||||||
var args = [
|
|
||||||
{
|
|
||||||
cert: 'devCert.pem',
|
|
||||||
key: 'devKey.pem',
|
|
||||||
production: false,
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cert: 'prodCert.pem',
|
|
||||||
key: 'prodKey.pem',
|
|
||||||
production: true,
|
|
||||||
bundleId: 'bundleIdAgain'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
var apns = new APNS(args);
|
|
||||||
expect(apns.conns.length).toBe(2);
|
|
||||||
var devApnsConnection = apns.conns[1];
|
|
||||||
expect(devApnsConnection.index).toBe(1);
|
|
||||||
var devApnsOptions = devApnsConnection.options;
|
|
||||||
expect(devApnsOptions.cert).toBe(args[0].cert);
|
|
||||||
expect(devApnsOptions.key).toBe(args[0].key);
|
|
||||||
expect(devApnsOptions.production).toBe(args[0].production);
|
|
||||||
expect(devApnsConnection.bundleId).toBe(args[0].bundleId);
|
|
||||||
|
|
||||||
var prodApnsConnection = apns.conns[0];
|
|
||||||
expect(prodApnsConnection.index).toBe(0);
|
|
||||||
// TODO: Remove this checking onec we inject APNS
|
|
||||||
var prodApnsOptions = prodApnsConnection.options;
|
|
||||||
expect(prodApnsOptions.cert).toBe(args[1].cert);
|
|
||||||
expect(prodApnsOptions.key).toBe(args[1].key);
|
|
||||||
expect(prodApnsOptions.production).toBe(args[1].production);
|
|
||||||
expect(prodApnsOptions.bundleId).toBe(args[1].bundleId);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can generate APNS notification', (done) => {
|
|
||||||
//Mock request data
|
|
||||||
var data = {
|
|
||||||
'alert': 'alert',
|
|
||||||
'badge': 100,
|
|
||||||
'sound': 'test',
|
|
||||||
'content-available': 1,
|
|
||||||
'category': 'INVITE_CATEGORY',
|
|
||||||
'key': 'value',
|
|
||||||
'keyAgain': 'valueAgain'
|
|
||||||
};
|
|
||||||
var expirationTime = 1454571491354
|
|
||||||
|
|
||||||
var notification = APNS.generateNotification(data, expirationTime);
|
|
||||||
|
|
||||||
expect(notification.alert).toEqual(data.alert);
|
|
||||||
expect(notification.badge).toEqual(data.badge);
|
|
||||||
expect(notification.sound).toEqual(data.sound);
|
|
||||||
expect(notification.contentAvailable).toEqual(1);
|
|
||||||
expect(notification.category).toEqual(data.category);
|
|
||||||
expect(notification.payload).toEqual({
|
|
||||||
'key': 'value',
|
|
||||||
'keyAgain': 'valueAgain'
|
|
||||||
});
|
|
||||||
expect(notification.expiry).toEqual(expirationTime);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can choose conns for device without appIdentifier', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [
|
|
||||||
{
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bundleId: 'bundleIdAgain'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Mock device
|
|
||||||
var device = {};
|
|
||||||
|
|
||||||
var qualifiedConns = APNS.chooseConns(conns, device);
|
|
||||||
expect(qualifiedConns).toEqual([0, 1]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can choose conns for device with valid appIdentifier', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [
|
|
||||||
{
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bundleId: 'bundleIdAgain'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Mock device
|
|
||||||
var device = {
|
|
||||||
appIdentifier: 'bundleId'
|
|
||||||
};
|
|
||||||
|
|
||||||
var qualifiedConns = APNS.chooseConns(conns, device);
|
|
||||||
expect(qualifiedConns).toEqual([0]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can choose conns for device with invalid appIdentifier', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [
|
|
||||||
{
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bundleId: 'bundleIdAgain'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Mock device
|
|
||||||
var device = {
|
|
||||||
appIdentifier: 'invalid'
|
|
||||||
};
|
|
||||||
|
|
||||||
var qualifiedConns = APNS.chooseConns(conns, device);
|
|
||||||
expect(qualifiedConns).toEqual([]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle transmission error when notification is not in cache or device is missing', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [];
|
|
||||||
var errorCode = 1;
|
|
||||||
var notification = undefined;
|
|
||||||
var device = {};
|
|
||||||
|
|
||||||
APNS.handleTransmissionError(conns, errorCode, notification, device);
|
|
||||||
|
|
||||||
var notification = {};
|
|
||||||
var device = undefined;
|
|
||||||
|
|
||||||
APNS.handleTransmissionError(conns, errorCode, notification, device);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle transmission error when there are other qualified conns', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId2'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
var errorCode = 1;
|
|
||||||
var notification = {};
|
|
||||||
var apnDevice = {
|
|
||||||
connIndex: 0,
|
|
||||||
appIdentifier: 'bundleId1'
|
|
||||||
};
|
|
||||||
|
|
||||||
APNS.handleTransmissionError(conns, errorCode, notification, apnDevice);
|
|
||||||
|
|
||||||
expect(conns[0].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[1].pushNotification).toHaveBeenCalled();
|
|
||||||
expect(conns[2].pushNotification).not.toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle transmission error when there is no other qualified conns', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
var errorCode = 1;
|
|
||||||
var notification = {};
|
|
||||||
var apnDevice = {
|
|
||||||
connIndex: 2,
|
|
||||||
appIdentifier: 'bundleId1'
|
|
||||||
};
|
|
||||||
|
|
||||||
APNS.handleTransmissionError(conns, errorCode, notification, apnDevice);
|
|
||||||
|
|
||||||
expect(conns[0].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[1].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[2].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[3].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[4].pushNotification).toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle transmission error when device has no appIdentifier', (done) => {
|
|
||||||
// Mock conns
|
|
||||||
var conns = [
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pushNotification: jasmine.createSpy('pushNotification'),
|
|
||||||
bundleId: 'bundleId3'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
var errorCode = 1;
|
|
||||||
var notification = {};
|
|
||||||
var apnDevice = {
|
|
||||||
connIndex: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
APNS.handleTransmissionError(conns, errorCode, notification, apnDevice);
|
|
||||||
|
|
||||||
expect(conns[0].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[1].pushNotification).not.toHaveBeenCalled();
|
|
||||||
expect(conns[2].pushNotification).toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can send APNS notification', (done) => {
|
|
||||||
var args = {
|
|
||||||
cert: 'prodCert.pem',
|
|
||||||
key: 'prodKey.pem',
|
|
||||||
production: true,
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
}
|
|
||||||
var apns = new APNS(args);
|
|
||||||
var conn = {
|
|
||||||
pushNotification: jasmine.createSpy('send'),
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
};
|
|
||||||
apns.conns = [ conn ];
|
|
||||||
// Mock data
|
|
||||||
var expirationTime = 1454571491354
|
|
||||||
var data = {
|
|
||||||
'expiration_time': expirationTime,
|
|
||||||
'data': {
|
|
||||||
'alert': 'alert'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Mock devices
|
|
||||||
var devices = [
|
|
||||||
{
|
|
||||||
deviceToken: '112233',
|
|
||||||
appIdentifier: 'bundleId'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
var promise = apns.send(data, devices);
|
|
||||||
expect(conn.pushNotification).toHaveBeenCalled();
|
|
||||||
var args = conn.pushNotification.calls.first().args;
|
|
||||||
var notification = args[0];
|
|
||||||
expect(notification.alert).toEqual(data.data.alert);
|
|
||||||
expect(notification.expiry).toEqual(data['expiration_time']);
|
|
||||||
var apnDevice = args[1]
|
|
||||||
expect(apnDevice.connIndex).toEqual(0);
|
|
||||||
expect(apnDevice.appIdentifier).toEqual('bundleId');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -3,7 +3,7 @@ var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
|||||||
var FilesAdapter = require("parse-server-fs-adapter").default;
|
var FilesAdapter = require("parse-server-fs-adapter").default;
|
||||||
var S3Adapter = require("parse-server-s3-adapter").default;
|
var S3Adapter = require("parse-server-s3-adapter").default;
|
||||||
var GCSAdapter = require("parse-server-gcs-adapter").default;
|
var GCSAdapter = require("parse-server-gcs-adapter").default;
|
||||||
var ParsePushAdapter = require("../src/Adapters/Push/ParsePushAdapter");
|
var ParsePushAdapter = require("parse-server-push-adapter").default;
|
||||||
|
|
||||||
describe("AdapterLoader", ()=>{
|
describe("AdapterLoader", ()=>{
|
||||||
|
|
||||||
|
|||||||
199
spec/GCM.spec.js
199
spec/GCM.spec.js
@@ -1,199 +0,0 @@
|
|||||||
var GCM = require('../src/GCM');
|
|
||||||
|
|
||||||
describe('GCM', () => {
|
|
||||||
it('can initialize', (done) => {
|
|
||||||
var args = {
|
|
||||||
apiKey: 'apiKey'
|
|
||||||
};
|
|
||||||
var gcm = new GCM(args);
|
|
||||||
expect(gcm.sender.key).toBe(args.apiKey);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can throw on initializing with invalid args', (done) => {
|
|
||||||
var args = 123
|
|
||||||
expect(function() {
|
|
||||||
new GCM(args);
|
|
||||||
}).toThrow();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can generate GCM Payload without expiration time', (done) => {
|
|
||||||
//Mock request data
|
|
||||||
var data = {
|
|
||||||
'alert': 'alert'
|
|
||||||
};
|
|
||||||
var pushId = 'pushId';
|
|
||||||
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 = 'pushId';
|
|
||||||
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 = 'pushId';
|
|
||||||
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 = 'pushId';
|
|
||||||
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: '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 devices
|
|
||||||
var devices = [
|
|
||||||
{
|
|
||||||
deviceToken: 'token'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
gcm.send(data, devices);
|
|
||||||
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(['token']);
|
|
||||||
expect(args[2]).toEqual(5);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can send GCM request', (done) => {
|
|
||||||
var gcm = new GCM({
|
|
||||||
apiKey: 'apiKey'
|
|
||||||
});
|
|
||||||
// Mock data
|
|
||||||
var expirationTime = 2454538822113;
|
|
||||||
var data = {
|
|
||||||
'expiration_time': expirationTime,
|
|
||||||
'data': {
|
|
||||||
'alert': 'alert'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Mock devices
|
|
||||||
var devices = [
|
|
||||||
{
|
|
||||||
deviceToken: 'token'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceToken: 'token2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceToken: 'token3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceToken: 'token4'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
gcm.send(data, devices).then((response) => {
|
|
||||||
expect(Array.isArray(response)).toBe(true);
|
|
||||||
expect(response.length).toEqual(devices.length);
|
|
||||||
expect(response.length).toEqual(4);
|
|
||||||
response.forEach((res, index) => {
|
|
||||||
expect(res.transmitted).toEqual(false);
|
|
||||||
expect(res.device).toEqual(devices[index]);
|
|
||||||
})
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can slice devices', (done) => {
|
|
||||||
// Mock devices
|
|
||||||
var devices = [makeDevice(1), makeDevice(2), makeDevice(3), makeDevice(4)];
|
|
||||||
|
|
||||||
var chunkDevices = GCM.sliceDevices(devices, 3);
|
|
||||||
expect(chunkDevices).toEqual([
|
|
||||||
[makeDevice(1), makeDevice(2), makeDevice(3)],
|
|
||||||
[makeDevice(4)]
|
|
||||||
]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeDevice(deviceToken) {
|
|
||||||
return {
|
|
||||||
deviceToken: deviceToken
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var OneSignalPushAdapter = require('../src/Adapters/Push/OneSignalPushAdapter');
|
|
||||||
var classifyInstallations = require('../src/Adapters/Push/PushAdapterUtils').classifyInstallations;
|
|
||||||
|
|
||||||
// Make mock config
|
|
||||||
var pushConfig = {
|
|
||||||
oneSignalAppId:"APP ID",
|
|
||||||
oneSignalApiKey:"API KEY"
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('OneSignalPushAdapter', () => {
|
|
||||||
it('can be initialized', (done) => {
|
|
||||||
|
|
||||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
|
||||||
|
|
||||||
var senderMap = oneSignalPushAdapter.senderMap;
|
|
||||||
|
|
||||||
expect(senderMap.ios instanceof Function).toBe(true);
|
|
||||||
expect(senderMap.android instanceof Function).toBe(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cannot be initialized if options are missing', (done) => {
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
new OneSignalPushAdapter();
|
|
||||||
}).toThrow("Trying to initialize OneSignalPushAdapter without oneSignalAppId or oneSignalApiKey");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can get valid push types', (done) => {
|
|
||||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
|
||||||
|
|
||||||
expect(oneSignalPushAdapter.getValidPushTypes()).toEqual(['ios', 'android']);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can classify installation', (done) => {
|
|
||||||
// Mock installations
|
|
||||||
var validPushTypes = ['ios', 'android'];
|
|
||||||
var installations = [
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: 'androidToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'ios',
|
|
||||||
deviceToken: 'iosToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'win',
|
|
||||||
deviceToken: 'winToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: undefined
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
var deviceMap = OneSignalPushAdapter.classifyInstallations(installations, validPushTypes);
|
|
||||||
expect(deviceMap['android']).toEqual([makeDevice('androidToken')]);
|
|
||||||
expect(deviceMap['ios']).toEqual([makeDevice('iosToken')]);
|
|
||||||
expect(deviceMap['win']).toBe(undefined);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('can send push notifications', (done) => {
|
|
||||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
|
||||||
|
|
||||||
// Mock android ios senders
|
|
||||||
var androidSender = jasmine.createSpy('send')
|
|
||||||
var iosSender = jasmine.createSpy('send')
|
|
||||||
|
|
||||||
var senderMap = {
|
|
||||||
ios: iosSender,
|
|
||||||
android: androidSender
|
|
||||||
};
|
|
||||||
oneSignalPushAdapter.senderMap = senderMap;
|
|
||||||
|
|
||||||
// Mock installations
|
|
||||||
var installations = [
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: 'androidToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'ios',
|
|
||||||
deviceToken: 'iosToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'win',
|
|
||||||
deviceToken: 'winToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: undefined
|
|
||||||
}
|
|
||||||
];
|
|
||||||
var data = {};
|
|
||||||
|
|
||||||
oneSignalPushAdapter.send(data, installations);
|
|
||||||
// Check android sender
|
|
||||||
expect(androidSender).toHaveBeenCalled();
|
|
||||||
var args = androidSender.calls.first().args;
|
|
||||||
expect(args[0]).toEqual(data);
|
|
||||||
expect(args[1]).toEqual([
|
|
||||||
makeDevice('androidToken')
|
|
||||||
]);
|
|
||||||
// Check ios sender
|
|
||||||
expect(iosSender).toHaveBeenCalled();
|
|
||||||
args = iosSender.calls.first().args;
|
|
||||||
expect(args[0]).toEqual(data);
|
|
||||||
expect(args[1]).toEqual([
|
|
||||||
makeDevice('iosToken')
|
|
||||||
]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can send iOS notifications", (done) => {
|
|
||||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
|
||||||
var sendToOneSignal = jasmine.createSpy('sendToOneSignal');
|
|
||||||
oneSignalPushAdapter.sendToOneSignal = sendToOneSignal;
|
|
||||||
|
|
||||||
oneSignalPushAdapter.sendToAPNS({'data':{
|
|
||||||
'badge': 1,
|
|
||||||
'alert': "Example content",
|
|
||||||
'sound': "Example sound",
|
|
||||||
'content-available': 1,
|
|
||||||
'misc-data': 'Example Data'
|
|
||||||
}},[{'deviceToken':'iosToken1'},{'deviceToken':'iosToken2'}])
|
|
||||||
|
|
||||||
expect(sendToOneSignal).toHaveBeenCalled();
|
|
||||||
var args = sendToOneSignal.calls.first().args;
|
|
||||||
expect(args[0]).toEqual({
|
|
||||||
'ios_badgeType':'SetTo',
|
|
||||||
'ios_badgeCount':1,
|
|
||||||
'contents': { 'en':'Example content'},
|
|
||||||
'ios_sound': 'Example sound',
|
|
||||||
'content_available':true,
|
|
||||||
'data':{'misc-data':'Example Data'},
|
|
||||||
'include_ios_tokens':['iosToken1','iosToken2']
|
|
||||||
})
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can send Android notifications", (done) => {
|
|
||||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
|
||||||
var sendToOneSignal = jasmine.createSpy('sendToOneSignal');
|
|
||||||
oneSignalPushAdapter.sendToOneSignal = sendToOneSignal;
|
|
||||||
|
|
||||||
oneSignalPushAdapter.sendToGCM({'data':{
|
|
||||||
'title': 'Example title',
|
|
||||||
'alert': 'Example content',
|
|
||||||
'misc-data': 'Example Data'
|
|
||||||
}},[{'deviceToken':'androidToken1'},{'deviceToken':'androidToken2'}])
|
|
||||||
|
|
||||||
expect(sendToOneSignal).toHaveBeenCalled();
|
|
||||||
var args = sendToOneSignal.calls.first().args;
|
|
||||||
expect(args[0]).toEqual({
|
|
||||||
'contents': { 'en':'Example content'},
|
|
||||||
'title': {'en':'Example title'},
|
|
||||||
'data':{'misc-data':'Example Data'},
|
|
||||||
'include_android_reg_ids': ['androidToken1','androidToken2']
|
|
||||||
})
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can post the correct data", (done) => {
|
|
||||||
|
|
||||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
|
||||||
|
|
||||||
var write = jasmine.createSpy('write');
|
|
||||||
oneSignalPushAdapter.https = {
|
|
||||||
'request': function(a,b) {
|
|
||||||
return {
|
|
||||||
'end':function(){},
|
|
||||||
'on':function(a,b){},
|
|
||||||
'write':write
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var installations = [
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: 'androidToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'ios',
|
|
||||||
deviceToken: 'iosToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'win',
|
|
||||||
deviceToken: 'winToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: undefined
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
oneSignalPushAdapter.send({'data':{
|
|
||||||
'title': 'Example title',
|
|
||||||
'alert': 'Example content',
|
|
||||||
'content-available':1,
|
|
||||||
'misc-data': 'Example Data'
|
|
||||||
}}, installations);
|
|
||||||
|
|
||||||
expect(write).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// iOS
|
|
||||||
let args = write.calls.first().args;
|
|
||||||
expect(args[0]).toEqual(JSON.stringify({
|
|
||||||
'contents': { 'en':'Example content'},
|
|
||||||
'content_available':true,
|
|
||||||
'data':{'title':'Example title','misc-data':'Example Data'},
|
|
||||||
'include_ios_tokens':['iosToken'],
|
|
||||||
'app_id':'APP ID'
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Android
|
|
||||||
args = write.calls.mostRecent().args;
|
|
||||||
expect(args[0]).toEqual(JSON.stringify({
|
|
||||||
'contents': { 'en':'Example content'},
|
|
||||||
'title': {'en':'Example title'},
|
|
||||||
'data':{"content-available":1,'misc-data':'Example Data'},
|
|
||||||
'include_android_reg_ids':['androidToken'],
|
|
||||||
'app_id':'APP ID'
|
|
||||||
}));
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeDevice(deviceToken, appIdentifier) {
|
|
||||||
return {
|
|
||||||
deviceToken: deviceToken,
|
|
||||||
appIdentifier: appIdentifier
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
var ParsePushAdapter = require('../src/Adapters/Push/ParsePushAdapter');
|
|
||||||
var APNS = require('../src/APNS');
|
|
||||||
var GCM = require('../src/GCM');
|
|
||||||
|
|
||||||
describe('ParsePushAdapter', () => {
|
|
||||||
it('can be initialized', (done) => {
|
|
||||||
// Make mock config
|
|
||||||
var pushConfig = {
|
|
||||||
android: {
|
|
||||||
senderId: 'senderId',
|
|
||||||
apiKey: 'apiKey'
|
|
||||||
},
|
|
||||||
ios: [
|
|
||||||
{
|
|
||||||
cert: 'prodCert.pem',
|
|
||||||
key: 'prodKey.pem',
|
|
||||||
production: true,
|
|
||||||
bundleId: 'bundleId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cert: 'devCert.pem',
|
|
||||||
key: 'devKey.pem',
|
|
||||||
production: false,
|
|
||||||
bundleId: 'bundleIdAgain'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
var parsePushAdapter = new ParsePushAdapter(pushConfig);
|
|
||||||
// Check ios
|
|
||||||
var iosSender = parsePushAdapter.senderMap['ios'];
|
|
||||||
expect(iosSender instanceof APNS).toBe(true);
|
|
||||||
// Check android
|
|
||||||
var androidSender = parsePushAdapter.senderMap['android'];
|
|
||||||
expect(androidSender instanceof GCM).toBe(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can throw on initializing with unsupported push type', (done) => {
|
|
||||||
// Make mock config
|
|
||||||
var pushConfig = {
|
|
||||||
win: {
|
|
||||||
senderId: 'senderId',
|
|
||||||
apiKey: 'apiKey'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(function() {
|
|
||||||
new ParsePushAdapter(pushConfig);
|
|
||||||
}).toThrow();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can get valid push types', (done) => {
|
|
||||||
var parsePushAdapter = new ParsePushAdapter();
|
|
||||||
|
|
||||||
expect(parsePushAdapter.getValidPushTypes()).toEqual(['ios', 'android']);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can classify installation', (done) => {
|
|
||||||
// Mock installations
|
|
||||||
var validPushTypes = ['ios', 'android'];
|
|
||||||
var installations = [
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: 'androidToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'ios',
|
|
||||||
deviceToken: 'iosToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'win',
|
|
||||||
deviceToken: 'winToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: undefined
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
var deviceMap = ParsePushAdapter.classifyInstallations(installations, validPushTypes);
|
|
||||||
expect(deviceMap['android']).toEqual([makeDevice('androidToken')]);
|
|
||||||
expect(deviceMap['ios']).toEqual([makeDevice('iosToken')]);
|
|
||||||
expect(deviceMap['win']).toBe(undefined);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('can send push notifications', (done) => {
|
|
||||||
var parsePushAdapter = new ParsePushAdapter();
|
|
||||||
// Mock android ios senders
|
|
||||||
var androidSender = {
|
|
||||||
send: jasmine.createSpy('send')
|
|
||||||
};
|
|
||||||
var iosSender = {
|
|
||||||
send: jasmine.createSpy('send')
|
|
||||||
};
|
|
||||||
var senderMap = {
|
|
||||||
ios: iosSender,
|
|
||||||
android: androidSender
|
|
||||||
};
|
|
||||||
parsePushAdapter.senderMap = senderMap;
|
|
||||||
// Mock installations
|
|
||||||
var installations = [
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: 'androidToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'ios',
|
|
||||||
deviceToken: 'iosToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'win',
|
|
||||||
deviceToken: 'winToken'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceType: 'android',
|
|
||||||
deviceToken: undefined
|
|
||||||
}
|
|
||||||
];
|
|
||||||
var data = {};
|
|
||||||
|
|
||||||
parsePushAdapter.send(data, installations);
|
|
||||||
// Check android sender
|
|
||||||
expect(androidSender.send).toHaveBeenCalled();
|
|
||||||
var args = androidSender.send.calls.first().args;
|
|
||||||
expect(args[0]).toEqual(data);
|
|
||||||
expect(args[1]).toEqual([
|
|
||||||
makeDevice('androidToken')
|
|
||||||
]);
|
|
||||||
// Check ios sender
|
|
||||||
expect(iosSender.send).toHaveBeenCalled();
|
|
||||||
args = iosSender.send.calls.first().args;
|
|
||||||
expect(args[0]).toEqual(data);
|
|
||||||
expect(args[1]).toEqual([
|
|
||||||
makeDevice('iosToken')
|
|
||||||
]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeDevice(deviceToken, appIdentifier) {
|
|
||||||
return {
|
|
||||||
deviceToken: deviceToken,
|
|
||||||
appIdentifier: appIdentifier
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
227
src/APNS.js
227
src/APNS.js
@@ -1,227 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Parse = require('parse/node').Parse;
|
|
||||||
// TODO: apn does not support the new HTTP/2 protocal. It is fine to use it in V1,
|
|
||||||
// but probably we will replace it in the future.
|
|
||||||
const apn = require('apn');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new connection to the APN service.
|
|
||||||
* @constructor
|
|
||||||
* @param {Object|Array} args An argument or a list of arguments to config APNS connection
|
|
||||||
* @param {String} args.cert The filename of the connection certificate to load from disk
|
|
||||||
* @param {String} args.key The filename of the connection key to load from disk
|
|
||||||
* @param {String} args.pfx The filename for private key, certificate and CA certs in PFX or PKCS12 format, it will overwrite cert and key
|
|
||||||
* @param {String} args.passphrase The passphrase for the connection key, if required
|
|
||||||
* @param {String} args.bundleId The bundleId for cert
|
|
||||||
* @param {Boolean} args.production Specifies which environment to connect to: Production (if true) or Sandbox
|
|
||||||
*/
|
|
||||||
function APNS(args) {
|
|
||||||
// Since for ios, there maybe multiple cert/key pairs,
|
|
||||||
// typePushConfig can be an array.
|
|
||||||
let apnsArgsList = [];
|
|
||||||
if (Array.isArray(args)) {
|
|
||||||
apnsArgsList = apnsArgsList.concat(args);
|
|
||||||
} else if (typeof args === 'object') {
|
|
||||||
apnsArgsList.push(args);
|
|
||||||
} else {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
'APNS Configuration is invalid');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conns = [];
|
|
||||||
for (let apnsArgs of apnsArgsList) {
|
|
||||||
let conn = new apn.Connection(apnsArgs);
|
|
||||||
if (!apnsArgs.bundleId) {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
'BundleId is mssing for %j', apnsArgs);
|
|
||||||
}
|
|
||||||
conn.bundleId = apnsArgs.bundleId;
|
|
||||||
// Set the priority of the conns, prod cert has higher priority
|
|
||||||
if (apnsArgs.production) {
|
|
||||||
conn.priority = 0;
|
|
||||||
} else {
|
|
||||||
conn.priority = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set apns client callbacks
|
|
||||||
conn.on('connected', () => {
|
|
||||||
console.log('APNS Connection %d Connected', conn.index);
|
|
||||||
});
|
|
||||||
|
|
||||||
conn.on('transmissionError', (errCode, notification, apnDevice) => {
|
|
||||||
handleTransmissionError(this.conns, errCode, notification, apnDevice);
|
|
||||||
});
|
|
||||||
|
|
||||||
conn.on('timeout', () => {
|
|
||||||
console.log('APNS Connection %d Timeout', conn.index);
|
|
||||||
});
|
|
||||||
|
|
||||||
conn.on('disconnected', () => {
|
|
||||||
console.log('APNS Connection %d Disconnected', conn.index);
|
|
||||||
});
|
|
||||||
|
|
||||||
conn.on('socketError', () => {
|
|
||||||
console.log('APNS Connection %d Socket Error', conn.index);
|
|
||||||
});
|
|
||||||
|
|
||||||
conn.on('transmitted', function(notification, device) {
|
|
||||||
if (device.callback) {
|
|
||||||
device.callback({
|
|
||||||
notification: notification,
|
|
||||||
transmitted: true,
|
|
||||||
device: device
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log('APNS Connection %d Notification transmitted to %s', conn.index, device.token.toString('hex'));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.conns.push(conn);
|
|
||||||
}
|
|
||||||
// Sort the conn based on priority ascending, high pri first
|
|
||||||
this.conns.sort((s1, s2) => {
|
|
||||||
return s1.priority - s2.priority;
|
|
||||||
});
|
|
||||||
// Set index of conns
|
|
||||||
for (let index = 0; index < this.conns.length; index++) {
|
|
||||||
this.conns[index].index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send apns request.
|
|
||||||
* @param {Object} data The data we need to send, the format is the same with api request body
|
|
||||||
* @param {Array} devices A array of devices
|
|
||||||
* @returns {Object} A promise which is resolved immediately
|
|
||||||
*/
|
|
||||||
APNS.prototype.send = function(data, devices) {
|
|
||||||
let coreData = data.data;
|
|
||||||
let expirationTime = data['expiration_time'];
|
|
||||||
let notification = generateNotification(coreData, expirationTime);
|
|
||||||
|
|
||||||
let promises = devices.map((device) => {
|
|
||||||
let qualifiedConnIndexs = chooseConns(this.conns, device);
|
|
||||||
// We can not find a valid conn, just ignore this device
|
|
||||||
if (qualifiedConnIndexs.length == 0) {
|
|
||||||
return Promise.resolve({
|
|
||||||
transmitted: false,
|
|
||||||
result: {error: 'No connection available'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let conn = this.conns[qualifiedConnIndexs[0]];
|
|
||||||
let apnDevice = new apn.Device(device.deviceToken);
|
|
||||||
apnDevice.connIndex = qualifiedConnIndexs[0];
|
|
||||||
// Add additional appIdentifier info to apn device instance
|
|
||||||
if (device.appIdentifier) {
|
|
||||||
apnDevice.appIdentifier = device.appIdentifier;
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
apnDevice.callback = resolve;
|
|
||||||
conn.pushNotification(notification, apnDevice);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return Parse.Promise.when(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTransmissionError(conns, errCode, notification, apnDevice) {
|
|
||||||
// This means the error notification is not in the cache anymore or the recepient is missing,
|
|
||||||
// we just ignore this case
|
|
||||||
if (!notification || !apnDevice) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If currentConn can not send the push notification, we try to use the next available conn.
|
|
||||||
// Since conns is sorted by priority, the next conn means the next low pri conn.
|
|
||||||
// If there is no conn available, we give up on sending the notification to that device.
|
|
||||||
let qualifiedConnIndexs = chooseConns(conns, apnDevice);
|
|
||||||
let currentConnIndex = apnDevice.connIndex;
|
|
||||||
|
|
||||||
let newConnIndex = -1;
|
|
||||||
// Find the next element of currentConnIndex in qualifiedConnIndexs
|
|
||||||
for (let index = 0; index < qualifiedConnIndexs.length - 1; index++) {
|
|
||||||
if (qualifiedConnIndexs[index] === currentConnIndex) {
|
|
||||||
newConnIndex = qualifiedConnIndexs[index + 1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// There is no more available conns, we give up in this case
|
|
||||||
if (newConnIndex < 0 || newConnIndex >= conns.length) {
|
|
||||||
if (apnDevice.callback) {
|
|
||||||
apnDevice.callback({
|
|
||||||
response: {error: `APNS can not find vaild connection for ${apnDevice.token}`, code: errCode},
|
|
||||||
status: errCode,
|
|
||||||
transmitted: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newConn = conns[newConnIndex];
|
|
||||||
// Update device conn info
|
|
||||||
apnDevice.connIndex = newConnIndex;
|
|
||||||
// Use the new conn to send the notification
|
|
||||||
newConn.pushNotification(notification, apnDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
function chooseConns(conns, device) {
|
|
||||||
// If device does not have appIdentifier, all conns maybe proper connections.
|
|
||||||
// Otherwise we try to match the appIdentifier with bundleId
|
|
||||||
let qualifiedConns = [];
|
|
||||||
for (let index = 0; index < conns.length; index++) {
|
|
||||||
let conn = conns[index];
|
|
||||||
// If the device we need to send to does not have
|
|
||||||
// appIdentifier, any conn could be a qualified connection
|
|
||||||
if (!device.appIdentifier || device.appIdentifier === '') {
|
|
||||||
qualifiedConns.push(index);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (device.appIdentifier === conn.bundleId) {
|
|
||||||
qualifiedConns.push(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return qualifiedConns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the apns notification from the data we get from api request.
|
|
||||||
* @param {Object} coreData The data field under api request body
|
|
||||||
* @returns {Object} A apns notification
|
|
||||||
*/
|
|
||||||
function generateNotification(coreData, expirationTime) {
|
|
||||||
let notification = new apn.notification();
|
|
||||||
let payload = {};
|
|
||||||
for (let key in coreData) {
|
|
||||||
switch (key) {
|
|
||||||
case 'alert':
|
|
||||||
notification.setAlertText(coreData.alert);
|
|
||||||
break;
|
|
||||||
case 'badge':
|
|
||||||
notification.badge = coreData.badge;
|
|
||||||
break;
|
|
||||||
case 'sound':
|
|
||||||
notification.sound = coreData.sound;
|
|
||||||
break;
|
|
||||||
case 'content-available':
|
|
||||||
notification.setNewsstandAvailable(true);
|
|
||||||
let isAvailable = coreData['content-available'] === 1;
|
|
||||||
notification.setContentAvailable(isAvailable);
|
|
||||||
break;
|
|
||||||
case 'category':
|
|
||||||
notification.category = coreData.category;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
payload[key] = coreData[key];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notification.payload = payload;
|
|
||||||
notification.expiry = expirationTime;
|
|
||||||
return notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
|
||||||
APNS.generateNotification = generateNotification;
|
|
||||||
APNS.chooseConns = chooseConns;
|
|
||||||
APNS.handleTransmissionError = handleTransmissionError;
|
|
||||||
}
|
|
||||||
module.exports = APNS;
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
// ParsePushAdapter is the default implementation of
|
|
||||||
// PushAdapter, it uses GCM for android push and APNS
|
|
||||||
// for ios push.
|
|
||||||
|
|
||||||
import { classifyInstallations } from './PushAdapterUtils';
|
|
||||||
|
|
||||||
const Parse = require('parse/node').Parse;
|
|
||||||
var deepcopy = require('deepcopy');
|
|
||||||
import PushAdapter from './PushAdapter';
|
|
||||||
|
|
||||||
export class OneSignalPushAdapter extends PushAdapter {
|
|
||||||
|
|
||||||
constructor(pushConfig = {}) {
|
|
||||||
super(pushConfig);
|
|
||||||
this.https = require('https');
|
|
||||||
|
|
||||||
this.validPushTypes = ['ios', 'android'];
|
|
||||||
this.senderMap = {};
|
|
||||||
this.OneSignalConfig = {};
|
|
||||||
const { oneSignalAppId, oneSignalApiKey } = pushConfig;
|
|
||||||
if (!oneSignalAppId || !oneSignalApiKey) {
|
|
||||||
throw "Trying to initialize OneSignalPushAdapter without oneSignalAppId or oneSignalApiKey";
|
|
||||||
}
|
|
||||||
this.OneSignalConfig['appId'] = pushConfig['oneSignalAppId'];
|
|
||||||
this.OneSignalConfig['apiKey'] = pushConfig['oneSignalApiKey'];
|
|
||||||
|
|
||||||
this.senderMap['ios'] = this.sendToAPNS.bind(this);
|
|
||||||
this.senderMap['android'] = this.sendToGCM.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
send(data, installations) {
|
|
||||||
let deviceMap = classifyInstallations(installations, this.validPushTypes);
|
|
||||||
|
|
||||||
let sendPromises = [];
|
|
||||||
for (let pushType in deviceMap) {
|
|
||||||
let sender = this.senderMap[pushType];
|
|
||||||
if (!sender) {
|
|
||||||
console.log('Can not find sender for push type %s, %j', pushType, data);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let devices = deviceMap[pushType];
|
|
||||||
|
|
||||||
if(devices.length > 0) {
|
|
||||||
sendPromises.push(sender(data, devices));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Parse.Promise.when(sendPromises);
|
|
||||||
}
|
|
||||||
|
|
||||||
static classifyInstallations(installations, validTypes) {
|
|
||||||
return classifyInstallations(installations, validTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
getValidPushTypes() {
|
|
||||||
return this.validPushTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToAPNS(data,tokens) {
|
|
||||||
|
|
||||||
data= deepcopy(data['data']);
|
|
||||||
|
|
||||||
var post = {};
|
|
||||||
if(data['badge']) {
|
|
||||||
if(data['badge'] == "Increment") {
|
|
||||||
post['ios_badgeType'] = 'Increase';
|
|
||||||
post['ios_badgeCount'] = 1;
|
|
||||||
} else {
|
|
||||||
post['ios_badgeType'] = 'SetTo';
|
|
||||||
post['ios_badgeCount'] = data['badge'];
|
|
||||||
}
|
|
||||||
delete data['badge'];
|
|
||||||
}
|
|
||||||
if(data['alert']) {
|
|
||||||
post['contents'] = {en: data['alert']};
|
|
||||||
delete data['alert'];
|
|
||||||
}
|
|
||||||
if(data['sound']) {
|
|
||||||
post['ios_sound'] = data['sound'];
|
|
||||||
delete data['sound'];
|
|
||||||
}
|
|
||||||
if(data['content-available'] == 1) {
|
|
||||||
post['content_available'] = true;
|
|
||||||
delete data['content-available'];
|
|
||||||
}
|
|
||||||
post['data'] = data;
|
|
||||||
|
|
||||||
let promise = new Parse.Promise();
|
|
||||||
|
|
||||||
var chunk = 2000 // OneSignal can process 2000 devices at a time
|
|
||||||
var tokenlength=tokens.length;
|
|
||||||
var offset = 0
|
|
||||||
// handle onesignal response. Start next batch if there's not an error.
|
|
||||||
let handleResponse = function(wasSuccessful) {
|
|
||||||
if (!wasSuccessful) {
|
|
||||||
return promise.reject("OneSignal Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(offset >= tokenlength) {
|
|
||||||
promise.resolve()
|
|
||||||
} else {
|
|
||||||
this.sendNext();
|
|
||||||
}
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
this.sendNext = function() {
|
|
||||||
post['include_ios_tokens'] = [];
|
|
||||||
tokens.slice(offset,offset+chunk).forEach(function(i) {
|
|
||||||
post['include_ios_tokens'].push(i['deviceToken'])
|
|
||||||
})
|
|
||||||
offset+=chunk;
|
|
||||||
this.sendToOneSignal(post, handleResponse);
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
this.sendNext()
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToGCM(data,tokens) {
|
|
||||||
data= deepcopy(data['data']);
|
|
||||||
|
|
||||||
var post = {};
|
|
||||||
|
|
||||||
if(data['alert']) {
|
|
||||||
post['contents'] = {en: data['alert']};
|
|
||||||
delete data['alert'];
|
|
||||||
}
|
|
||||||
if(data['title']) {
|
|
||||||
post['title'] = {en: data['title']};
|
|
||||||
delete data['title'];
|
|
||||||
}
|
|
||||||
if(data['uri']) {
|
|
||||||
post['url'] = data['uri'];
|
|
||||||
}
|
|
||||||
|
|
||||||
post['data'] = data;
|
|
||||||
|
|
||||||
let promise = new Parse.Promise();
|
|
||||||
|
|
||||||
var chunk = 2000 // OneSignal can process 2000 devices at a time
|
|
||||||
var tokenlength=tokens.length;
|
|
||||||
var offset = 0
|
|
||||||
// handle onesignal response. Start next batch if there's not an error.
|
|
||||||
let handleResponse = function(wasSuccessful) {
|
|
||||||
if (!wasSuccessful) {
|
|
||||||
return promise.reject("OneSIgnal Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(offset >= tokenlength) {
|
|
||||||
promise.resolve()
|
|
||||||
} else {
|
|
||||||
this.sendNext();
|
|
||||||
}
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
this.sendNext = function() {
|
|
||||||
post['include_android_reg_ids'] = [];
|
|
||||||
tokens.slice(offset,offset+chunk).forEach(function(i) {
|
|
||||||
post['include_android_reg_ids'].push(i['deviceToken'])
|
|
||||||
})
|
|
||||||
offset+=chunk;
|
|
||||||
this.sendToOneSignal(post, handleResponse);
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
|
|
||||||
this.sendNext();
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToOneSignal(data, cb) {
|
|
||||||
let headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": "Basic "+this.OneSignalConfig['apiKey']
|
|
||||||
};
|
|
||||||
let options = {
|
|
||||||
host: "onesignal.com",
|
|
||||||
port: 443,
|
|
||||||
path: "/api/v1/notifications",
|
|
||||||
method: "POST",
|
|
||||||
headers: headers
|
|
||||||
};
|
|
||||||
data['app_id'] = this.OneSignalConfig['appId'];
|
|
||||||
|
|
||||||
let request = this.https.request(options, function(res) {
|
|
||||||
if(res.statusCode < 299) {
|
|
||||||
cb(true);
|
|
||||||
} else {
|
|
||||||
console.log('OneSignal Error');
|
|
||||||
res.on('data', function(chunk) {
|
|
||||||
console.log(chunk.toString())
|
|
||||||
});
|
|
||||||
cb(false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
request.on('error', function(e) {
|
|
||||||
console.log("Error connecting to OneSignal")
|
|
||||||
console.log(e);
|
|
||||||
cb(false);
|
|
||||||
});
|
|
||||||
request.write(JSON.stringify(data))
|
|
||||||
request.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default OneSignalPushAdapter;
|
|
||||||
module.exports = OneSignalPushAdapter;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
// ParsePushAdapter is the default implementation of
|
|
||||||
// PushAdapter, it uses GCM for android push and APNS
|
|
||||||
// for ios push.
|
|
||||||
|
|
||||||
const Parse = require('parse/node').Parse;
|
|
||||||
const GCM = require('../../GCM');
|
|
||||||
const APNS = require('../../APNS');
|
|
||||||
import PushAdapter from './PushAdapter';
|
|
||||||
import { classifyInstallations } from './PushAdapterUtils';
|
|
||||||
|
|
||||||
export class ParsePushAdapter extends PushAdapter {
|
|
||||||
|
|
||||||
supportsPushTracking = true;
|
|
||||||
|
|
||||||
constructor(pushConfig = {}) {
|
|
||||||
super(pushConfig);
|
|
||||||
this.validPushTypes = ['ios', 'android'];
|
|
||||||
this.senderMap = {};
|
|
||||||
// used in PushController for Dashboard Features
|
|
||||||
this.feature = {
|
|
||||||
immediatePush: true
|
|
||||||
};
|
|
||||||
let pushTypes = Object.keys(pushConfig);
|
|
||||||
|
|
||||||
for (let pushType of pushTypes) {
|
|
||||||
if (this.validPushTypes.indexOf(pushType) < 0) {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
'Push to ' + pushTypes + ' is not supported');
|
|
||||||
}
|
|
||||||
switch (pushType) {
|
|
||||||
case 'ios':
|
|
||||||
this.senderMap[pushType] = new APNS(pushConfig[pushType]);
|
|
||||||
break;
|
|
||||||
case 'android':
|
|
||||||
this.senderMap[pushType] = new GCM(pushConfig[pushType]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getValidPushTypes() {
|
|
||||||
return this.validPushTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static classifyInstallations(installations, validTypes) {
|
|
||||||
return classifyInstallations(installations, validTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
send(data, installations) {
|
|
||||||
let deviceMap = classifyInstallations(installations, this.validPushTypes);
|
|
||||||
let sendPromises = [];
|
|
||||||
for (let pushType in deviceMap) {
|
|
||||||
let sender = this.senderMap[pushType];
|
|
||||||
if (!sender) {
|
|
||||||
sendPromises.push(Promise.resolve({
|
|
||||||
transmitted: false,
|
|
||||||
response: {'error': `Can not find sender for push type ${pushType}, ${data}`}
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
let devices = deviceMap[pushType];
|
|
||||||
sendPromises.push(sender.send(data, devices));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Parse.Promise.when(sendPromises);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ParsePushAdapter;
|
|
||||||
module.exports = ParsePushAdapter;
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/**g
|
|
||||||
* Classify the device token of installations based on its device type.
|
|
||||||
* @param {Object} installations An array of installations
|
|
||||||
* @param {Array} validPushTypes An array of valid push types(string)
|
|
||||||
* @returns {Object} A map whose key is device type and value is an array of device
|
|
||||||
*/
|
|
||||||
export function classifyInstallations(installations, validPushTypes) {
|
|
||||||
// Init deviceTokenMap, create a empty array for each valid pushType
|
|
||||||
let deviceMap = {};
|
|
||||||
for (let validPushType of validPushTypes) {
|
|
||||||
deviceMap[validPushType] = [];
|
|
||||||
}
|
|
||||||
for (let installation of installations) {
|
|
||||||
// No deviceToken, ignore
|
|
||||||
if (!installation.deviceToken) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let pushType = installation.deviceType;
|
|
||||||
if (deviceMap[pushType]) {
|
|
||||||
deviceMap[pushType].push({
|
|
||||||
deviceToken: installation.deviceToken,
|
|
||||||
appIdentifier: installation.appIdentifier
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deviceMap;
|
|
||||||
}
|
|
||||||
154
src/GCM.js
154
src/GCM.js
@@ -1,154 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Parse = require('parse/node').Parse;
|
|
||||||
const gcm = require('node-gcm');
|
|
||||||
const cryptoUtils = require('./cryptoUtils');
|
|
||||||
|
|
||||||
const GCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // GCM allows a max of 4 weeks
|
|
||||||
const GCMRegistrationTokensMax = 1000;
|
|
||||||
|
|
||||||
function GCM(args) {
|
|
||||||
if (typeof args !== 'object' || !args.apiKey) {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
'GCM Configuration is invalid');
|
|
||||||
}
|
|
||||||
this.sender = new gcm.Sender(args.apiKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send gcm request.
|
|
||||||
* @param {Object} data The data we need to send, the format is the same with api request body
|
|
||||||
* @param {Array} devices A array of devices
|
|
||||||
* @returns {Object} A promise which is resolved after we get results from gcm
|
|
||||||
*/
|
|
||||||
GCM.prototype.send = function(data, devices) {
|
|
||||||
let pushId = cryptoUtils.newObjectId();
|
|
||||||
// Make a new array
|
|
||||||
devices = new Array(...devices);
|
|
||||||
let timestamp = Date.now();
|
|
||||||
// For android, we can only have 1000 recepients per send, so we need to slice devices to
|
|
||||||
// chunk if necessary
|
|
||||||
let slices = sliceDevices(devices, GCMRegistrationTokensMax);
|
|
||||||
if (slices.length > 1) {
|
|
||||||
// Make 1 send per slice
|
|
||||||
let promises = slices.reduce((memo, slice) => {
|
|
||||||
let promise = this.send(data, slice, timestamp);
|
|
||||||
memo.push(promise);
|
|
||||||
return memo;
|
|
||||||
}, [])
|
|
||||||
return Parse.Promise.when(promises).then((results) => {
|
|
||||||
let allResults = results.reduce((memo, result) => {
|
|
||||||
return memo.concat(result);
|
|
||||||
}, []);
|
|
||||||
return Parse.Promise.as(allResults);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// get the devices back...
|
|
||||||
devices = slices[0];
|
|
||||||
|
|
||||||
let 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
|
|
||||||
// PushId is not a formal field of GCM, but Parse Android SDK uses this field to deduplicate push notifications
|
|
||||||
let gcmPayload = generateGCMPayload(data.data, pushId, timestamp, expirationTime);
|
|
||||||
// Make and send gcm request
|
|
||||||
let message = new gcm.Message(gcmPayload);
|
|
||||||
|
|
||||||
// Build a device map
|
|
||||||
let devicesMap = devices.reduce((memo, device) => {
|
|
||||||
memo[device.deviceToken] = device;
|
|
||||||
return memo;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let deviceTokens = Object.keys(devicesMap);
|
|
||||||
|
|
||||||
let promises = deviceTokens.map(() => new Parse.Promise());
|
|
||||||
let registrationTokens = deviceTokens;
|
|
||||||
this.sender.send(message, { registrationTokens: registrationTokens }, 5, (error, response) => {
|
|
||||||
// example response:
|
|
||||||
/*
|
|
||||||
{ "multicast_id":7680139367771848000,
|
|
||||||
"success":0,
|
|
||||||
"failure":4,
|
|
||||||
"canonical_ids":0,
|
|
||||||
"results":[ {"error":"InvalidRegistration"},
|
|
||||||
{"error":"InvalidRegistration"},
|
|
||||||
{"error":"InvalidRegistration"},
|
|
||||||
{"error":"InvalidRegistration"}] }
|
|
||||||
*/
|
|
||||||
let { results, multicast_id } = response || {};
|
|
||||||
registrationTokens.forEach((token, index) => {
|
|
||||||
let promise = promises[index];
|
|
||||||
let result = results ? results[index] : undefined;
|
|
||||||
let device = devicesMap[token];
|
|
||||||
let resolution = {
|
|
||||||
device,
|
|
||||||
multicast_id,
|
|
||||||
response: error || result,
|
|
||||||
};
|
|
||||||
if (!result || result.error) {
|
|
||||||
resolution.transmitted = false;
|
|
||||||
} else {
|
|
||||||
resolution.transmitted = true;
|
|
||||||
}
|
|
||||||
promise.resolve(resolution);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return Parse.Promise.when(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
function generateGCMPayload(coreData, pushId, timeStamp, expirationTime) {
|
|
||||||
let payloadData = {
|
|
||||||
'time': new Date(timeStamp).toISOString(),
|
|
||||||
'push_id': pushId,
|
|
||||||
'data': JSON.stringify(coreData)
|
|
||||||
}
|
|
||||||
let payload = {
|
|
||||||
priority: 'normal',
|
|
||||||
data: payloadData
|
|
||||||
};
|
|
||||||
if (expirationTime) {
|
|
||||||
// The timeStamp and expiration is in milliseconds but gcm requires second
|
|
||||||
let timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
|
|
||||||
if (timeToLive < 0) {
|
|
||||||
timeToLive = 0;
|
|
||||||
}
|
|
||||||
if (timeToLive >= GCMTimeToLiveMax) {
|
|
||||||
timeToLive = GCMTimeToLiveMax;
|
|
||||||
}
|
|
||||||
payload.timeToLive = timeToLive;
|
|
||||||
}
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slice a list of devices to several list of devices with fixed chunk size.
|
|
||||||
* @param {Array} devices An array of devices
|
|
||||||
* @param {Number} chunkSize The size of the a chunk
|
|
||||||
* @returns {Array} An array which contaisn several arries of devices with fixed chunk size
|
|
||||||
*/
|
|
||||||
function sliceDevices(devices, chunkSize) {
|
|
||||||
let chunkDevices = [];
|
|
||||||
while (devices.length > 0) {
|
|
||||||
chunkDevices.push(devices.splice(0, chunkSize));
|
|
||||||
}
|
|
||||||
return chunkDevices;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
|
||||||
GCM.generateGCMPayload = generateGCMPayload;
|
|
||||||
GCM.sliceDevices = sliceDevices;
|
|
||||||
}
|
|
||||||
module.exports = GCM;
|
|
||||||
@@ -14,7 +14,6 @@ var batch = require('./batch'),
|
|||||||
import cache from './cache';
|
import cache from './cache';
|
||||||
import Config from './Config';
|
import Config from './Config';
|
||||||
import parseServerPackage from '../package.json';
|
import parseServerPackage from '../package.json';
|
||||||
import ParsePushAdapter from './Adapters/Push/ParsePushAdapter';
|
|
||||||
import PromiseRouter from './PromiseRouter';
|
import PromiseRouter from './PromiseRouter';
|
||||||
import requiredParameter from './requiredParameter';
|
import requiredParameter from './requiredParameter';
|
||||||
import { AnalyticsRouter } from './Routers/AnalyticsRouter';
|
import { AnalyticsRouter } from './Routers/AnalyticsRouter';
|
||||||
@@ -45,6 +44,7 @@ import { SessionsRouter } from './Routers/SessionsRouter';
|
|||||||
import { UserController } from './Controllers/UserController';
|
import { UserController } from './Controllers/UserController';
|
||||||
import { UsersRouter } from './Routers/UsersRouter';
|
import { UsersRouter } from './Routers/UsersRouter';
|
||||||
|
|
||||||
|
import ParsePushAdapter from 'parse-server-push-adapter';
|
||||||
// Mutate the Parse object to add the Cloud Code handlers
|
// Mutate the Parse object to add the Cloud Code handlers
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user