Pular para o conteúdo
~/.primo-academy.sh
☰ Aulas · Real-time: WebSockets, SSE, Socket.IO, reconexao e presence · 0/7
Recomendado: essencial

WebSockets nativo: readyState, eventos, mensagens binarias e texto

5 min de leitura

fonte

Voce entendeu as 4 abordagens de real-time e escolheu WebSocket pro seu caso (bidirecional, baixa latencia, chat/jogo/ colaboracao). Agora vamos abrir a API nativa do browser: new WebSocket(url).

WebSocket e' a classe do browser (escrita como WebSocket, com W e S maiusculos, nao websocket). Apos instanciar, voce tem 4 estados, 4 eventos principais, e 2 metodos de envio (texto/JSON ou binario). Em 2026, e' a API mais usada pra real-time bidirecional no browser.

Se voce entende o readyState (o "ciclo de vida" da conexao), os 4 eventos principais (open, message, error, close), e como mandar/receber mensagens texto (JSON) e binarias (Blob/ArrayBuffer), voce sai de "como uso WebSocket?" pra "implemento o chat real-time" com seguranca.

O essencial 🟢

new WebSocket(url) - a entrada da API. O construtor aceita URL com protocolo especial: ws:// (HTTP) ou wss:// (HTTPS, equivalente a wss://TLS).

// Conexao basica
const ws = new WebSocket("wss://api.example.com/ws");

// Com subprotocol (opcional)
const ws = new WebSocket("wss://api.example.com/ws", "graphql-ws");

// Com headers custom (via subprotocol ou query string)
// WebSocket nao suporta headers custom na URL - use query string
const ws = new WebSocket("wss://api.example.com/ws?token=abc123");

O readyState - o ciclo de vida do WebSocket. Apos new WebSocket(), a conexao passa por 4 estados numericos:

Ciclo de vida do WebSocket: 4 estados (CONNECTING=0, OPEN=1, CLOSING=2, CLOSED=3). Eventos: open (handshake completo), message (dados recebidos), error (problema), close (conexao fechada). Reconexao e' manual.

Valores numericos:

  • 0 - CONNECTING: socket criado, handshake em andamento.
  • 1 - OPEN: handshake completo, pode mandar/receber.
  • 2 - CLOSING: close() chamado, handshake de fechamento em andamento.
  • 3 - CLOSED: conexao fechada.
const ws = new WebSocket(url);
console.log(ws.readyState);  // 0 (CONNECTING)

ws.addEventListener("open", () => {
  console.log(ws.readyState);  // 1 (OPEN)
});

Os 4 eventos principais. A API WebSocket e' event-based - voce registra listeners:

const ws = new WebSocket("wss://api.example.com/ws");

// 1. open: conexao estabelecida
ws.addEventListener("open", (event) => {
  console.log("Conectado!");
  ws.send(JSON.stringify({ type: "hello" }));
});

// 2. message: dados recebidos do server
ws.addEventListener("message", (event) => {
  // event.data pode ser string OU Blob/ArrayBuffer
  if (typeof event.data === "string") {
    const data = JSON.parse(event.data);
    handleMessage(data);
  } else {
    // binario
  }
});

// 3. error: problema na conexao
ws.addEventListener("error", (event) => {
  console.error("WebSocket error:", event);
  // event nao tem muito detalhe - WebSocket errors sao opacos
});

// 4. close: conexao fechada (limpa ou nao)
ws.addEventListener("close", (event) => {
  console.log(`Fechado: code=${event.code}, reason=${event.reason}`);
  // code 1000 = normal closure
  // code 1001 = going away
  // code 1006 = abnormal closure (sem close frame)
});

send() - mandar dados pro server. O metodo send() aceita string ou binario (Blob/ArrayBuffer). Tentar mandar em estado errado lanca erro:

// String (ou JSON.stringify pra objeto)
ws.send("Hello, server!");
ws.send(JSON.stringify({ type: "ping", ts: Date.now() }));

// Blob (binario)
const blob = new Blob([binaryData]);
ws.send(blob);

// ArrayBuffer (binario, mais comum)
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
ws.send(view);

// Erro: tentar mandar antes de OPEN
ws.send("test");  // throws InvalidStateError

Otimizacao: empacote multiplas mensagens em 1 so quando possivel (reduz overhead):

// Ruim: 10 sends = 10 frames WebSocket
for (const item of items) {
  ws.send(JSON.stringify(item));
}

