@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 CHANGED
@@ -5,6 +5,8 @@
5
5
  [![Coverage](https://img.shields.io/codecov/c/github/parse-community/parse-server-push-adapter/master.svg)](https://codecov.io/github/parse-community/parse-server-push-adapter?branch=master)
6
6
  [![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-server-push-adapter/releases)
7
7
 
8
+ [![Node Version](https://img.shields.io/badge/nodejs-18,_20-green.svg?logo=node.js&style=flat)](https://nodejs.org)
9
+
8
10
  [![npm latest version](https://img.shields.io/npm/v/@parse/push-adapter.svg)](https://www.npmjs.com/package/@parse/push-adapter)
9
11
 
10
12
  ---
package/lib/FCM.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";
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
- var fcmPayload = generateFCMPayload(data, pushId, timestamp, deviceTokens);
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(sendToDeviceSlice)).catch(function (err) {
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
- // Use rawPayload instead of the GCM implementation if it exists
135
- if (requestData.hasOwnProperty('rawPayload')) {
136
- payloadToUse.data = _extends({}, requestData.rawPayload, {
137
- tokens: deviceTokens
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
  }
@@ -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": "5.1.1",
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": ">= 14"
50
+ "node": ">=18.0.0 <21"
51
51
  }
52
52
  }