zapo. É voltada para contribuidores e para qualquer pessoa depurando no nível do protocolo. Para o protocolo em si, veja O protocolo do WhatsApp.
Mapa de módulos
src
client
transport
signal
crypto
message
appstate
store
auth
media
retry
protocol
infra
util
A stack
As stores atravessam todas as camadas; o connection manager e o keep-alive ficam ao lado do transporte.Ciclo de vida da conexão
OWaConnectionManager (em src/client/connection/) conduz o connect/disconnect:
- O
WaCommsabre o socket (WaWebSocketouWaMobileTcpSocket) e executa o handshake Noise (src/transport/noise/), autenticando o servidor e derivando as chaves de sessão. - O
WaNodeTransport.bindComms()anexa o codec binário ao socket criptografado. - O
WaKeepAlive(src/transport/keepalive/) inicia ping IQs periódicos para detectar um socket morto e estimar o desvio do relógio do servidor. - O client executa tarefas passivas pós-conexão (history sync, drenagem de mensagens offline) e emite
connection.
zapo deliberadamente não reconecta automaticamente — essa política pertence ao seu app (veja Reconexão).
Pipeline de mensagem de saída
client.message.send → WaMessageDispatchCoordinator:
- Build — o conteúdo (a send union) é construído em um
Proto.IMessage, fazendo upload da mídia se necessário. - Resolver dispositivos — a lista de dispositivos do destinatário é resolvida (fanout,
src/client/messaging/), buscando prekey bundles para dispositivos sem sessão. - Criptografar — por dispositivo: 1:1 via o ratchet Signal (
SignalProtocol→msg/pkmsg), grupos viaSenderKeyManager(skmsg) mais a distribuição de sender-key aos membros que precisam. Seus próprios dispositivos recebem umdeviceSentMessage. - Montar —
src/transport/node/builders/message.tsenvolve os participantes criptografados em uma única stanza<message>com a identidade do dispositivo, o hash de participantes e oaddressing_mode(pn/lid). - Enviar e ack —
WaNodeOrchestrator.sendNode()codifica e escreve; o coordinator aguarda o<ack>do servidor e retorna umWaMessagePublishResult. Falhas são reenviadas conforme as tentativas/backoff configurados.
Pipeline de entrada
- O
WaCommsdescriptografa um frame Noise; oWaNodeTransport.dispatchIncomingFrame()o decodifica em umBinaryNode. - O
WaIncomingNodeCoordinatorroteia por tag/type para o handler certo (src/client/events/). - Para mensagens, o corpo é descriptografado (ratchet Signal ou sender key), os wrappers de device-sent são desembrulhados e o padding PKCS7 é removido (
src/message/primitives/incoming.ts). - O resultado é normalizado em um payload tipado e emitido (
message,receipt,group, …). Um filtro de stanza pode descartar stanzas antes que os handlers rodem; o coordinator ainda dá ack emmessage/receipt/notificationpara que o servidor pare de reentregar.
Engine de app-state
OWaAppStateSyncClient (src/appstate/) reconcilia as configurações por conta:
- Uma sincronização envia a última versão conhecida por coleção; o servidor retorna patches (ou um snapshot completo).
- O índice e o valor de cada mutation são verificados por MAC e o valor é descriptografado (
WaAppStateCrypto: AES-CBC + HMAC, chaves por coleção derivadas via HKDF). - As mutations verificadas são aplicadas à store
appStatee expostas como eventosmutation. - Sink de contatos — mutations
Contactvencedoras (incluindo as de snapshot em pair-time) fluem incondicionalmente para a storecontacts, dando bootstrap no address book no primeiro connect independente do toggle públicoemitSnapshotMutations. O sink resolve o JID LID-canonical a partir decontactAction.lidJid, espelha a forma PN emphoneNumbere escrevefullName ?? firstNamecomodisplayName. O history-sync adicionalmente persisteinlineContactsedisplayName/usernamepor conversation quando o primary device os encaminha; o payload de pair anunciasupportInlineContactspara que o servidor saiba enviá-los. - Mudanças de saída de
client.chatsão codificadas como mutations, agrupadas em lote e descarregadas.
Camada de store
A store é dividida em contratos (src/store/contracts/ — a interface que cada domínio implementa) e providers (o provider memory embutido, mais os pacotes de backend externos). É isso que permite misturar backends por domínio em createStore.
Duas fronteiras de performance ficam aqui:
- Write-behind — mensagens/threads/contatos de entrada são agrupados em lote e descarregados de forma assíncrona (ajustado via
writeBehind) para que o caminho quente não fique bloqueado no banco de dados. - Caches limitados —
retry,groupMetadata,deviceListemessageSecretsão limitados em memória com TTLs para evitar crescimento ilimitado em processos de longa duração.
Confiabilidade
- Retry tracker (
src/retry/) — mapeia ids de mensagens que falharam para metadados de retry e impõe limites de tentativa/backoff. - Fila de recibos (
WaReceiptQueue) — armazena recibos que falham ao enviar durante uma desconexão, reproduzindo-os na reconexão (limitada para evitar crescimento). - Keep-alive — ping IQs periódicos detectam sockets mortos e medem o desvio do relógio; ele pula o ping enquanto uma query já está em andamento.
Composição do client
OWaClientFactory é a raiz de composição: ele constrói o auth client, o connection manager, o transporte + orchestrator, o keep-alive, os managers de Signal/sender-key, o app-state client, as stores e cada coordinator de funcionalidade, e então os injeta no WaClient. O WaClient em si permanece enxuto — um EventEmitter que expõe os getters de coordinator e o ciclo de vida da conexão.
Crypto
Osrc/crypto/ fornece as primitivas:
- Simétrica — AES-GCM (Noise, valores de app-state), AES-CBC (sender keys), AES-CTR (mídia).
- Hash/MAC/KDF — SHA-1/256/512, HMAC, HKDF.
- Curva elíptica — Curve25519 (X25519 DH) e assinaturas Ed25519/XEdDSA.
Convenções
Estas valem em todo o código-fonte e explicam boa parte do formato da API:Uint8Arrayem todo lugar para dados binários;Bufferé evitado. Zero-copy (subarray, byte views) nos caminhos críticos.- Estruturas em memória limitadas para evitar crescimento ilimitado.
- Apenas named exports; sem default exports.
- Sem enums — as constantes usam
Object.freeze({ … } as const), expostas como os objetosWA_*. - Path aliases (
@client,@crypto,@store, …) em vez de imports relativos../.
