bridgex 2.0.0 → 2.1.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/dist/CircuitBreaker.d.ts +25 -0
- package/dist/CircuitBreaker.js +58 -0
- package/dist/CredentialProvisioner.d.ts +27 -0
- package/dist/CredentialProvisioner.js +43 -0
- package/dist/HttpClient.js +30 -3
- package/dist/MessageFormatter.d.ts +13 -1
- package/dist/MessageFormatter.js +30 -6
- package/dist/PluginManager.d.ts +77 -0
- package/dist/PluginManager.js +101 -0
- package/dist/RetryHandler.d.ts +8 -1
- package/dist/RetryHandler.js +28 -6
- package/dist/SMSClient.d.ts +64 -0
- package/dist/SMSClient.js +293 -0
- package/dist/SMSQueue.d.ts +112 -0
- package/dist/SMSQueue.js +290 -0
- package/dist/SMSScheduler.d.ts +75 -0
- package/dist/SMSScheduler.js +196 -0
- package/dist/SMSService.d.ts +41 -0
- package/dist/SMSService.js +214 -0
- package/dist/SendConfig.d.ts +90 -0
- package/dist/SendConfig.js +165 -0
- package/dist/errors.d.ts +21 -1
- package/dist/errors.js +33 -5
- package/dist/helpers.d.ts +3 -0
- package/dist/helpers.js +23 -0
- package/dist/main.d.ts +20 -1
- package/dist/main.js +19 -1
- package/dist/types.d.ts +50 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/HttpClient.ts +49 -5
- package/src/SMSClient.ts +84 -20
- package/src/SMSQueue.ts +178 -46
- package/src/errors.ts +6 -0
- package/src/main.ts +13 -4
- package/src/types.ts +19 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SendConfig — fluent builder for reusable send settings.
|
|
3
|
+
*
|
|
4
|
+
* Build once, reuse everywhere. Eliminates repeating template/variables
|
|
5
|
+
* across multiple send calls. Supports overrides per-call.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const otpConfig = new SendConfig()
|
|
9
|
+
* .template("Your verification code is {code}. Expires in {expiryMinutes} minutes.")
|
|
10
|
+
* .defaults({ expiryMinutes: 10 })
|
|
11
|
+
* .tag("otp");
|
|
12
|
+
*
|
|
13
|
+
* await client.send(otpConfig.for("+15551234567", { code: "482910" }));
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Object config with auto-extraction
|
|
17
|
+
* const orderConfig = new SendConfig()
|
|
18
|
+
* .template("Hi {customer.name}, your order {orderId} is {status}.")
|
|
19
|
+
* .tag("order-notification");
|
|
20
|
+
*
|
|
21
|
+
* await client.sendObject(orderConfig.forObject("+15551234567", order));
|
|
22
|
+
*/
|
|
23
|
+
export default class SendConfig {
|
|
24
|
+
constructor() {
|
|
25
|
+
this._defaults = {};
|
|
26
|
+
this._tags = [];
|
|
27
|
+
this._priority = "normal";
|
|
28
|
+
}
|
|
29
|
+
// ── Builder methods ──────────────────────────────────────────────────────
|
|
30
|
+
/** Set the message template. Supports {variable} and {nested.path} placeholders. */
|
|
31
|
+
template(tpl) {
|
|
32
|
+
this._template = tpl;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/** Default variable values merged under per-call variables. */
|
|
36
|
+
defaults(vars) {
|
|
37
|
+
this._defaults = { ...this._defaults, ...vars };
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
/** Add a single default variable. */
|
|
41
|
+
set(key, value) {
|
|
42
|
+
this._defaults[key] = value;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
/** Tag this config (useful for logging, filtering, hooks). */
|
|
46
|
+
tag(...tags) {
|
|
47
|
+
this._tags.push(...tags);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Time-to-live in milliseconds.
|
|
52
|
+
* Messages scheduled/queued past this age will be dropped.
|
|
53
|
+
*/
|
|
54
|
+
ttl(ms) {
|
|
55
|
+
this._ttl = ms;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/** Queue priority. High-priority jobs jump the queue. */
|
|
59
|
+
priority(p) {
|
|
60
|
+
this._priority = p;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deduplication key.
|
|
65
|
+
* If two messages with the same dedupKey are enqueued within the TTL window,
|
|
66
|
+
* the second is silently dropped.
|
|
67
|
+
*/
|
|
68
|
+
dedupKey(key) {
|
|
69
|
+
this._dedupKey = key;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
// ── Materialise ──────────────────────────────────────────────────────────
|
|
73
|
+
/** Build a SendParams for a single recipient. Variables override defaults. */
|
|
74
|
+
for(to, variables) {
|
|
75
|
+
if (!this._template) {
|
|
76
|
+
throw new Error("SendConfig: template is required. Call .template() first.");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
to,
|
|
80
|
+
template: this._template,
|
|
81
|
+
variables: { ...this._defaults, ...variables },
|
|
82
|
+
_meta: this.meta(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Build a SendObjectParams for a single recipient. */
|
|
86
|
+
forObject(to, object) {
|
|
87
|
+
return {
|
|
88
|
+
to,
|
|
89
|
+
object,
|
|
90
|
+
template: this._template,
|
|
91
|
+
_meta: this.meta(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build SendParams for many recipients from an array.
|
|
96
|
+
* Each recipient entry can supply per-recipient variables.
|
|
97
|
+
*/
|
|
98
|
+
forMany(recipients) {
|
|
99
|
+
return recipients.map((r) => this.for(r.to, r.variables));
|
|
100
|
+
}
|
|
101
|
+
/** Produce the metadata snapshot for queuing / logging. */
|
|
102
|
+
meta() {
|
|
103
|
+
return {
|
|
104
|
+
tags: [...this._tags],
|
|
105
|
+
ttl: this._ttl,
|
|
106
|
+
priority: this._priority,
|
|
107
|
+
dedupKey: this._dedupKey,
|
|
108
|
+
createdAt: Date.now(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// ── Presets ──────────────────────────────────────────────────────────────
|
|
112
|
+
/** Clone this config so you can branch without mutating the original. */
|
|
113
|
+
clone() {
|
|
114
|
+
const c = new SendConfig();
|
|
115
|
+
c._template = this._template;
|
|
116
|
+
c._defaults = { ...this._defaults };
|
|
117
|
+
c._tags = [...this._tags];
|
|
118
|
+
c._ttl = this._ttl;
|
|
119
|
+
c._priority = this._priority;
|
|
120
|
+
c._dedupKey = this._dedupKey;
|
|
121
|
+
return c;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Merge another config on top of this one (other wins on conflicts).
|
|
125
|
+
* Returns a new config — neither is mutated.
|
|
126
|
+
*/
|
|
127
|
+
merge(other) {
|
|
128
|
+
return this.clone()
|
|
129
|
+
.template(other._template ?? this._template ?? "")
|
|
130
|
+
.defaults({ ...this._defaults, ...other._defaults })
|
|
131
|
+
.tag(...other._tags)
|
|
132
|
+
.priority(other._priority);
|
|
133
|
+
}
|
|
134
|
+
// ── Static factories ─────────────────────────────────────────────────────
|
|
135
|
+
/** Create an OTP config. */
|
|
136
|
+
static otp(expiryMinutes = 10) {
|
|
137
|
+
return new SendConfig()
|
|
138
|
+
.template("Your verification code is {code}. Expires in {expiryMinutes} minutes.")
|
|
139
|
+
.defaults({ expiryMinutes })
|
|
140
|
+
.tag("otp")
|
|
141
|
+
.ttl(expiryMinutes * 60 * 1000)
|
|
142
|
+
.priority("high");
|
|
143
|
+
}
|
|
144
|
+
/** Create an order-notification config. */
|
|
145
|
+
static orderNotification() {
|
|
146
|
+
return new SendConfig()
|
|
147
|
+
.template("Hi {name}, your order #{orderId} is now {status}.")
|
|
148
|
+
.tag("order")
|
|
149
|
+
.priority("normal");
|
|
150
|
+
}
|
|
151
|
+
/** Create a reminder config. */
|
|
152
|
+
static reminder() {
|
|
153
|
+
return new SendConfig()
|
|
154
|
+
.template("Reminder: {message}")
|
|
155
|
+
.tag("reminder")
|
|
156
|
+
.priority("low");
|
|
157
|
+
}
|
|
158
|
+
/** Create a marketing/promo config. */
|
|
159
|
+
static promo() {
|
|
160
|
+
return new SendConfig()
|
|
161
|
+
.template("{promoMessage}")
|
|
162
|
+
.tag("promo", "marketing")
|
|
163
|
+
.priority("low");
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
declare class SMSClientError extends Error {
|
|
2
2
|
code: string;
|
|
3
3
|
details?: unknown;
|
|
4
|
-
|
|
4
|
+
isClientError: boolean;
|
|
5
|
+
isServerError: boolean;
|
|
6
|
+
constructor(message: string, code: string, details?: unknown, isClientError?: boolean, isServerError?: boolean);
|
|
7
|
+
toJSON(): {
|
|
8
|
+
name: string;
|
|
9
|
+
message: string;
|
|
10
|
+
code: string;
|
|
11
|
+
details: unknown;
|
|
12
|
+
isClientError: boolean;
|
|
13
|
+
isServerError: boolean;
|
|
14
|
+
};
|
|
5
15
|
}
|
|
6
16
|
export declare class ServerError extends SMSClientError {
|
|
7
17
|
constructor(message: string, details?: unknown);
|
|
@@ -15,4 +25,14 @@ export declare class TemplateError extends SMSClientError {
|
|
|
15
25
|
export declare class NetworkError extends SMSClientError {
|
|
16
26
|
constructor(message: string, details?: unknown);
|
|
17
27
|
}
|
|
28
|
+
export declare class RateLimitError extends SMSClientError {
|
|
29
|
+
retryAfter?: number;
|
|
30
|
+
constructor(message: string, retryAfter?: number, details?: unknown);
|
|
31
|
+
}
|
|
32
|
+
export declare class CircuitOpenError extends SMSClientError {
|
|
33
|
+
constructor(message: string, details?: unknown);
|
|
34
|
+
}
|
|
35
|
+
export declare class MaxLimitError extends SMSClientError {
|
|
36
|
+
constructor(message: string, details?: unknown);
|
|
37
|
+
}
|
|
18
38
|
export {};
|
package/dist/errors.js
CHANGED
|
@@ -1,29 +1,57 @@
|
|
|
1
1
|
class SMSClientError extends Error {
|
|
2
|
-
constructor(message, code, details) {
|
|
2
|
+
constructor(message, code, details, isClientError = false, isServerError = false) {
|
|
3
3
|
super(message);
|
|
4
4
|
this.name = this.constructor.name;
|
|
5
5
|
this.code = code;
|
|
6
6
|
this.details = details;
|
|
7
|
+
this.isClientError = isClientError;
|
|
8
|
+
this.isServerError = isServerError;
|
|
7
9
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
8
10
|
}
|
|
11
|
+
toJSON() {
|
|
12
|
+
return {
|
|
13
|
+
name: this.name,
|
|
14
|
+
message: this.message,
|
|
15
|
+
code: this.code,
|
|
16
|
+
details: this.details,
|
|
17
|
+
isClientError: this.isClientError,
|
|
18
|
+
isServerError: this.isServerError,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
9
21
|
}
|
|
10
22
|
export class ServerError extends SMSClientError {
|
|
11
23
|
constructor(message, details) {
|
|
12
|
-
super(message, "SERVER_ERROR", details);
|
|
24
|
+
super(message, "SERVER_ERROR", details, false, true);
|
|
13
25
|
}
|
|
14
26
|
}
|
|
15
27
|
export class ValidationError extends SMSClientError {
|
|
16
28
|
constructor(message, details) {
|
|
17
|
-
super(message, "VALIDATION_ERROR", details);
|
|
29
|
+
super(message, "VALIDATION_ERROR", details, true, false);
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
export class TemplateError extends SMSClientError {
|
|
21
33
|
constructor(message, details) {
|
|
22
|
-
super(message, "TEMPLATE_ERROR", details);
|
|
34
|
+
super(message, "TEMPLATE_ERROR", details, true, false);
|
|
23
35
|
}
|
|
24
36
|
}
|
|
25
37
|
export class NetworkError extends SMSClientError {
|
|
26
38
|
constructor(message, details) {
|
|
27
|
-
super(message, "NETWORK_ERROR", details);
|
|
39
|
+
super(message, "NETWORK_ERROR", details, false, false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class RateLimitError extends SMSClientError {
|
|
43
|
+
constructor(message, retryAfter, details) {
|
|
44
|
+
super(message, "RATE_LIMIT_ERROR", details, false, true);
|
|
45
|
+
this.retryAfter = retryAfter;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class CircuitOpenError extends SMSClientError {
|
|
49
|
+
constructor(message, details) {
|
|
50
|
+
super(message, "CIRCUIT_OPEN", details, false, false);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class MaxLimitError extends SMSClientError {
|
|
54
|
+
constructor(message, details) {
|
|
55
|
+
super(message, "MAX_LIMIT_ERROR", details, false, true);
|
|
28
56
|
}
|
|
29
57
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function isObject(value: unknown): value is Record<string, unknown>;
|
|
2
|
+
export declare function flattenObject(obj: Record<string, unknown>, prefix?: string, separator?: string): Record<string, string>;
|
|
3
|
+
export declare function chunkArray<T>(array: T[], size: number): T[][];
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function isObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function flattenObject(obj, prefix = "", separator = ".") {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
7
|
+
const fullKey = prefix ? `${prefix}${separator}${key}` : key;
|
|
8
|
+
if (isObject(value)) {
|
|
9
|
+
Object.assign(result, flattenObject(value, fullKey, separator));
|
|
10
|
+
}
|
|
11
|
+
else if (value !== null && value !== undefined) {
|
|
12
|
+
result[fullKey] = String(value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
export function chunkArray(array, size) {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for (let i = 0; i < array.length; i += size) {
|
|
20
|
+
chunks.push(array.slice(i, i + size));
|
|
21
|
+
}
|
|
22
|
+
return chunks;
|
|
23
|
+
}
|
package/dist/main.d.ts
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
export { SMSClient } from "./
|
|
1
|
+
export { default as SMSClient } from "./SMSClient.js";
|
|
2
|
+
export type { SMSClientOptions } from "./SMSClient.js";
|
|
3
|
+
export { default as SendConfig } from "./SendConfig.js";
|
|
4
|
+
export type { SendMeta } from "./SendConfig.js";
|
|
5
|
+
export { default as SMSService } from "./SMSService.js";
|
|
6
|
+
export type { SMSServiceOptions } from "./SMSService.js";
|
|
7
|
+
export { default as SMSQueue } from "./SMSQueue.js";
|
|
8
|
+
export type { QueueOptions, QueueStats, QueueJob, FailedJob, AttemptRecord, } from "./SMSQueue.js";
|
|
9
|
+
export { default as SMSScheduler } from "./SMSScheduler.js";
|
|
10
|
+
export type { ScheduledJob } from "./SMSScheduler.js";
|
|
11
|
+
export { default as PluginManager, LoggerPlugin, MetricsPlugin, } from "./PluginManager.js";
|
|
12
|
+
export type { Plugin, PluginHooks, MetricsSnapshot } from "./PluginManager.js";
|
|
13
|
+
export { default as CredentialProvisioner, type ProvisionOptions, type Credentials, } from "./CredentialProvisioner.js";
|
|
14
|
+
export { default as CircuitBreaker } from "./CircuitBreaker.js";
|
|
15
|
+
export type { CircuitBreakerOptions } from "./CircuitBreaker.js";
|
|
16
|
+
export { default as RetryHandler } from "./RetryHandler.js";
|
|
17
|
+
export type { RetryOptions } from "./RetryHandler.js";
|
|
18
|
+
export { default as MessageFormatter } from "./MessageFormatter.js";
|
|
19
|
+
export { ServerError, ValidationError, TemplateError, NetworkError, RateLimitError, CircuitOpenError, MaxLimitError, } from "./errors.js";
|
|
20
|
+
export type { Result, ErrorLog, SendParams, SendObjectParams, BatchResult, FailedItem, SucceededItem, } from "./types.js";
|
package/dist/main.js
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
// Core client
|
|
2
|
+
export { default as SMSClient } from "./SMSClient.js";
|
|
3
|
+
// Config builder
|
|
4
|
+
export { default as SendConfig } from "./SendConfig.js";
|
|
5
|
+
// Microservice
|
|
6
|
+
export { default as SMSService } from "./SMSService.js";
|
|
7
|
+
// Queue & Scheduler
|
|
8
|
+
export { default as SMSQueue } from "./SMSQueue.js";
|
|
9
|
+
export { default as SMSScheduler } from "./SMSScheduler.js";
|
|
10
|
+
// Plugins
|
|
11
|
+
export { default as PluginManager, LoggerPlugin, MetricsPlugin, } from "./PluginManager.js";
|
|
12
|
+
// Credential provisioning
|
|
13
|
+
export { default as CredentialProvisioner, } from "./CredentialProvisioner.js";
|
|
14
|
+
// Lower-level building blocks (advanced use)
|
|
15
|
+
export { default as CircuitBreaker } from "./CircuitBreaker.js";
|
|
16
|
+
export { default as RetryHandler } from "./RetryHandler.js";
|
|
17
|
+
export { default as MessageFormatter } from "./MessageFormatter.js";
|
|
18
|
+
//errors
|
|
19
|
+
export { ServerError, ValidationError, TemplateError, NetworkError, RateLimitError, CircuitOpenError, MaxLimitError, } from "./errors.js";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type Result<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
data: T;
|
|
4
|
+
error: null;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
data: null;
|
|
8
|
+
error: ErrorLog;
|
|
9
|
+
};
|
|
10
|
+
export interface ErrorLog {
|
|
11
|
+
name: string;
|
|
12
|
+
message: string;
|
|
13
|
+
code: string;
|
|
14
|
+
isClientError: boolean;
|
|
15
|
+
isServerError: boolean;
|
|
16
|
+
details?: unknown;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SendParams {
|
|
20
|
+
to: string;
|
|
21
|
+
template: string;
|
|
22
|
+
variables?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface SendObjectParams<T extends Record<string, unknown>> {
|
|
25
|
+
to: string;
|
|
26
|
+
object: T;
|
|
27
|
+
/** Optional template; if omitted the object's message/body/text field is used */
|
|
28
|
+
template?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface SucceededItem<T> {
|
|
31
|
+
index: number;
|
|
32
|
+
to: string;
|
|
33
|
+
data: T;
|
|
34
|
+
}
|
|
35
|
+
export interface FailedItem {
|
|
36
|
+
index: number;
|
|
37
|
+
to: string;
|
|
38
|
+
error: ErrorLog;
|
|
39
|
+
/** The original params — preserved so you can re-enqueue or log without data loss */
|
|
40
|
+
originalParams: SendParams;
|
|
41
|
+
}
|
|
42
|
+
export interface BatchResult<T> {
|
|
43
|
+
succeeded: SucceededItem<T>[];
|
|
44
|
+
failed: FailedItem[];
|
|
45
|
+
total: number;
|
|
46
|
+
successCount: number;
|
|
47
|
+
failureCount: number;
|
|
48
|
+
/** True if the server hit a quota/max-limit and stopped sending */
|
|
49
|
+
hitMaxLimit: boolean;
|
|
50
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
package/src/HttpClient.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
NetworkError,
|
|
3
|
+
ServerError,
|
|
4
|
+
RateLimitError,
|
|
5
|
+
MaxLimitError,
|
|
6
|
+
} from "./errors.js";
|
|
2
7
|
|
|
3
8
|
interface HttpClientOptions {
|
|
4
9
|
baseUrl: string;
|
|
@@ -7,6 +12,19 @@ interface HttpClientOptions {
|
|
|
7
12
|
timeout?: number;
|
|
8
13
|
}
|
|
9
14
|
|
|
15
|
+
// Patterns that indicate a "max messages sent" quota error from the server
|
|
16
|
+
const MAX_LIMIT_PATTERNS = [
|
|
17
|
+
/max.{0,20}(messages?|sms|send)/i,
|
|
18
|
+
/quota.{0,20}(exceeded|reached|exhausted)/i,
|
|
19
|
+
/limit.{0,20}reached/i,
|
|
20
|
+
/daily.{0,20}(limit|cap)/i,
|
|
21
|
+
/monthly.{0,20}(limit|cap)/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function isMaxLimitBody(text: string): boolean {
|
|
25
|
+
return MAX_LIMIT_PATTERNS.some((p) => p.test(text));
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
export default class HttpClient {
|
|
11
29
|
constructor(private options: HttpClientOptions) {}
|
|
12
30
|
|
|
@@ -30,21 +48,47 @@ export default class HttpClient {
|
|
|
30
48
|
|
|
31
49
|
clearTimeout(timeoutId);
|
|
32
50
|
|
|
51
|
+
if (response.status === 429) {
|
|
52
|
+
const retryAfter =
|
|
53
|
+
Number(response.headers.get("Retry-After")) || undefined;
|
|
54
|
+
throw new RateLimitError("Rate limit exceeded", retryAfter);
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
if (!response.ok) {
|
|
34
58
|
const text = await response.text();
|
|
35
|
-
|
|
59
|
+
|
|
60
|
+
// Detect "max messages sent reached" specifically — not retryable
|
|
61
|
+
if (
|
|
62
|
+
response.status === 402 ||
|
|
63
|
+
response.status === 403 ||
|
|
64
|
+
isMaxLimitBody(text)
|
|
65
|
+
) {
|
|
66
|
+
throw new MaxLimitError(
|
|
67
|
+
`Message quota exceeded (HTTP ${response.status})`,
|
|
68
|
+
{ status: response.status, body: text },
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new ServerError(`Server responded with ${response.status}`, {
|
|
73
|
+
status: response.status,
|
|
74
|
+
body: text,
|
|
75
|
+
});
|
|
36
76
|
}
|
|
37
77
|
|
|
38
78
|
return await response.json();
|
|
39
79
|
} catch (error: any) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
|
|
40
82
|
if (error.name === "AbortError") {
|
|
41
83
|
throw new NetworkError("Request timed out");
|
|
42
84
|
}
|
|
43
|
-
|
|
44
|
-
|
|
85
|
+
if (
|
|
86
|
+
error instanceof ServerError ||
|
|
87
|
+
error instanceof RateLimitError ||
|
|
88
|
+
error instanceof MaxLimitError
|
|
89
|
+
) {
|
|
45
90
|
throw error;
|
|
46
91
|
}
|
|
47
|
-
|
|
48
92
|
throw new NetworkError("Network request failed", error);
|
|
49
93
|
}
|
|
50
94
|
}
|