Direct Sockets

Demián Renzulli
Demián Renzulli
Andrew Rayskiy
Andrew Rayskiy
Vlad Krot
Vlad Krot

Aplikasi web standar biasanya dibatasi untuk protokol komunikasi tertentu seperti HTTP dan API seperti WebSocket dan WebRTC. Meskipun sangat efektif, API ini dirancang untuk dibatasi secara ketat guna mencegah penyalahgunaan. Aplikasi tersebut tidak dapat membuat koneksi TCP atau UDP mentah, yang membatasi kemampuan aplikasi web untuk berkomunikasi dengan sistem lama atau perangkat hardware yang menggunakan protokol non-web mereka sendiri. Misalnya, Anda mungkin ingin membuat klien SSH berbasis web, terhubung ke printer lokal, atau mengelola sekumpulan perangkat IoT. Sebelumnya, hal ini memerlukan plugin browser atau aplikasi helper native.

Direct Sockets API mengatasi batasan ini dengan memungkinkan Aplikasi Web Terisolasi (IWA) membuat koneksi TCP dan UDP langsung tanpa server relay. Dengan IWA, berkat tindakan keamanan tambahan—seperti Kebijakan Keamanan Konten (CSP) yang ketat dan isolasi lintas origin—API ini dapat diekspos dengan aman.

Kasus penggunaan

Kapan sebaiknya Anda menggunakan Direct Sockets dibandingkan WebSocket standar?

  • IoT dan perangkat smart: Berkomunikasi dengan hardware yang menggunakan TCP/UDP mentah, bukan HTTP.
  • Sistem lama: Terhubung ke server email lama (SMTP/IMAP), server chat IRC, atau printer.
  • Desktop dan terminal jarak jauh: Menerapkan klien SSH, Telnet, atau RDP.
  • Sistem P2P: Menerapkan Distributed Hash Tables (DHT) atau alat kolaborasi yang tangguh (seperti IPFS).
  • Penyiaran media: Memanfaatkan UDP untuk melakukan streaming konten ke beberapa endpoint sekaligus (multicast), sehingga memungkinkan kasus penggunaan seperti pemutaran video yang terkoordinasi di seluruh jaringan kios retail.
  • Kemampuan server dan listener: Mengonfigurasi IWA agar bertindak sebagai endpoint penerima untuk koneksi TCP atau datagram UDP yang masuk menggunakan TCPServerSocket atau UDPSocket terikat.

Prasyarat untuk Direct Sockets

Sebelum menggunakan Soket Langsung, Anda harus menyiapkan IWA yang berfungsi. Kemudian, Anda dapat mengintegrasikan Soket Langsung ke halaman Anda.

Menambahkan kebijakan izin

Untuk menggunakan Direct Sockets, Anda harus mengonfigurasi objek permissions_policy di manifes IWA Anda. Anda perlu menambahkan kunci direct-sockets untuk mengaktifkan API secara eksplisit. Selain itu, Anda harus menyertakan kunci cross-origin-isolated. Kunci ini tidak khusus untuk Soket Langsung, tetapi diperlukan untuk semua IWA dan menentukan apakah dokumen dapat mengakses API yang memerlukan isolasi lintas origin.

{
  "permissions_policy": {
    "direct-sockets": ["self"],
    "cross-origin-isolated": ["self"]
  }
}

Kunci direct-sockets menentukan apakah panggilan ke new TCPSocket(...), new TCPServerSocket(...), atau new UDPSocket(...) diizinkan. Jika kebijakan ini tidak disetel, konstruktor ini akan langsung menolak dengan NotAllowedError.

Menerapkan TCPSocket

Aplikasi dapat meminta koneksi TCP dengan membuat instance TCPSocket.

Membuka koneksi

Untuk membuka koneksi, gunakan operator new dan await promise yang dibuka.

Konstruktor TCPSocket memulai koneksi menggunakan remoteAddress dan remotePort yang ditentukan.

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;

Objek konfigurasi opsional memungkinkan kontrol jaringan yang terperinci; dalam kasus khusus ini, keepAliveDelay disetel ke 720000 milidetik untuk mempertahankan koneksi selama periode tidak aktif. Developer juga dapat mengonfigurasi properti lainnya di sini, seperti noDelay, yang menonaktifkan algoritma Nagle untuk menghentikan sistem dari pengelompokan paket kecil—yang berpotensi mengurangi latensi—atau sendBufferSize dan receiveBufferSize untuk mengelola throughput.

