@parse/push-adapter 6.2.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -40
- package/package.json +18 -23
- package/src/APNS.js +323 -0
- package/src/EXPO.js +107 -0
- package/{lib → src}/FCM.js +147 -140
- package/{lib → src}/GCM.js +57 -85
- package/src/ParsePushAdapter.js +96 -0
- package/src/PushAdapterUtils.js +45 -0
- package/src/WEB.js +106 -0
- package/src/index.js +20 -0
- package/lib/APNS.js +0 -400
- package/lib/EXPO.js +0 -204
- package/lib/ParsePushAdapter.js +0 -167
- package/lib/PushAdapterUtils.js +0 -95
- package/lib/WEB.js +0 -204
- package/lib/index.js +0 -52
package/{lib → src}/GCM.js
RENAMED
|
@@ -1,38 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
var _npmlog2 = _interopRequireDefault(_npmlog);
|
|
18
|
-
|
|
19
|
-
var _nodeGcm = require('@parse/node-gcm');
|
|
20
|
-
|
|
21
|
-
var _nodeGcm2 = _interopRequireDefault(_nodeGcm);
|
|
22
|
-
|
|
23
|
-
var _PushAdapterUtils = require('./PushAdapterUtils');
|
|
24
|
-
|
|
25
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
26
|
-
|
|
27
|
-
var LOG_PREFIX = 'parse-server-push-adapter GCM';
|
|
28
|
-
var GCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // GCM allows a max of 4 weeks
|
|
29
|
-
var GCMRegistrationTokensMax = 1000;
|
|
30
|
-
|
|
31
|
-
function GCM(args) {
|
|
32
|
-
if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) !== 'object' || !args.apiKey) {
|
|
33
|
-
throw new _parse2.default.Error(_parse2.default.Error.PUSH_MISCONFIGURED, 'GCM Configuration is invalid');
|
|
3
|
+
import Parse from 'parse';
|
|
4
|
+
import log from 'npmlog';
|
|
5
|
+
import gcm from '@parse/node-gcm';
|
|
6
|
+
import { randomString } from './PushAdapterUtils.js';
|
|
7
|
+
|
|
8
|
+
const LOG_PREFIX = 'parse-server-push-adapter GCM';
|
|
9
|
+
const GCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // GCM allows a max of 4 weeks
|
|
10
|
+
const GCMRegistrationTokensMax = 1000;
|
|
11
|
+
|
|
12
|
+
export default function GCM(args) {
|
|
13
|
+
if (typeof args !== 'object' || !args.apiKey) {
|
|
14
|
+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
15
|
+
'GCM Configuration is invalid');
|
|
34
16
|
}
|
|
35
|
-
this.sender = new
|
|
17
|
+
this.sender = new gcm.Sender(args.apiKey, args.requestOptions);
|
|
36
18
|
}
|
|
37
19
|
|
|
38
20
|
GCM.GCMRegistrationTokensMax = GCMRegistrationTokensMax;
|
|
@@ -43,30 +25,28 @@ GCM.GCMRegistrationTokensMax = GCMRegistrationTokensMax;
|
|
|
43
25
|
* @param {Array} devices A array of devices
|
|
44
26
|
* @returns {Object} A promise which is resolved after we get results from gcm
|
|
45
27
|
*/
|
|
46
|
-
GCM.prototype.send = function
|
|
47
|
-
var _this = this;
|
|
48
|
-
|
|
28
|
+
GCM.prototype.send = function(data, devices) {
|
|
49
29
|
if (!data || !devices || !Array.isArray(devices)) {
|
|
50
|
-
|
|
30
|
+
log.warn(LOG_PREFIX, 'invalid push payload');
|
|
51
31
|
return;
|
|
52
32
|
}
|
|
53
|
-
|
|
33
|
+
let pushId = randomString(10);
|
|
54
34
|
// Make a new array
|
|
55
|
-
devices
|
|
56
|
-
|
|
35
|
+
devices=devices.slice(0);
|
|
36
|
+
let timestamp = Date.now();
|
|
57
37
|
// For android, we can only have 1000 recepients per send, so we need to slice devices to
|
|
58
38
|
// chunk if necessary
|
|
59
|
-
|
|
39
|
+
let slices = sliceDevices(devices, GCM.GCMRegistrationTokensMax);
|
|
60
40
|
if (slices.length > 1) {
|
|
61
|
-
|
|
41
|
+
log.verbose(LOG_PREFIX, `the number of devices exceeds ${GCMRegistrationTokensMax}`);
|
|
62
42
|
// Make 1 send per slice
|
|
63
|
-
|
|
64
|
-
|
|
43
|
+
let promises = slices.reduce((memo, slice) => {
|
|
44
|
+
let promise = this.send(data, slice, timestamp);
|
|
65
45
|
memo.push(promise);
|
|
66
46
|
return memo;
|
|
67
|
-
}, [])
|
|
68
|
-
return Promise.all(
|
|
69
|
-
|
|
47
|
+
}, [])
|
|
48
|
+
return Promise.all(promises).then((results) => {
|
|
49
|
+
let allResults = results.reduce((memo, result) => {
|
|
70
50
|
return memo.concat(result);
|
|
71
51
|
}, []);
|
|
72
52
|
return Promise.resolve(allResults);
|
|
@@ -75,7 +55,7 @@ GCM.prototype.send = function (data, devices) {
|
|
|
75
55
|
// get the devices back...
|
|
76
56
|
devices = slices[0];
|
|
77
57
|
|
|
78
|
-
|
|
58
|
+
let expirationTime;
|
|
79
59
|
// We handle the expiration_time convertion in push.js, so expiration_time is a valid date
|
|
80
60
|
// in Unix epoch time in milliseconds here
|
|
81
61
|
if (data['expiration_time']) {
|
|
@@ -83,28 +63,24 @@ GCM.prototype.send = function (data, devices) {
|
|
|
83
63
|
}
|
|
84
64
|
// Generate gcm payload
|
|
85
65
|
// PushId is not a formal field of GCM, but Parse Android SDK uses this field to deduplicate push notifications
|
|
86
|
-
|
|
66
|
+
let gcmPayload = generateGCMPayload(data, pushId, timestamp, expirationTime);
|
|
87
67
|
// Make and send gcm request
|
|
88
|
-
|
|
68
|
+
let message = new gcm.Message(gcmPayload);
|
|
89
69
|
|
|
90
70
|
// Build a device map
|
|
91
|
-
|
|
71
|
+
let devicesMap = devices.reduce((memo, device) => {
|
|
92
72
|
memo[device.deviceToken] = device;
|
|
93
73
|
return memo;
|
|
94
74
|
}, {});
|
|
95
75
|
|
|
96
|
-
|
|
76
|
+
let deviceTokens = Object.keys(devicesMap);
|
|
97
77
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
})
|
|
104
|
-
var registrationTokens = deviceTokens;
|
|
105
|
-
var length = registrationTokens.length;
|
|
106
|
-
_npmlog2.default.verbose(LOG_PREFIX, 'sending to ' + length + ' ' + (length > 1 ? 'devices' : 'device'));
|
|
107
|
-
this.sender.send(message, { registrationTokens: registrationTokens }, 5, function (error, response) {
|
|
78
|
+
const resolvers = [];
|
|
79
|
+
const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve)));
|
|
80
|
+
let registrationTokens = deviceTokens;
|
|
81
|
+
let length = registrationTokens.length;
|
|
82
|
+
log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`);
|
|
83
|
+
this.sender.send(message, { registrationTokens: registrationTokens }, 5, (error, response) => {
|
|
108
84
|
// example response:
|
|
109
85
|
/*
|
|
110
86
|
{ "multicast_id":7680139367771848000,
|
|
@@ -117,24 +93,20 @@ GCM.prototype.send = function (data, devices) {
|
|
|
117
93
|
{"error":"InvalidRegistration"}] }
|
|
118
94
|
*/
|
|
119
95
|
if (error) {
|
|
120
|
-
|
|
96
|
+
log.error(LOG_PREFIX, `send errored: %s`, JSON.stringify(error, null, 4));
|
|
121
97
|
} else {
|
|
122
|
-
|
|
98
|
+
log.verbose(LOG_PREFIX, `GCM Response: %s`, JSON.stringify(response, null, 4));
|
|
123
99
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
registrationTokens.forEach(function (token, index) {
|
|
130
|
-
var resolve = resolvers[index];
|
|
131
|
-
var result = results ? results[index] : undefined;
|
|
132
|
-
var device = devicesMap[token];
|
|
100
|
+
let { results, multicast_id } = response || {};
|
|
101
|
+
registrationTokens.forEach((token, index) => {
|
|
102
|
+
let resolve = resolvers[index];
|
|
103
|
+
let result = results ? results[index] : undefined;
|
|
104
|
+
let device = devicesMap[token];
|
|
133
105
|
device.deviceType = 'android';
|
|
134
|
-
|
|
135
|
-
device
|
|
136
|
-
multicast_id
|
|
137
|
-
response: error || result
|
|
106
|
+
let resolution = {
|
|
107
|
+
device,
|
|
108
|
+
multicast_id,
|
|
109
|
+
response: error || result,
|
|
138
110
|
};
|
|
139
111
|
if (!result || result.error) {
|
|
140
112
|
resolution.transmitted = false;
|
|
@@ -145,7 +117,7 @@ GCM.prototype.send = function (data, devices) {
|
|
|
145
117
|
});
|
|
146
118
|
});
|
|
147
119
|
return Promise.all(promises);
|
|
148
|
-
}
|
|
120
|
+
}
|
|
149
121
|
|
|
150
122
|
/**
|
|
151
123
|
* Generate the gcm payload from the data we get from api request.
|
|
@@ -156,24 +128,24 @@ GCM.prototype.send = function (data, devices) {
|
|
|
156
128
|
* @returns {Object} A promise which is resolved after we get results from gcm
|
|
157
129
|
*/
|
|
158
130
|
function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) {
|
|
159
|
-
|
|
131
|
+
let payload = {
|
|
160
132
|
priority: 'high'
|
|
161
133
|
};
|
|
162
134
|
payload.data = {
|
|
163
135
|
data: requestData.data,
|
|
164
136
|
push_id: pushId,
|
|
165
137
|
time: new Date(timeStamp).toISOString()
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
optionalKeys.forEach(
|
|
138
|
+
}
|
|
139
|
+
const optionalKeys = ['contentAvailable', 'notification'];
|
|
140
|
+
optionalKeys.forEach((key) => {
|
|
169
141
|
if (requestData.hasOwnProperty(key)) {
|
|
170
142
|
payload[key] = requestData[key];
|
|
171
143
|
}
|
|
172
144
|
});
|
|
173
145
|
|
|
174
146
|
if (expirationTime) {
|
|
175
|
-
|
|
176
|
-
|
|
147
|
+
// The timeStamp and expiration is in milliseconds but gcm requires second
|
|
148
|
+
let timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
|
|
177
149
|
if (timeToLive < 0) {
|
|
178
150
|
timeToLive = 0;
|
|
179
151
|
}
|
|
@@ -192,7 +164,7 @@ function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) {
|
|
|
192
164
|
* @returns {Array} An array which contaisn several arries of devices with fixed chunk size
|
|
193
165
|
*/
|
|
194
166
|
function sliceDevices(devices, chunkSize) {
|
|
195
|
-
|
|
167
|
+
let chunkDevices = [];
|
|
196
168
|
while (devices.length > 0) {
|
|
197
169
|
chunkDevices.push(devices.splice(0, chunkSize));
|
|
198
170
|
}
|
|
@@ -204,4 +176,4 @@ GCM.generateGCMPayload = generateGCMPayload;
|
|
|
204
176
|
/* istanbul ignore else */
|
|
205
177
|
if (process.env.TESTING) {
|
|
206
178
|
GCM.sliceDevices = sliceDevices;
|
|
207
|
-
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import Parse from 'parse';
|
|
3
|
+
import log from 'npmlog';
|
|
4
|
+
import APNS from './APNS.js';
|
|
5
|
+
import GCM from './GCM.js';
|
|
6
|
+
import FCM from './FCM.js';
|
|
7
|
+
import WEB from './WEB.js';
|
|
8
|
+
import EXPO from './EXPO.js';
|
|
9
|
+
import { classifyInstallations } from './PushAdapterUtils.js';
|
|
10
|
+
|
|
11
|
+
const LOG_PREFIX = 'parse-server-push-adapter';
|
|
12
|
+
|
|
13
|
+
export default class ParsePushAdapter {
|
|
14
|
+
|
|
15
|
+
supportsPushTracking = true;
|
|
16
|
+
|
|
17
|
+
constructor(pushConfig = {}) {
|
|
18
|
+
this.validPushTypes = ['ios', 'osx', 'tvos', 'android', 'fcm', 'web', 'expo'];
|
|
19
|
+
this.senderMap = {};
|
|
20
|
+
// used in PushController for Dashboard Features
|
|
21
|
+
this.feature = {
|
|
22
|
+
immediatePush: true
|
|
23
|
+
};
|
|
24
|
+
let pushTypes = Object.keys(pushConfig);
|
|
25
|
+
|
|
26
|
+
for (let pushType of pushTypes) {
|
|
27
|
+
// adapter may be passed as part of the parse-server initialization
|
|
28
|
+
if (this.validPushTypes.indexOf(pushType) < 0 && pushType != 'adapter') {
|
|
29
|
+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
30
|
+
'Push to ' + pushType + ' is not supported');
|
|
31
|
+
}
|
|
32
|
+
switch (pushType) {
|
|
33
|
+
case 'ios':
|
|
34
|
+
case 'tvos':
|
|
35
|
+
case 'osx':
|
|
36
|
+
if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) {
|
|
37
|
+
this.senderMap[pushType] = new FCM(pushConfig[pushType], 'apple');
|
|
38
|
+
} else {
|
|
39
|
+
this.senderMap[pushType] = new APNS(pushConfig[pushType]);
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case 'web':
|
|
43
|
+
this.senderMap[pushType] = new WEB(pushConfig[pushType]);
|
|
44
|
+
break;
|
|
45
|
+
case 'expo':
|
|
46
|
+
this.senderMap[pushType] = new EXPO(pushConfig[pushType]);
|
|
47
|
+
break;
|
|
48
|
+
case 'android':
|
|
49
|
+
case 'fcm':
|
|
50
|
+
if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) {
|
|
51
|
+
this.senderMap[pushType] = new FCM(pushConfig[pushType], 'android');
|
|
52
|
+
} else {
|
|
53
|
+
this.senderMap[pushType] = new GCM(pushConfig[pushType]);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getValidPushTypes() {
|
|
61
|
+
return this.validPushTypes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static classifyInstallations(installations, validTypes) {
|
|
65
|
+
return classifyInstallations(installations, validTypes)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
send(data, installations) {
|
|
69
|
+
let deviceMap = classifyInstallations(installations, this.validPushTypes);
|
|
70
|
+
let sendPromises = [];
|
|
71
|
+
for (let pushType in deviceMap) {
|
|
72
|
+
let sender = this.senderMap[pushType];
|
|
73
|
+
let devices = deviceMap[pushType];
|
|
74
|
+
|
|
75
|
+
if(Array.isArray(devices) && devices.length > 0) {
|
|
76
|
+
if (!sender) {
|
|
77
|
+
log.verbose(LOG_PREFIX, `Can not find sender for push type ${pushType}, ${data}`)
|
|
78
|
+
let results = devices.map((device) => {
|
|
79
|
+
return Promise.resolve({
|
|
80
|
+
device,
|
|
81
|
+
transmitted: false,
|
|
82
|
+
response: {'error': `Can not find sender for push type ${pushType}, ${data}`}
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
sendPromises.push(Promise.all(results));
|
|
86
|
+
} else {
|
|
87
|
+
sendPromises.push(sender.send(data, devices));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Promise.all(sendPromises).then((promises) => {
|
|
92
|
+
// flatten all
|
|
93
|
+
return [].concat.apply([], promises);
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**g
|
|
4
|
+
* Classify the device token of installations based on its device type.
|
|
5
|
+
* @param {Object} installations An array of installations
|
|
6
|
+
* @param {Array} validPushTypes An array of valid push types(string)
|
|
7
|
+
* @returns {Object} A map whose key is device type and value is an array of device
|
|
8
|
+
*/
|
|
9
|
+
export function classifyInstallations(installations, validPushTypes) {
|
|
10
|
+
// Init deviceTokenMap, create a empty array for each valid pushType
|
|
11
|
+
let deviceMap = {};
|
|
12
|
+
for (let validPushType of validPushTypes) {
|
|
13
|
+
deviceMap[validPushType] = [];
|
|
14
|
+
}
|
|
15
|
+
for (let installation of installations) {
|
|
16
|
+
// No deviceToken, ignore
|
|
17
|
+
if (!installation.deviceToken) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
let devices = deviceMap[installation.pushType] || deviceMap[installation.deviceType] || null;
|
|
21
|
+
if (Array.isArray(devices)) {
|
|
22
|
+
devices.push({
|
|
23
|
+
deviceToken: installation.deviceToken,
|
|
24
|
+
deviceType: installation.deviceType,
|
|
25
|
+
appIdentifier: installation.appIdentifier
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return deviceMap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function randomString(size) {
|
|
33
|
+
if (size === 0) {
|
|
34
|
+
throw new Error('Zero-length randomString is useless.');
|
|
35
|
+
}
|
|
36
|
+
let chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
|
|
37
|
+
'abcdefghijklmnopqrstuvwxyz' +
|
|
38
|
+
'0123456789');
|
|
39
|
+
let objectId = '';
|
|
40
|
+
let bytes = randomBytes(size);
|
|
41
|
+
for (let i = 0; i < bytes.length; ++i) {
|
|
42
|
+
objectId += chars[bytes.readUInt8(i) % chars.length];
|
|
43
|
+
}
|
|
44
|
+
return objectId;
|
|
45
|
+
}
|
package/src/WEB.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import Parse from 'parse';
|
|
4
|
+
import log from 'npmlog';
|
|
5
|
+
import webpush from 'web-push';
|
|
6
|
+
|
|
7
|
+
const LOG_PREFIX = 'parse-server-push-adapter WEB';
|
|
8
|
+
|
|
9
|
+
export class WEB {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new WEB push adapter.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} args https://github.com/web-push-libs/web-push#api-reference
|
|
14
|
+
*/
|
|
15
|
+
constructor(args) {
|
|
16
|
+
if (typeof args !== 'object' || !args.vapidDetails) {
|
|
17
|
+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'WEB Push Configuration is invalid');
|
|
18
|
+
}
|
|
19
|
+
this.options = args;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Send web push notification request.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} data The data we need to send, the format is the same with api request body
|
|
26
|
+
* @param {Array} devices An array of devices
|
|
27
|
+
* @returns {Object} A promise which is resolved immediately
|
|
28
|
+
*/
|
|
29
|
+
async send(data, devices) {
|
|
30
|
+
const coreData = data && data.data;
|
|
31
|
+
if (!coreData || !devices || !Array.isArray(devices)) {
|
|
32
|
+
log.warn(LOG_PREFIX, 'invalid push payload');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const devicesMap = devices.reduce((memo, device) => {
|
|
36
|
+
memo[device.deviceToken] = device;
|
|
37
|
+
return memo;
|
|
38
|
+
}, {});
|
|
39
|
+
const deviceTokens = Object.keys(devicesMap);
|
|
40
|
+
|
|
41
|
+
const resolvers = [];
|
|
42
|
+
const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve)));
|
|
43
|
+
let length = deviceTokens.length;
|
|
44
|
+
log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`);
|
|
45
|
+
|
|
46
|
+
const response = await WEB.sendNotifications(coreData, deviceTokens, this.options);
|
|
47
|
+
const { results, sent, failed } = response;
|
|
48
|
+
if (sent) {
|
|
49
|
+
log.verbose(LOG_PREFIX, `WEB Response: %d out of %d sent successfully`, sent, results.length);
|
|
50
|
+
}
|
|
51
|
+
if (failed) {
|
|
52
|
+
log.error(LOG_PREFIX, `send errored: %d out of %d failed with error %s`, failed, results.length, 'push subscription has unsubscribed or expired.');
|
|
53
|
+
}
|
|
54
|
+
deviceTokens.forEach((token, index) => {
|
|
55
|
+
const resolve = resolvers[index];
|
|
56
|
+
const { result, error } = results[index];
|
|
57
|
+
const device = devicesMap[token];
|
|
58
|
+
device.deviceType = 'web';
|
|
59
|
+
const resolution = {
|
|
60
|
+
device,
|
|
61
|
+
response: error || result,
|
|
62
|
+
transmitted: !error,
|
|
63
|
+
};
|
|
64
|
+
resolve(resolution);
|
|
65
|
+
});
|
|
66
|
+
return Promise.all(promises);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Send multiple web push notification request.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} payload The data we need to send, the format is the same with api request body
|
|
73
|
+
* @param {Array} deviceTokens An array of devicesTokens
|
|
74
|
+
* @param {Object} options The options for the request
|
|
75
|
+
* @returns {Object} A promise which is resolved immediately
|
|
76
|
+
*/
|
|
77
|
+
static async sendNotifications(payload, deviceTokens, options) {
|
|
78
|
+
const promises = deviceTokens.map((deviceToken) => {
|
|
79
|
+
if (typeof deviceToken === 'string') {
|
|
80
|
+
deviceToken = JSON.parse(deviceToken);
|
|
81
|
+
}
|
|
82
|
+
if (typeof payload === 'object') {
|
|
83
|
+
payload = JSON.stringify(payload);
|
|
84
|
+
}
|
|
85
|
+
return webpush.sendNotification(deviceToken, payload, options);
|
|
86
|
+
});
|
|
87
|
+
const allResults = await Promise.allSettled(promises);
|
|
88
|
+
const response = {
|
|
89
|
+
sent: 0,
|
|
90
|
+
failed: 0,
|
|
91
|
+
results: [],
|
|
92
|
+
};
|
|
93
|
+
allResults.forEach((result) => {
|
|
94
|
+
if (result.status === 'fulfilled') {
|
|
95
|
+
response.sent += 1;
|
|
96
|
+
response.results.push({ result: result.value.statusCode });
|
|
97
|
+
} else {
|
|
98
|
+
response.failed += 1;
|
|
99
|
+
response.results.push({ error: result.reason.body });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return response;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default WEB;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ParsePushAdapter is the default implementation of
|
|
3
|
+
// PushAdapter, it uses GCM for android push, APNS for ios push.
|
|
4
|
+
// WEB for web push.
|
|
5
|
+
import log from 'npmlog';
|
|
6
|
+
|
|
7
|
+
/* istanbul ignore if */
|
|
8
|
+
if (process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER_PUSH_ADAPTER) {
|
|
9
|
+
log.level = 'verbose';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
import ParsePushAdapter from './ParsePushAdapter.js';
|
|
13
|
+
import GCM from './GCM.js';
|
|
14
|
+
import APNS from './APNS.js';
|
|
15
|
+
import WEB from './WEB.js';
|
|
16
|
+
import EXPO from './EXPO.js';
|
|
17
|
+
import * as utils from './PushAdapterUtils.js';
|
|
18
|
+
|
|
19
|
+
export default ParsePushAdapter;
|
|
20
|
+
export { ParsePushAdapter, APNS, GCM, WEB, EXPO, utils };
|