Diagnóstico no ping de reunião
TLDR: anexar um payload de diagnóstico versionado (dispositivo, rede, qualidade Twilio) ao ping de reunião existente, para que problemas de conexão durante uma chamada possam ser atribuídos à rede do usuário, ao nosso servidor ou ao Twilio.
Contexto
Quando uma reunião falha ou tem qualidade ruim, não temos dados estruturados para responder: “o problema foi a internet do usuário, nosso servidor, ou o Twilio?”. O backend (chat.trg.club) já foi atualizado nesta branch para aceitar um payload de diagnóstico no corpo do ping. O frontend ainda envia um PUT vazio. usePingMeeting existe mas não está conectado a nenhum container.
Objetivos
- Conectar
usePingMeetingemCall.tsxpara que o ping de fato dispare durante as chamadas - Construir um payload de diagnóstico versionado no cliente e enviá-lo no corpo do ping
- Habilitar a Network Quality API do Twilio para obter um nível de qualidade (0–5) por participante
- Amostrar estatísticas WebRTC via
room.getStats()em baixa frequência para evitar sobrecarga da chamada - Capturar eventos de conexão (códigos 53001/53405) já logados em
connectionFactory.ts - Fornecer fallbacks seguros para APIs ausentes no Safari (ex.:
navigator.connection)
Fora de escopo
— (não registrado no documento original)
Mudanças
| Arquivo | O que muda |
|---|---|
src/hooks/usePingMeeting.ts |
Aceita callback getDiagnostics; inclui payload no corpo do PUT |
src/infra/CallManager/strategies/twilio/factories/connectionFactory.ts |
Adiciona networkQuality: { local: 1, remote: 1 } ao twilioConnect; expõe getNetworkStats() e eventos de conexão recentes |
src/infra/CallManager/CallManager.ts |
Expõe getNetworkStats() do connectionFactory na API pública |
src/infra/diagnostics/buildDiagnosticsPayload.ts |
Novo utilitário: monta o payload versionado a partir de APIs do browser + stats do Twilio |
src/containers/Call/Call.tsx |
Conecta usePingMeeting com o callback de diagnóstico |
Payload Schema
typescript
{
diagnostics: {
timestamp: string, // ISO 8601 (client-side; não faz parte do schema documentado do backend, mantido para depurar clock drift contra o pinged_at do servidor)
device: {
user_agent: string, // navigator.userAgent
},
network: {
online: boolean, // navigator.onLine
effective_type: string | null, // '4g' | '3g' | '2g' | 'slow-2g'; null quando navigator.connection não está disponível (Safari)
downlink: number | null, // Mbps
rtt: number | null, // ms
save_data: boolean,
},
app_state: {
visibility_state: 'visible' | 'hidden', // document.visibilityState
},
media_devices: {
audioinput_count: number,
audiooutput_count: number,
videoinput_count: number,
},
twilio: {
network_quality_level: number | null, // 0–5; null se ainda não disponível
network_quality_stats: object | null, // NetworkQualityStats bruto do twilio-video; null abaixo do nível de verbosidade 2
reconnection_events: Array<{ code: number; message: string; ts: string }>,
},
},
}
Tudo isso é embrulhado sob uma chave diagnostics no corpo do PUT, casando com os nomes das chaves de topo do próprio spec do backend (trgclub-api .project/specs/20260729143000_meeting_ping_diagnostics.md): device, network, app_state, media_devices, twilio. Nenhum campo schema_version é enviado.
Lacunas conhecidas vs. o schema documentado do backend — alinhadas em nome mas não em formato, já que fechar a lacuna exigiria nova coleta de dados, não apenas renomear:
- media_devices no backend é documentado como por-dispositivo { camera: { label, device_id }, microphone: {...}, audio_output: {...} }. O frontend só envia contagens agregadas (audioinput_count, etc.) — nunca pediu labels/IDs de dispositivo via enumerateDevices().
- twilio.network_quality_stats é documentado como um formato normalizado { audio: { send, recv }, video: { send, recv } }. O frontend repassa o objeto bruto do SDK do Twilio como está, sem normalizar.
- twilio.room_stats e twilio.errors (como campo distinto de reconnection_events) não são produzidos pelo frontend de forma alguma.
- Nada disso quebra nada hoje — o controller do backend usa to_unsafe_h sem whitelist de chaves/validação de formato, então qualquer hash é aceito e persistido como está.
Estratégia de amostragem: a qualidade de rede é lida a cada intervalo de ping (20 s). As estatísticas WebRTC (room.getStats()) são amostradas uma vez por minuto e cacheadas; o valor cacheado é anexado ao próximo ping. Isso evita chamar getStats() 3× por minuto.
Plano de implementação
-
test:
buildDiagnosticsPayload— cobre: navigator.connection presente, ausente (Safari), offline, aba oculta, contagem de dispositivos, stats do twilio mesclados —src/infra/diagnostics/__tests__/buildDiagnosticsPayload.test.ts -
feat: criar
src/infra/diagnostics/buildDiagnosticsPayload.ts— lê APIs do browser com fallbacks seguros, mescla o argumento de stats do twilio no payload versionado -
test: qualidade de rede do
connectionFactory— cobre: opção networkQuality passada ao twilioConnect, getNetworkStats retorna nível + stats, eventos de erro anexados ao buffer, buffer limpo no flush —src/infra/CallManager/strategies/twilio/factories/__tests__/connectionFactory.test.ts -
feat: atualizar
connectionFactory.ts— adicionarnetworkQuality: { local: 1, remote: 1 }às opções de connect; bufferizar códigos de erroreconnecting; exporgetNetworkStats()que retorna{ level, stats, recentErrors }e limpa o buffer de erros -
feat: expor
getNetworkStatsna API pública doCallManager—src/infra/CallManager/CallManager.ts -
test:
usePingMeetingcom payload — cobre: corpo do PUT inclui payload serializado, payload vazio ainda envia, retry false —src/hooks/__tests__/usePingMeeting.test.ts -
feat: atualizar
usePingMeeting.ts— aceitargetDiagnostics?: () => DiagnosticsPayloadopcional, chamá-lo noqueryFn, passar o resultado como corpo JSON do PUT -
feat: conectar
usePingMeetingemCall.tsx— passar um callbackgetDiagnosticsque chamabuildDiagnosticsPayloadcomcallManager.getNetworkStats()
Como verificar
- Abrir uma chamada; abrir DevTools → Network → filtrar por
pings - O corpo da requisição PUT deve ser um objeto JSON com uma chave
diagnosticsde topo (sem camposchema_version, em nenhum lugar) diagnostics.twilio.network_quality_leveldeve ser um número 0–5 após ~5 s de chamadadiagnostics.network.effective_typedeve sernullno Safari; deve ter um valor (ex.:"4g") no Chrome- Simular offline (
navigator.onLine = falseno console) e confirmar que o próximo ping mostradiagnostics.network.online: false - Verificar logs do backend (
chat.trg.club) /MeetingParticipantPing.last.diagnosticspara confirmar que o payload é recebido e persistido sob as chaves esperadas
Decisões e trade-offs
networkQuality: { local: 1, remote: 1 }(nível de verbosidade 1) — dá o inteiro de nível de qualidade sem o detalhamento por-track completo que o nível 3 produz; suficiente para triagem e baixo overhead- Stats WebRTC amostradas a 60 s —
room.getStats()é assíncrono e envolve round-trip ao SDK do Twilio; chamar a cada 20 s significaria 3 chamadas/min por participante. Cachear e anexar ao próximo ping disponível é o trade-off certo - Buffer de erros limpo na leitura — cada ping recebe os erros acumulados desde o último ping, sem duplicatas
usePingMeetingainda não conectado — esta mudança é a primeira vez que ele dispara em produção; vale destacar no PR- LGPD:
navigator.userAgente contagem de dispositivos são dados funcionais/operacionais, não pessoais;navigator.connectioné metadado de rede. Nenhum PII é coletado
Documentação
Atualizar .project/docs/rules/onboarding/pro_bono_step_renders_anjos_e_tutelados.md apenas se o diagrama de fluxo de chamada mudar (referência preservada do documento original — não verificada nesta migração).
Fora isso: nenhuma mudança de documentação necessária para este spec; o payload schema acima é a referência autoritativa.