@parse/push-adapter 5.1.1 → 6.0.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 +2 -0
- package/lib/FCM.js +197 -44
- package/lib/ParsePushAdapter.js +2 -2
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://codecov.io/github/parse-community/parse-server-push-adapter?branch=master)
|
|
6
6
|
[](https://github.com/parse-community/parse-server-push-adapter/releases)
|
|
7
7
|
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
8
10
|
[](https://www.npmjs.com/package/@parse/push-adapter)
|
|
9
11
|
|
|
10
12
|
---
|
package/lib/FCM.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
@@ -29,8 +29,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
29
29
|
var LOG_PREFIX = 'parse-server-push-adapter FCM';
|
|
30
30
|
var FCMRegistrationTokensMax = 500;
|
|
31
31
|
var FCMTimeToLiveMax = 4 * 7 * 24 * 60 * 60; // FCM allows a max of 4 weeks
|
|
32
|
+
var apnsIntegerDataKeys = ['badge', 'content-available', 'mutable-content', 'priority', 'expiration_time'];
|
|
32
33
|
|
|
33
|
-
function FCM(args) {
|
|
34
|
+
function FCM(args, pushType) {
|
|
34
35
|
if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) !== 'object' || !args.firebaseServiceAccount) {
|
|
35
36
|
throw new _parse2.default.Error(_parse2.default.Error.PUSH_MISCONFIGURED, 'FCM Configuration is invalid');
|
|
36
37
|
}
|
|
@@ -42,6 +43,7 @@ function FCM(args) {
|
|
|
42
43
|
app = (0, _app.getApp)();
|
|
43
44
|
}
|
|
44
45
|
this.sender = (0, _messaging.getMessaging)(app);
|
|
46
|
+
this.pushType = pushType; // Push type is only used to remain backwards compatible with APNS and GCM
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
FCM.FCMRegistrationTokensMax = FCMRegistrationTokensMax;
|
|
@@ -65,7 +67,7 @@ FCM.prototype.send = function (data, devices) {
|
|
|
65
67
|
// chunk if necessary
|
|
66
68
|
var slices = sliceDevices(devices, FCM.FCMRegistrationTokensMax);
|
|
67
69
|
|
|
68
|
-
var sendToDeviceSlice = function sendToDeviceSlice(deviceSlice) {
|
|
70
|
+
var sendToDeviceSlice = function sendToDeviceSlice(deviceSlice, pushType) {
|
|
69
71
|
var pushId = (0, _PushAdapterUtils.randomString)(10);
|
|
70
72
|
var timestamp = Date.now();
|
|
71
73
|
|
|
@@ -76,7 +78,8 @@ FCM.prototype.send = function (data, devices) {
|
|
|
76
78
|
}, {});
|
|
77
79
|
|
|
78
80
|
var deviceTokens = Object.keys(devicesMap);
|
|
79
|
-
|
|
81
|
+
|
|
82
|
+
var fcmPayload = generateFCMPayload(data, pushId, timestamp, deviceTokens, pushType);
|
|
80
83
|
var length = deviceTokens.length;
|
|
81
84
|
_npmlog2.default.info(LOG_PREFIX, 'sending push to ' + length + ' devices');
|
|
82
85
|
|
|
@@ -108,21 +111,205 @@ FCM.prototype.send = function (data, devices) {
|
|
|
108
111
|
});
|
|
109
112
|
};
|
|
110
113
|
|
|
111
|
-
var allPromises = Promise.all(slices.map(
|
|
114
|
+
var allPromises = Promise.all(slices.map(function (slice) {
|
|
115
|
+
return sendToDeviceSlice(slice, _this.pushType);
|
|
116
|
+
})).catch(function (err) {
|
|
112
117
|
_npmlog2.default.error(LOG_PREFIX, 'error sending push: ' + err);
|
|
113
118
|
});
|
|
114
119
|
|
|
115
120
|
return allPromises;
|
|
116
121
|
};
|
|
117
122
|
|
|
123
|
+
function _APNSToFCMPayload(requestData) {
|
|
124
|
+
var coreData = requestData;
|
|
125
|
+
|
|
126
|
+
if (requestData.hasOwnProperty('data')) {
|
|
127
|
+
coreData = requestData.data;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
var expirationTime = requestData['expiration_time'] || coreData['expiration_time'];
|
|
131
|
+
var collapseId = requestData['collapse_id'] || coreData['collapse_id'];
|
|
132
|
+
var pushType = requestData['push_type'] || coreData['push_type'];
|
|
133
|
+
var priority = requestData['priority'] || coreData['priority'];
|
|
134
|
+
|
|
135
|
+
var apnsPayload = { apns: { payload: { aps: {} } } };
|
|
136
|
+
var headers = {};
|
|
137
|
+
|
|
138
|
+
// Set to alert by default if not set explicitly
|
|
139
|
+
headers['apns-push-type'] = 'alert';
|
|
140
|
+
|
|
141
|
+
if (expirationTime) {
|
|
142
|
+
headers['apns-expiration'] = Math.round(expirationTime / 1000);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (collapseId) {
|
|
146
|
+
headers['apns-collapse-id'] = collapseId;
|
|
147
|
+
}
|
|
148
|
+
if (pushType) {
|
|
149
|
+
headers['apns-push-type'] = pushType;
|
|
150
|
+
}
|
|
151
|
+
if (priority) {
|
|
152
|
+
headers['apns-priority'] = priority;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (Object.keys(headers).length > 0) {
|
|
156
|
+
apnsPayload.apns.headers = headers;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (var key in coreData) {
|
|
160
|
+
switch (key) {
|
|
161
|
+
case 'aps':
|
|
162
|
+
apnsPayload['apns']['payload']['aps'] = coreData.aps;
|
|
163
|
+
break;
|
|
164
|
+
case 'alert':
|
|
165
|
+
if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
|
|
166
|
+
apnsPayload['apns']['payload']['aps']['alert'] = {};
|
|
167
|
+
}
|
|
168
|
+
// In APNS.js we set a body with the same value as alert in requestData.
|
|
169
|
+
// See L200 in APNS.spec.js
|
|
170
|
+
apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert;
|
|
171
|
+
break;
|
|
172
|
+
case 'title':
|
|
173
|
+
// Ensure the alert object exists before trying to assign the title
|
|
174
|
+
if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
|
|
175
|
+
apnsPayload['apns']['payload']['aps']['alert'] = {};
|
|
176
|
+
}
|
|
177
|
+
apnsPayload['apns']['payload']['aps']['alert']['title'] = coreData.title;
|
|
178
|
+
break;
|
|
179
|
+
case 'badge':
|
|
180
|
+
apnsPayload['apns']['payload']['aps']['badge'] = coreData.badge;
|
|
181
|
+
break;
|
|
182
|
+
case 'sound':
|
|
183
|
+
apnsPayload['apns']['payload']['aps']['sound'] = coreData.sound;
|
|
184
|
+
break;
|
|
185
|
+
case 'content-available':
|
|
186
|
+
apnsPayload['apns']['payload']['aps']['content-available'] = coreData['content-available'];
|
|
187
|
+
break;
|
|
188
|
+
case 'mutable-content':
|
|
189
|
+
apnsPayload['apns']['payload']['aps']['mutable-content'] = coreData['mutable-content'];
|
|
190
|
+
break;
|
|
191
|
+
case 'targetContentIdentifier':
|
|
192
|
+
apnsPayload['apns']['payload']['aps']['target-content-id'] = coreData.targetContentIdentifier;
|
|
193
|
+
break;
|
|
194
|
+
case 'interruptionLevel':
|
|
195
|
+
apnsPayload['apns']['payload']['aps']['interruption-level'] = coreData.interruptionLevel;
|
|
196
|
+
break;
|
|
197
|
+
case 'category':
|
|
198
|
+
apnsPayload['apns']['payload']['aps']['category'] = coreData.category;
|
|
199
|
+
break;
|
|
200
|
+
case 'threadId':
|
|
201
|
+
apnsPayload['apns']['payload']['aps']['thread-id'] = coreData.threadId;
|
|
202
|
+
break;
|
|
203
|
+
case 'expiration_time':
|
|
204
|
+
// Exclude header-related fields as these are set above
|
|
205
|
+
break;
|
|
206
|
+
case 'collapse_id':
|
|
207
|
+
break;
|
|
208
|
+
case 'push_type':
|
|
209
|
+
break;
|
|
210
|
+
case 'priority':
|
|
211
|
+
break;
|
|
212
|
+
default:
|
|
213
|
+
apnsPayload['apns']['payload'][key] = coreData[key]; // Custom keys should be outside aps
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return apnsPayload;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function _GCMToFCMPayload(requestData, timeStamp) {
|
|
221
|
+
var androidPayload = {
|
|
222
|
+
android: {
|
|
223
|
+
priority: 'high'
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (requestData.hasOwnProperty('notification')) {
|
|
228
|
+
androidPayload.android.notification = requestData.notification;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (requestData.hasOwnProperty('data')) {
|
|
232
|
+
// FCM gives an error on send if we have apns keys that should have integer values
|
|
233
|
+
var _iteratorNormalCompletion = true;
|
|
234
|
+
var _didIteratorError = false;
|
|
235
|
+
var _iteratorError = undefined;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
for (var _iterator = apnsIntegerDataKeys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
239
|
+
var key = _step.value;
|
|
240
|
+
|
|
241
|
+
if (requestData.data.hasOwnProperty(key)) {
|
|
242
|
+
delete requestData.data[key];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
_didIteratorError = true;
|
|
247
|
+
_iteratorError = err;
|
|
248
|
+
} finally {
|
|
249
|
+
try {
|
|
250
|
+
if (!_iteratorNormalCompletion && _iterator.return) {
|
|
251
|
+
_iterator.return();
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
if (_didIteratorError) {
|
|
255
|
+
throw _iteratorError;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
androidPayload.android.data = requestData.data;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (requestData['expiration_time']) {
|
|
264
|
+
var expirationTime = requestData['expiration_time'];
|
|
265
|
+
// Convert to seconds
|
|
266
|
+
var timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
|
|
267
|
+
if (timeToLive < 0) {
|
|
268
|
+
timeToLive = 0;
|
|
269
|
+
}
|
|
270
|
+
if (timeToLive >= FCMTimeToLiveMax) {
|
|
271
|
+
timeToLive = FCMTimeToLiveMax;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
androidPayload.android.ttl = timeToLive;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return androidPayload;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Converts payloads used by APNS or GCM into a FCMv1-compatible payload.
|
|
282
|
+
* Purpose is to remain backwards-compatible will payloads used in the APNS.js and GCM.js modules.
|
|
283
|
+
* If the key rawPayload is present in the requestData, a raw payload will be used. Otherwise, conversion is done.
|
|
284
|
+
* @param {Object} requestData The request body
|
|
285
|
+
* @param {String} pushType Either apple or android.
|
|
286
|
+
* @param {Number} timeStamp Used during GCM payload conversion for ttl
|
|
287
|
+
* @returns {Object} A FCMv1-compatible payload.
|
|
288
|
+
*/
|
|
289
|
+
function payloadConverter(requestData, pushType, timeStamp) {
|
|
290
|
+
if (requestData.hasOwnProperty('rawPayload')) {
|
|
291
|
+
return requestData.rawPayload;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (pushType === 'apple') {
|
|
295
|
+
return _APNSToFCMPayload(requestData);
|
|
296
|
+
} else if (pushType === 'android') {
|
|
297
|
+
return _GCMToFCMPayload(requestData, timeStamp);
|
|
298
|
+
} else {
|
|
299
|
+
throw new _parse2.default.Error(_parse2.default.Error.PUSH_MISCONFIGURED, 'Unsupported push type, apple or android only.');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
118
303
|
/**
|
|
119
304
|
* Generate the fcm payload from the data we get from api request.
|
|
120
305
|
* @param {Object} requestData The request body
|
|
121
306
|
* @param {String} pushId A random string
|
|
122
307
|
* @param {Number} timeStamp A number in milliseconds since the Unix Epoch
|
|
308
|
+
* @param {Array.<String>} deviceTokens An array of deviceTokens
|
|
309
|
+
* @param {String} pushType Either apple or android
|
|
123
310
|
* @returns {Object} A payload for FCM
|
|
124
311
|
*/
|
|
125
|
-
function generateFCMPayload(requestData, pushId, timeStamp, deviceTokens) {
|
|
312
|
+
function generateFCMPayload(requestData, pushId, timeStamp, deviceTokens, pushType) {
|
|
126
313
|
delete requestData['where'];
|
|
127
314
|
|
|
128
315
|
var payloadToUse = {
|
|
@@ -131,44 +318,10 @@ function generateFCMPayload(requestData, pushId, timeStamp, deviceTokens) {
|
|
|
131
318
|
time: new Date(timeStamp).toISOString()
|
|
132
319
|
};
|
|
133
320
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
139
|
-
} else {
|
|
140
|
-
// Android payload according to GCM implementation
|
|
141
|
-
var androidPayload = {
|
|
142
|
-
android: {
|
|
143
|
-
priority: 'high'
|
|
144
|
-
},
|
|
145
|
-
tokens: deviceTokens
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
if (requestData.hasOwnProperty('notification')) {
|
|
149
|
-
androidPayload.notification = requestData.notification;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (requestData.hasOwnProperty('data')) {
|
|
153
|
-
androidPayload.data = requestData.data;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (requestData['expiration_time']) {
|
|
157
|
-
var expirationTime = requestData['expiration_time'];
|
|
158
|
-
// Convert to seconds
|
|
159
|
-
var timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
|
|
160
|
-
if (timeToLive < 0) {
|
|
161
|
-
timeToLive = 0;
|
|
162
|
-
}
|
|
163
|
-
if (timeToLive >= FCMTimeToLiveMax) {
|
|
164
|
-
timeToLive = FCMTimeToLiveMax;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
androidPayload.android.ttl = timeToLive;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
payloadToUse.data = androidPayload;
|
|
171
|
-
}
|
|
321
|
+
var fcmPayload = payloadConverter(requestData, pushType, timeStamp);
|
|
322
|
+
payloadToUse.data = _extends({}, fcmPayload, {
|
|
323
|
+
tokens: deviceTokens
|
|
324
|
+
});
|
|
172
325
|
|
|
173
326
|
return payloadToUse;
|
|
174
327
|
}
|
package/lib/ParsePushAdapter.js
CHANGED
|
@@ -67,7 +67,7 @@ var ParsePushAdapter = function () {
|
|
|
67
67
|
case 'tvos':
|
|
68
68
|
case 'osx':
|
|
69
69
|
if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) {
|
|
70
|
-
this.senderMap[pushType] = new _FCM2.default(pushConfig[pushType]);
|
|
70
|
+
this.senderMap[pushType] = new _FCM2.default(pushConfig[pushType], 'apple');
|
|
71
71
|
} else {
|
|
72
72
|
this.senderMap[pushType] = new _APNS2.default(pushConfig[pushType]);
|
|
73
73
|
}
|
|
@@ -75,7 +75,7 @@ var ParsePushAdapter = function () {
|
|
|
75
75
|
case 'android':
|
|
76
76
|
case 'fcm':
|
|
77
77
|
if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) {
|
|
78
|
-
this.senderMap[pushType] = new _FCM2.default(pushConfig[pushType]);
|
|
78
|
+
this.senderMap[pushType] = new _FCM2.default(pushConfig[pushType], 'android');
|
|
79
79
|
} else {
|
|
80
80
|
this.senderMap[pushType] = new _GCM2.default(pushConfig[pushType]);
|
|
81
81
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parse/push-adapter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Base parse-server-push-adapter",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -22,6 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"author": "Parse",
|
|
24
24
|
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@parse/node-apn": "6.0.1",
|
|
27
|
+
"@parse/node-gcm": "1.0.2",
|
|
28
|
+
"firebase-admin": "12.0.0",
|
|
29
|
+
"npmlog": "7.0.1",
|
|
30
|
+
"parse": "5.0.0"
|
|
31
|
+
},
|
|
25
32
|
"devDependencies": {
|
|
26
33
|
"@semantic-release/changelog": "5.0.1",
|
|
27
34
|
"@semantic-release/commit-analyzer": "8.0.1",
|
|
@@ -39,14 +46,7 @@
|
|
|
39
46
|
"nyc": "14.1.1",
|
|
40
47
|
"semantic-release": "17.4.6"
|
|
41
48
|
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@parse/node-apn": "6.0.1",
|
|
44
|
-
"@parse/node-gcm": "1.0.2",
|
|
45
|
-
"firebase-admin": "12.0.0",
|
|
46
|
-
"npmlog": "7.0.1",
|
|
47
|
-
"parse": "4.2.0"
|
|
48
|
-
},
|
|
49
49
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
50
|
+
"node": ">=18.0.0 <21"
|
|
51
51
|
}
|
|
52
52
|
}
|