@onesignal/node-onesignal 5.8.0 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/apis/exception.d.ts +1 -0
- package/dist/apis/exception.js +43 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +11 -0
- package/dist/helpers.d.ts +12 -0
- package/dist/helpers.js +79 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types/PromiseAPI.d.ts +4 -0
- package/dist/types/PromiseAPI.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,15 @@ const response = await client.createNotification(notification);
|
|
|
62
62
|
console.log('Notification ID:', response.id);
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## Send with idempotent retries
|
|
66
|
+
|
|
67
|
+
`createNotificationWithRetry` generates a UUIDv4 `idempotency_key` when absent (a caller-provided key is respected), retries 429 / 503 / transport errors with the **same** key (honoring `Retry-After`, exponential backoff otherwise; `maxRetries` / `baseDelayMs` configurable via the options object), fails fast on other errors, and reports via `wasReplayed` whether the server answered from a previously completed request (`Idempotent-Replayed` response header). It is a `DefaultApi` method, so the call mirrors `createNotification`:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const result = await client.createNotificationWithRetry(notification);
|
|
71
|
+
console.log('Notification ID:', result.response.id, 'replayed:', result.wasReplayed);
|
|
72
|
+
```
|
|
73
|
+
|
|
65
74
|
## Send a push notification by External ID
|
|
66
75
|
|
|
67
76
|
Target specific users with the alias label `external_id` (snake_case). This is different from the notification-level `external_id` field, which is only for [idempotent requests](https://documentation.onesignal.com/docs/idempotent-notification-requests).
|
package/dist/apis/exception.d.ts
CHANGED
package/dist/apis/exception.js
CHANGED
|
@@ -10,6 +10,49 @@ class ApiException extends Error {
|
|
|
10
10
|
this.headers = headers;
|
|
11
11
|
Object.setPrototypeOf(this, ApiException.prototype);
|
|
12
12
|
}
|
|
13
|
+
get errorMessages() {
|
|
14
|
+
let parsed = this.body;
|
|
15
|
+
if (typeof parsed === "string") {
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(parsed);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const errors = parsed.errors;
|
|
27
|
+
if (typeof errors === "string") {
|
|
28
|
+
return [errors];
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(errors)) {
|
|
31
|
+
return errors
|
|
32
|
+
.map((e) => {
|
|
33
|
+
if (typeof e === "string") {
|
|
34
|
+
return e;
|
|
35
|
+
}
|
|
36
|
+
if (e !== null && typeof e === "object") {
|
|
37
|
+
const title = typeof e.title === "string" ? e.title : undefined;
|
|
38
|
+
const code = typeof e.code === "string" ? e.code : (e.code != null ? String(e.code) : undefined);
|
|
39
|
+
return title || code;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
})
|
|
43
|
+
.filter((m) => typeof m === "string");
|
|
44
|
+
}
|
|
45
|
+
if (errors !== null && typeof errors === "object") {
|
|
46
|
+
return Object.keys(errors)
|
|
47
|
+
.map((key) => {
|
|
48
|
+
const value = errors[key];
|
|
49
|
+
const rendered = typeof value === "string" ? value : JSON.stringify(value);
|
|
50
|
+
return key + ": " + rendered;
|
|
51
|
+
})
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
13
56
|
}
|
|
14
57
|
exports.ApiException = ApiException;
|
|
15
58
|
//# sourceMappingURL=exception.js.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const OneSignalErrors: {
|
|
2
|
+
readonly INVALID_API_KEY: "Access denied. Please include an 'Authorization: ...' header with a valid API key (https://documentation.onesignal.com/docs/en/keys-and-ids#api-keys).";
|
|
3
|
+
readonly NOTIFICATION_NOT_FOUND: "Notification not found";
|
|
4
|
+
readonly NO_SUBSCRIBERS: "All included players are not subscribed";
|
|
5
|
+
readonly NO_TARGETING_SPECIFIED: "You must include which players, segments, or tags you wish to send this notification to.";
|
|
6
|
+
readonly SERVICE_UNAVAILABLE: "Service temporarily unavailable";
|
|
7
|
+
};
|
|
8
|
+
export type OneSignalErrorMessage = typeof OneSignalErrors[keyof typeof OneSignalErrors];
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OneSignalErrors = void 0;
|
|
4
|
+
exports.OneSignalErrors = {
|
|
5
|
+
INVALID_API_KEY: "Access denied. Please include an 'Authorization: ...' header with a valid API key (https://documentation.onesignal.com/docs/en/keys-and-ids#api-keys).",
|
|
6
|
+
NOTIFICATION_NOT_FOUND: "Notification not found",
|
|
7
|
+
NO_SUBSCRIBERS: "All included players are not subscribed",
|
|
8
|
+
NO_TARGETING_SPECIFIED: "You must include which players, segments, or tags you wish to send this notification to.",
|
|
9
|
+
SERVICE_UNAVAILABLE: "Service temporarily unavailable",
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Configuration } from './configuration';
|
|
2
|
+
import { Notification } from './models/Notification';
|
|
3
|
+
import { CreateNotificationSuccessResponse } from './models/CreateNotificationSuccessResponse';
|
|
4
|
+
export interface CreateNotificationWithRetryOptions {
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
baseDelayMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateNotificationWithRetryResult {
|
|
9
|
+
response: CreateNotificationSuccessResponse;
|
|
10
|
+
wasReplayed: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function createNotificationWithRetry(configuration: Configuration, notification: Notification, options?: CreateNotificationWithRetryOptions): Promise<CreateNotificationWithRetryResult>;
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNotificationWithRetry = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const DefaultApi_1 = require("./apis/DefaultApi");
|
|
6
|
+
const RETRYABLE_STATUSES = [429, 503];
|
|
7
|
+
const MIN_BASE_DELAY_MS = 1000;
|
|
8
|
+
const MAX_BASE_DELAY_MS = 60000;
|
|
9
|
+
async function createNotificationWithRetry(configuration, notification, options) {
|
|
10
|
+
const maxRetries = options && options.maxRetries !== undefined ? options.maxRetries : 3;
|
|
11
|
+
const requestedBaseDelayMs = options && typeof options.baseDelayMs === 'number' && isFinite(options.baseDelayMs)
|
|
12
|
+
? options.baseDelayMs
|
|
13
|
+
: MIN_BASE_DELAY_MS;
|
|
14
|
+
const baseDelayMs = Math.min(MAX_BASE_DELAY_MS, Math.max(MIN_BASE_DELAY_MS, requestedBaseDelayMs));
|
|
15
|
+
if (!notification.idempotency_key) {
|
|
16
|
+
notification.idempotency_key = generateUuidV4();
|
|
17
|
+
}
|
|
18
|
+
const requestFactory = new DefaultApi_1.DefaultApiRequestFactory(configuration);
|
|
19
|
+
const responseProcessor = new DefaultApi_1.DefaultApiResponseProcessor();
|
|
20
|
+
let attempt = 0;
|
|
21
|
+
while (true) {
|
|
22
|
+
let response;
|
|
23
|
+
try {
|
|
24
|
+
const requestContext = await requestFactory.createNotification(notification, configuration);
|
|
25
|
+
response = await configuration.httpApi.send(requestContext).toPromise();
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
if (attempt >= maxRetries) {
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
await sleep(baseDelayMs * Math.pow(2, attempt));
|
|
32
|
+
attempt++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (RETRYABLE_STATUSES.indexOf(response.httpStatusCode) !== -1 && attempt < maxRetries) {
|
|
36
|
+
await sleep(retryDelayMs(response.headers, attempt, baseDelayMs));
|
|
37
|
+
attempt++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const body = await responseProcessor.createNotification(response);
|
|
41
|
+
return { response: body, wasReplayed: isReplayed(response.headers) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.createNotificationWithRetry = createNotificationWithRetry;
|
|
45
|
+
function headerValue(headers, name) {
|
|
46
|
+
const target = name.toLowerCase();
|
|
47
|
+
for (const key in headers) {
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(headers, key) && key.toLowerCase() === target) {
|
|
49
|
+
return headers[key];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
function isReplayed(headers) {
|
|
55
|
+
const value = headerValue(headers, 'idempotent-replayed');
|
|
56
|
+
return value !== undefined && value.trim().toLowerCase() === 'true';
|
|
57
|
+
}
|
|
58
|
+
function retryDelayMs(headers, attempt, baseDelayMs) {
|
|
59
|
+
const retryAfter = headerValue(headers, 'retry-after');
|
|
60
|
+
if (retryAfter !== undefined && /^\d+$/.test(retryAfter.trim())) {
|
|
61
|
+
return parseInt(retryAfter.trim(), 10) * 1000;
|
|
62
|
+
}
|
|
63
|
+
return baseDelayMs * Math.pow(2, attempt);
|
|
64
|
+
}
|
|
65
|
+
function generateUuidV4() {
|
|
66
|
+
const bytes = (0, crypto_1.randomBytes)(16);
|
|
67
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
68
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
69
|
+
const hex = bytes.toString('hex');
|
|
70
|
+
return (hex.substring(0, 8) + '-' +
|
|
71
|
+
hex.substring(8, 12) + '-' +
|
|
72
|
+
hex.substring(12, 16) + '-' +
|
|
73
|
+
hex.substring(16, 20) + '-' +
|
|
74
|
+
hex.substring(20));
|
|
75
|
+
}
|
|
76
|
+
function sleep(ms) {
|
|
77
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=helpers.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ export * from "./models/all";
|
|
|
4
4
|
export { createConfiguration } from "./configuration";
|
|
5
5
|
export { Configuration, ConfigurationParameters } from "./configuration";
|
|
6
6
|
export * from "./apis/exception";
|
|
7
|
+
export * from "./helpers";
|
|
7
8
|
export * from "./servers";
|
|
8
9
|
export { RequiredError } from "./apis/baseapi";
|
|
9
10
|
export { PromiseMiddleware as Middleware } from './middleware';
|
|
10
11
|
export { PromiseDefaultApi as DefaultApi } from './types/PromiseAPI';
|
|
12
|
+
export * from "./errors";
|
package/dist/index.js
CHANGED
|
@@ -21,9 +21,11 @@ __exportStar(require("./models/all"), exports);
|
|
|
21
21
|
var configuration_1 = require("./configuration");
|
|
22
22
|
Object.defineProperty(exports, "createConfiguration", { enumerable: true, get: function () { return configuration_1.createConfiguration; } });
|
|
23
23
|
__exportStar(require("./apis/exception"), exports);
|
|
24
|
+
__exportStar(require("./helpers"), exports);
|
|
24
25
|
__exportStar(require("./servers"), exports);
|
|
25
26
|
var baseapi_1 = require("./apis/baseapi");
|
|
26
27
|
Object.defineProperty(exports, "RequiredError", { enumerable: true, get: function () { return baseapi_1.RequiredError; } });
|
|
27
28
|
var PromiseAPI_1 = require("./types/PromiseAPI");
|
|
28
29
|
Object.defineProperty(exports, "DefaultApi", { enumerable: true, get: function () { return PromiseAPI_1.PromiseDefaultApi; } });
|
|
30
|
+
__exportStar(require("./errors"), exports);
|
|
29
31
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import * as models from '../models/all';
|
|
1
2
|
import { Configuration } from '../configuration';
|
|
3
|
+
import { CreateNotificationWithRetryOptions, CreateNotificationWithRetryResult } from '../helpers';
|
|
2
4
|
import { ApiKeyTokensListResponse } from '../models/ApiKeyTokensListResponse';
|
|
3
5
|
import { App } from '../models/App';
|
|
4
6
|
import { CopyTemplateRequest } from '../models/CopyTemplateRequest';
|
|
@@ -37,7 +39,9 @@ import { UserIdentityBody } from '../models/UserIdentityBody';
|
|
|
37
39
|
import { DefaultApiRequestFactory, DefaultApiResponseProcessor } from "../apis/DefaultApi";
|
|
38
40
|
export declare class PromiseDefaultApi {
|
|
39
41
|
private api;
|
|
42
|
+
private configuration;
|
|
40
43
|
constructor(configuration: Configuration, requestFactory?: DefaultApiRequestFactory, responseProcessor?: DefaultApiResponseProcessor);
|
|
44
|
+
createNotificationWithRetry(notification: models.Notification, options?: CreateNotificationWithRetryOptions): Promise<CreateNotificationWithRetryResult>;
|
|
41
45
|
cancelNotification(appId: string, notificationId: string, _options?: Configuration): Promise<GenericSuccessBoolResponse>;
|
|
42
46
|
copyTemplateToApp(templateId: string, appId: string, copyTemplateRequest: CopyTemplateRequest, _options?: Configuration): Promise<TemplateResource>;
|
|
43
47
|
createAlias(appId: string, aliasLabel: string, aliasId: string, userIdentityBody: UserIdentityBody, _options?: Configuration): Promise<UserIdentityBody>;
|
package/dist/types/PromiseAPI.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PromiseDefaultApi = void 0;
|
|
4
|
+
const helpers_1 = require("../helpers");
|
|
4
5
|
const ObservableAPI_1 = require("./ObservableAPI");
|
|
5
6
|
class PromiseDefaultApi {
|
|
6
7
|
constructor(configuration, requestFactory, responseProcessor) {
|
|
7
8
|
this.api = new ObservableAPI_1.ObservableDefaultApi(configuration, requestFactory, responseProcessor);
|
|
9
|
+
this.configuration = configuration;
|
|
10
|
+
}
|
|
11
|
+
createNotificationWithRetry(notification, options) {
|
|
12
|
+
return (0, helpers_1.createNotificationWithRetry)(this.configuration, notification, options);
|
|
8
13
|
}
|
|
9
14
|
cancelNotification(appId, notificationId, _options) {
|
|
10
15
|
const result = this.api.cancelNotification(appId, notificationId, _options);
|