Files
B2BCall-dialer/apps/frontend/src/components/tenant-shell/agent-me-actions.ts
Matheus 97ef8a6ba8 feat: edição de rotas de entrada, fix de 2 bugs reais no ESL, diagnóstico de NAT/áudio e softphone WebRTC (PHASE 65/66)
Três achados reportados pelo usuário numa mensagem só: (1) Rotas de Entrada
não tinha edição depois de criada — implementada no mesmo padrão de Filas;
(2) telas de Platform > Infraestrutura sempre davam "Timeout no ESL" nesta
VM — não era limitação permanente como o comentário antigo dizia, e sim
ESL_HOST=freeswitch (nome DNS que só existe dentro da rede do Docker) mais
um segundo bug independente (`show gateways as json` não é comando válido
nesta versão do FreeSWITCH); (3) ramal externo registrava mas sem áudio —
diagnosticado com contadores de pacote do iptables: a VM está atrás de um
roteador sem port-forward pra faixa de RTP, achado de infraestrutura de
rede, não bug de código.

Também integra o softphone WebRTC (handphone.js/OpenSIPS, já em produção):
código-fonte encontrado em git.falehandix.com.br/Handix/handphone-2.0,
patch mínimo pra aceitar o endereço do proxy em runtime (era build-time),
nova config global (Platform > Infraestrutura > Softphone WebRTC) e widget
na topbar do tenant que pega usuário/domínio/senha do ramal vinculado ao
agente logado.

Adiciona docs/QA_SETUP.md — runbook completo pra subir o ambiente do zero
numa máquina nova (Docker, migrations, seed, systemd), e completa o
.env.example que estava faltando a maioria das variáveis reais.

Testado ponta a ponta com Playwright: edição de rota (criar/editar/F5),
as 3 telas de Infraestrutura com dado real, e um tenant/ramal/agente de
teste criados na hora confirmando que o script do softphone recebe as
credenciais certas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 22:14:39 -03:00

72 lines
2.6 KiB
TypeScript

"use server";
import { requireSession } from "@/lib/session";
import { apiFetch, ApiError } from "@/lib/api";
function extractErrorMessage(err: unknown): string {
if (err instanceof ApiError) {
try {
const parsed = JSON.parse(err.message);
if (Array.isArray(parsed.message)) return parsed.message.join(" ");
if (typeof parsed.message === "string") return parsed.message;
} catch {
// corpo não era JSON
}
return err.message || "Falha inesperada na API.";
}
return "Falha inesperada. Tente novamente.";
}
type ActionResult = { ok: true; state: string } | { ok: false; error: string };
async function callAgentMe(path: string, body?: unknown): Promise<ActionResult> {
const session = await requireSession();
try {
const res = await apiFetch<{ state: string }>(`/agents/me${path}`, session.accessToken, {
method: "POST",
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
});
return { ok: true, state: res.state };
} catch (err) {
return { ok: false, error: extractErrorMessage(err) };
}
}
// Server Actions exportadas como arrow function que só repassam
// argumentos pra outra função quebram em runtime ("Server Actions must
// be async functions") — achado real já documentado nesta sessão
// (mesmo bug do wizard de campanhas). Precisam ser `async function` de
// verdade, mesmo só repassando.
export async function agentLogin(): Promise<ActionResult> {
return callAgentMe("/login");
}
export async function agentLogout(): Promise<ActionResult> {
return callAgentMe("/logout");
}
export async function agentResume(): Promise<ActionResult> {
return callAgentMe("/resume");
}
export async function agentPause(pauseReasonId: string): Promise<ActionResult> {
return callAgentMe("/pause", { pauseReasonId });
}
export type SoftphoneConfig =
| { hasExtension: false }
| { hasExtension: true; username: string; domain: string; password: string; displayName: string; proxyUrl: string | null };
/**
* Sob demanda, não no layout (agente.md secao 39/178: decifrar senha é
* sensível e fica no audit log — buscar isto em toda navegação encheria o
* log à toa). Chamado uma vez pelo próprio widget do softphone quando ele
* monta, só pra quem já tem um Agent com ramal vinculado.
*/
export async function getSoftphoneConfig(): Promise<{ ok: true; config: SoftphoneConfig } | { ok: false; error: string }> {
const session = await requireSession();
try {
const config = await apiFetch<SoftphoneConfig>("/agents/me/softphone-config", session.accessToken);
return { ok: true, config };
} catch (err) {
return { ok: false, error: extractErrorMessage(err) };
}
}