@reachflow/sdk 0.1.0 → 0.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @reachflow/sdk
2
2
 
3
- Client officiel **Node.js / TypeScript** pour l’[API publique ReachFlow](https://docs.reachflow.me/developpeurs) (REST v1).
3
+ Official **Node.js / TypeScript** client for the [ReachFlow public API](https://docs.reachflow.me/developpeurs) (REST v1).
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,7 +8,7 @@ Client officiel **Node.js / TypeScript** pour l’[API publique ReachFlow](https
8
8
  npm install @reachflow/sdk
9
9
  ```
10
10
 
11
- **Prérequis :** Node.js ≥ 18 (utilise `fetch` natif).
11
+ **Requirements:** Node.js ≥ 18 (native `fetch`).
12
12
 
13
13
  ## Configuration
14
14
 
@@ -16,22 +16,27 @@ npm install @reachflow/sdk
16
16
  import { ReachFlow } from '@reachflow/sdk';
17
17
 
18
18
  const client = new ReachFlow({
19
- apiKey: process.env.REACHFLOW_API_KEY!, // rfl_live_… ou rfl_test_…
20
- baseUrl: 'https://sandbox-api.reachflow.me', // optionnel
21
- timeoutMs: 30_000, // optionnel
22
- maxRetries: 2, // optionnel — 429 / 5xx
19
+ apiKey: process.env.REACHFLOW_API_KEY!, // rfl_live_… or rfl_test_…
20
+ environment: 'sandbox', // or 'live' — default: 'sandbox'
21
+ timeoutMs: 30_000, // optional
22
+ maxRetries: 2, // optional — 429 / 5xx
23
23
  });
24
24
  ```
25
25
 
26
- ## Exemples
26
+ | `environment` | API host |
27
+ |---------------|----------|
28
+ | `sandbox` *(default)* | `sandbox-api.reachflow.me` |
29
+ | `live` | `api.reachflow.me` |
27
30
 
28
- ### Envoyer un message
31
+ ## Examples
32
+
33
+ ### Send a message
29
34
 
30
35
  ```typescript
31
36
  const { messageId } = await client.messages.send({
32
- providerId: 'uuid-du-provider',
37
+ providerId: 'your-provider-uuid',
33
38
  to: '22996123456',
34
- message: 'Votre commande est confirmée.',
39
+ message: 'Your order is confirmed.',
35
40
  });
36
41
 
37
42
  const status = await client.messages.waitForTerminal(messageId);
@@ -42,12 +47,12 @@ console.log(status.status); // sent | delivered | failed | …
42
47
 
43
48
  ```typescript
44
49
  const { otpId } = await client.otp.send({
45
- providerId: 'uuid-du-provider',
50
+ providerId: 'your-provider-uuid',
46
51
  phoneNumber: '22996123456',
47
- brandName: 'Mon App',
52
+ brandName: 'My App',
48
53
  });
49
54
 
50
- // Le code arrive sur WhatsApp — jamais dans la réponse JSON.
55
+ // The code is delivered on WhatsApp only never in the JSON response.
51
56
  const result = await client.otp.verify({ otpId, code: '482910' });
52
57
  console.log(result.valid);
53
58
  ```
@@ -59,7 +64,7 @@ const { providers } = await client.providers.list();
59
64
  const connected = await client.providers.findConnected();
60
65
  ```
61
66
 
62
- ## Gestion des erreurs
67
+ ## Error handling
63
68
 
64
69
  ```typescript
65
70
  import { ReachFlow, ReachFlowError } from '@reachflow/sdk';
@@ -70,24 +75,24 @@ try {
70
75
  if (err instanceof ReachFlowError) {
71
76
  console.error(err.statusCode, err.code, err.message);
72
77
  if (err.retryable) {
73
- // retry manuel possible
78
+ // manual retry if needed
74
79
  }
75
80
  }
76
81
  }
77
82
  ```
78
83
 
79
- | Code | Signification |
80
- |------|---------------|
81
- | `unauthorized` | Clé API invalide ou révoquée |
82
- | `plan_required` | Plan ou add-on API manquant |
83
- | `insufficient_scope` | Scope clé insuffisant |
84
- | `rate_limit_exceeded` | Limite req/min dépassée |
85
- | `validation_error` | Corps de requête invalide |
86
- | `not_found` | Ressource introuvable |
84
+ | Code | Meaning |
85
+ |------|---------|
86
+ | `unauthorized` | Invalid or revoked API key |
87
+ | `plan_required` | Plan or API add-on missing |
88
+ | `insufficient_scope` | API key scope insufficient |
89
+ | `rate_limit_exceeded` | HTTP rate limit exceeded |
90
+ | `validation_error` | Invalid request body |
91
+ | `not_found` | Resource not found |
87
92
 
88
- ## Idempotence
93
+ ## Idempotency
89
94
 
90
- Passez `idempotencyKey` sur `messages.send`, `sendMedia` ou `sendBulk` :
95
+ Pass `idempotencyKey` on `messages.send`, `sendMedia`, or `sendBulk`:
91
96
 
92
97
  ```typescript
93
98
  await client.messages.send({
@@ -96,7 +101,7 @@ await client.messages.send({
96
101
  });
97
102
  ```
98
103
 
99
- ## Développement
104
+ ## Development
100
105
 
101
106
  ```bash
102
107
  npm install
@@ -104,6 +109,6 @@ npm test
104
109
  npm run build
105
110
  ```
106
111
 
107
- ## Licence
112
+ ## License
108
113
 
109
114
  MIT
package/dist/index.cjs CHANGED
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
24
+ DEFAULT_ENVIRONMENT: () => DEFAULT_ENVIRONMENT,
25
+ REACHFLOW_ENVIRONMENTS: () => REACHFLOW_ENVIRONMENTS,
24
26
  ReachFlow: () => ReachFlow,
25
27
  ReachFlowError: () => ReachFlowError,
26
28
  TERMINAL_MESSAGE_STATUSES: () => TERMINAL_MESSAGE_STATUSES
@@ -107,19 +109,26 @@ function parseRetryAfterMs(header) {
107
109
  }
108
110
 
109
111
  // src/types.ts
112
+ var REACHFLOW_ENVIRONMENTS = {
113
+ sandbox: "https://sandbox-api.reachflow.me",
114
+ live: "https://api.reachflow.me"
115
+ };
116
+ var DEFAULT_ENVIRONMENT = "sandbox";
110
117
  var TERMINAL_MESSAGE_STATUSES = /* @__PURE__ */ new Set([
111
118
  "sent",
112
119
  "delivered",
113
120
  "failed",
114
121
  "cancelled"
115
122
  ]);
116
- var DEFAULT_BASE_URL = "https://sandbox-api.reachflow.me";
123
+ var DEFAULT_BASE_URL = REACHFLOW_ENVIRONMENTS.sandbox;
117
124
 
118
125
  // src/http.ts
119
126
  function resolveConfig(options) {
120
- const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
127
+ const environment = options.environment ?? DEFAULT_ENVIRONMENT;
128
+ const baseUrl = REACHFLOW_ENVIRONMENTS[environment].replace(/\/$/, "");
121
129
  return {
122
130
  apiKey: options.apiKey.trim(),
131
+ environment,
123
132
  baseUrl,
124
133
  apiPrefix: `${baseUrl}/api/v1`,
125
134
  timeoutMs: options.timeoutMs ?? 3e4,
@@ -228,7 +237,7 @@ var MessagesResource = class {
228
237
  this.http = http;
229
238
  }
230
239
  http;
231
- /** Envoie un message texte (réponse HTTP 202). */
240
+ /** Send a text message (HTTP 202). */
232
241
  async send(params) {
233
242
  const { idempotencyKey, ...body } = params;
234
243
  return this.http.request({
@@ -238,7 +247,7 @@ var MessagesResource = class {
238
247
  idempotencyKey
239
248
  });
240
249
  }
241
- /** Envoie un média via URL HTTPS publique (réponse HTTP 202). */
250
+ /** Send media via a public HTTPS URL (HTTP 202). */
242
251
  async sendMedia(params) {
243
252
  const { idempotencyKey, ...body } = params;
244
253
  return this.http.request({
@@ -248,7 +257,7 @@ var MessagesResource = class {
248
257
  idempotencyKey
249
258
  });
250
259
  }
251
- /** Envoie un lot de messages (même modèle, destinataires multiples). */
260
+ /** Send a bulk batch (same template, multiple recipients). */
252
261
  async sendBulk(params) {
253
262
  const { idempotencyKey, ...body } = params;
254
263
  return this.http.request({
@@ -258,7 +267,7 @@ var MessagesResource = class {
258
267
  idempotencyKey
259
268
  });
260
269
  }
261
- /** Consulte le statut d'un message précédemment accepté. */
270
+ /** Get the status of a previously accepted message. */
262
271
  async getStatus(messageId) {
263
272
  return this.http.request({
264
273
  method: "GET",
@@ -266,8 +275,8 @@ var MessagesResource = class {
266
275
  });
267
276
  }
268
277
  /**
269
- * Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).
270
- * Lance `ReachFlowError` si timeout.
278
+ * Poll until a terminal status (`sent`, `delivered`, `failed`, `cancelled`).
279
+ * Throws `ReachFlowError` on timeout.
271
280
  */
272
281
  async waitForTerminal(messageId, options) {
273
282
  const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
@@ -299,8 +308,8 @@ var OtpResource = class {
299
308
  }
300
309
  http;
301
310
  /**
302
- * Génère et envoie un code OTP par WhatsApp.
303
- * Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.
311
+ * Generate and send an OTP via WhatsApp.
312
+ * The code is never returned by the API — only delivered on WhatsApp.
304
313
  */
305
314
  async send(params) {
306
315
  return this.http.request({
@@ -309,7 +318,7 @@ var OtpResource = class {
309
318
  body: params
310
319
  });
311
320
  }
312
- /** Vérifie un code saisi par l'utilisateur final. */
321
+ /** Verify a code entered by the end user. */
313
322
  async verify(params) {
314
323
  return this.http.request({
315
324
  method: "POST",
@@ -325,14 +334,14 @@ var ProvidersResource = class {
325
334
  this.http = http;
326
335
  }
327
336
  http;
328
- /** Liste les numéros WhatsApp accessibles via l'API. */
337
+ /** List WhatsApp numbers available via the API. */
329
338
  async list() {
330
339
  return this.http.request({
331
340
  method: "GET",
332
341
  path: "/providers"
333
342
  });
334
343
  }
335
- /** Détail d'un provider avec statistiques journalières. */
344
+ /** Get a provider with daily statistics. */
336
345
  async get(providerId) {
337
346
  return this.http.request({
338
347
  method: "GET",
@@ -340,8 +349,8 @@ var ProvidersResource = class {
340
349
  });
341
350
  }
342
351
  /**
343
- * Retourne le premier provider au statut `connected`, ou le premier de la liste.
344
- * Utile pour les scripts de test / démarrage rapide.
352
+ * Return the first `connected` provider, or the first in the list.
353
+ * Handy for smoke tests and quick starts.
345
354
  */
346
355
  async findConnected() {
347
356
  const { providers } = await this.list();
@@ -367,7 +376,11 @@ var ReachFlow = class {
367
376
  this.providers = new ProvidersResource(this.http);
368
377
  this.otp = new OtpResource(this.http);
369
378
  }
370
- /** URL de base configurée (sans `/api/v1`). */
379
+ /** Configured environment (`sandbox` or `live`). */
380
+ get environment() {
381
+ return this.config.environment;
382
+ }
383
+ /** Resolved API host (without `/api/v1`). */
371
384
  get baseUrl() {
372
385
  return this.config.baseUrl;
373
386
  }
@@ -375,6 +388,8 @@ var ReachFlow = class {
375
388
  // Annotate the CommonJS export names for ESM import in node:
376
389
  0 && (module.exports = {
377
390
  DEFAULT_BASE_URL,
391
+ DEFAULT_ENVIRONMENT,
392
+ REACHFLOW_ENVIRONMENTS,
378
393
  ReachFlow,
379
394
  ReachFlowError,
380
395
  TERMINAL_MESSAGE_STATUSES
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/types.ts","../src/http.ts","../src/resources/messages.ts","../src/resources/otp.ts","../src/resources/providers.ts","../src/client.ts"],"sourcesContent":["export { ReachFlow } from './client.js';\nexport { ReachFlowError } from './errors.js';\nexport type { ReachFlowErrorCode } from './errors.js';\nexport type {\n BulkRecipient,\n FailureCode,\n MediaType,\n MessageStatus,\n MessageStatusResponse,\n OtpSendParams,\n OtpSendResponse,\n OtpVerifyParams,\n OtpVerifyResponse,\n OtpVerifyReason,\n ProviderDailyStats,\n ProviderDetail,\n ProviderListResponse,\n ProviderSummary,\n ReachFlowClientOptions,\n SendAcceptedResponse,\n SendBulkParams,\n SendBulkResponse,\n SendMediaParams,\n SendMessageParams,\n WaitForTerminalOptions,\n} from './types.js';\nexport {\n DEFAULT_BASE_URL,\n TERMINAL_MESSAGE_STATUSES,\n} from './types.js';\n","export type ReachFlowErrorCode =\n | 'unauthorized'\n | 'plan_required'\n | 'insufficient_scope'\n | 'rate_limit_exceeded'\n | 'too_many_auth_failures'\n | 'validation_error'\n | 'not_found'\n | 'api_error'\n | 'network_error'\n | 'timeout';\n\nexport interface ReachFlowErrorBody {\n statusCode?: number;\n error?: string;\n message?: string;\n}\n\nexport class ReachFlowError extends Error {\n readonly statusCode: number;\n readonly code: ReachFlowErrorCode;\n readonly retryable: boolean;\n readonly retryAfterMs?: number;\n readonly body?: unknown;\n\n constructor(params: {\n message: string;\n statusCode: number;\n code: ReachFlowErrorCode;\n retryable?: boolean;\n retryAfterMs?: number;\n body?: unknown;\n cause?: unknown;\n }) {\n super(params.message, params.cause ? { cause: params.cause } : undefined);\n this.name = 'ReachFlowError';\n this.statusCode = params.statusCode;\n this.code = params.code;\n this.retryable = params.retryable ?? false;\n this.retryAfterMs = params.retryAfterMs;\n this.body = params.body;\n }\n\n static fromResponse(\n status: number,\n body: unknown,\n fallbackMessage?: string,\n ): ReachFlowError {\n const parsed = (body ?? {}) as ReachFlowErrorBody;\n const apiError = parsed.error;\n const message =\n parsed.message ??\n fallbackMessage ??\n `ReachFlow API error (HTTP ${status})`;\n\n const code = mapApiErrorToCode(status, apiError);\n const retryable =\n status === 429 || status === 408 || (status >= 500 && status < 600);\n\n return new ReachFlowError({\n message,\n statusCode: status,\n code,\n retryable,\n body,\n });\n }\n\n static network(cause: unknown): ReachFlowError {\n return new ReachFlowError({\n message: 'Network error while calling ReachFlow API',\n statusCode: 0,\n code: 'network_error',\n retryable: true,\n cause,\n });\n }\n\n static timeout(timeoutMs: number): ReachFlowError {\n return new ReachFlowError({\n message: `Request timed out after ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: true,\n });\n }\n}\n\nfunction mapApiErrorToCode(\n status: number,\n apiError?: string,\n): ReachFlowErrorCode {\n switch (apiError) {\n case 'unauthorized':\n return 'unauthorized';\n case 'plan_required':\n return 'plan_required';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'rate_limit_exceeded':\n return 'rate_limit_exceeded';\n case 'too_many_auth_failures':\n return 'too_many_auth_failures';\n default:\n break;\n }\n\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'insufficient_scope';\n if (status === 404) return 'not_found';\n if (status === 400 || status === 422) return 'validation_error';\n if (status === 429) return 'rate_limit_exceeded';\n return 'api_error';\n}\n\nexport function parseRetryAfterMs(header: string | null): number | undefined {\n if (!header) return undefined;\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(header);\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n","/** Statut de cycle de vie d'un message API. */\nexport type MessageStatus =\n | 'queued'\n | 'processing'\n | 'sent'\n | 'delivered'\n | 'failed'\n | 'cancelled';\n\nexport type MediaType = 'image' | 'document' | 'audio' | 'video';\n\nexport type FailureCode =\n | 'warmup_daily_limit'\n | 'risk_circuit_open'\n | 'provider_disconnected'\n | 'provider_banned'\n | 'send_not_allowed'\n | 'instance_busy'\n | 'delivery_timeout'\n | 'delivery_failed'\n | 'send_error'\n | 'provider_not_found';\n\nexport type OtpVerifyReason =\n | 'invalidated'\n | 'already_used'\n | 'expired'\n | 'max_attempts_reached'\n | 'invalid_code'\n | 'not_found';\n\nexport interface ReachFlowClientOptions {\n /** Clé API `rfl_live_…` ou `rfl_test_…`. */\n apiKey: string;\n /**\n * URL de base sans `/api/v1` (défaut : sandbox ReachFlow).\n * @default \"https://sandbox-api.reachflow.me\"\n */\n baseUrl?: string;\n /** Timeout HTTP en millisecondes. @default 30000 */\n timeoutMs?: number;\n /** Implémentation fetch (tests, Node < 18). @default global fetch */\n fetch?: typeof fetch;\n /** Nombre max de retries sur 429 / 5xx retryables. @default 2 */\n maxRetries?: number;\n}\n\nexport interface SendMessageParams {\n providerId: string;\n to: string;\n message: string;\n variables?: Record<string, string>;\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendMediaParams {\n providerId: string;\n to: string;\n mediaUrl: string;\n mediaType: MediaType;\n caption?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface BulkRecipient {\n to: string;\n variables?: Record<string, string>;\n}\n\nexport interface SendBulkParams {\n providerId: string;\n messageTemplate: string;\n recipients: BulkRecipient[];\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendAcceptedResponse {\n messageId: string;\n status: 'queued';\n queuedAt: string;\n}\n\nexport interface BulkRejection {\n to: string;\n reason: string;\n}\n\nexport interface SendBulkResponse {\n bulkId: string;\n accepted: number;\n rejected: number;\n rejections: BulkRejection[];\n messageIds: string[];\n}\n\nexport interface MessageStatusResponse {\n messageId: string;\n status: MessageStatus;\n to: string;\n providerId: string;\n queuedAt: string;\n sentAt: string | null;\n deliveredAt: string | null;\n failedAt: string | null;\n failureCode: FailureCode | null;\n failureReason: string | null;\n}\n\nexport interface ProviderSummary {\n id: string;\n name: string;\n phoneNumber: string | null;\n status: string;\n warmupDay: number;\n riskScore: number;\n}\n\nexport interface ProviderDailyStats {\n sent: number;\n delivered: number;\n failed: number;\n messagesLimit: number | null;\n newContactsLimit: number | null;\n}\n\nexport interface ProviderDetail extends ProviderSummary {\n dailyStats: ProviderDailyStats;\n}\n\nexport interface ProviderListResponse {\n providers: ProviderSummary[];\n}\n\nexport interface OtpSendParams {\n providerId: string;\n phoneNumber: string;\n codeLength?: number;\n expiresIn?: number;\n brandName?: string;\n template?: string;\n saveContact?: boolean;\n}\n\nexport interface OtpSendResponse {\n otpId: string;\n messageId: string;\n expiresAt: string;\n}\n\nexport interface OtpVerifyParams {\n otpId: string;\n code: string;\n}\n\nexport interface OtpVerifyResponse {\n valid: boolean;\n reason?: OtpVerifyReason;\n attemptsLeft?: number;\n}\n\nexport interface WaitForTerminalOptions {\n /** Intervalle entre polls en ms. @default 2000 */\n pollIntervalMs?: number;\n /** Timeout total en ms. @default 120000 */\n timeoutMs?: number;\n}\n\nexport const TERMINAL_MESSAGE_STATUSES: ReadonlySet<MessageStatus> = new Set([\n 'sent',\n 'delivered',\n 'failed',\n 'cancelled',\n]);\n\nexport const DEFAULT_BASE_URL = 'https://sandbox-api.reachflow.me';\n","import { ReachFlowError, parseRetryAfterMs } from './errors.js';\nimport type { ReachFlowClientOptions } from './types.js';\nimport { DEFAULT_BASE_URL } from './types.js';\n\nexport interface RequestOptions {\n method: 'GET' | 'POST';\n path: string;\n body?: unknown;\n idempotencyKey?: string;\n}\n\nexport interface ResolvedClientConfig {\n apiKey: string;\n baseUrl: string;\n apiPrefix: string;\n timeoutMs: number;\n fetchImpl: typeof fetch;\n maxRetries: number;\n}\n\nexport function resolveConfig(\n options: ReachFlowClientOptions,\n): ResolvedClientConfig {\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n return {\n apiKey: options.apiKey.trim(),\n baseUrl,\n apiPrefix: `${baseUrl}/api/v1`,\n timeoutMs: options.timeoutMs ?? 30_000,\n fetchImpl: options.fetch ?? globalThis.fetch,\n maxRetries: options.maxRetries ?? 2,\n };\n}\n\nexport class HttpClient {\n constructor(private readonly config: ResolvedClientConfig) {}\n\n async request<T>(options: RequestOptions): Promise<T> {\n let attempt = 0;\n let lastError: ReachFlowError | undefined;\n\n while (attempt <= this.config.maxRetries) {\n try {\n return await this.requestOnce<T>(options);\n } catch (err) {\n if (!(err instanceof ReachFlowError)) throw err;\n lastError = err;\n if (!err.retryable || attempt >= this.config.maxRetries) throw err;\n const delayMs = err.retryAfterMs ?? backoffMs(attempt);\n await sleep(delayMs);\n attempt += 1;\n }\n }\n\n throw lastError ?? new ReachFlowError({\n message: 'Request failed',\n statusCode: 0,\n code: 'api_error',\n });\n }\n\n private async requestOnce<T>(options: RequestOptions): Promise<T> {\n const url = `${this.config.apiPrefix}${options.path}`;\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-API-Key': this.config.apiKey,\n };\n\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const response = await this.config.fetchImpl(url, {\n method: options.method,\n headers,\n body:\n options.body !== undefined\n ? JSON.stringify(options.body)\n : undefined,\n signal: controller.signal,\n });\n\n const text = await response.text();\n let data: unknown = null;\n if (text) {\n try {\n data = JSON.parse(text) as unknown;\n } catch {\n data = { raw: text.slice(0, 500) };\n }\n }\n\n if (!response.ok) {\n const err = ReachFlowError.fromResponse(\n response.status,\n data,\n );\n const retryAfterMs = parseRetryAfterMs(\n response.headers.get('Retry-After'),\n );\n if (retryAfterMs !== undefined) {\n throw new ReachFlowError({\n message: err.message,\n statusCode: err.statusCode,\n code: err.code,\n retryable: err.retryable,\n retryAfterMs,\n body: err.body,\n });\n }\n throw err;\n }\n\n return data as T;\n } catch (err) {\n if (err instanceof ReachFlowError) throw err;\n if (err instanceof Error && err.name === 'AbortError') {\n throw ReachFlowError.timeout(this.config.timeoutMs);\n }\n throw ReachFlowError.network(err);\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(1000 * 2 ** attempt, 10_000);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport { ReachFlowError } from '../errors.js';\nimport type {\n MessageStatusResponse,\n SendAcceptedResponse,\n SendBulkParams,\n SendBulkResponse,\n SendMediaParams,\n SendMessageParams,\n WaitForTerminalOptions,\n} from '../types.js';\nimport { TERMINAL_MESSAGE_STATUSES } from '../types.js';\n\nexport class MessagesResource {\n constructor(private readonly http: HttpClient) {}\n\n /** Envoie un message texte (réponse HTTP 202). */\n async send(params: SendMessageParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send',\n body,\n idempotencyKey,\n });\n }\n\n /** Envoie un média via URL HTTPS publique (réponse HTTP 202). */\n async sendMedia(params: SendMediaParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send-media',\n body,\n idempotencyKey,\n });\n }\n\n /** Envoie un lot de messages (même modèle, destinataires multiples). */\n async sendBulk(params: SendBulkParams): Promise<SendBulkResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendBulkResponse>({\n method: 'POST',\n path: '/messages/send-bulk',\n body,\n idempotencyKey,\n });\n }\n\n /** Consulte le statut d'un message précédemment accepté. */\n async getStatus(messageId: string): Promise<MessageStatusResponse> {\n return this.http.request<MessageStatusResponse>({\n method: 'GET',\n path: `/messages/${messageId}`,\n });\n }\n\n /**\n * Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).\n * Lance `ReachFlowError` si timeout.\n */\n async waitForTerminal(\n messageId: string,\n options?: WaitForTerminalOptions,\n ): Promise<MessageStatusResponse> {\n const pollIntervalMs = options?.pollIntervalMs ?? 2_000;\n const timeoutMs = options?.timeoutMs ?? 120_000;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const status = await this.getStatus(messageId);\n if (TERMINAL_MESSAGE_STATUSES.has(status.status)) {\n return status;\n }\n await sleep(pollIntervalMs);\n }\n\n throw new ReachFlowError({\n message: `Message ${messageId} did not reach a terminal status within ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: false,\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport type {\n OtpSendParams,\n OtpSendResponse,\n OtpVerifyParams,\n OtpVerifyResponse,\n} from '../types.js';\n\nexport class OtpResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Génère et envoie un code OTP par WhatsApp.\n * Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.\n */\n async send(params: OtpSendParams): Promise<OtpSendResponse> {\n return this.http.request<OtpSendResponse>({\n method: 'POST',\n path: '/otp/send',\n body: params,\n });\n }\n\n /** Vérifie un code saisi par l'utilisateur final. */\n async verify(params: OtpVerifyParams): Promise<OtpVerifyResponse> {\n return this.http.request<OtpVerifyResponse>({\n method: 'POST',\n path: '/otp/verify',\n body: params,\n });\n }\n}\n","import type { HttpClient } from '../http.js';\nimport type { ProviderDetail, ProviderListResponse } from '../types.js';\n\nexport class ProvidersResource {\n constructor(private readonly http: HttpClient) {}\n\n /** Liste les numéros WhatsApp accessibles via l'API. */\n async list(): Promise<ProviderListResponse> {\n return this.http.request<ProviderListResponse>({\n method: 'GET',\n path: '/providers',\n });\n }\n\n /** Détail d'un provider avec statistiques journalières. */\n async get(providerId: string): Promise<ProviderDetail> {\n return this.http.request<ProviderDetail>({\n method: 'GET',\n path: `/providers/${providerId}`,\n });\n }\n\n /**\n * Retourne le premier provider au statut `connected`, ou le premier de la liste.\n * Utile pour les scripts de test / démarrage rapide.\n */\n async findConnected(): Promise<ProviderDetail | ProviderListResponse['providers'][number] | null> {\n const { providers } = await this.list();\n if (providers.length === 0) return null;\n return (\n providers.find((p) => p.status.toLowerCase() === 'connected') ??\n providers[0] ??\n null\n );\n }\n}\n","import { HttpClient, resolveConfig } from './http.js';\nimport { MessagesResource } from './resources/messages.js';\nimport { OtpResource } from './resources/otp.js';\nimport { ProvidersResource } from './resources/providers.js';\nimport type { ReachFlowClientOptions } from './types.js';\n\n/**\n * Client officiel pour l'API publique ReachFlow (REST v1).\n *\n * @example\n * ```ts\n * const client = new ReachFlow({ apiKey: process.env.REACHFLOW_API_KEY! });\n * const { providers } = await client.providers.list();\n * ```\n */\nexport class ReachFlow {\n readonly messages: MessagesResource;\n readonly providers: ProvidersResource;\n readonly otp: OtpResource;\n\n private readonly http: HttpClient;\n private readonly config: ReturnType<typeof resolveConfig>;\n\n constructor(options: ReachFlowClientOptions) {\n if (!options.apiKey?.trim()) {\n throw new Error('ReachFlow: apiKey is required');\n }\n\n this.config = resolveConfig(options);\n this.http = new HttpClient(this.config);\n this.messages = new MessagesResource(this.http);\n this.providers = new ProvidersResource(this.http);\n this.otp = new OtpResource(this.http);\n }\n\n /** URL de base configurée (sans `/api/v1`). */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n}\n"],"mappings":"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","names":["sleep"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/types.ts","../src/http.ts","../src/resources/messages.ts","../src/resources/otp.ts","../src/resources/providers.ts","../src/client.ts"],"sourcesContent":["export { ReachFlow } from './client.js';\nexport { ReachFlowError } from './errors.js';\nexport type { ReachFlowErrorCode } from './errors.js';\nexport type {\n BulkRecipient,\n FailureCode,\n MediaType,\n MessageStatus,\n MessageStatusResponse,\n OtpSendParams,\n OtpSendResponse,\n OtpVerifyParams,\n OtpVerifyResponse,\n OtpVerifyReason,\n ProviderDailyStats,\n ProviderDetail,\n ProviderListResponse,\n ProviderSummary,\n ReachFlowClientOptions,\n SendAcceptedResponse,\n SendBulkParams,\n SendBulkResponse,\n SendMediaParams,\n SendMessageParams,\n WaitForTerminalOptions,\n} from './types.js';\nexport {\n DEFAULT_BASE_URL,\n DEFAULT_ENVIRONMENT,\n REACHFLOW_ENVIRONMENTS,\n TERMINAL_MESSAGE_STATUSES,\n} from './types.js';\nexport type { ReachFlowEnvironment } from './types.js';\n","export type ReachFlowErrorCode =\n | 'unauthorized'\n | 'plan_required'\n | 'insufficient_scope'\n | 'rate_limit_exceeded'\n | 'too_many_auth_failures'\n | 'validation_error'\n | 'not_found'\n | 'api_error'\n | 'network_error'\n | 'timeout';\n\nexport interface ReachFlowErrorBody {\n statusCode?: number;\n error?: string;\n message?: string;\n}\n\nexport class ReachFlowError extends Error {\n readonly statusCode: number;\n readonly code: ReachFlowErrorCode;\n readonly retryable: boolean;\n readonly retryAfterMs?: number;\n readonly body?: unknown;\n\n constructor(params: {\n message: string;\n statusCode: number;\n code: ReachFlowErrorCode;\n retryable?: boolean;\n retryAfterMs?: number;\n body?: unknown;\n cause?: unknown;\n }) {\n super(params.message, params.cause ? { cause: params.cause } : undefined);\n this.name = 'ReachFlowError';\n this.statusCode = params.statusCode;\n this.code = params.code;\n this.retryable = params.retryable ?? false;\n this.retryAfterMs = params.retryAfterMs;\n this.body = params.body;\n }\n\n static fromResponse(\n status: number,\n body: unknown,\n fallbackMessage?: string,\n ): ReachFlowError {\n const parsed = (body ?? {}) as ReachFlowErrorBody;\n const apiError = parsed.error;\n const message =\n parsed.message ??\n fallbackMessage ??\n `ReachFlow API error (HTTP ${status})`;\n\n const code = mapApiErrorToCode(status, apiError);\n const retryable =\n status === 429 || status === 408 || (status >= 500 && status < 600);\n\n return new ReachFlowError({\n message,\n statusCode: status,\n code,\n retryable,\n body,\n });\n }\n\n static network(cause: unknown): ReachFlowError {\n return new ReachFlowError({\n message: 'Network error while calling ReachFlow API',\n statusCode: 0,\n code: 'network_error',\n retryable: true,\n cause,\n });\n }\n\n static timeout(timeoutMs: number): ReachFlowError {\n return new ReachFlowError({\n message: `Request timed out after ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: true,\n });\n }\n}\n\nfunction mapApiErrorToCode(\n status: number,\n apiError?: string,\n): ReachFlowErrorCode {\n switch (apiError) {\n case 'unauthorized':\n return 'unauthorized';\n case 'plan_required':\n return 'plan_required';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'rate_limit_exceeded':\n return 'rate_limit_exceeded';\n case 'too_many_auth_failures':\n return 'too_many_auth_failures';\n default:\n break;\n }\n\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'insufficient_scope';\n if (status === 404) return 'not_found';\n if (status === 400 || status === 422) return 'validation_error';\n if (status === 429) return 'rate_limit_exceeded';\n return 'api_error';\n}\n\nexport function parseRetryAfterMs(header: string | null): number | undefined {\n if (!header) return undefined;\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(header);\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n","/** Lifecycle status of an API message. */\nexport type MessageStatus =\n | 'queued'\n | 'processing'\n | 'sent'\n | 'delivered'\n | 'failed'\n | 'cancelled';\n\nexport type MediaType = 'image' | 'document' | 'audio' | 'video';\n\nexport type FailureCode =\n | 'warmup_daily_limit'\n | 'risk_circuit_open'\n | 'provider_disconnected'\n | 'provider_banned'\n | 'send_not_allowed'\n | 'instance_busy'\n | 'delivery_timeout'\n | 'delivery_failed'\n | 'send_error'\n | 'provider_not_found';\n\nexport type OtpVerifyReason =\n | 'invalidated'\n | 'already_used'\n | 'expired'\n | 'max_attempts_reached'\n | 'invalid_code'\n | 'not_found';\n\n/** Official ReachFlow API hosts (no custom base URL required). */\nexport const REACHFLOW_ENVIRONMENTS = {\n sandbox: 'https://sandbox-api.reachflow.me',\n live: 'https://api.reachflow.me',\n} as const;\n\nexport type ReachFlowEnvironment = keyof typeof REACHFLOW_ENVIRONMENTS;\n\nexport const DEFAULT_ENVIRONMENT: ReachFlowEnvironment = 'sandbox';\n\nexport interface ReachFlowClientOptions {\n /** API key (`rfl_live_…` or `rfl_test_…`). */\n apiKey: string;\n /**\n * ReachFlow API environment.\n * - `sandbox` → sandbox-api.reachflow.me\n * - `live` → api.reachflow.me\n * @default \"sandbox\"\n */\n environment?: ReachFlowEnvironment;\n /** HTTP timeout in milliseconds. @default 30000 */\n timeoutMs?: number;\n /** Custom fetch implementation (tests, Node < 18). @default global fetch */\n fetch?: typeof fetch;\n /** Max retries on 429 / retryable 5xx. @default 2 */\n maxRetries?: number;\n}\n\nexport interface SendMessageParams {\n providerId: string;\n to: string;\n message: string;\n variables?: Record<string, string>;\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendMediaParams {\n providerId: string;\n to: string;\n mediaUrl: string;\n mediaType: MediaType;\n caption?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface BulkRecipient {\n to: string;\n variables?: Record<string, string>;\n}\n\nexport interface SendBulkParams {\n providerId: string;\n messageTemplate: string;\n recipients: BulkRecipient[];\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendAcceptedResponse {\n messageId: string;\n status: 'queued';\n queuedAt: string;\n}\n\nexport interface BulkRejection {\n to: string;\n reason: string;\n}\n\nexport interface SendBulkResponse {\n bulkId: string;\n accepted: number;\n rejected: number;\n rejections: BulkRejection[];\n messageIds: string[];\n}\n\nexport interface MessageStatusResponse {\n messageId: string;\n status: MessageStatus;\n to: string;\n providerId: string;\n queuedAt: string;\n sentAt: string | null;\n deliveredAt: string | null;\n failedAt: string | null;\n failureCode: FailureCode | null;\n failureReason: string | null;\n}\n\nexport interface ProviderSummary {\n id: string;\n name: string;\n phoneNumber: string | null;\n status: string;\n warmupDay: number;\n riskScore: number;\n}\n\nexport interface ProviderDailyStats {\n sent: number;\n delivered: number;\n failed: number;\n messagesLimit: number | null;\n newContactsLimit: number | null;\n}\n\nexport interface ProviderDetail extends ProviderSummary {\n dailyStats: ProviderDailyStats;\n}\n\nexport interface ProviderListResponse {\n providers: ProviderSummary[];\n}\n\nexport interface OtpSendParams {\n providerId: string;\n phoneNumber: string;\n codeLength?: number;\n expiresIn?: number;\n brandName?: string;\n template?: string;\n saveContact?: boolean;\n}\n\nexport interface OtpSendResponse {\n otpId: string;\n messageId: string;\n expiresAt: string;\n}\n\nexport interface OtpVerifyParams {\n otpId: string;\n code: string;\n}\n\nexport interface OtpVerifyResponse {\n valid: boolean;\n reason?: OtpVerifyReason;\n attemptsLeft?: number;\n}\n\nexport interface WaitForTerminalOptions {\n /** Poll interval in ms. @default 2000 */\n pollIntervalMs?: number;\n /** Total timeout in ms. @default 120000 */\n timeoutMs?: number;\n}\n\nexport const TERMINAL_MESSAGE_STATUSES: ReadonlySet<MessageStatus> = new Set([\n 'sent',\n 'delivered',\n 'failed',\n 'cancelled',\n]);\n\n/** @deprecated Use `REACHFLOW_ENVIRONMENTS` and `environment` instead. */\nexport const DEFAULT_BASE_URL = REACHFLOW_ENVIRONMENTS.sandbox;\n","import { ReachFlowError, parseRetryAfterMs } from './errors.js';\nimport type { ReachFlowClientOptions } from './types.js';\nimport {\n DEFAULT_ENVIRONMENT,\n REACHFLOW_ENVIRONMENTS,\n} from './types.js';\n\nexport interface RequestOptions {\n method: 'GET' | 'POST';\n path: string;\n body?: unknown;\n idempotencyKey?: string;\n}\n\nexport interface ResolvedClientConfig {\n apiKey: string;\n environment: keyof typeof REACHFLOW_ENVIRONMENTS;\n baseUrl: string;\n apiPrefix: string;\n timeoutMs: number;\n fetchImpl: typeof fetch;\n maxRetries: number;\n}\n\nexport function resolveConfig(\n options: ReachFlowClientOptions,\n): ResolvedClientConfig {\n const environment = options.environment ?? DEFAULT_ENVIRONMENT;\n const baseUrl = REACHFLOW_ENVIRONMENTS[environment].replace(/\\/$/, '');\n return {\n apiKey: options.apiKey.trim(),\n environment,\n baseUrl,\n apiPrefix: `${baseUrl}/api/v1`,\n timeoutMs: options.timeoutMs ?? 30_000,\n fetchImpl: options.fetch ?? globalThis.fetch,\n maxRetries: options.maxRetries ?? 2,\n };\n}\n\nexport class HttpClient {\n constructor(private readonly config: ResolvedClientConfig) {}\n\n async request<T>(options: RequestOptions): Promise<T> {\n let attempt = 0;\n let lastError: ReachFlowError | undefined;\n\n while (attempt <= this.config.maxRetries) {\n try {\n return await this.requestOnce<T>(options);\n } catch (err) {\n if (!(err instanceof ReachFlowError)) throw err;\n lastError = err;\n if (!err.retryable || attempt >= this.config.maxRetries) throw err;\n const delayMs = err.retryAfterMs ?? backoffMs(attempt);\n await sleep(delayMs);\n attempt += 1;\n }\n }\n\n throw lastError ?? new ReachFlowError({\n message: 'Request failed',\n statusCode: 0,\n code: 'api_error',\n });\n }\n\n private async requestOnce<T>(options: RequestOptions): Promise<T> {\n const url = `${this.config.apiPrefix}${options.path}`;\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-API-Key': this.config.apiKey,\n };\n\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const response = await this.config.fetchImpl(url, {\n method: options.method,\n headers,\n body:\n options.body !== undefined\n ? JSON.stringify(options.body)\n : undefined,\n signal: controller.signal,\n });\n\n const text = await response.text();\n let data: unknown = null;\n if (text) {\n try {\n data = JSON.parse(text) as unknown;\n } catch {\n data = { raw: text.slice(0, 500) };\n }\n }\n\n if (!response.ok) {\n const err = ReachFlowError.fromResponse(\n response.status,\n data,\n );\n const retryAfterMs = parseRetryAfterMs(\n response.headers.get('Retry-After'),\n );\n if (retryAfterMs !== undefined) {\n throw new ReachFlowError({\n message: err.message,\n statusCode: err.statusCode,\n code: err.code,\n retryable: err.retryable,\n retryAfterMs,\n body: err.body,\n });\n }\n throw err;\n }\n\n return data as T;\n } catch (err) {\n if (err instanceof ReachFlowError) throw err;\n if (err instanceof Error && err.name === 'AbortError') {\n throw ReachFlowError.timeout(this.config.timeoutMs);\n }\n throw ReachFlowError.network(err);\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(1000 * 2 ** attempt, 10_000);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport { ReachFlowError } from '../errors.js';\nimport type {\n MessageStatusResponse,\n SendAcceptedResponse,\n SendBulkParams,\n SendBulkResponse,\n SendMediaParams,\n SendMessageParams,\n WaitForTerminalOptions,\n} from '../types.js';\nimport { TERMINAL_MESSAGE_STATUSES } from '../types.js';\n\nexport class MessagesResource {\n constructor(private readonly http: HttpClient) {}\n\n /** Send a text message (HTTP 202). */\n async send(params: SendMessageParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send',\n body,\n idempotencyKey,\n });\n }\n\n /** Send media via a public HTTPS URL (HTTP 202). */\n async sendMedia(params: SendMediaParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send-media',\n body,\n idempotencyKey,\n });\n }\n\n /** Send a bulk batch (same template, multiple recipients). */\n async sendBulk(params: SendBulkParams): Promise<SendBulkResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendBulkResponse>({\n method: 'POST',\n path: '/messages/send-bulk',\n body,\n idempotencyKey,\n });\n }\n\n /** Get the status of a previously accepted message. */\n async getStatus(messageId: string): Promise<MessageStatusResponse> {\n return this.http.request<MessageStatusResponse>({\n method: 'GET',\n path: `/messages/${messageId}`,\n });\n }\n\n /**\n * Poll until a terminal status (`sent`, `delivered`, `failed`, `cancelled`).\n * Throws `ReachFlowError` on timeout.\n */\n async waitForTerminal(\n messageId: string,\n options?: WaitForTerminalOptions,\n ): Promise<MessageStatusResponse> {\n const pollIntervalMs = options?.pollIntervalMs ?? 2_000;\n const timeoutMs = options?.timeoutMs ?? 120_000;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const status = await this.getStatus(messageId);\n if (TERMINAL_MESSAGE_STATUSES.has(status.status)) {\n return status;\n }\n await sleep(pollIntervalMs);\n }\n\n throw new ReachFlowError({\n message: `Message ${messageId} did not reach a terminal status within ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: false,\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport type {\n OtpSendParams,\n OtpSendResponse,\n OtpVerifyParams,\n OtpVerifyResponse,\n} from '../types.js';\n\nexport class OtpResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Generate and send an OTP via WhatsApp.\n * The code is never returned by the API — only delivered on WhatsApp.\n */\n async send(params: OtpSendParams): Promise<OtpSendResponse> {\n return this.http.request<OtpSendResponse>({\n method: 'POST',\n path: '/otp/send',\n body: params,\n });\n }\n\n /** Verify a code entered by the end user. */\n async verify(params: OtpVerifyParams): Promise<OtpVerifyResponse> {\n return this.http.request<OtpVerifyResponse>({\n method: 'POST',\n path: '/otp/verify',\n body: params,\n });\n }\n}\n","import type { HttpClient } from '../http.js';\nimport type { ProviderDetail, ProviderListResponse } from '../types.js';\n\nexport class ProvidersResource {\n constructor(private readonly http: HttpClient) {}\n\n /** List WhatsApp numbers available via the API. */\n async list(): Promise<ProviderListResponse> {\n return this.http.request<ProviderListResponse>({\n method: 'GET',\n path: '/providers',\n });\n }\n\n /** Get a provider with daily statistics. */\n async get(providerId: string): Promise<ProviderDetail> {\n return this.http.request<ProviderDetail>({\n method: 'GET',\n path: `/providers/${providerId}`,\n });\n }\n\n /**\n * Return the first `connected` provider, or the first in the list.\n * Handy for smoke tests and quick starts.\n */\n async findConnected(): Promise<ProviderDetail | ProviderListResponse['providers'][number] | null> {\n const { providers } = await this.list();\n if (providers.length === 0) return null;\n return (\n providers.find((p) => p.status.toLowerCase() === 'connected') ??\n providers[0] ??\n null\n );\n }\n}\n","import { HttpClient, resolveConfig } from './http.js';\nimport { MessagesResource } from './resources/messages.js';\nimport { OtpResource } from './resources/otp.js';\nimport { ProvidersResource } from './resources/providers.js';\nimport type { ReachFlowClientOptions, ReachFlowEnvironment } from './types.js';\n\n/**\n * Official client for the ReachFlow public API (REST v1).\n *\n * @example\n * ```ts\n * const client = new ReachFlow({ apiKey: process.env.REACHFLOW_API_KEY! });\n * const { providers } = await client.providers.list();\n * ```\n */\nexport class ReachFlow {\n readonly messages: MessagesResource;\n readonly providers: ProvidersResource;\n readonly otp: OtpResource;\n\n private readonly http: HttpClient;\n private readonly config: ReturnType<typeof resolveConfig>;\n\n constructor(options: ReachFlowClientOptions) {\n if (!options.apiKey?.trim()) {\n throw new Error('ReachFlow: apiKey is required');\n }\n\n this.config = resolveConfig(options);\n this.http = new HttpClient(this.config);\n this.messages = new MessagesResource(this.http);\n this.providers = new ProvidersResource(this.http);\n this.otp = new OtpResource(this.http);\n }\n\n /** Configured environment (`sandbox` or `live`). */\n get environment(): ReachFlowEnvironment {\n return this.config.environment;\n }\n\n /** Resolved API host (without `/api/v1`). */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n}\n"],"mappings":"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","names":["sleep"]}
package/dist/index.d.cts CHANGED
@@ -1,21 +1,30 @@
1
- /** Statut de cycle de vie d'un message API. */
1
+ /** Lifecycle status of an API message. */
2
2
  type MessageStatus = 'queued' | 'processing' | 'sent' | 'delivered' | 'failed' | 'cancelled';
3
3
  type MediaType = 'image' | 'document' | 'audio' | 'video';
4
4
  type FailureCode = 'warmup_daily_limit' | 'risk_circuit_open' | 'provider_disconnected' | 'provider_banned' | 'send_not_allowed' | 'instance_busy' | 'delivery_timeout' | 'delivery_failed' | 'send_error' | 'provider_not_found';
5
5
  type OtpVerifyReason = 'invalidated' | 'already_used' | 'expired' | 'max_attempts_reached' | 'invalid_code' | 'not_found';
6
+ /** Official ReachFlow API hosts (no custom base URL required). */
7
+ declare const REACHFLOW_ENVIRONMENTS: {
8
+ readonly sandbox: "https://sandbox-api.reachflow.me";
9
+ readonly live: "https://api.reachflow.me";
10
+ };
11
+ type ReachFlowEnvironment = keyof typeof REACHFLOW_ENVIRONMENTS;
12
+ declare const DEFAULT_ENVIRONMENT: ReachFlowEnvironment;
6
13
  interface ReachFlowClientOptions {
7
- /** Clé API `rfl_live_…` ou `rfl_test_…`. */
14
+ /** API key (`rfl_live_…` or `rfl_test_…`). */
8
15
  apiKey: string;
9
16
  /**
10
- * URL de base sans `/api/v1` (défaut : sandbox ReachFlow).
11
- * @default "https://sandbox-api.reachflow.me"
17
+ * ReachFlow API environment.
18
+ * - `sandbox` → sandbox-api.reachflow.me
19
+ * - `live` → api.reachflow.me
20
+ * @default "sandbox"
12
21
  */
13
- baseUrl?: string;
14
- /** Timeout HTTP en millisecondes. @default 30000 */
22
+ environment?: ReachFlowEnvironment;
23
+ /** HTTP timeout in milliseconds. @default 30000 */
15
24
  timeoutMs?: number;
16
- /** Implémentation fetch (tests, Node < 18). @default global fetch */
25
+ /** Custom fetch implementation (tests, Node < 18). @default global fetch */
17
26
  fetch?: typeof fetch;
18
- /** Nombre max de retries sur 429 / 5xx retryables. @default 2 */
27
+ /** Max retries on 429 / retryable 5xx. @default 2 */
19
28
  maxRetries?: number;
20
29
  }
21
30
  interface SendMessageParams {
@@ -121,13 +130,14 @@ interface OtpVerifyResponse {
121
130
  attemptsLeft?: number;
122
131
  }
123
132
  interface WaitForTerminalOptions {
124
- /** Intervalle entre polls en ms. @default 2000 */
133
+ /** Poll interval in ms. @default 2000 */
125
134
  pollIntervalMs?: number;
126
- /** Timeout total en ms. @default 120000 */
135
+ /** Total timeout in ms. @default 120000 */
127
136
  timeoutMs?: number;
128
137
  }
129
138
  declare const TERMINAL_MESSAGE_STATUSES: ReadonlySet<MessageStatus>;
130
- declare const DEFAULT_BASE_URL = "https://sandbox-api.reachflow.me";
139
+ /** @deprecated Use `REACHFLOW_ENVIRONMENTS` and `environment` instead. */
140
+ declare const DEFAULT_BASE_URL: "https://sandbox-api.reachflow.me";
131
141
 
132
142
  interface RequestOptions {
133
143
  method: 'GET' | 'POST';
@@ -137,6 +147,7 @@ interface RequestOptions {
137
147
  }
138
148
  interface ResolvedClientConfig {
139
149
  apiKey: string;
150
+ environment: keyof typeof REACHFLOW_ENVIRONMENTS;
140
151
  baseUrl: string;
141
152
  apiPrefix: string;
142
153
  timeoutMs: number;
@@ -153,17 +164,17 @@ declare class HttpClient {
153
164
  declare class MessagesResource {
154
165
  private readonly http;
155
166
  constructor(http: HttpClient);
156
- /** Envoie un message texte (réponse HTTP 202). */
167
+ /** Send a text message (HTTP 202). */
157
168
  send(params: SendMessageParams): Promise<SendAcceptedResponse>;
158
- /** Envoie un média via URL HTTPS publique (réponse HTTP 202). */
169
+ /** Send media via a public HTTPS URL (HTTP 202). */
159
170
  sendMedia(params: SendMediaParams): Promise<SendAcceptedResponse>;
160
- /** Envoie un lot de messages (même modèle, destinataires multiples). */
171
+ /** Send a bulk batch (same template, multiple recipients). */
161
172
  sendBulk(params: SendBulkParams): Promise<SendBulkResponse>;
162
- /** Consulte le statut d'un message précédemment accepté. */
173
+ /** Get the status of a previously accepted message. */
163
174
  getStatus(messageId: string): Promise<MessageStatusResponse>;
164
175
  /**
165
- * Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).
166
- * Lance `ReachFlowError` si timeout.
176
+ * Poll until a terminal status (`sent`, `delivered`, `failed`, `cancelled`).
177
+ * Throws `ReachFlowError` on timeout.
167
178
  */
168
179
  waitForTerminal(messageId: string, options?: WaitForTerminalOptions): Promise<MessageStatusResponse>;
169
180
  }
@@ -172,30 +183,30 @@ declare class OtpResource {
172
183
  private readonly http;
173
184
  constructor(http: HttpClient);
174
185
  /**
175
- * Génère et envoie un code OTP par WhatsApp.
176
- * Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.
186
+ * Generate and send an OTP via WhatsApp.
187
+ * The code is never returned by the API — only delivered on WhatsApp.
177
188
  */
178
189
  send(params: OtpSendParams): Promise<OtpSendResponse>;
179
- /** Vérifie un code saisi par l'utilisateur final. */
190
+ /** Verify a code entered by the end user. */
180
191
  verify(params: OtpVerifyParams): Promise<OtpVerifyResponse>;
181
192
  }
182
193
 
183
194
  declare class ProvidersResource {
184
195
  private readonly http;
185
196
  constructor(http: HttpClient);
186
- /** Liste les numéros WhatsApp accessibles via l'API. */
197
+ /** List WhatsApp numbers available via the API. */
187
198
  list(): Promise<ProviderListResponse>;
188
- /** Détail d'un provider avec statistiques journalières. */
199
+ /** Get a provider with daily statistics. */
189
200
  get(providerId: string): Promise<ProviderDetail>;
190
201
  /**
191
- * Retourne le premier provider au statut `connected`, ou le premier de la liste.
192
- * Utile pour les scripts de test / démarrage rapide.
202
+ * Return the first `connected` provider, or the first in the list.
203
+ * Handy for smoke tests and quick starts.
193
204
  */
194
205
  findConnected(): Promise<ProviderDetail | ProviderListResponse['providers'][number] | null>;
195
206
  }
196
207
 
197
208
  /**
198
- * Client officiel pour l'API publique ReachFlow (REST v1).
209
+ * Official client for the ReachFlow public API (REST v1).
199
210
  *
200
211
  * @example
201
212
  * ```ts
@@ -210,7 +221,9 @@ declare class ReachFlow {
210
221
  private readonly http;
211
222
  private readonly config;
212
223
  constructor(options: ReachFlowClientOptions);
213
- /** URL de base configurée (sans `/api/v1`). */
224
+ /** Configured environment (`sandbox` or `live`). */
225
+ get environment(): ReachFlowEnvironment;
226
+ /** Resolved API host (without `/api/v1`). */
214
227
  get baseUrl(): string;
215
228
  }
216
229
 
@@ -235,4 +248,4 @@ declare class ReachFlowError extends Error {
235
248
  static timeout(timeoutMs: number): ReachFlowError;
236
249
  }
237
250
 
238
- export { type BulkRecipient, DEFAULT_BASE_URL, type FailureCode, type MediaType, type MessageStatus, type MessageStatusResponse, type OtpSendParams, type OtpSendResponse, type OtpVerifyParams, type OtpVerifyReason, type OtpVerifyResponse, type ProviderDailyStats, type ProviderDetail, type ProviderListResponse, type ProviderSummary, ReachFlow, type ReachFlowClientOptions, ReachFlowError, type ReachFlowErrorCode, type SendAcceptedResponse, type SendBulkParams, type SendBulkResponse, type SendMediaParams, type SendMessageParams, TERMINAL_MESSAGE_STATUSES, type WaitForTerminalOptions };
251
+ export { type BulkRecipient, DEFAULT_BASE_URL, DEFAULT_ENVIRONMENT, type FailureCode, type MediaType, type MessageStatus, type MessageStatusResponse, type OtpSendParams, type OtpSendResponse, type OtpVerifyParams, type OtpVerifyReason, type OtpVerifyResponse, type ProviderDailyStats, type ProviderDetail, type ProviderListResponse, type ProviderSummary, REACHFLOW_ENVIRONMENTS, ReachFlow, type ReachFlowClientOptions, type ReachFlowEnvironment, ReachFlowError, type ReachFlowErrorCode, type SendAcceptedResponse, type SendBulkParams, type SendBulkResponse, type SendMediaParams, type SendMessageParams, TERMINAL_MESSAGE_STATUSES, type WaitForTerminalOptions };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,30 @@
1
- /** Statut de cycle de vie d'un message API. */
1
+ /** Lifecycle status of an API message. */
2
2
  type MessageStatus = 'queued' | 'processing' | 'sent' | 'delivered' | 'failed' | 'cancelled';
3
3
  type MediaType = 'image' | 'document' | 'audio' | 'video';
4
4
  type FailureCode = 'warmup_daily_limit' | 'risk_circuit_open' | 'provider_disconnected' | 'provider_banned' | 'send_not_allowed' | 'instance_busy' | 'delivery_timeout' | 'delivery_failed' | 'send_error' | 'provider_not_found';
5
5
  type OtpVerifyReason = 'invalidated' | 'already_used' | 'expired' | 'max_attempts_reached' | 'invalid_code' | 'not_found';
6
+ /** Official ReachFlow API hosts (no custom base URL required). */
7
+ declare const REACHFLOW_ENVIRONMENTS: {
8
+ readonly sandbox: "https://sandbox-api.reachflow.me";
9
+ readonly live: "https://api.reachflow.me";
10
+ };
11
+ type ReachFlowEnvironment = keyof typeof REACHFLOW_ENVIRONMENTS;
12
+ declare const DEFAULT_ENVIRONMENT: ReachFlowEnvironment;
6
13
  interface ReachFlowClientOptions {
7
- /** Clé API `rfl_live_…` ou `rfl_test_…`. */
14
+ /** API key (`rfl_live_…` or `rfl_test_…`). */
8
15
  apiKey: string;
9
16
  /**
10
- * URL de base sans `/api/v1` (défaut : sandbox ReachFlow).
11
- * @default "https://sandbox-api.reachflow.me"
17
+ * ReachFlow API environment.
18
+ * - `sandbox` → sandbox-api.reachflow.me
19
+ * - `live` → api.reachflow.me
20
+ * @default "sandbox"
12
21
  */
13
- baseUrl?: string;
14
- /** Timeout HTTP en millisecondes. @default 30000 */
22
+ environment?: ReachFlowEnvironment;
23
+ /** HTTP timeout in milliseconds. @default 30000 */
15
24
  timeoutMs?: number;
16
- /** Implémentation fetch (tests, Node < 18). @default global fetch */
25
+ /** Custom fetch implementation (tests, Node < 18). @default global fetch */
17
26
  fetch?: typeof fetch;
18
- /** Nombre max de retries sur 429 / 5xx retryables. @default 2 */
27
+ /** Max retries on 429 / retryable 5xx. @default 2 */
19
28
  maxRetries?: number;
20
29
  }
21
30
  interface SendMessageParams {
@@ -121,13 +130,14 @@ interface OtpVerifyResponse {
121
130
  attemptsLeft?: number;
122
131
  }
123
132
  interface WaitForTerminalOptions {
124
- /** Intervalle entre polls en ms. @default 2000 */
133
+ /** Poll interval in ms. @default 2000 */
125
134
  pollIntervalMs?: number;
126
- /** Timeout total en ms. @default 120000 */
135
+ /** Total timeout in ms. @default 120000 */
127
136
  timeoutMs?: number;
128
137
  }
129
138
  declare const TERMINAL_MESSAGE_STATUSES: ReadonlySet<MessageStatus>;
130
- declare const DEFAULT_BASE_URL = "https://sandbox-api.reachflow.me";
139
+ /** @deprecated Use `REACHFLOW_ENVIRONMENTS` and `environment` instead. */
140
+ declare const DEFAULT_BASE_URL: "https://sandbox-api.reachflow.me";
131
141
 
132
142
  interface RequestOptions {
133
143
  method: 'GET' | 'POST';
@@ -137,6 +147,7 @@ interface RequestOptions {
137
147
  }
138
148
  interface ResolvedClientConfig {
139
149
  apiKey: string;
150
+ environment: keyof typeof REACHFLOW_ENVIRONMENTS;
140
151
  baseUrl: string;
141
152
  apiPrefix: string;
142
153
  timeoutMs: number;
@@ -153,17 +164,17 @@ declare class HttpClient {
153
164
  declare class MessagesResource {
154
165
  private readonly http;
155
166
  constructor(http: HttpClient);
156
- /** Envoie un message texte (réponse HTTP 202). */
167
+ /** Send a text message (HTTP 202). */
157
168
  send(params: SendMessageParams): Promise<SendAcceptedResponse>;
158
- /** Envoie un média via URL HTTPS publique (réponse HTTP 202). */
169
+ /** Send media via a public HTTPS URL (HTTP 202). */
159
170
  sendMedia(params: SendMediaParams): Promise<SendAcceptedResponse>;
160
- /** Envoie un lot de messages (même modèle, destinataires multiples). */
171
+ /** Send a bulk batch (same template, multiple recipients). */
161
172
  sendBulk(params: SendBulkParams): Promise<SendBulkResponse>;
162
- /** Consulte le statut d'un message précédemment accepté. */
173
+ /** Get the status of a previously accepted message. */
163
174
  getStatus(messageId: string): Promise<MessageStatusResponse>;
164
175
  /**
165
- * Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).
166
- * Lance `ReachFlowError` si timeout.
176
+ * Poll until a terminal status (`sent`, `delivered`, `failed`, `cancelled`).
177
+ * Throws `ReachFlowError` on timeout.
167
178
  */
168
179
  waitForTerminal(messageId: string, options?: WaitForTerminalOptions): Promise<MessageStatusResponse>;
169
180
  }
@@ -172,30 +183,30 @@ declare class OtpResource {
172
183
  private readonly http;
173
184
  constructor(http: HttpClient);
174
185
  /**
175
- * Génère et envoie un code OTP par WhatsApp.
176
- * Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.
186
+ * Generate and send an OTP via WhatsApp.
187
+ * The code is never returned by the API — only delivered on WhatsApp.
177
188
  */
178
189
  send(params: OtpSendParams): Promise<OtpSendResponse>;
179
- /** Vérifie un code saisi par l'utilisateur final. */
190
+ /** Verify a code entered by the end user. */
180
191
  verify(params: OtpVerifyParams): Promise<OtpVerifyResponse>;
181
192
  }
182
193
 
183
194
  declare class ProvidersResource {
184
195
  private readonly http;
185
196
  constructor(http: HttpClient);
186
- /** Liste les numéros WhatsApp accessibles via l'API. */
197
+ /** List WhatsApp numbers available via the API. */
187
198
  list(): Promise<ProviderListResponse>;
188
- /** Détail d'un provider avec statistiques journalières. */
199
+ /** Get a provider with daily statistics. */
189
200
  get(providerId: string): Promise<ProviderDetail>;
190
201
  /**
191
- * Retourne le premier provider au statut `connected`, ou le premier de la liste.
192
- * Utile pour les scripts de test / démarrage rapide.
202
+ * Return the first `connected` provider, or the first in the list.
203
+ * Handy for smoke tests and quick starts.
193
204
  */
194
205
  findConnected(): Promise<ProviderDetail | ProviderListResponse['providers'][number] | null>;
195
206
  }
196
207
 
197
208
  /**
198
- * Client officiel pour l'API publique ReachFlow (REST v1).
209
+ * Official client for the ReachFlow public API (REST v1).
199
210
  *
200
211
  * @example
201
212
  * ```ts
@@ -210,7 +221,9 @@ declare class ReachFlow {
210
221
  private readonly http;
211
222
  private readonly config;
212
223
  constructor(options: ReachFlowClientOptions);
213
- /** URL de base configurée (sans `/api/v1`). */
224
+ /** Configured environment (`sandbox` or `live`). */
225
+ get environment(): ReachFlowEnvironment;
226
+ /** Resolved API host (without `/api/v1`). */
214
227
  get baseUrl(): string;
215
228
  }
216
229
 
@@ -235,4 +248,4 @@ declare class ReachFlowError extends Error {
235
248
  static timeout(timeoutMs: number): ReachFlowError;
236
249
  }
237
250
 
238
- export { type BulkRecipient, DEFAULT_BASE_URL, type FailureCode, type MediaType, type MessageStatus, type MessageStatusResponse, type OtpSendParams, type OtpSendResponse, type OtpVerifyParams, type OtpVerifyReason, type OtpVerifyResponse, type ProviderDailyStats, type ProviderDetail, type ProviderListResponse, type ProviderSummary, ReachFlow, type ReachFlowClientOptions, ReachFlowError, type ReachFlowErrorCode, type SendAcceptedResponse, type SendBulkParams, type SendBulkResponse, type SendMediaParams, type SendMessageParams, TERMINAL_MESSAGE_STATUSES, type WaitForTerminalOptions };
251
+ export { type BulkRecipient, DEFAULT_BASE_URL, DEFAULT_ENVIRONMENT, type FailureCode, type MediaType, type MessageStatus, type MessageStatusResponse, type OtpSendParams, type OtpSendResponse, type OtpVerifyParams, type OtpVerifyReason, type OtpVerifyResponse, type ProviderDailyStats, type ProviderDetail, type ProviderListResponse, type ProviderSummary, REACHFLOW_ENVIRONMENTS, ReachFlow, type ReachFlowClientOptions, type ReachFlowEnvironment, ReachFlowError, type ReachFlowErrorCode, type SendAcceptedResponse, type SendBulkParams, type SendBulkResponse, type SendMediaParams, type SendMessageParams, TERMINAL_MESSAGE_STATUSES, type WaitForTerminalOptions };
package/dist/index.js CHANGED
@@ -78,19 +78,26 @@ function parseRetryAfterMs(header) {
78
78
  }
79
79
 
80
80
  // src/types.ts
81
+ var REACHFLOW_ENVIRONMENTS = {
82
+ sandbox: "https://sandbox-api.reachflow.me",
83
+ live: "https://api.reachflow.me"
84
+ };
85
+ var DEFAULT_ENVIRONMENT = "sandbox";
81
86
  var TERMINAL_MESSAGE_STATUSES = /* @__PURE__ */ new Set([
82
87
  "sent",
83
88
  "delivered",
84
89
  "failed",
85
90
  "cancelled"
86
91
  ]);
87
- var DEFAULT_BASE_URL = "https://sandbox-api.reachflow.me";
92
+ var DEFAULT_BASE_URL = REACHFLOW_ENVIRONMENTS.sandbox;
88
93
 
89
94
  // src/http.ts
90
95
  function resolveConfig(options) {
91
- const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
96
+ const environment = options.environment ?? DEFAULT_ENVIRONMENT;
97
+ const baseUrl = REACHFLOW_ENVIRONMENTS[environment].replace(/\/$/, "");
92
98
  return {
93
99
  apiKey: options.apiKey.trim(),
100
+ environment,
94
101
  baseUrl,
95
102
  apiPrefix: `${baseUrl}/api/v1`,
96
103
  timeoutMs: options.timeoutMs ?? 3e4,
@@ -199,7 +206,7 @@ var MessagesResource = class {
199
206
  this.http = http;
200
207
  }
201
208
  http;
202
- /** Envoie un message texte (réponse HTTP 202). */
209
+ /** Send a text message (HTTP 202). */
203
210
  async send(params) {
204
211
  const { idempotencyKey, ...body } = params;
205
212
  return this.http.request({
@@ -209,7 +216,7 @@ var MessagesResource = class {
209
216
  idempotencyKey
210
217
  });
211
218
  }
212
- /** Envoie un média via URL HTTPS publique (réponse HTTP 202). */
219
+ /** Send media via a public HTTPS URL (HTTP 202). */
213
220
  async sendMedia(params) {
214
221
  const { idempotencyKey, ...body } = params;
215
222
  return this.http.request({
@@ -219,7 +226,7 @@ var MessagesResource = class {
219
226
  idempotencyKey
220
227
  });
221
228
  }
222
- /** Envoie un lot de messages (même modèle, destinataires multiples). */
229
+ /** Send a bulk batch (same template, multiple recipients). */
223
230
  async sendBulk(params) {
224
231
  const { idempotencyKey, ...body } = params;
225
232
  return this.http.request({
@@ -229,7 +236,7 @@ var MessagesResource = class {
229
236
  idempotencyKey
230
237
  });
231
238
  }
232
- /** Consulte le statut d'un message précédemment accepté. */
239
+ /** Get the status of a previously accepted message. */
233
240
  async getStatus(messageId) {
234
241
  return this.http.request({
235
242
  method: "GET",
@@ -237,8 +244,8 @@ var MessagesResource = class {
237
244
  });
238
245
  }
239
246
  /**
240
- * Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).
241
- * Lance `ReachFlowError` si timeout.
247
+ * Poll until a terminal status (`sent`, `delivered`, `failed`, `cancelled`).
248
+ * Throws `ReachFlowError` on timeout.
242
249
  */
243
250
  async waitForTerminal(messageId, options) {
244
251
  const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
@@ -270,8 +277,8 @@ var OtpResource = class {
270
277
  }
271
278
  http;
272
279
  /**
273
- * Génère et envoie un code OTP par WhatsApp.
274
- * Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.
280
+ * Generate and send an OTP via WhatsApp.
281
+ * The code is never returned by the API — only delivered on WhatsApp.
275
282
  */
276
283
  async send(params) {
277
284
  return this.http.request({
@@ -280,7 +287,7 @@ var OtpResource = class {
280
287
  body: params
281
288
  });
282
289
  }
283
- /** Vérifie un code saisi par l'utilisateur final. */
290
+ /** Verify a code entered by the end user. */
284
291
  async verify(params) {
285
292
  return this.http.request({
286
293
  method: "POST",
@@ -296,14 +303,14 @@ var ProvidersResource = class {
296
303
  this.http = http;
297
304
  }
298
305
  http;
299
- /** Liste les numéros WhatsApp accessibles via l'API. */
306
+ /** List WhatsApp numbers available via the API. */
300
307
  async list() {
301
308
  return this.http.request({
302
309
  method: "GET",
303
310
  path: "/providers"
304
311
  });
305
312
  }
306
- /** Détail d'un provider avec statistiques journalières. */
313
+ /** Get a provider with daily statistics. */
307
314
  async get(providerId) {
308
315
  return this.http.request({
309
316
  method: "GET",
@@ -311,8 +318,8 @@ var ProvidersResource = class {
311
318
  });
312
319
  }
313
320
  /**
314
- * Retourne le premier provider au statut `connected`, ou le premier de la liste.
315
- * Utile pour les scripts de test / démarrage rapide.
321
+ * Return the first `connected` provider, or the first in the list.
322
+ * Handy for smoke tests and quick starts.
316
323
  */
317
324
  async findConnected() {
318
325
  const { providers } = await this.list();
@@ -338,13 +345,19 @@ var ReachFlow = class {
338
345
  this.providers = new ProvidersResource(this.http);
339
346
  this.otp = new OtpResource(this.http);
340
347
  }
341
- /** URL de base configurée (sans `/api/v1`). */
348
+ /** Configured environment (`sandbox` or `live`). */
349
+ get environment() {
350
+ return this.config.environment;
351
+ }
352
+ /** Resolved API host (without `/api/v1`). */
342
353
  get baseUrl() {
343
354
  return this.config.baseUrl;
344
355
  }
345
356
  };
346
357
  export {
347
358
  DEFAULT_BASE_URL,
359
+ DEFAULT_ENVIRONMENT,
360
+ REACHFLOW_ENVIRONMENTS,
348
361
  ReachFlow,
349
362
  ReachFlowError,
350
363
  TERMINAL_MESSAGE_STATUSES
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/http.ts","../src/resources/messages.ts","../src/resources/otp.ts","../src/resources/providers.ts","../src/client.ts"],"sourcesContent":["export type ReachFlowErrorCode =\n | 'unauthorized'\n | 'plan_required'\n | 'insufficient_scope'\n | 'rate_limit_exceeded'\n | 'too_many_auth_failures'\n | 'validation_error'\n | 'not_found'\n | 'api_error'\n | 'network_error'\n | 'timeout';\n\nexport interface ReachFlowErrorBody {\n statusCode?: number;\n error?: string;\n message?: string;\n}\n\nexport class ReachFlowError extends Error {\n readonly statusCode: number;\n readonly code: ReachFlowErrorCode;\n readonly retryable: boolean;\n readonly retryAfterMs?: number;\n readonly body?: unknown;\n\n constructor(params: {\n message: string;\n statusCode: number;\n code: ReachFlowErrorCode;\n retryable?: boolean;\n retryAfterMs?: number;\n body?: unknown;\n cause?: unknown;\n }) {\n super(params.message, params.cause ? { cause: params.cause } : undefined);\n this.name = 'ReachFlowError';\n this.statusCode = params.statusCode;\n this.code = params.code;\n this.retryable = params.retryable ?? false;\n this.retryAfterMs = params.retryAfterMs;\n this.body = params.body;\n }\n\n static fromResponse(\n status: number,\n body: unknown,\n fallbackMessage?: string,\n ): ReachFlowError {\n const parsed = (body ?? {}) as ReachFlowErrorBody;\n const apiError = parsed.error;\n const message =\n parsed.message ??\n fallbackMessage ??\n `ReachFlow API error (HTTP ${status})`;\n\n const code = mapApiErrorToCode(status, apiError);\n const retryable =\n status === 429 || status === 408 || (status >= 500 && status < 600);\n\n return new ReachFlowError({\n message,\n statusCode: status,\n code,\n retryable,\n body,\n });\n }\n\n static network(cause: unknown): ReachFlowError {\n return new ReachFlowError({\n message: 'Network error while calling ReachFlow API',\n statusCode: 0,\n code: 'network_error',\n retryable: true,\n cause,\n });\n }\n\n static timeout(timeoutMs: number): ReachFlowError {\n return new ReachFlowError({\n message: `Request timed out after ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: true,\n });\n }\n}\n\nfunction mapApiErrorToCode(\n status: number,\n apiError?: string,\n): ReachFlowErrorCode {\n switch (apiError) {\n case 'unauthorized':\n return 'unauthorized';\n case 'plan_required':\n return 'plan_required';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'rate_limit_exceeded':\n return 'rate_limit_exceeded';\n case 'too_many_auth_failures':\n return 'too_many_auth_failures';\n default:\n break;\n }\n\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'insufficient_scope';\n if (status === 404) return 'not_found';\n if (status === 400 || status === 422) return 'validation_error';\n if (status === 429) return 'rate_limit_exceeded';\n return 'api_error';\n}\n\nexport function parseRetryAfterMs(header: string | null): number | undefined {\n if (!header) return undefined;\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(header);\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n","/** Statut de cycle de vie d'un message API. */\nexport type MessageStatus =\n | 'queued'\n | 'processing'\n | 'sent'\n | 'delivered'\n | 'failed'\n | 'cancelled';\n\nexport type MediaType = 'image' | 'document' | 'audio' | 'video';\n\nexport type FailureCode =\n | 'warmup_daily_limit'\n | 'risk_circuit_open'\n | 'provider_disconnected'\n | 'provider_banned'\n | 'send_not_allowed'\n | 'instance_busy'\n | 'delivery_timeout'\n | 'delivery_failed'\n | 'send_error'\n | 'provider_not_found';\n\nexport type OtpVerifyReason =\n | 'invalidated'\n | 'already_used'\n | 'expired'\n | 'max_attempts_reached'\n | 'invalid_code'\n | 'not_found';\n\nexport interface ReachFlowClientOptions {\n /** Clé API `rfl_live_…` ou `rfl_test_…`. */\n apiKey: string;\n /**\n * URL de base sans `/api/v1` (défaut : sandbox ReachFlow).\n * @default \"https://sandbox-api.reachflow.me\"\n */\n baseUrl?: string;\n /** Timeout HTTP en millisecondes. @default 30000 */\n timeoutMs?: number;\n /** Implémentation fetch (tests, Node < 18). @default global fetch */\n fetch?: typeof fetch;\n /** Nombre max de retries sur 429 / 5xx retryables. @default 2 */\n maxRetries?: number;\n}\n\nexport interface SendMessageParams {\n providerId: string;\n to: string;\n message: string;\n variables?: Record<string, string>;\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendMediaParams {\n providerId: string;\n to: string;\n mediaUrl: string;\n mediaType: MediaType;\n caption?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface BulkRecipient {\n to: string;\n variables?: Record<string, string>;\n}\n\nexport interface SendBulkParams {\n providerId: string;\n messageTemplate: string;\n recipients: BulkRecipient[];\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendAcceptedResponse {\n messageId: string;\n status: 'queued';\n queuedAt: string;\n}\n\nexport interface BulkRejection {\n to: string;\n reason: string;\n}\n\nexport interface SendBulkResponse {\n bulkId: string;\n accepted: number;\n rejected: number;\n rejections: BulkRejection[];\n messageIds: string[];\n}\n\nexport interface MessageStatusResponse {\n messageId: string;\n status: MessageStatus;\n to: string;\n providerId: string;\n queuedAt: string;\n sentAt: string | null;\n deliveredAt: string | null;\n failedAt: string | null;\n failureCode: FailureCode | null;\n failureReason: string | null;\n}\n\nexport interface ProviderSummary {\n id: string;\n name: string;\n phoneNumber: string | null;\n status: string;\n warmupDay: number;\n riskScore: number;\n}\n\nexport interface ProviderDailyStats {\n sent: number;\n delivered: number;\n failed: number;\n messagesLimit: number | null;\n newContactsLimit: number | null;\n}\n\nexport interface ProviderDetail extends ProviderSummary {\n dailyStats: ProviderDailyStats;\n}\n\nexport interface ProviderListResponse {\n providers: ProviderSummary[];\n}\n\nexport interface OtpSendParams {\n providerId: string;\n phoneNumber: string;\n codeLength?: number;\n expiresIn?: number;\n brandName?: string;\n template?: string;\n saveContact?: boolean;\n}\n\nexport interface OtpSendResponse {\n otpId: string;\n messageId: string;\n expiresAt: string;\n}\n\nexport interface OtpVerifyParams {\n otpId: string;\n code: string;\n}\n\nexport interface OtpVerifyResponse {\n valid: boolean;\n reason?: OtpVerifyReason;\n attemptsLeft?: number;\n}\n\nexport interface WaitForTerminalOptions {\n /** Intervalle entre polls en ms. @default 2000 */\n pollIntervalMs?: number;\n /** Timeout total en ms. @default 120000 */\n timeoutMs?: number;\n}\n\nexport const TERMINAL_MESSAGE_STATUSES: ReadonlySet<MessageStatus> = new Set([\n 'sent',\n 'delivered',\n 'failed',\n 'cancelled',\n]);\n\nexport const DEFAULT_BASE_URL = 'https://sandbox-api.reachflow.me';\n","import { ReachFlowError, parseRetryAfterMs } from './errors.js';\nimport type { ReachFlowClientOptions } from './types.js';\nimport { DEFAULT_BASE_URL } from './types.js';\n\nexport interface RequestOptions {\n method: 'GET' | 'POST';\n path: string;\n body?: unknown;\n idempotencyKey?: string;\n}\n\nexport interface ResolvedClientConfig {\n apiKey: string;\n baseUrl: string;\n apiPrefix: string;\n timeoutMs: number;\n fetchImpl: typeof fetch;\n maxRetries: number;\n}\n\nexport function resolveConfig(\n options: ReachFlowClientOptions,\n): ResolvedClientConfig {\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n return {\n apiKey: options.apiKey.trim(),\n baseUrl,\n apiPrefix: `${baseUrl}/api/v1`,\n timeoutMs: options.timeoutMs ?? 30_000,\n fetchImpl: options.fetch ?? globalThis.fetch,\n maxRetries: options.maxRetries ?? 2,\n };\n}\n\nexport class HttpClient {\n constructor(private readonly config: ResolvedClientConfig) {}\n\n async request<T>(options: RequestOptions): Promise<T> {\n let attempt = 0;\n let lastError: ReachFlowError | undefined;\n\n while (attempt <= this.config.maxRetries) {\n try {\n return await this.requestOnce<T>(options);\n } catch (err) {\n if (!(err instanceof ReachFlowError)) throw err;\n lastError = err;\n if (!err.retryable || attempt >= this.config.maxRetries) throw err;\n const delayMs = err.retryAfterMs ?? backoffMs(attempt);\n await sleep(delayMs);\n attempt += 1;\n }\n }\n\n throw lastError ?? new ReachFlowError({\n message: 'Request failed',\n statusCode: 0,\n code: 'api_error',\n });\n }\n\n private async requestOnce<T>(options: RequestOptions): Promise<T> {\n const url = `${this.config.apiPrefix}${options.path}`;\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-API-Key': this.config.apiKey,\n };\n\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const response = await this.config.fetchImpl(url, {\n method: options.method,\n headers,\n body:\n options.body !== undefined\n ? JSON.stringify(options.body)\n : undefined,\n signal: controller.signal,\n });\n\n const text = await response.text();\n let data: unknown = null;\n if (text) {\n try {\n data = JSON.parse(text) as unknown;\n } catch {\n data = { raw: text.slice(0, 500) };\n }\n }\n\n if (!response.ok) {\n const err = ReachFlowError.fromResponse(\n response.status,\n data,\n );\n const retryAfterMs = parseRetryAfterMs(\n response.headers.get('Retry-After'),\n );\n if (retryAfterMs !== undefined) {\n throw new ReachFlowError({\n message: err.message,\n statusCode: err.statusCode,\n code: err.code,\n retryable: err.retryable,\n retryAfterMs,\n body: err.body,\n });\n }\n throw err;\n }\n\n return data as T;\n } catch (err) {\n if (err instanceof ReachFlowError) throw err;\n if (err instanceof Error && err.name === 'AbortError') {\n throw ReachFlowError.timeout(this.config.timeoutMs);\n }\n throw ReachFlowError.network(err);\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(1000 * 2 ** attempt, 10_000);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport { ReachFlowError } from '../errors.js';\nimport type {\n MessageStatusResponse,\n SendAcceptedResponse,\n SendBulkParams,\n SendBulkResponse,\n SendMediaParams,\n SendMessageParams,\n WaitForTerminalOptions,\n} from '../types.js';\nimport { TERMINAL_MESSAGE_STATUSES } from '../types.js';\n\nexport class MessagesResource {\n constructor(private readonly http: HttpClient) {}\n\n /** Envoie un message texte (réponse HTTP 202). */\n async send(params: SendMessageParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send',\n body,\n idempotencyKey,\n });\n }\n\n /** Envoie un média via URL HTTPS publique (réponse HTTP 202). */\n async sendMedia(params: SendMediaParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send-media',\n body,\n idempotencyKey,\n });\n }\n\n /** Envoie un lot de messages (même modèle, destinataires multiples). */\n async sendBulk(params: SendBulkParams): Promise<SendBulkResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendBulkResponse>({\n method: 'POST',\n path: '/messages/send-bulk',\n body,\n idempotencyKey,\n });\n }\n\n /** Consulte le statut d'un message précédemment accepté. */\n async getStatus(messageId: string): Promise<MessageStatusResponse> {\n return this.http.request<MessageStatusResponse>({\n method: 'GET',\n path: `/messages/${messageId}`,\n });\n }\n\n /**\n * Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).\n * Lance `ReachFlowError` si timeout.\n */\n async waitForTerminal(\n messageId: string,\n options?: WaitForTerminalOptions,\n ): Promise<MessageStatusResponse> {\n const pollIntervalMs = options?.pollIntervalMs ?? 2_000;\n const timeoutMs = options?.timeoutMs ?? 120_000;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const status = await this.getStatus(messageId);\n if (TERMINAL_MESSAGE_STATUSES.has(status.status)) {\n return status;\n }\n await sleep(pollIntervalMs);\n }\n\n throw new ReachFlowError({\n message: `Message ${messageId} did not reach a terminal status within ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: false,\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport type {\n OtpSendParams,\n OtpSendResponse,\n OtpVerifyParams,\n OtpVerifyResponse,\n} from '../types.js';\n\nexport class OtpResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Génère et envoie un code OTP par WhatsApp.\n * Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.\n */\n async send(params: OtpSendParams): Promise<OtpSendResponse> {\n return this.http.request<OtpSendResponse>({\n method: 'POST',\n path: '/otp/send',\n body: params,\n });\n }\n\n /** Vérifie un code saisi par l'utilisateur final. */\n async verify(params: OtpVerifyParams): Promise<OtpVerifyResponse> {\n return this.http.request<OtpVerifyResponse>({\n method: 'POST',\n path: '/otp/verify',\n body: params,\n });\n }\n}\n","import type { HttpClient } from '../http.js';\nimport type { ProviderDetail, ProviderListResponse } from '../types.js';\n\nexport class ProvidersResource {\n constructor(private readonly http: HttpClient) {}\n\n /** Liste les numéros WhatsApp accessibles via l'API. */\n async list(): Promise<ProviderListResponse> {\n return this.http.request<ProviderListResponse>({\n method: 'GET',\n path: '/providers',\n });\n }\n\n /** Détail d'un provider avec statistiques journalières. */\n async get(providerId: string): Promise<ProviderDetail> {\n return this.http.request<ProviderDetail>({\n method: 'GET',\n path: `/providers/${providerId}`,\n });\n }\n\n /**\n * Retourne le premier provider au statut `connected`, ou le premier de la liste.\n * Utile pour les scripts de test / démarrage rapide.\n */\n async findConnected(): Promise<ProviderDetail | ProviderListResponse['providers'][number] | null> {\n const { providers } = await this.list();\n if (providers.length === 0) return null;\n return (\n providers.find((p) => p.status.toLowerCase() === 'connected') ??\n providers[0] ??\n null\n );\n }\n}\n","import { HttpClient, resolveConfig } from './http.js';\nimport { MessagesResource } from './resources/messages.js';\nimport { OtpResource } from './resources/otp.js';\nimport { ProvidersResource } from './resources/providers.js';\nimport type { ReachFlowClientOptions } from './types.js';\n\n/**\n * Client officiel pour l'API publique ReachFlow (REST v1).\n *\n * @example\n * ```ts\n * const client = new ReachFlow({ apiKey: process.env.REACHFLOW_API_KEY! });\n * const { providers } = await client.providers.list();\n * ```\n */\nexport class ReachFlow {\n readonly messages: MessagesResource;\n readonly providers: ProvidersResource;\n readonly otp: OtpResource;\n\n private readonly http: HttpClient;\n private readonly config: ReturnType<typeof resolveConfig>;\n\n constructor(options: ReachFlowClientOptions) {\n if (!options.apiKey?.trim()) {\n throw new Error('ReachFlow: apiKey is required');\n }\n\n this.config = resolveConfig(options);\n this.http = new HttpClient(this.config);\n this.messages = new MessagesResource(this.http);\n this.providers = new ProvidersResource(this.http);\n this.otp = new OtpResource(this.http);\n }\n\n /** URL de base configurée (sans `/api/v1`). */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n}\n"],"mappings":"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","names":["sleep"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/http.ts","../src/resources/messages.ts","../src/resources/otp.ts","../src/resources/providers.ts","../src/client.ts"],"sourcesContent":["export type ReachFlowErrorCode =\n | 'unauthorized'\n | 'plan_required'\n | 'insufficient_scope'\n | 'rate_limit_exceeded'\n | 'too_many_auth_failures'\n | 'validation_error'\n | 'not_found'\n | 'api_error'\n | 'network_error'\n | 'timeout';\n\nexport interface ReachFlowErrorBody {\n statusCode?: number;\n error?: string;\n message?: string;\n}\n\nexport class ReachFlowError extends Error {\n readonly statusCode: number;\n readonly code: ReachFlowErrorCode;\n readonly retryable: boolean;\n readonly retryAfterMs?: number;\n readonly body?: unknown;\n\n constructor(params: {\n message: string;\n statusCode: number;\n code: ReachFlowErrorCode;\n retryable?: boolean;\n retryAfterMs?: number;\n body?: unknown;\n cause?: unknown;\n }) {\n super(params.message, params.cause ? { cause: params.cause } : undefined);\n this.name = 'ReachFlowError';\n this.statusCode = params.statusCode;\n this.code = params.code;\n this.retryable = params.retryable ?? false;\n this.retryAfterMs = params.retryAfterMs;\n this.body = params.body;\n }\n\n static fromResponse(\n status: number,\n body: unknown,\n fallbackMessage?: string,\n ): ReachFlowError {\n const parsed = (body ?? {}) as ReachFlowErrorBody;\n const apiError = parsed.error;\n const message =\n parsed.message ??\n fallbackMessage ??\n `ReachFlow API error (HTTP ${status})`;\n\n const code = mapApiErrorToCode(status, apiError);\n const retryable =\n status === 429 || status === 408 || (status >= 500 && status < 600);\n\n return new ReachFlowError({\n message,\n statusCode: status,\n code,\n retryable,\n body,\n });\n }\n\n static network(cause: unknown): ReachFlowError {\n return new ReachFlowError({\n message: 'Network error while calling ReachFlow API',\n statusCode: 0,\n code: 'network_error',\n retryable: true,\n cause,\n });\n }\n\n static timeout(timeoutMs: number): ReachFlowError {\n return new ReachFlowError({\n message: `Request timed out after ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: true,\n });\n }\n}\n\nfunction mapApiErrorToCode(\n status: number,\n apiError?: string,\n): ReachFlowErrorCode {\n switch (apiError) {\n case 'unauthorized':\n return 'unauthorized';\n case 'plan_required':\n return 'plan_required';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'rate_limit_exceeded':\n return 'rate_limit_exceeded';\n case 'too_many_auth_failures':\n return 'too_many_auth_failures';\n default:\n break;\n }\n\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'insufficient_scope';\n if (status === 404) return 'not_found';\n if (status === 400 || status === 422) return 'validation_error';\n if (status === 429) return 'rate_limit_exceeded';\n return 'api_error';\n}\n\nexport function parseRetryAfterMs(header: string | null): number | undefined {\n if (!header) return undefined;\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(header);\n if (!Number.isNaN(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n","/** Lifecycle status of an API message. */\nexport type MessageStatus =\n | 'queued'\n | 'processing'\n | 'sent'\n | 'delivered'\n | 'failed'\n | 'cancelled';\n\nexport type MediaType = 'image' | 'document' | 'audio' | 'video';\n\nexport type FailureCode =\n | 'warmup_daily_limit'\n | 'risk_circuit_open'\n | 'provider_disconnected'\n | 'provider_banned'\n | 'send_not_allowed'\n | 'instance_busy'\n | 'delivery_timeout'\n | 'delivery_failed'\n | 'send_error'\n | 'provider_not_found';\n\nexport type OtpVerifyReason =\n | 'invalidated'\n | 'already_used'\n | 'expired'\n | 'max_attempts_reached'\n | 'invalid_code'\n | 'not_found';\n\n/** Official ReachFlow API hosts (no custom base URL required). */\nexport const REACHFLOW_ENVIRONMENTS = {\n sandbox: 'https://sandbox-api.reachflow.me',\n live: 'https://api.reachflow.me',\n} as const;\n\nexport type ReachFlowEnvironment = keyof typeof REACHFLOW_ENVIRONMENTS;\n\nexport const DEFAULT_ENVIRONMENT: ReachFlowEnvironment = 'sandbox';\n\nexport interface ReachFlowClientOptions {\n /** API key (`rfl_live_…` or `rfl_test_…`). */\n apiKey: string;\n /**\n * ReachFlow API environment.\n * - `sandbox` → sandbox-api.reachflow.me\n * - `live` → api.reachflow.me\n * @default \"sandbox\"\n */\n environment?: ReachFlowEnvironment;\n /** HTTP timeout in milliseconds. @default 30000 */\n timeoutMs?: number;\n /** Custom fetch implementation (tests, Node < 18). @default global fetch */\n fetch?: typeof fetch;\n /** Max retries on 429 / retryable 5xx. @default 2 */\n maxRetries?: number;\n}\n\nexport interface SendMessageParams {\n providerId: string;\n to: string;\n message: string;\n variables?: Record<string, string>;\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendMediaParams {\n providerId: string;\n to: string;\n mediaUrl: string;\n mediaType: MediaType;\n caption?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface BulkRecipient {\n to: string;\n variables?: Record<string, string>;\n}\n\nexport interface SendBulkParams {\n providerId: string;\n messageTemplate: string;\n recipients: BulkRecipient[];\n scheduleAt?: string;\n saveContact?: boolean;\n idempotencyKey?: string;\n}\n\nexport interface SendAcceptedResponse {\n messageId: string;\n status: 'queued';\n queuedAt: string;\n}\n\nexport interface BulkRejection {\n to: string;\n reason: string;\n}\n\nexport interface SendBulkResponse {\n bulkId: string;\n accepted: number;\n rejected: number;\n rejections: BulkRejection[];\n messageIds: string[];\n}\n\nexport interface MessageStatusResponse {\n messageId: string;\n status: MessageStatus;\n to: string;\n providerId: string;\n queuedAt: string;\n sentAt: string | null;\n deliveredAt: string | null;\n failedAt: string | null;\n failureCode: FailureCode | null;\n failureReason: string | null;\n}\n\nexport interface ProviderSummary {\n id: string;\n name: string;\n phoneNumber: string | null;\n status: string;\n warmupDay: number;\n riskScore: number;\n}\n\nexport interface ProviderDailyStats {\n sent: number;\n delivered: number;\n failed: number;\n messagesLimit: number | null;\n newContactsLimit: number | null;\n}\n\nexport interface ProviderDetail extends ProviderSummary {\n dailyStats: ProviderDailyStats;\n}\n\nexport interface ProviderListResponse {\n providers: ProviderSummary[];\n}\n\nexport interface OtpSendParams {\n providerId: string;\n phoneNumber: string;\n codeLength?: number;\n expiresIn?: number;\n brandName?: string;\n template?: string;\n saveContact?: boolean;\n}\n\nexport interface OtpSendResponse {\n otpId: string;\n messageId: string;\n expiresAt: string;\n}\n\nexport interface OtpVerifyParams {\n otpId: string;\n code: string;\n}\n\nexport interface OtpVerifyResponse {\n valid: boolean;\n reason?: OtpVerifyReason;\n attemptsLeft?: number;\n}\n\nexport interface WaitForTerminalOptions {\n /** Poll interval in ms. @default 2000 */\n pollIntervalMs?: number;\n /** Total timeout in ms. @default 120000 */\n timeoutMs?: number;\n}\n\nexport const TERMINAL_MESSAGE_STATUSES: ReadonlySet<MessageStatus> = new Set([\n 'sent',\n 'delivered',\n 'failed',\n 'cancelled',\n]);\n\n/** @deprecated Use `REACHFLOW_ENVIRONMENTS` and `environment` instead. */\nexport const DEFAULT_BASE_URL = REACHFLOW_ENVIRONMENTS.sandbox;\n","import { ReachFlowError, parseRetryAfterMs } from './errors.js';\nimport type { ReachFlowClientOptions } from './types.js';\nimport {\n DEFAULT_ENVIRONMENT,\n REACHFLOW_ENVIRONMENTS,\n} from './types.js';\n\nexport interface RequestOptions {\n method: 'GET' | 'POST';\n path: string;\n body?: unknown;\n idempotencyKey?: string;\n}\n\nexport interface ResolvedClientConfig {\n apiKey: string;\n environment: keyof typeof REACHFLOW_ENVIRONMENTS;\n baseUrl: string;\n apiPrefix: string;\n timeoutMs: number;\n fetchImpl: typeof fetch;\n maxRetries: number;\n}\n\nexport function resolveConfig(\n options: ReachFlowClientOptions,\n): ResolvedClientConfig {\n const environment = options.environment ?? DEFAULT_ENVIRONMENT;\n const baseUrl = REACHFLOW_ENVIRONMENTS[environment].replace(/\\/$/, '');\n return {\n apiKey: options.apiKey.trim(),\n environment,\n baseUrl,\n apiPrefix: `${baseUrl}/api/v1`,\n timeoutMs: options.timeoutMs ?? 30_000,\n fetchImpl: options.fetch ?? globalThis.fetch,\n maxRetries: options.maxRetries ?? 2,\n };\n}\n\nexport class HttpClient {\n constructor(private readonly config: ResolvedClientConfig) {}\n\n async request<T>(options: RequestOptions): Promise<T> {\n let attempt = 0;\n let lastError: ReachFlowError | undefined;\n\n while (attempt <= this.config.maxRetries) {\n try {\n return await this.requestOnce<T>(options);\n } catch (err) {\n if (!(err instanceof ReachFlowError)) throw err;\n lastError = err;\n if (!err.retryable || attempt >= this.config.maxRetries) throw err;\n const delayMs = err.retryAfterMs ?? backoffMs(attempt);\n await sleep(delayMs);\n attempt += 1;\n }\n }\n\n throw lastError ?? new ReachFlowError({\n message: 'Request failed',\n statusCode: 0,\n code: 'api_error',\n });\n }\n\n private async requestOnce<T>(options: RequestOptions): Promise<T> {\n const url = `${this.config.apiPrefix}${options.path}`;\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'X-API-Key': this.config.apiKey,\n };\n\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n if (options.idempotencyKey) {\n headers['Idempotency-Key'] = options.idempotencyKey;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const response = await this.config.fetchImpl(url, {\n method: options.method,\n headers,\n body:\n options.body !== undefined\n ? JSON.stringify(options.body)\n : undefined,\n signal: controller.signal,\n });\n\n const text = await response.text();\n let data: unknown = null;\n if (text) {\n try {\n data = JSON.parse(text) as unknown;\n } catch {\n data = { raw: text.slice(0, 500) };\n }\n }\n\n if (!response.ok) {\n const err = ReachFlowError.fromResponse(\n response.status,\n data,\n );\n const retryAfterMs = parseRetryAfterMs(\n response.headers.get('Retry-After'),\n );\n if (retryAfterMs !== undefined) {\n throw new ReachFlowError({\n message: err.message,\n statusCode: err.statusCode,\n code: err.code,\n retryable: err.retryable,\n retryAfterMs,\n body: err.body,\n });\n }\n throw err;\n }\n\n return data as T;\n } catch (err) {\n if (err instanceof ReachFlowError) throw err;\n if (err instanceof Error && err.name === 'AbortError') {\n throw ReachFlowError.timeout(this.config.timeoutMs);\n }\n throw ReachFlowError.network(err);\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(1000 * 2 ** attempt, 10_000);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport { ReachFlowError } from '../errors.js';\nimport type {\n MessageStatusResponse,\n SendAcceptedResponse,\n SendBulkParams,\n SendBulkResponse,\n SendMediaParams,\n SendMessageParams,\n WaitForTerminalOptions,\n} from '../types.js';\nimport { TERMINAL_MESSAGE_STATUSES } from '../types.js';\n\nexport class MessagesResource {\n constructor(private readonly http: HttpClient) {}\n\n /** Send a text message (HTTP 202). */\n async send(params: SendMessageParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send',\n body,\n idempotencyKey,\n });\n }\n\n /** Send media via a public HTTPS URL (HTTP 202). */\n async sendMedia(params: SendMediaParams): Promise<SendAcceptedResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendAcceptedResponse>({\n method: 'POST',\n path: '/messages/send-media',\n body,\n idempotencyKey,\n });\n }\n\n /** Send a bulk batch (same template, multiple recipients). */\n async sendBulk(params: SendBulkParams): Promise<SendBulkResponse> {\n const { idempotencyKey, ...body } = params;\n return this.http.request<SendBulkResponse>({\n method: 'POST',\n path: '/messages/send-bulk',\n body,\n idempotencyKey,\n });\n }\n\n /** Get the status of a previously accepted message. */\n async getStatus(messageId: string): Promise<MessageStatusResponse> {\n return this.http.request<MessageStatusResponse>({\n method: 'GET',\n path: `/messages/${messageId}`,\n });\n }\n\n /**\n * Poll until a terminal status (`sent`, `delivered`, `failed`, `cancelled`).\n * Throws `ReachFlowError` on timeout.\n */\n async waitForTerminal(\n messageId: string,\n options?: WaitForTerminalOptions,\n ): Promise<MessageStatusResponse> {\n const pollIntervalMs = options?.pollIntervalMs ?? 2_000;\n const timeoutMs = options?.timeoutMs ?? 120_000;\n const started = Date.now();\n\n while (Date.now() - started < timeoutMs) {\n const status = await this.getStatus(messageId);\n if (TERMINAL_MESSAGE_STATUSES.has(status.status)) {\n return status;\n }\n await sleep(pollIntervalMs);\n }\n\n throw new ReachFlowError({\n message: `Message ${messageId} did not reach a terminal status within ${timeoutMs}ms`,\n statusCode: 408,\n code: 'timeout',\n retryable: false,\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import type { HttpClient } from '../http.js';\nimport type {\n OtpSendParams,\n OtpSendResponse,\n OtpVerifyParams,\n OtpVerifyResponse,\n} from '../types.js';\n\nexport class OtpResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Generate and send an OTP via WhatsApp.\n * The code is never returned by the API — only delivered on WhatsApp.\n */\n async send(params: OtpSendParams): Promise<OtpSendResponse> {\n return this.http.request<OtpSendResponse>({\n method: 'POST',\n path: '/otp/send',\n body: params,\n });\n }\n\n /** Verify a code entered by the end user. */\n async verify(params: OtpVerifyParams): Promise<OtpVerifyResponse> {\n return this.http.request<OtpVerifyResponse>({\n method: 'POST',\n path: '/otp/verify',\n body: params,\n });\n }\n}\n","import type { HttpClient } from '../http.js';\nimport type { ProviderDetail, ProviderListResponse } from '../types.js';\n\nexport class ProvidersResource {\n constructor(private readonly http: HttpClient) {}\n\n /** List WhatsApp numbers available via the API. */\n async list(): Promise<ProviderListResponse> {\n return this.http.request<ProviderListResponse>({\n method: 'GET',\n path: '/providers',\n });\n }\n\n /** Get a provider with daily statistics. */\n async get(providerId: string): Promise<ProviderDetail> {\n return this.http.request<ProviderDetail>({\n method: 'GET',\n path: `/providers/${providerId}`,\n });\n }\n\n /**\n * Return the first `connected` provider, or the first in the list.\n * Handy for smoke tests and quick starts.\n */\n async findConnected(): Promise<ProviderDetail | ProviderListResponse['providers'][number] | null> {\n const { providers } = await this.list();\n if (providers.length === 0) return null;\n return (\n providers.find((p) => p.status.toLowerCase() === 'connected') ??\n providers[0] ??\n null\n );\n }\n}\n","import { HttpClient, resolveConfig } from './http.js';\nimport { MessagesResource } from './resources/messages.js';\nimport { OtpResource } from './resources/otp.js';\nimport { ProvidersResource } from './resources/providers.js';\nimport type { ReachFlowClientOptions, ReachFlowEnvironment } from './types.js';\n\n/**\n * Official client for the ReachFlow public API (REST v1).\n *\n * @example\n * ```ts\n * const client = new ReachFlow({ apiKey: process.env.REACHFLOW_API_KEY! });\n * const { providers } = await client.providers.list();\n * ```\n */\nexport class ReachFlow {\n readonly messages: MessagesResource;\n readonly providers: ProvidersResource;\n readonly otp: OtpResource;\n\n private readonly http: HttpClient;\n private readonly config: ReturnType<typeof resolveConfig>;\n\n constructor(options: ReachFlowClientOptions) {\n if (!options.apiKey?.trim()) {\n throw new Error('ReachFlow: apiKey is required');\n }\n\n this.config = resolveConfig(options);\n this.http = new HttpClient(this.config);\n this.messages = new MessagesResource(this.http);\n this.providers = new ProvidersResource(this.http);\n this.otp = new OtpResource(this.http);\n }\n\n /** Configured environment (`sandbox` or `live`). */\n get environment(): ReachFlowEnvironment {\n return this.config.environment;\n }\n\n /** Resolved API host (without `/api/v1`). */\n get baseUrl(): string {\n return this.config.baseUrl;\n }\n}\n"],"mappings":"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","names":["sleep"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reachflow/sdk",
3
- "version": "0.1.0",
4
- "description": "Client officiel Node.js / TypeScript pour l'API publique ReachFlow",
3
+ "version": "0.2.0",
4
+ "description": "Official Node.js / TypeScript client for the ReachFlow public REST API (WhatsApp, OTP)",
5
5
  "author": "ReachFlow",
6
6
  "license": "MIT",
7
7
  "homepage": "https://docs.reachflow.me/developpeurs",