@parse/push-adapter 6.1.0 → 6.2.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
@@ -20,6 +20,7 @@ The official Push Notification adapter for Parse Server. See [Parse Server Push
20
20
  - [Using a Custom Version on Parse Server](#using-a-custom-version-on-parse-server)
21
21
  - [Install Push Adapter](#install-push-adapter)
22
22
  - [Configure Parse Server](#configure-parse-server)
23
+ - [Expo Push Options](#expo-push-options)
23
24
 
24
25
  # Silent Notifications
25
26
 
@@ -57,16 +58,32 @@ const parseServerOptions = {
57
58
  push: {
58
59
  adapter: new PushAdapter({
59
60
  ios: {
60
- /* Apple push notification options */
61
+ /* Apple push options */
61
62
  },
62
63
  android: {
63
64
  /* Android push options */
64
- }
65
+ },
65
66
  web: {
66
67
  /* Web push options */
67
- }
68
- })
68
+ },
69
+ expo: {
70
+ /* Expo push options */
71
+ },
72
+ }),
69
73
  },
70
74
  /* Other Parse Server options */
71
75
  }
72
76
  ```
77
+
78
+ ### Expo Push Options
79
+
80
+ Example options:
81
+
82
+ ```js
83
+ expo: {
84
+ accessToken: '<EXPO_ACCESS_TOKEN>',
85
+ },
86
+ ```
87
+
88
+ For more information see the [Expo docs](https://docs.expo.dev/push-notifications/overview/).
89
+
package/lib/EXPO.js ADDED
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EXPO = undefined;
7
+
8
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
9
+
10
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
11
+
12
+ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
13
+
14
+ var _parse = require('parse');
15
+
16
+ var _parse2 = _interopRequireDefault(_parse);
17
+
18
+ var _npmlog = require('npmlog');
19
+
20
+ var _npmlog2 = _interopRequireDefault(_npmlog);
21
+
22
+ var _expoServerSdk = require('expo-server-sdk');
23
+
24
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25
+
26
+ function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
27
+
28
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
29
+
30
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
31
+
32
+ var LOG_PREFIX = 'parse-server-push-adapter EXPO';
33
+
34
+ function expoResultToParseResponse(result) {
35
+ if (result.status === 'ok') {
36
+ return result;
37
+ } else {
38
+ // ParseServer looks for "error", and supports ceratin codes like 'NotRegistered' for
39
+ // cleanup. Expo returns slighyly different ones so changing to match what is expected
40
+ // This can be taken out if the responsibility gets moved to the adapter itself.
41
+ var error = result.message === 'DeviceNotRegistered' ? 'NotRegistered' : result.message;
42
+ return _extends({
43
+ error: error
44
+ }, result);
45
+ }
46
+ }
47
+
48
+ var EXPO = exports.EXPO = function () {
49
+ /**
50
+ * Create a new EXPO push adapter. Based on Web Adapter.
51
+ *
52
+ * @param {Object} args https://github.com/expo/expo-server-sdk-node / https://docs.expo.dev/push-notifications/sending-notifications/
53
+ */
54
+ function EXPO(args) {
55
+ _classCallCheck(this, EXPO);
56
+
57
+ this.expo = undefined;
58
+
59
+ if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) !== 'object') {
60
+ throw new _parse2.default.Error(_parse2.default.Error.PUSH_MISCONFIGURED, 'EXPO Push Configuration is invalid');
61
+ }
62
+
63
+ this.expo = new _expoServerSdk.Expo(args);
64
+ this.options = args;
65
+ }
66
+
67
+ /**
68
+ * Send Expo push notification request.
69
+ *
70
+ * @param {Object} data The data we need to send, the format is the same with api request body
71
+ * @param {Array} devices An array of devices
72
+ * @returns {Object} A promise which is resolved immediately
73
+ */
74
+
75
+
76
+ _createClass(EXPO, [{
77
+ key: 'send',
78
+ value: function () {
79
+ var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(data, devices) {
80
+ var coreData, devicesMap, deviceTokens, resolvers, promises, length, response;
81
+ return regeneratorRuntime.wrap(function _callee$(_context) {
82
+ while (1) {
83
+ switch (_context.prev = _context.next) {
84
+ case 0:
85
+ coreData = data && data.data;
86
+
87
+ if (!(!coreData || !devices || !Array.isArray(devices))) {
88
+ _context.next = 4;
89
+ break;
90
+ }
91
+
92
+ _npmlog2.default.warn(LOG_PREFIX, 'invalid push payload');
93
+ return _context.abrupt('return');
94
+
95
+ case 4:
96
+ devicesMap = devices.reduce(function (memo, device) {
97
+ memo[device.deviceToken] = device;
98
+ return memo;
99
+ }, {});
100
+ deviceTokens = Object.keys(devicesMap);
101
+ resolvers = [];
102
+ promises = deviceTokens.map(function () {
103
+ return new Promise(function (resolve) {
104
+ return resolvers.push(resolve);
105
+ });
106
+ });
107
+ length = deviceTokens.length;
108
+
109
+
110
+ _npmlog2.default.verbose(LOG_PREFIX, 'sending to ' + length + ' ' + (length > 1 ? 'devices' : 'device'));
111
+
112
+ _context.next = 12;
113
+ return this.sendNotifications(coreData, deviceTokens);
114
+
115
+ case 12:
116
+ response = _context.sent;
117
+
118
+
119
+ _npmlog2.default.verbose(LOG_PREFIX, 'EXPO Response: %d sent', response.length);
120
+
121
+ deviceTokens.forEach(function (token, index) {
122
+ var resolve = resolvers[index];
123
+ var result = response[index];
124
+ var device = devicesMap[token];
125
+ var resolution = {
126
+ transmitted: result.status === 'ok',
127
+ device: _extends({}, device, {
128
+ pushType: 'expo'
129
+ }),
130
+ response: expoResultToParseResponse(result)
131
+ };
132
+ resolve(resolution);
133
+ });
134
+ return _context.abrupt('return', Promise.all(promises));
135
+
136
+ case 16:
137
+ case 'end':
138
+ return _context.stop();
139
+ }
140
+ }
141
+ }, _callee, this);
142
+ }));
143
+
144
+ function send(_x, _x2) {
145
+ return _ref.apply(this, arguments);
146
+ }
147
+
148
+ return send;
149
+ }()
150
+
151
+ /**
152
+ * Send multiple Expo push notification request.
153
+ *
154
+ * @param {Object} payload The data we need to send, the format is the same with api request body
155
+ * @param {Array} deviceTokens An array of devicesTokens
156
+ * @param {Object} options The options for the request
157
+ * @returns {Object} A promise which is resolved immediately
158
+ */
159
+
160
+ }, {
161
+ key: 'sendNotifications',
162
+ value: function () {
163
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(_ref3, deviceTokens) {
164
+ var alert = _ref3.alert,
165
+ body = _ref3.body,
166
+ payload = _objectWithoutProperties(_ref3, ['alert', 'body']);
167
+
168
+ var messages;
169
+ return regeneratorRuntime.wrap(function _callee2$(_context2) {
170
+ while (1) {
171
+ switch (_context2.prev = _context2.next) {
172
+ case 0:
173
+ messages = deviceTokens.map(function (token) {
174
+ return _extends({
175
+ to: token,
176
+ body: body || alert
177
+ }, payload);
178
+ });
179
+ _context2.next = 3;
180
+ return this.expo.sendPushNotificationsAsync(messages);
181
+
182
+ case 3:
183
+ return _context2.abrupt('return', _context2.sent);
184
+
185
+ case 4:
186
+ case 'end':
187
+ return _context2.stop();
188
+ }
189
+ }
190
+ }, _callee2, this);
191
+ }));
192
+
193
+ function sendNotifications(_x3, _x4) {
194
+ return _ref2.apply(this, arguments);
195
+ }
196
+
197
+ return sendNotifications;
198
+ }()
199
+ }]);
200
+
201
+ return EXPO;
202
+ }();
203
+
204
+ exports.default = EXPO;
package/lib/FCM.js CHANGED
@@ -162,15 +162,20 @@ function _APNSToFCMPayload(requestData) {
162
162
  apnsPayload['apns']['payload']['aps'] = coreData.aps;
163
163
  break;
164
164
  case 'alert':
165
- if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
165
+ if (_typeof(coreData.alert) == 'object') {
166
+ // When we receive a dictionary, use as is to remain
167
+ // compatible with how the APNS.js + node-apn work
168
+ apnsPayload['apns']['payload']['aps']['alert'] = coreData.alert;
169
+ } else {
170
+ // When we receive a value, prepare `alert` dictionary
171
+ // and set its `body` property
166
172
  apnsPayload['apns']['payload']['aps']['alert'] = {};
173
+ apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert;
167
174
  }
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
175
  break;
172
176
  case 'title':
173
177
  // Ensure the alert object exists before trying to assign the title
178
+ // title always goes into the nested `alert` dictionary
174
179
  if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) {
175
180
  apnsPayload['apns']['payload']['aps']['alert'] = {};
176
181
  }
@@ -217,7 +222,8 @@ function _APNSToFCMPayload(requestData) {
217
222
  return apnsPayload;
218
223
  }
219
224
 
220
- function _GCMToFCMPayload(requestData, timeStamp) {
225
+ function _GCMToFCMPayload(requestData, pushId, timeStamp) {
226
+
221
227
  var androidPayload = {
222
228
  android: {
223
229
  priority: 'high'
@@ -257,7 +263,11 @@ function _GCMToFCMPayload(requestData, timeStamp) {
257
263
  }
258
264
  }
259
265
 
260
- androidPayload.android.data = requestData.data;
266
+ androidPayload.android.data = {
267
+ push_id: pushId,
268
+ time: new Date(timeStamp).toISOString(),
269
+ data: JSON.stringify(requestData.data)
270
+ };
261
271
  }
262
272
 
263
273
  if (requestData['expiration_time']) {
@@ -283,10 +293,11 @@ function _GCMToFCMPayload(requestData, timeStamp) {
283
293
  * If the key rawPayload is present in the requestData, a raw payload will be used. Otherwise, conversion is done.
284
294
  * @param {Object} requestData The request body
285
295
  * @param {String} pushType Either apple or android.
286
- * @param {Number} timeStamp Used during GCM payload conversion for ttl
296
+ * @param {String} pushId Used during GCM payload conversion, required by Parse Android SDK.
297
+ * @param {Number} timeStamp Used during GCM payload conversion for ttl, required by Parse Android SDK.
287
298
  * @returns {Object} A FCMv1-compatible payload.
288
299
  */
289
- function payloadConverter(requestData, pushType, timeStamp) {
300
+ function payloadConverter(requestData, pushType, pushId, timeStamp) {
290
301
  if (requestData.hasOwnProperty('rawPayload')) {
291
302
  return requestData.rawPayload;
292
303
  }
@@ -294,7 +305,7 @@ function payloadConverter(requestData, pushType, timeStamp) {
294
305
  if (pushType === 'apple') {
295
306
  return _APNSToFCMPayload(requestData);
296
307
  } else if (pushType === 'android') {
297
- return _GCMToFCMPayload(requestData, timeStamp);
308
+ return _GCMToFCMPayload(requestData, pushId, timeStamp);
298
309
  } else {
299
310
  throw new _parse2.default.Error(_parse2.default.Error.PUSH_MISCONFIGURED, 'Unsupported push type, apple or android only.');
300
311
  }
@@ -313,12 +324,10 @@ function generateFCMPayload(requestData, pushId, timeStamp, deviceTokens, pushTy
313
324
  delete requestData['where'];
314
325
 
315
326
  var payloadToUse = {
316
- data: {},
317
- push_id: pushId,
318
- time: new Date(timeStamp).toISOString()
327
+ data: {}
319
328
  };
320
329
 
321
- var fcmPayload = payloadConverter(requestData, pushType, timeStamp);
330
+ var fcmPayload = payloadConverter(requestData, pushType, pushId, timeStamp);
322
331
  payloadToUse.data = _extends({}, fcmPayload, {
323
332
  tokens: deviceTokens
324
333
  });
@@ -30,6 +30,10 @@ var _WEB = require('./WEB');
30
30
 
31
31
  var _WEB2 = _interopRequireDefault(_WEB);
32
32
 
33
+ var _EXPO = require('./EXPO');
34
+
35
+ var _EXPO2 = _interopRequireDefault(_EXPO);
36
+
33
37
  var _PushAdapterUtils = require('./PushAdapterUtils');
34
38
 
35
39
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -46,7 +50,7 @@ var ParsePushAdapter = function () {
46
50
 
47
51
  this.supportsPushTracking = true;
48
52
 
49
- this.validPushTypes = ['ios', 'osx', 'tvos', 'android', 'fcm', 'web'];
53
+ this.validPushTypes = ['ios', 'osx', 'tvos', 'android', 'fcm', 'web', 'expo'];
50
54
  this.senderMap = {};
51
55
  // used in PushController for Dashboard Features
52
56
  this.feature = {
@@ -79,6 +83,9 @@ var ParsePushAdapter = function () {
79
83
  case 'web':
80
84
  this.senderMap[pushType] = new _WEB2.default(pushConfig[pushType]);
81
85
  break;
86
+ case 'expo':
87
+ this.senderMap[pushType] = new _EXPO2.default(pushConfig[pushType]);
88
+ break;
82
89
  case 'android':
83
90
  case 'fcm':
84
91
  if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) {
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
- exports.utils = exports.WEB = exports.GCM = exports.APNS = exports.ParsePushAdapter = undefined;
9
+ exports.utils = exports.EXPO = exports.WEB = exports.GCM = exports.APNS = exports.ParsePushAdapter = undefined;
10
10
 
11
11
  var _npmlog = require('npmlog');
12
12
 
@@ -28,6 +28,10 @@ var _WEB = require('./WEB');
28
28
 
29
29
  var _WEB2 = _interopRequireDefault(_WEB);
30
30
 
31
+ var _EXPO = require('./EXPO');
32
+
33
+ var _EXPO2 = _interopRequireDefault(_EXPO);
34
+
31
35
  var _PushAdapterUtils = require('./PushAdapterUtils');
32
36
 
33
37
  var utils = _interopRequireWildcard(_PushAdapterUtils);
@@ -44,4 +48,5 @@ exports.ParsePushAdapter = _ParsePushAdapter2.default;
44
48
  exports.APNS = _APNS2.default;
45
49
  exports.GCM = _GCM2.default;
46
50
  exports.WEB = _WEB2.default;
51
+ exports.EXPO = _EXPO2.default;
47
52
  exports.utils = utils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parse/push-adapter",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "Base parse-server-push-adapter",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -25,7 +25,8 @@
25
25
  "dependencies": {
26
26
  "@parse/node-apn": "6.0.1",
27
27
  "@parse/node-gcm": "1.0.2",
28
- "firebase-admin": "12.0.0",
28
+ "expo-server-sdk": "3.10.0",
29
+ "firebase-admin": "12.1.0",
29
30
  "npmlog": "7.0.1",
30
31
  "parse": "5.0.0",
31
32
  "web-push": "3.6.7"