Pular para conteúdo

Guia de Reconexão WebSocket — Integradores

Fluxo esperado, códigos de fechamento e boas práticas de implementação

Documento para integradores

Este guia explica como o SADI Online se comporta durante quedas de conexão, reconexões e troca de conexão ativa — e o que implementar no cliente.


Visão geral

O streaming WebSocket trabalha com o conceito de sessão, identificada pelo session_id (UUID). A sessão permanece ativa mesmo quando a conexão WebSocket cai temporariamente.

Conceito Descrição
Sessão Identificada pelo session_id retornado na conexão
Conexão WebSocket Canal temporário; pode cair e ser reaberto
Janela de reconexão Ilimitada por padrão; configurável para um prazo finito
Seleção da sessão O session_id explícito pode apontar para uma sessão histórica do próprio estudante
Substituição de conexão Nova conexão assume a sessão; antiga recebe 4007
Intervalo mínimo 5s entre reconexões bem-sucedidas consecutivas

Regra de ouro

Guarde o session_id retornado na conexão inicial e use-o em todas as reconexões. Mesmo após POST /sessions/{id}/end/, é possível reativar a mesma sessão reconectando com o mesmo session_id. Só inicie sessão nova (sem session_id) quando quiser descartar o histórico desta sessão.


Conexão nova (primeira vez)

Cliente → wss://ws.../v1/stream/?student_id=...&activity_id=...
  → autenticação (API Key)
  → servidor cria nova sessão
  → {"status": "connected", "session_id": "uuid..."}
Parâmetro Obrigatório Descrição
student_id Sim UUID do estudante
activity_id Sim (Enterprise) UUID da atividade
session_id Não Omitido = nova sessão

Armazene o session_id retornado para reconexões futuras.


Reconexão com session_id

Cliente → wss://...?student_id=...&activity_id=...&session_id={uuid}
  → servidor valida sessão ativa e pertencente ao estudante
  → conexão antiga (se existir) recebe 4007
  → nova conexão aceita
  → {"status": "connected", "session_id": "mesmo-uuid"}

Retome o envio de frames após receber a confirmação. Não é necessário reenviar frames anteriores.

Erros comuns ao reconectar (4001)

Motivo no JSON O que fazer
reconnect_window_expired Nova sessão (somente quando o grace finito configurado expirou)
reconnect_cooldown_active Aguardar 5s e tentar novamente
session_open_conflict Outra sessão do estudante ainda está aberta; finalizar ou retomar essa sessão antes
reactivate_not_ended Sessão não está encerrada; verificar estado via API

Disconnect normal e política de reconexão

Quando a conexão cai (1000, 1006, timeout):

  1. A sessão entra em RECONNECTING e conserva o mesmo session_id
  2. No padrão (DJANGO_WS_RECONNECT_GRACE_SECONDS=0), ela pode ser reconectada sem prazo
  3. Com DJANGO_WS_RECONNECT_GRACE_SECONDS=60 (modo legado), ela é encerrada se o deadline expirar

Durante uma queda transitória, não chame POST /sessions/{id}/end/.

O intervalo offline não entra na duração efetiva nem no billing. Uma sessão em RECONNECTING continua sendo uma sessão aberta e bloqueia a criação de outra sessão para o mesmo estudante. Para retenção operacional opcional, configure DJANGO_WS_RECONNECT_MAX_AGE_DAYS com um valor maior que zero; o sweeper encerra sessões abandonadas acima desse limite e libera a criação de uma nova sessão.

O servidor não exige que a sessão escolhida seja a mais recente. O cliente deve enviar o session_id da sessão que deseja retomar. Se outra sessão do estudante já estiver aberta, a retomada é rejeitada com session_open_conflict; o servidor não encerra a sessão aberta automaticamente.


Reconexão após POST /sessions/{id}/end/

Chamar /end/ marca a sessão como ENDED e fecha o WebSocket ativo com 4002 (se ainda houver conexão). Isso não impede reconectar depois com o mesmo session_id.

POST /sessions/{id}/end/  → status ENDED, socket recebe 4002
Usuário quer continuar
WebSocket com o mesmo session_id  → sessão reativada (status ACTIVE)
{"status": "connected", "session_id": "mesmo-uuid"}
Ação após /end/ Funciona? Resposta
WebSocket com o mesmo session_id Sim Sessão reativada; retoma frames
POST /pause/ Não HTTP 409 (session_already_ended) — reconecte antes de pausar
WebSocket sem session_id Sim Cria sessão nova (histórico anterior fica encerrado)

Dica de implementação