Di bagian terakhir cuplikan sebelumnya, kode menunggu promise yang dibuka, yang hanya diselesaikan setelah handshake selesai, dengan menampilkan objek TCPSocketOpenInfo yang berisi aliran yang dapat dibaca dan ditulis yang diperlukan untuk transmisi data.

Baca dan tulis

Setelah soket terbuka, berinteraksi dengannya menggunakan antarmuka Streams API standar.

  • Penulisan: Stream yang dapat ditulis menerima BufferSource (seperti ArrayBuffer).
  • Membaca: Aliran yang dapat dibaca menghasilkan data 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();

Membaca yang dioptimalkan dengan BYOB

Untuk aplikasi berperforma tinggi yang pengelolaan alokasi memorinya sangat penting, API ini mendukung pembacaan "Bring Your Own Buffer" (BYOB). Daripada membiarkan browser mengalokasikan buffer baru untuk setiap bagian data yang diterima, Anda dapat meneruskan buffer yang telah dialokasikan sebelumnya ke pembaca. Hal ini mengurangi overhead pengumpulan sampah dengan menulis data langsung ke memori yang ada.

// 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();

Menerapkan UDPSocket

Class UDPSocket memungkinkan komunikasi UDP. Fungsi ini beroperasi dalam dua mode berbeda, bergantung pada cara Anda mengonfigurasi opsi.

Mode terhubung

Dalam mode ini, soket berkomunikasi dengan satu tujuan tertentu. Hal ini berguna untuk tugas klien-server standar.

// Connect to a specific remote host
let udpSocket = new UDPSocket({
    remoteAddress: 'example.com',
    remotePort: 7 });

let { readable, writable } = await udpSocket.opened;

Mode terikat

Dalam mode ini, soket terikat ke endpoint IP lokal. Aplikasi ini dapat menerima datagram dari sumber arbitrer dan mengirimkannya ke tujuan arbitrer. Hal ini sering digunakan untuk protokol penemuan lokal atau perilaku seperti server.

// 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;

Menangani pesan UDP

Tidak seperti aliran byte TCP, aliran UDP menangani objek UDPMessage, yang berisi data dan info alamat jarak jauh. Kode berikut menunjukkan cara menangani operasi Input/Output saat menggunakan UDPSocket dalam "mode terikat".

// 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);

Tidak seperti "mode terhubung", saat soket dikunci ke peer tertentu, mode terikat memungkinkan soket berkomunikasi dengan tujuan arbitrer. Oleh karena itu, saat menulis data ke stream yang dapat ditulis, Anda harus meneruskan objek UDPMessage yang secara eksplisit menentukan remoteAddress dan remotePort untuk setiap paket, yang menginstruksikan soket secara tepat ke mana harus merutekan datagram tertentu tersebut. Demikian pula, saat membaca dari stream yang dapat dibaca, nilai yang ditampilkan tidak hanya mencakup payload data, tetapi juga remoteAddress dan remotePort pengirim, sehingga aplikasi Anda dapat mengidentifikasi asal setiap paket masuk.

Catatan: Saat menggunakan UDPSocket dalam "mode terhubung", soket akan dikunci secara efektif ke peer tertentu, sehingga menyederhanakan proses I/O. Dalam mode ini, properti remoteAddress dan remotePort pada dasarnya tidak beroperasi saat menulis, karena tujuannya sudah tetap. Demikian pula, saat membaca pesan, properti ini akan menampilkan null, karena sumber dijamin merupakan peer yang terhubung.

Dukungan multicast

Untuk kasus penggunaan seperti menyinkronkan pemutaran video di beberapa kios atau menerapkan penemuan perangkat lokal (misalnya, mDNS), Direct Sockets mendukung Multicast UDP. Hal ini memungkinkan pesan dikirim ke alamat "grup" dan diterima oleh semua pelanggan di jaringan, bukan hanya satu peer tertentu.

Izin multicast

Untuk menggunakan kemampuan multicast, Anda harus menambahkan izin direct-sockets-multicast tertentu ke manifes IWA Anda. Izin ini berbeda dengan izin soket langsung standar dan diperlukan karena multicast hanya digunakan di jaringan pribadi.

{
  "permissions_policy": {
    "direct-sockets": ["self"],
    "direct-sockets-multicast": ["self"],
    "direct-sockets-private": ["self"],
    "cross-origin-isolated": ["self"]
  }
}

