expo-server-sdk
Advanced tools
Comparing version 3.5.0 to 3.5.1
@@ -12,3 +12,3 @@ /// <reference types="node" /> | ||
*/ | ||
static isExpoPushToken(token: ExpoPushToken): boolean; | ||
static isExpoPushToken(token: unknown): token is ExpoPushToken; | ||
/** | ||
@@ -54,3 +54,3 @@ * Sends the given messages to their recipients via push notifications and returns an array of | ||
to: ExpoPushToken | ExpoPushToken[]; | ||
data?: Object; | ||
data?: object; | ||
title?: string; | ||
@@ -79,3 +79,3 @@ subtitle?: string; | ||
status: 'ok'; | ||
details?: Object; | ||
details?: object; | ||
__debug?: any; | ||
@@ -82,0 +82,0 @@ }; |
@@ -12,2 +12,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Expo = void 0; | ||
/** | ||
@@ -71,3 +72,3 @@ * expo-server-sdk | ||
const actualMessagesCount = Expo._getActualMessageCount(messages); | ||
let data = yield this.requestAsync(`${BASE_API_URL}/push/send`, { | ||
const data = yield this.requestAsync(`${BASE_API_URL}/push/send`, { | ||
httpMethod: 'post', | ||
@@ -80,3 +81,3 @@ body: messages, | ||
if (!Array.isArray(data) || data.length !== actualMessagesCount) { | ||
let apiError = new Error(`Expected Expo to respond with ${actualMessagesCount} ${actualMessagesCount === 1 ? 'ticket' : 'tickets'} but got ${data.length}`); | ||
const apiError = new Error(`Expected Expo to respond with ${actualMessagesCount} ${actualMessagesCount === 1 ? 'ticket' : 'tickets'} but got ${data.length}`); | ||
apiError.data = data; | ||
@@ -90,3 +91,3 @@ throw apiError; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let data = yield this.requestAsync(`${BASE_API_URL}/push/getReceipts`, { | ||
const data = yield this.requestAsync(`${BASE_API_URL}/push/getReceipts`, { | ||
httpMethod: 'post', | ||
@@ -99,3 +100,3 @@ body: { ids: receiptIds }, | ||
if (!data || typeof data !== 'object' || Array.isArray(data)) { | ||
let apiError = new Error(`Expected Expo to respond with a map from receipt IDs to receipts but received data of another type`); | ||
const apiError = new Error(`Expected Expo to respond with a map from receipt IDs to receipts but received data of another type`); | ||
apiError.data = data; | ||
@@ -108,9 +109,9 @@ throw apiError; | ||
chunkPushNotifications(messages) { | ||
let chunks = []; | ||
const chunks = []; | ||
let chunk = []; | ||
let chunkMessagesCount = 0; | ||
for (let message of messages) { | ||
for (const message of messages) { | ||
if (Array.isArray(message.to)) { | ||
let partialTo = []; | ||
for (let recipient of message.to) { | ||
for (const recipient of message.to) { | ||
partialTo.push(recipient); | ||
@@ -155,5 +156,5 @@ chunkMessagesCount++; | ||
chunkItems(items, chunkSize) { | ||
let chunks = []; | ||
const chunks = []; | ||
let chunk = []; | ||
for (let item of items) { | ||
for (const item of items) { | ||
chunk.push(item); | ||
@@ -173,4 +174,4 @@ if (chunk.length >= chunkSize) { | ||
let requestBody; | ||
let sdkVersion = require('../package.json').version; | ||
let requestHeaders = new node_fetch_1.Headers({ | ||
const sdkVersion = require('../package.json').version; | ||
const requestHeaders = new node_fetch_1.Headers({ | ||
Accept: 'application/json', | ||
@@ -181,3 +182,3 @@ 'Accept-Encoding': 'gzip, deflate', | ||
if (options.body != null) { | ||
let json = JSON.stringify(options.body); | ||
const json = JSON.stringify(options.body); | ||
assert(json != null, `JSON request body must not be null`); | ||
@@ -193,3 +194,3 @@ if (options.shouldCompress(json)) { | ||
} | ||
let response = yield this.limitConcurrentRequests(() => node_fetch_1.default(url, { | ||
const response = yield this.limitConcurrentRequests(() => node_fetch_1.default(url, { | ||
method: options.httpMethod, | ||
@@ -201,6 +202,6 @@ body: requestBody, | ||
if (response.status !== 200) { | ||
let apiError = yield this.parseErrorResponseAsync(response); | ||
const apiError = yield this.parseErrorResponseAsync(response); | ||
throw apiError; | ||
} | ||
let textBody = yield response.text(); | ||
const textBody = yield response.text(); | ||
// We expect the API response body to be JSON | ||
@@ -212,7 +213,7 @@ let result; | ||
catch (e) { | ||
let apiError = yield this.getTextResponseErrorAsync(response, textBody); | ||
const apiError = yield this.getTextResponseErrorAsync(response, textBody); | ||
throw apiError; | ||
} | ||
if (result.errors) { | ||
let apiError = this.getErrorFromResult(result); | ||
const apiError = this.getErrorFromResult(result); | ||
throw apiError; | ||
@@ -225,3 +226,3 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let textBody = yield response.text(); | ||
const textBody = yield response.text(); | ||
let result; | ||
@@ -235,3 +236,3 @@ try { | ||
if (!result.errors || !Array.isArray(result.errors) || !result.errors.length) { | ||
let apiError = yield this.getTextResponseErrorAsync(response, textBody); | ||
const apiError = yield this.getTextResponseErrorAsync(response, textBody); | ||
apiError.errorData = result; | ||
@@ -245,3 +246,3 @@ return apiError; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let apiError = new Error(`Expo responded with an error with status code ${response.status}: ` + text); | ||
const apiError = new Error(`Expo responded with an error with status code ${response.status}: ` + text); | ||
apiError.statusCode = response.status; | ||
@@ -258,6 +259,6 @@ apiError.errorText = text; | ||
assert(result.errors && result.errors.length > 0, `Expected at least one error from Expo`); | ||
let [errorData, ...otherErrorData] = result.errors; | ||
let error = this.getErrorFromResultError(errorData); | ||
const [errorData, ...otherErrorData] = result.errors; | ||
const error = this.getErrorFromResultError(errorData); | ||
if (otherErrorData.length) { | ||
error.others = otherErrorData.map(data => this.getErrorFromResultError(data)); | ||
error.others = otherErrorData.map((data) => this.getErrorFromResultError(data)); | ||
} | ||
@@ -270,3 +271,3 @@ return error; | ||
getErrorFromResultError(errorData) { | ||
let error = new Error(errorData.message); | ||
const error = new Error(errorData.message); | ||
error.code = errorData.code; | ||
@@ -273,0 +274,0 @@ if (errorData.details != null) { |
{ | ||
"name": "expo-server-sdk", | ||
"version": "3.5.0", | ||
"description": "Server side library for working with Expo using Node.js", | ||
"version": "3.5.1", | ||
"description": "Server-side library for working with Expo using Node.js", | ||
"main": "build/ExpoClient.js", | ||
@@ -12,5 +12,5 @@ "types": "build/ExpoClient.d.ts", | ||
"scripts": { | ||
"babel": "babel", | ||
"build": "./build.sh", | ||
"prepublish": "npm run build", | ||
"lint": "eslint src", | ||
"prepare": "yarn build", | ||
"test": "jest", | ||
@@ -20,20 +20,15 @@ "tsc": "tsc", | ||
}, | ||
"eslintConfig": { | ||
"extends": "universe/node", | ||
"settings": { | ||
"react": { | ||
"version": "999.999.0" | ||
} | ||
} | ||
}, | ||
"jest": { | ||
"coverageDirectory": "<rootDir>/../coverage", | ||
"preset": "ts-jest", | ||
"roots": [ | ||
"<rootDir>/src/" | ||
], | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"js" | ||
], | ||
"globals": { | ||
"ts-jest": { | ||
"tsConfig": "tsconfig.json" | ||
} | ||
}, | ||
"testEnvironment": "node", | ||
"testMatch": [ | ||
"**/__tests__/*.+(js|ts)" | ||
] | ||
"rootDir": "src", | ||
"testEnvironment": "node" | ||
}, | ||
@@ -59,9 +54,12 @@ "repository": { | ||
"devDependencies": { | ||
"@types/invariant": "^2.2.31", | ||
"@types/jest": "^25.1.4", | ||
"@types/node-fetch": "^2.5.5", | ||
"jest": "^25.2.4", | ||
"ts-jest": "~25.3.0", | ||
"typescript": "^3.8.3" | ||
"@types/jest": "^26.0.3", | ||
"@types/node-fetch": "^2.5.7", | ||
"eslint": "^7.3.1", | ||
"eslint-config-universe": "^3.0.2", | ||
"fetch-mock": "^9.10.3", | ||
"jest": "^26.1.0", | ||
"prettier": "^2.0.5", | ||
"ts-jest": "~26.1.1", | ||
"typescript": "^3.9.5" | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
# expo-server-sdk-node [![CircleCI](https://circleci.com/gh/expo/expo-server-sdk-node.svg?style=svg)](https://circleci.com/gh/expo/expo-server-sdk-node) [![codecov](https://codecov.io/gh/expo/expo-server-sdk-node/branch/master/graph/badge.svg)](https://codecov.io/gh/expo/expo-server-sdk-node) | ||
# expo-server-sdk-node ![Tests](https://github.com/expo/expo-server-sdk-node/workflows/Tests/badge.svg) [![codecov](https://codecov.io/gh/expo/expo-server-sdk-node/branch/master/graph/badge.svg)](https://codecov.io/gh/expo/expo-server-sdk-node) | ||
Server-side library for working with Expo using Node.js. | ||
@@ -20,3 +20,3 @@ | ||
// Create the messages that you want to send to clents | ||
// Create the messages that you want to send to clients | ||
let messages = []; | ||
@@ -136,9 +136,5 @@ for (let pushToken of somePushTokens) { | ||
## TODO | ||
* Need to add tests | ||
## See Also | ||
* https://github.com/expo/expo-server-sdk-ruby | ||
* https://github.com/expo/expo-server-sdk-python | ||
* https://github.com/expo-community/expo-server-sdk-ruby | ||
* https://github.com/expo-community/expo-server-sdk-python |
@@ -0,68 +1,277 @@ | ||
import fetch from 'node-fetch'; | ||
import ExpoClient, { ExpoPushMessage } from '../ExpoClient'; | ||
test('chunks lists of push notification messages', () => { | ||
let client = new ExpoClient(); | ||
let messages = new Array(999).fill({ to: '?' }); | ||
let chunks = client.chunkPushNotifications(messages); | ||
let totalMessageCount = 0; | ||
for (let chunk of chunks) { | ||
totalMessageCount += chunk.length; | ||
} | ||
expect(totalMessageCount).toBe(messages.length); | ||
afterEach(() => { | ||
(fetch as any).reset(); | ||
}); | ||
test('can chunk small lists of push notification messages', () => { | ||
let client = new ExpoClient(); | ||
let messages = new Array(10).fill({ to: '?' }); | ||
let chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(1); | ||
expect(chunks[0].length).toBe(10); | ||
test('limits the number of concurrent requests', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { data: [] }); | ||
expect((fetch as any).calls().length).toBe(0); | ||
const client = new ExpoClient({ maxConcurrentRequests: 1 }); | ||
const sendPromise1 = client.sendPushNotificationsAsync([]); | ||
const sendPromise2 = client.sendPushNotificationsAsync([]); | ||
expect((fetch as any).calls().length).toBe(1); | ||
await sendPromise1; | ||
expect((fetch as any).calls().length).toBe(2); | ||
await sendPromise2; | ||
expect((fetch as any).calls().length).toBe(2); | ||
}); | ||
test('chunks single push notification message with lists of recipients', () => { | ||
const messagesLength = 999; | ||
describe('sending push notification messages', () => { | ||
test('sends requests to the Expo API server', async () => { | ||
const mockTickets = [ | ||
{ status: 'ok', id: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' }, | ||
{ status: 'ok', id: 'YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY' }, | ||
]; | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { data: mockTickets }); | ||
let client = new ExpoClient(); | ||
let messages = [{ to: new Array(messagesLength).fill('?') }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
for (let chunk of chunks) { | ||
// Each chunk should only contain a single message with 100 recipients | ||
expect(chunk.length).toBe(1); | ||
} | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(messagesLength); | ||
const client = new ExpoClient(); | ||
const tickets = await client.sendPushNotificationsAsync([{ to: 'a' }, { to: 'b' }]); | ||
expect(tickets).toEqual(mockTickets); | ||
const [, options] = (fetch as any).lastCall('https://exp.host/--/api/v2/push/send'); | ||
expect(options.headers.get('accept')).toContain('application/json'); | ||
expect(options.headers.get('accept-encoding')).toContain('gzip'); | ||
expect(options.headers.get('content-type')).toContain('application/json'); | ||
expect(options.headers.get('user-agent')).toMatch(/^expo-server-sdk-node\//); | ||
}); | ||
test('compresses request bodies over 1 KiB', async () => { | ||
const mockTickets = [{ status: 'ok', id: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' }]; | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { data: mockTickets }); | ||
const client = new ExpoClient(); | ||
const messages = [{ to: 'a', body: new Array(1500).join('?') }]; | ||
expect(JSON.stringify(messages).length).toBeGreaterThan(1024); | ||
const tickets = await client.sendPushNotificationsAsync(messages); | ||
expect(tickets).toEqual(mockTickets); | ||
// Ensure the request body was compressed | ||
const [, options] = (fetch as any).lastCall('https://exp.host/--/api/v2/push/send'); | ||
expect(options.body.length).toBeLessThan(JSON.stringify(messages).length); | ||
expect(options.headers.get('content-encoding')).toContain('gzip'); | ||
}); | ||
test(`throws an error when the number of tickets doesn't match the number of messages`, async () => { | ||
const mockTickets = [ | ||
{ status: 'ok', id: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' }, | ||
{ status: 'ok', id: 'YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY' }, | ||
]; | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { data: mockTickets }); | ||
const client = new ExpoClient(); | ||
await expect(client.sendPushNotificationsAsync([{ to: 'a' }])).rejects.toThrowError( | ||
`Expected Expo to respond with 1 ticket but got 2` | ||
); | ||
await expect( | ||
client.sendPushNotificationsAsync([{ to: 'a' }, { to: 'b' }, { to: 'c' }]) | ||
).rejects.toThrowError(`Expected Expo to respond with 3 tickets but got 2`); | ||
}); | ||
test('handles 200 HTTP responses with well-formed API errors', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { | ||
status: 200, | ||
errors: [{ code: 'TEST_API_ERROR', message: `This is a test error` }], | ||
}); | ||
const client = new ExpoClient(); | ||
const rejection = expect(client.sendPushNotificationsAsync([])).rejects; | ||
await rejection.toThrowError(`This is a test error`); | ||
await rejection.toMatchObject({ code: 'TEST_API_ERROR' }); | ||
}); | ||
test('handles 200 HTTP responses with malformed JSON', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { | ||
status: 200, | ||
body: '<!DOCTYPE html><body>Not JSON</body>', | ||
}); | ||
const client = new ExpoClient(); | ||
await expect(client.sendPushNotificationsAsync([])).rejects.toThrowError( | ||
`Expo responded with an error` | ||
); | ||
}); | ||
test('handles non-200 HTTP responses with well-formed API errors', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { | ||
status: 400, | ||
body: { | ||
errors: [{ code: 'TEST_API_ERROR', message: `This is a test error` }], | ||
}, | ||
}); | ||
const client = new ExpoClient(); | ||
const rejection = expect(client.sendPushNotificationsAsync([])).rejects; | ||
await rejection.toThrowError(`This is a test error`); | ||
await rejection.toMatchObject({ code: 'TEST_API_ERROR' }); | ||
}); | ||
test('handles non-200 HTTP responses with arbitrary JSON', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { | ||
status: 400, | ||
body: { clowntown: true }, | ||
}); | ||
const client = new ExpoClient(); | ||
await expect(client.sendPushNotificationsAsync([])).rejects.toThrowError( | ||
`Expo responded with an error` | ||
); | ||
}); | ||
test('handles non-200 HTTP responses with arbitrary text', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { | ||
status: 400, | ||
body: '<!DOCTYPE html><body>Not JSON</body>', | ||
}); | ||
const client = new ExpoClient(); | ||
await expect(client.sendPushNotificationsAsync([])).rejects.toThrowError( | ||
`Expo responded with an error` | ||
); | ||
}); | ||
test('handles well-formed API responses with multiple errors and extra details', async () => { | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/send', { | ||
status: 400, | ||
body: { | ||
errors: [ | ||
{ | ||
code: 'TEST_API_ERROR', | ||
message: `This is a test error`, | ||
details: { __debug: 'test debug information' }, | ||
stack: | ||
'Error: This is a test error\n' + | ||
' at SomeServerModule.method (SomeServerModule.js:131:20)', | ||
}, | ||
{ | ||
code: 'SYSTEM_ERROR', | ||
message: `This is another error`, | ||
}, | ||
], | ||
}, | ||
}); | ||
const client = new ExpoClient(); | ||
const rejection = expect(client.sendPushNotificationsAsync([])).rejects; | ||
await rejection.toThrowError(`This is a test error`); | ||
await rejection.toMatchObject({ | ||
code: 'TEST_API_ERROR', | ||
details: { __debug: 'test debug information' }, | ||
serverStack: expect.any(String), | ||
others: expect.arrayContaining([expect.any(Error)]), | ||
}); | ||
}); | ||
}); | ||
test('can chunk single push notification message with small lists of recipients', () => { | ||
const messagesLength = 10; | ||
describe('retrieving push notification receipts', () => { | ||
test('gets receipts from the Expo API server', async () => { | ||
const mockReceipts = { | ||
'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX': { status: 'ok' }, | ||
'YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY': { status: 'ok' }, | ||
}; | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/getReceipts', { data: mockReceipts }); | ||
let client = new ExpoClient(); | ||
let messages = [{ to: new Array(messagesLength).fill('?') }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(1); | ||
expect(chunks[0].length).toBe(1); | ||
expect(chunks[0][0].to.length).toBe(messagesLength); | ||
const client = new ExpoClient(); | ||
const receipts = await client.getPushNotificationReceiptsAsync([ | ||
'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', | ||
'YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY', | ||
]); | ||
expect(receipts).toEqual(mockReceipts); | ||
const [, options] = (fetch as any).lastCall('https://exp.host/--/api/v2/push/getReceipts'); | ||
expect(options.headers.get('accept')).toContain('application/json'); | ||
expect(options.headers.get('accept-encoding')).toContain('gzip'); | ||
expect(options.headers.get('content-type')).toContain('application/json'); | ||
}); | ||
test('throws an error if the response is not a map', async () => { | ||
const mockReceipts = [{ status: 'ok' }]; | ||
(fetch as any).mock('https://exp.host/--/api/v2/push/getReceipts', { data: mockReceipts }); | ||
const client = new ExpoClient(); | ||
const rejection = expect( | ||
client.getPushNotificationReceiptsAsync(['XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX']) | ||
).rejects; | ||
await rejection.toThrowError(`Expected Expo to respond with a map`); | ||
await rejection.toMatchObject({ data: mockReceipts }); | ||
}); | ||
}); | ||
test('chunks push notification messages mixed with lists of recipients and single recipient', () => { | ||
let client = new ExpoClient(); | ||
let messages = [ | ||
{ to: new Array(888).fill('?') }, | ||
...new Array(999).fill({ | ||
to: '?', | ||
}), | ||
{ to: new Array(90).fill('?') }, | ||
...new Array(10).fill({ to: '?' }), | ||
]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(888 + 999 + 90 + 10); | ||
describe('chunking push notification messages', () => { | ||
test('defines the push notification chunk size', () => { | ||
expect(ExpoClient.pushNotificationChunkSizeLimit).toBeDefined(); | ||
}); | ||
test('chunks lists of push notification messages', () => { | ||
const client = new ExpoClient(); | ||
const messages = new Array(999).fill({ to: '?' }); | ||
const chunks = client.chunkPushNotifications(messages); | ||
let totalMessageCount = 0; | ||
for (const chunk of chunks) { | ||
totalMessageCount += chunk.length; | ||
} | ||
expect(totalMessageCount).toBe(messages.length); | ||
}); | ||
test('can chunk small lists of push notification messages', () => { | ||
const client = new ExpoClient(); | ||
const messages = new Array(10).fill({ to: '?' }); | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(1); | ||
expect(chunks[0].length).toBe(10); | ||
}); | ||
test('chunks single push notification message with lists of recipients', () => { | ||
const messagesLength = 999; | ||
const client = new ExpoClient(); | ||
const messages = [{ to: new Array(messagesLength).fill('?') }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
for (const chunk of chunks) { | ||
// Each chunk should only contain a single message with 100 recipients | ||
expect(chunk.length).toBe(1); | ||
} | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(messagesLength); | ||
}); | ||
test('can chunk single push notification message with small lists of recipients', () => { | ||
const messagesLength = 10; | ||
const client = new ExpoClient(); | ||
const messages = [{ to: new Array(messagesLength).fill('?') }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(1); | ||
expect(chunks[0].length).toBe(1); | ||
expect(chunks[0][0].to.length).toBe(messagesLength); | ||
}); | ||
test('chunks push notification messages mixed with lists of recipients and single recipient', () => { | ||
const client = new ExpoClient(); | ||
const messages = [ | ||
{ to: new Array(888).fill('?') }, | ||
...new Array(999).fill({ | ||
to: '?', | ||
}), | ||
{ to: new Array(90).fill('?') }, | ||
...new Array(10).fill({ to: '?' }), | ||
]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(888 + 999 + 90 + 10); | ||
}); | ||
}); | ||
describe('chunking a single push notification message with multiple recipients', () => { | ||
let client = new ExpoClient(); | ||
const client = new ExpoClient(); | ||
test('one message with 100 recipients', () => { | ||
let messages = [{ to: new Array(100).fill('?') }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages = [{ to: new Array(100).fill('?') }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(1); | ||
@@ -74,8 +283,8 @@ expect(chunks[0].length).toBe(1); | ||
test('one message with 101 recipients', () => { | ||
let messages = [{ to: new Array(101).fill('?') }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages = [{ to: new Array(101).fill('?') }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(2); | ||
expect(chunks[0].length).toBe(1); | ||
expect(chunks[1].length).toBe(1); | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(101); | ||
@@ -85,8 +294,8 @@ }); | ||
test('one message with 99 recipients and two additional messages', () => { | ||
let messages = [{ to: new Array(99).fill('?') }, ...new Array(2).fill({ to: '?' })]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages = [{ to: new Array(99).fill('?') }, ...new Array(2).fill({ to: '?' })]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(2); | ||
expect(chunks[0].length).toBe(2); | ||
expect(chunks[1].length).toBe(1); | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(99 + 2); | ||
@@ -96,8 +305,8 @@ }); | ||
test('one message with 100 recipients and two additional messages', () => { | ||
let messages = [{ to: new Array(100).fill('?') }, ...new Array(2).fill({ to: '?' })]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages = [{ to: new Array(100).fill('?') }, ...new Array(2).fill({ to: '?' })]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(2); | ||
expect(chunks[0].length).toBe(1); | ||
expect(chunks[1].length).toBe(2); | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(100 + 2); | ||
@@ -107,8 +316,8 @@ }); | ||
test('99 messages and one additional message with with two recipients', () => { | ||
let messages = [...new Array(99).fill({ to: '?' }), { to: new Array(2).fill('?') }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages = [...new Array(99).fill({ to: '?' }), { to: new Array(2).fill('?') }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(2); | ||
expect(chunks[0].length).toBe(100); | ||
expect(chunks[1].length).toBe(1); | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(99 + 2); | ||
@@ -118,4 +327,4 @@ }); | ||
test('no message', () => { | ||
let messages: ExpoPushMessage[] = []; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages: ExpoPushMessage[] = []; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(0); | ||
@@ -125,14 +334,14 @@ }); | ||
test('one message with no recipient', () => { | ||
let messages = [{ to: [] }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
const messages = [{ to: [] }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(0); | ||
}); | ||
test('two message and one additional message with no recipient', () => { | ||
let messages = [...new Array(2).fill({ to: '?' }), { to: [] }]; | ||
let chunks = client.chunkPushNotifications(messages); | ||
test('two messages and one additional message with no recipient', () => { | ||
const messages = [...new Array(2).fill({ to: '?' }), { to: [] }]; | ||
const chunks = client.chunkPushNotifications(messages); | ||
expect(chunks.length).toBe(1); | ||
// The message with no recipient should be removed. | ||
expect(chunks[0].length).toBe(2); | ||
let totalMessageCount = countAndValidateMessages(chunks); | ||
const totalMessageCount = countAndValidateMessages(chunks); | ||
expect(totalMessageCount).toBe(2); | ||
@@ -142,21 +351,19 @@ }); | ||
test('defines the push notification chunk size', () => { | ||
expect(ExpoClient.pushNotificationChunkSizeLimit).toBeDefined(); | ||
}); | ||
describe('chunking push notification receipt IDs', () => { | ||
test('defines the push notification receipt ID chunk size', () => { | ||
expect(ExpoClient.pushNotificationReceiptChunkSizeLimit).toBeDefined(); | ||
}); | ||
test('chunks lists of push notification receipt IDs', () => { | ||
let client = new ExpoClient(); | ||
let receiptIds = new Array(2999).fill('F5741A13-BCDA-434B-A316-5DC0E6FFA94F'); | ||
let chunks = client.chunkPushNotificationReceiptIds(receiptIds); | ||
let totalReceiptIdCount = 0; | ||
for (let chunk of chunks) { | ||
totalReceiptIdCount += chunk.length; | ||
} | ||
expect(totalReceiptIdCount).toBe(receiptIds.length); | ||
test('chunks lists of push notification receipt IDs', () => { | ||
const client = new ExpoClient(); | ||
const receiptIds = new Array(2999).fill('F5741A13-BCDA-434B-A316-5DC0E6FFA94F'); | ||
const chunks = client.chunkPushNotificationReceiptIds(receiptIds); | ||
let totalReceiptIdCount = 0; | ||
for (const chunk of chunks) { | ||
totalReceiptIdCount += chunk.length; | ||
} | ||
expect(totalReceiptIdCount).toBe(receiptIds.length); | ||
}); | ||
}); | ||
test('defines the push notification receipt ID chunk size', () => { | ||
expect(ExpoClient.pushNotificationReceiptChunkSizeLimit).toBeDefined(); | ||
}); | ||
test('can detect an Expo push token', () => { | ||
@@ -180,6 +387,6 @@ expect(ExpoClient.isExpoPushToken('ExpoPushToken[xxxxxxxxxxxxxxxxxxxxxx]')).toBe(true); | ||
function countAndValidateMessages(chunks: ExpoPushMessage[][]) { | ||
function countAndValidateMessages(chunks: ExpoPushMessage[][]): number { | ||
let totalMessageCount = 0; | ||
for (let chunk of chunks) { | ||
let chunkMessagesCount = ExpoClient._getActualMessageCount(chunk); | ||
for (const chunk of chunks) { | ||
const chunkMessagesCount = ExpoClient._getActualMessageCount(chunk); | ||
expect(chunkMessagesCount).toBeLessThanOrEqual(ExpoClient.pushNotificationChunkSizeLimit); | ||
@@ -186,0 +393,0 @@ totalMessageCount += chunkMessagesCount; |
@@ -54,3 +54,3 @@ /** | ||
*/ | ||
static isExpoPushToken(token: ExpoPushToken): boolean { | ||
static isExpoPushToken(token: unknown): token is ExpoPushToken { | ||
return ( | ||
@@ -78,3 +78,3 @@ typeof token === 'string' && | ||
let data = await this.requestAsync(`${BASE_API_URL}/push/send`, { | ||
const data = await this.requestAsync(`${BASE_API_URL}/push/send`, { | ||
httpMethod: 'post', | ||
@@ -88,3 +88,3 @@ body: messages, | ||
if (!Array.isArray(data) || data.length !== actualMessagesCount) { | ||
let apiError: ExtensibleError = new Error( | ||
const apiError: ExtensibleError = new Error( | ||
`Expected Expo to respond with ${actualMessagesCount} ${ | ||
@@ -104,3 +104,3 @@ actualMessagesCount === 1 ? 'ticket' : 'tickets' | ||
): Promise<{ [id: string]: ExpoPushReceipt }> { | ||
let data = await this.requestAsync(`${BASE_API_URL}/push/getReceipts`, { | ||
const data = await this.requestAsync(`${BASE_API_URL}/push/getReceipts`, { | ||
httpMethod: 'post', | ||
@@ -114,3 +114,3 @@ body: { ids: receiptIds }, | ||
if (!data || typeof data !== 'object' || Array.isArray(data)) { | ||
let apiError: ExtensibleError = new Error( | ||
const apiError: ExtensibleError = new Error( | ||
`Expected Expo to respond with a map from receipt IDs to receipts but received data of another type` | ||
@@ -126,10 +126,10 @@ ); | ||
chunkPushNotifications(messages: ExpoPushMessage[]): ExpoPushMessage[][] { | ||
let chunks: ExpoPushMessage[][] = []; | ||
const chunks: ExpoPushMessage[][] = []; | ||
let chunk: ExpoPushMessage[] = []; | ||
let chunkMessagesCount = 0; | ||
for (let message of messages) { | ||
for (const message of messages) { | ||
if (Array.isArray(message.to)) { | ||
let partialTo: ExpoPushToken[] = []; | ||
for (let recipient of message.to) { | ||
for (const recipient of message.to) { | ||
partialTo.push(recipient); | ||
@@ -177,5 +177,5 @@ chunkMessagesCount++; | ||
private chunkItems<T>(items: T[], chunkSize: number): T[][] { | ||
let chunks: T[][] = []; | ||
const chunks: T[][] = []; | ||
let chunk: T[] = []; | ||
for (let item of items) { | ||
for (const item of items) { | ||
chunk.push(item); | ||
@@ -198,4 +198,4 @@ if (chunk.length >= chunkSize) { | ||
let sdkVersion = require('../package.json').version; | ||
let requestHeaders = new Headers({ | ||
const sdkVersion = require('../package.json').version; | ||
const requestHeaders = new Headers({ | ||
Accept: 'application/json', | ||
@@ -207,3 +207,3 @@ 'Accept-Encoding': 'gzip, deflate', | ||
if (options.body != null) { | ||
let json = JSON.stringify(options.body); | ||
const json = JSON.stringify(options.body); | ||
assert(json != null, `JSON request body must not be null`); | ||
@@ -220,3 +220,3 @@ if (options.shouldCompress(json)) { | ||
let response = await this.limitConcurrentRequests(() => | ||
const response = await this.limitConcurrentRequests(() => | ||
fetch(url, { | ||
@@ -231,7 +231,7 @@ method: options.httpMethod, | ||
if (response.status !== 200) { | ||
let apiError = await this.parseErrorResponseAsync(response); | ||
const apiError = await this.parseErrorResponseAsync(response); | ||
throw apiError; | ||
} | ||
let textBody = await response.text(); | ||
const textBody = await response.text(); | ||
// We expect the API response body to be JSON | ||
@@ -242,3 +242,3 @@ let result: ApiResult; | ||
} catch (e) { | ||
let apiError = await this.getTextResponseErrorAsync(response, textBody); | ||
const apiError = await this.getTextResponseErrorAsync(response, textBody); | ||
throw apiError; | ||
@@ -248,3 +248,3 @@ } | ||
if (result.errors) { | ||
let apiError = this.getErrorFromResult(result); | ||
const apiError = this.getErrorFromResult(result); | ||
throw apiError; | ||
@@ -257,3 +257,3 @@ } | ||
private async parseErrorResponseAsync(response: FetchResponse): Promise<Error> { | ||
let textBody = await response.text(); | ||
const textBody = await response.text(); | ||
let result: ApiResult; | ||
@@ -267,3 +267,3 @@ try { | ||
if (!result.errors || !Array.isArray(result.errors) || !result.errors.length) { | ||
let apiError: ExtensibleError = await this.getTextResponseErrorAsync(response, textBody); | ||
const apiError: ExtensibleError = await this.getTextResponseErrorAsync(response, textBody); | ||
apiError.errorData = result; | ||
@@ -277,3 +277,3 @@ return apiError; | ||
private async getTextResponseErrorAsync(response: FetchResponse, text: string): Promise<Error> { | ||
let apiError: ExtensibleError = new Error( | ||
const apiError: ExtensibleError = new Error( | ||
`Expo responded with an error with status code ${response.status}: ` + text | ||
@@ -292,6 +292,6 @@ ); | ||
assert(result.errors && result.errors.length > 0, `Expected at least one error from Expo`); | ||
let [errorData, ...otherErrorData] = result.errors!; | ||
let error: ExtensibleError = this.getErrorFromResultError(errorData); | ||
const [errorData, ...otherErrorData] = result.errors!; | ||
const error: ExtensibleError = this.getErrorFromResultError(errorData); | ||
if (otherErrorData.length) { | ||
error.others = otherErrorData.map(data => this.getErrorFromResultError(data)); | ||
error.others = otherErrorData.map((data) => this.getErrorFromResultError(data)); | ||
} | ||
@@ -305,3 +305,3 @@ return error; | ||
private getErrorFromResultError(errorData: ApiResultError): Error { | ||
let error: ExtensibleError = new Error(errorData.message); | ||
const error: ExtensibleError = new Error(errorData.message); | ||
error.code = errorData.code; | ||
@@ -355,3 +355,3 @@ | ||
to: ExpoPushToken | ExpoPushToken[]; | ||
data?: Object; | ||
data?: object; | ||
title?: string; | ||
@@ -388,3 +388,3 @@ subtitle?: string; | ||
status: 'ok'; | ||
details?: Object; | ||
details?: object; | ||
// Internal field used only by developers working on Expo | ||
@@ -391,0 +391,0 @@ __debug?: any; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
58108
12
1066
9
139