/** * SDK TypeScript minimalista do Vértice (sem dependências). * * const client = new Vertice({ baseUrl: 'https://api.seudominio.com', apiKey: 'vtc_live_...' }); * await client.sendText({ instanceId, to: '5511999999999', text: 'Olá!' }); * * Para rotas de painel, use o cookie de sessão + CSRF (não recomendado fora do browser). */ export interface VerticeOptions { baseUrl: string; apiKey?: string; fetch?: typeof fetch; } export interface Instance { id: string; tenantId: string | null; name: string; status: 'created' | 'connecting' | 'qr' | 'connected' | 'disconnected' | 'logged_out'; phone: string | null; webhookUrl: string | null; webhookSecret?: string; webhookEvents: string[] | null; webhookNotifyFromMe: boolean; createdAt: string; updatedAt: string; } export interface MessageResult { id: string | null; messageId: string | null; zaapId: string; status: 'sent' | 'queued'; to: string; timestamp?: number; createdAt?: string; } export interface QueueJob { id: string; status: 'queued' | 'sending' | 'sent' | 'delivered' | 'read' | 'failed' | 'canceled'; toJid: string; attempts: number; createdAt: string; messageId: string | null; error: string | null; } export interface GroupSummary { id: string; subject: string; size: number; owner: string | null; announce: boolean; restrict: boolean; } export class VerticeError extends Error { constructor( message: string, public readonly status: number, public readonly code: string, ) { super(message); this.name = 'VerticeError'; } } export class Vertice { private readonly baseUrl: string; private readonly apiKey?: string; private readonly fetchImpl: typeof fetch; constructor(options: VerticeOptions) { this.baseUrl = options.baseUrl.replace(/\/+$/, ''); this.apiKey = options.apiKey; this.fetchImpl = options.fetch ?? fetch; } private async request( path: string, options: { method?: string; body?: unknown; idempotencyKey?: string } = {}, ): Promise { const headers: Record = {}; if (this.apiKey) headers['x-api-key'] = this.apiKey; if (options.body !== undefined) headers['content-type'] = 'application/json'; if (options.idempotencyKey) headers['idempotency-key'] = options.idempotencyKey; const response = await this.fetchImpl(`${this.baseUrl}${path}`, { method: options.method ?? 'GET', headers, body: options.body === undefined ? undefined : JSON.stringify(options.body), }); if (response.status === 204) return undefined as T; const payload = await response.json().catch(() => null); if (!response.ok) { throw new VerticeError( payload?.error?.message ?? `HTTP ${response.status}`, response.status, payload?.error?.code ?? '', ); } return (payload?.data ?? payload) as T; } /* instâncias */ listInstances(): Promise { return this.request('/instances'); } createInstance(input: { name: string; webhookUrl?: string }): Promise { return this.request('/instances', { method: 'POST', body: input }); } getInstance(id: string): Promise { return this.request(`/instances/${id}`); } connect(id: string): Promise { return this.request(`/instances/${id}/connect`, { method: 'POST' }); } disconnect(id: string): Promise { return this.request(`/instances/${id}/disconnect`, { method: 'POST' }); } logout(id: string): Promise { return this.request(`/instances/${id}/logout`, { method: 'POST' }); } qr(id: string): Promise<{ status: string; qr: string | null; dataUrl: string | null }> { return this.request(`/instances/${id}/qr`); } /* mensagens */ sendText(input: { instanceId: string; to: string; text: string; replyTo?: string; mentions?: string[]; queue?: boolean; delayMessage?: number; delayTyping?: number; idempotencyKey?: string; }): Promise { const { instanceId, ...body } = input; return this.request(`/instances/${instanceId}/messages`, { method: 'POST', body, idempotencyKey: input.idempotencyKey, }); } sendMedia(input: { instanceId: string; to: string; kind: 'image' | 'video' | 'audio' | 'document'; url?: string; base64?: string; caption?: string; fileName?: string; mimetype?: string; ptt?: boolean; }): Promise { const { instanceId, ...body } = input; return this.request(`/instances/${instanceId}/media`, { method: 'POST', body }); } react(instanceId: string, messageId: string, emoji: string, jid?: string): Promise { return this.request(`/instances/${instanceId}/messages/${messageId}/reaction`, { method: 'POST', body: { emoji, ...(jid ? { jid } : {}) }, }); } markRead(instanceId: string, jid: string, ids?: string[]): Promise { return this.request(`/instances/${instanceId}/chats/read`, { method: 'POST', body: { jid, ...(ids ? { ids } : {}) }, }); } typing(instanceId: string, jid: string, state: 'composing' | 'paused' = 'composing'): Promise { return this.request(`/instances/${instanceId}/presence`, { method: 'POST', body: { jid, state }, }); } /* fila */ listQueue(instanceId: string, status?: QueueJob['status']): Promise { const query = status ? `?status=${encodeURIComponent(status)}` : ''; return this.request(`/instances/${instanceId}/queue${query}`); } cancelJob(instanceId: string, jobId: string): Promise { return this.request(`/instances/${instanceId}/queue/${jobId}`, { method: 'DELETE' }); } clearQueue(instanceId: string): Promise<{ cleared: number }> { return this.request(`/instances/${instanceId}/queue`, { method: 'DELETE' }); } /* grupos */ listGroups(instanceId: string): Promise { return this.request(`/instances/${instanceId}/groups`); } createGroup(instanceId: string, subject: string, participants: string[]): Promise<{ id: string }> { return this.request(`/instances/${instanceId}/groups`, { method: 'POST', body: { subject, participants }, }); } groupMetadata(instanceId: string, jid: string): Promise> { return this.request(`/instances/${instanceId}/groups/${encodeURIComponent(jid)}`); } groupParticipants( instanceId: string, jid: string, participants: string[], action: 'add' | 'remove' | 'promote' | 'demote', ): Promise { return this.request(`/instances/${instanceId}/groups/${encodeURIComponent(jid)}/participants`, { method: 'POST', body: { participants, action }, }); } groupInvite(instanceId: string, jid: string): Promise<{ code: string; link: string }> { return this.request(`/instances/${instanceId}/groups/${encodeURIComponent(jid)}/invite`); } /* conversas e webhooks */ listChats(instanceId: string): Promise { return this.request(`/instances/${instanceId}/chats`); } chatMessages(instanceId: string, jid: string, limit = 200): Promise { return this.request( `/instances/${instanceId}/chats/messages?jid=${encodeURIComponent(jid)}&limit=${limit}`, ); } deliveries(instanceId: string, status?: 'pendente' | 'entregue' | 'falhou'): Promise { const query = status ? `?status=${status}` : ''; return this.request(`/instances/${instanceId}/webhook-deliveries${query}`); } retryDeliveries(instanceId: string, ids?: string[]): Promise<{ requeued: number }> { return this.request(`/instances/${instanceId}/webhook-deliveries/retry`, { method: 'POST', body: { ids: ids ?? [] }, }); } }