Achado ao iniciar a fase de Realtime Monitoring: nenhum evento CUSTOM (sofia::register, sofia::gateway_state, callcenter::info) jamais chegou em b2bcall-fs-events nesta sessao, apesar do estado real do FreeSWITCH mudar de verdade (confirmado originando uma chamada de teste pra dentro de uma fila real com agente logado). Isso deixava dois gaps documentados como "nao verificado" em docs/TRUNKS.md e docs/AGENTS.md. Causa raiz: `event_json(...SUBSCRIBED_EVENTS)` mandava "CUSTOM" como ultimo token do comando `event json`, sem nenhum subclass depois. O mod_event_socket do FreeSWITCH exige que os subclasses (callcenter::info, sofia::register, ...) venham imediatamente depois do token CUSTOM no mesmo comando — sem isso, zero eventos CUSTOM sao entregues, de qualquer subclass. Corrigido separando PLAIN_EVENTS (nomes normais, cada um vira um listener .on()) de CUSTOM_SUBCLASSES (so compoe o comando de subscricao — o client ESL sempre emite "CUSTOM" como nome de evento, com o subclass real no header Event-Subclass). Corrigido tambem um bug de nome de campo: normalizeCustomEvent lia CC-Agent-Status, que nao existe; o campo real e CC-Agent-State. Com o pipeline corrigido, chegam eventos ricos de callcenter::info nunca antes observados: agent-offering, bridge-agent-fail, members-count (fila em tempo real) e member-queue-end (com CC-Cause/CC-Cancel-Reason e timestamps de entrada/saida — atendida vs. abandonada). Adicionados como novos tipos normalizados: AGENT_OFFERED_CALL, AGENT_BRIDGE_FAILED, QUEUE_MEMBER_COUNT, QUEUE_MEMBER_LEFT. De quebra, achado e corrigido um segundo bug real ao reverificar Trunks com o pipeline de eventos funcionando: `sofia profile external rescan` nunca descarregava um gateway cujo arquivo .xml foi apagado (fica fantasma na memoria do Sofia indefinidamente). trunk-sync.ts agora roda `sofia profile external killgw <nome>` pra cada gateway removido, antes do rescan. Reverificado ponta a ponta pra ambos os bugs: - Trunk com register:true apontando pra host inexistente: Trunk.status no banco passa de UNKNOWN pra FAILED sozinho, via evento, sem polling. - Trunk apagado via API: gateway some imediatamente de `sofia status gateway`, sem esperar reinicio de profile. - Chamada de teste real numa fila com agente logado: members-count, agent-offering, agent-state-change (CC-Agent-State correto: Waiting/ Receiving), bridge-agent-fail, todos chegando certos no canal Redis b2bcall:events. typecheck do workspace inteiro limpo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1HxY46WGU4G1zmVDNKcWw
88 lines
4.5 KiB
Markdown
88 lines
4.5 KiB
Markdown
# 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`/`sofia::*` com nomes
|
|
de campo confirmados contra uma fila real na fase Realtime Monitoring —
|
|
ver "Achados" abaixo e docs/AGENTS.md.
|
|
|
|
## 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`.
|
|
4. **Bug real, só descoberto na fase Realtime Monitoring**: nenhum evento
|
|
CUSTOM (`sofia::gateway_state`, `sofia::register`, `callcenter::info`)
|
|
nunca chegou nesta sessão até então, apesar da subscrição incluir
|
|
`"CUSTOM"` na lista de `SUBSCRIBED_EVENTS`. Causa: `event_json(...events)`
|
|
manda `"CUSTOM"` como último token do comando `event json`, sem nenhum
|
|
subclass depois — o mod_event_socket do FreeSWITCH exige que os
|
|
subclasses venham imediatamente depois do token `CUSTOM` no mesmo
|
|
comando pra serem entregues. Corrigido: `SUBSCRIBED_EVENTS` agora termina
|
|
em `"CUSTOM", "sofia::register", "sofia::unregister", "sofia::expire",
|
|
"sofia::gateway_state", "callcenter::info"`, e o loop que registra
|
|
listeners `.on()` só usa os nomes "planos" (`PLAIN_EVENTS`) mais um único
|
|
`.on("CUSTOM", ...)` — os nomes de subclass nunca viram listener, só
|
|
compõem o comando de subscrição.
|
|
|
|
## 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).
|