@parse/push-adapter 6.2.0 → 6.4.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,46 +5,29 @@
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)
8
+ [![Node Version](https://img.shields.io/badge/nodejs-18,_20,_22-green.svg?logo=node.js&style=flat)](https://nodejs.org)
9
9
 
10
10
  [![npm latest version](https://img.shields.io/npm/v/@parse/push-adapter.svg)](https://www.npmjs.com/package/@parse/push-adapter)
11
11
 
12
12
  ---
13
13
 
14
- The official Push Notification adapter for Parse Server. See [Parse Server Push Configuration](http://docs.parseplatform.org/parse-server/guide/#push-notifications) for more details.
14
+ The official Push Notification adapter for Parse Server. See [Parse Server Push Configuration](http://docs.parseplatform.org/parse-server/guide/#push-notifications) for more details.
15
15
 
16
16
  ---
17
17
 
18
- - [Silent Notifications](#silent-notifications)
18
+ - [Installation](#installation)
19
+ - [Configure Parse Server](#configure-parse-server)
20
+ - [Apple Push Options](#apple-push-options)
21
+ - [Android Push Options](#android-push-options)
22
+ - [Google Cloud Service Account Key](#google-cloud-service-account-key)
23
+ - [Migration to FCM HTTP v1 API (June 2024)](#migration-to-fcm-http-v1-api-june-2024)
24
+ - [Expo Push Options](#expo-push-options)
25
+ - [Bundled with Parse Server](#bundled-with-parse-server)
19
26
  - [Logging](#logging)
20
- - [Using a Custom Version on Parse Server](#using-a-custom-version-on-parse-server)
21
- - [Install Push Adapter](#install-push-adapter)
22
- - [Configure Parse Server](#configure-parse-server)
23
- - [Expo Push Options](#expo-push-options)
24
27
 
25
- # Silent Notifications
28
+ ## Installation
26
29
 
27
- If you have migrated from parse.com and you are seeing situations where silent (newsstand-like presentless) notifications are failing to deliver please ensure that your payload is setting the content-available attribute to Int(1) and not "1" This value will be explicitly checked.
28
-
29
- # Logging
30
-
31
- You can enable verbose logging with environment variables:
32
-
33
- ```
34
- VERBOSE=1
35
-
36
- or
37
-
38
- VERBOSE_PARSE_SERVER_PUSH_ADAPTER=1
39
- ```
40
-
41
- This will produce a more verbose output for all the push sending attempts
42
-
43
- # Using a Custom Version on Parse Server
44
-
45
- ## Install Push Adapter
46
-
47
- ```
30
+ ```shell
48
31
  npm install --save @parse/push-adapter@<VERSION>
49
32
  ```
50
33
 
@@ -53,25 +36,106 @@ Replace `<VERSION>` with the version you want to install.
53
36
  ## Configure Parse Server
54
37
 
55
38
  ```js
56
- const PushAdapter = require('@parse/push-adapter').default;
39
+ import { ParsePushAdapter } from '@parse/push-adapter';
40
+
41
+ // For CommonJS replace the import statemtent above with the following line:
42
+ // const ParsePushAdapter = require('@parse/push-adapter').default;
43
+
57
44
  const parseServerOptions = {
58
45
  push: {
59
- adapter: new PushAdapter({
46
+ adapter: new ParsePushAdapter({
60
47
  ios: {
61
- /* Apple push options */
48
+ // Apple push options
62
49
  },
63
50
  android: {
64
- /* Android push options */
51
+ // Android push options
65
52
  },
66
53
  web: {
67
- /* Web push options */
54
+ // Web push options
68
55
  },
69
56
  expo: {
70
- /* Expo push options */
71
- },
72
- }),
57
+ // Expo push options
58
+ }
59
+ })
60
+ }
61
+ // Other Parse Server options
62
+ };
63
+ ```
64
+
65
+ ### Apple Push Options
66
+
67
+ Parse Server Push Adapter currently supports these types of Apple ecosystems:
68
+
69
+ - `ios`: iPhone, iPad, and iPod touch apps
70
+ - `osx`: macOS, and macCatalyst apps
71
+ - `tvos`: tvOS apps
72
+
73
+ Delivering push notifications to Apple devices can be done either via Apple Push Notification Service (APNS), or via Firebase Cloud Messaging (FMC). Note that each category of Apple devices require their own configuration section:
74
+
75
+ - APNS requires a private key that can be downloaded from the Apple Developer Center at https://developer.apple.com/account under _Certificates > Identifiers & Profiles._ The adapter options also require the app ID and team ID which can be found there.
76
+ - FCM requires a private key that can be downloaded from the Firebase Console at https://console.firebase.google.com in your project under _Settings > Cloud Messaging._
77
+
78
+ Example options:
79
+
80
+ Both services (APNS, FCM) can be used in combination for different Apple ecosystems.
81
+
82
+ ```js
83
+ ios: {
84
+ // Deliver push notifications to iOS devices via APNS
85
+ token: {
86
+ key: __dirname + '/apns.p8',
87
+ keyId: '<APNS_KEY_ID>',
88
+ teamId: '<APNS_TEAM_ID>'
73
89
  },
74
- /* Other Parse Server options */
90
+ topic: '<BUNDLE_IDENTIFIER>',
91
+ production: true
92
+ },
93
+ osx: {
94
+ // Deliver push notifications to macOS devices via FCM
95
+ firebaseServiceAccount: __dirname + '/firebase.json'
96
+ }
97
+ ```
98
+
99
+ ### Android Push Options
100
+
101
+ Delivering push notifications to Android devices can be done via Firebase Cloud Messaging (FCM):
102
+
103
+ - FCM requires a private key that can be downloaded from the Firebase Console at https://console.firebase.google.com in your project under _Settings > Cloud Messaging._
104
+
105
+ Example options:
106
+
107
+ ```js
108
+ android: {
109
+ firebaseServiceAccount: __dirname + '/firebase.json'
110
+ }
111
+ ```
112
+
113
+ #### Google Cloud Service Account Key
114
+
115
+ 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:
116
+
117
+ ```js
118
+ android: {
119
+ firebaseServiceAccount: {
120
+ projectId: '<PROJECT_ID>',
121
+ clientEmail: 'example@<PROJECT_ID>.iam.gserviceaccount.com',
122
+ privateKey: '-----BEGIN PRIVATE KEY-----<KEY>-----END PRIVATE KEY-----\n'
123
+ }
124
+ }
125
+ ```
126
+
127
+ This can be helpful if you are already managing credentials to Google Cloud APIs in other parts of your code and you want to reuse these credentials, or if you want to manage credentials on a more granular level directly in Google Cloud. Make sure that the service account has the permission `cloudmessaging.messages.create` which is for example part of role `Firebase Cloud Messaging API Admin`.
128
+
129
+ #### Migration to FCM HTTP v1 API (June 2024)
130
+
131
+ ⚠️ Sending push notifications to Android devices via the FCM legacy API was deprecated on June 20 2023 and was announced to be decommissioned in June 2024. See [Google docs](https://firebase.google.com/docs/cloud-messaging/migrate-v1). To send push notifications to the newer FCM HTTP v1 API you need to update your existing push configuration for Android by replacing the key `apiKey` with `firebaseServiceAccount`.
132
+
133
+ Example options (deprecated):
134
+
135
+ ```js
136
+ android: {
137
+ // Deliver push notifications via FCM legacy API (deprecated)
138
+ apiKey: '<API_KEY>'
75
139
  }
76
140
  ```
77
141
 
@@ -81,9 +145,40 @@ Example options:
81
145
 
82
146
  ```js
83
147
  expo: {
84
- accessToken: '<EXPO_ACCESS_TOKEN>',
85
- },
148
+ accessToken: '<EXPO_ACCESS_TOKEN>'
149
+ }
86
150
  ```
87
151
 
88
152
  For more information see the [Expo docs](https://docs.expo.dev/push-notifications/overview/).
89
-
153
+
154
+ ## Bundled with Parse Server
155
+
156
+ Parse Server already comes bundled with a specific version of the push adapter. This installation is only necessary when customizing the push adapter version that should be used by Parse Server. When using a customized version of the push adapter, ensure that it's compatible with the version of Parse Server you are using.
157
+
158
+ When using the bundled version, it is not necessary to initialize the push adapter in code and the push options are configured directly in the `push` key, without the nested `adapter` key:
159
+
160
+ ```js
161
+ const parseServerOptions = {
162
+ push: {
163
+ ios: {
164
+ // Apple push options
165
+ }
166
+ // Other push options
167
+ }
168
+ // Other Parse Server options
169
+ };
170
+ ```
171
+
172
+ ## Logging
173
+
174
+ You can enable verbose logging to produce a more detailed output for all push sending attempts with the following environment variables:
175
+
176
+ ```js
177
+ VERBOSE=1
178
+ ```
179
+
180
+ or
181
+
182
+ ```js
183
+ VERBOSE_PARSE_SERVER_PUSH_ADAPTER=1
184
+ ```
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@parse/push-adapter",
3
- "version": "6.2.0",
3
+ "version": "6.4.0",
4
4
  "description": "Base parse-server-push-adapter",
5
- "main": "lib/index.js",
5
+ "main": "src/index.js",
6
+ "type": "module",
6
7
  "files": [
7
- "lib/"
8
+ "src/"
8
9
  ],
9
10
  "scripts": {
10
- "build": "./node_modules/.bin/babel src/ -d lib/",
11
- "test": "TESTING=1 nyc ./node_modules/.bin/jasmine",
12
- "prepare": "npm run build"
11
+ "test": "TESTING=1 c8 ./node_modules/.bin/jasmine"
13
12
  },
14
13
  "keywords": [
15
14
  "parse-server",
@@ -26,29 +25,25 @@
26
25
  "@parse/node-apn": "6.0.1",
27
26
  "@parse/node-gcm": "1.0.2",
28
27
  "expo-server-sdk": "3.10.0",
29
- "firebase-admin": "12.1.0",
28
+ "firebase-admin": "12.1.1",
30
29
  "npmlog": "7.0.1",
31
- "parse": "5.0.0",
30
+ "parse": "5.1.0",
32
31
  "web-push": "3.6.7"
33
32
  },
34
33
  "devDependencies": {
35
- "@semantic-release/changelog": "5.0.1",
36
- "@semantic-release/commit-analyzer": "8.0.1",
37
- "@semantic-release/git": "9.0.0",
38
- "@semantic-release/github": "7.2.3",
39
- "@semantic-release/npm": "7.1.3",
40
- "@semantic-release/release-notes-generator": "9.0.3",
41
- "babel-cli": "6.26.0",
42
- "babel-core": "6.26.3",
43
- "babel-preset-es2015": "6.24.1",
44
- "babel-preset-stage-0": "6.24.1",
45
- "codecov": "3.7.1",
46
- "jasmine": "2.8.0",
47
- "jasmine-spec-reporter": "4.0.0",
48
- "nyc": "14.1.1",
49
- "semantic-release": "17.4.6"
34
+ "@semantic-release/changelog": "6.0.3",
35
+ "@semantic-release/commit-analyzer": "13.0.0",
36
+ "@semantic-release/git": "10.0.1",
37
+ "@semantic-release/github": "10.0.7",
38
+ "@semantic-release/npm": "12.0.1",
39
+ "@semantic-release/release-notes-generator": "14.0.1",
40
+ "c8": "10.1.2",
41
+ "codecov": "3.8.0",
42
+ "jasmine": "5.1.0",
43
+ "jasmine-spec-reporter": "7.0.0",
44
+ "semantic-release": "24.0.0"
50
45
  },
51
46
  "engines": {
52
- "node": ">=18.0.0 <21"
47
+ "node": "18 || 20 || 22"
53
48
  }
54
49
  }
package/src/APNS.js ADDED
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+ import apn from '@parse/node-apn';
3
+ import Parse from 'parse';
4
+ import log from 'npmlog';
5
+
6
+ const LOG_PREFIX = 'parse-server-push-adapter APNS';
7
+
8
+ export class APNS {
9
+
10
+ /**
11
+ * Create a new provider for the APN service.
12
+ * @constructor
13
+ * @param {Object|Array} args An argument or a list of arguments to config APNS provider
14
+ * @param {Object} args.token {Object} Configuration for Provider Authentication Tokens. (Defaults to: null i.e. fallback to Certificates)
15
+ * @param {Buffer|String} args.token.key The filename of the provider token key (as supplied by Apple) to load from disk, or a Buffer/String containing the key data.
16
+ * @param {String} args.token.keyId The ID of the key issued by Apple
17
+ * @param {String} args.token.teamId ID of the team associated with the provider token key
18
+ * @param {Buffer|String} args.cert The filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data.
19
+ * @param {Buffer|String} args.key {Buffer|String} The filename of the connection key to load from disk, or a Buffer/String containing the key data.
20
+ * @param {Buffer|String} args.pfx path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will always be used instead of certificate and key above.
21
+ * @param {String} args.passphrase The passphrase for the provider key, if required
22
+ * @param {Boolean} args.production Specifies which environment to connect to: Production (if true) or Sandbox
23
+ * @param {String} args.topic Specififies an App-Id for this Provider
24
+ * @param {String} args.bundleId DEPRECATED: Specifies an App-ID for this Provider
25
+ * @param {Number} args.connectionRetryLimit The maximum number of connection failures that will be tolerated before apn.Provider will "give up". (Defaults to: 3)
26
+ */
27
+ constructor(args) {
28
+ // Define class members
29
+ this.providers = [];
30
+
31
+ // Since for ios, there maybe multiple cert/key pairs, typePushConfig can be an array.
32
+ let apnsArgsList = [];
33
+ if (Array.isArray(args)) {
34
+ apnsArgsList = apnsArgsList.concat(args);
35
+ } else if (typeof args === 'object') {
36
+ apnsArgsList.push(args);
37
+ } else {
38
+ throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'APNS Configuration is invalid');
39
+ }
40
+
41
+ // Create Provider from each arg-object
42
+ for (let apnsArgs of apnsArgsList) {
43
+
44
+ // rewrite bundleId to topic for backward-compatibility
45
+ if (apnsArgs.bundleId) {
46
+ log.warn(LOG_PREFIX, 'bundleId is deprecated, use topic instead');
47
+ apnsArgs.topic = apnsArgs.bundleId
48
+ }
49
+
50
+ let provider = APNS._createProvider(apnsArgs);
51
+ this.providers.push(provider);
52
+ }
53
+
54
+ // Sort the providers based on priority ascending, high pri first
55
+ this.providers.sort((s1, s2) => {
56
+ return s1.priority - s2.priority;
57
+ });
58
+
59
+ // Set index-property of providers
60
+ for (let index = 0; index < this.providers.length; index++) {
61
+ this.providers[index].index = index;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Send apns request.
67
+ *
68
+ * @param {Object} data The data we need to send, the format is the same with api request body
69
+ * @param {Array} allDevices An array of devices
70
+ * @returns {Object} A promise which is resolved immediately
71
+ */
72
+ send(data, allDevices) {
73
+ let coreData = data && data.data;
74
+ if (!coreData || !allDevices || !Array.isArray(allDevices)) {
75
+ log.warn(LOG_PREFIX, 'invalid push payload');
76
+ return;
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'];
82
+ let allPromises = [];
83
+
84
+ let devicesPerAppIdentifier = {};
85
+
86
+ // Start by clustering the devices per appIdentifier
87
+ allDevices.forEach(device => {
88
+ let appIdentifier = device.appIdentifier;
89
+ devicesPerAppIdentifier[appIdentifier] = devicesPerAppIdentifier[appIdentifier] || [];
90
+ devicesPerAppIdentifier[appIdentifier].push(device);
91
+ });
92
+
93
+ for (let key in devicesPerAppIdentifier) {
94
+ let devices = devicesPerAppIdentifier[key];
95
+ let appIdentifier = devices[0].appIdentifier;
96
+ let providers = this._chooseProviders(appIdentifier);
97
+
98
+ // No Providers found
99
+ if (!providers || providers.length === 0) {
100
+ let errorPromises = devices.map(device => APNS._createErrorPromise(device.deviceToken, 'No Provider found'));
101
+ allPromises = allPromises.concat(errorPromises);
102
+ continue;
103
+ }
104
+
105
+ let headers = { expirationTime: expirationTime, topic: appIdentifier, collapseId: collapseId, pushType: pushType, priority: priority }
106
+ let notification = APNS._generateNotification(coreData, headers);
107
+ const deviceIds = devices.map(device => device.deviceToken);
108
+ let promise = this.sendThroughProvider(notification, deviceIds, providers);
109
+ allPromises.push(promise.then(this._handlePromise.bind(this)));
110
+ }
111
+
112
+ return Promise.all(allPromises).then((results) => {
113
+ // flatten all
114
+ return [].concat.apply([], results);
115
+ });
116
+ }
117
+
118
+ sendThroughProvider(notification, devices, providers) {
119
+ return providers[0]
120
+ .send(notification, devices)
121
+ .then((response) => {
122
+ if (response.failed
123
+ && response.failed.length > 0
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 {
136
+ return response;
137
+ }
138
+ });
139
+ }
140
+
141
+ static _validateAPNArgs(apnsArgs) {
142
+ if (apnsArgs.topic) {
143
+ return true;
144
+ }
145
+ return !(apnsArgs.cert || apnsArgs.key || apnsArgs.pfx);
146
+ }
147
+
148
+ /**
149
+ * Creates an Provider base on apnsArgs.
150
+ */
151
+ static _createProvider(apnsArgs) {
152
+ // if using certificate, then topic must be defined
153
+ if (!APNS._validateAPNArgs(apnsArgs)) {
154
+ throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is mssing for %j', apnsArgs);
155
+ }
156
+
157
+ let provider = new apn.Provider(apnsArgs);
158
+
159
+ // Sets the topic on this provider
160
+ provider.topic = apnsArgs.topic;
161
+
162
+ // Set the priority of the providers, prod cert has higher priority
163
+ if (apnsArgs.production) {
164
+ provider.priority = 0;
165
+ } else {
166
+ provider.priority = 1;
167
+ }
168
+
169
+ return provider;
170
+ }
171
+
172
+ /**
173
+ * Generate the apns Notification from the data we get from api request.
174
+ * @param {Object} coreData The data field under api request body
175
+ * @param {Object} headers The header properties for the notification (topic, expirationTime, collapseId, pushType, priority)
176
+ * @returns {Object} A apns Notification
177
+ */
178
+ static _generateNotification(coreData, headers) {
179
+ let notification = new apn.Notification();
180
+ let payload = {};
181
+ for (let key in coreData) {
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);
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
+ 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
+ }
222
+ }
223
+
224
+ notification.payload = payload;
225
+
226
+ notification.topic = headers.topic;
227
+ notification.expiry = Math.round(headers.expirationTime / 1000);
228
+ notification.collapseId = headers.collapseId;
229
+ // set alert as default push type. If push type is not set notifications are not delivered to devices running iOS 13, watchOS 6 and later.
230
+ notification.pushType = 'alert';
231
+ if (headers.pushType) {
232
+ notification.pushType = headers.pushType;
233
+ }
234
+ if (headers.priority) {
235
+ // if headers priority is not set 'node-apn' defaults it to 5 which is min. required value for background pushes to launch the app in background.
236
+ notification.priority = headers.priority
237
+ }
238
+ return notification;
239
+ }
240
+
241
+ /**
242
+ * Choose appropriate providers based on device appIdentifier.
243
+ *
244
+ * @param {String} appIdentifier appIdentifier for required provider
245
+ * @returns {Array} Returns Array with appropriate providers
246
+ */
247
+ _chooseProviders(appIdentifier) {
248
+ // If the device we need to send to does not have appIdentifier, any provider could be a qualified provider
249
+ /*if (!appIdentifier || appIdentifier === '') {
250
+ return this.providers.map((provider) => provider.index);
251
+ }*/
252
+
253
+ // Otherwise we try to match the appIdentifier with topic on provider
254
+ let qualifiedProviders = this.providers.filter((provider) => appIdentifier === provider.topic);
255
+
256
+ if (qualifiedProviders.length > 0) {
257
+ return qualifiedProviders;
258
+ }
259
+
260
+ // If qualifiedProviders empty, add all providers without topic
261
+ return this.providers
262
+ .filter((provider) => !provider.topic || provider.topic === '');
263
+ }
264
+
265
+ _handlePromise(response) {
266
+ let promises = [];
267
+ response.sent.forEach((token) => {
268
+ log.verbose(LOG_PREFIX, 'APNS transmitted to %s', token.device);
269
+ promises.push(APNS._createSuccesfullPromise(token.device));
270
+ });
271
+ response.failed.forEach((failure) => {
272
+ promises.push(APNS._handlePushFailure(failure));
273
+ });
274
+ return Promise.all(promises);
275
+ }
276
+
277
+ static _handlePushFailure(failure) {
278
+ if (failure.error) {
279
+ log.error(LOG_PREFIX, 'APNS error transmitting to device %s with error %s', failure.device, failure.error);
280
+ return APNS._createErrorPromise(failure.device, failure.error);
281
+ } else if (failure.status && failure.response && failure.response.reason) {
282
+ log.error(LOG_PREFIX, 'APNS error transmitting to device %s with status %s and reason %s', failure.device, failure.status, failure.response.reason);
283
+ return APNS._createErrorPromise(failure.device, failure.response.reason);
284
+ } else {
285
+ log.error(LOG_PREFIX, 'APNS error transmitting to device with unkown error');
286
+ return APNS._createErrorPromise(failure.device, 'Unkown status');
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Creates an errorPromise for return.
292
+ *
293
+ * @param {String} token Device-Token
294
+ * @param {String} errorMessage ErrrorMessage as string
295
+ */
296
+ static _createErrorPromise(token, errorMessage) {
297
+ return Promise.resolve({
298
+ transmitted: false,
299
+ device: {
300
+ deviceToken: token,
301
+ deviceType: 'ios'
302
+ },
303
+ response: { error: errorMessage }
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Creates an successfulPromise for return.
309
+ *
310
+ * @param {String} token Device-Token
311
+ */
312
+ static _createSuccesfullPromise(token) {
313
+ return Promise.resolve({
314
+ transmitted: true,
315
+ device: {
316
+ deviceToken: token,
317
+ deviceType: 'ios'
318
+ }
319
+ });
320
+ }
321
+ }
322
+
323
+ export default APNS;