actions/register_action_ws.ts view source
60000 import {DEFAULT_SERVER_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/register_action_ws.js'; Default inactivity window before the server closes a silent socket.
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.
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.
7 declarations
actions/register_action_ws.ts view source
60000 import {DEFAULT_SERVER_HEARTBEAT_TIMEOUT} from '@fuzdev/fuz_app/actions/register_action_ws.js'; Default inactivity window before the server closes a silent socket.
actions/register_action_ws.ts view source
(options: RegisterActionWsOptions): RegisterActionWsResult import {register_action_ws} from '@fuzdev/fuz_app/actions/register_action_ws.js'; Mount a JSON-RPC WebSocket endpoint that dispatches via the shared perform_action core.
Wire behavior:
cancel notifications abort the matching pending request's
ctx.signal before bubbling out.spec.side_effects: true) → DEV output validation.optionsRegisterActionWsResult the transport (supplied or freshly created) — retain it to wire create_ws_auth_guard or broadcast on audit events.
actions/register_action_ws.ts view source
RegisterActionWsOptions import type {RegisterActionWsOptions} from '@fuzdev/fuz_app/actions/register_action_ws.js'; Options for register_action_ws.
pathMount path (e.g., /api/ws).
type string
appThe Hono app to mount on.
type Hono
upgradeWebSocketHono's upgradeWebSocket helper from the runtime adapter.
type UpgradeWebSocket
actionsThe 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>
dbPool-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
actions/register_action_ws.ts view source
RegisterActionWsResult import type {RegisterActionWsResult} from '@fuzdev/fuz_app/actions/register_action_ws.js'; Result of register_action_ws.
transportThe transport bound to the endpoint — supplied or freshly created.
actions/register_action_ws.ts view source
ServerHeartbeatOptions import type {ServerHeartbeatOptions} from '@fuzdev/fuz_app/actions/register_action_ws.js'; timeout?Receive-silence (ms) past which the server closes the socket with
WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT. Any incoming message resets
the counter — chatty clients never trip it. First timeout
window after socket open is exempt (cold-start grace).
type number
actions/register_action_ws.ts view source
SocketCloseContext import type {SocketCloseContext} from '@fuzdev/fuz_app/actions/register_action_ws.js'; 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).
wsThe raw WebSocket context at close time.
type WSContext
connection_idConnection id captured at open time.
type Uuid
identityAuth identity captured at open time — still valid even if the transport already cleaned up.
type ConnectionIdentity
actions/register_action_ws.ts view source
SocketOpenContext import type {SocketOpenContext} from '@fuzdev/fuz_app/actions/register_action_ws.js'; 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.
wsThe raw WebSocket context — exposed for edge cases; prefer notify for sends.
type WSContext
connection_idConnection id assigned by BackendWebsocketTransport.add_connection.
type Uuid
identityAuth identity registered for this connection.
type ConnectionIdentity
notifySend 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
signalFires when this socket closes — threaded through to every handler's ctx.signal.
type AbortSignal