Ao receber 4002 após /end/, não descarte o session_id se o usuário puder querer continuar a mesma aula. Mostre "Retomar" e reconecte com o mesmo session_id. Descarte o session_id só quando o usuário iniciar uma atividade nova de fato.

O tempo entre o /end/ e a reconexão não entra na duração efetiva nem na cobrança. Apenas o tempo com a sessão ativa (antes e depois do encerramento) conta.


Código 4007 — Substituição de conexão

Significa que outra conexão assumiu esta sessão. Comportamento esperado, não é erro.

Ao receber 4007:

  • Pare de enviar frames nesta conexão
  • Não reconecte automaticamente nesta instância/aba
  • Não chame POST /sessions/{id}/end/
  • A sessão continua ativa na outra conexão

Código 4002 — Sessão encerrada

Significa que a sessão foi encerrada nesta conexão e não aceita mais frames por aqui.

Causas comuns:

  • Integrador chamou POST /sessions/{id}/end/
  • Expirou o deadline configurado em modo finito (DJANGO_WS_RECONNECT_GRACE_SECONDS>0)
  • Outro sistema encerrou a sessão via API

Erro frequente

Conexão cai → integrador chama POST /end/ no onclose → sessão encerra → socket recebe 4002. A sessão ainda pode ser reativada com o mesmo session_id se o usuário continuar a atividade.

Solução: nunca chame /end/ automaticamente no onclose.

Ao receber 4002:

  • Pare de enviar frames nesta conexão
  • Se o usuário pode continuar a mesma atividade: guarde o session_id e reconecte com ele (reativa a sessão)
  • Se o usuário encerrou de fato: descarte o session_id e conecte sem ele para nova sessão

Tabela de códigos de fechamento

Código Significado Sessão encerra? O que fazer
1000 Fechamento normal Não (grace ilimitado por padrão) Reconectar com session_id
1006 Conexão perdida Não (grace ilimitado por padrão) Reconectar com session_id
4001 Falha create/reconnect Ver motivo no JSON
4002 Sessão encerrada nesta conexão Sim (até reativar) Reconectar com session_id para continuar, ou nova sessão
4003 Autenticação inválida Verificar API Key
4004 Limite de uso excedido Sim Contatar suporte
4005 Frames inválidos Sim Corrigir formato
4006 Device revogado Sim Reautenticar
4007 Outra conexão assumiu Não Parar; não reconectar nem chamar /end

O que NÃO fazer

Comportamento incorreto Consequência
POST /end/ no onclose após queda de rede Sessão encerra; pode reativar com session_id ou criar nova
Auto-reconnect na aba que recebeu 4007 Loop de troca de conexão
Reconectar sem session_id quando quer continuar Nova sessão; histórico perdido
Múltiplas abas na mesma sessão Troca constante (4007)

Quando chamar POST /sessions/{id}/end/

Somente quando o usuário encerra explicitamente a atividade. Nunca no onclose do WebSocket.


Implementação recomendada no cliente

let sessionId = null;
let reconnectAttempts = 0;
let isSuperseded = false;
let shouldEndSession = false;

function onClose(event) {
  switch (event.code) {
    case 4007:
      isSuperseded = true;
      return;
    case 4002:
      // sessão encerrada nesta conexão; manter sessionId se o usuário puder retomar
      return;
    case 4001:
      // verificar reason no JSON de erro
      return;
    case 1000:
    case 1006:
    default:
      if (sessionId && !shouldEndSession) {
        reconnectWithBackoff();
      }
      return;
  }
}

async function endSessionByUser() {
  shouldEndSession = true;
  ws.close(1000, 'User ended session');
  await fetch(`/tenant/${orgId}/v1/sessions/${sessionId}/end/`, {
    method: 'POST',
    headers: { 'Authorization': `APIKey ${apiKey}` },
  });
  sessionId = null;
}

Troubleshooting

4002 logo após /end/: comportamento esperado. Reconecte com o mesmo session_id se o usuário quiser continuar.

4002 após queda de rede + /end/ no onclose: remova /end/ automático do onclose; use reconnect transitório (1000/1006) em vez disso.

Loop de 4007: duas abas disputando a mesma sessão — apenas uma deve manter conexão.

reconnect_cooldown_active: aguarde 5s após reconnect bem-sucedido.

reconnect_window_expired: o deadline finito expirou — criar nova sessão.

No modo padrão, esse erro não é produzido por passagem de tempo. Se a sessão continuar em RECONNECTING, reconecte usando o mesmo session_id ou encerre-a explicitamente; não crie outra sessão sem session_id esperando que a anterior seja substituída.


Documentos relacionados


SADI Online — Instituto Anexo · 23 de junho de 2026