/**
 * hosting/webSocketConversation — one upgraded socket, presented as a
 * {@link HostConversation}.
 *
 * This is the whole adapter side of the conversation port: it takes the
 * `'upgrade'` event a `node:http` server hands it, answers the handshake, and
 * turns the frames flowing over the socket into the six-member port a handler
 * sees. Everything protocol-shaped lives in `webSocketFrames.ts`; everything
 * port-shaped lives in `types.ts`; this file is the join.
 *
 * ── The laws it keeps, all of them pinned by tests ───────────────────────────
 *  - **A handshake the door guard refuses never gets a 101.** A WebSocket is
 *    not bound by the same-origin policy and its browser API cannot set a
 *    header, so there is no preflight to force: the host's guard judges Host,
 *    Origin and Fetch metadata on the handshake itself, and the session id the
 *    dialect read is bounded before the channel opens.
 *  - **A path this door does not own is not touched.** `node:http` calls EVERY
 *    `'upgrade'` listener for every upgrade, exactly as it calls every
 *    `'request'` listener — so a caller's own protocol lives beside this one on
 *    the same socket. Whether an unclaimed path gets an answer is the caller's
 *    business on a server they own, and this door's only on a server it owns.
 *  - **Frames that arrive before the handler subscribes are held, up to a
 *    declared bound.** An `async` handler that awaits before calling `onFrame`
 *    would otherwise lose the far side's opening frame. The bound is in BYTES
 *    and overflow ends the conversation with a stated reason, because an
 *    unbounded queue somebody else fills is a way to kill this process, and an
 *    undeclared ceiling is the exact thing the declared-ceilings rule exists to
 *    forbid.
 *  - **`close()` ends every live conversation before the socket is released.**
 *    Measured, not assumed: an upgraded socket keeps `server.close()` waiting
 *    forever, so a door that let go of its conversations would hang the whole
 *    shutdown.
 *  - **The port's frame is the whole message.** A message the transport
 *    delivered in fragments counts against `maxFrameBytes` in total, so
 *    fragmentation cannot be used to walk around the ceiling.
 *  - **Nothing in a conversation's lifecycle is ever the PROCESS's failure.**
 *    Node calls a socket's listeners from its own stack, so a throw inside one
 *    is uncaught and ends the container. Every listener body here that computes
 *    is wrapped: a surprise costs THIS conversation, with a reason, and the
 *    door keeps carrying everybody else's.
 *
 * Pattern: Adapter. Role: outer ring, one transport.
 */
/// <reference types="node" />
/// <reference types="node" />
import type { IncomingMessage } from 'node:http';
import type { Duplex } from 'node:stream';
import { type DoorGuard } from './doorGuard.js';
import { type ResolvedHostSignIn } from './signin/hostSignIn.js';
import type { ConversationHandler, ConversationLimits, HostRefusal } from './types.js';
/**
 * What a wire read out of one handshake — the conversation half of a
 * deployment's dialect.
 *
 * There is no body to read here, which is the whole difference from a request:
 * a handshake is a URL and some headers, so a dialect that wants a session id
 * or a credential has to find it in those.
 */
export interface ConversationHandshake {
    /** The session this conversation claims. Caller data; never identity. */
    readonly sessionId?: string;
    /**
     * Headers to merge OVER the raw lower-cased ones the transport delivered.
     *
     * This is how an adapter maps its own spelling into the port's vocabulary —
     * a credential a browser could only send as a subprotocol becomes an ordinary
     * `authorization` header here. The raw headers are never removed, so nothing
     * a mapping did not understand is lost.
     */
    readonly headers?: Readonly<Record<string, string>>;
    /**
     * The subprotocol to echo in the 101, when this dialect selects one.
     *
     * Selecting is the wire's job because only the wire read the offer. Echoing
     * something the client did not offer makes the client fail the connection.
     */
    readonly protocol?: string;
}
/** Everything the door needs to answer one upgrade. */
export interface ConversationDoorOptions {
    /** The adapter's name — every refusal carries it. */
    readonly hostName: string;
    /** The path this door owns. Anything else is not ours. */
    readonly path: string;
    /** The ceilings this door declares, already defaulted. */
    readonly limits: ConversationLimits;
    /** This deployment's handshake dialect. Absent ⇒ raw headers and no session. */
    readonly readConversation?: (facts: HandshakeFacts) => ConversationHandshake;
    /** Where a new conversation goes. */
    readonly handler: ConversationHandler;
    /** Whether the host is still taking conversations — false after `close()`. */
    readonly accepting: () => boolean;
    /**
     * The host's door guard — Host and Origin (and the browser's Fetch
     * metadata) judged on the handshake, BEFORE the 101. Absent, no handshake is
     * judged, which is the conversation door as it was before the guard.
     */
    readonly guard?: DoorGuard;
    /** Told about every handshake this door refuses on the guard's or the session bound's word. */
    readonly onRefusal?: (refusal: HostRefusal) => void;
    /**
     * The host's sign-in cookie. Present: the cookie is stripped from the
     * conversation's headers, the handshake is verified BEFORE the 101, and a
     * socket admitted by a sign-in is re-checked before every inbound frame and
     * closed when the sign-in ends. Absent: the door as it was.
     */
    readonly signIn?: ResolvedHostSignIn;
}
/** What a handshake dialect may read. Deliberately the same shape a request wire gets, minus the body. */
export interface HandshakeFacts {
    /** Header names lower-cased. */
    readonly headers: Readonly<Record<string, string>>;
    /** The query string, already parsed — where a browser has to put things it cannot header. */
    readonly query: URLSearchParams;
}
/** A door: hand it upgrades, close it when you are done. */
export interface ConversationDoor {
    /**
     * Take one upgrade, or decline it. Returns whether this door claimed the
     * path — the caller decides what an unclaimed upgrade deserves.
     */
    handleUpgrade(request: IncomingMessage, socket: Duplex): boolean;
    /** End every live conversation politely, then resolve. */
    closeAll(reason: string): Promise<void>;
    /** How many conversations are open right now. */
    readonly liveCount: number;
}
export declare function conversationDoor(options: ConversationDoorOptions): ConversationDoor;
