@wassist/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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +282 -0
- package/dist/index.cjs +1134 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1626 -0
- package/dist/index.d.ts +1626 -0
- package/dist/index.js +1113 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const __sdkRequire = (() => { try { return createRequire(import.meta.url); } catch { return undefined; } })();
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var WassistError = class extends Error {
|
|
7
|
+
/** HTTP status code if this error originated from an API response. */
|
|
8
|
+
statusCode;
|
|
9
|
+
/** Machine-readable error code (e.g. `not_found`) when the API provides one. */
|
|
10
|
+
code;
|
|
11
|
+
/**
|
|
12
|
+
* Request ID surfaced by the Wassist API in the `X-Request-Id` response
|
|
13
|
+
* header. Include this when filing support tickets.
|
|
14
|
+
*/
|
|
15
|
+
requestId;
|
|
16
|
+
/** Raw response body, parsed as JSON when possible. */
|
|
17
|
+
raw;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
super(opts.message);
|
|
20
|
+
this.name = new.target.name;
|
|
21
|
+
this.statusCode = opts.statusCode;
|
|
22
|
+
this.code = opts.code;
|
|
23
|
+
this.requestId = opts.requestId;
|
|
24
|
+
this.raw = opts.raw;
|
|
25
|
+
if (opts.cause !== void 0) {
|
|
26
|
+
this.cause = opts.cause;
|
|
27
|
+
}
|
|
28
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var WassistAuthenticationError = class extends WassistError {
|
|
32
|
+
};
|
|
33
|
+
var WassistPermissionError = class extends WassistError {
|
|
34
|
+
};
|
|
35
|
+
var WassistInvalidRequestError = class extends WassistError {
|
|
36
|
+
};
|
|
37
|
+
var WassistNotFoundError = class extends WassistError {
|
|
38
|
+
};
|
|
39
|
+
var WassistConflictError = class extends WassistError {
|
|
40
|
+
};
|
|
41
|
+
var WassistRateLimitError = class extends WassistError {
|
|
42
|
+
/** Suggested wait in seconds, from the `Retry-After` response header. */
|
|
43
|
+
retryAfter;
|
|
44
|
+
constructor(opts) {
|
|
45
|
+
super(opts);
|
|
46
|
+
this.retryAfter = opts.retryAfter;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var WassistAPIError = class extends WassistError {
|
|
50
|
+
};
|
|
51
|
+
var WassistConnectionError = class extends WassistError {
|
|
52
|
+
};
|
|
53
|
+
var WassistTimeoutError = class extends WassistError {
|
|
54
|
+
};
|
|
55
|
+
var WassistSignatureVerificationError = class extends WassistError {
|
|
56
|
+
/** The raw `X-Wassist-Signature` header value as received. */
|
|
57
|
+
header;
|
|
58
|
+
/** The payload that was verified, as the SDK saw it. */
|
|
59
|
+
payload;
|
|
60
|
+
constructor(opts) {
|
|
61
|
+
super(opts);
|
|
62
|
+
this.header = opts.header;
|
|
63
|
+
this.payload = opts.payload;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function errorFromResponse(args) {
|
|
67
|
+
const { status, body, requestId, retryAfter } = args;
|
|
68
|
+
const message = extractMessage(body) ?? `HTTP ${status}`;
|
|
69
|
+
const code = extractCode(body);
|
|
70
|
+
const base = { message, statusCode: status, code, requestId, raw: body };
|
|
71
|
+
if (status === 401) return new WassistAuthenticationError(base);
|
|
72
|
+
if (status === 403) return new WassistPermissionError(base);
|
|
73
|
+
if (status === 404) return new WassistNotFoundError(base);
|
|
74
|
+
if (status === 409) return new WassistConflictError(base);
|
|
75
|
+
if (status === 429) return new WassistRateLimitError({ ...base, retryAfter });
|
|
76
|
+
if (status === 400 || status === 422) {
|
|
77
|
+
return new WassistInvalidRequestError(base);
|
|
78
|
+
}
|
|
79
|
+
if (status >= 500 && status < 600) return new WassistAPIError(base);
|
|
80
|
+
return new WassistError(base);
|
|
81
|
+
}
|
|
82
|
+
function extractMessage(body) {
|
|
83
|
+
if (!body || typeof body !== "object") return void 0;
|
|
84
|
+
const b = body;
|
|
85
|
+
if (typeof b.error === "string") return b.error;
|
|
86
|
+
if (typeof b.message === "string") return b.message;
|
|
87
|
+
if (typeof b.detail === "string") return b.detail;
|
|
88
|
+
if (b.error && typeof b.error === "object") {
|
|
89
|
+
const inner = b.error.message;
|
|
90
|
+
if (typeof inner === "string") return inner;
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
function extractCode(body) {
|
|
95
|
+
if (!body || typeof body !== "object") return void 0;
|
|
96
|
+
const b = body;
|
|
97
|
+
if (typeof b.code === "string") return b.code;
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/version.ts
|
|
102
|
+
var SDK_VERSION = "0.1.0";
|
|
103
|
+
|
|
104
|
+
// src/http.ts
|
|
105
|
+
var DEFAULT_BASE_URL = "https://backend.wassist.app";
|
|
106
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
107
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
108
|
+
var API_VERSION_PREFIX = "/api/v1";
|
|
109
|
+
var RETRY_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
110
|
+
var HttpClient = class {
|
|
111
|
+
apiKey;
|
|
112
|
+
baseUrl;
|
|
113
|
+
timeout;
|
|
114
|
+
maxRetries;
|
|
115
|
+
fetchImpl;
|
|
116
|
+
constructor(config) {
|
|
117
|
+
if (!config.apiKey) {
|
|
118
|
+
throw new WassistError({
|
|
119
|
+
message: "A Wassist `apiKey` is required. Create one at https://wassist.app/settings."
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
this.apiKey = config.apiKey;
|
|
123
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
124
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
125
|
+
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
126
|
+
const provided = config.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
127
|
+
if (!provided) {
|
|
128
|
+
throw new WassistError({
|
|
129
|
+
message: "No `fetch` implementation found. Pass one via `fetch` in the Wassist constructor, or run on Node 18+ / a runtime with a global `fetch`."
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
this.fetchImpl = provided;
|
|
133
|
+
}
|
|
134
|
+
get(path, args) {
|
|
135
|
+
return this.request({ ...args, method: "GET", path });
|
|
136
|
+
}
|
|
137
|
+
post(path, body, options) {
|
|
138
|
+
return this.request({ method: "POST", path, body, options });
|
|
139
|
+
}
|
|
140
|
+
put(path, body, options) {
|
|
141
|
+
return this.request({ method: "PUT", path, body, options });
|
|
142
|
+
}
|
|
143
|
+
patch(path, body, options) {
|
|
144
|
+
return this.request({ method: "PATCH", path, body, options });
|
|
145
|
+
}
|
|
146
|
+
delete(path, options) {
|
|
147
|
+
return this.request({ method: "DELETE", path, options });
|
|
148
|
+
}
|
|
149
|
+
async request(args) {
|
|
150
|
+
const url = this.buildUrl(args.path, args.query);
|
|
151
|
+
const headers = this.buildHeaders(args);
|
|
152
|
+
const body = args.body === void 0 ? void 0 : JSON.stringify(args.body);
|
|
153
|
+
const maxRetries = args.options?.maxRetries ?? this.maxRetries;
|
|
154
|
+
const timeout = args.options?.timeout ?? this.timeout;
|
|
155
|
+
let lastError;
|
|
156
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
157
|
+
try {
|
|
158
|
+
const response = await this.doFetch({
|
|
159
|
+
url,
|
|
160
|
+
method: args.method,
|
|
161
|
+
headers,
|
|
162
|
+
body,
|
|
163
|
+
timeout,
|
|
164
|
+
signal: args.options?.signal
|
|
165
|
+
});
|
|
166
|
+
if (response.ok) {
|
|
167
|
+
if (response.status === 204) return void 0;
|
|
168
|
+
const text = await response.text();
|
|
169
|
+
if (!text) return void 0;
|
|
170
|
+
return JSON.parse(text);
|
|
171
|
+
}
|
|
172
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
173
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
174
|
+
const parsedBody = await safeJson(response);
|
|
175
|
+
const apiError = errorFromResponse({
|
|
176
|
+
status: response.status,
|
|
177
|
+
body: parsedBody,
|
|
178
|
+
requestId,
|
|
179
|
+
retryAfter
|
|
180
|
+
});
|
|
181
|
+
const isRetryable = RETRY_STATUSES.has(response.status);
|
|
182
|
+
if (isRetryable && attempt < maxRetries) {
|
|
183
|
+
await sleep(computeBackoff(attempt, apiError));
|
|
184
|
+
lastError = apiError;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
throw apiError;
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (err instanceof WassistError) {
|
|
190
|
+
if (attempt < maxRetries && (err instanceof WassistRateLimitError || isRetryableApiError(err))) {
|
|
191
|
+
await sleep(computeBackoff(attempt, err));
|
|
192
|
+
lastError = err;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
const mapped = mapFetchError(err);
|
|
198
|
+
if (attempt < maxRetries && isRetryableTransport(mapped)) {
|
|
199
|
+
await sleep(computeBackoff(attempt));
|
|
200
|
+
lastError = mapped;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
throw mapped;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw lastError instanceof Error ? lastError : new WassistError({ message: "Wassist request failed after retries." });
|
|
207
|
+
}
|
|
208
|
+
buildUrl(path, query) {
|
|
209
|
+
const prefixedPath = path.startsWith("/") ? path : `/${path}`;
|
|
210
|
+
const fullPath = prefixedPath.startsWith(API_VERSION_PREFIX) ? prefixedPath : `${API_VERSION_PREFIX}${prefixedPath}`;
|
|
211
|
+
const url = new URL(this.baseUrl + fullPath);
|
|
212
|
+
if (query) {
|
|
213
|
+
for (const [key, value] of Object.entries(query)) {
|
|
214
|
+
if (value === void 0 || value === null) continue;
|
|
215
|
+
url.searchParams.append(key, String(value));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return url.toString();
|
|
219
|
+
}
|
|
220
|
+
buildHeaders(args) {
|
|
221
|
+
const headers = {
|
|
222
|
+
"X-API-Key": this.apiKey,
|
|
223
|
+
"Accept": "application/json",
|
|
224
|
+
"User-Agent": `wassist-sdk-node/${SDK_VERSION}`,
|
|
225
|
+
"X-Wassist-Client": `wassist-sdk-node/${SDK_VERSION}`
|
|
226
|
+
};
|
|
227
|
+
if (args.body !== void 0) headers["Content-Type"] = "application/json";
|
|
228
|
+
if (args.options?.idempotencyKey) {
|
|
229
|
+
headers["Idempotency-Key"] = args.options.idempotencyKey;
|
|
230
|
+
}
|
|
231
|
+
if (args.options?.headers) {
|
|
232
|
+
for (const [k, v] of Object.entries(args.options.headers)) headers[k] = v;
|
|
233
|
+
}
|
|
234
|
+
return headers;
|
|
235
|
+
}
|
|
236
|
+
async doFetch(args) {
|
|
237
|
+
const controller = new AbortController();
|
|
238
|
+
const onAbort = () => controller.abort();
|
|
239
|
+
if (args.signal) {
|
|
240
|
+
if (args.signal.aborted) controller.abort();
|
|
241
|
+
else args.signal.addEventListener("abort", onAbort, { once: true });
|
|
242
|
+
}
|
|
243
|
+
const timeoutId = setTimeout(() => controller.abort(), args.timeout);
|
|
244
|
+
try {
|
|
245
|
+
return await this.fetchImpl(args.url, {
|
|
246
|
+
method: args.method,
|
|
247
|
+
headers: args.headers,
|
|
248
|
+
body: args.body,
|
|
249
|
+
signal: controller.signal
|
|
250
|
+
});
|
|
251
|
+
} finally {
|
|
252
|
+
clearTimeout(timeoutId);
|
|
253
|
+
if (args.signal) args.signal.removeEventListener("abort", onAbort);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
function safeJson(response) {
|
|
258
|
+
return response.text().then((text) => {
|
|
259
|
+
if (!text) return void 0;
|
|
260
|
+
try {
|
|
261
|
+
return JSON.parse(text);
|
|
262
|
+
} catch {
|
|
263
|
+
return text;
|
|
264
|
+
}
|
|
265
|
+
}).catch(() => void 0);
|
|
266
|
+
}
|
|
267
|
+
function parseRetryAfter(value) {
|
|
268
|
+
if (!value) return void 0;
|
|
269
|
+
const asNum = Number(value);
|
|
270
|
+
if (Number.isFinite(asNum) && asNum >= 0) return asNum;
|
|
271
|
+
const asDate = Date.parse(value);
|
|
272
|
+
if (Number.isNaN(asDate)) return void 0;
|
|
273
|
+
return Math.max(0, Math.floor((asDate - Date.now()) / 1e3));
|
|
274
|
+
}
|
|
275
|
+
function computeBackoff(attempt, err) {
|
|
276
|
+
if (err instanceof WassistRateLimitError && typeof err.retryAfter === "number") {
|
|
277
|
+
return err.retryAfter * 1e3;
|
|
278
|
+
}
|
|
279
|
+
const base = 500 * Math.pow(2, attempt);
|
|
280
|
+
const jitter = base * Math.random();
|
|
281
|
+
return Math.min(base + jitter, 8e3);
|
|
282
|
+
}
|
|
283
|
+
function sleep(ms) {
|
|
284
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
285
|
+
}
|
|
286
|
+
function isRetryableApiError(err) {
|
|
287
|
+
return err.statusCode !== void 0 && RETRY_STATUSES.has(err.statusCode);
|
|
288
|
+
}
|
|
289
|
+
function isRetryableTransport(err) {
|
|
290
|
+
return err instanceof WassistConnectionError;
|
|
291
|
+
}
|
|
292
|
+
function mapFetchError(err) {
|
|
293
|
+
if (err instanceof Error) {
|
|
294
|
+
if (err.name === "AbortError") {
|
|
295
|
+
return new WassistTimeoutError({
|
|
296
|
+
message: "Request timed out.",
|
|
297
|
+
cause: err
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return new WassistConnectionError({
|
|
301
|
+
message: `Network error: ${err.message}`,
|
|
302
|
+
cause: err
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return new WassistConnectionError({ message: "Unknown network error." });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/pagination.ts
|
|
309
|
+
var AutoPaginatedList = class {
|
|
310
|
+
fetchPage;
|
|
311
|
+
params;
|
|
312
|
+
options;
|
|
313
|
+
/** @internal */
|
|
314
|
+
constructor(fetchPage, params = {}, options) {
|
|
315
|
+
this.fetchPage = fetchPage;
|
|
316
|
+
this.params = params;
|
|
317
|
+
this.options = options;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Async iterator over every item across every page, fetched lazily.
|
|
321
|
+
*/
|
|
322
|
+
async *[Symbol.asyncIterator]() {
|
|
323
|
+
const limit = this.params.limit ?? 50;
|
|
324
|
+
let offset = this.params.offset ?? 0;
|
|
325
|
+
let hasMore = true;
|
|
326
|
+
while (hasMore) {
|
|
327
|
+
const page = await this.fetchPage({ limit, offset });
|
|
328
|
+
for (const item of page.results) yield item;
|
|
329
|
+
hasMore = page.next !== null && page.results.length > 0;
|
|
330
|
+
offset += page.results.length || limit;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Fetch the first page only and return it as a {@link Page}.
|
|
335
|
+
*/
|
|
336
|
+
async firstPage() {
|
|
337
|
+
const limit = this.params.limit ?? 50;
|
|
338
|
+
const offset = this.params.offset ?? 0;
|
|
339
|
+
const page = await this.fetchPage({ limit, offset });
|
|
340
|
+
return {
|
|
341
|
+
data: page.results,
|
|
342
|
+
totalCount: page.count,
|
|
343
|
+
hasMore: page.next !== null,
|
|
344
|
+
nextOffset: offset + page.results.length
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Walk every page and collect every item into a single array.
|
|
349
|
+
*
|
|
350
|
+
* Convenient for small lists; for large ones prefer `for await`.
|
|
351
|
+
*/
|
|
352
|
+
async all() {
|
|
353
|
+
const out = [];
|
|
354
|
+
for await (const item of this) out.push(item);
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// src/resources/agents.ts
|
|
360
|
+
var AgentsResource = class {
|
|
361
|
+
/** @internal */
|
|
362
|
+
constructor(http) {
|
|
363
|
+
this.http = http;
|
|
364
|
+
}
|
|
365
|
+
http;
|
|
366
|
+
/**
|
|
367
|
+
* List every agent on your account.
|
|
368
|
+
*
|
|
369
|
+
* Returns a lazily-fetched paginated list. Use `for await` to walk all
|
|
370
|
+
* pages, or `.firstPage()` for just the first.
|
|
371
|
+
*
|
|
372
|
+
* `GET /agents/`
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```ts
|
|
376
|
+
* for await (const agent of wassist.agents.list()) {
|
|
377
|
+
* console.log(agent.id, agent.name);
|
|
378
|
+
* }
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
list(params = {}, options) {
|
|
382
|
+
return new AutoPaginatedList(
|
|
383
|
+
(p) => this.http.get("/agents/", { query: p, options }),
|
|
384
|
+
params,
|
|
385
|
+
options
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Retrieve a single agent by ID.
|
|
390
|
+
*
|
|
391
|
+
* `GET /agents/{id}/`
|
|
392
|
+
*/
|
|
393
|
+
get(id, options) {
|
|
394
|
+
return this.http.get(`/agents/${encodeURIComponent(id)}/`, { options });
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Create a new agent with the given display name. Configure it further
|
|
398
|
+
* with {@link AgentsResource.update}.
|
|
399
|
+
*
|
|
400
|
+
* `POST /agents/`
|
|
401
|
+
*/
|
|
402
|
+
create(input, options) {
|
|
403
|
+
return this.http.post("/agents/", input, options);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Create a Bring-Your-Own-Agent (BYOA) — Wassist forwards inbound
|
|
407
|
+
* messages to your webhook and posts back replies you author yourself.
|
|
408
|
+
*
|
|
409
|
+
* `POST /agents/byoa/`
|
|
410
|
+
*/
|
|
411
|
+
createBYOA(input, options) {
|
|
412
|
+
return this.http.post("/agents/byoa/", input, options);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Partially update an agent. Only the fields you pass are modified.
|
|
416
|
+
*
|
|
417
|
+
* Nested sub-resource arrays (tools, memory keys, etc.) are diffed
|
|
418
|
+
* server-side by `id`: include an `id` to update an existing entry,
|
|
419
|
+
* omit it to create one.
|
|
420
|
+
*
|
|
421
|
+
* `PATCH /agents/{id}/`
|
|
422
|
+
*/
|
|
423
|
+
update(id, input, options) {
|
|
424
|
+
return this.http.patch(`/agents/${encodeURIComponent(id)}/`, input, options);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Soft-delete an agent. The agent stops responding immediately;
|
|
428
|
+
* conversation history is retained.
|
|
429
|
+
*
|
|
430
|
+
* `DELETE /agents/{id}/`
|
|
431
|
+
*/
|
|
432
|
+
delete(id, options) {
|
|
433
|
+
return this.http.delete(`/agents/${encodeURIComponent(id)}/`, options);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/resources/messages.ts
|
|
438
|
+
var MessagesResource = class {
|
|
439
|
+
/** @internal */
|
|
440
|
+
constructor(http) {
|
|
441
|
+
this.http = http;
|
|
442
|
+
}
|
|
443
|
+
http;
|
|
444
|
+
/**
|
|
445
|
+
* List messages in a conversation, newest first.
|
|
446
|
+
*
|
|
447
|
+
* `GET /conversations/{id}/messages/`
|
|
448
|
+
*/
|
|
449
|
+
list(conversationId, params = {}, options) {
|
|
450
|
+
const path = `/conversations/${encodeURIComponent(conversationId)}/messages/`;
|
|
451
|
+
return new AutoPaginatedList(
|
|
452
|
+
(p) => this.http.get(path, { query: p, options }),
|
|
453
|
+
params,
|
|
454
|
+
options
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Send a message in a conversation.
|
|
459
|
+
*
|
|
460
|
+
* The input is a discriminated union — set `type` and populate the
|
|
461
|
+
* matching field (`text`, `template`, `cta`, or `unified`).
|
|
462
|
+
*
|
|
463
|
+
* `POST /conversations/{id}/messages/`
|
|
464
|
+
*
|
|
465
|
+
* @example Plain text
|
|
466
|
+
* ```ts
|
|
467
|
+
* await wassist.conversations.messages.send(conv.id, {
|
|
468
|
+
* type: 'text',
|
|
469
|
+
* text: { body: 'Hello!' },
|
|
470
|
+
* });
|
|
471
|
+
* ```
|
|
472
|
+
*
|
|
473
|
+
* @example Approved template
|
|
474
|
+
* ```ts
|
|
475
|
+
* await wassist.conversations.messages.send(conv.id, {
|
|
476
|
+
* type: 'template',
|
|
477
|
+
* template: { name: 'order_update', variables: { body: ['Alex', '#1234'] } },
|
|
478
|
+
* });
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
send(conversationId, input, options) {
|
|
482
|
+
const path = `/conversations/${encodeURIComponent(conversationId)}/messages/`;
|
|
483
|
+
return this.http.post(path, input, options);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/resources/conversations.ts
|
|
488
|
+
var ConversationsResource = class {
|
|
489
|
+
/** @internal */
|
|
490
|
+
constructor(http) {
|
|
491
|
+
this.http = http;
|
|
492
|
+
this.messages = new MessagesResource(http);
|
|
493
|
+
}
|
|
494
|
+
http;
|
|
495
|
+
/** Send and list messages within a conversation. */
|
|
496
|
+
messages;
|
|
497
|
+
/**
|
|
498
|
+
* List conversations on your account with rich filtering.
|
|
499
|
+
*
|
|
500
|
+
* `GET /conversations/`
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```ts
|
|
504
|
+
* const recent = await wassist.conversations
|
|
505
|
+
* .list({ ordering: '-last_message_time', limit: 20 })
|
|
506
|
+
* .firstPage();
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
list(params = {}, options) {
|
|
510
|
+
const { limit, offset, ...filters } = params;
|
|
511
|
+
return new AutoPaginatedList(
|
|
512
|
+
(p) => this.http.get("/conversations/", {
|
|
513
|
+
query: { ...filters, ...p },
|
|
514
|
+
options
|
|
515
|
+
}),
|
|
516
|
+
{ limit, offset },
|
|
517
|
+
options
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Retrieve a single conversation by ID.
|
|
522
|
+
*
|
|
523
|
+
* `GET /conversations/{id}/`
|
|
524
|
+
*/
|
|
525
|
+
get(id, options) {
|
|
526
|
+
return this.http.get(`/conversations/${encodeURIComponent(id)}/`, {
|
|
527
|
+
options
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Create a new conversation with a contact.
|
|
532
|
+
*
|
|
533
|
+
* If the WhatsApp 24-hour reply window is closed for this contact, you
|
|
534
|
+
* must include a `template` message to open it.
|
|
535
|
+
*
|
|
536
|
+
* `POST /conversations/`
|
|
537
|
+
*/
|
|
538
|
+
create(input, options) {
|
|
539
|
+
return this.http.post("/conversations/", input, options);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Prompt the agent in this conversation with a custom instruction.
|
|
543
|
+
* Equivalent to "wake the agent up and have it say this" — useful for
|
|
544
|
+
* outbound triggers and follow-ups.
|
|
545
|
+
*
|
|
546
|
+
* `POST /conversations/{id}/prompt/`
|
|
547
|
+
*/
|
|
548
|
+
prompt(id, input, options) {
|
|
549
|
+
return this.http.post(
|
|
550
|
+
`/conversations/${encodeURIComponent(id)}/prompt/`,
|
|
551
|
+
input,
|
|
552
|
+
options
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Send a WhatsApp read receipt for the most recent inbound message.
|
|
557
|
+
*
|
|
558
|
+
* `POST /conversations/{id}/read/`
|
|
559
|
+
*/
|
|
560
|
+
read(id, options) {
|
|
561
|
+
return this.http.post(
|
|
562
|
+
`/conversations/${encodeURIComponent(id)}/read/`,
|
|
563
|
+
void 0,
|
|
564
|
+
options
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Display the WhatsApp typing indicator on the contact's device.
|
|
569
|
+
*
|
|
570
|
+
* Note: WhatsApp also marks the most recent inbound message as read as
|
|
571
|
+
* a side effect — this is a platform limitation.
|
|
572
|
+
*
|
|
573
|
+
* `POST /conversations/{id}/typing/`
|
|
574
|
+
*/
|
|
575
|
+
typing(id, options) {
|
|
576
|
+
return this.http.post(
|
|
577
|
+
`/conversations/${encodeURIComponent(id)}/typing/`,
|
|
578
|
+
void 0,
|
|
579
|
+
options
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Route this conversation to a webhook. The agent pipeline is skipped
|
|
584
|
+
* while the override is active.
|
|
585
|
+
*
|
|
586
|
+
* Fires a `subscription.activated` event on the target webhook.
|
|
587
|
+
*
|
|
588
|
+
* `POST /conversations/{id}/subscribe/`
|
|
589
|
+
*/
|
|
590
|
+
subscribe(id, input, options) {
|
|
591
|
+
return this.http.post(
|
|
592
|
+
`/conversations/${encodeURIComponent(id)}/subscribe/`,
|
|
593
|
+
input,
|
|
594
|
+
options
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Clear the per-conversation routing override and fall back to the
|
|
599
|
+
* phone number's default routing.
|
|
600
|
+
*
|
|
601
|
+
* Fires a `subscription.revoked` event on the previously-subscribed
|
|
602
|
+
* webhook (if any).
|
|
603
|
+
*
|
|
604
|
+
* `POST /conversations/{id}/unsubscribe/`
|
|
605
|
+
*/
|
|
606
|
+
unsubscribe(id, options) {
|
|
607
|
+
return this.http.post(
|
|
608
|
+
`/conversations/${encodeURIComponent(id)}/unsubscribe/`,
|
|
609
|
+
void 0,
|
|
610
|
+
options
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/resources/phone-numbers.ts
|
|
616
|
+
var PhoneNumbersResource = class {
|
|
617
|
+
/** @internal */
|
|
618
|
+
constructor(http) {
|
|
619
|
+
this.http = http;
|
|
620
|
+
}
|
|
621
|
+
http;
|
|
622
|
+
/**
|
|
623
|
+
* List every WhatsApp number on your account.
|
|
624
|
+
*
|
|
625
|
+
* `GET /phone-numbers/`
|
|
626
|
+
*/
|
|
627
|
+
list(params = {}, options) {
|
|
628
|
+
return new AutoPaginatedList(
|
|
629
|
+
(p) => this.http.get("/phone-numbers/", {
|
|
630
|
+
query: p,
|
|
631
|
+
options
|
|
632
|
+
}),
|
|
633
|
+
params,
|
|
634
|
+
options
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* View the WhatsApp Business Profile (about, address, websites, etc.)
|
|
639
|
+
* attached to this number.
|
|
640
|
+
*
|
|
641
|
+
* `GET /phone-numbers/{number}/business-profile/`
|
|
642
|
+
*/
|
|
643
|
+
getBusinessProfile(number, options) {
|
|
644
|
+
return this.http.get(
|
|
645
|
+
`/phone-numbers/${encodeURIComponent(number)}/business-profile/`,
|
|
646
|
+
{ options }
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Route inbound messages on this number to a webhook by default. Drops
|
|
651
|
+
* any agent previously connected to the number.
|
|
652
|
+
*
|
|
653
|
+
* Phone-number routing changes do **not** fire
|
|
654
|
+
* `subscription.activated` / `.revoked` webhook events — those are
|
|
655
|
+
* reserved for per-conversation
|
|
656
|
+
* {@link ConversationsResource.subscribe} /
|
|
657
|
+
* {@link ConversationsResource.unsubscribe}.
|
|
658
|
+
*
|
|
659
|
+
* `POST /phone-numbers/{number}/subscribe/`
|
|
660
|
+
*/
|
|
661
|
+
subscribe(number, input, options) {
|
|
662
|
+
return this.http.post(
|
|
663
|
+
`/phone-numbers/${encodeURIComponent(number)}/subscribe/`,
|
|
664
|
+
input,
|
|
665
|
+
options
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Assign an agent as the default for this number. Drops any subscribed
|
|
670
|
+
* webhook.
|
|
671
|
+
*
|
|
672
|
+
* The agent must already be owned by or shared with the authenticated
|
|
673
|
+
* user.
|
|
674
|
+
*
|
|
675
|
+
* `POST /phone-numbers/{number}/connect-agent/`
|
|
676
|
+
*/
|
|
677
|
+
connectAgent(number, input, options) {
|
|
678
|
+
return this.http.post(
|
|
679
|
+
`/phone-numbers/${encodeURIComponent(number)}/connect-agent/`,
|
|
680
|
+
input,
|
|
681
|
+
options
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Clear default routing on this number entirely. Drops both the agent
|
|
686
|
+
* and the webhook.
|
|
687
|
+
*
|
|
688
|
+
* `POST /phone-numbers/{number}/unsubscribe/`
|
|
689
|
+
*/
|
|
690
|
+
unsubscribe(number, input = {}, options) {
|
|
691
|
+
return this.http.post(
|
|
692
|
+
`/phone-numbers/${encodeURIComponent(number)}/unsubscribe/`,
|
|
693
|
+
input,
|
|
694
|
+
options
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// src/resources/whatsapp-accounts.ts
|
|
700
|
+
var WhatsAppAccountsResource = class {
|
|
701
|
+
/** @internal */
|
|
702
|
+
constructor(http) {
|
|
703
|
+
this.http = http;
|
|
704
|
+
}
|
|
705
|
+
http;
|
|
706
|
+
/**
|
|
707
|
+
* List every WhatsApp Business Account on your Wassist account.
|
|
708
|
+
*
|
|
709
|
+
* `GET /whatsapp-accounts/`
|
|
710
|
+
*/
|
|
711
|
+
list(params = {}, options) {
|
|
712
|
+
return new AutoPaginatedList(
|
|
713
|
+
(p) => this.http.get("/whatsapp-accounts/", {
|
|
714
|
+
query: p,
|
|
715
|
+
options
|
|
716
|
+
}),
|
|
717
|
+
params,
|
|
718
|
+
options
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Retrieve a single WhatsApp Business Account by ID.
|
|
723
|
+
*
|
|
724
|
+
* `GET /whatsapp-accounts/{id}/`
|
|
725
|
+
*/
|
|
726
|
+
get(id, options) {
|
|
727
|
+
return this.http.get(
|
|
728
|
+
`/whatsapp-accounts/${encodeURIComponent(id)}/`,
|
|
729
|
+
{ options }
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Add a pre-verified phone number (purchased via Meta's embedded
|
|
734
|
+
* signup flow) to a WhatsApp Business Account.
|
|
735
|
+
*
|
|
736
|
+
* `POST /whatsapp-accounts/{id}/add-number/`
|
|
737
|
+
*/
|
|
738
|
+
addNumber(id, input, options) {
|
|
739
|
+
return this.http.post(
|
|
740
|
+
`/whatsapp-accounts/${encodeURIComponent(id)}/add-number/`,
|
|
741
|
+
input,
|
|
742
|
+
options
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* List Twilio phone numbers available for purchase + verification.
|
|
747
|
+
*
|
|
748
|
+
* `GET /available-numbers/`
|
|
749
|
+
*/
|
|
750
|
+
availableNumbers(options) {
|
|
751
|
+
return this.http.get("/available-numbers/", { options });
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// src/resources/whatsapp-link-sessions.ts
|
|
756
|
+
var WhatsAppLinkSessionsResource = class {
|
|
757
|
+
/** @internal */
|
|
758
|
+
constructor(http) {
|
|
759
|
+
this.http = http;
|
|
760
|
+
}
|
|
761
|
+
http;
|
|
762
|
+
/**
|
|
763
|
+
* List every link session you've created.
|
|
764
|
+
*
|
|
765
|
+
* `GET /whatsapp-link-sessions/`
|
|
766
|
+
*/
|
|
767
|
+
list(params = {}, options) {
|
|
768
|
+
return new AutoPaginatedList(
|
|
769
|
+
(p) => this.http.get(
|
|
770
|
+
"/whatsapp-link-sessions/",
|
|
771
|
+
{ query: p, options }
|
|
772
|
+
),
|
|
773
|
+
params,
|
|
774
|
+
options
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Retrieve a single link session by ID.
|
|
779
|
+
*
|
|
780
|
+
* `GET /whatsapp-link-sessions/{id}/`
|
|
781
|
+
*/
|
|
782
|
+
get(id, options) {
|
|
783
|
+
return this.http.get(
|
|
784
|
+
`/whatsapp-link-sessions/${encodeURIComponent(id)}/`,
|
|
785
|
+
{ options }
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Create a new link session. Direct the user to the returned `linkUrl`.
|
|
790
|
+
*
|
|
791
|
+
* `POST /whatsapp-link-sessions/`
|
|
792
|
+
*/
|
|
793
|
+
create(input, options) {
|
|
794
|
+
return this.http.post(
|
|
795
|
+
"/whatsapp-link-sessions/",
|
|
796
|
+
input,
|
|
797
|
+
options
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Expire a pending link session early. Useful for invalidating stale
|
|
802
|
+
* URLs you've handed out to customers.
|
|
803
|
+
*
|
|
804
|
+
* `POST /whatsapp-link-sessions/{id}/expire/`
|
|
805
|
+
*/
|
|
806
|
+
expire(id, options) {
|
|
807
|
+
return this.http.post(
|
|
808
|
+
`/whatsapp-link-sessions/${encodeURIComponent(id)}/expire/`,
|
|
809
|
+
void 0,
|
|
810
|
+
options
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// src/resources/whatsapp-templates.ts
|
|
816
|
+
var WhatsAppTemplatesResource = class {
|
|
817
|
+
/** @internal */
|
|
818
|
+
constructor(http) {
|
|
819
|
+
this.http = http;
|
|
820
|
+
}
|
|
821
|
+
http;
|
|
822
|
+
/**
|
|
823
|
+
* List every template you've created in Wassist.
|
|
824
|
+
*
|
|
825
|
+
* `GET /whatsapp-templates/`
|
|
826
|
+
*/
|
|
827
|
+
list(params = {}, options) {
|
|
828
|
+
return new AutoPaginatedList(
|
|
829
|
+
(p) => this.http.get("/whatsapp-templates/", {
|
|
830
|
+
query: p,
|
|
831
|
+
options
|
|
832
|
+
}),
|
|
833
|
+
params,
|
|
834
|
+
options
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Retrieve a single template by ID.
|
|
839
|
+
*
|
|
840
|
+
* `GET /whatsapp-templates/{id}/`
|
|
841
|
+
*/
|
|
842
|
+
get(id, options) {
|
|
843
|
+
return this.http.get(
|
|
844
|
+
`/whatsapp-templates/${encodeURIComponent(id)}/`,
|
|
845
|
+
{ options }
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Create a new template. Published per-WABA separately via {@link publish}.
|
|
850
|
+
*
|
|
851
|
+
* `POST /whatsapp-templates/`
|
|
852
|
+
*/
|
|
853
|
+
create(input, options) {
|
|
854
|
+
return this.http.post("/whatsapp-templates/", input, options);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Update an existing template's metadata and components.
|
|
858
|
+
*
|
|
859
|
+
* `PATCH /whatsapp-templates/{id}/`
|
|
860
|
+
*/
|
|
861
|
+
update(id, input, options) {
|
|
862
|
+
return this.http.patch(
|
|
863
|
+
`/whatsapp-templates/${encodeURIComponent(id)}/`,
|
|
864
|
+
input,
|
|
865
|
+
options
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Delete a template (and unpublish from every WABA it was published to).
|
|
870
|
+
*
|
|
871
|
+
* `DELETE /whatsapp-templates/{id}/`
|
|
872
|
+
*/
|
|
873
|
+
delete(id, options) {
|
|
874
|
+
return this.http.delete(
|
|
875
|
+
`/whatsapp-templates/${encodeURIComponent(id)}/`,
|
|
876
|
+
options
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Submit this template to one or more WABAs for approval.
|
|
881
|
+
*
|
|
882
|
+
* Returns the updated template, plus per-account `publishResults` /
|
|
883
|
+
* `publishErrors`.
|
|
884
|
+
*
|
|
885
|
+
* `POST /whatsapp-templates/{id}/publish/`
|
|
886
|
+
*/
|
|
887
|
+
publish(id, input, options) {
|
|
888
|
+
return this.http.post(
|
|
889
|
+
`/whatsapp-templates/${encodeURIComponent(id)}/publish/`,
|
|
890
|
+
input,
|
|
891
|
+
options
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Remove this template from one or more WABAs.
|
|
896
|
+
*
|
|
897
|
+
* `POST /whatsapp-templates/{id}/unpublish/`
|
|
898
|
+
*/
|
|
899
|
+
unpublish(id, input, options) {
|
|
900
|
+
return this.http.post(
|
|
901
|
+
`/whatsapp-templates/${encodeURIComponent(id)}/unpublish/`,
|
|
902
|
+
input,
|
|
903
|
+
options
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/webhooks.ts
|
|
909
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
910
|
+
var Webhooks = class {
|
|
911
|
+
/**
|
|
912
|
+
* Verify the signature on a webhook delivery, parse the body, and return
|
|
913
|
+
* the typed event.
|
|
914
|
+
*
|
|
915
|
+
* @param payload The raw request body, exactly as received — `string`,
|
|
916
|
+
* `Buffer`, or `Uint8Array`. **Do not parse it first;**
|
|
917
|
+
* even whitespace differences will break verification.
|
|
918
|
+
* @param header The `X-Wassist-Signature` header value.
|
|
919
|
+
* @param secret Your webhook signing secret. Find it in the
|
|
920
|
+
* [Wassist dashboard](https://wassist.app/developers/webhooks).
|
|
921
|
+
* @param options.tolerance Replay-protection window in seconds (default 300).
|
|
922
|
+
* @throws {WassistSignatureVerificationError} When the header is malformed,
|
|
923
|
+
* the HMAC doesn't match, or the timestamp is outside the tolerance window.
|
|
924
|
+
*/
|
|
925
|
+
constructEvent(payload, header, secret, options = {}) {
|
|
926
|
+
const { signedPayload, parts } = this.prepare(payload, header);
|
|
927
|
+
const expected = this.signSync(signedPayload, secret);
|
|
928
|
+
this.assertValid(parts, expected, header ?? "", signedPayload, options);
|
|
929
|
+
return this.parseEvent(payload);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Async variant of {@link constructEvent} that uses Web Crypto, for
|
|
933
|
+
* runtimes that don't expose `node:crypto` (Cloudflare Workers, Deno,
|
|
934
|
+
* the browser).
|
|
935
|
+
*/
|
|
936
|
+
async constructEventAsync(payload, header, secret, options = {}) {
|
|
937
|
+
const { signedPayload, parts } = this.prepare(payload, header);
|
|
938
|
+
const expected = await this.signAsync(signedPayload, secret);
|
|
939
|
+
this.assertValid(parts, expected, header ?? "", signedPayload, options);
|
|
940
|
+
return this.parseEvent(payload);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Compute the v1 signature for a `t.<body>` payload — exported as a
|
|
944
|
+
* convenience for testing.
|
|
945
|
+
*/
|
|
946
|
+
signSync(signedPayload, secret) {
|
|
947
|
+
const nodeCrypto = loadNodeCrypto();
|
|
948
|
+
if (!nodeCrypto) {
|
|
949
|
+
throw new WassistSignatureVerificationError({
|
|
950
|
+
message: "node:crypto is unavailable in this runtime. Use `constructEventAsync` instead."
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return nodeCrypto.createHmac("sha256", secret).update(signedPayload).digest("hex");
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Web Crypto variant of {@link signSync}.
|
|
957
|
+
*/
|
|
958
|
+
async signAsync(signedPayload, secret) {
|
|
959
|
+
const subtle = globalThis.crypto?.subtle;
|
|
960
|
+
if (!subtle) {
|
|
961
|
+
throw new WassistSignatureVerificationError({
|
|
962
|
+
message: "Web Crypto (`globalThis.crypto.subtle`) is unavailable in this runtime."
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
const enc = new TextEncoder();
|
|
966
|
+
const key = await subtle.importKey(
|
|
967
|
+
"raw",
|
|
968
|
+
enc.encode(secret),
|
|
969
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
970
|
+
false,
|
|
971
|
+
["sign"]
|
|
972
|
+
);
|
|
973
|
+
const sig = await subtle.sign("HMAC", key, enc.encode(signedPayload));
|
|
974
|
+
return toHex(new Uint8Array(sig));
|
|
975
|
+
}
|
|
976
|
+
// =============================================================================
|
|
977
|
+
// Internals
|
|
978
|
+
// =============================================================================
|
|
979
|
+
prepare(payload, header) {
|
|
980
|
+
if (!header) {
|
|
981
|
+
throw new WassistSignatureVerificationError({
|
|
982
|
+
message: "Missing X-Wassist-Signature header.",
|
|
983
|
+
header: header ?? void 0
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
const parts = parseHeader(header);
|
|
987
|
+
if (!parts.t || !parts.v1) {
|
|
988
|
+
throw new WassistSignatureVerificationError({
|
|
989
|
+
message: "Malformed X-Wassist-Signature header \u2014 expected `t=<unix>,v1=<hex>`.",
|
|
990
|
+
header
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
const bodyText = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
994
|
+
return { signedPayload: `${parts.t}.${bodyText}`, parts };
|
|
995
|
+
}
|
|
996
|
+
assertValid(parts, expected, header, signedPayload, options) {
|
|
997
|
+
if (!constantTimeEqualsHex(parts.v1, expected)) {
|
|
998
|
+
throw new WassistSignatureVerificationError({
|
|
999
|
+
message: "Webhook signature did not match the expected value.",
|
|
1000
|
+
header,
|
|
1001
|
+
payload: signedPayload
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS;
|
|
1005
|
+
if (tolerance > 0) {
|
|
1006
|
+
const now = options.currentTimestamp ?? Math.floor(Date.now() / 1e3);
|
|
1007
|
+
const ts = Number(parts.t);
|
|
1008
|
+
if (!Number.isFinite(ts)) {
|
|
1009
|
+
throw new WassistSignatureVerificationError({
|
|
1010
|
+
message: `Invalid timestamp in X-Wassist-Signature: ${parts.t}`,
|
|
1011
|
+
header
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
if (Math.abs(now - ts) > tolerance) {
|
|
1015
|
+
throw new WassistSignatureVerificationError({
|
|
1016
|
+
message: `Webhook timestamp is outside the ${tolerance}s tolerance window (received ${ts}, now ${now}).`,
|
|
1017
|
+
header
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
parseEvent(payload) {
|
|
1023
|
+
const text = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
1024
|
+
try {
|
|
1025
|
+
return JSON.parse(text);
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
throw new WassistSignatureVerificationError({
|
|
1028
|
+
message: "Failed to parse webhook payload as JSON.",
|
|
1029
|
+
payload: text,
|
|
1030
|
+
cause: err
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
function parseHeader(header) {
|
|
1036
|
+
const parts = {};
|
|
1037
|
+
for (const segment of header.split(",")) {
|
|
1038
|
+
const eq = segment.indexOf("=");
|
|
1039
|
+
if (eq === -1) continue;
|
|
1040
|
+
const key = segment.slice(0, eq).trim();
|
|
1041
|
+
const value = segment.slice(eq + 1).trim();
|
|
1042
|
+
if (key === "t" || key === "v1") parts[key] = value;
|
|
1043
|
+
}
|
|
1044
|
+
return parts;
|
|
1045
|
+
}
|
|
1046
|
+
function constantTimeEqualsHex(a, b) {
|
|
1047
|
+
if (a.length !== b.length) return false;
|
|
1048
|
+
let diff = 0;
|
|
1049
|
+
for (let i = 0; i < a.length; i++) {
|
|
1050
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1051
|
+
}
|
|
1052
|
+
return diff === 0;
|
|
1053
|
+
}
|
|
1054
|
+
function toHex(bytes) {
|
|
1055
|
+
let out = "";
|
|
1056
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1057
|
+
const v = bytes[i] ?? 0;
|
|
1058
|
+
out += v.toString(16).padStart(2, "0");
|
|
1059
|
+
}
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
var webhooks = new Webhooks();
|
|
1063
|
+
var cachedNodeCrypto;
|
|
1064
|
+
function loadNodeCrypto() {
|
|
1065
|
+
if (cachedNodeCrypto !== void 0) return cachedNodeCrypto ?? void 0;
|
|
1066
|
+
try {
|
|
1067
|
+
const req = typeof __sdkRequire === "function" ? __sdkRequire : globalThis.require;
|
|
1068
|
+
cachedNodeCrypto = req?.("node:crypto") ?? null;
|
|
1069
|
+
} catch {
|
|
1070
|
+
cachedNodeCrypto = null;
|
|
1071
|
+
}
|
|
1072
|
+
return cachedNodeCrypto ?? void 0;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/client.ts
|
|
1076
|
+
var Wassist = class {
|
|
1077
|
+
/** Create and manage AI agents. */
|
|
1078
|
+
agents;
|
|
1079
|
+
/** Send messages and manage conversations (with nested `messages` resource). */
|
|
1080
|
+
conversations;
|
|
1081
|
+
/** Manage your connected WhatsApp phone numbers and their routing. */
|
|
1082
|
+
phoneNumbers;
|
|
1083
|
+
/** Manage WhatsApp Business Accounts. */
|
|
1084
|
+
whatsappAccounts;
|
|
1085
|
+
/** Manage hosted WhatsApp account-linking sessions. */
|
|
1086
|
+
whatsappLinkSessions;
|
|
1087
|
+
/** Manage WhatsApp message templates. */
|
|
1088
|
+
whatsappTemplates;
|
|
1089
|
+
/** Verify inbound webhook signatures. */
|
|
1090
|
+
webhooks = webhooks;
|
|
1091
|
+
/**
|
|
1092
|
+
* Webhook helpers — usable without instantiating a client.
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* ```ts
|
|
1096
|
+
* const event = Wassist.webhooks.constructEvent(body, header, secret);
|
|
1097
|
+
* ```
|
|
1098
|
+
*/
|
|
1099
|
+
static webhooks = webhooks;
|
|
1100
|
+
constructor(config) {
|
|
1101
|
+
const http = new HttpClient(config);
|
|
1102
|
+
this.agents = new AgentsResource(http);
|
|
1103
|
+
this.conversations = new ConversationsResource(http);
|
|
1104
|
+
this.phoneNumbers = new PhoneNumbersResource(http);
|
|
1105
|
+
this.whatsappAccounts = new WhatsAppAccountsResource(http);
|
|
1106
|
+
this.whatsappLinkSessions = new WhatsAppLinkSessionsResource(http);
|
|
1107
|
+
this.whatsappTemplates = new WhatsAppTemplatesResource(http);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
export { AgentsResource, AutoPaginatedList, ConversationsResource, MessagesResource, PhoneNumbersResource, SDK_VERSION, Wassist, WassistAPIError, WassistAuthenticationError, WassistConflictError, WassistConnectionError, WassistError, WassistInvalidRequestError, WassistNotFoundError, WassistPermissionError, WassistRateLimitError, WassistSignatureVerificationError, WassistTimeoutError, Webhooks, WhatsAppAccountsResource, WhatsAppLinkSessionsResource, WhatsAppTemplatesResource };
|
|
1112
|
+
//# sourceMappingURL=index.js.map
|
|
1113
|
+
//# sourceMappingURL=index.js.map
|