@open-apime/sdk 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,816 @@
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
+ ApiError: () => ApiError,
24
+ Apime: () => Apime,
25
+ ApimeError: () => ApimeError,
26
+ ApimeInstanceClient: () => ApimeInstanceClient,
27
+ ApimeUserClient: () => ApimeUserClient,
28
+ AuthenticationError: () => AuthenticationError,
29
+ ConfigurationError: () => ConfigurationError,
30
+ ConflictError: () => ConflictError,
31
+ ConnectionError: () => ConnectionError,
32
+ InvalidRequestError: () => InvalidRequestError,
33
+ InvalidSignatureError: () => InvalidSignatureError,
34
+ NotFoundError: () => NotFoundError,
35
+ PermissionError: () => PermissionError,
36
+ RateLimitError: () => RateLimitError,
37
+ SessionUnavailableError: () => SessionUnavailableError,
38
+ TimeoutError: () => TimeoutError,
39
+ UnprocessableError: () => UnprocessableError,
40
+ constructEvent: () => constructEvent,
41
+ hasSentry: () => hasSentry,
42
+ verifyWebhookSignature: () => verifyWebhookSignature
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/errors/index.ts
47
+ var ApimeError = class extends Error {
48
+ status;
49
+ body;
50
+ /** Value of the request id header, when the response carried one. */
51
+ requestId;
52
+ constructor(message, options = {}) {
53
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
54
+ this.name = new.target.name;
55
+ this.status = options.status;
56
+ this.body = options.body;
57
+ this.requestId = options.requestId;
58
+ }
59
+ };
60
+ var AuthenticationError = class extends ApimeError {
61
+ };
62
+ var PermissionError = class extends ApimeError {
63
+ };
64
+ var NotFoundError = class extends ApimeError {
65
+ };
66
+ var ConflictError = class extends ApimeError {
67
+ };
68
+ var UnprocessableError = class extends ApimeError {
69
+ };
70
+ var RateLimitError = class extends ApimeError {
71
+ retryAfterMs;
72
+ constructor(message, options = {}) {
73
+ super(message, options);
74
+ this.retryAfterMs = options.retryAfterMs;
75
+ }
76
+ };
77
+ var InvalidRequestError = class extends ApimeError {
78
+ };
79
+ var ApiError = class extends ApimeError {
80
+ };
81
+ var SessionUnavailableError = class extends ApiError {
82
+ };
83
+ var ConnectionError = class extends ApimeError {
84
+ };
85
+ var TimeoutError = class extends ConnectionError {
86
+ };
87
+ var ConfigurationError = class extends ApimeError {
88
+ };
89
+
90
+ // src/observability/sentry.ts
91
+ function findScope() {
92
+ const carrier = globalThis.__SENTRY__;
93
+ if (!carrier?.version) return void 0;
94
+ const versioned = carrier[carrier.version];
95
+ const scope = versioned?.globalScope;
96
+ return typeof scope?.captureException === "function" ? scope : void 0;
97
+ }
98
+ function hasSentry() {
99
+ return findScope() !== void 0;
100
+ }
101
+ function reportToSentry(info) {
102
+ const scope = findScope();
103
+ if (!scope) return;
104
+ scope.captureException(info.error, {
105
+ captureContext: {
106
+ tags: {
107
+ sdk: "open-apime",
108
+ route: info.route,
109
+ method: info.method,
110
+ ...info.status !== void 0 ? { status: String(info.status) } : {}
111
+ },
112
+ extra: {
113
+ // Nao mandamos `path`: ele carrega o jid do contato, ou seja o telefone
114
+ // de quem conversa. `route` ja identifica o endpoint sem isso.
115
+ instanceId: info.instanceId,
116
+ attempt: info.attempt,
117
+ durationMs: info.durationMs,
118
+ idempotencyKey: info.idempotencyKey
119
+ }
120
+ }
121
+ });
122
+ }
123
+
124
+ // src/observability/index.ts
125
+ function safeNotify(fn) {
126
+ if (!fn) return;
127
+ try {
128
+ fn();
129
+ } catch {
130
+ }
131
+ }
132
+ function toRoute(path) {
133
+ return path.replace(/\/instances\/[^/]+/, "/instances/{id}").replace(/\/(profile|business)\/[^/]+\/picture/, "/$1/{jid}/picture").replace(/\/(profile|business|contacts|userinfo|newsletters)\/[^/]+/, "/$1/{jid}").replace(/\/chat-settings\/[^/]+/, "/chat-settings/{chat}").replace(/\/groups\/(?!join$|resolve-invite$)[^/]+/, "/groups/{group}").replace(/\/tokens\/[^/]+$/, "/tokens/{tokenId}").replace(/\/users\/[^/]+(?=\/|$)/, "/users/{id}");
134
+ }
135
+
136
+ // src/http/errors.ts
137
+ function messageFrom(body, status) {
138
+ if (body && typeof body === "object" && "error" in body) {
139
+ const raw = body.error;
140
+ if (typeof raw === "string" && raw.length > 0) return raw;
141
+ }
142
+ return `HTTP ${status}`;
143
+ }
144
+ function retryAfterMs(headers) {
145
+ const raw = headers.get("retry-after");
146
+ if (!raw) return void 0;
147
+ const seconds = Number(raw);
148
+ if (Number.isFinite(seconds)) return seconds * 1e3;
149
+ const date = Date.parse(raw);
150
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
151
+ }
152
+ function errorFromResponse(status, body, headers) {
153
+ const message = messageFrom(body, status);
154
+ const requestId = headers.get("x-request-id") ?? void 0;
155
+ const base = { status, body, ...requestId !== void 0 ? { requestId } : {} };
156
+ switch (status) {
157
+ case 401:
158
+ return new AuthenticationError(message, base);
159
+ case 403:
160
+ return new PermissionError(message, base);
161
+ case 404:
162
+ return new NotFoundError(message, base);
163
+ case 409:
164
+ return new ConflictError(message, base);
165
+ case 422:
166
+ return new UnprocessableError(message, base);
167
+ case 429: {
168
+ const after = retryAfterMs(headers);
169
+ return new RateLimitError(message, { ...base, ...after !== void 0 ? { retryAfterMs: after } : {} });
170
+ }
171
+ case 503:
172
+ return new SessionUnavailableError(message, base);
173
+ default:
174
+ if (status >= 500) return new ApiError(message, base);
175
+ return new InvalidRequestError(message, base);
176
+ }
177
+ }
178
+
179
+ // src/http/url.ts
180
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set(["169.254.169.254", "metadata.google.internal"]);
181
+ function assertUsableBaseUrl(raw) {
182
+ let parsed;
183
+ try {
184
+ parsed = new URL(raw);
185
+ } catch {
186
+ throw new ConfigurationError(`baseUrl inv\xE1lida: ${raw}`);
187
+ }
188
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
189
+ throw new ConfigurationError(`baseUrl deve usar http ou https, veio ${parsed.protocol}`);
190
+ }
191
+ if (BLOCKED_HOSTS.has(parsed.hostname)) {
192
+ throw new ConfigurationError("baseUrl aponta para endpoint de metadados da nuvem");
193
+ }
194
+ if (isPrivateIPv4(parsed.hostname)) {
195
+ throw new ConfigurationError("baseUrl aponta para IP privado ou loopback");
196
+ }
197
+ return parsed;
198
+ }
199
+ function isPrivateIPv4(host) {
200
+ const parts = host.split(".");
201
+ if (parts.length !== 4) return false;
202
+ const nums = parts.map(Number);
203
+ if (nums.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return false;
204
+ const [a = 0, b = 0] = nums;
205
+ return a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
206
+ }
207
+ function pathSegment(value) {
208
+ return encodeURIComponent(value);
209
+ }
210
+ function buildUrl(base, path, query) {
211
+ const prefix = base.pathname.replace(/\/+$/, "");
212
+ const url = new URL(`${prefix}/api${path}`, base);
213
+ if (query) {
214
+ for (const [key, value] of Object.entries(query)) {
215
+ if (value !== void 0) url.searchParams.set(key, String(value));
216
+ }
217
+ }
218
+ return url.toString();
219
+ }
220
+
221
+ // src/http/client.ts
222
+ var DEFAULT_TIMEOUT_MS = 12e4;
223
+ var DEFAULT_MAX_RETRIES = 2;
224
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
225
+ var HttpClient = class {
226
+ baseUrl;
227
+ auth;
228
+ timeoutMs;
229
+ maxRetries;
230
+ fetchImpl;
231
+ userAgent;
232
+ observer;
233
+ autoReport;
234
+ instanceId;
235
+ constructor(options) {
236
+ this.baseUrl = assertUsableBaseUrl(options.baseUrl);
237
+ this.auth = options.auth;
238
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
239
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
240
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
241
+ this.userAgent = options.appName ? `open-apime-sdk (${options.appName})` : "open-apime-sdk";
242
+ this.observer = options.observer;
243
+ this.autoReport = options.autoReport ?? true;
244
+ this.instanceId = options.auth.type === "instanceToken" ? options.auth.instanceId : void 0;
245
+ }
246
+ info(args, attempt) {
247
+ return {
248
+ method: args.method,
249
+ route: toRoute(args.path),
250
+ path: args.path,
251
+ instanceId: this.instanceId,
252
+ attempt: attempt + 1,
253
+ idempotencyKey: args.options?.idempotencyKey
254
+ };
255
+ }
256
+ async request(args) {
257
+ const attempts = (args.options?.maxRetries ?? this.maxRetries) + 1;
258
+ let lastError;
259
+ for (let attempt = 0; attempt < attempts; attempt++) {
260
+ const info = this.info(args, attempt);
261
+ const startedAt = Date.now();
262
+ safeNotify(this.observer?.onRequest && (() => this.observer?.onRequest?.(info)));
263
+ try {
264
+ const result = await this.attempt(args);
265
+ const durationMs = Date.now() - startedAt;
266
+ safeNotify(this.observer?.onSuccess && (() => this.observer?.onSuccess?.({ ...info, status: 200, durationMs })));
267
+ return result;
268
+ } catch (error) {
269
+ lastError = error;
270
+ const isLast = attempt === attempts - 1;
271
+ const retryable = !isLast && this.mayRetry(args, error);
272
+ const failure = {
273
+ ...info,
274
+ status: error instanceof ApimeError ? error.status : void 0,
275
+ durationMs: Date.now() - startedAt,
276
+ error,
277
+ willRetry: retryable
278
+ };
279
+ if (this.observer?.onError) {
280
+ safeNotify(() => this.observer?.onError?.(failure));
281
+ } else if (this.autoReport && !retryable) {
282
+ safeNotify(() => reportToSentry(failure));
283
+ }
284
+ if (isLast || !retryable) break;
285
+ const delayMs = backoffMs(attempt, error);
286
+ safeNotify(this.observer?.onRetry && (() => this.observer?.onRetry?.({ ...failure, delayMs })));
287
+ await sleep(delayMs, args.options?.signal);
288
+ }
289
+ }
290
+ throw lastError;
291
+ }
292
+ /**
293
+ * A write is only retried when the caller passed an idempotency key: without
294
+ * it a retried send can deliver the same WhatsApp message twice. The one
295
+ * exception is 503, which the apime returns before touching WhatsApp.
296
+ */
297
+ mayRetry(args, error) {
298
+ if (!(error instanceof ApimeError)) return false;
299
+ if (error instanceof TimeoutError) return this.writeIsSafe(args, error);
300
+ if (error instanceof ConnectionError) return this.writeIsSafe(args, error);
301
+ if (error.status === void 0) return false;
302
+ if (!RETRYABLE_STATUS.has(error.status)) return false;
303
+ return this.writeIsSafe(args, error);
304
+ }
305
+ writeIsSafe(args, error) {
306
+ if (args.method === "GET") return true;
307
+ if (args.options?.idempotencyKey) return true;
308
+ return error instanceof ApimeError && error.status === 503;
309
+ }
310
+ async attempt(args) {
311
+ const url = buildUrl(this.baseUrl, args.path, args.query);
312
+ const timeoutMs = args.options?.timeoutMs ?? this.timeoutMs;
313
+ const controller = new AbortController();
314
+ const onAbort = () => controller.abort();
315
+ args.options?.signal?.addEventListener("abort", onAbort, { once: true });
316
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
317
+ const headers = {
318
+ authorization: `Bearer ${this.auth.token}`,
319
+ accept: "application/json",
320
+ "user-agent": this.userAgent,
321
+ ...args.options?.headers
322
+ };
323
+ if (args.options?.idempotencyKey) headers["idempotency-key"] = args.options.idempotencyKey;
324
+ let init = { method: args.method, headers, signal: controller.signal };
325
+ if (args.body instanceof FormData) {
326
+ init = { ...init, body: args.body };
327
+ } else if (args.body !== void 0) {
328
+ headers["content-type"] = "application/json";
329
+ init = { ...init, body: JSON.stringify(args.body) };
330
+ }
331
+ let response;
332
+ try {
333
+ response = await this.fetchImpl(url, init);
334
+ } catch (cause) {
335
+ if (controller.signal.aborted && args.options?.signal?.aborted !== true) {
336
+ throw new TimeoutError(`a requisi\xE7\xE3o passou de ${timeoutMs}ms`, { cause });
337
+ }
338
+ throw new ConnectionError("falha de rede ao falar com o apime", { cause });
339
+ } finally {
340
+ clearTimeout(timer);
341
+ args.options?.signal?.removeEventListener("abort", onAbort);
342
+ }
343
+ const payload = await readBody(response);
344
+ if (!response.ok) throw errorFromResponse(response.status, payload, response.headers);
345
+ return unwrap(payload);
346
+ }
347
+ };
348
+ function unwrap(body) {
349
+ if (body && typeof body === "object" && "data" in body) {
350
+ return body.data;
351
+ }
352
+ return body;
353
+ }
354
+ async function readBody(response) {
355
+ const text = await response.text();
356
+ if (text.length === 0) return void 0;
357
+ try {
358
+ return JSON.parse(text);
359
+ } catch {
360
+ return text;
361
+ }
362
+ }
363
+ function backoffMs(attempt, error) {
364
+ if (error instanceof RateLimitError && error.retryAfterMs !== void 0) {
365
+ return error.retryAfterMs;
366
+ }
367
+ const ceiling = Math.min(1e3 * 2 ** attempt, 8e3);
368
+ return Math.random() * ceiling;
369
+ }
370
+ function sleep(ms, signal) {
371
+ return new Promise((resolve, reject) => {
372
+ if (signal?.aborted) return reject(new ConnectionError("requisi\xE7\xE3o cancelada"));
373
+ const timer = setTimeout(resolve, ms);
374
+ signal?.addEventListener("abort", () => {
375
+ clearTimeout(timer);
376
+ reject(new ConnectionError("requisi\xE7\xE3o cancelada"));
377
+ }, { once: true });
378
+ });
379
+ }
380
+
381
+ // src/resources/instances/index.ts
382
+ var InstancesResource = class {
383
+ constructor(http) {
384
+ this.http = http;
385
+ }
386
+ http;
387
+ list(options) {
388
+ return this.http.request({ method: "GET", path: "/instances", ...options ? { options } : {} });
389
+ }
390
+ create(input, options) {
391
+ return this.http.request({ method: "POST", path: "/instances", body: input, ...options ? { options } : {} });
392
+ }
393
+ get(instanceId, options) {
394
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(instanceId)}`, ...options ? { options } : {} });
395
+ }
396
+ update(instanceId, input, options) {
397
+ return this.http.request({ method: "PUT", path: `/instances/${pathSegment(instanceId)}`, body: input, ...options ? { options } : {} });
398
+ }
399
+ delete(instanceId, options) {
400
+ return this.http.request({ method: "DELETE", path: `/instances/${pathSegment(instanceId)}`, ...options ? { options } : {} });
401
+ }
402
+ rotateToken(instanceId, options) {
403
+ return this.http.request({ method: "POST", path: `/instances/${pathSegment(instanceId)}/token/rotate`, ...options ? { options } : {} });
404
+ }
405
+ getQrCode(instanceId, options) {
406
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(instanceId)}/qr`, ...options ? { options } : {} });
407
+ }
408
+ disconnect(instanceId, options) {
409
+ return this.http.request({ method: "POST", path: `/instances/${pathSegment(instanceId)}/disconnect`, ...options ? { options } : {} });
410
+ }
411
+ listEvents(instanceId, query, options) {
412
+ return this.http.request({
413
+ method: "GET",
414
+ path: `/instances/${pathSegment(instanceId)}/events`,
415
+ ...query ? { query } : {},
416
+ ...options ? { options } : {}
417
+ });
418
+ }
419
+ };
420
+ var ScopedInstanceResource = class {
421
+ constructor(http, instanceId) {
422
+ this.http = http;
423
+ this.instanceId = instanceId;
424
+ }
425
+ http;
426
+ instanceId;
427
+ get(options) {
428
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(this.instanceId)}`, ...options ? { options } : {} });
429
+ }
430
+ /** Only reachable with an instance token; a user token gets a 403. */
431
+ info(options) {
432
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(this.instanceId)}/info`, ...options ? { options } : {} });
433
+ }
434
+ getQrCode(options) {
435
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(this.instanceId)}/qr`, ...options ? { options } : {} });
436
+ }
437
+ disconnect(options) {
438
+ return this.http.request({ method: "POST", path: `/instances/${pathSegment(this.instanceId)}/disconnect`, ...options ? { options } : {} });
439
+ }
440
+ listEvents(query, options) {
441
+ return this.http.request({
442
+ method: "GET",
443
+ path: `/instances/${pathSegment(this.instanceId)}/events`,
444
+ ...query ? { query } : {},
445
+ ...options ? { options } : {}
446
+ });
447
+ }
448
+ getProfile(jid, options) {
449
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(this.instanceId)}/profile/${pathSegment(jid)}`, ...options ? { options } : {} });
450
+ }
451
+ getBusinessProfile(jid, options) {
452
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(this.instanceId)}/business/${pathSegment(jid)}`, ...options ? { options } : {} });
453
+ }
454
+ getProfilePicture(jid, options) {
455
+ return this.http.request({ method: "GET", path: `/instances/${pathSegment(this.instanceId)}/profile/${pathSegment(jid)}/picture`, ...options ? { options } : {} });
456
+ }
457
+ };
458
+
459
+ // src/resources/messages/index.ts
460
+ var MessagesResource = class {
461
+ constructor(http, instanceId) {
462
+ this.http = http;
463
+ this.instanceId = instanceId;
464
+ }
465
+ http;
466
+ instanceId;
467
+ get base() {
468
+ return `/instances/${pathSegment(this.instanceId)}/messages`;
469
+ }
470
+ sendText(input, options) {
471
+ return this.http.request({ method: "POST", path: `${this.base}/text`, body: input, ...options ? { options } : {} });
472
+ }
473
+ sendMedia(input, options) {
474
+ const form = toForm(input, ["file", "filename"]);
475
+ form.set("file", input.file, input.filename ?? fileName(input.file, "media"));
476
+ return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
477
+ }
478
+ sendAudio(input, options) {
479
+ const form = toForm(input, ["file", "filename"]);
480
+ form.set("file", input.file, input.filename ?? fileName(input.file, "audio.ogg"));
481
+ return this.http.request({ method: "POST", path: `${this.base}/audio`, body: form, ...options ? { options } : {} });
482
+ }
483
+ sendDocument(input, options) {
484
+ const form = toForm(input, ["file"]);
485
+ form.set("file", input.file, input.filename ?? fileName(input.file, "document"));
486
+ return this.http.request({ method: "POST", path: `${this.base}/document`, body: form, ...options ? { options } : {} });
487
+ }
488
+ sendContact(input, options) {
489
+ return this.http.request({ method: "POST", path: `${this.base}/contact`, body: input, ...options ? { options } : {} });
490
+ }
491
+ sendLocation(input, options) {
492
+ return this.http.request({ method: "POST", path: `${this.base}/location`, body: input, ...options ? { options } : {} });
493
+ }
494
+ /** Generic enqueue, for a type the SDK does not model yet. */
495
+ enqueue(input, options) {
496
+ return this.http.request({ method: "POST", path: this.base, body: input, ...options ? { options } : {} });
497
+ }
498
+ list(options) {
499
+ return this.http.request({ method: "GET", path: this.base, ...options ? { options } : {} });
500
+ }
501
+ };
502
+ function toForm(input, skip) {
503
+ const form = new FormData();
504
+ for (const [key, value] of Object.entries(input)) {
505
+ if (skip.includes(key) || value === void 0 || value === null) continue;
506
+ form.set(key, typeof value === "string" ? value : String(value));
507
+ }
508
+ return form;
509
+ }
510
+ function fileName(file, fallback) {
511
+ return "name" in file && typeof file.name === "string" && file.name.length > 0 ? file.name : fallback;
512
+ }
513
+
514
+ // src/resources/tokens/index.ts
515
+ var TokensResource = class {
516
+ constructor(http) {
517
+ this.http = http;
518
+ }
519
+ http;
520
+ list(options) {
521
+ return this.http.request({ method: "GET", path: "/tokens", ...options ? { options } : {} });
522
+ }
523
+ create(input, options) {
524
+ return this.http.request({ method: "POST", path: "/tokens", body: input, ...options ? { options } : {} });
525
+ }
526
+ delete(tokenId, options) {
527
+ return this.http.request({ method: "DELETE", path: `/tokens/${pathSegment(tokenId)}`, ...options ? { options } : {} });
528
+ }
529
+ };
530
+
531
+ // src/resources/users/index.ts
532
+ var UsersResource = class {
533
+ constructor(http) {
534
+ this.http = http;
535
+ }
536
+ http;
537
+ list(options) {
538
+ return this.http.request({ method: "GET", path: "/users", ...options ? { options } : {} });
539
+ }
540
+ create(input, options) {
541
+ return this.http.request({ method: "POST", path: "/users", body: input, ...options ? { options } : {} });
542
+ }
543
+ updatePassword(userId, password, options) {
544
+ return this.http.request({ method: "PATCH", path: `/users/${pathSegment(userId)}/password`, body: { password }, ...options ? { options } : {} });
545
+ }
546
+ rotateApiToken(userId, options) {
547
+ return this.http.request({ method: "POST", path: `/users/${pathSegment(userId)}/token`, ...options ? { options } : {} });
548
+ }
549
+ delete(userId, options) {
550
+ return this.http.request({ method: "DELETE", path: `/users/${pathSegment(userId)}`, ...options ? { options } : {} });
551
+ }
552
+ };
553
+
554
+ // src/resources/whatsapp/index.ts
555
+ var WhatsAppResource = class {
556
+ constructor(http, instanceId) {
557
+ this.http = http;
558
+ this.instanceId = instanceId;
559
+ this.groups = new GroupsResource(http, instanceId);
560
+ this.newsletters = new NewslettersResource(http, instanceId);
561
+ }
562
+ http;
563
+ instanceId;
564
+ groups;
565
+ newsletters;
566
+ get base() {
567
+ return `/instances/${pathSegment(this.instanceId)}/whatsapp`;
568
+ }
569
+ checkNumber(phone, options) {
570
+ return this.http.request({ method: "POST", path: `${this.base}/check`, body: { phone }, ...options ? { options } : {} });
571
+ }
572
+ /** `to` is optional: without it the presence is global, not per chat. */
573
+ setPresence(input, options) {
574
+ return this.http.request({ method: "POST", path: `${this.base}/presence`, body: input, ...options ? { options } : {} });
575
+ }
576
+ listContacts(options) {
577
+ return this.http.request({ method: "GET", path: `${this.base}/contacts`, ...options ? { options } : {} });
578
+ }
579
+ getContact(jid, options) {
580
+ return this.http.request({ method: "GET", path: `${this.base}/contacts/${pathSegment(jid)}`, ...options ? { options } : {} });
581
+ }
582
+ getUserInfo(jid, options) {
583
+ return this.http.request({ method: "GET", path: `${this.base}/userinfo/${pathSegment(jid)}`, ...options ? { options } : {} });
584
+ }
585
+ /** `sender` is required in groups, to identify whose message it is. */
586
+ markRead(input, options) {
587
+ return this.http.request({ method: "POST", path: `${this.base}/messages/read`, body: input, ...options ? { options } : {} });
588
+ }
589
+ deleteMessage(input, options) {
590
+ return this.http.request({ method: "POST", path: `${this.base}/messages/delete`, body: input, ...options ? { options } : {} });
591
+ }
592
+ editMessage(input, options) {
593
+ return this.http.request({ method: "POST", path: `${this.base}/messages/edit`, body: input, ...options ? { options } : {} });
594
+ }
595
+ /** An empty `emoji` removes the reaction. */
596
+ react(input, options) {
597
+ return this.http.request({ method: "POST", path: `${this.base}/messages/react`, body: input, ...options ? { options } : {} });
598
+ }
599
+ getPrivacySettings(options) {
600
+ return this.http.request({ method: "GET", path: `${this.base}/privacy`, ...options ? { options } : {} });
601
+ }
602
+ setPrivacySetting(input, options) {
603
+ return this.http.request({ method: "POST", path: `${this.base}/privacy`, body: input, ...options ? { options } : {} });
604
+ }
605
+ getStatusPrivacy(options) {
606
+ return this.http.request({ method: "GET", path: `${this.base}/status-privacy`, ...options ? { options } : {} });
607
+ }
608
+ getChatSettings(chat, options) {
609
+ return this.http.request({ method: "GET", path: `${this.base}/chat-settings/${pathSegment(chat)}`, ...options ? { options } : {} });
610
+ }
611
+ /** `muted_until` as null unmutes. */
612
+ setChatSettings(chat, input, options) {
613
+ return this.http.request({ method: "POST", path: `${this.base}/chat-settings/${pathSegment(chat)}`, body: input, ...options ? { options } : {} });
614
+ }
615
+ setStatusMessage(message, options) {
616
+ return this.http.request({ method: "POST", path: `${this.base}/status`, body: { message }, ...options ? { options } : {} });
617
+ }
618
+ /** `seconds` as 0 turns disappearing messages off. */
619
+ setDefaultDisappearingTimer(seconds, options) {
620
+ return this.http.request({ method: "POST", path: `${this.base}/disappearing-timer`, body: { seconds }, ...options ? { options } : {} });
621
+ }
622
+ uploadMedia(input, options) {
623
+ return this.http.request({ method: "POST", path: `${this.base}/upload`, body: input, ...options ? { options } : {} });
624
+ }
625
+ /** `revoke` invalidates the previous link and issues another. */
626
+ getContactQrLink(query, options) {
627
+ return this.http.request({
628
+ method: "GET",
629
+ path: `${this.base}/qr/contact`,
630
+ ...query?.revoke !== void 0 ? { query: { revoke: String(query.revoke) } } : {},
631
+ ...options ? { options } : {}
632
+ });
633
+ }
634
+ resolveContactQrLink(code, options) {
635
+ return this.http.request({ method: "POST", path: `${this.base}/qr/contact/resolve`, body: { code }, ...options ? { options } : {} });
636
+ }
637
+ resolveBusinessMessageLink(code, options) {
638
+ return this.http.request({ method: "POST", path: `${this.base}/qr/business-message/resolve`, body: { code }, ...options ? { options } : {} });
639
+ }
640
+ };
641
+ var GroupsResource = class {
642
+ constructor(http, instanceId) {
643
+ this.http = http;
644
+ this.instanceId = instanceId;
645
+ }
646
+ http;
647
+ instanceId;
648
+ get base() {
649
+ return `/instances/${pathSegment(this.instanceId)}/whatsapp/groups`;
650
+ }
651
+ list(options) {
652
+ return this.http.request({ method: "GET", path: this.base, ...options ? { options } : {} });
653
+ }
654
+ create(input, options) {
655
+ return this.http.request({ method: "POST", path: this.base, body: input, ...options ? { options } : {} });
656
+ }
657
+ get(group, options) {
658
+ return this.http.request({ method: "GET", path: `${this.base}/${pathSegment(group)}`, ...options ? { options } : {} });
659
+ }
660
+ getInviteLink(group, options) {
661
+ return this.http.request({ method: "GET", path: `${this.base}/${pathSegment(group)}/invite-link`, ...options ? { options } : {} });
662
+ }
663
+ resolveInvite(link, options) {
664
+ return this.http.request({ method: "POST", path: `${this.base}/resolve-invite`, body: { link }, ...options ? { options } : {} });
665
+ }
666
+ join(link, options) {
667
+ return this.http.request({ method: "POST", path: `${this.base}/join`, body: { link }, ...options ? { options } : {} });
668
+ }
669
+ leave(group, options) {
670
+ return this.http.request({ method: "POST", path: `${this.base}/${pathSegment(group)}/leave`, ...options ? { options } : {} });
671
+ }
672
+ updateParticipants(group, input, options) {
673
+ return this.http.request({ method: "POST", path: `${this.base}/${pathSegment(group)}/participants`, body: input, ...options ? { options } : {} });
674
+ }
675
+ listJoinRequests(group, options) {
676
+ return this.http.request({ method: "GET", path: `${this.base}/${pathSegment(group)}/requests`, ...options ? { options } : {} });
677
+ }
678
+ updateJoinRequests(group, input, options) {
679
+ return this.http.request({ method: "POST", path: `${this.base}/${pathSegment(group)}/requests`, body: input, ...options ? { options } : {} });
680
+ }
681
+ };
682
+ var NewslettersResource = class {
683
+ constructor(http, instanceId) {
684
+ this.http = http;
685
+ this.instanceId = instanceId;
686
+ }
687
+ http;
688
+ instanceId;
689
+ base(jid) {
690
+ return `/instances/${pathSegment(this.instanceId)}/whatsapp/newsletters/${pathSegment(jid)}`;
691
+ }
692
+ subscribeLiveUpdates(jid, options) {
693
+ return this.http.request({ method: "POST", path: `${this.base(jid)}/live-updates`, ...options ? { options } : {} });
694
+ }
695
+ markViewed(jid, serverIds, options) {
696
+ return this.http.request({ method: "POST", path: `${this.base(jid)}/mark-viewed`, body: { server_ids: serverIds }, ...options ? { options } : {} });
697
+ }
698
+ react(jid, input, options) {
699
+ return this.http.request({ method: "POST", path: `${this.base(jid)}/reaction`, body: input, ...options ? { options } : {} });
700
+ }
701
+ getMessageUpdates(jid, options) {
702
+ return this.http.request({ method: "POST", path: `${this.base(jid)}/message-updates`, ...options ? { options } : {} });
703
+ }
704
+ };
705
+
706
+ // src/client.ts
707
+ var ApimeUserClient = class {
708
+ instances;
709
+ /** Requires the user to have role admin; other users get a 403. */
710
+ users;
711
+ tokens;
712
+ http;
713
+ constructor(auth, options) {
714
+ this.http = new HttpClient(toHttpOptions(auth, options));
715
+ this.instances = new InstancesResource(this.http);
716
+ this.users = new UsersResource(this.http);
717
+ this.tokens = new TokensResource(this.http);
718
+ }
719
+ /** Escape hatch for a route the SDK does not cover yet. */
720
+ request(args) {
721
+ return this.http.request(args);
722
+ }
723
+ };
724
+ var ApimeInstanceClient = class {
725
+ instanceId;
726
+ instance;
727
+ messages;
728
+ whatsapp;
729
+ http;
730
+ constructor(auth, options) {
731
+ this.http = new HttpClient(toHttpOptions(auth, options));
732
+ this.instanceId = auth.instanceId;
733
+ this.instance = new ScopedInstanceResource(this.http, auth.instanceId);
734
+ this.messages = new MessagesResource(this.http, auth.instanceId);
735
+ this.whatsapp = new WhatsAppResource(this.http, auth.instanceId);
736
+ }
737
+ request(args) {
738
+ return this.http.request(args);
739
+ }
740
+ };
741
+ function toHttpOptions(auth, options) {
742
+ return {
743
+ baseUrl: options.baseUrl,
744
+ auth,
745
+ ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
746
+ ...options.maxRetries !== void 0 ? { maxRetries: options.maxRetries } : {},
747
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
748
+ ...options.appName !== void 0 ? { appName: options.appName } : {},
749
+ ...options.observer !== void 0 ? { observer: options.observer } : {},
750
+ ...options.autoReport !== void 0 ? { autoReport: options.autoReport } : {}
751
+ };
752
+ }
753
+ var Apime = {
754
+ withUserJwt(token, options) {
755
+ return new ApimeUserClient({ type: "userJwt", token }, options);
756
+ },
757
+ withApiToken(token, options) {
758
+ return new ApimeUserClient({ type: "apiToken", token }, options);
759
+ },
760
+ withInstanceToken(credentials, options) {
761
+ return new ApimeInstanceClient(
762
+ { type: "instanceToken", token: credentials.token, instanceId: credentials.instanceId },
763
+ options
764
+ );
765
+ }
766
+ };
767
+
768
+ // src/webhook/index.ts
769
+ var import_node_crypto = require("crypto");
770
+ var InvalidSignatureError = class extends ApimeError {
771
+ };
772
+ function verifyWebhookSignature(rawBody, signature, secret) {
773
+ if (!signature || !secret) return false;
774
+ const body = typeof rawBody === "string" ? Buffer.from(rawBody, "utf8") : rawBody;
775
+ const expected = `sha256=${(0, import_node_crypto.createHmac)("sha256", secret).update(body).digest("hex")}`;
776
+ const received = Buffer.from(signature, "utf8");
777
+ const digest = Buffer.from(expected, "utf8");
778
+ if (received.length !== digest.length) return false;
779
+ return (0, import_node_crypto.timingSafeEqual)(received, digest);
780
+ }
781
+ function constructEvent(rawBody, signature, secret) {
782
+ if (!verifyWebhookSignature(rawBody, signature, secret)) {
783
+ throw new InvalidSignatureError("assinatura do webhook n\xE3o confere");
784
+ }
785
+ const text = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
786
+ let parsed;
787
+ try {
788
+ parsed = JSON.parse(text);
789
+ } catch (cause) {
790
+ throw new InvalidSignatureError("corpo do webhook n\xE3o \xE9 JSON v\xE1lido", { cause });
791
+ }
792
+ return parsed;
793
+ }
794
+ // Annotate the CommonJS export names for ESM import in node:
795
+ 0 && (module.exports = {
796
+ ApiError,
797
+ Apime,
798
+ ApimeError,
799
+ ApimeInstanceClient,
800
+ ApimeUserClient,
801
+ AuthenticationError,
802
+ ConfigurationError,
803
+ ConflictError,
804
+ ConnectionError,
805
+ InvalidRequestError,
806
+ InvalidSignatureError,
807
+ NotFoundError,
808
+ PermissionError,
809
+ RateLimitError,
810
+ SessionUnavailableError,
811
+ TimeoutError,
812
+ UnprocessableError,
813
+ constructEvent,
814
+ hasSentry,
815
+ verifyWebhookSignature
816
+ });