expo-server-sdk
Advanced tools
Comparing version
@@ -1,45 +0,21 @@ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _toArray2 = require('babel-runtime/helpers/toArray'); | ||
var _toArray3 = _interopRequireDefault(_toArray2); | ||
var _regenerator = require('babel-runtime/regenerator'); | ||
var _regenerator2 = _interopRequireDefault(_regenerator); | ||
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); | ||
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); | ||
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); | ||
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); | ||
var _createClass2 = require('babel-runtime/helpers/createClass'); | ||
var _createClass3 = _interopRequireDefault(_createClass2); | ||
var _invariant = require('invariant'); | ||
var _invariant2 = _interopRequireDefault(_invariant); | ||
var _nodeFetch = require('node-fetch'); | ||
var _nodeFetch2 = _interopRequireDefault(_nodeFetch); | ||
var _promiseLimit = require('promise-limit'); | ||
var _promiseLimit2 = _interopRequireDefault(_promiseLimit); | ||
var _zlib = require('zlib'); | ||
var _zlib2 = _interopRequireDefault(_zlib); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
@@ -50,8 +26,9 @@ * expo-server-sdk | ||
* https://expo.io | ||
* | ||
* | ||
*/ | ||
var BASE_URL = 'https://exp.host'; | ||
var BASE_API_URL = BASE_URL + '/--/api/v2'; | ||
const assert_1 = __importDefault(require("assert")); | ||
const node_fetch_1 = __importStar(require("node-fetch")); | ||
const promise_limit_1 = __importDefault(require("promise-limit")); | ||
const zlib_1 = __importDefault(require("zlib")); | ||
const BASE_URL = 'https://exp.host'; | ||
const BASE_API_URL = `${BASE_URL}/--/api/v2`; | ||
/** | ||
@@ -61,69 +38,36 @@ * The max number of push notifications to be sent at once. Since we can't automatically upgrade | ||
*/ | ||
var PUSH_NOTIFICATION_CHUNK_LIMIT = 100; | ||
const PUSH_NOTIFICATION_CHUNK_LIMIT = 100; | ||
/** | ||
* The max number of push notification receipts to request at once. | ||
*/ | ||
const PUSH_NOTIFICATION_RECEIPT_CHUNK_LIMIT = 300; | ||
/** | ||
* The default max number of concurrent HTTP requests to send at once and spread out the load, | ||
* increasing the reliability of notification delivery. | ||
*/ | ||
var DEFAULT_CONCURRENT_REQUEST_LIMIT = 6; | ||
const DEFAULT_CONCURRENT_REQUEST_LIMIT = 6; | ||
// TODO: Eventually we'll want to have developers authenticate. Right now it's not necessary because | ||
// push notifications are the only API we have and the push tokens are secret anyway. | ||
var ExpoClient = function () { | ||
function ExpoClient() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
(0, _classCallCheck3.default)(this, ExpoClient); | ||
this._httpAgent = options.httpAgent; | ||
this._limitConcurrentRequests = (0, _promiseLimit2.default)(options.maxConcurrentRequests != null ? options.maxConcurrentRequests : DEFAULT_CONCURRENT_REQUEST_LIMIT); | ||
} | ||
/** | ||
* Returns `true` if the token is an Expo push token | ||
*/ | ||
(0, _createClass3.default)(ExpoClient, [{ | ||
key: 'sendPushNotificationAsync', | ||
class ExpoClient { | ||
constructor(options = {}) { | ||
this._httpAgent = options.httpAgent; | ||
this._limitConcurrentRequests = promise_limit_1.default(options.maxConcurrentRequests != null | ||
? options.maxConcurrentRequests | ||
: DEFAULT_CONCURRENT_REQUEST_LIMIT); | ||
} | ||
/** | ||
* Sends the given message to its recipient via a push notification | ||
* Returns `true` if the token is an Expo push token | ||
*/ | ||
value: function () { | ||
var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(message) { | ||
var receipts; | ||
return _regenerator2.default.wrap(function _callee$(_context) { | ||
while (1) { | ||
switch (_context.prev = _context.next) { | ||
case 0: | ||
_context.next = 2; | ||
return this.sendPushNotificationsAsync([message]); | ||
case 2: | ||
receipts = _context.sent; | ||
(0, _invariant2.default)(receipts.length === 1, 'Expected exactly one push receipt'); | ||
return _context.abrupt('return', receipts[0]); | ||
case 5: | ||
case 'end': | ||
return _context.stop(); | ||
} | ||
} | ||
}, _callee, this); | ||
})); | ||
function sendPushNotificationAsync(_x2) { | ||
return _ref.apply(this, arguments); | ||
} | ||
return sendPushNotificationAsync; | ||
}() | ||
static isExpoPushToken(token) { | ||
return (typeof token === 'string' && | ||
(((token.startsWith('ExponentPushToken[') || token.startsWith('ExpoPushToken[')) && | ||
token.endsWith(']')) || | ||
/^[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12}$/i.test(token))); | ||
} | ||
/** | ||
* Sends the given messages to their recipients via push notifications and returns an array of | ||
* push receipts. Each receipt corresponds to the message at its respective index (the nth receipt | ||
* is for the nth message). | ||
* push tickets. Each ticket corresponds to the message at its respective index (the nth receipt | ||
* is for the nth message) and contains a receipt ID. Later, after Expo attempts to deliver the | ||
* messages to the underlying push notification services, the receipts with those IDs will be | ||
* available for a period of time (approximately a day). | ||
* | ||
@@ -134,308 +78,133 @@ * There is a limit on the number of push notifications you can send at once. Use | ||
*/ | ||
}, { | ||
key: 'sendPushNotificationsAsync', | ||
value: function () { | ||
var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(messages) { | ||
var data, apiError; | ||
return _regenerator2.default.wrap(function _callee2$(_context2) { | ||
while (1) { | ||
switch (_context2.prev = _context2.next) { | ||
case 0: | ||
_context2.next = 2; | ||
return this._requestAsync(BASE_API_URL + '/push/send', { | ||
httpMethod: 'post', | ||
body: messages, | ||
shouldCompress: function shouldCompress(body) { | ||
sendPushNotificationsAsync(messages) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let data = yield this._requestAsync(`${BASE_API_URL}/push/send`, { | ||
httpMethod: 'post', | ||
body: messages, | ||
shouldCompress(body) { | ||
return body.length > 1024; | ||
} | ||
}); | ||
case 2: | ||
data = _context2.sent; | ||
if (!(!Array.isArray(data) || data.length !== messages.length)) { | ||
_context2.next = 7; | ||
break; | ||
} | ||
apiError = new Error('Expected Exponent to respond with ' + messages.length + ' ' + ((messages.length === 1 ? 'receipt' : 'receipts') + ' but got ') + ('' + data.length)); | ||
}, | ||
}); | ||
if (!Array.isArray(data) || data.length !== messages.length) { | ||
let apiError = new Error(`Expected Expo to respond with ${messages.length} ${messages.length === 1 | ||
? 'ticket' | ||
: 'tickets'} but got ${data.length}`); | ||
apiError.data = data; | ||
throw apiError; | ||
case 7: | ||
return _context2.abrupt('return', data); | ||
case 8: | ||
case 'end': | ||
return _context2.stop(); | ||
} | ||
} | ||
}, _callee2, this); | ||
})); | ||
function sendPushNotificationsAsync(_x3) { | ||
return _ref2.apply(this, arguments); | ||
} | ||
return sendPushNotificationsAsync; | ||
}() | ||
}, { | ||
key: 'chunkPushNotifications', | ||
value: function chunkPushNotifications(messages) { | ||
var chunks = []; | ||
var chunk = []; | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = messages[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _message = _step.value; | ||
chunk.push(_message); | ||
if (chunk.length >= PUSH_NOTIFICATION_CHUNK_LIMIT) { | ||
return data; | ||
}); | ||
} | ||
getPushNotificationReceiptsAsync(receiptIds) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let data = yield this._requestAsync(`${BASE_API_URL}/push/getReceipts`, { | ||
httpMethod: 'post', | ||
body: { ids: receiptIds }, | ||
shouldCompress(body) { | ||
return body.length > 1024; | ||
}, | ||
}); | ||
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`); | ||
apiError.data = data; | ||
throw apiError; | ||
} | ||
return data; | ||
}); | ||
} | ||
chunkPushNotifications(messages) { | ||
return this._chunkItems(messages, PUSH_NOTIFICATION_CHUNK_LIMIT); | ||
} | ||
chunkPushNotificationReceiptIds(receiptIds) { | ||
return this._chunkItems(receiptIds, PUSH_NOTIFICATION_RECEIPT_CHUNK_LIMIT); | ||
} | ||
_chunkItems(items, chunkSize) { | ||
let chunks = []; | ||
let chunk = []; | ||
for (let item of items) { | ||
chunk.push(item); | ||
if (chunk.length >= chunkSize) { | ||
chunks.push(chunk); | ||
chunk = []; | ||
} | ||
} | ||
if (chunk.length) { | ||
chunks.push(chunk); | ||
chunk = []; | ||
} | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
if (chunk.length) { | ||
chunks.push(chunk); | ||
} | ||
return chunks; | ||
return chunks; | ||
} | ||
}, { | ||
key: '_requestAsync', | ||
value: function () { | ||
var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(url, options) { | ||
var sdkVersion, fetchOptions, json, response, apiError, textBody, result, _apiError, _apiError2; | ||
return _regenerator2.default.wrap(function _callee3$(_context3) { | ||
while (1) { | ||
switch (_context3.prev = _context3.next) { | ||
case 0: | ||
sdkVersion = require('../package.json').version; | ||
fetchOptions = { | ||
method: options.httpMethod, | ||
body: JSON.stringify(options.body), | ||
headers: new _nodeFetch.Headers({ | ||
Accept: 'application/json', | ||
'Accept-Encoding': 'gzip, deflate', | ||
'User-Agent': 'expo-server-sdk-node/' + sdkVersion | ||
}), | ||
agent: this._httpAgent | ||
}; | ||
if (!(options.body != null)) { | ||
_context3.next = 14; | ||
break; | ||
_requestAsync(url, options) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let requestBody; | ||
let sdkVersion = require('../package.json').version; | ||
let requestHeaders = new node_fetch_1.Headers({ | ||
Accept: 'application/json', | ||
'Accept-Encoding': 'gzip, deflate', | ||
'User-Agent': `expo-server-sdk-node/${sdkVersion}`, | ||
}); | ||
if (options.body != null) { | ||
let json = JSON.stringify(options.body); | ||
assert_1.default(json != null, `JSON request body must not be null`); | ||
if (options.shouldCompress(json)) { | ||
requestBody = yield _gzipAsync(Buffer.from(json)); | ||
requestHeaders.set('Content-Encoding', 'gzip'); | ||
} | ||
json = JSON.stringify(options.body); | ||
(0, _invariant2.default)(json != null, 'JSON request body must not be null'); | ||
if (!options.shouldCompress(json)) { | ||
_context3.next = 12; | ||
break; | ||
else { | ||
requestBody = json; | ||
} | ||
_context3.next = 8; | ||
return _gzipAsync(Buffer.from(json)); | ||
case 8: | ||
fetchOptions.body = _context3.sent; | ||
fetchOptions.headers.set('Content-Encoding', 'gzip'); | ||
_context3.next = 13; | ||
break; | ||
case 12: | ||
fetchOptions.body = json; | ||
case 13: | ||
fetchOptions.headers.set('Content-Type', 'application/json'); | ||
case 14: | ||
_context3.next = 16; | ||
return this._limitConcurrentRequests(function () { | ||
return (0, _nodeFetch2.default)(url, fetchOptions); | ||
}); | ||
case 16: | ||
response = _context3.sent; | ||
if (!(response.status !== 200)) { | ||
_context3.next = 22; | ||
break; | ||
} | ||
_context3.next = 20; | ||
return this._parseErrorResponseAsync(response); | ||
case 20: | ||
apiError = _context3.sent; | ||
requestHeaders.set('Content-Type', 'application/json'); | ||
} | ||
let response = yield this._limitConcurrentRequests(() => node_fetch_1.default(url, { | ||
method: options.httpMethod, | ||
body: requestBody, | ||
headers: requestHeaders, | ||
agent: this._httpAgent, | ||
})); | ||
if (response.status !== 200) { | ||
let apiError = yield this._parseErrorResponseAsync(response); | ||
throw apiError; | ||
case 22: | ||
_context3.next = 24; | ||
return response.text(); | ||
case 24: | ||
textBody = _context3.sent; | ||
// We expect the API response body to be JSON | ||
result = void 0; | ||
_context3.prev = 26; | ||
} | ||
let textBody = yield response.text(); | ||
// We expect the API response body to be JSON | ||
let result; | ||
try { | ||
result = JSON.parse(textBody); | ||
_context3.next = 36; | ||
break; | ||
case 30: | ||
_context3.prev = 30; | ||
_context3.t0 = _context3['catch'](26); | ||
_context3.next = 34; | ||
return this._getTextResponseErrorAsync(response, textBody); | ||
case 34: | ||
_apiError = _context3.sent; | ||
throw _apiError; | ||
case 36: | ||
if (!result.errors) { | ||
_context3.next = 39; | ||
break; | ||
} | ||
_apiError2 = this._getErrorFromResult(result); | ||
throw _apiError2; | ||
case 39: | ||
return _context3.abrupt('return', result.data); | ||
case 40: | ||
case 'end': | ||
return _context3.stop(); | ||
} | ||
} | ||
}, _callee3, this, [[26, 30]]); | ||
})); | ||
function _requestAsync(_x4, _x5) { | ||
return _ref3.apply(this, arguments); | ||
} | ||
return _requestAsync; | ||
}() | ||
}, { | ||
key: '_parseErrorResponseAsync', | ||
value: function () { | ||
var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(response) { | ||
var textBody, result, apiError; | ||
return _regenerator2.default.wrap(function _callee4$(_context4) { | ||
while (1) { | ||
switch (_context4.prev = _context4.next) { | ||
case 0: | ||
_context4.next = 2; | ||
return response.text(); | ||
case 2: | ||
textBody = _context4.sent; | ||
result = void 0; | ||
_context4.prev = 4; | ||
catch (e) { | ||
let apiError = yield this._getTextResponseErrorAsync(response, textBody); | ||
throw apiError; | ||
} | ||
if (result.errors) { | ||
let apiError = this._getErrorFromResult(result); | ||
throw apiError; | ||
} | ||
return result.data; | ||
}); | ||
} | ||
_parseErrorResponseAsync(response) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let textBody = yield response.text(); | ||
let result; | ||
try { | ||
result = JSON.parse(textBody); | ||
_context4.next = 13; | ||
break; | ||
case 8: | ||
_context4.prev = 8; | ||
_context4.t0 = _context4['catch'](4); | ||
_context4.next = 12; | ||
return this._getTextResponseErrorAsync(response, textBody); | ||
case 12: | ||
return _context4.abrupt('return', _context4.sent); | ||
case 13: | ||
if (!(!result.errors || !Array.isArray(result.errors) || !result.errors.length)) { | ||
_context4.next = 19; | ||
break; | ||
} | ||
_context4.next = 16; | ||
return this._getTextResponseErrorAsync(response, textBody); | ||
case 16: | ||
apiError = _context4.sent; | ||
} | ||
catch (e) { | ||
return yield this._getTextResponseErrorAsync(response, textBody); | ||
} | ||
if (!result.errors || !Array.isArray(result.errors) || !result.errors.length) { | ||
let apiError = yield this._getTextResponseErrorAsync(response, textBody); | ||
apiError.errorData = result; | ||
return _context4.abrupt('return', apiError); | ||
case 19: | ||
return _context4.abrupt('return', this._getErrorFromResult(result)); | ||
case 20: | ||
case 'end': | ||
return _context4.stop(); | ||
return apiError; | ||
} | ||
} | ||
}, _callee4, this, [[4, 8]]); | ||
})); | ||
function _parseErrorResponseAsync(_x6) { | ||
return _ref4.apply(this, arguments); | ||
} | ||
return _parseErrorResponseAsync; | ||
}() | ||
}, { | ||
key: '_getTextResponseErrorAsync', | ||
value: function () { | ||
var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(response, text) { | ||
var apiError; | ||
return _regenerator2.default.wrap(function _callee5$(_context5) { | ||
while (1) { | ||
switch (_context5.prev = _context5.next) { | ||
case 0: | ||
apiError = new Error('Expo responded with an error with status code ' + response.status + ': ' + text); | ||
apiError.statusCode = response.status; | ||
apiError.errorText = text; | ||
return _context5.abrupt('return', apiError); | ||
case 4: | ||
case 'end': | ||
return _context5.stop(); | ||
} | ||
} | ||
}, _callee5, this); | ||
})); | ||
function _getTextResponseErrorAsync(_x7, _x8) { | ||
return _ref5.apply(this, arguments); | ||
} | ||
return _getTextResponseErrorAsync; | ||
}() | ||
return this._getErrorFromResult(result); | ||
}); | ||
} | ||
_getTextResponseErrorAsync(response, text) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let apiError = new Error(`Expo responded with an error with status code ${response.status}: ` + text); | ||
apiError.statusCode = response.status; | ||
apiError.errorText = text; | ||
return apiError; | ||
}); | ||
} | ||
/** | ||
@@ -445,79 +214,43 @@ * Returns an error for the first API error in the result, with an optional `others` field that | ||
*/ | ||
}, { | ||
key: '_getErrorFromResult', | ||
value: function _getErrorFromResult(result) { | ||
var _this = this; | ||
(0, _invariant2.default)(result.errors && result.errors.length > 0, 'Expected at least one error from Expo'); | ||
var _result$errors = (0, _toArray3.default)(result.errors), | ||
errorData = _result$errors[0], | ||
otherErrorData = _result$errors.slice(1); | ||
var error = this._getErrorFromResultError(errorData); | ||
if (otherErrorData.length) { | ||
error.others = otherErrorData.map(function (data) { | ||
return _this._getErrorFromResultError(data); | ||
}); | ||
} | ||
return error; | ||
_getErrorFromResult(result) { | ||
assert_1.default(result.errors && result.errors.length > 0, `Expected at least one error from Expo`); | ||
let [errorData, ...otherErrorData] = result.errors; | ||
let error = this._getErrorFromResultError(errorData); | ||
if (otherErrorData.length) { | ||
error.others = otherErrorData.map(data => this._getErrorFromResultError(data)); | ||
} | ||
return error; | ||
} | ||
/** | ||
* Returns an error for a single API error | ||
*/ | ||
}, { | ||
key: '_getErrorFromResultError', | ||
value: function _getErrorFromResultError(errorData) { | ||
var error = new Error(errorData.message); | ||
error.code = errorData.code; | ||
if (errorData.details != null) { | ||
error.details = errorData.details; | ||
} | ||
if (errorData.stack != null) { | ||
error.serverStack = errorData.stack; | ||
} | ||
return error; | ||
_getErrorFromResultError(errorData) { | ||
let error = new Error(errorData.message); | ||
error.code = errorData.code; | ||
if (errorData.details != null) { | ||
error.details = errorData.details; | ||
} | ||
if (errorData.stack != null) { | ||
error.serverStack = errorData.stack; | ||
} | ||
return error; | ||
} | ||
}], [{ | ||
key: 'isExpoPushToken', | ||
value: function isExpoPushToken(token) { | ||
return typeof token === 'string' && (token.startsWith('ExponentPushToken[') || token.startsWith('ExpoPushToken[')) && token.endsWith(']'); | ||
} | ||
/** | ||
* Legacy alias for isExpoPushToken | ||
*/ | ||
}, { | ||
key: 'isExponentPushToken', | ||
value: function isExponentPushToken(token) { | ||
return ExpoClient.isExpoPushToken(token); | ||
} | ||
}]); | ||
return ExpoClient; | ||
}(); | ||
} | ||
ExpoClient.pushNotificationChunkSizeLimit = PUSH_NOTIFICATION_CHUNK_LIMIT; | ||
ExpoClient.pushNotificationReceiptChunkSizeLimit = PUSH_NOTIFICATION_RECEIPT_CHUNK_LIMIT; | ||
exports.default = ExpoClient; | ||
function _gzipAsync(data) { | ||
return new Promise(function (resolve, reject) { | ||
_zlib2.default.gzip(data, function (error, result) { | ||
if (error) { | ||
reject(error); | ||
} else { | ||
resolve(result); | ||
} | ||
return new Promise((resolve, reject) => { | ||
zlib_1.default.gzip(data, (error, result) => { | ||
if (error) { | ||
reject(error); | ||
} | ||
else { | ||
resolve(result); | ||
} | ||
}); | ||
}); | ||
}); | ||
} | ||
module.exports = exports['default']; | ||
class ExtensibleError extends Error { | ||
} | ||
//# sourceMappingURL=ExpoClient.js.map |
{ | ||
"name": "expo-server-sdk", | ||
"version": "2.4.0", | ||
"version": "3.0.0-alpha.0", | ||
"description": "Server side library for working with Expo using Node.js", | ||
"main": "build/ExpoClient.js", | ||
"types": "src/ExpoClient.ts", | ||
"files": [ | ||
"build" | ||
"build", | ||
"src" | ||
], | ||
@@ -14,3 +16,4 @@ "scripts": { | ||
"test": "jest", | ||
"watch": "babel src --out-dir build --ignore __tests__ --source-maps --watch" | ||
"tsc": "tsc", | ||
"watch": "tsc --watch" | ||
}, | ||
@@ -21,3 +24,18 @@ "jest": { | ||
], | ||
"testEnvironment": "node" | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"js" | ||
], | ||
"transform": { | ||
"^.+\\.ts$": "ts-jest" | ||
}, | ||
"globals": { | ||
"ts-jest": { | ||
"tsConfigFile": "tsconfig.json" | ||
} | ||
}, | ||
"testEnvironment": "node", | ||
"testMatch": [ | ||
"**/__tests__/*.+(js|ts)" | ||
] | ||
}, | ||
@@ -30,3 +48,2 @@ "repository": { | ||
"expo", | ||
"react-native", | ||
"push-notifications" | ||
@@ -41,4 +58,4 @@ ], | ||
"dependencies": { | ||
"babel-runtime": "^6.11.6", | ||
"invariant": "^2.2.4", | ||
"@types/invariant": "^2.2.29", | ||
"@types/node-fetch": "^2.1.1", | ||
"node-fetch": "^2.1.2", | ||
@@ -48,12 +65,6 @@ "promise-limit": "^2.6.0" | ||
"devDependencies": { | ||
"babel-cli": "^6.24.0", | ||
"babel-plugin-add-module-exports": "^0.2.1", | ||
"babel-plugin-transform-class-properties": "^6.23.0", | ||
"babel-plugin-transform-flow-strip-types": "^6.22.0", | ||
"babel-plugin-transform-runtime": "^6.15.0", | ||
"babel-preset-es2015": "^6.24.0", | ||
"babel-preset-es2016": "^6.11.3", | ||
"babel-preset-es2017": "^6.14.0", | ||
"jest": "^23.1.0" | ||
"jest": "^23.1.0", | ||
"ts-jest": "^23.0.0", | ||
"typescript": "^2.9.2" | ||
} | ||
} |
@@ -12,4 +12,2 @@ # expo-server-sdk-node [](https://circleci.com/gh/expo/exponent-server-sdk-node) [](https://codecov.io/gh/expo/exponent-server-sdk-node) | ||
**Note for Firebase:** You must have a paid plan to send push notifications with this library. As [Firebase's pricing page](https://firebase.google.com/pricing/) says, "The Spark plan allows outbound network requests only to Google-owned services." | ||
```js | ||
@@ -47,3 +45,3 @@ import Expo from 'expo-server-sdk'; | ||
let chunks = expo.chunkPushNotifications(messages); | ||
let tickets = []; | ||
(async () => { | ||
@@ -55,4 +53,53 @@ // Send the chunks to the Expo push notification service. There are | ||
try { | ||
let receipts = await expo.sendPushNotificationsAsync(chunk); | ||
let ticketChunk = await expo.sendPushNotificationsAsync(chunk); | ||
console.log(ticketChunk); | ||
tickets.push(...ticketChunk); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} | ||
})(); | ||
... | ||
// Later, after the Expo push notification service has delivered the | ||
// notifications to Apple or Google (usually quickly, but allow the the service | ||
// up to 30 minutes when under load), a "receipt" for each notification is | ||
// created. The receipts will be available for at least a day; stale receipts | ||
// are deleted. | ||
// | ||
// The ID of each receipt is sent back in the response "ticket" for each | ||
// notification. In summary, sending a notification produces a ticket, which | ||
// contains a receipt ID you later use to get the receipt. | ||
// | ||
// The receipts may contain error codes to which you must respond. In | ||
// particular, Apple or Google may block apps that continue to send | ||
// notifications to devices that have blocked notifications or have uninstalled | ||
// your app. Expo does not control this policy and sends back the feedback from | ||
// Apple and Google so you can handle it appropriately. | ||
let receiptIds = tickets.map(ticket => ticket.id); | ||
let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds); | ||
(async () => { | ||
// Like sending notifications, there are different strategies you could use | ||
// to retrieve batches of receipts from the Expo service. | ||
for (let chunk of receiptIdChunks) { | ||
try { | ||
let receipts = await expo.getPushNotificationReceiptsAsync(chunk); | ||
console.log(receipts); | ||
// The receipts specify whether Apple or Google successfully received the | ||
// notification and information about an error, if one occurred. | ||
for (let receipt of receipts) { | ||
if (receipt.status === 'ok') { | ||
continue; | ||
} else if (receipt.status === 'error') { | ||
console.error(`There was an error sending a notification: ${receipt.message}`); | ||
if (receipt.details && receipt.details.error) { | ||
// The error codes are listed in the Expo documentation: | ||
// https://docs.expo.io/versions/latest/guides/push-notifications#response-format | ||
// You must handle the errors appropriately. | ||
console.error(`The error code is ${receipt.details.error}`); | ||
} | ||
} | ||
} | ||
} catch (error) { | ||
@@ -63,2 +110,3 @@ console.error(error); | ||
})(); | ||
``` | ||
@@ -65,0 +113,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
3
-66.67%7
16.67%583
42.89%126
61.54%35486
-19.11%1
Infinity%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed