/**
 * 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.
 *
 * @module
 */
import type { Hono } from 'hono';
import type { UpgradeWebSocket, WSContext } from 'hono/ws';
import { type Logger as LoggerType } from '@fuzdev/fuz_util/log.js';
import type { Uuid } from '@fuzdev/fuz_util/id.js';
import type { RateLimiter } from '../rate_limiter.js';
import type { Db } from '../db/db.js';
import { type Action } from './action_types.js';
import { BackendWebsocketTransport, type ConnectionIdentity } from './transports_ws_backend.js';
export type { Action };
/** Default inactivity window before the server closes a silent socket. */
export declare const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60000;
/**
 * 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.
 */
export interface SocketOpenContext {
    /** The raw WebSocket context — exposed for edge cases; prefer `notify` for sends. */
    ws: WSContext;
    /** Connection id assigned by `BackendWebsocketTransport.add_connection`. */
    connection_id: Uuid;
    /** Auth identity registered for this connection. */
    identity: ConnectionIdentity;
    /**
     * Send a JSON-RPC notification to just this socket. Mirrors `ctx.notify`
     * on per-message handler contexts — same socket-scoped semantics.
     */
    notify: (method: string, params: unknown) => void;
    /** Fires when this socket closes — threaded through to every handler's `ctx.signal`. */
    signal: AbortSignal;
}
/**
 * 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).
 */
export interface SocketCloseContext {
    /** The raw WebSocket context at close time. */
    ws: WSContext;
    /** Connection id captured at open time. */
    connection_id: Uuid;
    /** Auth identity captured at open time — still valid even if the transport already cleaned up. */
    identity: ConnectionIdentity;
}
export interface ServerHeartbeatOptions {
    /**
     * 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).
     */
    timeout?: number;
}
/** Options for `register_action_ws`. */
export interface RegisterActionWsOptions {
    /** Mount path (e.g., `/api/ws`). */
    path: string;
    /** The Hono app to mount on. */
    app: Hono;
    /** Hono's `upgradeWebSocket` helper from the runtime adapter. */
    upgradeWebSocket: UpgradeWebSocket;
    /**
     * 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.
     */
    actions: ReadonlyArray<Action>;
    /**
     * 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.
     */
    db: Db;
    /**
     * 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`.
     */
    transport?: BackendWebsocketTransport;
    /**
     * 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.
     */
    heartbeat?: boolean | ServerHeartbeatOptions;
    /** Optional per-message delay for testing loading states. Ignored when `0`. */
    artificial_delay?: number;
    /** Optional logger; defaults to `[ws]` namespace. */
    log?: LoggerType;
    /**
     * 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.
     */
    on_socket_open?: (ctx: SocketOpenContext) => void | Promise<void>;
    /**
     * 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.
     */
    on_socket_close?: (ctx: SocketCloseContext) => void | Promise<void>;
    /**
     * 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.
     */
    action_ip_rate_limiter?: RateLimiter | null;
    /**
     * 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.
     */
    action_account_rate_limiter?: RateLimiter | null;
}
/** Result of `register_action_ws`. */
export interface RegisterActionWsResult {
    /** The transport bound to the endpoint — supplied or freshly created. */
    transport: BackendWebsocketTransport;
}
/**
 * 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`.
 *
 * @returns the transport (supplied or freshly created) — retain it to wire
 *   `create_ws_auth_guard` or broadcast on audit events.
 * @mutates options.app - registers a `GET path` route via `upgradeWebSocket`
 * @mutates options.transport - on every message, adds/removes connections
 *   in the transport's internal maps via `add_connection` / `remove_connection`
 */
export declare const register_action_ws: (options: RegisterActionWsOptions) => RegisterActionWsResult;
//# sourceMappingURL=register_action_ws.d.ts.map