expo-server-sdk
Advanced tools
Comparing version 2.4.0 to 3.0.0-alpha.0
@@ -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 [![CircleCI](https://circleci.com/gh/expo/exponent-server-sdk-node.svg?style=svg)](https://circleci.com/gh/expo/exponent-server-sdk-node) [![codecov](https://codecov.io/gh/expo/exponent-server-sdk-node/branch/master/graph/badge.svg)](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
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
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
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
3
7
583
126
35486
1
1
+ Added@types/invariant@^2.2.29
+ Added@types/node-fetch@^2.1.1
+ Added@types/invariant@2.2.37(transitive)
+ Added@types/node@22.9.1(transitive)
+ Added@types/node-fetch@2.6.12(transitive)
+ Addedasynckit@0.4.0(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addedform-data@4.0.1(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedundici-types@6.19.8(transitive)
- Removedbabel-runtime@^6.11.6
- Removedinvariant@^2.2.4
- Removedbabel-runtime@6.26.0(transitive)
- Removedcore-js@2.6.12(transitive)
- Removedinvariant@2.2.4(transitive)
- Removedjs-tokens@4.0.0(transitive)
- Removedloose-envify@1.4.0(transitive)
- Removedregenerator-runtime@0.11.1(transitive)