Files
B2BCall-dialer/docs/EVENT_SOCKET.md
Matheus 60e9f6838e feat: add Event Socket integration (b2bcall-fs-events)
- packages/telephony: TelephonyProvider interface (agente.md secao 25) and
  FreeSwitchTelephonyProvider implementation over the 'esl' library
  (actively maintained, TypeScript-native, built-in reconnect-with-backoff
  satisfying secao 195); normalizeEslEvent() translates raw ESL events into
  the internal vocabulary (secao 24)
- apps/freeswitch-events (b2bcall-fs-events): permanent ESL connection,
  resubscribes on every reconnect, publishes normalized events to the
  'b2bcall:events' Redis pub/sub channel; containerized (Dockerfile +
  docker-compose service) since its whole job is reaching the freeswitch
  container by internal hostname
- packages/shared: reusable createLogger() (structured JSON per secao 189),
  fixed a BigInt serialization crash surfaced by the esl library's error
  stats
- found and fixed a real FreeSWITCH 1.11 default: without an explicit
  apply-inbound-acl, mod_event_socket silently rejects any non-loopback
  connection ('Access Denied, go away.') even with the correct password —
  added a dedicated ACL (loopback + the Docker Compose network range, never
  0.0.0.0/0) in infrastructure/freeswitch/overrides/autoload_configs/
- verified end-to-end with a local loopback test call: CALL_CREATED ->
  CALL_ANSWERED -> CALL_ENDED observed on the Redis channel with the
  correct callUuid and hangup cause
- docs/EVENT_SOCKET.md
2026-08-28 06:47:28 -03:00

3.6 KiB

Event Socket

b2bcall-fs-events (apps/freeswitch-events) mantém a conexão ESL permanente com o FreeSWITCH (agente.md secao 21) — nenhum outro serviço deve rodar fs_cli via shell pra ações operacionais.

Biblioteca

Usa esl (v11, mantida ativamente, TypeScript nativo, zero dependências de libesl). A classe FreeSwitchClient já resolve reconexão com backoff sozinha (agente.md secao 195) — só precisamos reagir a connect/reconnecting/error e resubscrever a cada connect (a lib entrega um objeto de chamada novo a cada reconexão).

packages/telephony

  • TelephonyProvider (interface, agente.md secao 25) + FreeSwitchTelephonyProvider (implementação sobre esl). Métodos testados manualmente contra o FreeSWITCH rodando: originate, killCall, getChannels, getCalls, getGateways, getRegistrations, reloadXml. Os métodos de fila/agente (setAgentStatus, addAgentToQueue, ...) seguem a sintaxe documentada do mod_callcenter mas não foram exercitados contra uma fila real ainda — não existe nenhuma (fase Queues).
  • normalizeEslEvent(): traduz eventos ESL crus pro vocabulário interno (agente.md secao 24). Mapeamento de callcenter::infoAGENT_STATUS_CHANGED é best-effort (nomes de campo inferidos da documentação, não testados — revisar na fase Queues/Agents).

Achados durante os testes

  1. ACL implícita do Event Socket: sem apply-inbound-acl explícito, o FreeSWITCH 1.11 rejeita ("Access Denied, go away.") qualquer conexão que não seja loopback — mesmo com a senha certa. Descoberto porque b2bcall-fs-events (outro container) não conseguia conectar. Corrigido criando uma ACL própria (b2bcall_internal, em overrides/autoload_configs/acl.conf.xml) cobrindo loopback + a rede interna do Docker Compose (172.16.0.0/12, nunca 0.0.0.0/0).
  2. Nessa mesma correção, um erro de digitação inicial (usar só localnet.auto, que cobre a rede Docker mas não loopback) quebrou até o fs_cli local — corrigido combinando as duas faixas na mesma ACL.
  3. O logger JSON de packages/shared quebrava (TypeError: Do not know how to serialize a BigInt) porque a lib esl usa bigint nos campos de estatística de erro. Corrigido com um replacer no JSON.stringify.

Eventos consumidos e publicados

Lista completa em apps/freeswitch-events/src/main.ts (SUBSCRIBED_EVENTS), cobrindo a secao 23 do agente.md. HEARTBEAT só é logado em debug, nunca normalizado. Eventos normalizados são publicados em JSON no canal Redis b2bcall:events (pub/sub simples — vira a base pra WebSocket multi-tenant na fase Realtime Monitoring, que ainda não existe).

Verificado ponta a ponta

Sem SIP real disponível ainda, a verificação usou uma chamada loopback local:

docker exec b2bcall-freeswitch fs_cli -p "$ESL_PASSWORD" -x "originate null/_test_ &park()"
# ... uuid_kill pra encerrar

Resultado observado no canal Redis: CALL_CREATEDCALL_ANSWEREDCALL_ENDED (com hangupCause), todos com o callUuid correto.

Limitações desta fase

  • Reconciliação pós-reconexão (secao 195: "reconcile calls, agents, queues, registrations, gateways") não é possível ainda — não existem tabelas de calls/agents/queues persistidas pra reconciliar contra. Só a resubscrição de eventos está implementada. Revisitar quando essas tabelas existirem.
  • b2bcall-fs-events roda via tsx direto (sem etapa de build/dist) — simples mas ~99MB de RAM em runtime (razoável no orçamento atual, mas vale revisar se muitos workers assim rodarem juntos mais pra frente).