{"version":3,"sources":["../src/protocol.ts","../src/types.ts","../src/client.ts"],"names":["PartySocket"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Shared wire protocol for `@reactor-team/queue`.\n *\n * Both the PartyKit server (`@reactor-team/queue/server`) and the browser\n * client (`@reactor-team/queue`) import these types so the messages they\n * exchange over the WebSocket stay in lockstep. Nothing here depends on\n * PartyKit, the Reactor SDK, React, or the DOM — it is plain data.\n */\n\n/** Current protocol version. Bumped only on breaking wire changes. */\nexport const PROTOCOL_VERSION = 2 as const;\n\n/** Default PartyKit room id. A single room is the source of truth for one queue. */\nexport const DEFAULT_ROOM = \"reactor-queue\";\n\n/** Query-string key used to carry the stable per-browser id on connect. */\nexport const CLIENT_ID_QUERY_KEY = \"rqClientId\";\n\n/** Set to `1` on the WebSocket URL to open an admin connection (not queued). */\nexport const ADMIN_MODE_QUERY_KEY = \"rqAdmin\";\n\n/**\n * Default tunables. Every one of these is overridable from server config and/or\n * environment variables (see `@reactor-team/queue/server`).\n */\nexport const DEFAULTS = {\n  /** Max concurrent Reactor sessions (GPU ceiling). */\n  maxSessions: 1,\n  /** Members per session (default 1 = today's behavior; >1 when platform allows N). */\n  usersPerSession: 1,\n  /** Full session budget once a user has `claim()`ed their slot. */\n  sessionDurationMs: 120_000,\n  /**\n   * Grace window an admitted user gets to actually start (claim) their session\n   * before the slot is reclaimed. Prevents an idle admit from wasting a slot.\n   */\n  admissionGraceMs: 45_000,\n  /** How long before expiry to emit a `time_warning`. */\n  warningBeforeMs: 30_000,\n  /** Lifetime requested for each minted Reactor JWT. Deliberately short. */\n  tokenTtlSeconds: 60,\n  /**\n   * How often the server re-checks tracked live sessions against the Reactor\n   * API to catch sessions that ended without a clean `session_ended`/close.\n   */\n  pollIntervalMs: 15_000,\n  /** Client-side skew: refresh the JWT this long before it actually expires. */\n  tokenSkewMs: 10_000,\n} as const;\n\n/** Reactor session states that mean \"the slot is free again\". */\nexport const TERMINAL_SESSION_STATES = [\"CLOSED\", \"INACTIVE\"] as const;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Server → Client messages\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** You are waiting in line. `position` is 1-based. */\nexport interface QueuePositionMessage {\n  type: \"queue_position\";\n  position: number;\n  total: number;\n  active: number;\n  capacity: number;\n}\n\n/**\n * You reached the front and a capacity slot is reserved for you. No Reactor\n * session exists yet — the server creates it only when you `claim()`, so an\n * abandoned grace never leaves an orphaned GPU session. You have until the\n * admission grace expires to `claim()`.\n */\nexport interface AdmittedMessage {\n  type: \"admitted\";\n  active: number;\n  /** Total live users = maxSessions * usersPerSession. */\n  capacity: number;\n  /** ms the client has to `claim()` before the slot is reclaimed. */\n  graceMs: number;\n  /** Full session budget (ms) the client receives once it `claim()`s. For countdown UI. */\n  sessionDurationMs: number;\n}\n\n/**\n * Sent after `claim()`: the server has created (or reused) the Reactor session\n * and minted a WebRTC connection under it for this member. Attach with\n * `connect({ sessionId, connectionId })` — the server owns both, so the client\n * never creates or stops anything.\n */\nexport interface SessionReadyMessage {\n  type: \"session_ready\";\n  /** Reactor session id created by the server — pass to connect({ sessionId }). */\n  sessionId: string;\n  /**\n   * Server-minted WebRTC connection id for this member — pass to\n   * connect({ connectionId }). The server registered it under `sessionId`, so\n   * the client adopts it instead of registering its own.\n   */\n  connectionId: number;\n  /** Full session budget (ms). */\n  sessionDurationMs: number;\n  /** Unix epoch ms when the session ends. */\n  expiresAt: number;\n}\n\n/** A freshly minted, short-lived Reactor JWT. Sent on admission and on each `request_token`. */\nexport interface TokenMessage {\n  type: \"token\";\n  jwt: string;\n  /** Unix epoch seconds at which the JWT expires. */\n  expiresAt: number;\n}\n\n/** Your session is about to end. */\nexport interface TimeWarningMessage {\n  type: \"time_warning\";\n  secondsLeft: number;\n  /** Unix epoch ms when the session ends. */\n  expiresAt: number;\n}\n\n/** Your session ended (time ran out, or the server reclaimed the slot). */\nexport interface ExpiredMessage {\n  type: \"expired\";\n  reason: \"timeout\" | \"grace_timeout\" | \"server\";\n}\n\n/** You were refused entry. */\nexport interface RejectedMessage {\n  type: \"rejected\";\n  reason: \"already_connected\" | \"server_error\" | \"forbidden_origin\" | string;\n}\n\n/** A non-fatal error (e.g. token mint failed); the client may retry. */\nexport interface ErrorMessage {\n  type: \"error\";\n  message: string;\n}\n\nexport type ServerMessage =\n  | QueuePositionMessage\n  | AdmittedMessage\n  | SessionReadyMessage\n  | TokenMessage\n  | TimeWarningMessage\n  | ExpiredMessage\n  | RejectedMessage\n  | ErrorMessage;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Client → Server messages\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** \"I'm actually entering the demo\" — upgrades the grace window to the full session. */\nexport interface ClaimMessage {\n  type: \"claim\";\n}\n\n/** Ask for a fresh JWT. The server only answers if you currently hold a slot. */\nexport interface RequestTokenMessage {\n  type: \"request_token\";\n}\n\n/** The user ended the Reactor session from the client; free the slot now. */\nexport interface SessionEndedMessage {\n  type: \"session_ended\";\n}\n\n/** Leave the queue / release the slot without intending to rejoin. */\nexport interface LeaveMessage {\n  type: \"leave\";\n}\n\nexport type ClientMessage = ClaimMessage | RequestTokenMessage | SessionEndedMessage | LeaveMessage;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Admin mode (server → admin client)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Read-only server tunables included in every admin snapshot. */\nexport interface AdminConfigSnapshot {\n  maxSessions: number;\n  usersPerSession: number;\n  capacity: number;\n  model: string;\n  webrtcVersion: string;\n  sessionDurationMs: number;\n  admissionGraceMs: number;\n  warningBeforeMs: number;\n  tokenTtlSeconds: number;\n  pollIntervalMs: number;\n  coordinatorUrl: string;\n  apiVersion: number;\n  stopSessionsOnExpiry: boolean;\n  allowDuplicateConnections: boolean;\n  /** \"default\" = queue creates/stops sessions; \"custom\" = acquire/release overridden. */\n  sessionSource: \"default\" | \"custom\";\n}\n\n/** One person waiting in the FIFO queue. */\nexport interface AdminQueuedUserSnapshot {\n  connId: string;\n  /** 1-based position in line. */\n  position: number;\n  clientId: string | null;\n}\n\n/** One admitted member (may or may not have claimed yet). */\nexport interface AdminMemberSnapshot {\n  connId: string;\n  /** Reactor session id once claimed; null while still in grace (no session yet). */\n  sessionId: string | null;\n  /** Server-minted WebRTC connection id once claimed; null while still in grace. */\n  connectionId: number | null;\n  clientId: string | null;\n  claimed: boolean;\n  expiresAt: number;\n  msLeft: number;\n}\n\n/** One capacity slot and its member connection ids. */\nexport interface AdminSessionSnapshot {\n  /** Reactor session id, or null while the slot is reserved but unclaimed (no GPU session yet). */\n  sessionId: string | null;\n  members: string[];\n  createdAt: number;\n  msSinceCreated: number;\n}\n\n/** Full room state pushed to authenticated admin connections. */\nexport interface AdminSnapshotMessage {\n  type: \"admin_snapshot\";\n  at: number;\n  activeCount: number;\n  sessionCount: number;\n  config: AdminConfigSnapshot;\n  queue: AdminQueuedUserSnapshot[];\n  sessions: AdminSessionSnapshot[];\n  members: AdminMemberSnapshot[];\n}\n\n/** Admin WebSocket authenticated; snapshots follow on changes. */\nexport interface AdminReadyMessage {\n  type: \"admin_ready\";\n}\n\nexport interface AdminRejectedMessage {\n  type: \"admin_rejected\";\n  reason: \"admin_disabled\" | \"invalid_password\" | \"auth_required\" | \"forbidden_origin\";\n}\n\nexport interface AdminActionResultMessage {\n  type: \"admin_action_result\";\n  action: \"kick_member\" | \"kick_queued\" | \"close_session\";\n  ok: boolean;\n  message?: string;\n}\n\n/** Severity of an {@link AdminLogEntry}. Mirrors `console.log`/`warn`/`error`. */\nexport type AdminLogLevel = \"info\" | \"warn\" | \"error\";\n\n/**\n * One structured server event. The queue server emits these for every notable\n * thing that happens in a room — a user joining, an admission, a session being\n * created or closed, and crucially the **reason an API call failed** (e.g. a\n * Coordinator quota rejection, with its HTTP status and body in `data`). They\n * are streamed live to admins and kept in a bounded server-side ring buffer so\n * a freshly-connected admin sees recent history.\n */\nexport interface AdminLogEntry {\n  /** Stable unique id (also usable as a React key). */\n  id: string;\n  /** Unix epoch ms when the event happened. */\n  at: number;\n  level: AdminLogLevel;\n  /** Machine-readable event code, e.g. `\"user_admitted\"`, `\"session_create_failed\"`. */\n  event: string;\n  /** Human-readable, already-formatted summary line. */\n  message: string;\n  /** The connection this event concerns, when applicable. */\n  connId?: string;\n  /** The Reactor session this event concerns, when applicable. */\n  sessionId?: string;\n  /** Extra structured context (HTTP status, response body, reason, …). */\n  data?: Record<string, unknown>;\n}\n\n/** A single new log line, pushed live to authenticated admins as it happens. */\nexport interface AdminLogMessage {\n  type: \"admin_log\";\n  entry: AdminLogEntry;\n}\n\n/** Recent log history (oldest → newest), sent once right after admin auth. */\nexport interface AdminLogHistoryMessage {\n  type: \"admin_log_history\";\n  entries: AdminLogEntry[];\n}\n\nexport type AdminServerMessage =\n  | AdminReadyMessage\n  | AdminRejectedMessage\n  | AdminSnapshotMessage\n  | AdminActionResultMessage\n  | AdminLogMessage\n  | AdminLogHistoryMessage;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Admin mode (admin client → server)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** First message on an admin connection; password must match `RQ_ADMIN_PASSWORD`. */\nexport interface AdminAuthMessage {\n  type: \"admin_auth\";\n  password: string;\n}\n\n/** Remove a member from their session and free capacity (same as forced expiry). */\nexport interface AdminKickMemberMessage {\n  type: \"admin_kick_member\";\n  connId: string;\n}\n\n/** Drop a still-waiting connection from the queue and close its socket. */\nexport interface AdminKickQueuedMessage {\n  type: \"admin_kick_queued\";\n  connId: string;\n}\n\n/** Stop the Reactor session and evict all members. */\nexport interface AdminCloseSessionMessage {\n  type: \"admin_close_session\";\n  sessionId: string;\n}\n\n/** Request a fresh snapshot (also sent automatically on room changes). */\nexport interface AdminRefreshMessage {\n  type: \"admin_refresh\";\n}\n\nexport type AdminClientMessage =\n  | AdminAuthMessage\n  | AdminKickMemberMessage\n  | AdminKickQueuedMessage\n  | AdminCloseSessionMessage\n  | AdminRefreshMessage;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Narrowing parse for an inbound server message. Returns null on garbage. */\nexport function parseServerMessage(raw: string): ServerMessage | null {\n  try {\n    const msg = JSON.parse(raw) as ServerMessage;\n    return typeof msg?.type === \"string\" ? msg : null;\n  } catch {\n    return null;\n  }\n}\n\n/** Narrowing parse for an inbound client message. Returns null on garbage. */\nexport function parseClientMessage(raw: string): ClientMessage | null {\n  try {\n    const msg = JSON.parse(raw) as ClientMessage;\n    if (typeof msg?.type !== \"string\") return null;\n    if (msg.type.startsWith(\"admin_\")) return null;\n    return msg;\n  } catch {\n    return null;\n  }\n}\n\n/** Parse an admin client message. Returns null on garbage or non-admin types. */\nexport function parseAdminClientMessage(raw: string): AdminClientMessage | null {\n  try {\n    const msg = JSON.parse(raw) as AdminClientMessage;\n    if (typeof msg?.type !== \"string\" || !msg.type.startsWith(\"admin_\")) return null;\n    return msg;\n  } catch {\n    return null;\n  }\n}\n\n/** Parse a server message sent to an admin connection. */\nexport function parseAdminServerMessage(raw: string): AdminServerMessage | null {\n  try {\n    const msg = JSON.parse(raw) as AdminServerMessage;\n    if (typeof msg?.type !== \"string\" || !msg.type.startsWith(\"admin_\")) return null;\n    return msg;\n  } catch {\n    return null;\n  }\n}\n","/** Where the user is in the queue lifecycle. */\nexport type QueuePhase =\n  /** Not connected and not trying to. */\n  | \"idle\"\n  /** Socket opening / waiting for the first server message. */\n  | \"connecting\"\n  /** In line, not yet at the front. */\n  | \"queued\"\n  /** At the front, capacity slot reserved — call `claim()` to enter. No session yet. */\n  | \"admitted\"\n  /** Claimed; waiting for the server to create the session and send `session_ready`. */\n  | \"starting\"\n  /** Session is ready (`sessionId` set); attach with the SDK. */\n  | \"active\"\n  /** Time ran out or the slot was reclaimed by the server. */\n  | \"expired\"\n  /** Refused entry (e.g. duplicate tab). */\n  | \"rejected\"\n  /** Socket closed without reaching a terminal phase. */\n  | \"disconnected\";\n\n/** Immutable snapshot of the queue client. Re-emitted on every change. */\nexport interface QueueState {\n  phase: QueuePhase;\n  /** 1-based position in line; 0 when not queued. */\n  position: number;\n  /** Total people in line. */\n  total: number;\n  /** Sessions currently active across all users. */\n  active: number;\n  /** Total live users the server allows (maxSessions × usersPerSession). */\n  capacity: number;\n  /** Current short-lived Reactor JWT, or null. */\n  token: string | null;\n  /** Unix epoch seconds at which `token` expires. */\n  tokenExpiresAt: number | null;\n  /** Unix epoch ms at which the user's session ends (known after admit/claim). */\n  sessionEndsAt: number | null;\n  /** Full session budget (ms) the slot grants after claim; null until admitted. */\n  sessionDurationMs: number | null;\n  /** Seconds left as of the last `time_warning`, else null. */\n  secondsLeft: number | null;\n  /** Reactor session id from the server (set on `session_ready`). */\n  sessionId: string | null;\n  /** Server-minted WebRTC connection id (set on `session_ready`); pass with sessionId. */\n  connectionId: number | null;\n  /** Reason for the most recent rejection/expiry/error, if any. */\n  reason: string | null;\n}\n\nexport const INITIAL_STATE: QueueState = {\n  phase: \"idle\",\n  position: 0,\n  total: 0,\n  active: 0,\n  capacity: 0,\n  token: null,\n  tokenExpiresAt: null,\n  sessionEndsAt: null,\n  sessionDurationMs: null,\n  secondsLeft: null,\n  sessionId: null,\n  connectionId: null,\n  reason: null,\n};\n\nexport interface ReactorQueueClientOptions {\n  /** PartyKit host, e.g. `my-app.username.partykit.dev` or `127.0.0.1:1999` for dev. */\n  host: string;\n  /** Room id. Must match the server. Defaults to the protocol default room. */\n  room?: string;\n  /** PartyKit party (server binding) name. Defaults to `\"main\"`. */\n  party?: string;\n  /** Stable per-browser id; auto-generated + persisted in localStorage if omitted. */\n  clientId?: string;\n  /** Connect immediately on construction. Default false (the React provider sets this). */\n  autoConnect?: boolean;\n  /** Refresh the JWT this many ms before it expires. */\n  tokenSkewMs?: number;\n  /** How long `getJwt()` waits for a fresh token before rejecting. */\n  tokenRequestTimeoutMs?: number;\n  /** Auto re-join this many ms after a `rejected` (e.g. duplicate tab clears). 0 disables. */\n  retryRejectedMs?: number;\n}\n","import PartySocket from \"partysocket\";\nimport {\n  CLIENT_ID_QUERY_KEY,\n  DEFAULTS,\n  DEFAULT_ROOM,\n  parseServerMessage,\n  type ClientMessage,\n} from \"./protocol\";\nimport { INITIAL_STATE, type QueueState, type ReactorQueueClientOptions } from \"./types\";\n\nconst CLIENT_ID_STORAGE_KEY = \"reactor-queue:client-id\";\n\nfunction persistentClientId(): string {\n  // Feature-detect rather than just checking `typeof localStorage`. Node 25\n  // unflagged Web Storage and, without `--localstorage-file`, exposes a\n  // `localStorage` global whose methods are all undefined, so an SSR render\n  // under modern Node hits \"localStorage.getItem is not a function\". Node 26\n  // goes further and throws a DOMException on any access to the global, which\n  // is why the try/catch wraps even the `typeof` checks. It also covers Safari\n  // private mode (setItem throws) and disabled storage.\n  try {\n    if (\n      typeof localStorage === \"undefined\" ||\n      typeof localStorage.getItem !== \"function\" ||\n      typeof localStorage.setItem !== \"function\"\n    ) {\n      return cryptoRandomId();\n    }\n    let id = localStorage.getItem(CLIENT_ID_STORAGE_KEY);\n    if (!id) {\n      id = cryptoRandomId();\n      localStorage.setItem(CLIENT_ID_STORAGE_KEY, id);\n    }\n    return id;\n  } catch {\n    return cryptoRandomId();\n  }\n}\n\nfunction cryptoRandomId(): string {\n  if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) return crypto.randomUUID();\n  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\ntype Listener = (state: QueueState) => void;\n\ninterface PendingTokenRequest {\n  resolve: (jwt: string) => void;\n  reject: (err: Error) => void;\n  timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Framework-agnostic queue client. Manages one PartyKit WebSocket, tracks queue\n * state, and exposes a {@link ReactorQueueClient.getJwt} resolver that hands a\n * fresh short-lived Reactor JWT to the Reactor SDK on demand.\n *\n * It is intentionally decoupled from `@reactor-team/js-sdk`: you wire the two\n * together by passing `getJwt` to the SDK and `connectOptions.sessionId` from\n * {@link ReactorQueueClient.getState}'s `sessionId` (set on admission).\n */\nexport class ReactorQueueClient {\n  private readonly opts: Required<Omit<ReactorQueueClientOptions, \"clientId\" | \"party\">> & {\n    clientId: string;\n    party?: string;\n  };\n\n  private socket: PartySocket | null = null;\n  private retryTimer: ReturnType<typeof setTimeout> | null = null;\n  private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n  private destroyed = false;\n\n  private state: QueueState = { ...INITIAL_STATE };\n  private listeners = new Set<Listener>();\n  private pendingToken: PendingTokenRequest | null = null;\n\n  constructor(options: ReactorQueueClientOptions) {\n    this.opts = {\n      host: options.host,\n      room: options.room ?? DEFAULT_ROOM,\n      party: options.party,\n      clientId: options.clientId ?? persistentClientId(),\n      autoConnect: options.autoConnect ?? false,\n      tokenSkewMs: options.tokenSkewMs ?? DEFAULTS.tokenSkewMs,\n      tokenRequestTimeoutMs: options.tokenRequestTimeoutMs ?? 10_000,\n      retryRejectedMs: options.retryRejectedMs ?? 3_000,\n    };\n    if (this.opts.autoConnect) this.connect();\n  }\n\n  // ── public state API ──────────────────────────────────────────────────────\n\n  getState(): QueueState {\n    return this.state;\n  }\n\n  subscribe(listener: Listener): () => void {\n    this.listeners.add(listener);\n    listener(this.state);\n    return () => this.listeners.delete(listener);\n  }\n\n  private setState(patch: Partial<QueueState>): void {\n    this.state = { ...this.state, ...patch };\n    for (const l of this.listeners) l(this.state);\n  }\n\n  // ── connection control ──────────────────────────────────────────────────\n\n  connect(): void {\n    if (this.destroyed) return;\n    this.teardownSocket();\n    this.clearRetry();\n    this.setState({\n      ...INITIAL_STATE,\n      phase: \"connecting\",\n      // keep concurrency hints across reconnects so the UI doesn't flicker to 0\n      capacity: this.state.capacity,\n    });\n\n    const socket = new PartySocket({\n      host: this.opts.host,\n      room: this.opts.room,\n      party: this.opts.party,\n      query: { [CLIENT_ID_QUERY_KEY]: this.opts.clientId },\n    });\n    this.socket = socket;\n\n    // No app-level heartbeat: the PartyKit platform tracks connection liveness\n    // (and fires the server's onClose on disconnect) even while the room is\n    // hibernated. Heartbeats would wake the hibernated room on every tick.\n    socket.addEventListener(\"message\", (evt) => this.handleMessage(String(evt.data)));\n    socket.addEventListener(\"close\", () => this.handleClose());\n  }\n\n  /**\n   * Leave the queue / release the slot and do not auto-rejoin. Returns to\n   * `idle` — from the SDK's perspective leaving and never-having-joined are the\n   * same state; the app decides whether to show a \"rejoin?\" prompt. The cached\n   * token is intentionally kept so any in-flight SDK cleanup (e.g. its\n   * `DELETE /sessions`) can still resolve a JWT during teardown.\n   */\n  leave(): void {\n    this.send({ type: \"leave\" });\n    this.clearRetry();\n    this.clearRefresh();\n    this.teardownSocket();\n    this.setState({ phase: \"idle\" });\n  }\n\n  /** Re-enter the line (e.g. after expiry). */\n  rejoin(): void {\n    this.connect();\n  }\n\n  /**\n   * \"I'm entering the demo now.\" The server creates the Reactor session and\n   * replies with `session_ready` (carrying `sessionId`). Until then we sit in\n   * `starting` so the UI can show a spinner; we do not have a `sessionId` yet.\n   */\n  claim(): void {\n    this.send({ type: \"claim\" });\n    if (this.state.phase === \"admitted\") {\n      this.setState({ phase: \"starting\", secondsLeft: null });\n    }\n  }\n\n  /**\n   * The Reactor session ended client-side (e.g. the user quit the turn): free\n   * the slot so the queue slides, and return to `idle` so the app can show its\n   * menu or a \"play again\" prompt. From an in-session phase this mirrors\n   * {@link leave} — tear the socket down and reset to `idle` — but it sends\n   * `session_ended` (not `leave`) so the server admits the next person, and it\n   * drops the token: unlike `leave`, the session is already over, so no\n   * in-flight SDK `DELETE /sessions` needs a JWT.\n   *\n   * Without the phase reset the client would be wedged in `active` with no\n   * `sessionId` (\"phantom-active\"), a state nothing else recovers from. Re-enter\n   * the line with {@link rejoin} (the server only re-queues on connect).\n   *\n   * This also doubles as unmount cleanup, so it can fire *after* the server has\n   * already moved us to a terminal phase (`expired`/`rejected`) or after\n   * `leave()` set `idle`. In those cases we only clear the session fields and\n   * leave the existing phase — and the already-closed socket — untouched.\n   */\n  endSession(): void {\n    this.send({ type: \"session_ended\" });\n    const inSession = this.state.phase === \"active\" || this.state.phase === \"starting\";\n    if (inSession) {\n      this.clearRetry();\n      this.clearRefresh();\n      this.teardownSocket();\n    }\n    this.setState({\n      phase: inSession ? \"idle\" : this.state.phase,\n      sessionId: null,\n      connectionId: null,\n      token: null,\n      tokenExpiresAt: null,\n    });\n  }\n\n  /** Tear everything down. The instance is unusable afterwards. */\n  destroy(): void {\n    this.destroyed = true;\n    this.failPending(new Error(\"client destroyed\"));\n    this.teardownSocket();\n    this.clearRetry();\n    this.clearRefresh();\n    this.listeners.clear();\n  }\n\n  // ── JWT resolver for the Reactor SDK ──────────────────────────────────────\n\n  /**\n   * Resolver compatible with the Reactor SDK's `getJwt` option. Returns the\n   * cached token while it's fresh, otherwise asks the server for a new one over\n   * the WebSocket and resolves when it arrives.\n   *\n   * Bound as an arrow so it can be passed directly: `getJwt={queue.getJwt}`.\n   */\n  getJwt = async (): Promise<string> => {\n    const now = Date.now();\n    const fresh =\n      this.state.token &&\n      this.state.tokenExpiresAt &&\n      this.state.tokenExpiresAt * 1000 - now > this.opts.tokenSkewMs;\n    if (fresh) return this.state.token as string;\n\n    // Socket gone (teardown / leave / brief reconnect): hand over the last\n    // token we hold instead of throwing, so the SDK can finish in-flight\n    // cleanup like DELETE /sessions during disconnect.\n    if (this.state.token && (!this.socket || this.socket.readyState !== WebSocket.OPEN)) {\n      return this.state.token;\n    }\n    return this.requestToken();\n  };\n\n  private requestToken(): Promise<string> {\n    if (this.pendingToken) {\n      return new Promise<string>((resolve, reject) => {\n        const prev = this.pendingToken!;\n        this.pendingToken = {\n          resolve: (jwt) => {\n            prev.resolve(jwt);\n            resolve(jwt);\n          },\n          reject: (err) => {\n            prev.reject(err);\n            reject(err);\n          },\n          timer: prev.timer,\n        };\n      });\n    }\n\n    return new Promise<string>((resolve, reject) => {\n      const socket = this.socket;\n      if (!socket) {\n        reject(new Error(\"queue is not connected; cannot mint token\"));\n        return;\n      }\n      const timer = setTimeout(() => {\n        this.pendingToken = null;\n        reject(new Error(\"timed out waiting for a queue token\"));\n      }, this.opts.tokenRequestTimeoutMs);\n      this.pendingToken = { resolve, reject, timer };\n\n      // The SDK may call getJwt a hair before the socket finishes opening (or\n      // during a brief reconnect). Send now if open, otherwise on the next open.\n      if (socket.readyState === WebSocket.OPEN) {\n        this.send({ type: \"request_token\" });\n      } else {\n        socket.addEventListener(\"open\", () => this.send({ type: \"request_token\" }), { once: true });\n      }\n    });\n  }\n\n  /**\n   * Keep the cached token warm: refresh it shortly before it expires so the SDK\n   * never has to block on a round-trip mid-session. Best-effort; failures are\n   * swallowed because the reactive {@link getJwt} path is the real guarantee.\n   */\n  private scheduleTokenRefresh(): void {\n    this.clearRefresh();\n    if (!this.state.tokenExpiresAt) return;\n    const lead = this.state.tokenExpiresAt * 1000 - Date.now() - this.opts.tokenSkewMs;\n    this.refreshTimer = setTimeout(\n      () => {\n        if (this.destroyed) return;\n        if ([\"admitted\", \"starting\", \"active\"].includes(this.state.phase)) {\n          this.requestToken().catch(() => {\n            /* reactive getJwt will retry when the SDK next needs a token */\n          });\n        }\n      },\n      Math.max(1_000, lead)\n    );\n  }\n\n  private clearRefresh(): void {\n    if (this.refreshTimer) {\n      clearTimeout(this.refreshTimer);\n      this.refreshTimer = null;\n    }\n  }\n\n  private resolvePending(jwt: string): void {\n    if (!this.pendingToken) return;\n    clearTimeout(this.pendingToken.timer);\n    this.pendingToken.resolve(jwt);\n    this.pendingToken = null;\n  }\n\n  private failPending(err: Error): void {\n    if (!this.pendingToken) return;\n    clearTimeout(this.pendingToken.timer);\n    this.pendingToken.reject(err);\n    this.pendingToken = null;\n  }\n\n  // ── inbound message handling ──────────────────────────────────────────────\n\n  private handleMessage(raw: string): void {\n    const msg = parseServerMessage(raw);\n    if (!msg) return;\n\n    switch (msg.type) {\n      case \"queue_position\":\n        this.setState({\n          phase: \"queued\",\n          position: msg.position,\n          total: msg.total,\n          active: msg.active,\n          capacity: msg.capacity,\n        });\n        break;\n\n      case \"admitted\":\n        this.setState({\n          // Stay in active/starting if a late admitted arrives; otherwise the\n          // user must claim to get a session.\n          phase:\n            this.state.phase === \"active\" || this.state.phase === \"starting\"\n              ? this.state.phase\n              : \"admitted\",\n          position: 0,\n          total: 0,\n          active: msg.active,\n          capacity: msg.capacity,\n          sessionEndsAt: Date.now() + msg.graceMs,\n          sessionDurationMs: msg.sessionDurationMs,\n          secondsLeft: null,\n        });\n        break;\n\n      case \"session_ready\":\n        this.setState({\n          phase: \"active\",\n          sessionId: msg.sessionId,\n          connectionId: msg.connectionId,\n          sessionEndsAt: msg.expiresAt,\n          sessionDurationMs: msg.sessionDurationMs,\n          secondsLeft: null,\n        });\n        break;\n\n      case \"token\":\n        this.setState({ token: msg.jwt, tokenExpiresAt: msg.expiresAt });\n        this.resolvePending(msg.jwt);\n        this.scheduleTokenRefresh();\n        break;\n\n      case \"time_warning\":\n        this.setState({ secondsLeft: msg.secondsLeft, sessionEndsAt: msg.expiresAt });\n        break;\n\n      case \"expired\":\n        this.failPending(new Error(\"session expired\"));\n        this.clearRefresh();\n        // Keep the token (it's a short-lived JWT, not session-scoped) so any\n        // in-flight SDK call can still resolve one; it ages out on its own.\n        this.setState({\n          phase: \"expired\",\n          sessionId: null,\n          connectionId: null,\n          reason: msg.reason,\n        });\n        this.teardownSocket();\n        break;\n\n      case \"rejected\":\n        this.failPending(new Error(`rejected: ${msg.reason}`));\n        this.setState({ phase: \"rejected\", reason: msg.reason });\n        this.teardownSocket();\n        this.scheduleRetry();\n        break;\n\n      case \"error\":\n        this.setState({ reason: msg.message });\n        this.failPending(new Error(msg.message));\n        break;\n    }\n  }\n\n  private handleClose(): void {\n    // Only downgrade phase if we weren't already in a terminal state.\n    if ([\"connecting\", \"queued\", \"admitted\", \"starting\", \"active\"].includes(this.state.phase)) {\n      this.setState({ phase: \"disconnected\" });\n    }\n  }\n\n  // ── internals ─────────────────────────────────────────────────────────────\n\n  private send(msg: ClientMessage): void {\n    if (this.socket && this.socket.readyState === WebSocket.OPEN) {\n      this.socket.send(JSON.stringify(msg));\n    }\n  }\n\n  private teardownSocket(): void {\n    if (this.socket) {\n      try {\n        this.socket.close();\n      } catch {\n        /* already closing */\n      }\n      this.socket = null;\n    }\n  }\n\n  private scheduleRetry(): void {\n    if (this.opts.retryRejectedMs <= 0 || this.destroyed) return;\n    this.clearRetry();\n    this.retryTimer = setTimeout(() => this.connect(), this.opts.retryRejectedMs);\n  }\n\n  private clearRetry(): void {\n    if (this.retryTimer) {\n      clearTimeout(this.retryTimer);\n      this.retryTimer = null;\n    }\n  }\n}\n"]}