Skip to main content

Networking

Kog gives every app networking as global, zero-import APIs — you write await fetch(url) or new WebSocket(url) exactly as in a browser, with no import. The globals are installed by the runtime before your app runs. This page is the reference; the Networking guides are the tutorial.

The surface is intentionally close to the web platform, but narrowed to what a microcontroller can actually deliver. Every place it diverges is called out inline and collected in Divergences — and each divergence is backed by a test in @kog/net.

Where each API runs

APIESP32 deviceDesktop simulatorWeb playground
fetchyes (esp_http_client)yesyes — the browser's fetch (CORS applies)
WebSocketyesyesyes — the browser's WebSocket
TCPSocket / UDPSocket / TCPServeryesyesthrows KogNetUnavailableError
WiFiyes (real join)emulated (always connected)emulated

CORS is real in the playground. In the web playground fetch and WebSocket are the browser's own, so a cross-origin request to a server without permissive CORS headers fails just as it would on any web page. On a device there is no browser and no CORS — the same code reaches any host the network routes to. See the fetch guide.


fetch

fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>

Resolves with a Response when the response headers arrive (the body streams in after, per the standard). A non-2xx status is not a rejection — check response.ok / response.status. Rejects with a TypeError on a network/DNS/connection failure, or an AbortError if an AbortSignal fires.

RequestInit fields Kog honors:

FieldTypeNotes
methodstringdefault "GET"
headersHeaders | Record<string,string> | [string,string][]
bodystring | ArrayBuffer | ArrayBufferView | URLSearchParamsstrings sent UTF-8; URLSearchParams sets the form content-type
signalAbortSignalcancels the in-flight request
redirect"follow" | "error" | "manual"default "follow"

Browser-only fields (mode, credentials, cache, referrer, referrerPolicy, integrity, priority, keepalive) are accepted and ignored — they type-check so browser code ports cleanly, but a device has no origin, cookie jar, or HTTP cache. Streaming request bodies (ReadableStream) are not supported (see divergences).

