actions/socket.svelte.ts

Frontend WebSocket client — portable, Svelte-reactive, implements WebsocketConnection.

Plain class with $state runes (no Cell inheritance, no app coupling). Drop into any SvelteKit frontend as the underlying connection for FrontendWebsocketTransport. Handles auto-reconnect with exponential backoff, respects WS_CLOSE_SESSION_REVOKED (no reconnect loop after the server revokes auth), exposes reactive status for UI indicators, and ships three correctness primitives default-on:

- FrontendWebsocketClient.request — promise-based JSON-RPC with auto-assigned ids and a pending-id map. Intercepts responses on the message path so request/response correlation is transport-level rather than re-invented per consumer. - Durable queuerequest() calls made while disconnected buffer up to DEFAULT_QUEUE_MAX_SIZE requests and flush on reopen. Overflow rejects with queue_overflow. Raw FrontendWebsocketClient.send is drop-on-disconnect (fire-and-forget notifications want that). - Activity-aware heartbeat — idles fire a shared heartbeat request; receive-silence past DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT closes with WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT and lets auto-reconnect pick back up.

Declarations
#

16 declarations

view source

DEFAULT_BACKOFF_FACTOR
#

DEFAULT_CLOSE_CODE
#

DEFAULT_HEARTBEAT_INTERVAL
#

DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT
#

DEFAULT_QUEUE_MAX_SIZE
#

DEFAULT_RECONNECT_DELAY
#

DEFAULT_RECONNECT_DELAY_MAX
#

FrontendWebsocketClient
#

actions/socket.svelte.ts view source

Reactive WebSocket client implementing WebsocketConnection.

Construct with a URL and optional config; call connect() to open the socket and begin auto-reconnect. Register message/error handlers via add_message_handler / add_error_handler — both return unsubscribe functions. FrontendWebsocketTransport consumes this as its connection.

Session-revocation close codes (WS_CLOSE_SESSION_REVOKED) put the client in a permanently-closed state; reconnecting would just loop on 401.

inheritance

implements:

ws

type WebSocket | null

status

type SocketStatus

reconnect_count

type number

current_reconnect_delay

type number

last_connect_time

Epoch ms of the most recent successful open. Never cleared on close.

type number | null

last_close_time

Epoch ms of the most recent close event or client-initiated close.

type number | null

last_close_code

Close code from the most recent close. Initial null means "never closed."

type number | null

last_close_reason

Reason string from the most recent close event (may be empty).

type string | null

last_send_error

The error thrown by the most recent attempted send(), or null if the most recent attempt succeeded or none has been attempted yet. Populated when the underlying ws.send throws (e.g., buffer full, serialization error); reset to null on the next successful send. Not touched when send() short-circuits because the socket is not connected — consult connected for that case. Wrappers surfacing per-message failure reasons can read this after a false return from send().

type Error | null

connected

type boolean

readonly

constructor

type new (url: string, options?: FrontendWebsocketClientOptions): FrontendWebsocketClient

url
type string
options
default {}

set_reconnect

Swap the auto-reconnect policy in place. Accepts the same shape as the constructor's reconnect option: false disables reconnect, true or null/omitted restores the defaults, or a config object customizes specific fields (missing fields fall back to defaults, not "keep current" — each call defines the whole policy atomically, same as the constructor).

In-flight reconnect schedules are monotonically shortened: the effective total wait from arm-time never exceeds what the new policy prescribes. If the new target is already past the time already elapsed, the reconnect fires immediately (on the next tick). The wait is never extended.

Turning reconnect off while a reconnect timer is pending cancels that timer and transitions status to closed (since the lie of 'reconnecting' would be visible to UI indicators). Turning it back on does not synthesize a reconnect — wait for the next close.

type (reconnect?: boolean | FrontendWebsocketReconnectOptions | null): void

reconnect
type boolean | FrontendWebsocketReconnectOptions | null
default null
returns void

set_heartbeat

Swap the heartbeat policy in place. Accepts the same shape as the constructor's heartbeat option: false disables the timer, true or null/omitted restores the defaults, or a config object customizes specific fields (missing fields fall back to defaults, not "keep current" — each call defines the whole policy atomically, same as the constructor and set_reconnect).

When connected, the live timer is restarted immediately so the new interval / receive_timeout take effect without a reconnect; when disconnected, just stashes the policy for the next open.

type (heartbeat?: boolean | FrontendWebsocketHeartbeatOptions | null): void

heartbeat
type boolean | FrontendWebsocketHeartbeatOptions | null
default null
returns void

cancel_reconnect

Cancel a scheduled reconnect without closing the client or disabling auto-reconnect. Transitions status from reconnectingclosed and resets the backoff counters — the next close still triggers a fresh reconnect cycle under the current policy. No-op when no reconnect is pending.

Use this when UI state asks "stop trying for now" without the finality of disconnect (which also rejects pending/queued requests and clears heartbeat) or the policy change of set_reconnect(false) (which disables future reconnects). The queue stays intact so that calling connect later flushes buffered work.

type (): void

returns void

connect

