WebSockets
A connection Niles keeps open to an endpoint the manifest names: connecting, sending, the callbacks, and the limits.
Interface 0.4, supported by Niles 0.5.0. Interface 0.4 adds an outbound WebSocket client managed by Niles. The plugin names a manifest grant, supplies a URL and optional handshake headers, then receives asynchronous status and message callbacks. The WIT and the implementation in
crates/niles-plugins/src/websocket.rsare authoritative.
Declare the endpoint
Add a named grant under [permissions]. Its host is an exact hostname or IP, optionally setting:<key> referring to a declared string setting. port defaults to 443 when tls = true, otherwise 80. tls defaults to false.
interface = "0.4"
min_niles = "0.5.0"
[permissions]
websocket = [{ name = "feed", host = "stream.example.com", tls = true }]
[[settings]]
key = "token"
label = "Access token"
type = "secret"
required = trueUse wss for a public endpoint and ws for a local endpoint. A plain ws grant stays on the home network: it names a private address, a host name also listed under local_network, or a setting: address, and Niles refuses any public address it resolves to. The URL's scheme must match tls; its hostname and effective port must exactly match the grant. The resolved address is checked against Niles's WebSocket address policy. A setting: host is read from the plugin's settings, its declared default included. Grants have unique [a-z0-9_]+ names (maximum 32 characters), and port zero is invalid. The manifest validator requires interface 0.4 whenever WebSocket grants are declared. Ask only for endpoints the plugin needs.
Handshake headers are optional. At most 32 headers and 8 KiB total name/value bytes are accepted; invalid names, CR/LF in values, and WebSocket protocol/handshake headers (host, connection, upgrade, origin, sec-websocket-*) are rejected. Do not put credentials in URL userinfo or fragments; these are rejected too. A secret setting can be read with config.secret and used as an authorization header.
API and callbacks
websocket.connect(grant, url, headers) validates the grant and request synchronously, then starts the connection asynchronously and immediately returns its connection id. Network and handshake failures arrive via on-websocket-status; a successful connection produces connected. On connection, implement the on-websocket-message(conn, message) and on-websocket-status(conn, status) exports in the 0.4 world, even if a particular connection is unused.
message is either text(string) or binary(list<u8>). Call websocket.send(conn, message) to queue one message; it returns unavailable while not connected and limit when a message or queue exceeds bounds. It does not mean the peer received the frame. websocket.close(conn) is idempotent; closing an unknown id has no effect.
The maintained runnable component example is tests/fixtures/websocket: its src/lib.rs implements initialization, connect, status/message callbacks, send/echo and close using the generated interface bindings. Install the target with rustup target add wasm32-wasip2. From the repository root, build and copy the component into the host test fixture with env -u CARGO_TARGET_DIR sh crates/niles-plugins/tests/fixtures/websocket/build.sh. Run the host integration test with cargo test -p niles-plugins --test interface interface_0_4_websocket_round_trip -- --exact from the repository root.
The host integration test uses a synthetic WebSocket server bound to 127.0.0.1 and explicitly enables loopback for that test. This is test-only wiring, not production address policy or a production endpoint recommendation. The example intentionally reuses the maintained fixture rather than duplicating sample component code here.
Keep callbacks short and avoid blocking host calls. Callbacks are delivered serially to the plugin. Reconnect establishes a new generation; unread messages from older generations are discarded and counted in overflow(count). The latest pending disconnect/failure boundary is retained before the new connected status. Repeated statuses may be coalesced without counting them as message loss. The host does not promise replay of messages missed while offline.
Bounds and loss
- At most 4 connections per plugin.
- Each assembled inbound or outbound message is at most 64 KiB.
- The incoming callback queue holds at most 128 messages and 4 MiB total. When full, oldest messages are discarded; an
overflow(count)status reports the number dropped for that stream. Overflow is delivered before later messages. - The outgoing queue holds at most 32 messages and 1 MiB total. A full queue makes
sendfail withlimit. - Text and binary messages larger than 64 KiB are rejected by the host. A receive that exceeds the WebSocket library limit ends that session; the host reports disconnect and retries.
These are bounded, lossy queues: do not use this interface where every event must be delivered. Design application protocols to tolerate disconnects, duplicates and missing updates, and request a fresh snapshot after reconnect where needed.
Disconnect, retry, and terminal failure
DNS, TCP, peer-close, receive and send failures produce disconnected(reason) and trigger retries after 1, 2, 5, 10, 30, then 60 seconds; subsequent delays remain at 60 seconds. A successfully established WebSocket resets this consecutive-failure schedule, so a later disconnect starts again at 1 second. During a retry delay, queued sends are discarded and do not shorten the deadline; close interrupts the wait immediately. The schedule has no jitter. Some invalid requests, policy denials and handshake errors are classified as terminal failed(kind); kinds are policy, authentication, tls and protocol. Do not rely on a precise mapping from every network error to a failure kind: the current implementation classifies some handshake failures coarsely. A terminal failure ends automatic retry for that connection; close it and call connect again after correcting the grant, endpoint or credentials.
Handshake HTTP 401/403 responses are terminal authentication failures. HTTP 408/429 and 5xx responses, I/O failures, and handshake timeouts are retried. Other HTTP rejections and protocol errors are terminal; TLS errors, including certificate failures, are terminal TLS failures. Response bodies and underlying error details are not forwarded to callbacks.
close stops retries and requests a WebSocket close; if that cannot be queued, the host aborts the task. Otherwise the task has at most 250 ms to finish before forced cancellation, including during connection setup. Closing is local cleanup, not a remote application-level acknowledgement. stop is called before an instance is dropped, with a two-second deadline; nothing is guaranteed after it. Niles cancels that instance's remaining WebSocket tasks when their connections are dropped. Also close connections when no longer needed, but do not depend on stop to run after crashes or timeouts.
Compatibility
The manifest uses interface = "0.4"; build against the frozen 0.4 WIT package and set min_niles = "0.5.0". WebSocket support is available in Niles 0.5.0. Niles also runs frozen 0.1, 0.2 and 0.3 worlds; those worlds do not import WebSockets. Do not copy the current schema while claiming an older interface. See interface versions.