@open-apime/sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,8 +32,8 @@ await conexao.messages.sendText(
32
32
 
33
33
  | | |
34
34
  |---|---|
35
- | [Guia de uso](https://github.com/open-apime/sdk/blob/main/docs/guia.md) | clientes, envio, erros, retry, observabilidade e webhook |
36
- | [Segurança](https://github.com/open-apime/sdk/blob/main/docs/seguranca.md) | o que o SDK protege, e como |
35
+ | [Guia de uso](docs/guia.md) | clientes, envio, erros, retry, observabilidade e webhook |
36
+ | [Segurança](docs/seguranca.md) | o que o SDK protege, e como |
37
37
  | [openapi.yaml](https://github.com/open-apime/apime/blob/main/openapi.yaml) | contrato da API |
38
38
  | [webhook-payloads.md](https://github.com/open-apime/apime/blob/main/docs/webhook-payloads.md) | os 12 eventos |
39
39
 
package/dist/index.cjs CHANGED
@@ -490,6 +490,18 @@ var MessagesResource = class {
490
490
  form.set("file", input.file, input.filename ?? fileName(input.file, "media"));
491
491
  return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
492
492
  }
493
+ sendGif(input, options) {
494
+ const form = toForm(input, ["file", "filename"]);
495
+ form.set("type", "gif");
496
+ form.set("file", input.file, input.filename ?? fileName(input.file, "animation.mp4"));
497
+ return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
498
+ }
499
+ sendSticker(input, options) {
500
+ const form = toForm(input, ["file", "filename"]);
501
+ form.set("type", "sticker");
502
+ form.set("file", input.file, input.filename ?? fileName(input.file, "sticker.webp"));
503
+ return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
504
+ }
493
505
  sendAudio(input, options) {
494
506
  const form = toForm(input, ["file", "filename"]);
495
507
  form.set("file", input.file, input.filename ?? fileName(input.file, "audio.ogg"));
package/dist/index.d.cts CHANGED
@@ -230,6 +230,32 @@ interface SendMediaInput extends QuoteInput, MarkReadInput {
230
230
  filename?: string;
231
231
  caption?: string;
232
232
  }
233
+ /**
234
+ * A GIF on WhatsApp is an MP4 VIDEO carrying the gif-playback flag, which is what makes the client
235
+ * loop it with no controls. Sending a real `.gif` file arrives as a still image, so it gets a method
236
+ * of its own rather than a `type` on sendMedia: the name is what warns the caller at the call site.
237
+ */
238
+ interface SendGifInput extends QuoteInput, MarkReadInput {
239
+ to: string;
240
+ /** MP4 bytes. A `.gif` file is NOT what WhatsApp expects here. */
241
+ file: Blob | File;
242
+ filename?: string;
243
+ caption?: string;
244
+ }
245
+ /**
246
+ * WhatsApp takes stickers as WebP 512x512, up to 500 KB, transparent background; animated ones are
247
+ * animated WebP. The server validates and refuses outside that, because a sticker off-spec is
248
+ * accepted by the wire and then fails to render, with nothing reporting it.
249
+ *
250
+ * There is no caption: the protocol has no such field on a sticker, so one would be dropped in
251
+ * silence. That is why this does not reuse `SendMediaInput`.
252
+ */
253
+ interface SendStickerInput extends QuoteInput, MarkReadInput {
254
+ to: string;
255
+ /** WebP bytes, 512x512, up to 500 KB. */
256
+ file: Blob | File;
257
+ filename?: string;
258
+ }
233
259
  interface SendAudioInput extends QuoteInput, MarkReadInput {
234
260
  to: string;
235
261
  file: Blob | File;
@@ -275,6 +301,8 @@ declare class MessagesResource {
275
301
  private get base();
276
302
  sendText(input: SendTextInput, options?: RequestOptions): Promise<SentMessage>;
277
303
  sendMedia(input: SendMediaInput, options?: RequestOptions): Promise<SentMessage>;
304
+ sendGif(input: SendGifInput, options?: RequestOptions): Promise<SentMessage>;
305
+ sendSticker(input: SendStickerInput, options?: RequestOptions): Promise<SentMessage>;
278
306
  sendAudio(input: SendAudioInput, options?: RequestOptions): Promise<SentMessage>;
279
307
  sendDocument(input: SendDocumentInput, options?: RequestOptions): Promise<SentMessage>;
280
308
  sendContact(input: SendContactInput, options?: RequestOptions): Promise<SentMessage>;
@@ -535,4 +563,4 @@ interface HealthResult {
535
563
  declare const DEFAULT_HEALTH_TIMEOUT_MS = 10000;
536
564
  declare function checkHealth(baseUrl: string, options?: HealthOptions): Promise<HealthResult>;
537
565
 
538
- export { type ApiToken, type ApiTokenAuth, Apime, ApimeError, ApimeInstanceClient, ApimeUserClient, type Auth, type AuthKind, type CheckNumberResult, type ClientOptions, type ContactEntry, type CreateInstanceInput, DEFAULT_HEALTH_TIMEOUT_MS, EventLogEntry, type FailureInfo, type GroupInfo, type GroupParticipant, type HealthOptions, type HealthResult, Instance, InstanceInfo, type InstanceTokenAuth, type IpFamily, type JoinRequestAction, type MarkReadInput, type Observer, type ParticipantAction, type PresenceState, Profile, QrCode, type QuoteInput, type RequestInfo, type RequestOptions, type SendAudioInput, type SendContactInput, type SendDocumentInput, type SendLocationInput, type SendMediaInput, type SendTextInput, SentMessage, type SuccessInfo, type UpdateInstanceInput, type User, type UserAuth, type UserJwtAuth, checkHealth, hasSentry };
566
+ export { type ApiToken, type ApiTokenAuth, Apime, ApimeError, ApimeInstanceClient, ApimeUserClient, type Auth, type AuthKind, type CheckNumberResult, type ClientOptions, type ContactEntry, type CreateInstanceInput, DEFAULT_HEALTH_TIMEOUT_MS, EventLogEntry, type FailureInfo, type GroupInfo, type GroupParticipant, type HealthOptions, type HealthResult, Instance, InstanceInfo, type InstanceTokenAuth, type IpFamily, type JoinRequestAction, type MarkReadInput, type Observer, type ParticipantAction, type PresenceState, Profile, QrCode, type QuoteInput, type RequestInfo, type RequestOptions, type SendAudioInput, type SendContactInput, type SendDocumentInput, type SendGifInput, type SendLocationInput, type SendMediaInput, type SendStickerInput, type SendTextInput, SentMessage, type SuccessInfo, type UpdateInstanceInput, type User, type UserAuth, type UserJwtAuth, checkHealth, hasSentry };
package/dist/index.d.ts CHANGED
@@ -230,6 +230,32 @@ interface SendMediaInput extends QuoteInput, MarkReadInput {
230
230
  filename?: string;
231
231
  caption?: string;
232
232
  }
233
+ /**
234
+ * A GIF on WhatsApp is an MP4 VIDEO carrying the gif-playback flag, which is what makes the client
235
+ * loop it with no controls. Sending a real `.gif` file arrives as a still image, so it gets a method
236
+ * of its own rather than a `type` on sendMedia: the name is what warns the caller at the call site.
237
+ */
238
+ interface SendGifInput extends QuoteInput, MarkReadInput {
239
+ to: string;
240
+ /** MP4 bytes. A `.gif` file is NOT what WhatsApp expects here. */
241
+ file: Blob | File;
242
+ filename?: string;
243
+ caption?: string;
244
+ }
245
+ /**
246
+ * WhatsApp takes stickers as WebP 512x512, up to 500 KB, transparent background; animated ones are
247
+ * animated WebP. The server validates and refuses outside that, because a sticker off-spec is
248
+ * accepted by the wire and then fails to render, with nothing reporting it.
249
+ *
250
+ * There is no caption: the protocol has no such field on a sticker, so one would be dropped in
251
+ * silence. That is why this does not reuse `SendMediaInput`.
252
+ */
253
+ interface SendStickerInput extends QuoteInput, MarkReadInput {
254
+ to: string;
255
+ /** WebP bytes, 512x512, up to 500 KB. */
256
+ file: Blob | File;
257
+ filename?: string;
258
+ }
233
259
  interface SendAudioInput extends QuoteInput, MarkReadInput {
234
260
  to: string;
235
261
  file: Blob | File;
@@ -275,6 +301,8 @@ declare class MessagesResource {
275
301
  private get base();
276
302
  sendText(input: SendTextInput, options?: RequestOptions): Promise<SentMessage>;
277
303
  sendMedia(input: SendMediaInput, options?: RequestOptions): Promise<SentMessage>;
304
+ sendGif(input: SendGifInput, options?: RequestOptions): Promise<SentMessage>;
305
+ sendSticker(input: SendStickerInput, options?: RequestOptions): Promise<SentMessage>;
278
306
  sendAudio(input: SendAudioInput, options?: RequestOptions): Promise<SentMessage>;
279
307
  sendDocument(input: SendDocumentInput, options?: RequestOptions): Promise<SentMessage>;
280
308
  sendContact(input: SendContactInput, options?: RequestOptions): Promise<SentMessage>;
@@ -535,4 +563,4 @@ interface HealthResult {
535
563
  declare const DEFAULT_HEALTH_TIMEOUT_MS = 10000;
536
564
  declare function checkHealth(baseUrl: string, options?: HealthOptions): Promise<HealthResult>;
537
565
 
538
- export { type ApiToken, type ApiTokenAuth, Apime, ApimeError, ApimeInstanceClient, ApimeUserClient, type Auth, type AuthKind, type CheckNumberResult, type ClientOptions, type ContactEntry, type CreateInstanceInput, DEFAULT_HEALTH_TIMEOUT_MS, EventLogEntry, type FailureInfo, type GroupInfo, type GroupParticipant, type HealthOptions, type HealthResult, Instance, InstanceInfo, type InstanceTokenAuth, type IpFamily, type JoinRequestAction, type MarkReadInput, type Observer, type ParticipantAction, type PresenceState, Profile, QrCode, type QuoteInput, type RequestInfo, type RequestOptions, type SendAudioInput, type SendContactInput, type SendDocumentInput, type SendLocationInput, type SendMediaInput, type SendTextInput, SentMessage, type SuccessInfo, type UpdateInstanceInput, type User, type UserAuth, type UserJwtAuth, checkHealth, hasSentry };
566
+ export { type ApiToken, type ApiTokenAuth, Apime, ApimeError, ApimeInstanceClient, ApimeUserClient, type Auth, type AuthKind, type CheckNumberResult, type ClientOptions, type ContactEntry, type CreateInstanceInput, DEFAULT_HEALTH_TIMEOUT_MS, EventLogEntry, type FailureInfo, type GroupInfo, type GroupParticipant, type HealthOptions, type HealthResult, Instance, InstanceInfo, type InstanceTokenAuth, type IpFamily, type JoinRequestAction, type MarkReadInput, type Observer, type ParticipantAction, type PresenceState, Profile, QrCode, type QuoteInput, type RequestInfo, type RequestOptions, type SendAudioInput, type SendContactInput, type SendDocumentInput, type SendGifInput, type SendLocationInput, type SendMediaInput, type SendStickerInput, type SendTextInput, SentMessage, type SuccessInfo, type UpdateInstanceInput, type User, type UserAuth, type UserJwtAuth, checkHealth, hasSentry };
package/dist/index.js CHANGED
@@ -419,6 +419,18 @@ var MessagesResource = class {
419
419
  form.set("file", input.file, input.filename ?? fileName(input.file, "media"));
420
420
  return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
421
421
  }
422
+ sendGif(input, options) {
423
+ const form = toForm(input, ["file", "filename"]);
424
+ form.set("type", "gif");
425
+ form.set("file", input.file, input.filename ?? fileName(input.file, "animation.mp4"));
426
+ return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
427
+ }
428
+ sendSticker(input, options) {
429
+ const form = toForm(input, ["file", "filename"]);
430
+ form.set("type", "sticker");
431
+ form.set("file", input.file, input.filename ?? fileName(input.file, "sticker.webp"));
432
+ return this.http.request({ method: "POST", path: `${this.base}/media`, body: form, ...options ? { options } : {} });
433
+ }
422
434
  sendAudio(input, options) {
423
435
  const form = toForm(input, ["file", "filename"]);
424
436
  form.set("file", input.file, input.filename ?? fileName(input.file, "audio.ogg"));
package/docs/guia.md ADDED
@@ -0,0 +1,225 @@
1
+ # Guia do `@open-apime/sdk`
2
+
3
+ Referência de uso. O [README](../README.md) tem o resumo e a instalação.
4
+
5
+ ## Os três tokens decidem o que você pode chamar
6
+
7
+ A API do apime não é uniforme: 46 rotas só aceitam token de instância, 13 só aceitam token de
8
+ usuário. Por isso o SDK tem **dois clientes**, e o que você constrói define o que o compilador
9
+ deixa chamar. Sem isso, o erro só apareceria como `403` em produção.
10
+
11
+ ```ts
12
+ import { Apime } from "@open-apime/sdk";
13
+
14
+ // token de usuário: administra instâncias
15
+ const admin = Apime.withApiToken(process.env.APIME_TOKEN!, {
16
+ baseUrl: "https://apime.example.com",
17
+ });
18
+ await admin.instances.list();
19
+
20
+ // token de instância: opera UMA instância, e o id vem da credencial
21
+ const conexao = Apime.withInstanceToken(
22
+ { token: process.env.INSTANCE_TOKEN!, instanceId: "abc-123" },
23
+ { baseUrl: "https://apime.example.com" },
24
+ );
25
+ await conexao.instance.info();
26
+ ```
27
+
28
+ `admin.instance` não existe, e `conexao.instances.create()` também não. É erro de compilação, não
29
+ de runtime.
30
+
31
+ ### O que cada cliente tem
32
+
33
+ | Cliente | Recursos |
34
+ |---|---|
35
+ | `withApiToken` / `withUserJwt` | `instances`, `users` (exige admin), `tokens` |
36
+ | `withInstanceToken` | `instance`, `messages`, `whatsapp` (com `.groups` e `.newsletters`) |
37
+
38
+ ## Enviando mensagem
39
+
40
+ ```ts
41
+ await conexao.messages.sendText(
42
+ { to: "5511999999999", text: "Olá" },
43
+ { idempotencyKey: mensagem.id },
44
+ );
45
+
46
+ await conexao.messages.sendMedia(
47
+ { to: "5511999999999", type: "image", file: blob, caption: "Segue" },
48
+ { idempotencyKey: mensagem.id },
49
+ );
50
+
51
+ // GIF é MP4 com reprodução em laço, não arquivo .gif: mandar um .gif de verdade
52
+ // faz a mensagem chegar como imagem parada.
53
+ await conexao.messages.sendGif(
54
+ { to: "5511999999999", file: mp4Blob },
55
+ { idempotencyKey: mensagem.id },
56
+ );
57
+
58
+ // Figurinha exige WebP 512x512 até 500 KB (animada ou não), e não tem legenda:
59
+ // o protocolo não traz esse campo. Fora da especificação, o apime recusa com 400.
60
+ await conexao.messages.sendSticker(
61
+ { to: "5511999999999", file: webpBlob },
62
+ { idempotencyKey: mensagem.id },
63
+ );
64
+
65
+ await conexao.whatsapp.groups.updateParticipants(grupoJid, {
66
+ action: "add",
67
+ participants: ["5511999999999@s.whatsapp.net"],
68
+ });
69
+ ```
70
+
71
+ ## Erros
72
+
73
+ Uma classe por condição, então dá para ramificar sem ler status code:
74
+
75
+ ```ts
76
+ import { PermissionError, RateLimitError, SessionUnavailableError } from "@open-apime/sdk";
77
+
78
+ try {
79
+ await conexao.instance.info();
80
+ } catch (erro) {
81
+ if (erro instanceof SessionUnavailableError) {/* sessão não pronta, pode repetir */}
82
+ if (erro instanceof RateLimitError) {/* erro.retryAfterMs */}
83
+ if (erro instanceof PermissionError) {/* token errado para esta rota */}
84
+ }
85
+ ```
86
+
87
+ ## Retry: por que o envio não retenta sozinho
88
+
89
+ Leitura retenta com backoff exponencial e jitter. **Escrita não**, a menos que você passe uma
90
+ `Idempotency-Key`, porque retentar um envio sem chave entrega a mesma mensagem duas vezes ao
91
+ cliente final.
92
+
93
+ ```ts
94
+ await conexao.request({
95
+ method: "POST",
96
+ path: `/instances/${conexao.instanceId}/messages/text`,
97
+ body: { to: "5511999999999", text: "Olá" },
98
+ // a MESMA chave na retentativa. Um id estável da sua mensagem serve melhor que um uuid novo.
99
+ options: { idempotencyKey: mensagem.id },
100
+ });
101
+ ```
102
+
103
+ A exceção é `503`: o apime devolve isso quando a sessão não está pronta, antes de tocar o WhatsApp,
104
+ então nada saiu e o SDK repete mesmo sem chave.
105
+
106
+ `4xx` (fora `408` e `429`) nunca retenta, porque tentar de novo não muda o resultado.
107
+
108
+ ## Observabilidade: vigiando o próprio SDK
109
+
110
+ O SDK **não importa Sentry, nem OpenTelemetry, nem nada**, e mantém zero dependência de runtime.
111
+
112
+ **Rodando com Sentry já iniciado no projeto, a falha definitiva é reportada sozinha.** O SDK acha o
113
+ cliente global do `@sentry/node` sem importar o pacote, então não pina versão nem obriga ninguém.
114
+ **Sem Sentry, não faz nada e não quebra.** Para calar, `autoReport: false`.
115
+
116
+ Querendo controlar o que é reportado, o `observer` tem prioridade sobre o automático:
117
+
118
+ ```ts
119
+ import * as Sentry from "@sentry/node";
120
+
121
+ const conexao = Apime.withInstanceToken(credenciais, {
122
+ baseUrl,
123
+ appName: "meu-app",
124
+ observer: {
125
+ // Dispara em toda tentativa falha, inclusive as que serão repetidas.
126
+ onError: ({ error, route, method, status, instanceId, attempt, willRetry, idempotencyKey }) => {
127
+ if (willRetry) return; // ainda vai tentar de novo, não é falha definitiva
128
+ Sentry.captureException(error, {
129
+ tags: { sdk: "open-apime", route, method, status: String(status) },
130
+ extra: { instanceId, attempt, idempotencyKey },
131
+ });
132
+ },
133
+ },
134
+ });
135
+ ```
136
+
137
+ A `route` vem **normalizada** (`/instances/{id}/profile/{jid}`), e não com os ids reais. Sem isso o
138
+ error tracker criaria uma issue por instância e por contato, e o volume esconderia o problema.
139
+
140
+ Os quatro ganchos: `onRequest`, `onSuccess`, `onError` e `onRetry`. Um observer que lança exceção
141
+ **não derruba a requisição**: observabilidade nunca é motivo para perder um envio.
142
+
143
+ ## Health
144
+
145
+ `/healthz` não pede credencial, então é função avulsa, e não método de cliente: exigir token para
146
+ perguntar se o servidor está de pé seria ao contrário.
147
+
148
+ ```ts
149
+ import { checkHealth } from "@open-apime/sdk";
150
+
151
+ const health = await checkHealth("https://apime.example.com"); // { status: "ok", version, name }
152
+ ```
153
+
154
+ Passa pelo mesmo guard anti-SSRF do resto, e o timeout é curto (10s), porque health que pendura é
155
+ pior que health que falha.
156
+
157
+ ## Rede: forçar IPv4
158
+
159
+ Host atrás de Cloudflare responde A e AAAA. Onde a rede não tem rota IPv6, o Node tenta os
160
+ endereços v6 primeiro e colhe `ENETUNREACH` em cada um antes de chegar ao v4. Nada falha, mas o
161
+ ruído acaba gravado no erro da mensagem que estava sendo enviada.
162
+
163
+ ```ts
164
+ const conexao = Apime.withInstanceToken(credenciais, { baseUrl, ipFamily: 4 });
165
+ ```
166
+
167
+ **Vale para o processo inteiro**, porque o Node não oferece esse ajuste por requisição: é o
168
+ `dns.setDefaultResultOrder`. Por isso o SDK aplica **uma vez só**, na primeira vez que alguém pede,
169
+ e ignora pedido conflitante depois. Sem `ipFamily`, o SDK não mexe na resolução do processo.
170
+
171
+ ## Webhook
172
+
173
+ ```ts
174
+ import { constructEvent } from "@open-apime/sdk/webhook";
175
+
176
+ app.post("/webhooks/apime", express.raw({ type: "application/json" }), (req, res) => {
177
+ let evento;
178
+ try {
179
+ // corpo CRU. JSON já parseado e reserializado quebra a assinatura.
180
+ evento = constructEvent(req.body, req.get("X-ApiMe-Signature"), process.env.WEBHOOK_SECRET!);
181
+ } catch {
182
+ return res.sendStatus(401);
183
+ }
184
+
185
+ switch (evento.type) {
186
+ case "message":
187
+ console.log(evento.payload.text); // tipado, estreitado pelo `type`
188
+ break;
189
+ case "temporary_ban":
190
+ console.log(evento.payload.restrictedUntil);
191
+ break;
192
+ }
193
+ res.sendStatus(200);
194
+ });
195
+ ```
196
+
197
+ São **12 tipos de evento**. `reply`, `url`, `copy` e `call` **não** são eventos, são tipos de botão
198
+ dentro de `payload.buttons[].type`.
199
+
200
+ ## Cobertura
201
+
202
+ Cobre **as 66 operações** do contrato do apime. Ficam de fora `/auth/login`, que é como se obtém a
203
+ credencial, e `/media/{instanceId}/{mediaId}`, que serve arquivo direto ao navegador.
204
+
205
+ Para qualquer rota que o SDK ainda não tenha, `client.request()` fala com a API mantendo auth,
206
+ retry e observabilidade.
207
+
208
+ A API completa está no [openapi.yaml](https://github.com/open-apime/apime/blob/main/openapi.yaml)
209
+ do apime, e os eventos em [webhook-payloads.md](https://github.com/open-apime/apime/blob/main/docs/webhook-payloads.md).
210
+
211
+ Enquanto está em `0.x`, minor pode trazer mudança incompatível.
212
+
213
+ ## Segurança
214
+
215
+ - **Sem sourcemap no pacote.** O `.map` embute o código-fonte inteiro em `sourcesContent`, então
216
+ publicá-lo entregaria a fonte junto do build. O fonte vive no GitHub, que é onde se lê.
217
+ - **Segmento de path é escapado.** Id vindo do banco ou de entrada de usuário não escapa da rota:
218
+ sem isso, um id com `../` faria `/instances/{id}` cair em `/users/admin`.
219
+ - **Guard anti-SSRF na `baseUrl`.** IP privado, loopback, link-local e endpoint de metadados de
220
+ nuvem são recusados antes de qualquer requisição.
221
+ - **O token não aparece em erro.** Nem na mensagem, nem no corpo, nem no stack.
222
+ - **O relato ao Sentry não leva telefone.** Vai a rota normalizada (`/instances/{id}/profile/{jid}`),
223
+ nunca o path real com o jid do contato.
224
+ - **Verificação de webhook em tempo constante**, sobre o corpo cru.
225
+
@@ -0,0 +1,13 @@
1
+ # Segurança do `@open-apime/sdk`
2
+
3
+ - **Sem sourcemap no pacote.** O `.map` embute o código-fonte inteiro em `sourcesContent`, então
4
+ publicá-lo entregaria a fonte junto do build. O fonte vive no GitHub, que é onde se lê.
5
+ - **Segmento de path é escapado.** Id vindo do banco ou de entrada de usuário não escapa da rota:
6
+ sem isso, um id com `../` faria `/instances/{id}` cair em `/users/admin`.
7
+ - **Guard anti-SSRF na `baseUrl`.** IP privado, loopback, link-local e endpoint de metadados de
8
+ nuvem são recusados antes de qualquer requisição.
9
+ - **O token não aparece em erro.** Nem na mensagem, nem no corpo, nem no stack.
10
+ - **O relato ao Sentry não leva telefone.** Vai a rota normalizada (`/instances/{id}/profile/{jid}`),
11
+ nunca o path real com o jid do contato.
12
+ - **Verificação de webhook em tempo constante**, sobre o corpo cru.
13
+
package/package.json CHANGED
@@ -1,16 +1,8 @@
1
1
  {
2
2
  "name": "@open-apime/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Cliente TypeScript da API do apime: WhatsApp, instâncias e webhooks",
5
5
  "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/open-apime/sdk.git"
9
- },
10
- "homepage": "https://github.com/open-apime/sdk#readme",
11
- "bugs": {
12
- "url": "https://github.com/open-apime/sdk/issues"
13
- },
14
6
  "keywords": [
15
7
  "apime",
16
8
  "whatsapp",
@@ -31,6 +23,7 @@
31
23
  "dist/**/*.cjs",
32
24
  "dist/**/*.d.ts",
33
25
  "dist/**/*.d.cts",
26
+ "docs/*.md",
34
27
  "README.md",
35
28
  "LICENSE"
36
29
  ],