- 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
5.2 KiB
5.2 KiB
TODO — B2BCall
PHASE 01 — Infrastructure
- Diagnóstico do servidor (Debian 13, 2 vCPU, ~1.9GB RAM, 26GB disco livre)
- Docker + Docker Compose instalados
- Estrutura de monorepo criada (apps/, packages/, infrastructure/, scripts/, docs/)
- PostgreSQL 18 (docker-compose, porta 127.0.0.1:5432)
- Redis 7 (docker-compose, porta 127.0.0.1:6379)
- Secrets gerados em
.env(POSTGRES_PASSWORD, REDIS_PASSWORD, JWT_SECRET, JWT_REFRESH_SECRET, ENCRYPTION_KEY, ESL_PASSWORD) FREESWITCH_PATconfigurado em.env(não commitado)- FreeSWITCH (imagem própria via pacotes SignalWire, não compilada da fonte — ver docs/FREESWITCH.md; rodando, saudável, ~44MB RAM, senha ESL customizada, nenhuma porta exposta ao host)
- nginx (reverse proxy)
PHASE 05 — FreeSWITCH (agente.md secao 232)
- Imagem própria (
infrastructure/freeswitch/), pacotes SignalWire (PAT via BuildKit secret, nunca na imagem final — verificado comdocker history) - Módulos mínimos carregados: sofia, event_socket, commands, dptools, callcenter, avmd, curl, local_stream, etc. (mod_xml_curl instalado mas desativado até existir b2bcall-fs-config)
- Senha do Event Socket trocada da padrão via entrypoint runtime (nunca fica na imagem); porta 8021 não publicada no host
- docs/FREESWITCH.md, docs/NETWORK_ARCHITECTURE.md (decisão de network_mode adiada pra quando existir tronco SIP real)
- Diretório/dialplan ainda são os estáticos da config vanilla (ramais de teste 1000-1019, senhas fracas) — substituir por mod_xml_curl na fase Extensions/Trunks/Dialplan
PHASE 06 — Event Socket (agente.md secao 21-25, 195)
packages/telephony: interfaceTelephonyProvider+FreeSwitchTelephonyProvider(sobre a libesl, reconexão com backoff já embutida na lib)normalizeEslEvent(): eventos ESL crus → vocabulário interno (secao 24)apps/freeswitch-events(b2bcall-fs-events): conexão ESL permanente, resubscreve a cada reconexão, publica eventos normalizados no canal Redisb2bcall:events- Achado: FreeSWITCH 1.11 aplica ACL implícita (só loopback) sem
apply-inbound-acl— bloqueava conexão de outro container mesmo com senha certa. Corrigido com ACL própria cobrindo loopback + rede Docker. - Testado ponta a ponta com chamada loopback local: CALL_CREATED → CALL_ANSWERED → CALL_ENDED corretos no Redis
- Reconciliação pós-reconexão (calls/agents/queues/registrations/gateways) — não é possível ainda, sem essas tabelas persistidas
PHASE 02 — SaaS Core
- Monorepo Node.js/TypeScript (pnpm workspaces, tsconfig base)
- Node 22 LTS + pnpm instalados no host
packages/database(Prisma 7 + driver adapterpg, migration inicial)packages/types(TenantStatus, AgentState),packages/shared- Tabela
tenantscriada via migration (seção 29 do agente.md)
PHASE 03 — Tenant Isolation
- Tabelas
users+tenant_memberships(tenant-scoped) - RLS (
ENABLE/FORCE ROW LEVEL SECURITY+ policy) emtenant_memberships - Tenant context via
set_config('app.current_tenant_id', ..., true)(transaction-local) - Helper
withTenantContext()empackages/database - Role de banco separado para runtime (
b2bcall_app, sem SUPERUSER/BYPASSRLS) — achado crítico: o role padrão do Docker Postgres é SUPERUSER e SEMPRE ignora RLS, até com FORCE. Verdocs/TENANT_ISOLATION.md. - Teste automatizado de isolamento (
pnpm --filter @b2bcall/database run test:isolation)
PHASE 04 — Authentication / RBAC
packages/auth: hash Argon2id (@node-rs/argon2), JWT access token (jose), refresh token opaco com rotation- Tabelas
roles,permissions,role_permissions,user_roles,sessions,audit_logs login()/refreshSession()/logout()/listUserTenants()/setActiveTenant()userHasPermission()(RBAC com scope PLATFORM/TENANT)- Seed: catálogo de permissions + roles de sistema + Platform Super Admin inicial
(senha em
FIRST_LOGIN.txt, fora do Git,mustChangePassword=true) - Teste automatizado (
pnpm --filter @b2bcall/auth run test:auth) apps/api(NestJS + Fastify): endpoints de auth, JwtAuthGuard, DomainExceptionFilter, rate limit de login via Redis (5/min por IP e por e-mail), helmet/cors, health checks — testado ponta a ponta com curl (login, refresh rotation, logout, RBAC, 401/403/429)- Password reset por e-mail — depende de SMTP configurado
PHASE 07+ — ver agente.md seções 26 em diante (XML Curl, Extensions, Trunks, Dialplan,
Call Center, Predictive Dialer, Recordings, AI, Billing, Frontend, Reports, Security, Tests)
Riscos conhecidos
- RAM da VM (1.9GB total): medido com Postgres+Redis+FreeSWITCH rodando juntos — ~91MB no total (Postgres 37MB, Redis 10MB, FreeSWITCH 44MB), bem tranquilo. O risco real ainda não testado é o build/runtime do Next.js (frontend) e vários workers Node simultâneos — reavaliar quando chegarmos lá.
- Disco (26GB livre): build do FreeSWITCH + imagens Docker + gravações vão consumir
espaço rápido. Monitorar com
df -h.