Mengirim datagram multicast

Pengiriman ke grup multicast sangat mirip dengan "mode terhubung" UDP standar, dengan penambahan opsi tertentu untuk mengontrol perilaku paket.

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...

Menerima datagram multicast

Untuk menerima traffic multicast, Anda harus membuka UDPSocket dalam "mode terikat" (biasanya terikat ke 0.0.0.0 atau ::), lalu bergabung ke grup tertentu menggunakan MulticastController. Anda juga dapat menggunakan opsi multicastAllowAddressSharing (mirip dengan SO_REUSEADDR di Unix), yang penting untuk protokol penemuan perangkat saat beberapa aplikasi di perangkat yang sama perlu memproses port yang sama.

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');

Buat server

API ini juga mendukung TCPServerSocket untuk menerima koneksi TCP masuk, sehingga IWA Anda dapat bertindak sebagai server lokal. Kode berikut mengilustrasikan cara membuat server TCP menggunakan antarmuka 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

Dengan membuat instance class dengan alamat '::', server akan terikat ke semua antarmuka jaringan IPv6 yang tersedia untuk memproses upaya masuk. Tidak seperti API server berbasis callback tradisional, API ini menggunakan pola Streams API web: koneksi masuk dikirimkan sebagai ReadableStream. Saat Anda memanggil reader.read(), aplikasi akan menunggu dan menerima koneksi berikutnya dari antrean, yang diselesaikan ke nilai yang merupakan instance TCPSocket yang berfungsi penuh dan siap untuk komunikasi dua arah dengan klien tertentu tersebut.

Men-debug Soket Langsung dengan Chrome DevTools

Mulai Chrome 138, Anda dapat men-debug traffic Direct Sockets langsung dalam panel Jaringan di Chrome DevTools, sehingga tidak memerlukan penganalisis paket eksternal. Alat ini memungkinkan Anda memantau koneksi TCPSocket serta traffic UDPSocket (dalam mode terikat dan terhubung) bersama dengan permintaan HTTP standar Anda.

Untuk memeriksa aktivitas jaringan aplikasi Anda:

  1. Buka panel Network di Chrome DevTools.
  2. Temukan dan pilih koneksi socket di tabel permintaan.
  3. Buka tab Pesan untuk melihat log semua data yang dikirim dan diterima.

Data di tab Pesan di DevTools.

Tampilan ini menyediakan Hex Viewer, yang memungkinkan Anda memeriksa payload biner mentah pesan TCP dan UDP, sehingga memastikan implementasi protokol Anda sempurna per byte.

Demo

IWA Kitchen Sink menampilkan aplikasi dengan beberapa tab, yang masing-masing mendemonstrasikan berbagai IWA API seperti Direct Sockets, Controlled Frame, dan lainnya.

Atau, demo klien telnet berisi Aplikasi Web Terisolasi yang memungkinkan pengguna terhubung ke server TCP/IP melalui terminal interaktif. Dengan kata lain, klien Telnet.

Kesimpulan

Direct Sockets API menutup kesenjangan fungsi penting dengan memungkinkan aplikasi web menangani protokol jaringan mentah yang sebelumnya tidak mungkin didukung tanpa wrapper native. Selain konektivitas klien yang sederhana, dengan TCPServerSocket, aplikasi dapat memproses koneksi masuk, sementara UDPSocket menawarkan mode fleksibel untuk komunikasi peer-to-peer dan penemuan jaringan lokal.

Dengan mengekspos kemampuan TCP dan UDP mentah ini melalui Streams API modern, Anda kini dapat membuat penerapan berfitur lengkap dari protokol lama—seperti SSH, RDP, atau standar IoT kustom—langsung di JavaScript. Karena API ini memberikan akses jaringan tingkat rendah, API ini memiliki implikasi keamanan yang signifikan. Oleh karena itu, fitur ini dibatasi untuk Aplikasi Web Terisolasi (IWA), sehingga memastikan bahwa kemampuan tersebut hanya diberikan kepada aplikasi tepercaya yang diinstal secara eksplisit dan menerapkan kebijakan keamanan yang ketat. Keseimbangan ini memungkinkan Anda membangun aplikasi yang canggih dan berfokus pada perangkat sekaligus mempertahankan keamanan yang diharapkan pengguna dari platform web.

Resource