@omnizapi/connect
v1.1.1
Published
Omnizapi SDK for browser applications
Readme
@omnizapi/connect
SDK Omnizapi para conectar canais Meta WhatsApp, Messenger, Instagram, Telegram e Z-API WhatsApp em aplicacoes web. Coleta o resultado da autorizacao; seu backend valida e finaliza a conexao. Nao envie secret keys ao frontend.
Versao atual: 1.1.1. As mudancas desta versao estao no changelog, disponivel com o pacote publicado.
Instalacao e importacao
eu
npm install @omnizapi/connectimport Omnizapi, { Environment, OmnizapiError } from "@omnizapi/connect";
// Alternativa: import { Omnizapi } from "@omnizapi/connect";
import type {
OmnizapiClient,
OmnizapiClientProps,
ConnectionOptions,
ConnectionResult,
ConnectionSuccess,
ConnectionFailure,
ConnectionFailureReason,
OmnizapiErrorCode,
ChannelType,
} from "@omnizapi/connect";CommonJS: const { Omnizapi } = require("@omnizapi/connect").
No navegador, o bundle expoe o global Omnizapi:
<script src="https://unpkg.com/@omnizapi/[email protected]/dist/omnizapi.min.js"></script>O CDN usa uma versao fixa; homologue sua integracao antes do deploy. Imports ESM/CommonJS e newClient sao seguros em SSR; connect exige navegador e rejeita com BROWSER_REQUIRED no servidor. O bundle de script e exclusivo do navegador.
Uso
Crie/reutilize o cliente com Omnizapi.newClient({ publicKey: "sua-public-key" }) dentro do try do handler de clique, para que INVALID_CONFIGURATION tambem seja tratado pela UI.
Dispare client.connect({ channelId }) diretamente no clique, sem outro await antes, nunca durante renderizacao, montagem ou importacao. Os exemplos completos incluem UI, try/catch/finally, tratamento de reason e envio ao backend:
| Exemplo | Uso |
| --- | --- |
| TypeScript basico | Botao e mensagem de status no DOM |
| HTML com CDN | Pagina sem bundler |
| React / Next.js | 'use client', UI de conexao por componente |
| Angular | Componente com guarda de navegador para SSR |
| Vue | SFC com guarda de navegador para SSR |
| Opcoes | SANDBOX, URLs explicitas, logo, fluxo padrao/direto e timeout |
| Erros | Mensagens para todos os motivos e codigos tipados |
Os links abrem o codigo-fonte dos exemplos da versao 1.1.1, incluido no pacote npm. Nao sao demos hospedadas: adapte as chaves, o canal e o backend na sua aplicacao.
Os arquivos examples/error-handling.ts e examples/react.tsx demonstram AbortSignal. No React, o cliente fica em useRef, e so e criado no try do clique; o cleanup de useEffect aborta a tentativa/requisicao pendente sem atualizar a UI desmontada. O exemplo de erros usa Record<OmnizapiErrorCode, string> exaustivo e permite cancelar apenas a autorizacao, nao a finalizacao no backend. Nao ha botao para limpar a quarentena nativa.
Os exemplos usam /api/connect-channel como rota da sua aplicacao, nao fornecida por este pacote. Ela deve autenticar a sessao, validar o resultado e responder com HTTP 2xx somente quando a finalizacao tiver sido confirmada. Os exemplos aguardam fetch e verificam response.ok antes de exibir sucesso. Adapte a autenticacao e a protecao CSRF ao seu backend. Nao registre resultados, tokens, codigos OAuth ou erros brutos que possam conter URLs sensiveis.
API
Omnizapi.newClient(props): OmnizapiClient
OmnizapiClientProps:
| Campo | Tipo | Descricao |
| --- | --- | --- |
| publicKey | string | Obrigatorio; chave publica Omnizapi |
| logo | string | URL opcional da logo no popup proxy |
| environment | Environment | Padrao Environment.PRODUCTION |
| apiUrl | string | Sobrescreve a URL base da API |
| frontUrl | string | Sobrescreve a URL base do frontend de conexao |
| Ambiente | API padrao | Frontend padrao |
| --- | --- | --- |
| PRODUCTION | https://api.omni.z-api.io | https://app.omni.z-api.io |
| STAGING | https://api.staging.omni.z-api.io | https://app.staging.omni.z-api.io |
| SANDBOX | Exige URL configurada | Exige URL configurada |
Precedencia por URL: props > OMNIZAPI_API_URL / OMNIZAPI_FRONT_URL no build do SDK > padroes do ambiente. Variaveis do processo da aplicacao nao reconfiguram um bundle ja compilado; para isso use props. SANDBOX exige ambas as URLs, via props ou build. URLs base devem ser HTTP(S), sem credenciais, query ou fragmento; use HTTPS em producao. Configuracao invalida pode lancar OmnizapiError com INVALID_CONFIGURATION na criacao do cliente.
client.connect(options): Promise<ConnectionResult>
ConnectionOptions:
| Campo | Tipo | Descricao |
| --- | --- | --- |
| channelId | string | Obrigatorio; canal a conectar |
| direct | boolean | Solicita o Meta WhatsApp Embedded Signup nativo na propria pagina, sem o popup proxy. Com direct o SDK nao reserva janela no clique, porque o dialogo da Meta abre a sua e navegadores concedem um popup por clique. Use so em pagina onde o clique e dedicado a conexao (o frontend de conexao faz isso em /oauth/proxy-cloud-api); em outros canais, ou quando a pagina nao pode rodar o nativo, a janela do proxy e aberta na navegacao e pode ser bloqueada. Sem direct, todo Meta WhatsApp usa a janela reservada, inclusive com appCustom e na origem do frontend de conexao |
| getQRCode | () => Promise<{ connected?: boolean; value?: string }> | Obrigatorio para Z_API_WHATSAPP: devolve o QR do canal, buscado no seu backend; veja QR code do WhatsApp Web. Sem ele, connect rejeita com INVALID_OPTIONS antes de a janela navegar, e a janela reservada no clique e fechada. Ignorado nos outros canais |
| timeoutMs | number | Prazo total positivo e finito em ms, ate 2147483647; padrao 600000 (10 minutos) |
| signal | AbortSignal | Opcional; permite interromper a tentativa via AbortController |
HTTP e carregamento do SDK Facebook tem limite de 30 segundos, respeitando o prazo total restante da tentativa. O POST ao seu backend nao faz parte desse prazo e precisa de politica propria de timeout.
Ha uma tentativa ativa por pagina, inclusive entre clientes diferentes. Outra chamada concorrente rejeita com CONNECTION_IN_PROGRESS; nao implemente retry automatico para abrir popups.
O SDK reserva um popup about:blank durante o clique, para qualquer canal sem direct, antes de aguardar a configuracao do canal; apos validar a resposta, navega a mesma janela. A janela provisoria e fechada ao encerrar a tentativa. Com direct nao ha reserva: o dialogo nativo da Meta usa o popup que o clique concede. Fechar a janela reservada antes da navegacao nao cancela a tentativa: o fechamento so resulta em popup_closed quando o fluxo ja depende da janela (apos a navegacao) ou quando ela e encontrada fechada na hora de navegar. Ainda trate POPUP_BLOCKED e permita nova tentativa por clique. A reserva nao transfere o gesto ao FB.login: o fluxo nativo, apos carregar a configuracao/SDK Meta, ainda exige homologacao externa com navegadores reais.
Cancelamento: passe um sinal novo por tentativa. Se o sinal ja estiver abortado ou for abortado enquanto connect estiver pendente, a Promise rejeita com OmnizapiError.code === "ABORTED", nao resolve com reason: "cancelled". O SDK encerra requisicoes pendentes, timers e listeners da tentativa, fecha o popup que controla e restaura seus globais temporarios sem sobrescrever alteracoes do host, nem expor signal.reason. Scripts/callbacks de terceiros nao sao removidos; a observacao de um script Meta externo compartilhado pode continuar ate carregar, falhar ou atingir o limite de 30 segundos.
Abort nao desfaz uma autorizacao na Meta, nao fecha necessariamente o dialogo nativo e nao remove a quarentena de um login nativo ainda sem callback. Depois que connect termina, abortar esse sinal nao cancela automaticamente seu POST; o React passa o sinal tambem ao fetch, mas nem isso desfaz operacoes ja recebidas pelo servidor. Confira o estado no backend antes de repetir uma finalizacao sem confirmacao.
O retorno e uma uniao discriminada por success, nao uma confirmacao de persistencia no seu backend:
| Tipo | Campos |
| --- | --- |
| ConnectionSuccess | success: true, channelId: string, type: ChannelType, coexistence: boolean; code, wabaId, phoneId, redirectUri, botToken, pageId, accessToken: string \| null |
| ConnectionFailure | success: false, reason: ConnectionFailureReason, channelId: string, type: ChannelType \| null; code, wabaId, phoneId: null |
ChannelType: META_MESSENGER, META_INSTAGRAM, META_WHATSAPP, BOT_TELEGRAM, Z_API_WHATSAPP. Nem todos os canais retornam as mesmas credenciais. O resultado de sucesso inclui channelId: envie-o junto ao backend e confira sua vinculacao a sessao/tenant no servidor.
Resultados negativos resolvem a Promise e nao significam sempre cancelamento:
| reason | Significado |
| --- | --- |
| cancelled | Fluxo cancelado |
| popup_closed | Popup fechado antes da conclusao |
| not_completed | Fluxo nao concluido |
| invalid_channel | ID de canal vazio ou canal rejeitado pelo fluxo |
| origin_not_allowed | Origem da aplicacao nao autorizada para o canal (veja Origens liberadas do canal) |
| unsupported_channel | Tipo de canal nao suportado |
Uma pagina servida com Cross-Origin-Opener-Policy: same-origin faz o navegador cortar a referencia da janela popup; o SDK reporta popup_closed (ou POPUP_BLOCKED), indistinguivel de um fechamento pelo usuario. Nao ha como diferenciar os dois casos via JavaScript: permita nova tentativa por clique e, se possivel, ajuste a politica da pagina de conexao.
Falhas tecnicas rejeitam com OmnizapiError; use instanceof OmnizapiError e error.code, nao o texto da mensagem:
| Codigo | Significado |
| --- | --- |
| BROWSER_REQUIRED | Operacao exige navegador |
| CONNECTION_IN_PROGRESS | Tentativa ativa nesta pagina ou login Meta nativo anterior incompleto; veja os limites abaixo |
| INVALID_CONFIGURATION | Props, ambiente ou APIs de navegador invalidos; conflitos de app/inicializacao do SDK Meta degradam para o fluxo proxy, sem rejeitar |
| INVALID_OPTIONS | Opcoes da operacao invalidas, como channelId com tipo incorreto, timeout ou sinal invalido, ou Z_API_WHATSAPP sem getQRCode |
| NETWORK_ERROR | Falha de transporte |
| HTTP_ERROR | Resposta HTTP sem sucesso |
| INVALID_RESPONSE | Resposta da API invalida |
| POPUP_BLOCKED | Navegador impediu a abertura do popup |
| TIMEOUT | Prazo de operacao excedido |
| ABORTED | Tentativa interrompida pelo AbortSignal da aplicacao, com mensagem segura |
| SDK_LOAD_FAILED | Falha ao carregar/inicializar SDK do provedor |
| PROVIDER_ERROR | Erro reportado pelo provedor |
client.getListMessengerPages(channelId, code, signal?): Promise<unknown>
Consulta as paginas Messenger usando o codigo OAuth e o canal. Valide o retorno antes de acessa-lo: o schema externo ainda nao foi confirmado, portanto o SDK nao publica DTO de pagina. Nao use cast para inventar esse contrato, nem registre o codigo ou a resposta. A chamada tambem pode rejeitar com erro tecnico e tem timeout HTTP de 30 segundos. O terceiro argumento aceita um AbortSignal opcional; um sinal abortado (antes ou durante a chamada) rejeita com OmnizapiError.code === "ABORTED", e um sinal invalido rejeita com INVALID_OPTIONS.
QR code do WhatsApp Web
Para Z_API_WHATSAPP, a janela de conexao da plataforma pede o QR a sua pagina, e o SDK chama o seu getQRCode. A busca do QR usa a Secret Key, entao acontece no seu backend; a chave nunca vai para o navegador.
No navegador:
const result = await client.connect({
channelId,
getQRCode: async () => {
const response = await fetch(`/api/omni/channels/${encodeURIComponent(channelId)}/qr-code`, { credentials: "include" });
const body = await response.json();
// Recusa com status e mensagem: a janela mostra a mensagem quando o status e 4xx.
if (!response.ok) throw { status: response.status, message: body.message ?? null };
// Sem value e sem connected: true, a janela entende que o QR ainda nao esta pronto.
return { connected: body.connected === true, value: body.qrcode ?? undefined };
},
});No seu backend, confira a sessao do usuario e se o canal pertence ao tenant dele antes de chamar a API. Um endpoint que entrega o QR de qualquer canal permite que alguem ligue o proprio numero a um canal alheio.
app.get("/api/omni/channels/:id/qr-code", requireSessionAndChannelOwnership, async (req, res) => {
const response = await fetch(
`https://api.omni.z-api.io/v1/channels/${encodeURIComponent(req.params.id)}/zapi/qr-code/image`,
{ headers: { Authorization: `Bearer ${process.env.OMNI_SECRET_KEY}` } },
);
const body = await response.json();
if (!response.ok) {
// O erro da API vem em error.errors[0]; repasse um texto, e nao o objeto.
return res.status(response.status).json({ message: body?.error?.errors?.[0]?.description ?? null });
}
res.json({ qrcode: body.qrcode, connected: body.connected });
});Como o SDK le a recusa do getQRCode:
status:statusouresponse.status, inteiro entre 100 e 599;message:response.data.message,response.data.errorouresponse.data.detail; semresponse, o propriomessage. O texto tecnico do axios ("Request failed with status code 422") nao e usado quando haresponse. Ate 300 caracteres;- a janela mostra a mensagem nas recusas 4xx; as demais falhas contam como passageiras, e tres seguidas mostram o erro de rede.
A janela manda o pedido para as origens exatas liberadas do canal (menu Seguranca do painel) e, quando a lista tem curinga, para a origem do referrer se ela casar com alguma entrada (regra em Origens liberadas do canal); nunca para a entrada com * nem para *. Se nao conseguir ler as origens, usa a origem do referrer. Com Referrer-Policy: no-referrer na sua pagina e a sua origem coberta so por curinga (ou as origens ilegiveis), a janela nao tem para onde mandar o pedido, e a conexao termina em erro de rede.
Origens liberadas do canal
O connect so segue quando a origem da pagina (window.location.origin) e a do frontUrl ou casa com alguma entrada da lista de dominios do canal (menu Seguranca do painel, allowOrigins do sdk-info). Cada entrada e lida assim:
| Entrada | Casa com | Nao casa com |
| --- | --- | --- |
| https://app.cliente.com | so https://app.cliente.com (caminho ignorado; :443 e o mesmo que sem porta) | https://cliente.com, https://x.app.cliente.com, http://app.cliente.com, https://app.cliente.com:8443 |
| https://*.cliente.com | qualquer subdominio, de qualquer profundidade, com o mesmo esquema e a mesma porta: https://app.cliente.com, https://a.b.cliente.com | o proprio https://cliente.com, https://evilcliente.com, https://cliente.com.evil.com, http://app.cliente.com, https://app.cliente.com:8443 |
| cliente.com, *.cliente.com (sem esquema, como a tela sugere) | o mesmo que https://cliente.com e https://*.cliente.com | qualquer origem http://; para http (ex.: http://localhost:3000) escreva o esquema |
- O host e comparado sem diferenciar maiusculas de minusculas.
- O
*so vale como primeiro rotulo inteiro, seguido de pelo menos dois rotulos.*,*.com,a*.cliente.com,app.*.cliente.come*.*.cliente.comnao casam com nada. - O
*so e interpretado na autoridade; no caminho, na query ou no fragmento ele e ignorado junto com o resto (https://app.cliente.com/*vale comohttps://app.cliente.com). - So
httpehttpssao aceitos (ftp://,ws://,javascript:nao casam com nada); entrada com@na autoridade (https://[email protected],https://@app.cliente.com) ou que nao e URL nao casa com nada. Uma entrada invalida nao invalida as outras. - Limite conhecido: o SDK nao conhece a lista de sufixos publicos.
*.com.bre aceito e libera qualquer site.com.br; o mesmo vale para sufixos onde qualquer pessoa cria um subdominio, como*.vercel.app,*.github.io,*.herokuapp.com,*.netlify.appou*.pages.dev: liberar um deles libera o site de qualquer pessoa nessa hospedagem. Cadastre sempre o seu dominio (*.cliente.com.br) ou a origem exata do seu projeto (https://meu-projeto.vercel.app).
Instagram e Messenger no seu site exigem a 1.1.1. A pagina de retorno do OAuth so responde para fora do painel quando o state traz a pk e a origem do currentUrl casa com a lista acima. Com a 1.1.0 ou anterior (sem pk no state), ou com a lista ilegivel ou vazia, ela nao responde e a tentativa termina por fechamento da janela ou timeout. Dentro do proprio painel nada muda.
Ate a 1.1.0 a comparacao era por origem exata: entrada com * nunca casava e entrada sem esquema era descartada, e a tentativa terminava em origin_not_allowed.
Protocolo de popup
Este contrato exige deploy coordenado do frontend/proxy e backend externos; nenhum desses servicos e alterado neste repositorio. Nao ha fallback para mensagens sem os identificadores exigidos.
- O SDK gera
attemptIdpor tentativa. Popups abertos diretamente nofrontUrlrecebemattemptIdna query; as rotas/connect/telegrame/connect/whatsapp-webtambem recebempk(a chave publica) na query, e/connect/whatsapp-webrecebeqr=message, permitindo ao frontend consultar as origens autorizadas do canal e restringir otargetOriginda resposta. No OAuth Messenger/Instagram, ostateJSON e{ currentUrl, attemptId, channelId, pk }; apk(chave publica, a mesma dosdk-info) existe desde a 1.1.1 e permite a pagina de retorno ler as origens liberadas do canal e so responder aocurrentUrlque casar com elas; o proxy deve preserva-lo e vincula-lo a sessao autorizada, ao canal e ao tenant.statee query nao sao prova de autorizacao por si so. - A pagina final deve estar na origem exata de
frontUrle emitir da propria janela popup aberta pelo SDK, nao de iframe, outra janela ou backend. Ela responde aowindow.openerusandopostMessagecomtype: 'after-connect',attemptId,channelId,successe as credenciais aplicaveis.attemptIdechannelIddevem corresponder exatamente a tentativa ativa. - O SDK verifica a origem exata do frontend,
event.sourceigual ao popup ativo e os dois identificadores. O emissor usatargetOriginigual a origem exata da aplicacao autorizada, nunca*e nunca uma origem derivada de query/state nao validados. Quando a aplicacao esta liberada por curinga (https://*.cliente.com), a origem exata e a doreferrerda janela, aceita so se casar com a entrada. - Sucesso dos proxies Meta exige
code; Telegram exigebotToken.Z_API_WHATSAPPpode retornar sucesso autenticado sem credenciais, mas o backend deve confirmar a conexao real para aquele canal/tenant. Para negativo, enviesuccess: falsee oreasontipado aplicavel, sem credenciais. - Com
qr=message, a pagina/connect/whatsapp-webpede o QR aowindow.openercom{ type: 'qr-code-request', attemptId, channelId, requestId }, um pedido por vez. O SDK confere a origem exata do frontend,event.sourceigual ao popup ativo, os dois identificadores e umrequestIdem texto nao vazio de ate 64 caracteres; pedido que nao confere e ignorado. Com uma chamada aogetQRCodeem andamento, o pedido novo recebe o resultado dela, sem chamar de novo. A resposta vai so para a origem exata do frontend:{ type: 'qr-code-response', attemptId, channelId, requestId, connected, value }, ou{ ..., error: { status, message } }quando ogetQRCoderejeita ou devolve algo invalido. A pagina confereevent.sourceigual aowindow.opener, a origem e os tres identificadores.
Privacidade de state: currentUrl continua sendo window.location.href COMPLETO, incluindo caminho, query e fragmento. Esta rodada nao reduz esse valor a origem: a compatibilidade dos proxies com uma URL minimizada ainda e desconhecida. Tokens ou outros dados sensiveis na query/fragmento da pagina podem assim ser copiados para o state OAuth e expostos ao provedor, proxy, historico ou logs de URLs. Evite iniciar a conexao em paginas com esses dados e nao registre state nem URLs completas. Fica pendente verificar com os responsaveis pelos proxies se aceitam apenas a origem antes de propor qualquer mudanca coordenada desse contrato.
Mensagens com origem, janela, identificadores ou formato incorretos sao ignoradas; a tentativa continua aguardando conclusao, fechamento ou timeout. Uma mensagem de sucesso sem a credencial exigida resulta em not_completed.
Trecho ilustrativo no frontend externo, apos o servidor validar a sessao e a autorizacao da tentativa. authorizedSession representa dados validados pelo servidor, nao um DTO deste SDK; nao a construa lendo parametros da URL sem validacao:
const { applicationOrigin, attemptId, channelId } = authorizedSession;
// applicationOrigin e uma origem exata previamente autorizada para esta sessao.
// code foi validado para o fluxo Meta desta mesma tentativa.
if (window.opener) {
window.opener.postMessage(
{ type: "after-connect", attemptId, channelId, success: true, code },
applicationOrigin,
);
}O Meta Embedded Signup nativo nao fornece attemptId nem uma referencia customizada da janela emissora (event.source) para correlacao. Ele aceita eventos WA_EMBEDDED_SIGNUP de qualquer origem HTTPS em facebook.com ou subdominio (www, web, business), como a Meta orienta, e o callback FB.login. Os eventos FINISH, FINISH_ONLY_WABA e FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING concluem a conexao; CANCEL e ERROR a encerram. Nao se pode garantir o isolamento de eventos nativos tardios entre tentativas; essas verificacoes nao sao garantia absoluta de seguranca.
Um login nativo fica em quarentena ate a Meta responder o callback FB.login (com codigo, cancelamento ou erro) ou ate a tentativa ter sucesso. O callback, inclusive tardio, libera apenas a quarentena da tentativa a que pertence; nao libera a de outra tentativa. Fechar/cancelar o dialogo ou receber um evento de cancelamento nao garante que esse callback ja tenha chegado. Se um login terminar por timeout, abort ou cancelamento sem callback, novas tentativas nativas continuam em CONNECTION_IN_PROGRESS: aguarde o callback ou recarregue a pagina para tentar outro login nativo. Um callback recebido de cancelamento nao exige reload. Um FB.login que falha ao iniciar (excecao sincrona, sem abrir dialogo e sem callback futuro) libera a quarentena imediatamente, sem exigir reload. Essa restricao reduz o risco de atribuir a uma nova tentativa a resposta tardia de um dialogo anterior; nao se aplica aos popups proxy.
Durante uma tentativa, apenas __omnizapiPublicKey, __omnizapiCurrentChannel e __omnizapiGetQrCode sao publicados temporariamente no window da aplicacao, e removidos ao fim. So uma pagina na mesma origem consegue le-los: e o caminho da pagina de conexao anterior a qr=message, mantido para o painel da plataforma. De outra origem, o QR viaja por mensagem (item 5 do protocolo), porque uma funcao no window da aplicacao nao atravessa origens nem e serializada por postMessage.
CSP da pagina que roda o login nativo
O FB.login conclui por um iframe oculto de staticxx.facebook.com na propria pagina. Se a sua pagina tem Content-Security-Policy e usa direct: true, libere:
script-src https://connect.facebook.net
frame-src https://www.facebook.com https://web.facebook.com https://business.facebook.com https://staticxx.facebook.comSem o frame-src acima, o dialogo da Meta abre, o usuario conclui, e o dialogo fica preso em dialog/oauth/business/cancel sem devolver o resultado. Sem direct, o login roda no frontend de conexao da plataforma e a sua pagina nao precisa de nada disso.
App da Meta proprio (appCustom)
Por padrao o canal usa o app da Meta da plataforma, e a Meta so enxerga o frontUrl (app.hubmessage.io ou app.omni.z-api.io): o login do WhatsApp e o retorno do OAuth do Messenger e do Instagram acontecem na janela da plataforma. Nesse caso o seu dominio nao precisa de nada na Meta, so na allowlist de origens do canal.
Quando o canal tem app proprio (appCustom no sdk-info), o login do WhatsApp Meta (Embedded Signup) continua na janela da plataforma (/oauth/proxy-cloud-api), mas com o seu appId e configId, e o OAuth do Messenger e do Instagram tambem volta para a plataforma. Entao o seu app da Meta precisa conhecer o dominio da plataforma e, se voce usar direct na sua pagina, tambem o seu:
| Onde no app da Meta | O que cadastrar |
| --- | --- |
| Facebook Login > Configuracoes > Dominios permitidos para o SDK do JavaScript | O frontUrl da plataforma (ex.: https://app.omni.z-api.io) e, se voce usar direct, todas as origens onde o seu site chama connect |
| Facebook Login > Configuracoes > URIs de redirecionamento do OAuth validos | ${frontUrl}/oauth/proxy-messenger e ${frontUrl}/oauth/proxy-instagram da marca que voce usa (ex.: https://app.omni.z-api.io/oauth/proxy-messenger) |
| Configuracoes basicas > Dominios do app | O dominio da plataforma e, com direct, o do seu site |
| WhatsApp > Embedded Signup | A configuracao cujo id e o configId cadastrado na plataforma; o app precisa ter os produtos WhatsApp, Messenger e Instagram habilitados conforme os canais que vai conectar |
Sem o dominio do seu site nos dominios do SDK, a Meta pode recusar o FB.login. PROVIDER_ERROR depende de a Meta reportar um erro ao SDK; nao e possivel garantir esse codigo para toda falha de dominio. Sem os redirects corretos, o OAuth do Messenger e do Instagram pode ser bloqueado pela Meta. Sem a origem do seu site na allowlist do canal, o SDK resolve com origin_not_allowed antes de navegar para a autorizacao; uma janela provisoria ja pode ter sido reservada e sera fechada.
Inicializacao Meta: o SDK inicializa uma vez e reutiliza o mesmo appId somente quando essa inicializacao foi rastreada pelo proprio SDK. Nao reinicializa silenciosamente o FB compartilhado com outro app: quando a pagina nao pode rodar o nativo com seguranca — FB do host com inicializacao desconhecida, app conflitante, appId retornado pela API que nao e uma string numerica valida ou inicializacao que nao pode ser observada — a tentativa degrada para o fluxo proxy na janela reservada, em vez de rejeitar com INVALID_CONFIGURATION. Se a janela reservada tiver sido fechada ou bloqueada, a tentativa termina com popup_closed ou POPUP_BLOCKED. A quarentena de login nativo (CONNECTION_IN_PROGRESS) nao degrada: ela protege a atribuicao de callbacks tardios da Meta.
Com o fallback, um FB preexistente do host (inclusive de outro app) nao impede mais a conexao: o login acontece na janela da plataforma. Para canais com appCustom, isso exige que o seu app da Meta conheca o dominio da plataforma, conforme a tabela acima; sem os redirects e dominios corretos, a Meta pode bloquear o fluxo proxy do seu app. Nao reinicialize o FB por fora depois que o SDK rastrear seu app. Nao presuma que direct: false force proxy nem que direct: true garanta nativo: appCustom, a origem do frontend e agora a disponibilidade do nativo na pagina tambem decidem o fluxo.
Em SDKs carregados pela biblioteca, chamadas externas ao FB.init atual sao encaminhadas normalmente, mas invalidam o app conhecido: uma conexao nativa posterior degrada para o proxy em vez de usar o app errado. Referencias ao init original salvas antes dessa observacao nao podem ser monitoradas. Se um script externo expirar sem falha conclusiva, seu carregamento ainda pode terminar: novos SDKs nao recebem proveniencia propria ate recarregar a pagina. O script do host nao e removido.
Desenvolvimento e verificacao
Use Node.js 22, conforme .nvmrc, e as dependencias do lockfile:
nvm use
npm ci
npm run checknpm run check executa lint, testes unitarios, build limpo e smoke tests do pacote. Ele limpa os overrides OMNIZAPI_API_URL / OMNIZAPI_FRONT_URL no ambiente dos subprocessos, sem alterar o ambiente do chamador. A verificacao de release exige os padroes de producao Omnizapi (https://api.omni.z-api.io e https://app.omni.z-api.io), sem URLs customizadas embutidas no artefato. O lifecycle prepack protege automaticamente npm pack e npm publish; nao use --ignore-scripts para contornar essa protecao na preparacao de uma release. A CI faz verificacoes, nao publicacao automatica.
Para os testes locais de navegador, prepare antes os binarios dos tres engines:
npm exec playwright install chromium firefox webkit
npm run test:browsertest:browser cobre Chromium, Firefox e WebKit usando o artefato ja compilado: execute npm run check antes, pois esse teste nao recompila o SDK. Testes com provedores simulados nao homologam o SDK Meta, os proxies, credenciais ou canais reais; o fluxo nativo e os servicos externos continuam exigindo teste de integracao.
npm run build continua permitindo builds customizados com variaveis explicitas OMNIZAPI_API_URL e OMNIZAPI_FRONT_URL. Isso nao substitui o build com defaults de producao exigido pelo guard de release. As props apiUrl / frontUrl continuam sendo o modo de configurar o consumidor sem recompilar o SDK.
O hook de versao scripts/sync-version.mjs sincroniza os links UNPKG e scripts CDN do README/exemplo vanilla em futuras execucoes aprovadas de npm version <nova-versao> --no-git-tag-version. Isso nao publica o pacote.
Checklist de deploy
- [ ] Antes de uma publicacao futura aprovada, executar as verificacoes e conferir exports default/named
Omnizapi, tipos na raiz edist/omnizapi.min.js; manter imports, versoes CDN e configuracao dos consumidores coordenados. - [ ] Conferir os defaults de producao no artefato de release. Para consumidores customizados, configurar
apiUrl/frontUrlnas props; conferir staging e ambas as URLs de sandbox. Variaveis no build ficam restritas a builds customizados explicitos. - [ ] Manter na API as rotas existentes
GET /instances/:channelId/sdk-infoePOST /v1/channels/:channelId/list-messenger-pages, com autenticacao por chave publica, schema validado e sem exposicao de secrets. - [ ] Manter no frontend
/oauth/proxy-cloud-api,/oauth/proxy-messenger,/oauth/proxy-instagram,/connect/telegrame/connect/whatsapp-web, inclusive ao usar uma URL base com prefixo. - [ ] Conferir a logo customizada e o asset padrao
${frontUrl}/logos/SVG/Ativo%2016.svg, acessiveis por HTTPS e com identidade Omnizapi. - [ ] Autorizar as origens exatas da aplicacao por canal e configurar CORS da API para essas origens, metodos e headers (
Authorization,Content-Type). Validacao client-side nao substitui autorizacao no servidor. - [ ] Cadastrar os redirects Meta exatos
${frontUrl}/oauth/proxy-messengere${frontUrl}/oauth/proxy-instagram, alem de dominios/configuracoes do Embedded Signup e app IDs corretos. - [ ] Implementar preservacao de
state, sessao autorizada e protocoloafter-connectacima em todas as rotas; confirmar origem final, janela emissora,attemptId,channelIdetargetOriginexatos. Conferir politicas de navegador/COOP que possam removeropener. - [ ] Revisar exposicao de query/fragmento em
state.currentUrl, que permanece completo. Verificar com os proxies a compatibilidade com origem apenas antes de qualquer futura minimizacao; nao assumir suporte. - [ ] Validar no backend sessao, CSRF, canal/tenant e credenciais especificas do provedor antes de finalizar; trocar/armazenar tokens somente no servidor, limitar repeticao/replay e nao aceitar
success: truedo navegador como prova suficiente. - [ ] Homologar abort, cancelamento com/sem callback Meta (inclusive tardio), fechamento/bloqueio de popup, concorrencia, timeout, conflitos de app ID (que degradam para o fluxo proxy), mensagens de origem/janela/tentativa incorretas e falhas HTTP; validar em servicos reais o gesto do login nativo, a integracao QR e cada canal, inclusive Z-API. Os exemplos nao substituem essa homologacao.
Licenca
MIT - Copyright (c) 2026 FourPixel IT. Autor: FourPixel IT. Texto completo no arquivo local LICENSE e na licenca do pacote apos a publicacao correspondente.