const res = await fetch('https://api.example.com/data', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ hello: 'world' }),
signal: AbortSignal.timeout(5000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

Headers

new Headers(init?: Headers | Record<string, string> | [string, string][])

append · delete · get · getSetCookie · has · set · forEach · keys / values / entries / [Symbol.iterator]. Names are case-insensitive; get combines duplicates with ", "; iteration is sorted by lowercased name. Duplicate headers (multiple Set-Cookie) are preserved.

Request

new Request(input: string | URL | Request, init?: RequestInit)

Properties url · method · headers · redirect · signal · bodyUsed · body (always null — no streams). Body accessors, each usable once: .text() · .json() · .arrayBuffer() · .bytes() (→ Uint8Array) · .clone(). There is no .blob() / .formData().

Response

new Response(body?: BodyInit | null, init?: ResponseInit)
Response.json(data, init?): Response
Response.error(): Response
Response.redirect(url, status?): Response

Properties status · statusText · ok · headers · url · redirected · type · bodyUsed · body (null). Same body accessors as Request.

AbortController & AbortSignal

const ac = new AbortController();
fetch(url, { signal: ac.signal });
ac.abort(reason?); // rejects the fetch with AbortError

AbortSignal.timeout(ms): AbortSignal // the idiomatic fetch timeout
AbortSignal.abort(reason?): AbortSignal
AbortSignal.any(signals): AbortSignal

AbortSignal is an EventTarget: .aborted, .reason, .onabort, addEventListener('abort', …), .throwIfAborted(). Aborting a fetch truly cancels the underlying request (not just the JS promise).


WebSocket

The standard browser WebSocket.

const ws = new WebSocket('wss://example.com/socket', protocols?);
ws.onopen = () => ws.send('hello');
ws.onmessage = (e) => console.log(e.data); // string, or ArrayBuffer for binary
ws.onclose = (e) => console.log(e.code, e.reason);
ws.onerror = () => {};
ws.send('text' | arrayBuffer | typedArray);
ws.close(code?, reason?);

Readystate constants WebSocket.CONNECTING/OPEN/CLOSING/CLOSED (0–3). Both the onopen/onmessage/onclose/onerror handler properties and addEventListener work. Properties: url, readyState, protocol, extensions (""), bufferedAmount (best-effort), binaryType.

binaryType defaults to "arraybuffer" (the browser default is "blob"), and "blob" is unsupported — Kog has no Blob. Binary frames therefore arrive as an ArrayBuffer.


Sockets — TCPSocket, UDPSocket, TCPServer

A Kog-native promise + callback API — not node's net/dgram, and not the browser. Each onX(cb) registers a listener and returns an unsubscribe function. Inbound data is always an ArrayBuffer (never a string or Buffer); send-family calls also accept a string, which is UTF-8 encoded for you.

These throw KogNetUnavailableError in the web playground — a browser has no raw sockets. Run on a device or the desktop simulator.

TCPSocket

const s = new TCPSocket();
await s.connect(host: string, port: number, opts?: { tls?: boolean; timeoutMs?: number });
const off = s.onData((chunk: ArrayBuffer) => {}); // returns unsubscribe
s.onClose((info) => {});
s.onError((err: KogNetError) => {});
s.onDrain(() => {}); // backpressure pacing
s.send('ping' | arrayBuffer | typedArray); // queues; throws if not open
await s.close(); // graceful flush-then-FIN

Properties: readyState ("connecting" | "open" | "closing" | "closed"), remoteAddress, remotePort, localAddress, localPort, bufferedAmount, bytesRead, bytesWritten. connect resolves once established (after the TLS handshake when tls: true).

UDPSocket

const u = new UDPSocket();
await u.bind(port?: number, opts?: { address?: string; reuseAddr?: boolean });
u.onMessage((msg: ArrayBuffer, rinfo: { address; port; family; size }) => {});
u.onError((err) => {});
u.send('payload' | arrayBuffer, port: number, host: string);
u.close();

TCPServer

const srv = new TCPServer();
await srv.listen(port: number, opts?: { host?: string; backlog?: number });
srv.onConnection((sock: TCPSocket) => { /* an already-open TCPSocket */ });
srv.onError((err) => {});
await srv.close(); // stops accepting; existing sockets keep running

WiFi

A global WiFi object. On a device it drives a real association; on the desktop simulator and web playground it is always-connected emulation (so a tutorial that gates on connectivity runs unchanged).

WiFi.connect(ssid?, password?, opts?: { timeoutMs?; bssid?; save? }): Promise<{ ssid; ip }>;
WiFi.disconnect(): Promise<void>;
WiFi.status(): { state: 'disconnected'|'connecting'|'connected'|'failed'; ssid?; ip?; rssi?; mac? };
WiFi.ready(): Promise<void>; // resolves when connected
WiFi.scan(): Promise<WiFiNetwork[]>;
WiFi.on('connect'|'disconnect'|'ipchange'|'error', cb): () => void;
WiFi.isConnected: boolean;

WiFi.connect() with no arguments joins the credentials you provisioned with kog wifi. Passing { save: true } persists the credentials to the device on a successful join (off by default so a loop can't wear out flash). Credentials are baked into the device at flash time, never into your app bundle — see the Wi-Fi guide.

opts.timeoutMs is enforced by the device's join logic (the only target with a real radio); on the desktop simulator and web playground it is a no-op, since connect() resolves immediately against the always-connected emulation.


Supporting globals

The runtime also ships the web globals fetch and friends rely on, all zero-import: TextEncoder / TextDecoder, URL / URLSearchParams, btoa / atob, AbortController / AbortSignal, and a minimal Event / EventTarget / MessageEvent / CloseEvent / DOMException. TextDecoder supports UTF-8, ASCII, and Latin-1; URL is a WHATWG subset covering the common schemes (http/https/ws/wss/file + relative resolution).

const bytes = new TextEncoder().encode('héllo');
const text = new TextDecoder().decode(bytes);
const url = new URL('/path?q=1', 'https://example.com');
url.searchParams.get('q'); // "1"

Divergences from the web standard

Kog's networking is honest about being a microcontroller runtime, not a browser. Each item below is verified by a test in @kog/net.

  1. No streaming bodies. Request.body / Response.body are always null; bodies are buffered. Use .text() / .arrayBuffer() / .json().
  2. No Blob / FormData. No .blob() / .formData(); use ArrayBuffer and URLSearchParams.
  3. fetch ignores browser-only init (credentials, mode, cache, …) — a device has no origin, cookies, or cache.
  4. CORS applies only in the web playground (browser fetch/WebSocket); a device reaches any routable host.
  5. WebSocket.binaryType defaults to "arraybuffer", and "blob" is unsupported.
  6. WebSocket.bufferedAmount is best-effort — a lower bound, never used for correctness.
  7. Sockets are Kog-nativeonData/onMessage/onConnection returning an unsubscribe, not node's EventEmitter or DOM events; inbound data is ArrayBuffer.
  8. Raw sockets throw in the web playground (KogNetUnavailableError) — a browser has none.
  9. WiFi is emulated off-device — the sim and playground report connected without a real radio.
  10. Minimal Event / DOMException — only what WebSocket / AbortSignal need (no capture/bubble phases).
  11. URL is a WHATWG subset; exotic IDNA/punycode host normalization is best-effort.
  12. TextDecoder is UTF-8 / ASCII / Latin-1 only; other labels throw RangeError.
  13. No HTTP/2 and no automatic decompression on device — fetch is HTTP/1.1.

Errors

fetch / WebSocket throw the standard types (TypeError for network failures, DOMException for AbortError / InvalidStateError). The Kog-native surfaces (sockets, WiFi) reject or emit a KogNetError with a stable, machine-readable .code string — branch on the string, never a raw number:

.codeMeaning
NET_UNSUPPORTEDoperation unavailable on this target (e.g. sockets in the web playground)
NET_DNShost lookup failed
NET_CONNREFUSEDconnection refused
NET_TIMEOUTtimed out
NET_TLSTLS handshake / certificate failure
NET_CLOSEDoperation on a closed handle
NET_RESETconnection reset by peer
NET_ADDR_IN_USEaddress/port already in use
NET_UNREACHABLEhost unreachable
NET_ABORTEDaborted
NET_WIFIWi-Fi join failed
NET_AUTHWi-Fi auth failure / wrong password
NET_PROTOmalformed HTTP/WS
try {
await s.connect('10.0.0.1', 80);
} catch (err) {
if (err instanceof KogNetError && err.code === 'NET_CONNREFUSED') {
// ...
}
}