Por lo general, las aplicaciones web estándar se restringen a protocolos de comunicación específicos, como HTTP, y a APIs, como WebSocket y WebRTC. Si bien son potentes, están diseñados para estar muy restringidos y evitar abusos. No pueden establecer conexiones TCP o UDP sin procesar, lo que limita la capacidad de las apps web para comunicarse con sistemas heredados o dispositivos de hardware que usan sus propios protocolos no web. Por ejemplo, es posible que desees compilar un cliente SSH basado en la Web, conectarte a una impresora local o administrar una flota de dispositivos IoT. Históricamente, esto requería complementos del navegador o aplicaciones auxiliares nativas.
La API de Direct Sockets aborda esta limitación, ya que permite que las apps web aisladas (IWA) establezcan conexiones TCP y UDP directas sin un servidor de retransmisión. Con las IWA, gracias a las medidas de seguridad adicionales, como la estricta Política de Seguridad del Contenido (CSP) y el aislamiento de origen cruzado, esta API se puede exponer de forma segura.
Casos de uso
¿Cuándo deberías usar Direct Sockets en lugar de WebSockets estándar?
- IoT y dispositivos inteligentes: Comunicación con hardware que usa TCP/UDP sin procesar en lugar de HTTP
- Sistemas heredados: Conexión a servidores de correo electrónico más antiguos (SMTP/IMAP), servidores de chat IRC o impresoras
- Terminales y escritorio remoto: Implementa clientes de SSH, Telnet o RDP.
- Sistemas P2P: Implementar tablas hash distribuidas (DHT) o herramientas de colaboración resilientes (como IPFS)
- Transmisión de contenido multimedia: Aprovecha UDP para transmitir contenido a varios extremos a la vez (multidifusión), lo que permite casos de uso como la reproducción de video coordinada en una red de quioscos minoristas.
- Capacidades del servidor y del receptor: Configurar la IWA para que actúe como un extremo receptor para las conexiones TCP entrantes o los datagramas UDP con
TCPServerSocketoUDPSocketvinculados
Requisitos previos para Direct Sockets
Antes de usar Direct Sockets, deberás configurar una IWA funcional. Luego, puedes integrar Sockets directos en tus páginas.
Agregar política de permisos
Para usar Direct Sockets, debes configurar el objeto permissions_policy en el manifiesto de tu IWA. Debes agregar la clave direct-sockets para habilitar la API de forma explícita. Además, debes incluir la clave cross-origin-isolated. Esta clave no es específica de los sockets directos, pero se requiere para todas las IWA y determina si el documento puede acceder a las APIs que requieren aislamiento de origen cruzado.
{
"permissions_policy": {
"direct-sockets": ["self"],
"cross-origin-isolated": ["self"]
}
}
La clave direct-sockets determina si se permiten las llamadas a new TCPSocket(...), new
TCPServerSocket(...) o new UDPSocket(...). Si no se establece esta política, estos constructores rechazarán de inmediato con un NotAllowedError.
Implementa TCPSocket
Las aplicaciones pueden solicitar una conexión TCP creando una instancia de TCPSocket.
Cómo abrir una conexión
Para abrir una conexión, usa el operador new y await la promesa abierta.
El constructor TCPSocket inicia la conexión con los parámetros remoteAddress y 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;
El objeto de configuración opcional permite un control detallado de la red. En este caso específico, keepAliveDelay se establece en 720,000 milisegundos para mantener la conexión durante los períodos de inactividad. Los desarrolladores también pueden configurar otras propiedades aquí, como noDelay, que inhabilita el algoritmo de Nagle para evitar que el sistema agrupe paquetes pequeños, lo que podría reducir la latencia, o sendBufferSize y receiveBufferSize para administrar el rendimiento.
En la última parte del fragmento anterior, el código espera la promesa abierta, que solo se resuelve una vez que se completa el handshake y devuelve un objeto TCPSocketOpenInfo que contiene los flujos legibles y grabables necesarios para la transmisión de datos.
Escritura y lectura
Una vez que el socket esté abierto, interactúa con él usando las interfaces estándar de la API de Streams.
- Escritura: El flujo de escritura acepta un
BufferSource(como unArrayBuffer). - Lectura: El flujo legible genera datos de
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();
Lectura optimizada con BYOB
Para las aplicaciones de alto rendimiento en las que es fundamental administrar la asignación de memoria, la API admite la lectura de "Bring Your Own Buffer" (BYOB). En lugar de permitir que el navegador asigne un nuevo búfer para cada fragmento de datos recibido, puedes pasar un búfer preasignado al lector. Esto reduce la sobrecarga de la recolección de basura, ya que escribe los datos directamente en la memoria existente.
// 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();
Implementa UDPSocket
La clase UDPSocket permite la comunicación a través de UDP. Funciona en dos modos distintos según cómo configures las opciones.
Modo conectado
En este modo, el socket se comunica con un solo destino específico. Esto es útil para las tareas estándar 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 de vinculación
En este modo, el socket está vinculado a un extremo IP local. Puede recibir datagramas de fuentes arbitrarias y enviarlos a destinos arbitrarios. Esto se suele usar para protocolos de descubrimiento locales o comportamientos similares a los de un 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;
Cómo controlar los mensajes UDP
A diferencia del flujo de bytes TCP, los flujos UDP se ocupan de objetos UDPMessage, que contienen los datos y la información de la dirección remota. El siguiente código muestra cómo controlar las operaciones de entrada y salida cuando se usa un UDPSocket en "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);
A diferencia del "modo conectado", en el que el socket está bloqueado para un par específico, el modo vinculado permite que el socket se comunique con destinos arbitrarios. Por lo tanto, cuando escribas datos en el flujo de escritura, debes pasar un objeto UDPMessage que especifique de forma explícita el remoteAddress y el remotePort para cada paquete, lo que le indicará al socket exactamente dónde enrutar ese datagrama específico. Del mismo modo, cuando se lee desde el flujo legible, el valor devuelto incluye no solo la carga útil de datos, sino también el remoteAddress y el remotePort del remitente, lo que permite que tu aplicación identifique el origen de cada paquete entrante.
Nota: Cuando se usa UDPSocket en el "modo conectado", el socket se bloquea de manera efectiva para un par específico, lo que simplifica el proceso de E/S. En este modo, las propiedades remoteAddress y remotePort no tienen efecto cuando se escriben, ya que el destino ya está fijo. Del mismo modo, cuando se leen mensajes, estas propiedades devolverán nulo, ya que se garantiza que la fuente es el par conectado.
Compatibilidad con Multicast
Para casos de uso como la sincronización de la reproducción de video en varios quioscos o la implementación de la detección de dispositivos locales (por ejemplo, mDNS), Direct Sockets admite UDP de multidifusión. Esto permite que los mensajes se envíen a una dirección de "grupo" y que los reciban todos los suscriptores de la red, en lugar de un solo par específico.
Permisos de transmisión por IP
Para usar las capacidades de transmisión multidifusión, debes agregar el permiso direct-sockets-multicast específico al manifiesto de tu AWP. Este permiso es distinto del permiso estándar de sockets directos y es necesario porque la transmisión multidifusión solo se usa en redes privadas.
{
"permissions_policy": {
"direct-sockets": ["self"],
"direct-sockets-multicast": ["self"],
"direct-sockets-private": ["self"],
"cross-origin-isolated": ["self"]
}
}
Envía datagramas de transmisión por multidifusión
El envío a un grupo de multicast es muy similar al "modo conectado" de UDP estándar, con la adición de opciones específicas para controlar el comportamiento de los paquetes.
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...
Recibir datagramas de multidifusión
Para recibir tráfico de multidifusión, debes abrir un UDPSocket en "modo vinculado" (por lo general, vinculándolo a 0.0.0.0 o ::) y, luego, unirte a un grupo específico con el MulticastController. También puedes usar la opción multicastAllowAddressSharing (similar a SO_REUSEADDR en Unix), que es esencial para los protocolos de detección de dispositivos en los que varias aplicaciones del mismo dispositivo deben escuchar el mismo puerto.
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');
Crea un servidor
La API también admite TCPServerSocket para aceptar conexiones TCP entrantes, lo que permite que tu IWA actúe como un servidor local. En el siguiente código, se ilustra cómo establecer un servidor TCP con la interfaz 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
Cuando se crea una instancia de la clase con la dirección '::', el servidor se vincula a todas las interfaces de red IPv6 disponibles para escuchar los intentos entrantes. A diferencia de las APIs de servidor tradicionales basadas en devoluciones de llamadas, esta API utiliza el patrón de la API de Streams de la Web: las conexiones entrantes se entregan como un ReadableStream. Cuando llamas a reader.read(), la aplicación espera y acepta la siguiente conexión de la fila, y se resuelve en un valor que es una instancia TCPSocket completamente funcional y lista para la comunicación bidireccional con ese cliente específico.
Cómo depurar sockets directos con las Herramientas para desarrolladores de Chrome
A partir de Chrome 138, puedes depurar el tráfico de Direct Sockets directamente en el panel Network de las Herramientas para desarrolladores de Chrome, lo que elimina la necesidad de usar analizadores de paquetes externos. Estas herramientas te permiten supervisar las conexiones TCPSocket y el tráfico UDPSocket (en los modos vinculado y conectado) junto con tus solicitudes HTTP estándar.
Para inspeccionar la actividad de red de tu app, haz lo siguiente:
- Abre el panel Red en las Herramientas para desarrolladores de Chrome.
- Ubica y selecciona la conexión de socket en la tabla de solicitudes.
- Abre la pestaña Mensajes para ver un registro de todos los datos transmitidos y recibidos.