// Bom: 1 send = 1 frame
ws.send(JSON.stringify(items));

close() - fechar a conexao limpa. Quando terminar a sessao, chame close():

ws.close(1000, "User logged out");
// 1000 = normal closure
// "User logged out" = reason (opcional, visivel so no server)

close() vs close() automatico:

  • close(): graceful - envia close frame, server confirma, conexao fecha.
  • Sem close(): o server detecta via close event, ou o socket fica aberto ate o browser fechar (memory leak).

Sempre chame close() em cleanup (component unmount, user logout, page unload).

binaryType - text vs Blob vs ArrayBuffer. Por default, event.data em mensagem binaria vem como Blob. Voce pode mudar pra ArrayBuffer:

const ws = new WebSocket("wss://api.example.com/ws");
ws.binaryType = "arraybuffer";  // ou "blob" (default)

ws.addEventListener("message", (event) => {
  if (event.data instanceof ArrayBuffer) {
    // Manipular como ArrayBuffer
    const view = new Uint8Array(event.data);
  }
});

Quando usar arraybuffer: quando voce vai processar os bytes (parsear protobuf, descomprimir, etc). Quando usar blob (default): quando voce vai armazenar (file upload, video chunk, etc).

Subprotocols - o "content-type" do WebSocket. Alem do URL, da' pra especificar subprotocol (negociado durante o handshake):

// Client
const ws = new WebSocket("wss://api.example.com/graphql", "graphql-transport-ws");

// Server (Node `ws`)
const wss = new WebSocket.Server({
  handleProtocols: (protocols) => {
    if (protocols.has("graphql-transport-ws")) return "graphql-transport-ws";
    return false;  // rejeita conexao
  },
});

Subprotocols comuns em 2026:

  • graphql-transport-ws (GraphQL subscriptions).
  • wamp (Web Application Messaging Protocol).
  • Protocolos custom (chat, game, etc).

Reconectar - o lado que voce implementa. O WebSocket nao tem reconexao automatica. Se o servidor cair ou a rede cair, a conexao morre e seu cliente fica desconectado ate voce reconectar manualmente. Cobre em reconexao-e-estado com exponential backoff + heartbeat.

// Pattern basico de reconexao
function connect() {
  const ws = new WebSocket(url);

  ws.addEventListener("close", () => {
    setTimeout(connect, 3000);  // tenta em 3s
  });

  return ws;
}

Em prod, sempre combine com exponential backoff + jitter + heartbeat.

Aprofundamento 🟡

ping/pong - o heartbeat do WebSocket. WebSocket nao detecta conexao morta (network drop, NAT timeout, proxy timeout). O ping/pong e' o heartbeat que detecta:

// Server (Node `ws`)
const wss = new WebSocket.Server({ port: 8080 });

wss.on("connection", (ws) => {
  ws.isAlive = true;
  ws.on("pong", () => { ws.isAlive = true; });
});

setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) {
      ws.terminate();  // mata conexao zumbi
      return;
    }
    ws.isAlive = false;
    ws.ping();  // envia ping
  });
}, 30000);  // a cada 30s
// Client (browser)
ws.addEventListener("pong", () => {
  // marcar como vivo
  lastPong = Date.now();
});

setInterval(() => {
  if (Date.now() - lastPong > 60000) {
    ws.close();  // 60s sem pong = morto
    reconnect();
  }
}, 5000);

Sem ping/pong, em redes instaveis (mobile, wifi), a conexao "morre" sem voce saber. Cobre em reconexao-e-estado.

perMessageDeflate - compressao por mensagem. WebSocket suporta compressao de cada mensagem (RFC 7692). Reduz bandwidth ate 80% (mensagens JSON sao muito compressiveis). Ativar:

// Server
const wss = new WebSocket.Server({
  port: 8080,
  perMessageDeflate: true,
});

Client (browser): automaticamente negociado se o server suportar. Nada a fazer no client.

Cuidado: compressao adiciona CPU. Em apps de alta performance (games), meca se a compressao ajuda ou atrapalha. Em apps com texto (chat), sempre ajuda.

