import type { CapabilityUnavailableReason, DesktopRfbFactory, DesktopStreamCapability } from "@opengeni/sdk"; import { type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from "react"; import { type InteractionHostPlatform } from "../lib/host-platform.js"; import type { DesktopWebSocketFactory } from "../hooks/use-relay-frame-stream.js"; export type DesktopViewerProps = { /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */ capability: DesktopStreamCapability | null; /** * Initial control mode. Default false (watch). When the user flips * "Take control" the viewer drives input — but only if `capability.mode` * permits it (server-gated; a read-only deployment disables the toggle). * Pass a value to control it externally; omit to let the viewer own the state. */ interactive?: boolean | undefined; /** Render the built-in Watching ⇄ Take control toggle (default true). */ showControlToggle?: boolean | undefined; scaleViewport?: boolean | undefined; /** Custom RFB factory; otherwise enableDesktopViewer() supplies lazy noVNC. */ rfbFactory?: DesktopRfbFactory | undefined; /** Authenticated controller protocols for proxied RFB transports. */ webSocketProtocols?: string[] | undefined; /** Custom socket factory for the self-hosted `relay-frames` transport (tests). * Defaults to `new WebSocket(url)`. Mirrors `rfbFactory`. */ webSocketFactory?: DesktopWebSocketFactory | undefined; /** * Optional host-owned paste path. ComputerSession hosts use their native, * target-scoped clipboard action instead of relying on an RFB server to sync * ClientCutText into the graphical seat. Return true once the host accepted * the text. Without a host authority the viewer leaves paste untouched rather * than claiming success for an unreliable display-server clipboard heuristic. */ onPasteText?: ((text: string) => boolean) | undefined; /** OS behind the RFB seat. Used only to translate the host's primary shortcut * (Cmd on macOS, Ctrl elsewhere) to the target's primary modifier. */ targetPlatform?: "linux" | "macos" | "windows" | null | undefined; /** * @deprecated Desktop acknowledgment is automatic. Retained as an ignored * compatibility prop so existing embedders keep compiling. */ renderConsentGate?: ((onAccept: () => void, shared: boolean) => ReactNode) | undefined; /** * Called once when the viewer mounts. Hosts use this to engage the desktop * holder immediately when the Desktop tab opens. */ onActivate?: (() => void) | undefined; /** * Called automatically when the server requires acknowledgment of the * un-redacted (and possibly shared) pixel plane. The viewer shows its ordinary * opening state while this completes; no click-through gate is rendered. */ onAcknowledge?: (() => void | Promise) | undefined; /** * Whether the host has the viewer attach engaged. Drives the cold-state * behaviour: when watching, a cold-but-warmable lease AUTO-WARMS (and re-warms * when the box drains) instead of dead-ending. Mounting the viewer counts as * watching when this is omitted. */ watching?: boolean | undefined; /** * Request a (re)warm of the sandbox WITHOUT re-acknowledging (the consent has * already been recorded — only the box drained). The host wires this to * "engage the viewer attach + re-negotiate". Called automatically when a * watched desktop is found cold-but-warmable, and behind the manual retry on * the warming notice. Distinct from the automatic acknowledgment request. */ onWarm?: (() => void) | undefined; /** Shown when transport is null (headless backend / degraded / disabled). */ renderUnavailable?: ((reason: CapabilityUnavailableReason | null) => ReactNode) | undefined; /** Shown while the box is cold/warming (no live address yet). */ renderWarming?: (() => ReactNode) | undefined; /** Shown when the per-session viewer cap (429) was hit. */ renderViewerCap?: (() => ReactNode) | undefined; /** Surface the 429 cap state from `useSessionCapabilities().viewerCapReached`. */ viewerCapReached?: boolean | undefined; /** * Connect watchdog (ms): if a live url is present but the RFB hasn't connected * within this window, surface a "Couldn't connect" + Reconnect instead of an * eternal idle scrim. Default 13000. 0 disables the watchdog. */ connectTimeoutMs?: number | undefined; className?: string | undefined; }; /** * The desktop surface: a noVNC client connecting to the Channel-B scoped tunnel * URL from the capability doc. Owns the mount `
`, drives * `useDesktopStream` (SSR-safe lazy RFB), and renders a real * cold → warming → connecting → connected → error state machine with live * feedback (spinners, transitions) — never a dead black box with stale text. * * The read-only vs interactive decision is enforced server-first * (`capability.mode`): when the deployment advertises mode "interactive" the * viewer can TAKE CONTROL and drive the mouse & keyboard into the box's :0; a * "read-only" deployment disables the take-control affordance (graceful, with a * reason). * * Warming: a cold-but-warmable lease (`reason: "lease_cold"`) is NOT a dead end. * When the viewer is mounted, it asks the host to engage the desktop and records * any required acknowledgment automatically. A cold-but-warmable box then * (re)warms through `onWarm`; if it later drains to cold it re-warms. * Genuinely-unavailable surfaces (headless/policy/os/backend) keep a clear, * static unavailable notice. */ export declare function DesktopViewer({ capability, interactive, showControlToggle, scaleViewport, rfbFactory, webSocketProtocols, webSocketFactory, onPasteText, targetPlatform, onActivate, onAcknowledge, watching, onWarm, renderUnavailable, renderWarming, renderViewerCap, viewerCapReached, connectTimeoutMs, className, }: DesktopViewerProps): import("react").JSX.Element; type RfbKeyEvent = { keysym: number; code: string; down: boolean; }; /** Translate one host-primary chord into deterministic RFB events for the * target OS. noVNC otherwise treats a Mac Command key as remote Alt, which * makes ordinary Cmd+A/Z shortcuts silently wrong on Linux sandboxes. */ export declare function desktopPrimaryShortcut(event: Pick, "altKey" | "ctrlKey" | "key" | "metaKey" | "shiftKey" | "code">, hostPlatform: InteractionHostPlatform, targetPlatform: "linux" | "macos" | "windows"): RfbKeyEvent[] | "clipboard" | null; export {};