import type { Output, SignalValue, StatusJson } from "@mochabug/adapt-core"; import type { AdaptTheme } from "./theme.js"; export type { Output, SignalValue, StatusJson } from "@mochabug/adapt-core"; export type { AdaptTheme } from "./theme.js"; /** * Options for session persistence across page refreshes. */ export interface PersistOptions { /** Storage type: 'session' (default) uses sessionStorage, 'local' uses localStorage. */ storage?: "session" | "local"; /** Max TTL in seconds (default: 3600). Capped by the session's own expiresAt. */ ttl?: number; /** Custom storage key suffix to distinguish multiple embeds of the same automation. */ key?: string; } /** * Discriminated union for fork display configuration. * * - `"side-by-side"`: Shows main and fork iframes side by side * - `"dialog"`: Shows fork in a floating group */ export type ForkDisplay = { mode: "side-by-side"; split?: number; } | { mode: "dialog"; }; /** * Custom label overrides for the Cap.js widget. * All fields are optional — unspecified labels use Cap.js defaults. * * These map to `data-cap-i18n-*` attributes on the underlying ``. * * @example * ```typescript * i18n: { * initialState: "Verify you're human", * verifyingLabel: "Checking...", * solvedLabel: "Verified!", * errorLabel: "Failed" * } * ``` */ export interface CapWidgetI18n { /** Text shown while the proof-of-work is being solved. Default: `"Verifying..."` */ verifyingLabel?: string; /** Text shown before the user clicks the checkbox. Default: `"Verify you're human"` */ initialState?: string; /** Text shown after a successful solve. Default: `"You're a human"` */ solvedLabel?: string; /** Text shown when solving fails. Default: `"Error. Try again."` */ errorLabel?: string; /** Text shown for the blocked-user troubleshooting link. Default: `"Troubleshoot"` */ troubleshootingLabel?: string; /** Text shown when WebAssembly is unavailable. */ wasmDisabled?: string; /** ARIA label for the initial interactive state. */ verifyAriaLabel?: string; /** ARIA label while verification is running. */ verifyingAriaLabel?: string; /** ARIA label after successful verification. */ verifiedAriaLabel?: string; /** Validation text when the widget is required in a form. */ requiredLabel?: string; /** ARIA label when an error occurs. */ errorAriaLabel?: string; } /** * Options for the Cap.js widget when used via `AdaptWebClient` with `requiresChallenge: true`. * * @example * ```typescript * capWidgetOptions: { * workerCount: 4, * i18n: { initialState: "Click to verify" } * } * ``` */ export interface CapWidgetOptions { /** Custom text labels for the widget. */ i18n?: CapWidgetI18n; /** Number of Web Workers for solving. @default navigator.hardwareConcurrency || 8 */ workerCount?: number; /** Name of the hidden token input generated by Cap.js. @default `"cap-token"` */ hiddenFieldName?: string; /** Custom URL for Cap.js's troubleshooting link when instrumentation blocks a user. */ troubleshootingUrl?: string; /** Disable Cap.js haptic feedback for this widget. */ disableHaptics?: boolean; } /** * Challenge information returned from createChallenge */ export interface ChallengeInfo { /** Number of challenges to solve */ count: number; /** Salt size in bytes */ size: number; /** Difficulty target (hex prefix length) */ difficulty: number; /** When the challenge expires */ expires: Date; /** Hex-encoded seed for Cap.js PRNG derivation (64 chars) */ token: string; /** Encrypted verification token for server */ verificationToken: string; /** Optional Cap.js instrumentation payload to execute before redeeming */ instrumentation?: string; } export interface RedeemChallengeOptions { /** Successful Cap.js instrumentation result sent as `instr` by the widget */ instrumentation?: Record; /** True when Cap.js reports that instrumentation detected an automated browser */ instrumentationBlocked?: boolean; /** True when Cap.js reports that instrumentation timed out */ instrumentationTimeout?: boolean; } /** * Redeemed challenge returned from redeemChallenge */ export interface RedeemedChallenge { /** The redeemed token to pass to startSession/runSession */ token: string; /** When the redeemed token expires */ expires: Date; } /** * Configuration options for AdaptWebClient * * @example * ```typescript * // Basic usage * const client = new AdaptWebClient({ * container: 'my-container', * automationId: 'automation-123', * authToken: 'optional-auth-token' * }); * * // With fork display options * const client = new AdaptWebClient({ * container: 'my-container', * automationId: 'automation-123', * forkDisplay: { mode: 'dialog' } * }); * * // With custom styling * const client = new AdaptWebClient({ * container: 'my-container', * automationId: 'automation-123', * classNames: { * root: 'my-custom-class', * iframe: 'my-iframe-class' * }, * styles: { * height: '600px' * } * }); * ``` */ export interface AdaptWebClientOptions { /** * HTML element or ID of the element where the client will render. * The element must exist in the DOM before creating the client. * * @example * ```html *
* ``` * ```typescript * container: 'my-container' * // or * container: document.getElementById('my-container') * ``` */ container: string | HTMLElement; /** * Automation ID to connect to. * This ID is provided by the Adapt automation platform. */ automationId: string; /** * Pre-created session token from server-side session creation. * When provided, skips client-side startSession/inheritSession calls entirely. * * Use this for SSR frameworks (Next.js, Nuxt, etc.) where the authToken * should remain on the server for security. * * @example * ```typescript * // Server-side (e.g., Next.js API route or server component) * const { token } = await startSession({ id: 'automation-123' }, authToken); * * // Client-side * const client = new AdaptWebClient({ * container: 'my-container', * automationId: 'automation-123', * sessionToken: token // Use pre-created session * }); * ``` */ sessionToken?: string; /** * Authentication token for starting a new session client-side. * Use this for SPA applications where the token is available on the frontend. * * Ignored if sessionToken is provided. */ authToken?: string; /** * The transmitter to start from (optional). * Use this when your automation has multiple transmitters and you want to * specify which one to start from. Defaults to the first transmitter if not specified. * * Ignored if sessionToken is provided. * * @example * ```typescript * transmitter: 'my-transmitter-name' * ``` */ transmitter?: string; /** * Initial signals to pass to the transmitter. * The signals must align with the transmitter's JTD schema. * * Ignored if sessionToken is provided. * * @example * ```typescript * signals: { * 'input': { mimeType: 'text/plain', data: btoa('Hello World') } * } * ``` */ signals?: { [key: string]: SignalValue; }; /** * Pre-solved challenge token for automations requiring proof-of-work. * When provided, skips the Cap.js widget and uses this token directly. * * Ignored if sessionToken is provided. * * @example * ```typescript * // From server-side pre-solving * challengeToken: presolvedToken * ``` */ challengeToken?: string; /** * Show a Cap.js proof-of-work widget before starting the session. * The widget handles challenge creation, solving, and redemption automatically. * It is centered inside the container and auto-removes after a successful solve. * * **Styling:** Customize via `--mb-adapt-cap-*` CSS variables on the `.mb-adapt` root. * Dark mode colors are applied automatically when `darkMode: true`. * Never use `::part()` selectors on the inner `` — this breaks * Cap.js's Shadow DOM animations (checkbox, spinner, label transitions). * * Ignored when `sessionToken` or `challengeToken` is provided. * * @default false */ requiresChallenge?: boolean; /** * Options for the Cap.js widget (labels and worker count). * Only used when `requiresChallenge` is `true`. * * @example * ```typescript * capWidgetOptions: { * workerCount: 4, * i18n: { initialState: "Verify you're human" } * } * ``` */ capWidgetOptions?: CapWidgetOptions; /** * Token to inherit an existing session. * When provided, calls inheritSession() instead of startSession(). * * Ignored if sessionToken is provided. */ inheritToken?: string; /** * Auto-parse inherit token from URL. * Token is removed from URL after parsing (via history.replaceState). * Takes precedence over inheritToken if token found in URL. * * @example * ```typescript * // Parse from hash: example.com#mb_session=xxx * inheritFrom: { hash: 'mb_session' } * * // Parse from query param: example.com?token=xxx * inheritFrom: { param: 'token' } * ``` */ inheritFrom?: { hash: string; } | { param: string; }; /** * How to display fork URLs when automation creates sub-sessions. * * - `{ mode: "side-by-side", split?: number }`: Shows main and fork tabs docked to the right * - `{ mode: "dialog" }`: Shows fork tabs in a floating group * * @default { mode: "side-by-side" } * @example * ```typescript * // Side-by-side with 60/40 split * forkDisplay: { mode: "side-by-side", split: 60 } * * // Dialog (floating group) * forkDisplay: { mode: "dialog" } * ``` */ forkDisplay?: ForkDisplay; /** * Whether users can pop out / float panels. * When false, hides "Pop out" buttons and blocks user-initiated floating. * Does NOT affect `forkDisplay="dialog"` — that's the developer's chosen initial placement. * * @default true */ allowFloating?: boolean; /** * Whether users can minimize panels (both docked and floating). * When false, hides "Minimize" buttons on docked group headers and floating toolbars. * * @default true */ allowMinimize?: boolean; /** * Whether users can maximize panels (both docked and floating). * When false, hides "Maximize" buttons on docked group headers and floating toolbars. * * @default true */ allowMaximize?: boolean; /** * Whether users can dock floating panels back to the main layout. * When false, hides "Dock" button on floating toolbar and minimized tabs. * Does NOT affect `forkDisplay="side-by-side"` — that's the developer's chosen initial placement. * * @default true */ allowDocking?: boolean; /** * Whether users can create splits inside floating dialog overlays by dragging tabs. * When false, tabs dragged inside a floating overlay will merge into existing groups * instead of creating new split panes. * * @default true */ allowDialogDocking?: boolean; /** * Auto-resize floating overlays based on iframe content height. * When true, floating overlays auto-resize based on iframe content (same mechanism * as `autoResizing` for the main panel, but targeting floating overlays). * Independent of the main `autoResizing` flag. * * @default false */ floatingAutoResize?: boolean; /** * Enable dark mode. When changed, sends message to iframes via postMessage. * * @default false */ darkMode?: boolean; /** * Auto-resize iframe height based on content. * When true, listens for resize messages from iframes and adjusts height. * Width remains at 100%. * * @default false */ autoResizing?: boolean; /** * Prompt the user before closing panels that haven't completed, and before * navigating away from the page while a session is active. * * When true: * - Closing a fork tab that isn't marked complete shows a styled confirmation dialog. * - Navigating away (or refreshing) while a session is running triggers * the browser's native `beforeunload` prompt. * * The confirmation dialog can be styled via CSS variables on `.mb-adapt`: * --mb-adapt-confirm-overlay-bg, --mb-adapt-confirm-bg, --mb-adapt-confirm-text, * --mb-adapt-confirm-btn-bg, --mb-adapt-confirm-btn-text, * --mb-adapt-confirm-cancel-bg, --mb-adapt-confirm-cancel-text * * @default false */ confirmOnClose?: boolean; /** * Callback invoked when session state changes. * Receives the status and optional fork identifier. * * @param status - Current session status (e.g., 'STATUS_RUNNING', 'STATUS_COMPLETED') * @param fork - Optional fork identifier (present for forked sessions) * * @example * ```typescript * onSession: (status, fork) => { * console.log('Session status:', status); * if (fork) console.log('Fork:', fork); * } * ``` */ onSession?: (status: StatusJson, fork?: string) => void; /** * Callback invoked when automation produces output. * Receives Output objects containing logs, data, or other results. * * @example * ```typescript * onOutput: (output) => { * console.log('Output:', output); * } * ``` */ onOutput?: (output: Output) => void; /** * Callback invoked when a fork becomes visually active or inactive. * Active means the fork is taking up visual space (side-by-side non-collapsed, or dialog open). * * @param active - Whether a fork is visually active */ onForkActive?: (active: boolean) => void; /** * Callback invoked when the automation encounters an error. * Receives a structured error with classification, message, and retriability. * Fires for both init-phase errors (e.g. invalid inheritToken) and streaming errors. * * @example * ```typescript * onError: (error) => { * if (error.retriable) { * console.log('Retriable error:', error.kind, error.message); * } else { * console.error('Fatal error:', error.kind, error.message); * } * } * ``` */ onError?: (error: AdaptError) => void; /** * Custom CSS class names to override default styling. * All properties are optional. Unspecified classes use defaults. * * @example * ```typescript * // Using custom CSS classes * classNames: { * root: 'my-adapt-container', * iframe: 'my-iframe' * } * ``` * * @example * ```typescript * // Using Tailwind classes * classNames: { * root: 'w-full h-screen', * iframe: 'rounded-lg shadow-xl' * } * ``` */ classNames?: { /** Root container class. Default: `'mb-adapt'` */ root?: string; /** Iframe element class. Default: `'mb-adapt__iframe'` */ iframe?: string; /** Status message overlay container. Default: `'mb-adapt__status-message'` */ statusMessage?: string; /** Status card inside the overlay. Default: `'mb-adapt__status-card'` */ statusCard?: string; }; /** * Theme configuration for semantic theming. * Applies CSS custom properties to the root element based on semantic tokens. * Use `theme.vars` for direct per-variable overrides. * * @example * ```typescript * // Simple — match your brand * theme: { primary: '#FFF700' } * * // Full design system * theme: { * mode: 'dark', * primary: '#FFF700', * background: '#121212', * surface: '#1e1e1e', * text: '#fff', * border: 'rgba(255,255,255,0.12)', * font: 'Inter, sans-serif', * } * ``` */ theme?: AdaptTheme; /** * Inline styles to apply to the root element. * Useful for quick positioning or sizing adjustments. * * @example * ```typescript * styles: { * width: '100%', * height: '600px', * maxWidth: '1200px', * margin: '0 auto' * } * ``` */ styles?: Partial; /** * Enable session persistence across page refreshes. * * When enabled, the session token and URL state are stored in browser storage. * On page load, the client reconnects to the existing session instead of creating a new one. * * - `true` — use sessionStorage with 3600s TTL * - `PersistOptions` — customize storage type and TTL * * @default undefined (disabled) */ persist?: boolean | PersistOptions; /** * Enable debug logging to the browser console. * When true, logs all session, persistence, URL, fork, and layout operations * with the `[Adapt]` prefix. Also activates debug logging in the core client. * * @default false */ debug?: boolean; /** * Custom text overrides for status messages shown to the user. * Each property is optional — unspecified messages use built-in defaults. */ text?: StatusText; } /** * Custom text overrides for status messages and error states. */ export interface StatusText { /** Shown when the session is stopped. Default: `"This session has been stopped"` */ stopped?: string; /** Shown when the session token has expired. Default: `"Your session has expired."` */ sessionExpired?: string; /** Shown on ResourceExhausted. Default: `"This automation is currently at capacity. Please try again later."` */ resourceExhausted?: string; /** Shown on NotFound when automation doesn't exist. Default: `"This automation could not be found."` */ notFound?: string; /** Shown on NotFound when session doesn't exist (timed out). Default: `"This session has timed out or been stopped."` */ sessionNotFound?: string; /** Shown on PermissionDenied or Unauthenticated. Default: `"You don't have permission to run this automation."` */ permissionDenied?: string; /** Shown on any other non-retriable error. Default: `"Something went wrong. Please try again later."` */ error?: string; /** Label for the restart/try-again button on status cards. Default: `"Try again"` */ restartButton?: string; } export type AdaptErrorKind = 'auth' | 'expired' | 'not_found' | 'session_not_found' | 'capacity' | 'error'; export interface AdaptError { kind: AdaptErrorKind; message: string; retriable: boolean; cause?: unknown; }