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
This commit is contained in:
74
docs/EVENT_SOCKET.md
Normal file
74
docs/EVENT_SOCKET.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 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`](https://www.npmjs.com/package/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::info` → `AGENT_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:
|
||||
|
||||
```bash
|
||||
docker exec b2bcall-freeswitch fs_cli -p "$ESL_PASSWORD" -x "originate null/_test_ &park()"
|
||||
# ... uuid_kill pra encerrar
|
||||
```
|
||||
|
||||
Resultado observado no canal Redis: `CALL_CREATED` → `CALL_ANSWERED` →
|
||||
`CALL_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).
|
||||
Reference in New Issue
Block a user