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:
2026-08-28 06:47:28 -03:00
parent b3b0aaacb3
commit 60e9f6838e
22 changed files with 762 additions and 15 deletions

View File

@@ -1 +1,2 @@
export * from "@b2bcall/types";
export * from "./logger";

View File

@@ -0,0 +1,28 @@
/**
* Logging JSON estruturado (agente.md secao 189). Sempre inclui `service` e
* `ts`; nunca logar secrets — quem chama é responsável por isso.
*/
export interface Logger {
debug(msg: string, context?: Record<string, unknown>): void;
info(msg: string, context?: Record<string, unknown>): void;
warn(msg: string, context?: Record<string, unknown>): void;
error(msg: string, context?: Record<string, unknown>): void;
}
export function createLogger(service: string): Logger {
const write = (level: string, msg: string, context: Record<string, unknown> = {}) => {
process.stdout.write(
`${JSON.stringify(
{ level, service, msg, ts: new Date().toISOString(), ...context },
(_key, value) => (typeof value === "bigint" ? value.toString() : value),
)}\n`,
);
};
return {
debug: (msg, context) => write("debug", msg, context),
info: (msg, context) => write("info", msg, context),
warn: (msg, context) => write("warn", msg, context),
error: (msg, context) => write("error", msg, context),
};
}