actions/register_action_ws.ts view source
60000 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 Default inactivity window before the server closes a silent socket.
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.
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 Options for register_action_ws.
pathMount path (e.g., /api/ws).
stringappThe Hono app to mount on.
HonoupgradeWebSocketHono's upgradeWebSocket helper from the runtime adapter.
UpgradeWebSocketactionsThe 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.
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.
transportExisting 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.
heartbeatServer-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.
boolean | ServerHeartbeatOptionsartificial_delayOptional per-message delay for testing loading states. Ignored when 0.
numberlogOptional logger; defaults to [ws] namespace.
LoggerTypeon_socket_openCalled 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.
(ctx: SocketOpenContext) => void | Promise<void>on_socket_closeCalled 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.
(ctx: SocketCloseContext) => void | Promise<void>action_ip_rate_limiterPer-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.
RateLimiter | nullaction_account_rate_limiterPer-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.
RateLimiter | nullactions/register_action_ws.ts view source
RegisterActionWsResult Result of register_action_ws.
transportThe transport bound to the endpoint — supplied or freshly created.
actions/register_action_ws.ts view source
ServerHeartbeatOptions timeoutReceive-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).
numberactions/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).
wsThe raw WebSocket context at close time.
WSContextconnection_idConnection id captured at open time.
UuididentityAuth identity captured at open time — still valid even if the transport already cleaned up.
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.
wsThe raw WebSocket context — exposed for edge cases; prefer notify for sends.
WSContextconnection_idConnection id assigned by BackendWebsocketTransport.add_connection.
UuididentityAuth identity registered for this connection.
notifySend a JSON-RPC notification to just this socket. Mirrors ctx.notify
on per-message handler contexts — same socket-scoped semantics.
(method: string, params: unknown) => voidsignalFires when this socket closes — threaded through to every handler's ctx.signal.
AbortSignal