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): both transports parse their wire envelope, then call the shared perform_action core (actions/perform_action.ts) for the post-parse pipeline. WS-specific concerns — connection lifecycle, heartbeat, cancel-notification interception, socket-scoped notify — stay in this module; everything else (auth gates, input validation, authorization phase, rate limiting, transactional dispatch, DEV output validation, thrown-error normalization) is shared.

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). Per-action auth runs inside perform_action on every message via the same gates HTTP RPC uses.

Declarations
#

7 declarations

view source

DEFAULT_SERVER_HEARTBEAT_TIMEOUT
#

register_action_ws
#

actions/register_action_ws.ts view source

(options: RegisterActionWsOptions): RegisterActionWsResult

Mount a JSON-RPC WebSocket endpoint that dispatches via the shared perform_action core.

Wire behavior: - Batch JSON-RPC is rejected (single-message only). - Notifications (method + no id) are silently dropped per JSON-RPC spec. Exception: cancel notifications abort the matching pending request's ctx.signal before bubbling out. - Per-message dispatch goes through perform_action: pre-validation auth (401) → input validation (400) → authorization phase → post-authorization auth (403) → rate limit (429) → handler (with transaction wrap iff spec.side_effects: true) → DEV output validation. - Authorization phase runs per message — role_grant changes during a connection lifetime are picked up on the next message without any in-place refresh. Authentication invalidation closes the socket via create_ws_auth_guard.

options

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

Options for register_action_ws.

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>

db

Pool-level DB. The dispatcher wraps in db.transaction for side_effects: true actions, the same way HTTP RPC does. Per-message authorization phase reads through this pool.

Audit writes and other rollback-resilient fire-and-forget calls run through AppDeps.audit.emit from the action factory's closure — the dispatcher never holds an audit-side pool reference; the bound emitter owns the pool.

type Db

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-account rate limiter consulted for actions whose spec declares rate_limit: 'account' or 'both'. Keyed on request_context.account.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. spawning a per-account unit and pushing 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
#