Desenvolvimento
Backend (WebSocket Server)
Detalhes do servidor WebSocket - arquitetura, serviços e integração ARI.
Backend (stcall-ws)
O servidor WebSocket é uma aplicação Bun nativa que conecta os navegadores dos agentes ao Asterisk ARI.
Stack
| Tecnologia | Uso |
|---|---|
| Bun 1.x | Runtime TypeScript nativo |
| WebSocket (Bun) | Comunicação em tempo real |
| Prisma 7 | ORM para PostgreSQL e MySQL |
| pg | Driver PostgreSQL |
| mariadb | Driver MySQL/MariaDB |
| bcryptjs | Hash de senhas |
Entry Point: server.ts
O servidor principal:
- Inicia servidor HTTP/WebSocket Bun
- Conecta ao Asterisk ARI via WebSocket
- Aceita conexões de agentes (com validação JWT)
- Roteia eventos ARI para agentes
- Executa comandos dos agentes via ARI HTTP
Módulos Principais
agents/registry.ts
Mantém um Map<string, AgentConnection> em memória com todos os agentes conectados:
interface AgentConnection {
agentId: string
websocket: WebSocket
connectedAt: Date
lastPing: Date
channels: Set<string>
}
agents/auth.ts
Validação JWT manual (sem dependência de biblioteca JWT):
- Divide token em 3 partes (header, payload, signature)
- Decodifica payload (base64)
- Verifica campos obrigatórios
- Verifica expiração
- Verifica assinatura HMAC-SHA256
asterisk/connection.ts
Mantém conexão WebSocket persistente com Asterisk ARI:
- URL:
ws://host:8088/ari/events?app=stCall - Reconexão automática com backoff exponencial
- Heartbeat configurável
asterisk/api-client.ts
Cliente HTTP para ARI REST API:
- Executa comandos (originate, answer, hangup, hold, etc.)
- Autenticação Basic
- Retorna resultados ao agente via WebSocket
commands/handler.ts
Processa comandos recebidos dos agentes:
switch (command.action) {
case 'originate': // Iniciar chamada
case 'answer': // Atender canal
case 'hangup': // Desligar canal
case 'hold': // Colocar em espera
case 'unhold': // Remover da espera
case 'mute': // Silenciar
case 'unmute': // Ativar áudio
case 'fetchCallHistory': // Buscar histórico
// ...
}
routing/filter.ts
Filtra eventos ARI e determina para qual agente enviar:
- Eventos de canal do agente → agente específico
- Eventos de sistema → todos os admins
- Eventos de chamada → agente responsável
services/call-tracker.ts
Registra chamadas no PostgreSQL:
class CallTracker {
async handleStasisStart(event) {
await this.prisma.call.upsert({
where: { channelId: channel.id },
create: { /* dados da chamada */ }
})
}
}
services/pjsip-sync.ts
Sincroniza endpoints PJSIP com FreePBX MySQL:
class PJSIPSyncService {
async createEndpoint(config) {
await this.prisma.$transaction(async (tx) => {
await tx.psEndpoint.create({ data: { /* ... */ } })
await tx.psAuth.create({ data: { /* ... */ } })
await tx.psAor.create({ data: { /* ... */ } })
})
}
}
utils/logger.ts
Logger estruturado que emite JSON para stdout:
logger.info('Agent connected', { agentId: 'agent-123' })
// {"level":"INFO","message":"Agent connected","agentId":"agent-123","timestamp":"..."}
Níveis: DEBUG, INFO, WARN, ERROR
utils/encryption.ts
Criptografia de senhas SIP usando SIP_ENCRYPTION_KEY:
- Senhas armazenadas criptografadas em
users.sipPassword - Descriptografadas apenas quando necessário para sincronização PJSIP
Protocolo WebSocket
Mensagens do Servidor → Cliente
// Evento ARI
{ type: "event", data: { type: "StasisStart", channel: {...} }, timestamp: "..." }
// Mensagem do sistema
{ type: "system", data: { message: "Connected", severity: "info" }, timestamp: "..." }
// Resposta a comando
{ type: "command_response", requestId: "uuid", data: { success: true, result: {...} } }
// Pong (heartbeat)
{ type: "pong", timestamp: "..." }
Mensagens do Cliente → Servidor
// Comando
{ type: "command", requestId: "uuid", data: { action: "hangup", params: { channelId: "..." } } }
// Ping (heartbeat)
{ type: "ping" }
Rotas HTTP
| Rota | Método | Descrição |
|---|---|---|
/health | GET | Health check do servidor |
/api/auth/login | POST | Autenticação de agente |
/api/users | GET/POST | Gerenciamento de usuários |
/api/pjsip/sync | POST | Sincronização PJSIP |