@parse/push-adapter 6.4.0 → 6.5.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
@@ -19,8 +19,10 @@ The official Push Notification adapter for Parse Server. See [Parse Server Push
19
19
  - [Configure Parse Server](#configure-parse-server)
20
20
  - [Apple Push Options](#apple-push-options)
21
21
  - [Android Push Options](#android-push-options)
22
+ - [Firebase Cloud Messaging (FCM)](#firebase-cloud-messaging-fcm)
22
23
  - [Google Cloud Service Account Key](#google-cloud-service-account-key)
23
24
  - [Migration to FCM HTTP v1 API (June 2024)](#migration-to-fcm-http-v1-api-june-2024)
25
+ - [HTTP/1.1 Legacy Option](#http11-legacy-option)
24
26
  - [Expo Push Options](#expo-push-options)
25
27
  - [Bundled with Parse Server](#bundled-with-parse-server)
26
28
  - [Logging](#logging)
@@ -110,6 +112,10 @@ android: {
110
112
  }
111
113
  ```
112
114
 
115
+ ### Firebase Cloud Messaging (FCM)
116
+
117
+ This section contains some considerations when using FCM, regardless of the destination ecosystems the push notification is sent to.
118
+
113
119
  #### Google Cloud Service Account Key
114
120
 
115
121
  The Firebase console allows to easily create and download a Google Cloud service account key JSON file with the required permissions. Instead of setting `firebaseServiceAccount` to the path of the JSON file, you can provide an object representing a Google Cloud service account key:
@@ -139,6 +145,19 @@ android: {
139
145
  }
140
146
  ```
141
147
 
148
+ #### HTTP/1.1 Legacy Option
149
+
150
+ With the introduction of the FCM HTTP v1 API, support for HTTP/2 was added which provides faster throughput for push notifications. To use the older version HTTP/1.1 set `fcmEnableLegacyHttpTransport: true` in your push options.
151
+
152
+ Example options:
153
+
154
+ ```js
155
+ android: {
156
+ firebaseServiceAccount: __dirname + '/firebase.json',
157
+ fcmEnableLegacyHttpTransport: true
158
+ }
159
+ ```
160
+
142
161
  ### Expo Push Options
143
162
 
144
163
  Example options:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parse/push-adapter",
3
- "version": "6.4.0",
3
+ "version": "6.5.0",
4
4
  "description": "Base parse-server-push-adapter",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -8,6 +8,8 @@
8
8
  "src/"
9
9
  ],
10
10
  "scripts": {
11
+ "lint": "eslint --cache ./",
12
+ "lint:fix": "eslint --fix --cache ./",
11
13
  "test": "TESTING=1 c8 ./node_modules/.bin/jasmine"
12
14
  },
13
15
  "keywords": [
@@ -25,12 +27,13 @@
25
27
  "@parse/node-apn": "6.0.1",
26
28
  "@parse/node-gcm": "1.0.2",
27
29
  "expo-server-sdk": "3.10.0",
28
- "firebase-admin": "12.1.1",
30
+ "firebase-admin": "12.3.0",
29
31
  "npmlog": "7.0.1",
30
- "parse": "5.1.0",
32
+ "parse": "5.2.0",
31
33
  "web-push": "3.6.7"
32
34
  },
33
35
  "devDependencies": {
36
+ "@eslint/js": "9.6.0",
34
37
  "@semantic-release/changelog": "6.0.3",
35
38
  "@semantic-release/commit-analyzer": "13.0.0",
36
39
  "@semantic-release/git": "10.0.1",
@@ -39,6 +42,7 @@
39
42
  "@semantic-release/release-notes-generator": "14.0.1",
40
43
  "c8": "10.1.2",
41
44
  "codecov": "3.8.0",
45
+ "eslint": "9.6.0",
42
46
  "jasmine": "5.1.0",
43
47
  "jasmine-spec-reporter": "7.0.0",
44
48
  "semantic-release": "24.0.0"
package/src/APNS.js CHANGED
@@ -39,7 +39,7 @@ export class APNS {
39
39
  }
40
40
 
41
41
  // Create Provider from each arg-object
42
- for (let apnsArgs of apnsArgsList) {
42
+ for (const apnsArgs of apnsArgsList) {
43
43
 
44
44
  // rewrite bundleId to topic for backward-compatibility
45
45
  if (apnsArgs.bundleId) {
@@ -47,7 +47,7 @@ export class APNS {
47
47
  apnsArgs.topic = apnsArgs.bundleId
48
48
  }
49
49
 
50
- let provider = APNS._createProvider(apnsArgs);
50
+ const provider = APNS._createProvider(apnsArgs);
51
51
  this.providers.push(provider);
52
52
  }
53
53
 
@@ -70,42 +70,42 @@ export class APNS {
70
70
  * @returns {Object} A promise which is resolved immediately
71
71
  */
72
72
  send(data, allDevices) {
73
- let coreData = data && data.data;
73
+ const coreData = data && data.data;
74
74
  if (!coreData || !allDevices || !Array.isArray(allDevices)) {
75
75
  log.warn(LOG_PREFIX, 'invalid push payload');
76
76
  return;
77
77
  }
78
- let expirationTime = data['expiration_time'] || coreData['expiration_time'];
79
- let collapseId = data['collapse_id'] || coreData['collapse_id'];
80
- let pushType = data['push_type'] || coreData['push_type'];
81
- let priority = data['priority'] || coreData['priority'];
78
+ const expirationTime = data['expiration_time'] || coreData['expiration_time'];
79
+ const collapseId = data['collapse_id'] || coreData['collapse_id'];
80
+ const pushType = data['push_type'] || coreData['push_type'];
81
+ const priority = data['priority'] || coreData['priority'];
82
82
  let allPromises = [];
83
83
 
84
- let devicesPerAppIdentifier = {};
84
+ const devicesPerAppIdentifier = {};
85
85
 
86
86
  // Start by clustering the devices per appIdentifier
87
87
  allDevices.forEach(device => {
88
- let appIdentifier = device.appIdentifier;
88
+ const appIdentifier = device.appIdentifier;
89
89
  devicesPerAppIdentifier[appIdentifier] = devicesPerAppIdentifier[appIdentifier] || [];
90
90
  devicesPerAppIdentifier[appIdentifier].push(device);
91
91
  });
92
92
 
93
- for (let key in devicesPerAppIdentifier) {
94
- let devices = devicesPerAppIdentifier[key];
95
- let appIdentifier = devices[0].appIdentifier;
96
- let providers = this._chooseProviders(appIdentifier);
93
+ for (const key in devicesPerAppIdentifier) {
94
+ const devices = devicesPerAppIdentifier[key];
95
+ const appIdentifier = devices[0].appIdentifier;
96
+ const providers = this._chooseProviders(appIdentifier);
97
97
 
98
98
  // No Providers found
99
99
  if (!providers || providers.length === 0) {
100
- let errorPromises = devices.map(device => APNS._createErrorPromise(device.deviceToken, 'No Provider found'));
100
+ const errorPromises = devices.map(device => APNS._createErrorPromise(device.deviceToken, 'No Provider found'));
101
101
  allPromises = allPromises.concat(errorPromises);
102
102
  continue;
103
103
  }
104
104
 
105
- let headers = { expirationTime: expirationTime, topic: appIdentifier, collapseId: collapseId, pushType: pushType, priority: priority }
106
- let notification = APNS._generateNotification(coreData, headers);
105
+ const headers = { expirationTime: expirationTime, topic: appIdentifier, collapseId: collapseId, pushType: pushType, priority: priority }
106
+ const notification = APNS._generateNotification(coreData, headers);
107
107
  const deviceIds = devices.map(device => device.deviceToken);
108
- let promise = this.sendThroughProvider(notification, deviceIds, providers);
108
+ const promise = this.sendThroughProvider(notification, deviceIds, providers);
109
109
  allPromises.push(promise.then(this._handlePromise.bind(this)));
110
110
  }
111
111
 
@@ -117,25 +117,25 @@ export class APNS {
117
117
 
118
118
  sendThroughProvider(notification, devices, providers) {
119
119
  return providers[0]
120
- .send(notification, devices)
121
- .then((response) => {
122
- if (response.failed
120
+ .send(notification, devices)
121
+ .then((response) => {
122
+ if (response.failed
123
123
  && response.failed.length > 0
124
124
  && providers && providers.length > 1) {
125
- let devices = response.failed.map((failure) => { return failure.device; });
126
- // Reset the failures as we'll try next connection
127
- response.failed = [];
128
- return this.sendThroughProvider(notification,
129
- devices,
130
- providers.slice(1, providers.length)).then((retryResponse) => {
131
- response.failed = response.failed.concat(retryResponse.failed);
132
- response.sent = response.sent.concat(retryResponse.sent);
133
- return response;
134
- });
135
- } else {
125
+ const devices = response.failed.map((failure) => { return failure.device; });
126
+ // Reset the failures as we'll try next connection
127
+ response.failed = [];
128
+ return this.sendThroughProvider(notification,
129
+ devices,
130
+ providers.slice(1, providers.length)).then((retryResponse) => {
131
+ response.failed = response.failed.concat(retryResponse.failed);
132
+ response.sent = response.sent.concat(retryResponse.sent);
136
133
  return response;
137
- }
138
- });
134
+ });
135
+ } else {
136
+ return response;
137
+ }
138
+ });
139
139
  }
140
140
 
141
141
  static _validateAPNArgs(apnsArgs) {
@@ -154,7 +154,7 @@ export class APNS {
154
154
  throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is mssing for %j', apnsArgs);
155
155
  }
156
156
 
157
- let provider = new apn.Provider(apnsArgs);
157
+ const provider = new apn.Provider(apnsArgs);
158
158
 
159
159
  // Sets the topic on this provider
160
160
  provider.topic = apnsArgs.topic;
@@ -176,48 +176,46 @@ export class APNS {
176
176
  * @returns {Object} A apns Notification
177
177
  */
178
178
  static _generateNotification(coreData, headers) {
179
- let notification = new apn.Notification();
180
- let payload = {};
181
- for (let key in coreData) {
179
+ const notification = new apn.Notification();
180
+ const payload = {};
181
+ for (const key in coreData) {
182
182
  switch (key) {
183
- case 'aps':
184
- notification.aps = coreData.aps;
185
- break;
186
- case 'alert':
187
- notification.setAlert(coreData.alert);
188
- break;
189
- case 'title':
190
- notification.setTitle(coreData.title);
183
+ case 'aps':
184
+ notification.aps = coreData.aps;
185
+ break;
186
+ case 'alert':
187
+ notification.setAlert(coreData.alert);
188
+ break;
189
+ case 'title':
190
+ notification.setTitle(coreData.title);
191
+ break;
192
+ case 'badge':
193
+ notification.setBadge(coreData.badge);
194
+ break;
195
+ case 'sound':
196
+ notification.setSound(coreData.sound);
197
+ break;
198
+ case 'content-available':
199
+ notification.setContentAvailable(coreData['content-available'] === 1);
200
+ break;
201
+ case 'mutable-content':
202
+ notification.setMutableContent(coreData['mutable-content'] === 1);
203
+ break;
204
+ case 'targetContentIdentifier':
205
+ notification.setTargetContentIdentifier(coreData.targetContentIdentifier);
206
+ break;
207
+ case 'interruptionLevel':
208
+ notification.setInterruptionLevel(coreData.interruptionLevel);
209
+ break;
210
+ case 'category':
211
+ notification.setCategory(coreData.category);
212
+ break;
213
+ case 'threadId':
214
+ notification.setThreadId(coreData.threadId);
215
+ break;
216
+ default:
217
+ payload[key] = coreData[key];
191
218
  break;
192
- case 'badge':
193
- notification.setBadge(coreData.badge);
194
- break;
195
- case 'sound':
196
- notification.setSound(coreData.sound);
197
- break;
198
- case 'content-available':
199
- let isAvailable = coreData['content-available'] === 1;
200
- notification.setContentAvailable(isAvailable);
201
- break;
202
- case 'mutable-content':
203
- let isMutable = coreData['mutable-content'] === 1;
204
- notification.setMutableContent(isMutable);
205
- break;
206
- case 'targetContentIdentifier':
207
- notification.setTargetContentIdentifier(coreData.targetContentIdentifier);
208
- break;
209
- case 'interruptionLevel':
210
- notification.setInterruptionLevel(coreData.interruptionLevel);
211
- break;
212
- case 'category':
213
- notification.setCategory(coreData.category);
214
- break;
215
- case 'threadId':
216
- notification.setThreadId(coreData.threadId);
217
- break;
218
- default:
219
- payload[key] = coreData[key];
220
- break;
221
219
  }
222
220
  }
223
221
 
@@ -251,7 +249,7 @@ export class APNS {
251
249
  }*/
252
250
 
253
251
  // Otherwise we try to match the appIdentifier with topic on provider
254
- let qualifiedProviders = this.providers.filter((provider) => appIdentifier === provider.topic);
252
+ const qualifiedProviders = this.providers.filter((provider) => appIdentifier === provider.topic);
255
253
 
256
254
  if (qualifiedProviders.length > 0) {
257
255
  return qualifiedProviders;
@@ -263,7 +261,7 @@ export class APNS {
263
261
  }
264
262
 
265
263
  _handlePromise(response) {
266
- let promises = [];
264
+ const promises = [];
267
265
  response.sent.forEach((token) => {
268
266
  log.verbose(LOG_PREFIX, 'APNS transmitted to %s', token.device);
269
267
  promises.push(APNS._createSuccesfullPromise(token.device));
@@ -282,8 +280,8 @@ export class APNS {
282
280
  log.error(LOG_PREFIX, 'APNS error transmitting to device %s with status %s and reason %s', failure.device, failure.status, failure.response.reason);
283
281
  return APNS._createErrorPromise(failure.device, failure.response.reason);
284
282
  } else {
285
- log.error(LOG_PREFIX, 'APNS error transmitting to device with unkown error');
286
- return APNS._createErrorPromise(failure.device, 'Unkown status');
283
+ log.error(LOG_PREFIX, 'APNS error transmitting to device with unkown error');
284
+ return APNS._createErrorPromise(failure.device, 'Unkown status');
287
285
  }
288
286
  }
289
287
 
package/src/EXPO.js CHANGED
@@ -7,19 +7,19 @@ import { Expo } from 'expo-server-sdk';
7
7
  const LOG_PREFIX = 'parse-server-push-adapter EXPO';
8
8
 
9
9
  function expoResultToParseResponse(result) {
10
- if (result.status === 'ok') {
11
- return result;
12
- } else {
13
- // ParseServer looks for "error", and supports ceratin codes like 'NotRegistered' for
14
- // cleanup. Expo returns slighyly different ones so changing to match what is expected
15
- // This can be taken out if the responsibility gets moved to the adapter itself.
16
- const error = result.message === 'DeviceNotRegistered' ?
17
- 'NotRegistered' : result.message;
18
- return {
19
- error,
20
- ...result
21
- }
10
+ if (result.status === 'ok') {
11
+ return result;
12
+ } else {
13
+ // ParseServer looks for "error", and supports ceratin codes like 'NotRegistered' for
14
+ // cleanup. Expo returns slighyly different ones so changing to match what is expected
15
+ // This can be taken out if the responsibility gets moved to the adapter itself.
16
+ const error = result.message === 'DeviceNotRegistered' ?
17
+ 'NotRegistered' : result.message;
18
+ return {
19
+ error,
20
+ ...result
22
21
  }
22
+ }
23
23
  }
24
24
 
25
25
  export class EXPO {
@@ -60,7 +60,7 @@ export class EXPO {
60
60
 
61
61
  const resolvers = [];
62
62
  const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve)));
63
- let length = deviceTokens.length;
63
+ const length = deviceTokens.length;
64
64
 
65
65
  log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`);
66
66
 
package/src/FCM.js CHANGED
@@ -25,14 +25,26 @@ export default function FCM(args, pushType) {
25
25
  );
26
26
  }
27
27
 
28
+ const fcmEnableLegacyHttpTransport = typeof args.fcmEnableLegacyHttpTransport === 'boolean'
29
+ ? args.fcmEnableLegacyHttpTransport
30
+ : false;
31
+
28
32
  let app;
29
33
  if (getApps().length === 0) {
30
34
  app = initializeApp({ credential: cert(args.firebaseServiceAccount) });
31
35
  } else {
32
36
  app = getApp();
33
37
  }
38
+
34
39
  this.sender = getMessaging(app);
35
- this.pushType = pushType; // Push type is only used to remain backwards compatible with APNS and GCM
40
+
41
+ if (fcmEnableLegacyHttpTransport) {
42
+ this.sender.enableLegacyHttpTransport();
43
+ log.warn(LOG_PREFIX, 'Legacy HTTP/1.1 transport is enabled. This is a deprecated feature and support for this flag will be removed in the future.');
44
+ }
45
+
46
+ // Push type is only used to remain backwards compatible with APNS and GCM
47
+ this.pushType = pushType;
36
48
  }
37
49
 
38
50
  FCM.FCMRegistrationTokensMax = FCMRegistrationTokensMax;
@@ -142,14 +154,14 @@ function _APNSToFCMPayload(requestData) {
142
154
  coreData = requestData.data;
143
155
  }
144
156
 
145
- let expirationTime =
157
+ const expirationTime =
146
158
  requestData['expiration_time'] || coreData['expiration_time'];
147
- let collapseId = requestData['collapse_id'] || coreData['collapse_id'];
148
- let pushType = requestData['push_type'] || coreData['push_type'];
149
- let priority = requestData['priority'] || coreData['priority'];
159
+ const collapseId = requestData['collapse_id'] || coreData['collapse_id'];
160
+ const pushType = requestData['push_type'] || coreData['push_type'];
161
+ const priority = requestData['priority'] || coreData['priority'];
150
162
 
151
- let apnsPayload = { apns: { payload: { aps: {} } } };
152
- let headers = {};
163
+ const apnsPayload = { apns: { payload: { aps: {} } } };
164
+ const headers = {};
153
165
 
154
166
  // Set to alert by default if not set explicitly
155
167
  headers['apns-push-type'] = 'alert';
@@ -172,70 +184,70 @@ function _APNSToFCMPayload(requestData) {
172
184
  apnsPayload.apns.headers = headers;
173
185
  }
174
186
 
175
- for (let key in coreData) {
187
+ for (const key in coreData) {
176
188
  switch (key) {
177
- case 'aps':
178
- apnsPayload['apns']['payload']['aps'] = coreData.aps;
179
- break;
180
- case 'alert':
181
- if (typeof coreData.alert == 'object') {
182
- // When we receive a dictionary, use as is to remain
183
- // compatible with how the APNS.js + node-apn work
184
- apnsPayload['apns']['payload']['aps']['alert'] = coreData.alert;
185
- } else {
186
- // When we receive a value, prepare `alert` dictionary
187
- // and set its `body` property
188
- apnsPayload['apns']['payload']['aps']['alert'] = {};
189
- apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert;
190
- }
191
- break;
192
- case 'title':
193
- // Ensure the alert object exists before trying to assign the title
194
- // title always goes into the nested `alert` dictionary
195
- if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
196
- apnsPayload['apns']['payload']['aps']['alert'] = {};
197
- }
198
- apnsPayload['apns']['payload']['aps']['alert']['title'] = coreData.title;
199
- break;
200
- case 'badge':
201
- apnsPayload['apns']['payload']['aps']['badge'] = coreData.badge;
202
- break;
203
- case 'sound':
204
- apnsPayload['apns']['payload']['aps']['sound'] = coreData.sound;
205
- break;
206
- case 'content-available':
207
- apnsPayload['apns']['payload']['aps']['content-available'] =
189
+ case 'aps':
190
+ apnsPayload['apns']['payload']['aps'] = coreData.aps;
191
+ break;
192
+ case 'alert':
193
+ if (typeof coreData.alert == 'object') {
194
+ // When we receive a dictionary, use as is to remain
195
+ // compatible with how the APNS.js + node-apn work
196
+ apnsPayload['apns']['payload']['aps']['alert'] = coreData.alert;
197
+ } else {
198
+ // When we receive a value, prepare `alert` dictionary
199
+ // and set its `body` property
200
+ apnsPayload['apns']['payload']['aps']['alert'] = {};
201
+ apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert;
202
+ }
203
+ break;
204
+ case 'title':
205
+ // Ensure the alert object exists before trying to assign the title
206
+ // title always goes into the nested `alert` dictionary
207
+ if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
208
+ apnsPayload['apns']['payload']['aps']['alert'] = {};
209
+ }
210
+ apnsPayload['apns']['payload']['aps']['alert']['title'] = coreData.title;
211
+ break;
212
+ case 'badge':
213
+ apnsPayload['apns']['payload']['aps']['badge'] = coreData.badge;
214
+ break;
215
+ case 'sound':
216
+ apnsPayload['apns']['payload']['aps']['sound'] = coreData.sound;
217
+ break;
218
+ case 'content-available':
219
+ apnsPayload['apns']['payload']['aps']['content-available'] =
208
220
  coreData['content-available'];
209
- break;
210
- case 'mutable-content':
211
- apnsPayload['apns']['payload']['aps']['mutable-content'] =
221
+ break;
222
+ case 'mutable-content':
223
+ apnsPayload['apns']['payload']['aps']['mutable-content'] =
212
224
  coreData['mutable-content'];
213
- break;
214
- case 'targetContentIdentifier':
215
- apnsPayload['apns']['payload']['aps']['target-content-id'] =
225
+ break;
226
+ case 'targetContentIdentifier':
227
+ apnsPayload['apns']['payload']['aps']['target-content-id'] =
216
228
  coreData.targetContentIdentifier;
217
- break;
218
- case 'interruptionLevel':
219
- apnsPayload['apns']['payload']['aps']['interruption-level'] =
229
+ break;
230
+ case 'interruptionLevel':
231
+ apnsPayload['apns']['payload']['aps']['interruption-level'] =
220
232
  coreData.interruptionLevel;
221
- break;
222
- case 'category':
223
- apnsPayload['apns']['payload']['aps']['category'] = coreData.category;
224
- break;
225
- case 'threadId':
226
- apnsPayload['apns']['payload']['aps']['thread-id'] = coreData.threadId;
227
- break;
228
- case 'expiration_time': // Exclude header-related fields as these are set above
229
- break;
230
- case 'collapse_id':
231
- break;
232
- case 'push_type':
233
- break;
234
- case 'priority':
235
- break;
236
- default:
237
- apnsPayload['apns']['payload'][key] = coreData[key]; // Custom keys should be outside aps
238
- break;
233
+ break;
234
+ case 'category':
235
+ apnsPayload['apns']['payload']['aps']['category'] = coreData.category;
236
+ break;
237
+ case 'threadId':
238
+ apnsPayload['apns']['payload']['aps']['thread-id'] = coreData.threadId;
239
+ break;
240
+ case 'expiration_time': // Exclude header-related fields as these are set above
241
+ break;
242
+ case 'collapse_id':
243
+ break;
244
+ case 'push_type':
245
+ break;
246
+ case 'priority':
247
+ break;
248
+ default:
249
+ apnsPayload['apns']['payload'][key] = coreData[key]; // Custom keys should be outside aps
250
+ break;
239
251
  }
240
252
  }
241
253
  return apnsPayload;
package/src/GCM.js CHANGED
@@ -12,7 +12,7 @@ const GCMRegistrationTokensMax = 1000;
12
12
  export default function GCM(args) {
13
13
  if (typeof args !== 'object' || !args.apiKey) {
14
14
  throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
15
- 'GCM Configuration is invalid');
15
+ 'GCM Configuration is invalid');
16
16
  }
17
17
  this.sender = new gcm.Sender(args.apiKey, args.requestOptions);
18
18
  }
@@ -30,23 +30,23 @@ GCM.prototype.send = function(data, devices) {
30
30
  log.warn(LOG_PREFIX, 'invalid push payload');
31
31
  return;
32
32
  }
33
- let pushId = randomString(10);
33
+ const pushId = randomString(10);
34
34
  // Make a new array
35
- devices=devices.slice(0);
36
- let timestamp = Date.now();
35
+ devices = devices.slice(0);
36
+ const timestamp = Date.now();
37
37
  // For android, we can only have 1000 recepients per send, so we need to slice devices to
38
38
  // chunk if necessary
39
- let slices = sliceDevices(devices, GCM.GCMRegistrationTokensMax);
39
+ const slices = sliceDevices(devices, GCM.GCMRegistrationTokensMax);
40
40
  if (slices.length > 1) {
41
41
  log.verbose(LOG_PREFIX, `the number of devices exceeds ${GCMRegistrationTokensMax}`);
42
42
  // Make 1 send per slice
43
- let promises = slices.reduce((memo, slice) => {
44
- let promise = this.send(data, slice, timestamp);
43
+ const promises = slices.reduce((memo, slice) => {
44
+ const promise = this.send(data, slice, timestamp);
45
45
  memo.push(promise);
46
46
  return memo;
47
47
  }, [])
48
- return Promise.all(promises).then((results) => {
49
- let allResults = results.reduce((memo, result) => {
48
+ return Promise.all(promises).then((results) => {
49
+ const allResults = results.reduce((memo, result) => {
50
50
  return memo.concat(result);
51
51
  }, []);
52
52
  return Promise.resolve(allResults);
@@ -63,23 +63,23 @@ GCM.prototype.send = function(data, devices) {
63
63
  }
64
64
  // Generate gcm payload
65
65
  // PushId is not a formal field of GCM, but Parse Android SDK uses this field to deduplicate push notifications
66
- let gcmPayload = generateGCMPayload(data, pushId, timestamp, expirationTime);
66
+ const gcmPayload = generateGCMPayload(data, pushId, timestamp, expirationTime);
67
67
  // Make and send gcm request
68
- let message = new gcm.Message(gcmPayload);
68
+ const message = new gcm.Message(gcmPayload);
69
69
 
70
70
  // Build a device map
71
- let devicesMap = devices.reduce((memo, device) => {
71
+ const devicesMap = devices.reduce((memo, device) => {
72
72
  memo[device.deviceToken] = device;
73
73
  return memo;
74
74
  }, {});
75
75
 
76
- let deviceTokens = Object.keys(devicesMap);
76
+ const deviceTokens = Object.keys(devicesMap);
77
77
 
78
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'}`);
79
+ const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve)));
80
+ const registrationTokens = deviceTokens;
81
+ const length = registrationTokens.length;
82
+ log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`);
83
83
  this.sender.send(message, { registrationTokens: registrationTokens }, 5, (error, response) => {
84
84
  // example response:
85
85
  /*
@@ -97,13 +97,13 @@ GCM.prototype.send = function(data, devices) {
97
97
  } else {
98
98
  log.verbose(LOG_PREFIX, `GCM Response: %s`, JSON.stringify(response, null, 4));
99
99
  }
100
- let { results, multicast_id } = response || {};
100
+ const { results, multicast_id } = response || {};
101
101
  registrationTokens.forEach((token, index) => {
102
- let resolve = resolvers[index];
103
- let result = results ? results[index] : undefined;
104
- let device = devicesMap[token];
102
+ const resolve = resolvers[index];
103
+ const result = results ? results[index] : undefined;
104
+ const device = devicesMap[token];
105
105
  device.deviceType = 'android';
106
- let resolution = {
106
+ const resolution = {
107
107
  device,
108
108
  multicast_id,
109
109
  response: error || result,
@@ -128,7 +128,7 @@ GCM.prototype.send = function(data, devices) {
128
128
  * @returns {Object} A promise which is resolved after we get results from gcm
129
129
  */
130
130
  function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) {
131
- let payload = {
131
+ const payload = {
132
132
  priority: 'high'
133
133
  };
134
134
  payload.data = {
@@ -144,7 +144,7 @@ function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) {
144
144
  });
145
145
 
146
146
  if (expirationTime) {
147
- // The timeStamp and expiration is in milliseconds but gcm requires second
147
+ // The timeStamp and expiration is in milliseconds but gcm requires second
148
148
  let timeToLive = Math.floor((expirationTime - timeStamp) / 1000);
149
149
  if (timeToLive < 0) {
150
150
  timeToLive = 0;
@@ -164,7 +164,7 @@ function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) {
164
164
  * @returns {Array} An array which contaisn several arries of devices with fixed chunk size
165
165
  */
166
166
  function sliceDevices(devices, chunkSize) {
167
- let chunkDevices = [];
167
+ const chunkDevices = [];
168
168
  while (devices.length > 0) {
169
169
  chunkDevices.push(devices.splice(0, chunkSize));
170
170
  }
@@ -21,38 +21,38 @@ export default class ParsePushAdapter {
21
21
  this.feature = {
22
22
  immediatePush: true
23
23
  };
24
- let pushTypes = Object.keys(pushConfig);
24
+ const pushTypes = Object.keys(pushConfig);
25
25
 
26
- for (let pushType of pushTypes) {
26
+ for (const pushType of pushTypes) {
27
27
  // adapter may be passed as part of the parse-server initialization
28
28
  if (this.validPushTypes.indexOf(pushType) < 0 && pushType != 'adapter') {
29
29
  throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
30
- 'Push to ' + pushType + ' is not supported');
30
+ 'Push to ' + pushType + ' is not supported');
31
31
  }
32
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;
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
56
  }
57
57
  }
58
58
  }
@@ -66,16 +66,16 @@ export default class ParsePushAdapter {
66
66
  }
67
67
 
68
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];
69
+ const deviceMap = classifyInstallations(installations, this.validPushTypes);
70
+ const sendPromises = [];
71
+ for (const pushType in deviceMap) {
72
+ const sender = this.senderMap[pushType];
73
+ const devices = deviceMap[pushType];
74
74
 
75
75
  if(Array.isArray(devices) && devices.length > 0) {
76
76
  if (!sender) {
77
77
  log.verbose(LOG_PREFIX, `Can not find sender for push type ${pushType}, ${data}`)
78
- let results = devices.map((device) => {
78
+ const results = devices.map((device) => {
79
79
  return Promise.resolve({
80
80
  device,
81
81
  transmitted: false,
@@ -88,7 +88,7 @@ export default class ParsePushAdapter {
88
88
  }
89
89
  }
90
90
  }
91
- return Promise.all(sendPromises).then((promises) => {
91
+ return Promise.all(sendPromises).then((promises) => {
92
92
  // flatten all
93
93
  return [].concat.apply([], promises);
94
94
  })
@@ -8,16 +8,16 @@ import { randomBytes } from 'crypto';
8
8
  */
9
9
  export function classifyInstallations(installations, validPushTypes) {
10
10
  // Init deviceTokenMap, create a empty array for each valid pushType
11
- let deviceMap = {};
12
- for (let validPushType of validPushTypes) {
11
+ const deviceMap = {};
12
+ for (const validPushType of validPushTypes) {
13
13
  deviceMap[validPushType] = [];
14
14
  }
15
- for (let installation of installations) {
15
+ for (const installation of installations) {
16
16
  // No deviceToken, ignore
17
17
  if (!installation.deviceToken) {
18
18
  continue;
19
19
  }
20
- let devices = deviceMap[installation.pushType] || deviceMap[installation.deviceType] || null;
20
+ const devices = deviceMap[installation.pushType] || deviceMap[installation.deviceType] || null;
21
21
  if (Array.isArray(devices)) {
22
22
  devices.push({
23
23
  deviceToken: installation.deviceToken,
@@ -33,11 +33,11 @@ export function randomString(size) {
33
33
  if (size === 0) {
34
34
  throw new Error('Zero-length randomString is useless.');
35
35
  }
36
- let chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
36
+ const chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
37
37
  'abcdefghijklmnopqrstuvwxyz' +
38
38
  '0123456789');
39
39
  let objectId = '';
40
- let bytes = randomBytes(size);
40
+ const bytes = randomBytes(size);
41
41
  for (let i = 0; i < bytes.length; ++i) {
42
42
  objectId += chars[bytes.readUInt8(i) % chars.length];
43
43
  }
package/src/WEB.js CHANGED
@@ -9,7 +9,7 @@ const LOG_PREFIX = 'parse-server-push-adapter WEB';
9
9
  export class WEB {
10
10
  /**
11
11
  * Create a new WEB push adapter.
12
- *
12
+ *
13
13
  * @param {Object} args https://github.com/web-push-libs/web-push#api-reference
14
14
  */
15
15
  constructor(args) {
@@ -39,9 +39,9 @@ export class WEB {
39
39
  const deviceTokens = Object.keys(devicesMap);
40
40
 
41
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'}`);
42
+ const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve)));
43
+ const length = deviceTokens.length;
44
+ log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`);
45
45
 
46
46
  const response = await WEB.sendNotifications(coreData, deviceTokens, this.options);
47
47
  const { results, sent, failed } = response;
package/src/index.js CHANGED
@@ -3,11 +3,13 @@
3
3
  // PushAdapter, it uses GCM for android push, APNS for ios push.
4
4
  // WEB for web push.
5
5
  import log from 'npmlog';
6
+ import { booleanParser } from './utils.js';
6
7
 
7
- /* istanbul ignore if */
8
- if (process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER_PUSH_ADAPTER) {
8
+ /* c8 ignore start */
9
+ if (booleanParser(process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER_PUSH_ADAPTER)) {
9
10
  log.level = 'verbose';
10
11
  }
12
+ /* c8 ignore stop */
11
13
 
12
14
  import ParsePushAdapter from './ParsePushAdapter.js';
13
15
  import GCM from './GCM.js';
package/src/utils.js ADDED
@@ -0,0 +1,10 @@
1
+ function booleanParser(opt) {
2
+ if (opt == true || opt == 'true' || opt == '1') {
3
+ return true;
4
+ }
5
+ return false;
6
+ }
7
+
8
+ export {
9
+ booleanParser,
10
+ }