Esta vista proporciona un visualizador hexadecimal que te permite inspeccionar la carga útil binaria sin procesar de tus mensajes TCP y UDP, lo que garantiza que la implementación de tu protocolo sea perfecta en términos de bytes.
Demostración
IWA Kitchen Sink incluye una app con varias pestañas, cada una de las cuales demuestra una API de IWA diferente, como Sockets directos, Controlled Frame y muchas más.
Como alternativa, la demostración del cliente de Telnet contiene una app web aislada que permite al usuario conectarse a un servidor TCP/IP a través de una terminal interactiva. En otras palabras, un cliente de Telnet.
Conclusión
La API de Direct Sockets cierra una brecha crítica de funcionalidad, ya que permite que las aplicaciones web controlen protocolos de red sin procesar que antes eran imposibles de admitir sin wrappers nativos. Va más allá de la simple conectividad del cliente. Con TCPServerSocket, las aplicaciones pueden escuchar las conexiones entrantes, mientras que UDPSocket ofrece modos flexibles para la comunicación punto a punto y el descubrimiento de redes locales.
Al exponer estas capacidades sin procesar de TCP y UDP a través de la moderna API de Streams, ahora puedes compilar implementaciones completas de protocolos heredados, como SSH, RDP o estándares personalizados de IoT, directamente en JavaScript. Debido a que esta API otorga acceso de red de bajo nivel, tiene implicaciones de seguridad significativas. Por lo tanto, se restringe a las apps web aisladas (IWA), lo que garantiza que este poder solo se otorgue a las aplicaciones de confianza instaladas explícitamente que aplican políticas de seguridad estrictas. Este equilibrio te permite crear aplicaciones potentes y centradas en el dispositivo, al mismo tiempo que mantienes la seguridad que los usuarios esperan de la plataforma web.