actions/register_action_ws.ts

WebSocket JSON-RPC dispatch — the low-level WS transport binding.

Most consumers should mount WS endpoints via register_ws_endpoint (actions/register_ws_endpoint.ts), which wraps this function with the standard upgrade stack (origin check + auth + optional role). This module stays exported as the lower-level entry point for tests that drive the dispatcher directly via create_ws_test_harness.

Symmetric to create_rpc_endpoint (from actions/action_rpc.ts): consumer supplies action specs + a handler map, the dispatcher parses the envelope, checks per-action auth, validates input, invokes the handler with a per-request context, and writes the response.

Extracted from zzz's register_websocket_actions to converge pattern drift across consumers (zzz, tx, undying). Broadcast-style notifications remain domain-shaped today — this module only covers per-request dispatch + the socket-scoped ctx.notify + per-socket ctx.signal. See BackendWebsocketTransport.send for broadcast.

Auth expectations

The consumer is responsible for rejecting unauthenticated upgrades *before* routing to this handler (fuz_app's require_auth middleware, or register_ws_endpoint which wires it for you). Inside the dispatcher, get_request_context(c) is treated as guaranteed non-null and per-action auth is enforced on each message.

Declarations
#

7 declarations

view source

DEFAULT_SERVER_HEARTBEAT_TIMEOUT
#

register_action_ws
#

actions/register_action_ws.ts view source

<TCtx extends BaseHandlerContext>(options: RegisterActionWsOptions<TCtx>): RegisterActionWsResult

Mount a JSON-RPC WebSocket endpoint that dispatches to the supplied handler map. Per-request context is built from the base + consumer-provided RegisterActionWsOptions.extend_context.

Wire behavior: - Batch JSON-RPC is rejected (single-message only). - Notifications (method + no id) are silently dropped per JSON-RPC spec. - Per-action auth: public / authenticated pass through (upgrade auth already verified identity); keeper requires daemon_token credential type *and* the keeper role; role-based {role} requires the named role via has_role, matching the HTTP path in actions/action_rpc.ts. - DEV mode validates handler output against the spec's output schema and warns on mismatches.

options

type RegisterActionWsOptions<TCtx>

returns

RegisterActionWsResult

the transport (supplied or freshly created) — retain it to wire create_ws_auth_guard or broadcast on audit events.

RegisterActionWsOptions
#

actions/register_action_ws.ts view source

RegisterActionWsOptions<TCtx>

Options for register_action_ws.

generics

TCtx

path

Mount path (e.g., /api/ws).

type string

app

The Hono app to mount on.

type Hono

upgradeWebSocket

Hono's upgradeWebSocket helper from the runtime adapter.

type UpgradeWebSocket

actions

The actions registered on this endpoint — each carries a spec (drives method lookup, per-action auth, input/output validation) and an optional handler (omit for client-only specs like inbound notifications). Spread protocol_actions from actions/protocol.ts here to complete the disconnect-detection + per-request cancel pairing with the frontend client.

type ReadonlyArray<Action<TCtx>>

extend_context

Build the per-request context from the base and the upgrade-time Hono context. Called once per incoming message. Consumers use this to attach domain singletons (backend) or per-socket auth (auth, credential_type) without re-reading them from c inside every handler.

type (base: BaseHandlerContext, c: Context) => TCtx

transport

Existing transport to register connections with. When omitted, a fresh one is created and returned in the result. Pass your own to keep a handle for create_ws_auth_guard and send_to/broadcast.

heartbeat

Server-side heartbeat policy. Default-on (receive-silence detection, 60s timeout). false disables the timer entirely — only do this if the upstream stack (TCP keepalive, Cloudflare idle timeout, etc.) already owns disconnect detection. Pass an object to tune the timeout.

type boolean | ServerHeartbeatOptions

artificial_delay

Optional per-message delay for testing loading states. Ignored when 0.

type number

log

Optional logger; defaults to [ws] namespace.

type LoggerType

on_socket_open

Called once per socket, after the transport registers the connection. Awaited before any message is dispatched. Throwing logs an error and closes the socket with an internal_error frame — a failing bootstrap should not leave a partially-initialized socket alive.

type (ctx: SocketOpenContext) => void | Promise<void>

on_socket_close

Called once per socket on close, *before* the transport removes the connection. Receives connection_id and identity captured at open time, so it is safe to read even when the audit guard has already torn down the transport's internal state. Errors are logged and swallowed.

type (ctx: SocketCloseContext) => void | Promise<void>

action_ip_rate_limiter

Per-IP rate limiter consulted for actions whose spec declares rate_limit: 'ip' or 'both'. null (or omitted) disables the IP check. Same limiter is shared with the HTTP RPC dispatcher so one budget covers both transports per action. Resolved at upgrade time and reused for every message on the socket.

type RateLimiter | null

action_account_rate_limiter

Per-actor rate limiter consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on request_context.actor.id. null (or omitted) disables the account check. Same limiter is shared with the HTTP RPC dispatcher.

type RateLimiter | null

RegisterActionWsResult
#

ServerHeartbeatOptions
#

SocketCloseContext
#

actions/register_action_ws.ts view source

SocketCloseContext

Context passed to the on_socket_close hook.

Fires before transport.remove_connection runs, so consumer cleanup can still read identity before it's torn down. Fires for both client-initiated closes (Hono onClose) and server-initiated closes via audit revocation (the audit guard calls ws.close(), which triggers Hono's onClose).

ws

The raw WebSocket context at close time.

type WSContext

connection_id

Connection id captured at open time.

type Uuid

identity

Auth identity captured at open time — still valid even if the transport already cleaned up.

SocketOpenContext
#

actions/register_action_ws.ts view source

SocketOpenContext

Context passed to the on_socket_open hook.

Fires after the transport has registered the new connection (so connection_id is valid) but before any client message can dispatch. Consumers use this to bootstrap per-socket domain state — e.g. undying spawns the per-account spirit unit and pushes an initial state snapshot.

ws

The raw WebSocket context — exposed for edge cases; prefer notify for sends.

type WSContext

connection_id

Connection id assigned by BackendWebsocketTransport.add_connection.

type Uuid

identity

Auth identity registered for this connection.

notify

Send a JSON-RPC notification to just this socket. Mirrors ctx.notify on per-message handler contexts — same socket-scoped semantics.

type (method: string, params: unknown) => void

signal

Fires when this socket closes — threaded through to every handler's ctx.signal.

type AbortSignal

Depends on
#

Imported by
#