fix(frontend): widget do softphone preso em cinza — dupla chamada de connect() sem cooldown

Dois efeitos de auto-connect do Widget.jsx (mount + reconnect-on-drop)
podiam disparar connect() em sequência rápida sem respeitar o cooldown
de 5s (só o efeito de reconexão gravava o timestamp), abrindo um
segundo SimpleUser/registro concorrente que sip.js rejeita com
RequestPendingError — e o catch sobrescrevia connectionStatus pra
"disconnected" incondicionalmente, travando o widget em cinza.
Trava por promise em useSip.js::connect() + cooldown também no
auto-connect de mount resolvem: testado com Playwright dentro do app
real, connect() dispara uma única vez e o status nunca mais volta
pra cinza.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-31 13:46:52 -03:00
parent 9d3c84064f
commit e75872faec
2 changed files with 39 additions and 1 deletions

38
TODO.md
View File

@@ -2598,6 +2598,44 @@ problema")
atual" de um ramal já existente) mostram o ícone verde de sucesso atual" de um ramal já existente) mostram o ícone verde de sucesso
via o fallback. via o fallback.
## PHASE 72 — Widget do softphone voltou a ficar preso em cinza (pedido
do usuário: "o widget não conecta mais fica só cinza")
- [x] **Achado real**: só reproduzia dentro do app de verdade (tenant
logado), nunca numa página HTML isolada — Playwright com log de
diagnóstico (`new Error().stack`) provou que `connect()` era
chamado duas vezes em sequência rápida a partir do MESMO efeito de
auto-connect do `Widget.jsx`: o efeito de "auto-connect no mount"
dispara `handleConnect`, e se essa primeira tentativa falha (ou
demora), o efeito de "auto-reconnect quando cai" também dispara —
mas ele só grava `lastReconnectRef` (o cooldown de 5s) quando É ELE
quem chama `handleConnect`, nunca quando é o efeito de mount que
chama. Resultado: a primeira tentativa falha, o efeito de
reconexão vê o cooldown zerado e dispara uma segunda tentativa
SEM NENHUM atraso, criando um segundo `SimpleUser`/registro
simultâneo ao primeiro — sip.js rejeita com `RequestPendingError`
("REGISTER request already in progress"), e o `catch` do
`connect()` sobrescreve `connectionStatus` pra `"disconnected"`
incondicionalmente, travando o botão em cinza pra sempre mesmo que
a primeira tentativa estivesse indo bem.
- [x] Fix (mesma cópia local patcheada do
`git.falehandix.com.br/Handix/handphone-2.0`, nunca enviada pro
repo externo): (1) `useSip.js::connect()` agora é só um wrapper com
trava (`connectPromiseRef`) — uma chamada concorrente reaproveita a
MESMA promise em andamento em vez de abrir um `SimpleUser` novo
colidindo com o primeiro; (2) `Widget.jsx`, o efeito de
auto-connect no mount agora também grava `lastReconnectRef` antes
de chamar `handleConnect`, então o cooldown de 5s do efeito de
reconexão vale pra QUALQUER tentativa automática, não só as dele
próprio. Rebuildado e revendorizado em
`apps/frontend/public/handphone.js`.
- [x] Testado ponta a ponta com Playwright dentro do app de verdade
(tenant/ramal/agente criados na hora via fluxo normal da UI, sem
tocar em conta real): antes do fix, `connect()` disparava duas
vezes e o widget acabava preso em `"disconnected"`; depois do fix,
dispara uma única vez (`doConnect() ENTER, count= 1` — confirmado
em 4 execuções seguidas) e o status evolui
`connecting → connected` sem nunca voltar pra cinza.
--- ---
## Riscos conhecidos ## Riscos conhecidos

File diff suppressed because one or more lines are too long