@erlancarreira/evolution-chat 0.1.0 → 0.1.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/api/index.cjs +53 -4
- package/api/index.cjs.map +1 -1
- package/api/index.d.cts +31 -0
- package/api/index.d.ts +31 -0
- package/api/index.js +53 -4
- package/api/index.js.map +1 -1
- package/bridge/index.cjs +192 -6
- package/bridge/index.cjs.map +1 -1
- package/bridge/index.d.cts +40 -2
- package/bridge/index.d.ts +40 -2
- package/bridge/index.js +192 -6
- package/bridge/index.js.map +1 -1
- package/next/index.d.cts +39 -1
- package/next/index.d.ts +39 -1
- package/package.json +1 -1
- package/transports/supabase/index.cjs +24 -1
- package/transports/supabase/index.cjs.map +1 -1
- package/transports/supabase/index.d.cts +4 -2
- package/transports/supabase/index.d.ts +4 -2
- package/transports/supabase/index.js +23 -1
- package/transports/supabase/index.js.map +1 -1
- package/widget/index.cjs +39 -11
- package/widget/index.cjs.map +1 -1
- package/widget/index.d.cts +2 -2
- package/widget/index.d.ts +2 -2
- package/widget/index.js +39 -11
- package/widget/index.js.map +1 -1
- package/widget/styles.css +23 -0
- package/widget/styles.css.map +1 -1
- package/widget-embed/evolution-chat.iife.js +58 -12
package/api/index.cjs
CHANGED
|
@@ -147,6 +147,12 @@ function createEvolutionClient(cfg) {
|
|
|
147
147
|
if (typeof value !== "string" || value === "") throw new EvolutionApiError(res.status, res.text, res.operation);
|
|
148
148
|
return value;
|
|
149
149
|
}
|
|
150
|
+
async function fetchConnectionState(instance) {
|
|
151
|
+
const res = await requestJson("getConnectionState", "GET", `/instance/connectionState/${instance}`);
|
|
152
|
+
const state = readPath(res.json, "instance", "state");
|
|
153
|
+
if (state === "open" || state === "connecting" || state === "close") return state;
|
|
154
|
+
throw new EvolutionApiError(res.status, res.text, res.operation);
|
|
155
|
+
}
|
|
150
156
|
return {
|
|
151
157
|
async sendText(instance, number, text) {
|
|
152
158
|
const res = await requestJson("sendText", "POST", `/message/sendText/${instance}`, { number, text });
|
|
@@ -161,14 +167,17 @@ function createEvolutionClient(cfg) {
|
|
|
161
167
|
}
|
|
162
168
|
return { groupJid };
|
|
163
169
|
},
|
|
170
|
+
async setGroupPicture(instance, groupJid, image) {
|
|
171
|
+
await request("setGroupPicture", "POST", `/group/updateGroupPicture/${instance}`, { groupJid, image });
|
|
172
|
+
},
|
|
164
173
|
async leaveGroup(instance, groupJid) {
|
|
165
174
|
await request("leaveGroup", "DELETE", `/group/leave/${instance}`, { groupId: groupJid });
|
|
166
175
|
},
|
|
176
|
+
async logout(instance) {
|
|
177
|
+
await request("logout", "DELETE", `/instance/logout/${instance}`);
|
|
178
|
+
},
|
|
167
179
|
async getConnectionState(instance) {
|
|
168
|
-
|
|
169
|
-
const state = readPath(res.json, "instance", "state");
|
|
170
|
-
if (state === "open" || state === "connecting" || state === "close") return state;
|
|
171
|
-
throw new EvolutionApiError(res.status, res.text, res.operation);
|
|
180
|
+
return fetchConnectionState(instance);
|
|
172
181
|
},
|
|
173
182
|
async connectQR(instance) {
|
|
174
183
|
const res = await requestJson("connectQR", "GET", `/instance/connect/${instance}`);
|
|
@@ -180,6 +189,17 @@ function createEvolutionClient(cfg) {
|
|
|
180
189
|
pairingCode: typeof pairingCode === "string" ? pairingCode : typeof code === "string" ? code : null
|
|
181
190
|
};
|
|
182
191
|
},
|
|
192
|
+
async createInstance(instance, integration = "WHATSAPP-BAILEYS") {
|
|
193
|
+
await request("createInstance", "POST", "/instance/create", { instanceName: instance, integration });
|
|
194
|
+
},
|
|
195
|
+
async ensureInstance(instance) {
|
|
196
|
+
try {
|
|
197
|
+
await fetchConnectionState(instance);
|
|
198
|
+
return;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
await this.createInstance(instance);
|
|
202
|
+
},
|
|
183
203
|
async setWebhook(instance, url, events) {
|
|
184
204
|
await request("setWebhook", "POST", `/webhook/set/${instance}`, {
|
|
185
205
|
webhook: { enabled: true, url, events, webhookByEvents: false, webhookBase64: false }
|
|
@@ -252,6 +272,9 @@ function parseWebhookEvent(payload) {
|
|
|
252
272
|
if (typeof state === "string" && state.length > 0) return { kind: "connection", state };
|
|
253
273
|
return { kind: "ignored", reason: "connection.update sem state" };
|
|
254
274
|
}
|
|
275
|
+
if (event === "group-participants.update") {
|
|
276
|
+
return parseGroupParticipants(data);
|
|
277
|
+
}
|
|
255
278
|
if (event !== "messages.upsert") {
|
|
256
279
|
return { kind: "ignored", reason: `evento n\xE3o tratado: ${event}` };
|
|
257
280
|
}
|
|
@@ -264,6 +287,32 @@ function parseWebhookEvent(payload) {
|
|
|
264
287
|
}
|
|
265
288
|
return lastIgnored;
|
|
266
289
|
}
|
|
290
|
+
function parseGroupParticipants(data) {
|
|
291
|
+
if (!isRecord(data)) return { kind: "ignored", reason: "group-participants sem data" };
|
|
292
|
+
const groupJid = data["id"];
|
|
293
|
+
if (typeof groupJid !== "string" || groupJid.length === 0) {
|
|
294
|
+
return { kind: "ignored", reason: "group-participants sem id (groupJid)" };
|
|
295
|
+
}
|
|
296
|
+
const rawParticipants = data["participants"];
|
|
297
|
+
if (!Array.isArray(rawParticipants)) {
|
|
298
|
+
return { kind: "ignored", reason: "group-participants sem participants[]" };
|
|
299
|
+
}
|
|
300
|
+
const participants = [];
|
|
301
|
+
for (const p of rawParticipants) {
|
|
302
|
+
if (typeof p === "string" && p.length > 0) participants.push(p);
|
|
303
|
+
}
|
|
304
|
+
if (participants.length === 0) {
|
|
305
|
+
return { kind: "ignored", reason: "group-participants sem participantes v\xE1lidos" };
|
|
306
|
+
}
|
|
307
|
+
const actionRaw = data["action"];
|
|
308
|
+
const action = typeof actionRaw === "string" ? actionRaw : "unknown";
|
|
309
|
+
const authorRaw = data["author"];
|
|
310
|
+
const author = typeof authorRaw === "string" ? authorRaw : null;
|
|
311
|
+
return {
|
|
312
|
+
kind: "group_participants",
|
|
313
|
+
event: { groupJid, participants, action, author, raw: data }
|
|
314
|
+
};
|
|
315
|
+
}
|
|
267
316
|
// Annotate the CommonJS export names for ESM import in node:
|
|
268
317
|
0 && (module.exports = {
|
|
269
318
|
EvolutionApiError,
|
package/api/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/index.ts","../src/errors.ts","../src/api/phone.ts","../src/api/ids.ts","../src/api/client.ts","../src/api/webhook-parser.ts"],"sourcesContent":["// src/api/index.ts — superfície pública do subpacote `evolution-chat/api`.\n//\n// Cliente HTTP tipado e framework-agnóstico da Evolution API v2. Auth por header\n// `apikey`. Sem dependências pesadas (fetch global). O erro de transporte\n// (EvolutionApiError) é exportado daqui para o painel distinguir evolution_error\n// de config_error.\n\nexport * from \"./phone\";\nexport * from \"./ids\";\nexport * from \"./client\";\nexport * from \"./webhook-parser\";\n\n// Tipos canônicos compartilhados (re-export para conveniência).\nexport type { InboundMessage } from \"../types\";\n","// src/errors.ts — erro de domínio canônico do SDK.\n//\n// ChatError carrega um `code` estável para mapear em HTTP nas rotas sem depender\n// de instanceof entre bundles (cada entry do tsup inline-iza a classe; usamos\n// `.code` para decidir o status). O erro de transporte da Evolution vive em\n// src/api/client.ts (EvolutionApiError), junto do cliente que o lança.\n\nexport type ChatErrorCode =\n | \"invalid_input\"\n | \"rate_limited\"\n | \"group_create_failed\"\n | \"send_failed\"\n | \"session_not_found\"\n | \"session_closed\"\n | \"disabled\"\n | \"unauthorized\"\n | \"store_error\"\n | \"webhook_invalid\";\n\nexport class ChatError extends Error {\n public readonly code: ChatErrorCode;\n public override readonly cause?: unknown;\n\n constructor(message: string, code: ChatErrorCode, cause?: unknown) {\n super(message);\n this.name = \"ChatError\";\n this.code = code;\n this.cause = cause;\n }\n}\n","import { ChatError } from \"../errors\";\n\nconst NON_DIGITS = /\\D+/g;\n\nexport function normalizePhone(raw: string, defaultDdi = \"55\"): string {\n const digits = raw.replace(NON_DIGITS, \"\");\n if (!digits) throw new ChatError(\"Telefone vazio\", \"invalid_input\");\n // Prefixo \"+\" explícito (E.164) sinaliza DDI já presente — ex.: \"+1 415 555 2671\" permanece \"14155552671\".\n const explicitDdi = raw.includes(\"+\");\n const withDdi = digits.length <= 11 && !explicitDdi && !digits.startsWith(defaultDdi) ? defaultDdi + digits : digits;\n if (withDdi.length < 10 || withDdi.length > 15) {\n throw new ChatError(\"Telefone inválido\", \"invalid_input\");\n }\n return withDdi;\n}\nexport function isValidPhone(digits: string): boolean {\n return digits.length >= 10 && digits.length <= 15;\n}\nexport function toWhatsappJid(phoneDigits: string): string {\n return `${phoneDigits}@s.whatsapp.net`;\n}\n","// src/api/ids.ts — geração de ids com webcrypto (isomórfico, sem node:crypto).\n\nconst SESSION_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\"; // sem 0/O/1/I\nconst SESSION_CODE_LENGTH = 4;\n// Rejection sampling: só aceita bytes < 248 (múltiplo de 31 ≤ 256), evitando viés de módulo.\nconst SESSION_ACCEPT_LIMIT = 256 - (256 % SESSION_ALPHABET.length);\n\nconst BASE64URL_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst TOKEN_BYTES = 24; // 24 bytes → exatamente 32 caracteres base64url, sem padding\n\nfunction randomChar(alphabet: string, acceptLimit: number): string {\n const buffer = new Uint8Array(1);\n for (;;) {\n globalThis.crypto.getRandomValues(buffer);\n const byte = buffer[0];\n if (byte !== undefined && byte < acceptLimit) {\n return alphabet.charAt(byte % alphabet.length);\n }\n }\n}\n\nexport function generateSessionCode(): string {\n return Array.from({ length: SESSION_CODE_LENGTH }, () => randomChar(SESSION_ALPHABET, SESSION_ACCEPT_LIMIT)).join(\"\");\n}\n\nexport function generateRealtimeToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n globalThis.crypto.getRandomValues(bytes);\n let token = \"\";\n let acc = 0;\n let bits = 0;\n for (const byte of bytes) {\n acc = (acc << 8) | byte;\n bits += 8;\n while (bits >= 6) {\n bits -= 6;\n token += BASE64URL_ALPHABET.charAt((acc >>> bits) & 0x3f);\n }\n }\n return token;\n}\n","// src/api/client.ts — Adapter REST da Evolution API v2 (fetch injetável p/ testes).\nimport { ChatError } from \"../errors\";\n\nexport interface EvolutionFetch { (url: string, init: RequestInit): Promise<Response>; } // ponto de injeção (DI)\n\nexport interface SendTextResult { waMessageId: string; }\nexport interface CreateGroupResult { groupJid: string; }\n\nexport interface EvolutionClient {\n sendText(instance: string, number: string, text: string): Promise<SendTextResult>;\n createGroup(instance: string, subject: string, participants: string[], description?: string): Promise<CreateGroupResult>;\n leaveGroup(instance: string, groupJid: string): Promise<void>;\n getConnectionState(instance: string): Promise<\"open\" | \"connecting\" | \"close\">;\n connectQR(instance: string): Promise<{ qrBase64: string | null; pairingCode: string | null }>;\n setWebhook(instance: string, url: string, events: string[]): Promise<void>;\n}\n\nexport class EvolutionApiError extends Error {\n constructor(readonly status: number, readonly body: string, operation: string) {\n super(`Evolution ${operation} falhou (${status})`);\n this.name = \"EvolutionApiError\";\n }\n}\n\ninterface JsonOk { operation: string; status: number; text: string; json: Record<string, unknown>; }\n\n// Lê path aninhado (ex.: \"key\", \"id\") sem lançar: devolve undefined se faltar ou não for objeto.\nfunction readPath(json: Record<string, unknown>, ...path: string[]): unknown {\n let cur: unknown = json;\n for (const key of path) {\n if (!cur || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\nexport function createEvolutionClient(cfg: { baseUrl: string; apiKey: string; fetchImpl?: EvolutionFetch }): EvolutionClient {\n const doFetch: EvolutionFetch = cfg.fetchImpl ?? fetch;\n const base = cfg.baseUrl.replace(/\\/+$/, \"\"); // normaliza p/ evitar double slash na concatenação\n const headers = { \"content-type\": \"application/json\", apikey: cfg.apiKey };\n\n async function request(operation: string, method: string, path: string, body?: unknown): Promise<Response> {\n const response = await doFetch(`${base}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n if (!response.ok) throw new EvolutionApiError(response.status, await response.text(), operation);\n return response;\n }\n\n // Contrato: toda falha de HTTP/parse/shape é EvolutionApiError (a ponte captura via instanceof).\n async function requestJson(operation: string, method: string, path: string, body?: unknown): Promise<JsonOk> {\n const response = await request(operation, method, path, body);\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new EvolutionApiError(response.status, text, operation);\n }\n if (!parsed || typeof parsed !== \"object\") throw new EvolutionApiError(response.status, text, operation);\n return { operation, status: response.status, text, json: parsed as Record<string, unknown> };\n }\n\n function requireString(res: JsonOk, ...path: string[]): string {\n const value = readPath(res.json, ...path);\n if (typeof value !== \"string\" || value === \"\") throw new EvolutionApiError(res.status, res.text, res.operation);\n return value;\n }\n\n return {\n async sendText(instance, number, text) {\n const res = await requestJson(\"sendText\", \"POST\", `/message/sendText/${instance}`, { number, text });\n return { waMessageId: requireString(res, \"key\", \"id\") };\n },\n\n async createGroup(instance, subject, participants, description) {\n const payload = description === undefined ? { subject, participants } : { subject, participants, description };\n const res = await requestJson(\"createGroup\", \"POST\", `/group/create/${instance}`, payload);\n const groupJid = readPath(res.json, \"group\", \"id\") ?? res.json.id; // mesmo derivador de antes, sem cast\n if (typeof groupJid !== \"string\" || groupJid === \"\") {\n throw new ChatError(\"Evolution createGroup: groupJid ausente/inválido na resposta\", \"send_failed\");\n }\n return { groupJid };\n },\n\n async leaveGroup(instance, groupJid) {\n await request(\"leaveGroup\", \"DELETE\", `/group/leave/${instance}`, { groupId: groupJid });\n },\n\n async getConnectionState(instance) {\n const res = await requestJson(\"getConnectionState\", \"GET\", `/instance/connectionState/${instance}`);\n const state = readPath(res.json, \"instance\", \"state\");\n if (state === \"open\" || state === \"connecting\" || state === \"close\") return state;\n throw new EvolutionApiError(res.status, res.text, res.operation);\n },\n\n async connectQR(instance) {\n const res = await requestJson(\"connectQR\", \"GET\", `/instance/connect/${instance}`);\n const base64 = readPath(res.json, \"base64\");\n const pairingCode = readPath(res.json, \"pairingCode\");\n const code = readPath(res.json, \"code\");\n return {\n qrBase64: typeof base64 === \"string\" ? base64 : null,\n pairingCode: typeof pairingCode === \"string\" ? pairingCode : typeof code === \"string\" ? code : null,\n };\n },\n\n async setWebhook(instance, url, events) {\n await request(\"setWebhook\", \"POST\", `/webhook/set/${instance}`, {\n webhook: { enabled: true, url, events, webhookByEvents: false, webhookBase64: false },\n });\n },\n };\n}\n","// src/api/webhook-parser.ts — normalização defensiva de webhooks da Evolution API v2.\n//\n// Contrato: parseWebhookEvent(payload: unknown): ParsedWebhook.\n// NUNCA lança exceção — qualquer payload inesperado/inválido vira { kind: \"ignored\", reason }.\n\nimport type { InboundMessage } from \"../types\";\n\nexport type ParsedWebhook =\n | { kind: \"message\"; event: InboundMessage }\n | { kind: \"connection\"; state: string }\n | { kind: \"ignored\"; reason: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// Texto só sai de `conversation` ou `extendedTextMessage.text`; string vazia/só espaços → null.\nfunction textFromMessage(message: unknown): string | null {\n if (!isRecord(message)) return null;\n const conversation = message[\"conversation\"];\n if (typeof conversation === \"string\") {\n const trimmed = conversation.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n const extended = message[\"extendedTextMessage\"];\n if (isRecord(extended)) {\n const text = extended[\"text\"];\n if (typeof text === \"string\") {\n const trimmed = text.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n }\n return null;\n}\n\n// messageTimestamp em segundos (aceita número ou string numérica; ausente/inválido → 0).\nfunction timestampSeconds(value: unknown): number {\n const seconds = typeof value === \"number\" ? value : typeof value === \"string\" ? Number(value) : Number.NaN;\n return Number.isFinite(seconds) ? Math.floor(seconds) : 0;\n}\n\nfunction parseMessageItem(item: unknown): ParsedWebhook {\n if (!isRecord(item)) return { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n\n const key = isRecord(item[\"key\"]) ? item[\"key\"] : null;\n const waMessageId = key?.[\"id\"];\n const jid = key?.[\"remoteJid\"];\n if (typeof waMessageId !== \"string\" || waMessageId.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.id\" };\n }\n if (typeof jid !== \"string\" || jid.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.remoteJid\" };\n }\n\n const text = textFromMessage(item[\"message\"]);\n if (text === null) {\n return { kind: \"ignored\", reason: \"mensagem sem texto (conversation/extendedTextMessage)\" };\n }\n\n const senderJid = key?.[\"participant\"];\n return {\n kind: \"message\",\n event: {\n waMessageId,\n jid,\n fromMe: key?.[\"fromMe\"] === true,\n senderJid: typeof senderJid === \"string\" ? senderJid : null,\n text,\n timestamp: timestampSeconds(item[\"messageTimestamp\"]),\n raw: item,\n },\n };\n}\n\nexport function parseWebhookEvent(payload: unknown): ParsedWebhook {\n if (!isRecord(payload)) return { kind: \"ignored\", reason: \"payload não é um objeto\" };\n\n const event = payload[\"event\"];\n if (typeof event !== \"string\") return { kind: \"ignored\", reason: \"payload sem campo event\" };\n\n const data = payload[\"data\"];\n\n if (event === \"connection.update\") {\n const state = isRecord(data) ? data[\"state\"] : undefined;\n if (typeof state === \"string\" && state.length > 0) return { kind: \"connection\", state };\n return { kind: \"ignored\", reason: \"connection.update sem state\" };\n }\n\n if (event !== \"messages.upsert\") {\n return { kind: \"ignored\", reason: `evento não tratado: ${event}` };\n }\n\n // Evolution v2 pode mandar `data` como objeto único ou array de mensagens (queue-flush/bulk).\n // Contrato de evento único: devolve o PRIMEIRO item válido, não `items[0]` — senão um item\n // nulo/inválido na frente descartaria silenciosamente as mensagens válidas seguintes.\n const items: readonly unknown[] = Array.isArray(data) ? data : [data];\n let lastIgnored: ParsedWebhook = { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n for (const item of items) {\n const parsed = parseMessageItem(item);\n if (parsed.kind !== \"ignored\") return parsed;\n lastIgnored = parsed;\n }\n return lastIgnored;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/api/index.ts","../src/errors.ts","../src/api/phone.ts","../src/api/ids.ts","../src/api/client.ts","../src/api/webhook-parser.ts"],"sourcesContent":["// src/api/index.ts — superfície pública do subpacote `evolution-chat/api`.\n//\n// Cliente HTTP tipado e framework-agnóstico da Evolution API v2. Auth por header\n// `apikey`. Sem dependências pesadas (fetch global). O erro de transporte\n// (EvolutionApiError) é exportado daqui para o painel distinguir evolution_error\n// de config_error.\n\nexport * from \"./phone\";\nexport * from \"./ids\";\nexport * from \"./client\";\nexport * from \"./webhook-parser\";\n\n// Tipos canônicos compartilhados (re-export para conveniência).\nexport type { InboundMessage } from \"../types\";\n","// src/errors.ts — erro de domínio canônico do SDK.\n//\n// ChatError carrega um `code` estável para mapear em HTTP nas rotas sem depender\n// de instanceof entre bundles (cada entry do tsup inline-iza a classe; usamos\n// `.code` para decidir o status). O erro de transporte da Evolution vive em\n// src/api/client.ts (EvolutionApiError), junto do cliente que o lança.\n\nexport type ChatErrorCode =\n | \"invalid_input\"\n | \"rate_limited\"\n | \"group_create_failed\"\n | \"send_failed\"\n | \"session_not_found\"\n | \"session_closed\"\n | \"disabled\"\n | \"unauthorized\"\n | \"store_error\"\n | \"webhook_invalid\";\n\nexport class ChatError extends Error {\n public readonly code: ChatErrorCode;\n public override readonly cause?: unknown;\n\n constructor(message: string, code: ChatErrorCode, cause?: unknown) {\n super(message);\n this.name = \"ChatError\";\n this.code = code;\n this.cause = cause;\n }\n}\n","import { ChatError } from \"../errors\";\n\nconst NON_DIGITS = /\\D+/g;\n\nexport function normalizePhone(raw: string, defaultDdi = \"55\"): string {\n const digits = raw.replace(NON_DIGITS, \"\");\n if (!digits) throw new ChatError(\"Telefone vazio\", \"invalid_input\");\n // Prefixo \"+\" explícito (E.164) sinaliza DDI já presente — ex.: \"+1 415 555 2671\" permanece \"14155552671\".\n const explicitDdi = raw.includes(\"+\");\n const withDdi = digits.length <= 11 && !explicitDdi && !digits.startsWith(defaultDdi) ? defaultDdi + digits : digits;\n if (withDdi.length < 10 || withDdi.length > 15) {\n throw new ChatError(\"Telefone inválido\", \"invalid_input\");\n }\n return withDdi;\n}\nexport function isValidPhone(digits: string): boolean {\n return digits.length >= 10 && digits.length <= 15;\n}\nexport function toWhatsappJid(phoneDigits: string): string {\n return `${phoneDigits}@s.whatsapp.net`;\n}\n","// src/api/ids.ts — geração de ids com webcrypto (isomórfico, sem node:crypto).\n\nconst SESSION_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\"; // sem 0/O/1/I\nconst SESSION_CODE_LENGTH = 4;\n// Rejection sampling: só aceita bytes < 248 (múltiplo de 31 ≤ 256), evitando viés de módulo.\nconst SESSION_ACCEPT_LIMIT = 256 - (256 % SESSION_ALPHABET.length);\n\nconst BASE64URL_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst TOKEN_BYTES = 24; // 24 bytes → exatamente 32 caracteres base64url, sem padding\n\nfunction randomChar(alphabet: string, acceptLimit: number): string {\n const buffer = new Uint8Array(1);\n for (;;) {\n globalThis.crypto.getRandomValues(buffer);\n const byte = buffer[0];\n if (byte !== undefined && byte < acceptLimit) {\n return alphabet.charAt(byte % alphabet.length);\n }\n }\n}\n\nexport function generateSessionCode(): string {\n return Array.from({ length: SESSION_CODE_LENGTH }, () => randomChar(SESSION_ALPHABET, SESSION_ACCEPT_LIMIT)).join(\"\");\n}\n\nexport function generateRealtimeToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n globalThis.crypto.getRandomValues(bytes);\n let token = \"\";\n let acc = 0;\n let bits = 0;\n for (const byte of bytes) {\n acc = (acc << 8) | byte;\n bits += 8;\n while (bits >= 6) {\n bits -= 6;\n token += BASE64URL_ALPHABET.charAt((acc >>> bits) & 0x3f);\n }\n }\n return token;\n}\n","// src/api/client.ts — Adapter REST da Evolution API v2 (fetch injetável p/ testes).\nimport { ChatError } from \"../errors\";\n\nexport interface EvolutionFetch { (url: string, init: RequestInit): Promise<Response>; } // ponto de injeção (DI)\n\nexport interface SendTextResult { waMessageId: string; }\nexport interface CreateGroupResult { groupJid: string; }\n\nexport interface EvolutionClient {\n sendText(instance: string, number: string, text: string): Promise<SendTextResult>;\n createGroup(instance: string, subject: string, participants: string[], description?: string): Promise<CreateGroupResult>;\n setGroupPicture(instance: string, groupJid: string, image: string): Promise<void>;\n leaveGroup(instance: string, groupJid: string): Promise<void>;\n /** Encerra a sessão WhatsApp da instância (desvincula o dispositivo). */\n logout(instance: string): Promise<void>;\n getConnectionState(instance: string): Promise<\"open\" | \"connecting\" | \"close\">;\n connectQR(instance: string): Promise<{ qrBase64: string | null; pairingCode: string | null }>;\n setWebhook(instance: string, url: string, events: string[]): Promise<void>;\n /** Cria a instância na Evolution (one-time). Exige a apiKey com permissão de criação. */\n createInstance(instance: string, integration?: \"WHATSAPP-BAILEYS\" | \"WHATSAPP-BUSINESS\"): Promise<void>;\n /**\n * Garante que a instância existe (idempotente): se `getConnectionState` responde\n * (qualquer estado), não faz nada; só chama `createInstance` quando a leitura falha\n * (instância inexistente). Assim o LMS gerencia o ciclo de vida sem criar manualmente\n * na Evolution — o único passo humano é escanear o QR de pareamento.\n */\n ensureInstance(instance: string): Promise<void>;\n}\n\nexport class EvolutionApiError extends Error {\n constructor(readonly status: number, readonly body: string, operation: string) {\n super(`Evolution ${operation} falhou (${status})`);\n this.name = \"EvolutionApiError\";\n }\n}\n\ninterface JsonOk { operation: string; status: number; text: string; json: Record<string, unknown>; }\n\n// Lê path aninhado (ex.: \"key\", \"id\") sem lançar: devolve undefined se faltar ou não for objeto.\nfunction readPath(json: Record<string, unknown>, ...path: string[]): unknown {\n let cur: unknown = json;\n for (const key of path) {\n if (!cur || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\nexport function createEvolutionClient(cfg: { baseUrl: string; apiKey: string; fetchImpl?: EvolutionFetch }): EvolutionClient {\n const doFetch: EvolutionFetch = cfg.fetchImpl ?? fetch;\n const base = cfg.baseUrl.replace(/\\/+$/, \"\"); // normaliza p/ evitar double slash na concatenação\n const headers = { \"content-type\": \"application/json\", apikey: cfg.apiKey };\n\n async function request(operation: string, method: string, path: string, body?: unknown): Promise<Response> {\n const response = await doFetch(`${base}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n if (!response.ok) throw new EvolutionApiError(response.status, await response.text(), operation);\n return response;\n }\n\n // Contrato: toda falha de HTTP/parse/shape é EvolutionApiError (a ponte captura via instanceof).\n async function requestJson(operation: string, method: string, path: string, body?: unknown): Promise<JsonOk> {\n const response = await request(operation, method, path, body);\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new EvolutionApiError(response.status, text, operation);\n }\n if (!parsed || typeof parsed !== \"object\") throw new EvolutionApiError(response.status, text, operation);\n return { operation, status: response.status, text, json: parsed as Record<string, unknown> };\n }\n\n function requireString(res: JsonOk, ...path: string[]): string {\n const value = readPath(res.json, ...path);\n if (typeof value !== \"string\" || value === \"\") throw new EvolutionApiError(res.status, res.text, res.operation);\n return value;\n }\n\n // Estado da instância (open/connecting/close); lança EvolutionApiError se a resposta\n // for inválida OU a instância não existir (a Evolution devolve erro nesse caso).\n async function fetchConnectionState(instance: string): Promise<\"open\" | \"connecting\" | \"close\"> {\n const res = await requestJson(\"getConnectionState\", \"GET\", `/instance/connectionState/${instance}`);\n const state = readPath(res.json, \"instance\", \"state\");\n if (state === \"open\" || state === \"connecting\" || state === \"close\") return state;\n throw new EvolutionApiError(res.status, res.text, res.operation);\n }\n\n return {\n async sendText(instance, number, text) {\n const res = await requestJson(\"sendText\", \"POST\", `/message/sendText/${instance}`, { number, text });\n return { waMessageId: requireString(res, \"key\", \"id\") };\n },\n\n async createGroup(instance, subject, participants, description) {\n const payload = description === undefined ? { subject, participants } : { subject, participants, description };\n const res = await requestJson(\"createGroup\", \"POST\", `/group/create/${instance}`, payload);\n const groupJid = readPath(res.json, \"group\", \"id\") ?? res.json.id; // mesmo derivador de antes, sem cast\n if (typeof groupJid !== \"string\" || groupJid === \"\") {\n throw new ChatError(\"Evolution createGroup: groupJid ausente/inválido na resposta\", \"send_failed\");\n }\n return { groupJid };\n },\n\n async setGroupPicture(instance, groupJid, image) {\n // Evolution v2: POST /group/updateGroupPicture/{instance} com { groupJid, image }\n // (image = URL pública ou base64). Imagem é cosmética: qualquer falha vira EvolutionApiError.\n await request(\"setGroupPicture\", \"POST\", `/group/updateGroupPicture/${instance}`, { groupJid, image });\n },\n\n async leaveGroup(instance, groupJid) {\n await request(\"leaveGroup\", \"DELETE\", `/group/leave/${instance}`, { groupId: groupJid });\n },\n\n async logout(instance) {\n await request(\"logout\", \"DELETE\", `/instance/logout/${instance}`);\n },\n\n async getConnectionState(instance) {\n return fetchConnectionState(instance);\n },\n\n async connectQR(instance) {\n const res = await requestJson(\"connectQR\", \"GET\", `/instance/connect/${instance}`);\n const base64 = readPath(res.json, \"base64\");\n const pairingCode = readPath(res.json, \"pairingCode\");\n const code = readPath(res.json, \"code\");\n return {\n qrBase64: typeof base64 === \"string\" ? base64 : null,\n pairingCode: typeof pairingCode === \"string\" ? pairingCode : typeof code === \"string\" ? code : null,\n };\n },\n\n async createInstance(instance, integration = \"WHATSAPP-BAILEYS\") {\n await request(\"createInstance\", \"POST\", \"/instance/create\", { instanceName: instance, integration });\n },\n\n async ensureInstance(instance) {\n try {\n await fetchConnectionState(instance); // existe (qualquer estado) → ok, não faz nada\n return;\n } catch {\n // instância inexistente (ou erro de leitura) → tenta criar\n }\n await this.createInstance(instance);\n },\n\n async setWebhook(instance, url, events) {\n await request(\"setWebhook\", \"POST\", `/webhook/set/${instance}`, {\n webhook: { enabled: true, url, events, webhookByEvents: false, webhookBase64: false },\n });\n },\n };\n}\n","// src/api/webhook-parser.ts — normalização defensiva de webhooks da Evolution API v2.\n//\n// Contrato: parseWebhookEvent(payload: unknown): ParsedWebhook.\n// NUNCA lança exceção — qualquer payload inesperado/inválido vira { kind: \"ignored\", reason }.\n\nimport type { GroupParticipantChange, InboundMessage } from \"../types\";\n\nexport type ParsedWebhook =\n | { kind: \"message\"; event: InboundMessage }\n | { kind: \"connection\"; state: string }\n | { kind: \"group_participants\"; event: GroupParticipantChange }\n | { kind: \"ignored\"; reason: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// Texto só sai de `conversation` ou `extendedTextMessage.text`; string vazia/só espaços → null.\nfunction textFromMessage(message: unknown): string | null {\n if (!isRecord(message)) return null;\n const conversation = message[\"conversation\"];\n if (typeof conversation === \"string\") {\n const trimmed = conversation.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n const extended = message[\"extendedTextMessage\"];\n if (isRecord(extended)) {\n const text = extended[\"text\"];\n if (typeof text === \"string\") {\n const trimmed = text.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n }\n return null;\n}\n\n// messageTimestamp em segundos (aceita número ou string numérica; ausente/inválido → 0).\nfunction timestampSeconds(value: unknown): number {\n const seconds = typeof value === \"number\" ? value : typeof value === \"string\" ? Number(value) : Number.NaN;\n return Number.isFinite(seconds) ? Math.floor(seconds) : 0;\n}\n\nfunction parseMessageItem(item: unknown): ParsedWebhook {\n if (!isRecord(item)) return { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n\n const key = isRecord(item[\"key\"]) ? item[\"key\"] : null;\n const waMessageId = key?.[\"id\"];\n const jid = key?.[\"remoteJid\"];\n if (typeof waMessageId !== \"string\" || waMessageId.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.id\" };\n }\n if (typeof jid !== \"string\" || jid.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.remoteJid\" };\n }\n\n const text = textFromMessage(item[\"message\"]);\n if (text === null) {\n return { kind: \"ignored\", reason: \"mensagem sem texto (conversation/extendedTextMessage)\" };\n }\n\n const senderJid = key?.[\"participant\"];\n return {\n kind: \"message\",\n event: {\n waMessageId,\n jid,\n fromMe: key?.[\"fromMe\"] === true,\n senderJid: typeof senderJid === \"string\" ? senderJid : null,\n text,\n timestamp: timestampSeconds(item[\"messageTimestamp\"]),\n raw: item,\n },\n };\n}\n\nexport function parseWebhookEvent(payload: unknown): ParsedWebhook {\n if (!isRecord(payload)) return { kind: \"ignored\", reason: \"payload não é um objeto\" };\n\n const event = payload[\"event\"];\n if (typeof event !== \"string\") return { kind: \"ignored\", reason: \"payload sem campo event\" };\n\n const data = payload[\"data\"];\n\n if (event === \"connection.update\") {\n const state = isRecord(data) ? data[\"state\"] : undefined;\n if (typeof state === \"string\" && state.length > 0) return { kind: \"connection\", state };\n return { kind: \"ignored\", reason: \"connection.update sem state\" };\n }\n\n if (event === \"group-participants.update\") {\n return parseGroupParticipants(data);\n }\n\n if (event !== \"messages.upsert\") {\n return { kind: \"ignored\", reason: `evento não tratado: ${event}` };\n }\n\n // Evolution v2 pode mandar `data` como objeto único ou array de mensagens (queue-flush/bulk).\n // Contrato de evento único: devolve o PRIMEIRO item válido, não `items[0]` — senão um item\n // nulo/inválido na frente descartaria silenciosamente as mensagens válidas seguintes.\n const items: readonly unknown[] = Array.isArray(data) ? data : [data];\n let lastIgnored: ParsedWebhook = { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n for (const item of items) {\n const parsed = parseMessageItem(item);\n if (parsed.kind !== \"ignored\") return parsed;\n lastIgnored = parsed;\n }\n return lastIgnored;\n}\n\n/**\n * Normaliza o evento `group-participants.update` da Evolution:\n * { id: \"…@g.us\", participants: [\"…@s.whatsapp.net\"], author: \"…@s.whatsapp.net\",\n * action: \"add\" | \"remove\" | \"leave\" | \"promote\" | \"demote\" }\n * Campos faltantes/fora do formato viram { kind: \"ignored\" } (nunca lança).\n */\nfunction parseGroupParticipants(data: unknown): ParsedWebhook {\n if (!isRecord(data)) return { kind: \"ignored\", reason: \"group-participants sem data\" };\n\n const groupJid = data[\"id\"];\n if (typeof groupJid !== \"string\" || groupJid.length === 0) {\n return { kind: \"ignored\", reason: \"group-participants sem id (groupJid)\" };\n }\n\n const rawParticipants = data[\"participants\"];\n if (!Array.isArray(rawParticipants)) {\n return { kind: \"ignored\", reason: \"group-participants sem participants[]\" };\n }\n const participants: string[] = [];\n for (const p of rawParticipants) {\n if (typeof p === \"string\" && p.length > 0) participants.push(p);\n }\n if (participants.length === 0) {\n return { kind: \"ignored\", reason: \"group-participants sem participantes válidos\" };\n }\n\n const actionRaw = data[\"action\"];\n const action = typeof actionRaw === \"string\" ? actionRaw : \"unknown\";\n const authorRaw = data[\"author\"];\n const author = typeof authorRaw === \"string\" ? authorRaw : null;\n\n return {\n kind: \"group_participants\",\n event: { groupJid, participants, action, author, raw: data },\n };\n}\n"],"mappings":"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","names":[]}
|
package/api/index.d.cts
CHANGED
|
@@ -17,13 +17,25 @@ interface CreateGroupResult {
|
|
|
17
17
|
interface EvolutionClient {
|
|
18
18
|
sendText(instance: string, number: string, text: string): Promise<SendTextResult>;
|
|
19
19
|
createGroup(instance: string, subject: string, participants: string[], description?: string): Promise<CreateGroupResult>;
|
|
20
|
+
setGroupPicture(instance: string, groupJid: string, image: string): Promise<void>;
|
|
20
21
|
leaveGroup(instance: string, groupJid: string): Promise<void>;
|
|
22
|
+
/** Encerra a sessão WhatsApp da instância (desvincula o dispositivo). */
|
|
23
|
+
logout(instance: string): Promise<void>;
|
|
21
24
|
getConnectionState(instance: string): Promise<"open" | "connecting" | "close">;
|
|
22
25
|
connectQR(instance: string): Promise<{
|
|
23
26
|
qrBase64: string | null;
|
|
24
27
|
pairingCode: string | null;
|
|
25
28
|
}>;
|
|
26
29
|
setWebhook(instance: string, url: string, events: string[]): Promise<void>;
|
|
30
|
+
/** Cria a instância na Evolution (one-time). Exige a apiKey com permissão de criação. */
|
|
31
|
+
createInstance(instance: string, integration?: "WHATSAPP-BAILEYS" | "WHATSAPP-BUSINESS"): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Garante que a instância existe (idempotente): se `getConnectionState` responde
|
|
34
|
+
* (qualquer estado), não faz nada; só chama `createInstance` quando a leitura falha
|
|
35
|
+
* (instância inexistente). Assim o LMS gerencia o ciclo de vida sem criar manualmente
|
|
36
|
+
* na Evolution — o único passo humano é escanear o QR de pareamento.
|
|
37
|
+
*/
|
|
38
|
+
ensureInstance(instance: string): Promise<void>;
|
|
27
39
|
}
|
|
28
40
|
declare class EvolutionApiError extends Error {
|
|
29
41
|
readonly status: number;
|
|
@@ -49,6 +61,22 @@ interface InboundMessage {
|
|
|
49
61
|
timestamp: number;
|
|
50
62
|
raw: unknown;
|
|
51
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Mudança de participantes em um grupo WhatsApp, normalizada a partir do evento
|
|
66
|
+
* `group-participants.update` da Evolution API. Usada para notificar no chat quando
|
|
67
|
+
* alguém entra, sai ou é removido/adicionado ao grupo da sessão.
|
|
68
|
+
*/
|
|
69
|
+
interface GroupParticipantChange {
|
|
70
|
+
/** JID do grupo (ex.: "120363000000000001@g.us"). */
|
|
71
|
+
groupJid: string;
|
|
72
|
+
/** JIDs dos participantes afetados (ex.: ["5511999998888@s.whatsapp.net"]). */
|
|
73
|
+
participants: string[];
|
|
74
|
+
/** Ação da Evolution: "add" | "remove" | "leave" | "promote" | "demote". */
|
|
75
|
+
action: "add" | "remove" | "leave" | "promote" | "demote" | (string & {});
|
|
76
|
+
/** Quem executou a ação (JID); nulo para "leave" (o próprio participante saiu). */
|
|
77
|
+
author: string | null;
|
|
78
|
+
raw: unknown;
|
|
79
|
+
}
|
|
52
80
|
|
|
53
81
|
type ParsedWebhook = {
|
|
54
82
|
kind: "message";
|
|
@@ -56,6 +84,9 @@ type ParsedWebhook = {
|
|
|
56
84
|
} | {
|
|
57
85
|
kind: "connection";
|
|
58
86
|
state: string;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "group_participants";
|
|
89
|
+
event: GroupParticipantChange;
|
|
59
90
|
} | {
|
|
60
91
|
kind: "ignored";
|
|
61
92
|
reason: string;
|
package/api/index.d.ts
CHANGED
|
@@ -17,13 +17,25 @@ interface CreateGroupResult {
|
|
|
17
17
|
interface EvolutionClient {
|
|
18
18
|
sendText(instance: string, number: string, text: string): Promise<SendTextResult>;
|
|
19
19
|
createGroup(instance: string, subject: string, participants: string[], description?: string): Promise<CreateGroupResult>;
|
|
20
|
+
setGroupPicture(instance: string, groupJid: string, image: string): Promise<void>;
|
|
20
21
|
leaveGroup(instance: string, groupJid: string): Promise<void>;
|
|
22
|
+
/** Encerra a sessão WhatsApp da instância (desvincula o dispositivo). */
|
|
23
|
+
logout(instance: string): Promise<void>;
|
|
21
24
|
getConnectionState(instance: string): Promise<"open" | "connecting" | "close">;
|
|
22
25
|
connectQR(instance: string): Promise<{
|
|
23
26
|
qrBase64: string | null;
|
|
24
27
|
pairingCode: string | null;
|
|
25
28
|
}>;
|
|
26
29
|
setWebhook(instance: string, url: string, events: string[]): Promise<void>;
|
|
30
|
+
/** Cria a instância na Evolution (one-time). Exige a apiKey com permissão de criação. */
|
|
31
|
+
createInstance(instance: string, integration?: "WHATSAPP-BAILEYS" | "WHATSAPP-BUSINESS"): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Garante que a instância existe (idempotente): se `getConnectionState` responde
|
|
34
|
+
* (qualquer estado), não faz nada; só chama `createInstance` quando a leitura falha
|
|
35
|
+
* (instância inexistente). Assim o LMS gerencia o ciclo de vida sem criar manualmente
|
|
36
|
+
* na Evolution — o único passo humano é escanear o QR de pareamento.
|
|
37
|
+
*/
|
|
38
|
+
ensureInstance(instance: string): Promise<void>;
|
|
27
39
|
}
|
|
28
40
|
declare class EvolutionApiError extends Error {
|
|
29
41
|
readonly status: number;
|
|
@@ -49,6 +61,22 @@ interface InboundMessage {
|
|
|
49
61
|
timestamp: number;
|
|
50
62
|
raw: unknown;
|
|
51
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Mudança de participantes em um grupo WhatsApp, normalizada a partir do evento
|
|
66
|
+
* `group-participants.update` da Evolution API. Usada para notificar no chat quando
|
|
67
|
+
* alguém entra, sai ou é removido/adicionado ao grupo da sessão.
|
|
68
|
+
*/
|
|
69
|
+
interface GroupParticipantChange {
|
|
70
|
+
/** JID do grupo (ex.: "120363000000000001@g.us"). */
|
|
71
|
+
groupJid: string;
|
|
72
|
+
/** JIDs dos participantes afetados (ex.: ["5511999998888@s.whatsapp.net"]). */
|
|
73
|
+
participants: string[];
|
|
74
|
+
/** Ação da Evolution: "add" | "remove" | "leave" | "promote" | "demote". */
|
|
75
|
+
action: "add" | "remove" | "leave" | "promote" | "demote" | (string & {});
|
|
76
|
+
/** Quem executou a ação (JID); nulo para "leave" (o próprio participante saiu). */
|
|
77
|
+
author: string | null;
|
|
78
|
+
raw: unknown;
|
|
79
|
+
}
|
|
52
80
|
|
|
53
81
|
type ParsedWebhook = {
|
|
54
82
|
kind: "message";
|
|
@@ -56,6 +84,9 @@ type ParsedWebhook = {
|
|
|
56
84
|
} | {
|
|
57
85
|
kind: "connection";
|
|
58
86
|
state: string;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "group_participants";
|
|
89
|
+
event: GroupParticipantChange;
|
|
59
90
|
} | {
|
|
60
91
|
kind: "ignored";
|
|
61
92
|
reason: string;
|
package/api/index.js
CHANGED
|
@@ -114,6 +114,12 @@ function createEvolutionClient(cfg) {
|
|
|
114
114
|
if (typeof value !== "string" || value === "") throw new EvolutionApiError(res.status, res.text, res.operation);
|
|
115
115
|
return value;
|
|
116
116
|
}
|
|
117
|
+
async function fetchConnectionState(instance) {
|
|
118
|
+
const res = await requestJson("getConnectionState", "GET", `/instance/connectionState/${instance}`);
|
|
119
|
+
const state = readPath(res.json, "instance", "state");
|
|
120
|
+
if (state === "open" || state === "connecting" || state === "close") return state;
|
|
121
|
+
throw new EvolutionApiError(res.status, res.text, res.operation);
|
|
122
|
+
}
|
|
117
123
|
return {
|
|
118
124
|
async sendText(instance, number, text) {
|
|
119
125
|
const res = await requestJson("sendText", "POST", `/message/sendText/${instance}`, { number, text });
|
|
@@ -128,14 +134,17 @@ function createEvolutionClient(cfg) {
|
|
|
128
134
|
}
|
|
129
135
|
return { groupJid };
|
|
130
136
|
},
|
|
137
|
+
async setGroupPicture(instance, groupJid, image) {
|
|
138
|
+
await request("setGroupPicture", "POST", `/group/updateGroupPicture/${instance}`, { groupJid, image });
|
|
139
|
+
},
|
|
131
140
|
async leaveGroup(instance, groupJid) {
|
|
132
141
|
await request("leaveGroup", "DELETE", `/group/leave/${instance}`, { groupId: groupJid });
|
|
133
142
|
},
|
|
143
|
+
async logout(instance) {
|
|
144
|
+
await request("logout", "DELETE", `/instance/logout/${instance}`);
|
|
145
|
+
},
|
|
134
146
|
async getConnectionState(instance) {
|
|
135
|
-
|
|
136
|
-
const state = readPath(res.json, "instance", "state");
|
|
137
|
-
if (state === "open" || state === "connecting" || state === "close") return state;
|
|
138
|
-
throw new EvolutionApiError(res.status, res.text, res.operation);
|
|
147
|
+
return fetchConnectionState(instance);
|
|
139
148
|
},
|
|
140
149
|
async connectQR(instance) {
|
|
141
150
|
const res = await requestJson("connectQR", "GET", `/instance/connect/${instance}`);
|
|
@@ -147,6 +156,17 @@ function createEvolutionClient(cfg) {
|
|
|
147
156
|
pairingCode: typeof pairingCode === "string" ? pairingCode : typeof code === "string" ? code : null
|
|
148
157
|
};
|
|
149
158
|
},
|
|
159
|
+
async createInstance(instance, integration = "WHATSAPP-BAILEYS") {
|
|
160
|
+
await request("createInstance", "POST", "/instance/create", { instanceName: instance, integration });
|
|
161
|
+
},
|
|
162
|
+
async ensureInstance(instance) {
|
|
163
|
+
try {
|
|
164
|
+
await fetchConnectionState(instance);
|
|
165
|
+
return;
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
await this.createInstance(instance);
|
|
169
|
+
},
|
|
150
170
|
async setWebhook(instance, url, events) {
|
|
151
171
|
await request("setWebhook", "POST", `/webhook/set/${instance}`, {
|
|
152
172
|
webhook: { enabled: true, url, events, webhookByEvents: false, webhookBase64: false }
|
|
@@ -219,6 +239,9 @@ function parseWebhookEvent(payload) {
|
|
|
219
239
|
if (typeof state === "string" && state.length > 0) return { kind: "connection", state };
|
|
220
240
|
return { kind: "ignored", reason: "connection.update sem state" };
|
|
221
241
|
}
|
|
242
|
+
if (event === "group-participants.update") {
|
|
243
|
+
return parseGroupParticipants(data);
|
|
244
|
+
}
|
|
222
245
|
if (event !== "messages.upsert") {
|
|
223
246
|
return { kind: "ignored", reason: `evento n\xE3o tratado: ${event}` };
|
|
224
247
|
}
|
|
@@ -231,6 +254,32 @@ function parseWebhookEvent(payload) {
|
|
|
231
254
|
}
|
|
232
255
|
return lastIgnored;
|
|
233
256
|
}
|
|
257
|
+
function parseGroupParticipants(data) {
|
|
258
|
+
if (!isRecord(data)) return { kind: "ignored", reason: "group-participants sem data" };
|
|
259
|
+
const groupJid = data["id"];
|
|
260
|
+
if (typeof groupJid !== "string" || groupJid.length === 0) {
|
|
261
|
+
return { kind: "ignored", reason: "group-participants sem id (groupJid)" };
|
|
262
|
+
}
|
|
263
|
+
const rawParticipants = data["participants"];
|
|
264
|
+
if (!Array.isArray(rawParticipants)) {
|
|
265
|
+
return { kind: "ignored", reason: "group-participants sem participants[]" };
|
|
266
|
+
}
|
|
267
|
+
const participants = [];
|
|
268
|
+
for (const p of rawParticipants) {
|
|
269
|
+
if (typeof p === "string" && p.length > 0) participants.push(p);
|
|
270
|
+
}
|
|
271
|
+
if (participants.length === 0) {
|
|
272
|
+
return { kind: "ignored", reason: "group-participants sem participantes v\xE1lidos" };
|
|
273
|
+
}
|
|
274
|
+
const actionRaw = data["action"];
|
|
275
|
+
const action = typeof actionRaw === "string" ? actionRaw : "unknown";
|
|
276
|
+
const authorRaw = data["author"];
|
|
277
|
+
const author = typeof authorRaw === "string" ? authorRaw : null;
|
|
278
|
+
return {
|
|
279
|
+
kind: "group_participants",
|
|
280
|
+
event: { groupJid, participants, action, author, raw: data }
|
|
281
|
+
};
|
|
282
|
+
}
|
|
234
283
|
export {
|
|
235
284
|
EvolutionApiError,
|
|
236
285
|
createEvolutionClient,
|
package/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/api/phone.ts","../src/api/ids.ts","../src/api/client.ts","../src/api/webhook-parser.ts"],"sourcesContent":["// src/errors.ts — erro de domínio canônico do SDK.\n//\n// ChatError carrega um `code` estável para mapear em HTTP nas rotas sem depender\n// de instanceof entre bundles (cada entry do tsup inline-iza a classe; usamos\n// `.code` para decidir o status). O erro de transporte da Evolution vive em\n// src/api/client.ts (EvolutionApiError), junto do cliente que o lança.\n\nexport type ChatErrorCode =\n | \"invalid_input\"\n | \"rate_limited\"\n | \"group_create_failed\"\n | \"send_failed\"\n | \"session_not_found\"\n | \"session_closed\"\n | \"disabled\"\n | \"unauthorized\"\n | \"store_error\"\n | \"webhook_invalid\";\n\nexport class ChatError extends Error {\n public readonly code: ChatErrorCode;\n public override readonly cause?: unknown;\n\n constructor(message: string, code: ChatErrorCode, cause?: unknown) {\n super(message);\n this.name = \"ChatError\";\n this.code = code;\n this.cause = cause;\n }\n}\n","import { ChatError } from \"../errors\";\n\nconst NON_DIGITS = /\\D+/g;\n\nexport function normalizePhone(raw: string, defaultDdi = \"55\"): string {\n const digits = raw.replace(NON_DIGITS, \"\");\n if (!digits) throw new ChatError(\"Telefone vazio\", \"invalid_input\");\n // Prefixo \"+\" explícito (E.164) sinaliza DDI já presente — ex.: \"+1 415 555 2671\" permanece \"14155552671\".\n const explicitDdi = raw.includes(\"+\");\n const withDdi = digits.length <= 11 && !explicitDdi && !digits.startsWith(defaultDdi) ? defaultDdi + digits : digits;\n if (withDdi.length < 10 || withDdi.length > 15) {\n throw new ChatError(\"Telefone inválido\", \"invalid_input\");\n }\n return withDdi;\n}\nexport function isValidPhone(digits: string): boolean {\n return digits.length >= 10 && digits.length <= 15;\n}\nexport function toWhatsappJid(phoneDigits: string): string {\n return `${phoneDigits}@s.whatsapp.net`;\n}\n","// src/api/ids.ts — geração de ids com webcrypto (isomórfico, sem node:crypto).\n\nconst SESSION_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\"; // sem 0/O/1/I\nconst SESSION_CODE_LENGTH = 4;\n// Rejection sampling: só aceita bytes < 248 (múltiplo de 31 ≤ 256), evitando viés de módulo.\nconst SESSION_ACCEPT_LIMIT = 256 - (256 % SESSION_ALPHABET.length);\n\nconst BASE64URL_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst TOKEN_BYTES = 24; // 24 bytes → exatamente 32 caracteres base64url, sem padding\n\nfunction randomChar(alphabet: string, acceptLimit: number): string {\n const buffer = new Uint8Array(1);\n for (;;) {\n globalThis.crypto.getRandomValues(buffer);\n const byte = buffer[0];\n if (byte !== undefined && byte < acceptLimit) {\n return alphabet.charAt(byte % alphabet.length);\n }\n }\n}\n\nexport function generateSessionCode(): string {\n return Array.from({ length: SESSION_CODE_LENGTH }, () => randomChar(SESSION_ALPHABET, SESSION_ACCEPT_LIMIT)).join(\"\");\n}\n\nexport function generateRealtimeToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n globalThis.crypto.getRandomValues(bytes);\n let token = \"\";\n let acc = 0;\n let bits = 0;\n for (const byte of bytes) {\n acc = (acc << 8) | byte;\n bits += 8;\n while (bits >= 6) {\n bits -= 6;\n token += BASE64URL_ALPHABET.charAt((acc >>> bits) & 0x3f);\n }\n }\n return token;\n}\n","// src/api/client.ts — Adapter REST da Evolution API v2 (fetch injetável p/ testes).\nimport { ChatError } from \"../errors\";\n\nexport interface EvolutionFetch { (url: string, init: RequestInit): Promise<Response>; } // ponto de injeção (DI)\n\nexport interface SendTextResult { waMessageId: string; }\nexport interface CreateGroupResult { groupJid: string; }\n\nexport interface EvolutionClient {\n sendText(instance: string, number: string, text: string): Promise<SendTextResult>;\n createGroup(instance: string, subject: string, participants: string[], description?: string): Promise<CreateGroupResult>;\n leaveGroup(instance: string, groupJid: string): Promise<void>;\n getConnectionState(instance: string): Promise<\"open\" | \"connecting\" | \"close\">;\n connectQR(instance: string): Promise<{ qrBase64: string | null; pairingCode: string | null }>;\n setWebhook(instance: string, url: string, events: string[]): Promise<void>;\n}\n\nexport class EvolutionApiError extends Error {\n constructor(readonly status: number, readonly body: string, operation: string) {\n super(`Evolution ${operation} falhou (${status})`);\n this.name = \"EvolutionApiError\";\n }\n}\n\ninterface JsonOk { operation: string; status: number; text: string; json: Record<string, unknown>; }\n\n// Lê path aninhado (ex.: \"key\", \"id\") sem lançar: devolve undefined se faltar ou não for objeto.\nfunction readPath(json: Record<string, unknown>, ...path: string[]): unknown {\n let cur: unknown = json;\n for (const key of path) {\n if (!cur || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\nexport function createEvolutionClient(cfg: { baseUrl: string; apiKey: string; fetchImpl?: EvolutionFetch }): EvolutionClient {\n const doFetch: EvolutionFetch = cfg.fetchImpl ?? fetch;\n const base = cfg.baseUrl.replace(/\\/+$/, \"\"); // normaliza p/ evitar double slash na concatenação\n const headers = { \"content-type\": \"application/json\", apikey: cfg.apiKey };\n\n async function request(operation: string, method: string, path: string, body?: unknown): Promise<Response> {\n const response = await doFetch(`${base}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n if (!response.ok) throw new EvolutionApiError(response.status, await response.text(), operation);\n return response;\n }\n\n // Contrato: toda falha de HTTP/parse/shape é EvolutionApiError (a ponte captura via instanceof).\n async function requestJson(operation: string, method: string, path: string, body?: unknown): Promise<JsonOk> {\n const response = await request(operation, method, path, body);\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new EvolutionApiError(response.status, text, operation);\n }\n if (!parsed || typeof parsed !== \"object\") throw new EvolutionApiError(response.status, text, operation);\n return { operation, status: response.status, text, json: parsed as Record<string, unknown> };\n }\n\n function requireString(res: JsonOk, ...path: string[]): string {\n const value = readPath(res.json, ...path);\n if (typeof value !== \"string\" || value === \"\") throw new EvolutionApiError(res.status, res.text, res.operation);\n return value;\n }\n\n return {\n async sendText(instance, number, text) {\n const res = await requestJson(\"sendText\", \"POST\", `/message/sendText/${instance}`, { number, text });\n return { waMessageId: requireString(res, \"key\", \"id\") };\n },\n\n async createGroup(instance, subject, participants, description) {\n const payload = description === undefined ? { subject, participants } : { subject, participants, description };\n const res = await requestJson(\"createGroup\", \"POST\", `/group/create/${instance}`, payload);\n const groupJid = readPath(res.json, \"group\", \"id\") ?? res.json.id; // mesmo derivador de antes, sem cast\n if (typeof groupJid !== \"string\" || groupJid === \"\") {\n throw new ChatError(\"Evolution createGroup: groupJid ausente/inválido na resposta\", \"send_failed\");\n }\n return { groupJid };\n },\n\n async leaveGroup(instance, groupJid) {\n await request(\"leaveGroup\", \"DELETE\", `/group/leave/${instance}`, { groupId: groupJid });\n },\n\n async getConnectionState(instance) {\n const res = await requestJson(\"getConnectionState\", \"GET\", `/instance/connectionState/${instance}`);\n const state = readPath(res.json, \"instance\", \"state\");\n if (state === \"open\" || state === \"connecting\" || state === \"close\") return state;\n throw new EvolutionApiError(res.status, res.text, res.operation);\n },\n\n async connectQR(instance) {\n const res = await requestJson(\"connectQR\", \"GET\", `/instance/connect/${instance}`);\n const base64 = readPath(res.json, \"base64\");\n const pairingCode = readPath(res.json, \"pairingCode\");\n const code = readPath(res.json, \"code\");\n return {\n qrBase64: typeof base64 === \"string\" ? base64 : null,\n pairingCode: typeof pairingCode === \"string\" ? pairingCode : typeof code === \"string\" ? code : null,\n };\n },\n\n async setWebhook(instance, url, events) {\n await request(\"setWebhook\", \"POST\", `/webhook/set/${instance}`, {\n webhook: { enabled: true, url, events, webhookByEvents: false, webhookBase64: false },\n });\n },\n };\n}\n","// src/api/webhook-parser.ts — normalização defensiva de webhooks da Evolution API v2.\n//\n// Contrato: parseWebhookEvent(payload: unknown): ParsedWebhook.\n// NUNCA lança exceção — qualquer payload inesperado/inválido vira { kind: \"ignored\", reason }.\n\nimport type { InboundMessage } from \"../types\";\n\nexport type ParsedWebhook =\n | { kind: \"message\"; event: InboundMessage }\n | { kind: \"connection\"; state: string }\n | { kind: \"ignored\"; reason: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// Texto só sai de `conversation` ou `extendedTextMessage.text`; string vazia/só espaços → null.\nfunction textFromMessage(message: unknown): string | null {\n if (!isRecord(message)) return null;\n const conversation = message[\"conversation\"];\n if (typeof conversation === \"string\") {\n const trimmed = conversation.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n const extended = message[\"extendedTextMessage\"];\n if (isRecord(extended)) {\n const text = extended[\"text\"];\n if (typeof text === \"string\") {\n const trimmed = text.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n }\n return null;\n}\n\n// messageTimestamp em segundos (aceita número ou string numérica; ausente/inválido → 0).\nfunction timestampSeconds(value: unknown): number {\n const seconds = typeof value === \"number\" ? value : typeof value === \"string\" ? Number(value) : Number.NaN;\n return Number.isFinite(seconds) ? Math.floor(seconds) : 0;\n}\n\nfunction parseMessageItem(item: unknown): ParsedWebhook {\n if (!isRecord(item)) return { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n\n const key = isRecord(item[\"key\"]) ? item[\"key\"] : null;\n const waMessageId = key?.[\"id\"];\n const jid = key?.[\"remoteJid\"];\n if (typeof waMessageId !== \"string\" || waMessageId.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.id\" };\n }\n if (typeof jid !== \"string\" || jid.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.remoteJid\" };\n }\n\n const text = textFromMessage(item[\"message\"]);\n if (text === null) {\n return { kind: \"ignored\", reason: \"mensagem sem texto (conversation/extendedTextMessage)\" };\n }\n\n const senderJid = key?.[\"participant\"];\n return {\n kind: \"message\",\n event: {\n waMessageId,\n jid,\n fromMe: key?.[\"fromMe\"] === true,\n senderJid: typeof senderJid === \"string\" ? senderJid : null,\n text,\n timestamp: timestampSeconds(item[\"messageTimestamp\"]),\n raw: item,\n },\n };\n}\n\nexport function parseWebhookEvent(payload: unknown): ParsedWebhook {\n if (!isRecord(payload)) return { kind: \"ignored\", reason: \"payload não é um objeto\" };\n\n const event = payload[\"event\"];\n if (typeof event !== \"string\") return { kind: \"ignored\", reason: \"payload sem campo event\" };\n\n const data = payload[\"data\"];\n\n if (event === \"connection.update\") {\n const state = isRecord(data) ? data[\"state\"] : undefined;\n if (typeof state === \"string\" && state.length > 0) return { kind: \"connection\", state };\n return { kind: \"ignored\", reason: \"connection.update sem state\" };\n }\n\n if (event !== \"messages.upsert\") {\n return { kind: \"ignored\", reason: `evento não tratado: ${event}` };\n }\n\n // Evolution v2 pode mandar `data` como objeto único ou array de mensagens (queue-flush/bulk).\n // Contrato de evento único: devolve o PRIMEIRO item válido, não `items[0]` — senão um item\n // nulo/inválido na frente descartaria silenciosamente as mensagens válidas seguintes.\n const items: readonly unknown[] = Array.isArray(data) ? data : [data];\n let lastIgnored: ParsedWebhook = { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n for (const item of items) {\n const parsed = parseMessageItem(item);\n if (parsed.kind !== \"ignored\") return parsed;\n lastIgnored = parsed;\n }\n return lastIgnored;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/api/phone.ts","../src/api/ids.ts","../src/api/client.ts","../src/api/webhook-parser.ts"],"sourcesContent":["// src/errors.ts — erro de domínio canônico do SDK.\n//\n// ChatError carrega um `code` estável para mapear em HTTP nas rotas sem depender\n// de instanceof entre bundles (cada entry do tsup inline-iza a classe; usamos\n// `.code` para decidir o status). O erro de transporte da Evolution vive em\n// src/api/client.ts (EvolutionApiError), junto do cliente que o lança.\n\nexport type ChatErrorCode =\n | \"invalid_input\"\n | \"rate_limited\"\n | \"group_create_failed\"\n | \"send_failed\"\n | \"session_not_found\"\n | \"session_closed\"\n | \"disabled\"\n | \"unauthorized\"\n | \"store_error\"\n | \"webhook_invalid\";\n\nexport class ChatError extends Error {\n public readonly code: ChatErrorCode;\n public override readonly cause?: unknown;\n\n constructor(message: string, code: ChatErrorCode, cause?: unknown) {\n super(message);\n this.name = \"ChatError\";\n this.code = code;\n this.cause = cause;\n }\n}\n","import { ChatError } from \"../errors\";\n\nconst NON_DIGITS = /\\D+/g;\n\nexport function normalizePhone(raw: string, defaultDdi = \"55\"): string {\n const digits = raw.replace(NON_DIGITS, \"\");\n if (!digits) throw new ChatError(\"Telefone vazio\", \"invalid_input\");\n // Prefixo \"+\" explícito (E.164) sinaliza DDI já presente — ex.: \"+1 415 555 2671\" permanece \"14155552671\".\n const explicitDdi = raw.includes(\"+\");\n const withDdi = digits.length <= 11 && !explicitDdi && !digits.startsWith(defaultDdi) ? defaultDdi + digits : digits;\n if (withDdi.length < 10 || withDdi.length > 15) {\n throw new ChatError(\"Telefone inválido\", \"invalid_input\");\n }\n return withDdi;\n}\nexport function isValidPhone(digits: string): boolean {\n return digits.length >= 10 && digits.length <= 15;\n}\nexport function toWhatsappJid(phoneDigits: string): string {\n return `${phoneDigits}@s.whatsapp.net`;\n}\n","// src/api/ids.ts — geração de ids com webcrypto (isomórfico, sem node:crypto).\n\nconst SESSION_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\"; // sem 0/O/1/I\nconst SESSION_CODE_LENGTH = 4;\n// Rejection sampling: só aceita bytes < 248 (múltiplo de 31 ≤ 256), evitando viés de módulo.\nconst SESSION_ACCEPT_LIMIT = 256 - (256 % SESSION_ALPHABET.length);\n\nconst BASE64URL_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst TOKEN_BYTES = 24; // 24 bytes → exatamente 32 caracteres base64url, sem padding\n\nfunction randomChar(alphabet: string, acceptLimit: number): string {\n const buffer = new Uint8Array(1);\n for (;;) {\n globalThis.crypto.getRandomValues(buffer);\n const byte = buffer[0];\n if (byte !== undefined && byte < acceptLimit) {\n return alphabet.charAt(byte % alphabet.length);\n }\n }\n}\n\nexport function generateSessionCode(): string {\n return Array.from({ length: SESSION_CODE_LENGTH }, () => randomChar(SESSION_ALPHABET, SESSION_ACCEPT_LIMIT)).join(\"\");\n}\n\nexport function generateRealtimeToken(): string {\n const bytes = new Uint8Array(TOKEN_BYTES);\n globalThis.crypto.getRandomValues(bytes);\n let token = \"\";\n let acc = 0;\n let bits = 0;\n for (const byte of bytes) {\n acc = (acc << 8) | byte;\n bits += 8;\n while (bits >= 6) {\n bits -= 6;\n token += BASE64URL_ALPHABET.charAt((acc >>> bits) & 0x3f);\n }\n }\n return token;\n}\n","// src/api/client.ts — Adapter REST da Evolution API v2 (fetch injetável p/ testes).\nimport { ChatError } from \"../errors\";\n\nexport interface EvolutionFetch { (url: string, init: RequestInit): Promise<Response>; } // ponto de injeção (DI)\n\nexport interface SendTextResult { waMessageId: string; }\nexport interface CreateGroupResult { groupJid: string; }\n\nexport interface EvolutionClient {\n sendText(instance: string, number: string, text: string): Promise<SendTextResult>;\n createGroup(instance: string, subject: string, participants: string[], description?: string): Promise<CreateGroupResult>;\n setGroupPicture(instance: string, groupJid: string, image: string): Promise<void>;\n leaveGroup(instance: string, groupJid: string): Promise<void>;\n /** Encerra a sessão WhatsApp da instância (desvincula o dispositivo). */\n logout(instance: string): Promise<void>;\n getConnectionState(instance: string): Promise<\"open\" | \"connecting\" | \"close\">;\n connectQR(instance: string): Promise<{ qrBase64: string | null; pairingCode: string | null }>;\n setWebhook(instance: string, url: string, events: string[]): Promise<void>;\n /** Cria a instância na Evolution (one-time). Exige a apiKey com permissão de criação. */\n createInstance(instance: string, integration?: \"WHATSAPP-BAILEYS\" | \"WHATSAPP-BUSINESS\"): Promise<void>;\n /**\n * Garante que a instância existe (idempotente): se `getConnectionState` responde\n * (qualquer estado), não faz nada; só chama `createInstance` quando a leitura falha\n * (instância inexistente). Assim o LMS gerencia o ciclo de vida sem criar manualmente\n * na Evolution — o único passo humano é escanear o QR de pareamento.\n */\n ensureInstance(instance: string): Promise<void>;\n}\n\nexport class EvolutionApiError extends Error {\n constructor(readonly status: number, readonly body: string, operation: string) {\n super(`Evolution ${operation} falhou (${status})`);\n this.name = \"EvolutionApiError\";\n }\n}\n\ninterface JsonOk { operation: string; status: number; text: string; json: Record<string, unknown>; }\n\n// Lê path aninhado (ex.: \"key\", \"id\") sem lançar: devolve undefined se faltar ou não for objeto.\nfunction readPath(json: Record<string, unknown>, ...path: string[]): unknown {\n let cur: unknown = json;\n for (const key of path) {\n if (!cur || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\nexport function createEvolutionClient(cfg: { baseUrl: string; apiKey: string; fetchImpl?: EvolutionFetch }): EvolutionClient {\n const doFetch: EvolutionFetch = cfg.fetchImpl ?? fetch;\n const base = cfg.baseUrl.replace(/\\/+$/, \"\"); // normaliza p/ evitar double slash na concatenação\n const headers = { \"content-type\": \"application/json\", apikey: cfg.apiKey };\n\n async function request(operation: string, method: string, path: string, body?: unknown): Promise<Response> {\n const response = await doFetch(`${base}${path}`, {\n method,\n headers,\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n });\n if (!response.ok) throw new EvolutionApiError(response.status, await response.text(), operation);\n return response;\n }\n\n // Contrato: toda falha de HTTP/parse/shape é EvolutionApiError (a ponte captura via instanceof).\n async function requestJson(operation: string, method: string, path: string, body?: unknown): Promise<JsonOk> {\n const response = await request(operation, method, path, body);\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new EvolutionApiError(response.status, text, operation);\n }\n if (!parsed || typeof parsed !== \"object\") throw new EvolutionApiError(response.status, text, operation);\n return { operation, status: response.status, text, json: parsed as Record<string, unknown> };\n }\n\n function requireString(res: JsonOk, ...path: string[]): string {\n const value = readPath(res.json, ...path);\n if (typeof value !== \"string\" || value === \"\") throw new EvolutionApiError(res.status, res.text, res.operation);\n return value;\n }\n\n // Estado da instância (open/connecting/close); lança EvolutionApiError se a resposta\n // for inválida OU a instância não existir (a Evolution devolve erro nesse caso).\n async function fetchConnectionState(instance: string): Promise<\"open\" | \"connecting\" | \"close\"> {\n const res = await requestJson(\"getConnectionState\", \"GET\", `/instance/connectionState/${instance}`);\n const state = readPath(res.json, \"instance\", \"state\");\n if (state === \"open\" || state === \"connecting\" || state === \"close\") return state;\n throw new EvolutionApiError(res.status, res.text, res.operation);\n }\n\n return {\n async sendText(instance, number, text) {\n const res = await requestJson(\"sendText\", \"POST\", `/message/sendText/${instance}`, { number, text });\n return { waMessageId: requireString(res, \"key\", \"id\") };\n },\n\n async createGroup(instance, subject, participants, description) {\n const payload = description === undefined ? { subject, participants } : { subject, participants, description };\n const res = await requestJson(\"createGroup\", \"POST\", `/group/create/${instance}`, payload);\n const groupJid = readPath(res.json, \"group\", \"id\") ?? res.json.id; // mesmo derivador de antes, sem cast\n if (typeof groupJid !== \"string\" || groupJid === \"\") {\n throw new ChatError(\"Evolution createGroup: groupJid ausente/inválido na resposta\", \"send_failed\");\n }\n return { groupJid };\n },\n\n async setGroupPicture(instance, groupJid, image) {\n // Evolution v2: POST /group/updateGroupPicture/{instance} com { groupJid, image }\n // (image = URL pública ou base64). Imagem é cosmética: qualquer falha vira EvolutionApiError.\n await request(\"setGroupPicture\", \"POST\", `/group/updateGroupPicture/${instance}`, { groupJid, image });\n },\n\n async leaveGroup(instance, groupJid) {\n await request(\"leaveGroup\", \"DELETE\", `/group/leave/${instance}`, { groupId: groupJid });\n },\n\n async logout(instance) {\n await request(\"logout\", \"DELETE\", `/instance/logout/${instance}`);\n },\n\n async getConnectionState(instance) {\n return fetchConnectionState(instance);\n },\n\n async connectQR(instance) {\n const res = await requestJson(\"connectQR\", \"GET\", `/instance/connect/${instance}`);\n const base64 = readPath(res.json, \"base64\");\n const pairingCode = readPath(res.json, \"pairingCode\");\n const code = readPath(res.json, \"code\");\n return {\n qrBase64: typeof base64 === \"string\" ? base64 : null,\n pairingCode: typeof pairingCode === \"string\" ? pairingCode : typeof code === \"string\" ? code : null,\n };\n },\n\n async createInstance(instance, integration = \"WHATSAPP-BAILEYS\") {\n await request(\"createInstance\", \"POST\", \"/instance/create\", { instanceName: instance, integration });\n },\n\n async ensureInstance(instance) {\n try {\n await fetchConnectionState(instance); // existe (qualquer estado) → ok, não faz nada\n return;\n } catch {\n // instância inexistente (ou erro de leitura) → tenta criar\n }\n await this.createInstance(instance);\n },\n\n async setWebhook(instance, url, events) {\n await request(\"setWebhook\", \"POST\", `/webhook/set/${instance}`, {\n webhook: { enabled: true, url, events, webhookByEvents: false, webhookBase64: false },\n });\n },\n };\n}\n","// src/api/webhook-parser.ts — normalização defensiva de webhooks da Evolution API v2.\n//\n// Contrato: parseWebhookEvent(payload: unknown): ParsedWebhook.\n// NUNCA lança exceção — qualquer payload inesperado/inválido vira { kind: \"ignored\", reason }.\n\nimport type { GroupParticipantChange, InboundMessage } from \"../types\";\n\nexport type ParsedWebhook =\n | { kind: \"message\"; event: InboundMessage }\n | { kind: \"connection\"; state: string }\n | { kind: \"group_participants\"; event: GroupParticipantChange }\n | { kind: \"ignored\"; reason: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// Texto só sai de `conversation` ou `extendedTextMessage.text`; string vazia/só espaços → null.\nfunction textFromMessage(message: unknown): string | null {\n if (!isRecord(message)) return null;\n const conversation = message[\"conversation\"];\n if (typeof conversation === \"string\") {\n const trimmed = conversation.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n const extended = message[\"extendedTextMessage\"];\n if (isRecord(extended)) {\n const text = extended[\"text\"];\n if (typeof text === \"string\") {\n const trimmed = text.trim();\n return trimmed.length > 0 ? trimmed : null;\n }\n }\n return null;\n}\n\n// messageTimestamp em segundos (aceita número ou string numérica; ausente/inválido → 0).\nfunction timestampSeconds(value: unknown): number {\n const seconds = typeof value === \"number\" ? value : typeof value === \"string\" ? Number(value) : Number.NaN;\n return Number.isFinite(seconds) ? Math.floor(seconds) : 0;\n}\n\nfunction parseMessageItem(item: unknown): ParsedWebhook {\n if (!isRecord(item)) return { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n\n const key = isRecord(item[\"key\"]) ? item[\"key\"] : null;\n const waMessageId = key?.[\"id\"];\n const jid = key?.[\"remoteJid\"];\n if (typeof waMessageId !== \"string\" || waMessageId.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.id\" };\n }\n if (typeof jid !== \"string\" || jid.length === 0) {\n return { kind: \"ignored\", reason: \"mensagem sem key.remoteJid\" };\n }\n\n const text = textFromMessage(item[\"message\"]);\n if (text === null) {\n return { kind: \"ignored\", reason: \"mensagem sem texto (conversation/extendedTextMessage)\" };\n }\n\n const senderJid = key?.[\"participant\"];\n return {\n kind: \"message\",\n event: {\n waMessageId,\n jid,\n fromMe: key?.[\"fromMe\"] === true,\n senderJid: typeof senderJid === \"string\" ? senderJid : null,\n text,\n timestamp: timestampSeconds(item[\"messageTimestamp\"]),\n raw: item,\n },\n };\n}\n\nexport function parseWebhookEvent(payload: unknown): ParsedWebhook {\n if (!isRecord(payload)) return { kind: \"ignored\", reason: \"payload não é um objeto\" };\n\n const event = payload[\"event\"];\n if (typeof event !== \"string\") return { kind: \"ignored\", reason: \"payload sem campo event\" };\n\n const data = payload[\"data\"];\n\n if (event === \"connection.update\") {\n const state = isRecord(data) ? data[\"state\"] : undefined;\n if (typeof state === \"string\" && state.length > 0) return { kind: \"connection\", state };\n return { kind: \"ignored\", reason: \"connection.update sem state\" };\n }\n\n if (event === \"group-participants.update\") {\n return parseGroupParticipants(data);\n }\n\n if (event !== \"messages.upsert\") {\n return { kind: \"ignored\", reason: `evento não tratado: ${event}` };\n }\n\n // Evolution v2 pode mandar `data` como objeto único ou array de mensagens (queue-flush/bulk).\n // Contrato de evento único: devolve o PRIMEIRO item válido, não `items[0]` — senão um item\n // nulo/inválido na frente descartaria silenciosamente as mensagens válidas seguintes.\n const items: readonly unknown[] = Array.isArray(data) ? data : [data];\n let lastIgnored: ParsedWebhook = { kind: \"ignored\", reason: \"messages.upsert sem data válido\" };\n for (const item of items) {\n const parsed = parseMessageItem(item);\n if (parsed.kind !== \"ignored\") return parsed;\n lastIgnored = parsed;\n }\n return lastIgnored;\n}\n\n/**\n * Normaliza o evento `group-participants.update` da Evolution:\n * { id: \"…@g.us\", participants: [\"…@s.whatsapp.net\"], author: \"…@s.whatsapp.net\",\n * action: \"add\" | \"remove\" | \"leave\" | \"promote\" | \"demote\" }\n * Campos faltantes/fora do formato viram { kind: \"ignored\" } (nunca lança).\n */\nfunction parseGroupParticipants(data: unknown): ParsedWebhook {\n if (!isRecord(data)) return { kind: \"ignored\", reason: \"group-participants sem data\" };\n\n const groupJid = data[\"id\"];\n if (typeof groupJid !== \"string\" || groupJid.length === 0) {\n return { kind: \"ignored\", reason: \"group-participants sem id (groupJid)\" };\n }\n\n const rawParticipants = data[\"participants\"];\n if (!Array.isArray(rawParticipants)) {\n return { kind: \"ignored\", reason: \"group-participants sem participants[]\" };\n }\n const participants: string[] = [];\n for (const p of rawParticipants) {\n if (typeof p === \"string\" && p.length > 0) participants.push(p);\n }\n if (participants.length === 0) {\n return { kind: \"ignored\", reason: \"group-participants sem participantes válidos\" };\n }\n\n const actionRaw = data[\"action\"];\n const action = typeof actionRaw === \"string\" ? actionRaw : \"unknown\";\n const authorRaw = data[\"author\"];\n const author = typeof authorRaw === \"string\" ? authorRaw : null;\n\n return {\n kind: \"group_participants\",\n event: { groupJid, participants, action, author, raw: data },\n };\n}\n"],"mappings":"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","names":[]}
|