Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

expo-server-sdk

Package Overview
Dependencies
Maintainers
27
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

expo-server-sdk - npm Package Compare versions

Comparing version 3.11.0 to 3.12.0

20

build/ExpoClient.d.ts

@@ -1,3 +0,2 @@

/// <reference types="node" />
import { Agent } from 'http';
import { Agent } from 'node:http';
export declare class Expo {

@@ -10,3 +9,4 @@ static pushNotificationChunkSizeLimit: number;

private useFcmV1;
constructor(options?: ExpoClientOptions);
private retryMinTimeout;
constructor(options?: Partial<ExpoClientOptions>);
/**

@@ -50,6 +50,7 @@ * Returns `true` if the token is an Expo push token

export type ExpoClientOptions = {
httpAgent?: Agent;
maxConcurrentRequests?: number;
accessToken?: string;
useFcmV1?: boolean;
httpAgent: Agent;
maxConcurrentRequests: number;
retryMinTimeout: number;
accessToken: string;
useFcmV1: boolean;
};

@@ -59,3 +60,3 @@ export type ExpoPushToken = string;

to: ExpoPushToken | ExpoPushToken[];
data?: object;
data?: Record<string, unknown>;
title?: string;

@@ -76,2 +77,3 @@ subtitle?: string;

mutableContent?: boolean;
_contentAvailable?: boolean;
};

@@ -95,6 +97,6 @@ export type ExpoPushReceiptId = string;

error?: 'DeveloperError' | 'DeviceNotRegistered' | 'ExpoError' | 'InvalidCredentials' | 'MessageRateExceeded' | 'MessageTooBig' | 'ProviderError';
expoPushToken?: string;
};
expoPushToken?: string;
__debug?: any;
};
export type ExpoPushReceipt = ExpoPushSuccessReceipt | ExpoPushErrorReceipt;

@@ -25,11 +25,2 @@ "use strict";

};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
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) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -47,14 +38,20 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

*/
const assert_1 = __importDefault(require("assert"));
const node_fetch_1 = __importStar(require("node-fetch"));
const node_assert_1 = __importDefault(require("node:assert"));
const node_zlib_1 = require("node:zlib");
const promise_limit_1 = __importDefault(require("promise-limit"));
const promise_retry_1 = __importDefault(require("promise-retry"));
const zlib_1 = __importDefault(require("zlib"));
const ExpoClientValues_1 = require("./ExpoClientValues");
class Expo {
static pushNotificationChunkSizeLimit = ExpoClientValues_1.pushNotificationChunkLimit;
static pushNotificationReceiptChunkSizeLimit = ExpoClientValues_1.pushNotificationReceiptChunkLimit;
httpAgent;
limitConcurrentRequests;
accessToken;
useFcmV1;
retryMinTimeout;
constructor(options = {}) {
this.httpAgent = options.httpAgent;
this.limitConcurrentRequests = (0, promise_limit_1.default)(options.maxConcurrentRequests != null
? options.maxConcurrentRequests
: ExpoClientValues_1.defaultConcurrentRequestLimit);
this.limitConcurrentRequests = (0, promise_limit_1.default)(options.maxConcurrentRequests ?? ExpoClientValues_1.defaultConcurrentRequestLimit);
this.retryMinTimeout = options.retryMinTimeout ?? ExpoClientValues_1.requestRetryMinTimeout;
this.accessToken = options.accessToken;

@@ -83,58 +80,54 @@ this.useFcmV1 = options.useFcmV1;

*/
sendPushNotificationsAsync(messages) {
return __awaiter(this, void 0, void 0, function* () {
const url = new URL(ExpoClientValues_1.sendApiUrl);
// Only append the useFcmV1 option if the option is set to false
if (this.useFcmV1 === false) {
url.searchParams.append('useFcmV1', String(this.useFcmV1));
}
const actualMessagesCount = Expo._getActualMessageCount(messages);
const data = yield this.limitConcurrentRequests(() => __awaiter(this, void 0, void 0, function* () {
return yield (0, promise_retry_1.default)((retry) => __awaiter(this, void 0, void 0, function* () {
try {
return yield this.requestAsync(url.toString(), {
httpMethod: 'post',
body: messages,
shouldCompress(body) {
return body.length > 1024;
},
});
async sendPushNotificationsAsync(messages) {
const url = new URL(ExpoClientValues_1.sendApiUrl);
// Only append the useFcmV1 option if the option is set to false
if (this.useFcmV1 === false) {
url.searchParams.append('useFcmV1', String(this.useFcmV1));
}
const actualMessagesCount = Expo._getActualMessageCount(messages);
const data = await this.limitConcurrentRequests(async () => {
return await (0, promise_retry_1.default)(async (retry) => {
try {
return await this.requestAsync(url.toString(), {
httpMethod: 'post',
body: messages,
shouldCompress(body) {
return body.length > 1024;
},
});
}
catch (e) {
// if Expo servers rate limit, retry with exponential backoff
if (e.statusCode === 429) {
return retry(e);
}
catch (e) {
// if Expo servers rate limit, retry with exponential backoff
if (e.statusCode === 429) {
return retry(e);
}
throw e;
}
}), {
retries: 2,
factor: 2,
minTimeout: ExpoClientValues_1.requestRetryMinTimeout,
});
}));
if (!Array.isArray(data) || data.length !== actualMessagesCount) {
const apiError = new Error(`Expected Expo to respond with ${actualMessagesCount} ${actualMessagesCount === 1 ? 'ticket' : 'tickets'} but got ${data.length}`);
apiError.data = data;
throw apiError;
}
return data;
throw e;
}
}, {
retries: 2,
factor: 2,
minTimeout: this.retryMinTimeout,
});
});
if (!Array.isArray(data) || data.length !== actualMessagesCount) {
const apiError = new Error(`Expected Expo to respond with ${actualMessagesCount} ${actualMessagesCount === 1 ? 'ticket' : 'tickets'} but got ${data.length}`);
apiError['data'] = data;
throw apiError;
}
return data;
}
getPushNotificationReceiptsAsync(receiptIds) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.requestAsync(ExpoClientValues_1.getReceiptsApiUrl, {
httpMethod: 'post',
body: { ids: receiptIds },
shouldCompress(body) {
return body.length > 1024;
},
});
if (!data || typeof data !== 'object' || Array.isArray(data)) {
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;
throw apiError;
}
return data;
async getPushNotificationReceiptsAsync(receiptIds) {
const data = await this.requestAsync(ExpoClientValues_1.getReceiptsApiUrl, {
httpMethod: 'post',
body: { ids: receiptIds },
shouldCompress(body) {
return body.length > 1024;
},
});
if (!data || typeof data !== 'object' || Array.isArray(data)) {
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;
throw apiError;
}
return data;
}

@@ -154,3 +147,3 @@ chunkPushNotifications(messages) {

// Then create a new chunk to continue on the remaining recipients for this message.
chunk.push(Object.assign(Object.assign({}, message), { to: partialTo }));
chunk.push({ ...message, to: partialTo });
chunks.push(chunk);

@@ -164,3 +157,3 @@ chunk = [];

// Add remaining `partialTo` to the chunk.
chunk.push(Object.assign(Object.assign({}, message), { to: partialTo }));
chunk.push({ ...message, to: partialTo });
}

@@ -204,79 +197,73 @@ }

}
requestAsync(url, options) {
return __awaiter(this, void 0, void 0, function* () {
let requestBody;
const sdkVersion = require('../package.json').version;
const requestHeaders = new node_fetch_1.Headers({
Accept: 'application/json',
'Accept-Encoding': 'gzip, deflate',
'User-Agent': `expo-server-sdk-node/${sdkVersion}`,
});
if (this.accessToken) {
requestHeaders.set('Authorization', `Bearer ${this.accessToken}`);
}
if (options.body != null) {
const json = JSON.stringify(options.body);
(0, 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');
}
else {
requestBody = json;
}
requestHeaders.set('Content-Type', 'application/json');
}
const response = yield (0, node_fetch_1.default)(url, {
method: options.httpMethod,
body: requestBody,
headers: requestHeaders,
agent: this.httpAgent,
});
if (response.status !== 200) {
const apiError = yield this.parseErrorResponseAsync(response);
throw apiError;
}
const textBody = yield response.text();
// We expect the API response body to be JSON
let result;
try {
result = JSON.parse(textBody);
}
catch (_a) {
const apiError = yield this.getTextResponseErrorAsync(response, textBody);
throw apiError;
}
if (result.errors) {
const apiError = this.getErrorFromResult(response, result);
throw apiError;
}
return result.data;
async requestAsync(url, options) {
let requestBody;
const sdkVersion = require('../package.json').version;
const requestHeaders = new node_fetch_1.Headers({
Accept: 'application/json',
'Accept-Encoding': 'gzip, deflate',
'User-Agent': `expo-server-sdk-node/${sdkVersion}`,
});
}
parseErrorResponseAsync(response) {
return __awaiter(this, void 0, void 0, function* () {
const textBody = yield response.text();
let result;
try {
result = JSON.parse(textBody);
if (this.accessToken) {
requestHeaders.set('Authorization', `Bearer ${this.accessToken}`);
}
if (options.body != null) {
const json = JSON.stringify(options.body);
(0, node_assert_1.default)(json != null, `JSON request body must not be null`);
if (options.shouldCompress(json)) {
requestBody = (0, node_zlib_1.gzipSync)(Buffer.from(json));
requestHeaders.set('Content-Encoding', 'gzip');
}
catch (_a) {
return yield this.getTextResponseErrorAsync(response, textBody);
else {
requestBody = json;
}
if (!result.errors || !Array.isArray(result.errors) || !result.errors.length) {
const apiError = yield this.getTextResponseErrorAsync(response, textBody);
apiError.errorData = result;
return apiError;
}
return this.getErrorFromResult(response, result);
requestHeaders.set('Content-Type', 'application/json');
}
const response = await (0, node_fetch_1.default)(url, {
method: options.httpMethod,
body: requestBody,
headers: requestHeaders,
agent: this.httpAgent,
});
if (response.status !== 200) {
const apiError = await this.parseErrorResponseAsync(response);
throw apiError;
}
const textBody = await response.text();
// We expect the API response body to be JSON
let result;
try {
result = JSON.parse(textBody);
}
catch {
const apiError = await this.getTextResponseErrorAsync(response, textBody);
throw apiError;
}
if (result.errors) {
const apiError = this.getErrorFromResult(response, result);
throw apiError;
}
return result.data;
}
getTextResponseErrorAsync(response, text) {
return __awaiter(this, void 0, void 0, function* () {
const apiError = new Error(`Expo responded with an error with status code ${response.status}: ` + text);
apiError.statusCode = response.status;
apiError.errorText = text;
async parseErrorResponseAsync(response) {
const textBody = await response.text();
let result;
try {
result = JSON.parse(textBody);
}
catch {
return await this.getTextResponseErrorAsync(response, textBody);
}
if (!result.errors || !Array.isArray(result.errors) || !result.errors.length) {
const apiError = await this.getTextResponseErrorAsync(response, textBody);
apiError['errorData'] = result;
return apiError;
});
}
return this.getErrorFromResult(response, result);
}
async getTextResponseErrorAsync(response, text) {
const apiError = new Error(`Expo responded with an error with status code ${response.status}: ` + text);
apiError['statusCode'] = response.status;
apiError['errorText'] = text;
return apiError;
}
/**

@@ -287,9 +274,11 @@ * Returns an error for the first API error in the result, with an optional `others` field that

getErrorFromResult(response, result) {
(0, assert_1.default)(result.errors && result.errors.length > 0, `Expected at least one error from Expo`);
const noErrorsMessage = `Expected at least one error from Expo`;
(0, node_assert_1.default)(result.errors, noErrorsMessage);
const [errorData, ...otherErrorData] = result.errors;
node_assert_1.default.ok(errorData, noErrorsMessage);
const error = this.getErrorFromResultError(errorData);
if (otherErrorData.length) {
error.others = otherErrorData.map((data) => this.getErrorFromResultError(data));
error['others'] = otherErrorData.map((data) => this.getErrorFromResultError(data));
}
error.statusCode = response.status;
error['statusCode'] = response.status;
return error;

@@ -302,8 +291,8 @@ }

const error = new Error(errorData.message);
error.code = errorData.code;
error['code'] = errorData.code;
if (errorData.details != null) {
error.details = errorData.details;
error['details'] = errorData.details;
}
if (errorData.stack != null) {
error.serverStack = errorData.stack;
error['serverStack'] = errorData.stack;
}

@@ -325,19 +314,5 @@ return error;

exports.Expo = Expo;
Expo.pushNotificationChunkSizeLimit = ExpoClientValues_1.pushNotificationChunkLimit;
Expo.pushNotificationReceiptChunkSizeLimit = ExpoClientValues_1.pushNotificationReceiptChunkLimit;
exports.default = Expo;
function gzipAsync(data) {
return new Promise((resolve, reject) => {
zlib_1.default.gzip(data, (error, result) => {
if (error) {
reject(error);
}
else {
resolve(result);
}
});
});
}
class ExtensibleError extends Error {
}
//# sourceMappingURL=ExpoClient.js.map
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });

@@ -11,3 +10,3 @@ exports.requestRetryMinTimeout = exports.defaultConcurrentRequestLimit = exports.pushNotificationReceiptChunkLimit = exports.pushNotificationChunkLimit = exports.getReceiptsApiUrl = exports.sendApiUrl = void 0;

*/
const baseUrl = (_a = process.env.EXPO_BASE_URL) !== null && _a !== void 0 ? _a : 'https://exp.host';
const baseUrl = process.env['EXPO_BASE_URL'] ?? 'https://exp.host';
exports.sendApiUrl = `${baseUrl}/--/api/v2/push/send`;

@@ -14,0 +13,0 @@ exports.getReceiptsApiUrl = `${baseUrl}/--/api/v2/push/getReceipts`;

{
"name": "expo-server-sdk",
"version": "3.11.0",
"version": "3.12.0",
"description": "Server-side library for working with Expo using Node.js",

@@ -11,3 +11,3 @@ "main": "build/ExpoClient.js",

"scripts": {
"build": "./build.sh",
"build": "tsc --project tsconfig.build.json",
"lint": "eslint src",

@@ -24,2 +24,10 @@ "prepare": "yarn build",

"coverageDirectory": "<rootDir>/../coverage",
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 0
}
},
"preset": "ts-jest",

@@ -49,13 +57,15 @@ "rootDir": "src",

"devDependencies": {
"@types/jest": "^29.5.12",
"@tsconfig/node-lts": "^20.1.3",
"@tsconfig/strictest": "^2.0.5",
"@types/node-fetch": "^2.6.11",
"@types/promise-retry": "^1.1.6",
"eslint": "^8.57.0",
"eslint-config-universe": "^12.0.0",
"eslint-config-universe": "^13.0.0",
"fetch-mock": "^9.11.0",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "~29.1.2",
"ts-jest": "~29.2.5",
"typescript": "^5.4.2"
}
},
"packageManager": "yarn@4.4.1"
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc