Os aplicativos da Web padrão geralmente são restritos a protocolos de comunicação específicos, como HTTP, e APIs como WebSocket e WebRTC. Embora sejam poderosos, eles foram projetados para serem altamente restritos e evitar abusos. Eles não podem estabelecer conexões TCP ou UDP brutas, o que limita a capacidade dos apps da Web de se comunicar com sistemas legados ou dispositivos de hardware que usam protocolos próprios não relacionados à Web. Por exemplo, você pode criar um cliente SSH baseado na Web, conectar-se a uma impressora local ou gerenciar uma frota de dispositivos IoT. Antes, isso exigia plug-ins de navegador ou aplicativos assistentes nativos.
A API Direct Sockets resolve essa limitação permitindo que Apps Isolados da Web (AIWs) estabeleçam conexões diretas TCP e UDP sem um servidor de retransmissão. Com as IWAs, graças a outras medidas de segurança, como a Política de Segurança de Conteúdo (CSP) estrita e o isolamento entre origens, essa API pode ser exposta com segurança.
Casos de uso
Quando usar Direct Sockets em vez de WebSockets padrão?
- IoT e dispositivos inteligentes:comunicação com hardware que usa TCP/UDP bruto em vez de HTTP.
- Sistemas legados:conexão com servidores de e-mail mais antigos (SMTP/IMAP), servidores de chat IRC ou impressoras.
- Área de trabalho remota e terminais:implementação de clientes SSH, Telnet ou RDP.
- Sistemas P2P:implementação de tabelas hash distribuídas (DHT) ou ferramentas de colaboração resilientes (como IPFS).
- Transmissão de mídia:uso do UDP para transmitir conteúdo para vários endpoints de uma só vez (multicast), permitindo casos de uso como reprodução de vídeo coordenada em uma rede de quiosques de varejo.
- Capacidades de servidor e listener:configure o IWA para atuar como um
endpoint de recebimento de conexões TCP ou datagramas UDP usando
TCPServerSocketouUDPSocketvinculados.
Pré-requisitos para a API Direct Sockets
Antes de usar os sockets diretos, configure uma IWA funcional. Em seguida, você pode integrar sockets diretos às suas páginas.
Adicionar política de permissões
Para usar a API Direct Sockets, configure o objeto permissions_policy no manifesto do IWA. É necessário adicionar a chave direct-sockets para ativar explicitamente
a API. Além disso, é necessário incluir a chave cross-origin-isolated. Essa chave não é específica para soquetes diretos, mas é necessária para todas as IWAs e determina se o documento pode acessar APIs que exigem isolamento entre origens.
{
"permissions_policy": {
"direct-sockets": ["self"],
"cross-origin-isolated": ["self"]
}
}
A chave "direct-sockets" determina se as chamadas para new TCPSocket(...), new
TCPServerSocket(...) ou new UDPSocket(...) são permitidas. Se essa política não for definida, esses construtores vão rejeitar imediatamente com um NotAllowedError.
Implementar TCPSocket
Os aplicativos podem solicitar uma conexão TCP criando uma instância TCPSocket.
Abrir uma conexão
Para abrir uma conexão, use o operador new e await a promessa aberta.
O construtor TCPSocket inicia a conexão usando os remoteAddress e remotePort especificados.
const remoteAddress = 'example.com';
const remotePort = 7;
// Configure options like keepAlive or buffering
const options = {
keepAlive: true,
keepAliveDelay: 720000
};
let tcpSocket = new TCPSocket(remoteAddress, remotePort, options);
// Wait for the connection to be established
let { readable, writable } = await tcpSocket.opened;
O objeto de configuração opcional permite um controle refinado da rede. Neste caso específico, keepAliveDelay é definido como 720.000 milissegundos para manter a conexão durante períodos de inatividade. Os desenvolvedores também podem configurar outras propriedades aqui, como noDelay, que desativa o algoritmo de Nagle para impedir que o sistema agrupe pequenos pacotes, reduzindo potencialmente a latência, ou sendBufferSize e receiveBufferSize para gerenciar a taxa de transferência.
Na última parte do snippet anterior, o código aguarda a promessa aberta,
que só é resolvida quando o handshake é concluído, retornando um
objeto TCPSocketOpenInfo que contém os fluxos legíveis e graváveis necessários
para a transmissão de dados.
Leitura e gravação
Depois que o soquete for aberto, interaja com ele usando as interfaces padrão da API Streams.
- Gravação:o fluxo gravável aceita um
BufferSource(como umArrayBuffer). - Leitura:o fluxo legível gera dados
Uint8Array.
// Writing data
const writer = writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode("Hello Server"));
// Call when done
writer.releaseLock();
// Reading data
const reader = readable.getReader();
const { value, done } = await reader.read();
if (!done) {
const decoder = new TextDecoder();
console.log("Received:", decoder.decode(value));
}
// Call when done
reader.releaseLock();
Leitura otimizada com BYOB
Para aplicativos de alto desempenho em que o gerenciamento da alocação de memória é fundamental, a API oferece suporte à leitura "Traga seu próprio buffer" (BYOB, na sigla em inglês). Em vez de deixar o navegador alocar um novo buffer para cada parte de dados recebida, você pode transmitir um buffer pré-alocado ao leitor. Isso reduz a sobrecarga da coleta de lixo ao gravar dados diretamente na memória atual.
// 1. Get a BYOB reader explicitly
const reader = readable.getReader({ mode: 'byob' });
// 2. Allocate a reusable buffer (e.g., 4KB)
let buffer = new Uint8Array(4096);
// 3. Read directly into the existing buffer
const { value, done } = await reader.read(buffer);
if (!done) {
// 'value' is a view of the data written directly into your buffer
console.log("Bytes received:", value.byteLength);
}
reader.releaseLock();
Implementar UDPSocket
A classe UDPSocket permite a comunicação UDP. Ele opera em dois modos distintos, dependendo de como você configura as opções.
Modo conectado
Nesse modo, o soquete se comunica com um único destino específico. Isso é útil para tarefas padrão de cliente-servidor.
// Connect to a specific remote host
let udpSocket = new UDPSocket({
remoteAddress: 'example.com',
remotePort: 7 });
let { readable, writable } = await udpSocket.opened;
Modo vinculado
Nesse modo, o soquete é vinculado a um endpoint IP local. Ele pode receber datagramas de fontes arbitrárias e enviá-los para destinos arbitrários. Isso é usado com frequência para protocolos de descoberta local ou comportamento semelhante a um servidor.
// Bind to all interfaces (IPv6)
let udpSocket = new UDPSocket({
localAddress: '::'
// omitting localPort lets the OS pick one
});
// localPort will tell you the OS-selected port.
let { readable, writable, localPort } = await udpSocket.opened;
Processar mensagens UDP
Ao contrário do fluxo de bytes TCP, os fluxos UDP trabalham com objetos UDPMessage, que
contêm os dados e as informações de endereço remoto. O código a seguir demonstra
como processar operações de entrada/saída ao usar um UDPSocket no "modo vinculado".
// Writing (Bound Mode requires specifying destination)
const writer = writable.getWriter();
await writer.write({
data: new TextEncoder().encode("Ping"),
remoteAddress: '192.168.1.50',
remotePort: 8080
});
// Reading
const reader = readable.getReader();
const { value } = await reader.read();
// value contains: { data, remoteAddress, remotePort }
console.log(`Received from ${value.remoteAddress}:`, value.data);
Ao contrário do "modo conectado", em que o soquete é bloqueado para um peer específico, o modo
vinculado permite que o soquete se comunique com destinos arbitrários. Consequentemente, ao gravar dados no fluxo gravável, é necessário transmitir um objeto UDPMessage que especifica explicitamente o remoteAddress e o remotePort de cada pacote, instruindo o soquete exatamente para onde rotear esse datagrama específico. Da mesma forma, ao ler do fluxo legível, o valor retornado inclui não apenas o payload de dados, mas também o remoteAddress e o remotePort do remetente, permitindo que seu aplicativo identifique a origem de cada pacote recebido.
Observação:ao usar UDPSocket no "modo conectado", o soquete fica efetivamente
bloqueado para um peer específico, simplificando o processo de E/S. Nesse modo, as propriedades
remoteAddress e remotePort são efetivamente nulas ao gravar,
já que o destino já está fixo. Da mesma forma, ao ler mensagens, essas propriedades vão retornar nulo, já que a origem é garantida como o peer conectado.
Suporte a multicast
Para casos de uso como sincronizar a reprodução de vídeo em vários quiosques ou implementar a descoberta de dispositivos locais (por exemplo, mDNS), o Direct Sockets é compatível com UDP multicast. Isso permite que as mensagens sejam enviadas para um endereço de "grupo" e recebidas por todos os assinantes da rede, em vez de um único peer específico.
Permissões de multicast
Para usar recursos de multicast, adicione a permissão direct-sockets-multicast específica ao manifesto da IWA. Isso é diferente da permissão padrão de soquetes diretos e é necessário porque o multicast é usado apenas em redes particulares.
{
"permissions_policy": {
"direct-sockets": ["self"],
"direct-sockets-multicast": ["self"],
"direct-sockets-private": ["self"],
"cross-origin-isolated": ["self"]
}
}
Enviar datagramas multicast
O envio para um grupo de multicast é muito semelhante ao "modo conectado" UDP padrão, com a adição de opções específicas para controlar o comportamento do pacote.
const MULTICAST_GROUP = '239.0.0.1';
const PORT = 12345;
const socket = new UDPSocket({
remoteAddress: MULTICAST_GROUP,
remotePort: PORT,
// Time To Live: How many router hops the packet can survive (default: 1)
multicastTimeToLive: 5,
// Loopback: Whether to receive your own packets (default: true)
multicastLoopback: true
});
const { writable } = await socket.opened;
// Write to the stream as usual...
Receber datagramas multicast
Para receber tráfego multicast, abra um UDPSocket no "modo vinculado"
(normalmente vinculando a 0.0.0.0 ou ::) e participe de um grupo específico usando
o MulticastController. Você também pode usar a opção multicastAllowAddressSharing (semelhante a SO_REUSEADDR no Unix), que é essencial para protocolos de descoberta de dispositivos em que vários aplicativos no mesmo dispositivo precisam ouvir a mesma porta.
const socket = new UDPSocket({
localAddress: '0.0.0.0', // Listen on all interfaces
localPort: 12345,
multicastAllowAddressSharing: true // Allow multiple applications to bind to the same address / port pair.
});
// The open info contains the MulticastController
const { readable, multicastController } = await socket.opened;
// Join the group to start receiving packets
await multicastController.joinGroup('239.0.0.1');
const reader = readable.getReader();
// Read the stream...
const { value } = await reader.read();
console.log(`Received multicast from ${value.remoteAddress}`);
// When finished, you can leave the group (this is an optional, but recommended practice)
await multicastController.leaveGroup('239.0.0.1');
Criar um servidor
A API também oferece suporte a TCPServerSocket para aceitar conexões TCP de entrada, permitindo que sua IWA funcione como um servidor local. O código a seguir
ilustra como estabelecer um servidor TCP usando a interface TCPServerSocket.
// Listen on all interfaces (IPv6)
let tcpServerSocket = new TCPServerSocket('::');
// Accept connections via the readable stream
let { readable } = await tcpServerSocket.opened;
let reader = readable.getReader();
// Wait for a client to connect
let { value: clientSocket } = await reader.read();
// 'clientSocket' is a standard TCPSocket you can now read/write to
Ao instanciar a classe com o endereço '::', o servidor se vincula a todas as
interfaces de rede IPv6 disponíveis para detectar tentativas de entrada. Ao contrário das APIs de servidor tradicionais baseadas em callback, essa API usa o padrão da API Streams da Web: as conexões recebidas são entregues como um ReadableStream. Quando você chama
reader.read(), o aplicativo aguarda e aceita a próxima conexão da
fila, resolvendo para um valor que é uma instância TCPSocket totalmente funcional
pronta para comunicação bidirecional com esse cliente específico.
Depurar sockets diretos com o Chrome DevTools
A partir do Chrome 138, é possível depurar o tráfego de sockets diretos diretamente no painel Rede do Chrome DevTools, eliminando a necessidade de sniffers de pacotes externos. Com essa ferramenta, é possível monitorar conexões TCPSocket e tráfego UDPSocket (nos modos vinculado e conectado) junto com suas solicitações HTTP padrão.
Para inspecionar a atividade de rede do app:
- Abra o painel Rede no Chrome DevTools.
- Localize e selecione a conexão de soquete na tabela de solicitações.
- Abra a guia Mensagens para conferir um registro de todos os dados transmitidos e recebidos.

Essa visualização fornece um visualizador hexadecimal, permitindo inspecionar a carga binária bruta das mensagens TCP e UDP, garantindo que a implementação do protocolo seja perfeita em bytes.
Demonstração
O IWA Kitchen Sink apresenta um app com várias guias, cada uma demonstrando uma API IWA diferente, como soquetes diretos, frame controlado e muito mais.
Como alternativa, a demonstração do cliente telnet (link em inglês) contém um app da Web isolado que permite ao usuário se conectar a um servidor TCP/IP por um terminal interativo. Em outras palavras, um cliente Telnet.
Conclusão
A API Direct Sockets preenche uma lacuna de funcionalidade crítica, permitindo que aplicativos da Web processem protocolos de rede brutos que antes eram impossíveis de oferecer suporte sem wrappers nativos. Ele vai além da simples conectividade do cliente. Com o
TCPServerSocket, os aplicativos podem detectar conexões recebidas, enquanto o
UDPSocket oferece modos flexíveis para comunicação ponto a ponto e descoberta de rede
local.
Ao expor esses recursos TCP e UDP brutos pela API Streams moderna, agora é possível criar implementações completas de protocolos legados, como SSH, RDP ou padrões personalizados de IoT, diretamente em JavaScript. Como essa API concede acesso à rede de baixo nível, ela tem implicações de segurança significativas. Por isso, ela é restrita a Apps Isolados da Web (AIWs), garantindo que esse poder seja concedido apenas a aplicativos confiáveis e instalados explicitamente que aplicam políticas de segurança rigorosas. Esse equilíbrio permite criar aplicativos potentes e centrados em dispositivos, mantendo a segurança que os usuários esperam da plataforma da Web.