Open the WebSocket. No-op on SSR, or if the session has been revoked. Cancels any pending reconnect and tears down any existing connection first; an open prior socket is closed with a normal-closure code.

type (): void

returns void

disconnect

Close the WebSocket, cancel any pending reconnect, and reset the reconnect backoff counters. Puts the client in closed status; call connect() to reopen. Safe to call more than once.

type (code?: number): void

code
type number
default DEFAULT_CLOSE_CODE
returns void

[Symbol.dispose]

Explicit-resource-management hook — supports using client = new FrontendWebsocketClient(url).

type (): void

returns void

send

type (data: object): boolean

data
type object
returns boolean

request

Promise-based JSON-RPC over the socket. Auto-assigns a monotonic request id (or uses an explicit one supplied via options.id — used by FrontendWebsocketTransport which delegates to this method and has its own peer-minted UUID), tracks the pending promise, and resolves when the server sends a matching response.

Callers supplying an explicit options.id are responsible for uniqueness — the pending map is keyed by id, and a duplicate silently overwrites the prior entry. Auto-minted ids are monotonic and never collide with themselves or with peer-minted UUIDs (the types differ: integer vs string).

While the socket is disconnected, the request is buffered in a bounded queue (default-on, DEFAULT_QUEUE_MAX_SIZE) and flushed on reopen. Pass {queue: false} to reject immediately when disconnected — used internally by the heartbeat, which must not fight the queue for the disconnect-detection slot.

On AbortSignal fire: rejects the local promise *and* sends the shared cancel notification (cancel_action_spec.method) so the server-side dispatcher can abort the matching handler's ctx.signal. Suppressed for queued-but-never-sent (server doesn't know about it) and response-beat-cancel races.

type <R = unknown>(method: string, params?: unknown, options?: { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }): Promise<R>

method
type string
params
type unknown
default {}
options
type { signal?: AbortSignal | undefined; queue?: boolean | undefined; id?: string | number | undefined; }
default {}
returns Promise<R>
throws
  • ThrownJsonrpcError - on the returned promise — never thrown

add_message_handler

type (handler: SocketMessageHandler): () => void

handler
returns () => void

add_error_handler

type (handler: SocketErrorHandler): () => void

handler
returns () => void

FrontendWebsocketClientOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketClientOptions

reconnect

Auto-reconnect policy. false disables reconnect entirely; true or omit for default timing; pass an object to customize.

type boolean | FrontendWebsocketReconnectOptions | null

heartbeat

Activity-aware heartbeat. true/null/omit for defaults; false disables the timer entirely (only do this if the server side is also running without heartbeat); pass an object to tune interval / receive_timeout.

type boolean | FrontendWebsocketHeartbeatOptions | null

queue

Durable queue for FrontendWebsocketClient.request. true or omit for defaults; false disables buffering (requests while disconnected reject immediately). Raw FrontendWebsocketClient.send is never queued — use request() for RPC semantics.

type boolean | FrontendWebsocketQueueOptions

log

Optional logger for diagnostic messages.

type Logger | null

FrontendWebsocketHeartbeatOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketHeartbeatOptions

interval

Idle duration (ms) after which a heartbeat is sent. Reset by any send or receive — chatty clients never emit extras. Defaults to DEFAULT_HEARTBEAT_INTERVAL.

type number

receive_timeout

Receive-silence (ms) after which the client closes the socket with WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, letting auto-reconnect kick in. Should be a comfortable multiple of interval. Defaults to DEFAULT_HEARTBEAT_RECEIVE_TIMEOUT.

type number

FrontendWebsocketQueueOptions
#

FrontendWebsocketReconnectOptions
#

actions/socket.svelte.ts view source

FrontendWebsocketReconnectOptions

delay

Base reconnect delay in ms. Defaults to 1000.

type number

delay_max

Max reconnect delay in ms (cap on exponential backoff). Defaults to 10000.

type number

factor

Exponential backoff factor. Defaults to 1.5.

type number

socket_status_to_async_status
#

actions/socket.svelte.ts view source

(status: SocketStatus, revoked: boolean): AsyncStatus

Project SocketStatus onto fuz_util's AsyncStatus — the 5-way → 4-way mapping every consumer re-derives to surface connection state to UI (loading indicators, retry banners). Collapses reconnecting into failure (UI shows "lost, retrying") and splits closed by revoked so a terminal session-revocation read as failure while a clean client- initiated close reads as initial (the "not connected, not trying" state).

status

revoked

whether the session has been permanently revoked (typically FrontendWebsocketClient.revoked)

type boolean

returns

AsyncStatus

SocketErrorHandler
#

SocketMessageHandler
#

SocketStatus
#

actions/socket.svelte.ts view source

SocketStatus

Client-side WebSocket status.

- initial — never connected; connect() has not been called. - connecting — WebSocket readyState === CONNECTING. - connected — WebSocket readyState === OPEN. - reconnecting — close fired; waiting out backoff before next attempt. - closed — socket is not open. Terminal only when revoked is true or auto-reconnect is disabled; otherwise connect() reopens.

Depends on
#