@reachflow/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/index.cjs +382 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +238 -0
- package/dist/index.d.ts +238 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ReachFlow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @reachflow/sdk
|
|
2
|
+
|
|
3
|
+
Client officiel **Node.js / TypeScript** pour l’[API publique ReachFlow](https://docs.reachflow.me/developpeurs) (REST v1).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @reachflow/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Prérequis :** Node.js ≥ 18 (utilise `fetch` natif).
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { ReachFlow } from '@reachflow/sdk';
|
|
17
|
+
|
|
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
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Exemples
|
|
27
|
+
|
|
28
|
+
### Envoyer un message
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const { messageId } = await client.messages.send({
|
|
32
|
+
providerId: 'uuid-du-provider',
|
|
33
|
+
to: '22996123456',
|
|
34
|
+
message: 'Votre commande est confirmée.',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const status = await client.messages.waitForTerminal(messageId);
|
|
38
|
+
console.log(status.status); // sent | delivered | failed | …
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### OTP
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const { otpId } = await client.otp.send({
|
|
45
|
+
providerId: 'uuid-du-provider',
|
|
46
|
+
phoneNumber: '22996123456',
|
|
47
|
+
brandName: 'Mon App',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Le code arrive sur WhatsApp — jamais dans la réponse JSON.
|
|
51
|
+
const result = await client.otp.verify({ otpId, code: '482910' });
|
|
52
|
+
console.log(result.valid);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Providers
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const { providers } = await client.providers.list();
|
|
59
|
+
const connected = await client.providers.findConnected();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Gestion des erreurs
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { ReachFlow, ReachFlowError } from '@reachflow/sdk';
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await client.messages.send({ /* … */ });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof ReachFlowError) {
|
|
71
|
+
console.error(err.statusCode, err.code, err.message);
|
|
72
|
+
if (err.retryable) {
|
|
73
|
+
// retry manuel possible
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
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 |
|
|
87
|
+
|
|
88
|
+
## Idempotence
|
|
89
|
+
|
|
90
|
+
Passez `idempotencyKey` sur `messages.send`, `sendMedia` ou `sendBulk` :
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await client.messages.send({
|
|
94
|
+
providerId, to, message,
|
|
95
|
+
idempotencyKey: 'order-12345',
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Développement
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install
|
|
103
|
+
npm test
|
|
104
|
+
npm run build
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Licence
|
|
108
|
+
|
|
109
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
24
|
+
ReachFlow: () => ReachFlow,
|
|
25
|
+
ReachFlowError: () => ReachFlowError,
|
|
26
|
+
TERMINAL_MESSAGE_STATUSES: () => TERMINAL_MESSAGE_STATUSES
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var ReachFlowError = class _ReachFlowError extends Error {
|
|
32
|
+
statusCode;
|
|
33
|
+
code;
|
|
34
|
+
retryable;
|
|
35
|
+
retryAfterMs;
|
|
36
|
+
body;
|
|
37
|
+
constructor(params) {
|
|
38
|
+
super(params.message, params.cause ? { cause: params.cause } : void 0);
|
|
39
|
+
this.name = "ReachFlowError";
|
|
40
|
+
this.statusCode = params.statusCode;
|
|
41
|
+
this.code = params.code;
|
|
42
|
+
this.retryable = params.retryable ?? false;
|
|
43
|
+
this.retryAfterMs = params.retryAfterMs;
|
|
44
|
+
this.body = params.body;
|
|
45
|
+
}
|
|
46
|
+
static fromResponse(status, body, fallbackMessage) {
|
|
47
|
+
const parsed = body ?? {};
|
|
48
|
+
const apiError = parsed.error;
|
|
49
|
+
const message = parsed.message ?? fallbackMessage ?? `ReachFlow API error (HTTP ${status})`;
|
|
50
|
+
const code = mapApiErrorToCode(status, apiError);
|
|
51
|
+
const retryable = status === 429 || status === 408 || status >= 500 && status < 600;
|
|
52
|
+
return new _ReachFlowError({
|
|
53
|
+
message,
|
|
54
|
+
statusCode: status,
|
|
55
|
+
code,
|
|
56
|
+
retryable,
|
|
57
|
+
body
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
static network(cause) {
|
|
61
|
+
return new _ReachFlowError({
|
|
62
|
+
message: "Network error while calling ReachFlow API",
|
|
63
|
+
statusCode: 0,
|
|
64
|
+
code: "network_error",
|
|
65
|
+
retryable: true,
|
|
66
|
+
cause
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
static timeout(timeoutMs) {
|
|
70
|
+
return new _ReachFlowError({
|
|
71
|
+
message: `Request timed out after ${timeoutMs}ms`,
|
|
72
|
+
statusCode: 408,
|
|
73
|
+
code: "timeout",
|
|
74
|
+
retryable: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function mapApiErrorToCode(status, apiError) {
|
|
79
|
+
switch (apiError) {
|
|
80
|
+
case "unauthorized":
|
|
81
|
+
return "unauthorized";
|
|
82
|
+
case "plan_required":
|
|
83
|
+
return "plan_required";
|
|
84
|
+
case "insufficient_scope":
|
|
85
|
+
return "insufficient_scope";
|
|
86
|
+
case "rate_limit_exceeded":
|
|
87
|
+
return "rate_limit_exceeded";
|
|
88
|
+
case "too_many_auth_failures":
|
|
89
|
+
return "too_many_auth_failures";
|
|
90
|
+
default:
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (status === 401) return "unauthorized";
|
|
94
|
+
if (status === 403) return "insufficient_scope";
|
|
95
|
+
if (status === 404) return "not_found";
|
|
96
|
+
if (status === 400 || status === 422) return "validation_error";
|
|
97
|
+
if (status === 429) return "rate_limit_exceeded";
|
|
98
|
+
return "api_error";
|
|
99
|
+
}
|
|
100
|
+
function parseRetryAfterMs(header) {
|
|
101
|
+
if (!header) return void 0;
|
|
102
|
+
const seconds = Number(header);
|
|
103
|
+
if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1e3);
|
|
104
|
+
const date = Date.parse(header);
|
|
105
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/types.ts
|
|
110
|
+
var TERMINAL_MESSAGE_STATUSES = /* @__PURE__ */ new Set([
|
|
111
|
+
"sent",
|
|
112
|
+
"delivered",
|
|
113
|
+
"failed",
|
|
114
|
+
"cancelled"
|
|
115
|
+
]);
|
|
116
|
+
var DEFAULT_BASE_URL = "https://sandbox-api.reachflow.me";
|
|
117
|
+
|
|
118
|
+
// src/http.ts
|
|
119
|
+
function resolveConfig(options) {
|
|
120
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
121
|
+
return {
|
|
122
|
+
apiKey: options.apiKey.trim(),
|
|
123
|
+
baseUrl,
|
|
124
|
+
apiPrefix: `${baseUrl}/api/v1`,
|
|
125
|
+
timeoutMs: options.timeoutMs ?? 3e4,
|
|
126
|
+
fetchImpl: options.fetch ?? globalThis.fetch,
|
|
127
|
+
maxRetries: options.maxRetries ?? 2
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
var HttpClient = class {
|
|
131
|
+
constructor(config) {
|
|
132
|
+
this.config = config;
|
|
133
|
+
}
|
|
134
|
+
config;
|
|
135
|
+
async request(options) {
|
|
136
|
+
let attempt = 0;
|
|
137
|
+
let lastError;
|
|
138
|
+
while (attempt <= this.config.maxRetries) {
|
|
139
|
+
try {
|
|
140
|
+
return await this.requestOnce(options);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (!(err instanceof ReachFlowError)) throw err;
|
|
143
|
+
lastError = err;
|
|
144
|
+
if (!err.retryable || attempt >= this.config.maxRetries) throw err;
|
|
145
|
+
const delayMs = err.retryAfterMs ?? backoffMs(attempt);
|
|
146
|
+
await sleep(delayMs);
|
|
147
|
+
attempt += 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
throw lastError ?? new ReachFlowError({
|
|
151
|
+
message: "Request failed",
|
|
152
|
+
statusCode: 0,
|
|
153
|
+
code: "api_error"
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async requestOnce(options) {
|
|
157
|
+
const url = `${this.config.apiPrefix}${options.path}`;
|
|
158
|
+
const headers = {
|
|
159
|
+
Accept: "application/json",
|
|
160
|
+
"X-API-Key": this.config.apiKey
|
|
161
|
+
};
|
|
162
|
+
if (options.body !== void 0) {
|
|
163
|
+
headers["Content-Type"] = "application/json";
|
|
164
|
+
}
|
|
165
|
+
if (options.idempotencyKey) {
|
|
166
|
+
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
167
|
+
}
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.config.fetchImpl(url, {
|
|
172
|
+
method: options.method,
|
|
173
|
+
headers,
|
|
174
|
+
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
175
|
+
signal: controller.signal
|
|
176
|
+
});
|
|
177
|
+
const text = await response.text();
|
|
178
|
+
let data = null;
|
|
179
|
+
if (text) {
|
|
180
|
+
try {
|
|
181
|
+
data = JSON.parse(text);
|
|
182
|
+
} catch {
|
|
183
|
+
data = { raw: text.slice(0, 500) };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
const err = ReachFlowError.fromResponse(
|
|
188
|
+
response.status,
|
|
189
|
+
data
|
|
190
|
+
);
|
|
191
|
+
const retryAfterMs = parseRetryAfterMs(
|
|
192
|
+
response.headers.get("Retry-After")
|
|
193
|
+
);
|
|
194
|
+
if (retryAfterMs !== void 0) {
|
|
195
|
+
throw new ReachFlowError({
|
|
196
|
+
message: err.message,
|
|
197
|
+
statusCode: err.statusCode,
|
|
198
|
+
code: err.code,
|
|
199
|
+
retryable: err.retryable,
|
|
200
|
+
retryAfterMs,
|
|
201
|
+
body: err.body
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
return data;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (err instanceof ReachFlowError) throw err;
|
|
209
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
210
|
+
throw ReachFlowError.timeout(this.config.timeoutMs);
|
|
211
|
+
}
|
|
212
|
+
throw ReachFlowError.network(err);
|
|
213
|
+
} finally {
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
function backoffMs(attempt) {
|
|
219
|
+
return Math.min(1e3 * 2 ** attempt, 1e4);
|
|
220
|
+
}
|
|
221
|
+
function sleep(ms) {
|
|
222
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/resources/messages.ts
|
|
226
|
+
var MessagesResource = class {
|
|
227
|
+
constructor(http) {
|
|
228
|
+
this.http = http;
|
|
229
|
+
}
|
|
230
|
+
http;
|
|
231
|
+
/** Envoie un message texte (réponse HTTP 202). */
|
|
232
|
+
async send(params) {
|
|
233
|
+
const { idempotencyKey, ...body } = params;
|
|
234
|
+
return this.http.request({
|
|
235
|
+
method: "POST",
|
|
236
|
+
path: "/messages/send",
|
|
237
|
+
body,
|
|
238
|
+
idempotencyKey
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** Envoie un média via URL HTTPS publique (réponse HTTP 202). */
|
|
242
|
+
async sendMedia(params) {
|
|
243
|
+
const { idempotencyKey, ...body } = params;
|
|
244
|
+
return this.http.request({
|
|
245
|
+
method: "POST",
|
|
246
|
+
path: "/messages/send-media",
|
|
247
|
+
body,
|
|
248
|
+
idempotencyKey
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/** Envoie un lot de messages (même modèle, destinataires multiples). */
|
|
252
|
+
async sendBulk(params) {
|
|
253
|
+
const { idempotencyKey, ...body } = params;
|
|
254
|
+
return this.http.request({
|
|
255
|
+
method: "POST",
|
|
256
|
+
path: "/messages/send-bulk",
|
|
257
|
+
body,
|
|
258
|
+
idempotencyKey
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/** Consulte le statut d'un message précédemment accepté. */
|
|
262
|
+
async getStatus(messageId) {
|
|
263
|
+
return this.http.request({
|
|
264
|
+
method: "GET",
|
|
265
|
+
path: `/messages/${messageId}`
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Poll jusqu'à un statut terminal (`sent`, `delivered`, `failed`, `cancelled`).
|
|
270
|
+
* Lance `ReachFlowError` si timeout.
|
|
271
|
+
*/
|
|
272
|
+
async waitForTerminal(messageId, options) {
|
|
273
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
|
|
274
|
+
const timeoutMs = options?.timeoutMs ?? 12e4;
|
|
275
|
+
const started = Date.now();
|
|
276
|
+
while (Date.now() - started < timeoutMs) {
|
|
277
|
+
const status = await this.getStatus(messageId);
|
|
278
|
+
if (TERMINAL_MESSAGE_STATUSES.has(status.status)) {
|
|
279
|
+
return status;
|
|
280
|
+
}
|
|
281
|
+
await sleep2(pollIntervalMs);
|
|
282
|
+
}
|
|
283
|
+
throw new ReachFlowError({
|
|
284
|
+
message: `Message ${messageId} did not reach a terminal status within ${timeoutMs}ms`,
|
|
285
|
+
statusCode: 408,
|
|
286
|
+
code: "timeout",
|
|
287
|
+
retryable: false
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
function sleep2(ms) {
|
|
292
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/resources/otp.ts
|
|
296
|
+
var OtpResource = class {
|
|
297
|
+
constructor(http) {
|
|
298
|
+
this.http = http;
|
|
299
|
+
}
|
|
300
|
+
http;
|
|
301
|
+
/**
|
|
302
|
+
* Génère et envoie un code OTP par WhatsApp.
|
|
303
|
+
* Le code n'est jamais retourné par l'API — uniquement sur WhatsApp.
|
|
304
|
+
*/
|
|
305
|
+
async send(params) {
|
|
306
|
+
return this.http.request({
|
|
307
|
+
method: "POST",
|
|
308
|
+
path: "/otp/send",
|
|
309
|
+
body: params
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/** Vérifie un code saisi par l'utilisateur final. */
|
|
313
|
+
async verify(params) {
|
|
314
|
+
return this.http.request({
|
|
315
|
+
method: "POST",
|
|
316
|
+
path: "/otp/verify",
|
|
317
|
+
body: params
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// src/resources/providers.ts
|
|
323
|
+
var ProvidersResource = class {
|
|
324
|
+
constructor(http) {
|
|
325
|
+
this.http = http;
|
|
326
|
+
}
|
|
327
|
+
http;
|
|
328
|
+
/** Liste les numéros WhatsApp accessibles via l'API. */
|
|
329
|
+
async list() {
|
|
330
|
+
return this.http.request({
|
|
331
|
+
method: "GET",
|
|
332
|
+
path: "/providers"
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/** Détail d'un provider avec statistiques journalières. */
|
|
336
|
+
async get(providerId) {
|
|
337
|
+
return this.http.request({
|
|
338
|
+
method: "GET",
|
|
339
|
+
path: `/providers/${providerId}`
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Retourne le premier provider au statut `connected`, ou le premier de la liste.
|
|
344
|
+
* Utile pour les scripts de test / démarrage rapide.
|
|
345
|
+
*/
|
|
346
|
+
async findConnected() {
|
|
347
|
+
const { providers } = await this.list();
|
|
348
|
+
if (providers.length === 0) return null;
|
|
349
|
+
return providers.find((p) => p.status.toLowerCase() === "connected") ?? providers[0] ?? null;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/client.ts
|
|
354
|
+
var ReachFlow = class {
|
|
355
|
+
messages;
|
|
356
|
+
providers;
|
|
357
|
+
otp;
|
|
358
|
+
http;
|
|
359
|
+
config;
|
|
360
|
+
constructor(options) {
|
|
361
|
+
if (!options.apiKey?.trim()) {
|
|
362
|
+
throw new Error("ReachFlow: apiKey is required");
|
|
363
|
+
}
|
|
364
|
+
this.config = resolveConfig(options);
|
|
365
|
+
this.http = new HttpClient(this.config);
|
|
366
|
+
this.messages = new MessagesResource(this.http);
|
|
367
|
+
this.providers = new ProvidersResource(this.http);
|
|
368
|
+
this.otp = new OtpResource(this.http);
|
|
369
|
+
}
|
|
370
|
+
/** URL de base configurée (sans `/api/v1`). */
|
|
371
|
+
get baseUrl() {
|
|
372
|
+
return this.config.baseUrl;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
376
|
+
0 && (module.exports = {
|
|
377
|
+
DEFAULT_BASE_URL,
|
|
378
|
+
ReachFlow,
|
|
379
|
+
ReachFlowError,
|
|
380
|
+
TERMINAL_MESSAGE_STATUSES
|
|
381
|
+
});
|
|
382
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|