WebSocket + CORS. Diferente de fetch, WebSocket nao usa CORS (e' um protocolo separado). A regra de conexao e' simplesmente:

  • Origem mesma do server: conecta sempre.
  • Origem diferente: conecta desde que o server aceite (via header Access-Control-Allow-Origin no handshake, mas nao e' CORS de verdade - e' decisao do server).

Autenticacao em WebSocket e' o seu problema (cookie funciona automaticamente, token requer query string ou primeira mensagem).

ArrayBuffer vs Blob para binario.

TipoQuando usar
ArrayBufferProcessar bytes (parse, crypto, etc)
BlobArmazenar/enviar (file upload, video)

Exemplo: file upload progressivo:

const file = input.files[0];
const chunkSize = 64 * 1024;  // 64KB

for (let offset = 0; offset < file.size; offset += chunkSize) {
  const chunk = file.slice(offset, offset + chunkSize);
  ws.send(chunk);  // Blob
}

// Server recebe Blob, pode armazenar chunk por chunk

SharedArrayBuffer + WebSocket - o alto desempenho. Pra apps que transferem muito dado binario (video chunks, game state, simulacoes), use SharedArrayBuffer em vez de ArrayBuffer. Worker thread acessa o buffer, main thread le. Sem copia - zero-copy transfer.

// Main thread
const buffer = new SharedArrayBuffer(1024 * 1024);
const view = new Float32Array(buffer);

// Worker preenche o buffer
worker.postMessage(buffer, [buffer]);  // transfer

// WebSocket envia o buffer
ws.send(buffer);

Limitacao: SharedArrayBuffer exige COOP/COEP headers (cross-origin isolation) - detalhe em performance-web e csp-e-headers.

WebSocket + Service Worker (PWA). Em PWA, o Service Worker pode fazer proxy de WebSocket: a pagina cria WebSocket, passa pro SW, SW faz relay. Util pra economia de bateria (mobile)

  • SW dorme, acorda so quando ha data. Experimental - API em rollout.

Pra quem quer ir mais alem 🔴

WebSocket vs WebTransport - o sucessor. WebTransport (Chrome 97+, Firefox 114+, Safari 17+) e' o sucessor moderno do WebSocket: HTTP/3 + UDP + bidirecional. Vantagens:

  • Latencia menor (UDP sem handshake).
  • Multiplexing (multiplos streams em 1 conexao).
  • Datagrams (mensagens sem ordering, como UDP).

Limitacao em 2026: requer HTTP/3 (ainda em rollout em CDNs). Para apps de gaming ou colaboracao de alta performance, WebTransport vale. Pra chat/notificacoes, WebSocket ainda e' o default estavel.

EventSource reconexao automatica vs WebSocket manual. Por que SSE tem reconexao automatica mas WebSocket nao? SSE e' HTTP - o browser ja gerencia conexoes HTTP, sabe quando cai. WebSocket e' protocolo proprio - o browser nao sabe "quando reconectar" (depende do app). A decisao de quando reconectar e' do app - o que justifica o pattern de exponential backoff + heartbeat.

Por que WebSocket nao tem multiplexing nativo. WebSocket e' 1 stream por conexao. Pra mandar mensagens em paralelo (chat + presence + typing indicators), voce precisa de sub-protocolo ou Socket.IO (que adiciona multiplexing). WebTransport ja tem multiplexing nativo - 1 conexao, N streams. Em 2026+, WebTransport substituira WebSocket em apps de alta demanda.

eventSource vs WebSocket proxy. Em PWA, da' pra fazer o Service Worker proxy de WebSocket: a pagina abre WebSocket, SW intercepta, faz relay. Util pra background sync - SW pode manter WS vivo mesmo com a pagina fechada. Ainda em proposal em 2026.

Leitura recomendada:

Dica: o erro mais comum em WebSocket e' assumir que a conexao fica viva para sempre. Em redes reais (mobile, wifi instavel), a conexao morre - sem heartbeat, voce nao detecta. Sempre implemente: (1) ping/pong a cada 30s, (2) deteccao de morte (60s sem pong), (3) reconexao com exponential backoff. Sem isso, app quebra em prod mesmo funcionando em dev.

No proximo no, vamos Server-Sent Events (SSE): a alternativa one-way ao WebSocket. Mais simples, reconexao automatica nativa, HTTP puro. Ideal pra notificacoes, dashboards live, e log streams.

// Quiz

Por que WebSocket precisa de heartbeat (ping/pong) para detectar conexao morta?

Escolha uma alternativa

// recursos

// avaliação da trilha

—
ainda sem avaliações