/** * @file * * Defines the transport interface for communicating with a running Obsidian instance. */ import type { CaptureScreenshotParams } from './capture-screenshot.cjs'; /** * An opaque handle identifying a console-capture window opened by * {@link ObsidianTransport.beginConsoleCapture} and consumed by * {@link ObsidianTransport.readConsoleCaptureSince}. */ export interface ConsoleCaptureHandle { /** * A unique marker written into the target's log stream at capture start, * used to slice out everything logged after this point. */ readonly marker: string; } /** * A pluggable transport that evaluates JavaScript expressions inside a running * Obsidian instance and manages vault lifecycle. * * Implementations handle the platform-specific details: * - {@link DesktopCdpTransport} — Desktop Obsidian via Chrome DevTools Protocol * (a harness-owned isolated instance by default, or attach to a running one) * - {@link AppiumTransport} — Mobile Obsidian via Appium WebView JS injection */ export interface ObsidianTransport { /** * Begins capturing the target's native console/error stream so a later * {@link ObsidianTransport.readConsoleCaptureSince} can return everything * logged since this call. * * Used to surface the *real* plugin-load error when Obsidian swallows it * before the harness's `loadPlugin` monkey-patch sees it (the error then * only exists in the renderer/WebView console). Optional and platform-specific: * implemented by {@link AppiumTransport} (tails `adb logcat`); absent on * {@link DesktopCdpTransport} (desktop errors are surfaced in-renderer, and a * developer can open DevTools directly). * * @returns An opaque handle to pass to {@link ObsidianTransport.readConsoleCaptureSince}, or `undefined` when unsupported. */ beginConsoleCapture?: () => Promise; /** * Captures a PNG screenshot of the running Obsidian instance. * * Optional and platform-specific, like the other members here. Implemented by * {@link DesktopCdpTransport} (CDP `Page.captureScreenshot`, optionally with * the viewport pinned to an exact size) and by {@link AppiumTransport} * (`takeScreenshot`, always the device's native framebuffer — the size knobs * are ignored, so a mobile capture is sized by choosing an AVD with the wanted * screen geometry). * * @param params - Which window to capture, and the exact size to capture it at. * @returns A {@link Promise} that resolves to the raw PNG bytes. */ captureScreenshot?: (params: CaptureScreenshotParams) => Promise; /** * Disposes of transport resources (e.g. WebSocket connections, Appium sessions). */ dispose?: () => Promise; /** * Synchronous disposal for use in `process.on('exit')` handlers where * async work is not possible. * * Implementations should perform only synchronous cleanup here (e.g. killing * child processes). Async operations like unregistering vaults are skipped. */ disposeSync?: () => void; /** * Evaluates a JavaScript expression string inside Obsidian and returns the * raw string result. * * The transport normalizes the output — stripping transport-specific prefixes * and handling transport-specific errors — so callers receive a clean result * string (e.g. a JSON string, or `(no output)`). * * @param expression - A self-contained JavaScript expression (typically an async IIFE). * @param options - Evaluation options including the working directory. * @returns The raw result string from Obsidian. */ evaluate: (expression: string, options: TransportEvalOptions) => Promise; /** * Whether this transport targets a mobile Obsidian instance. * * When `true`, desktop-only plugins (with `isDesktopOnly: true` in manifest) * will refuse to run integration tests. */ isMobile: boolean; /** * Runs transport-specific preflight checks before evaluation. * * For example, the CLI transport verifies that the vault is registered, * the CLI is enabled, and the CLI binary is in PATH. * * @param vaultPath - The absolute path to the vault folder. */ preflightCheck: (vaultPath: string) => Promise; /** * Pushes files into a vault directory on the target device. * * On desktop this is a no-op (files are written to the local filesystem directly). * On mobile this uses the device's file transfer mechanism (e.g. Appium `pushFile`). * * @param vaultPath - The absolute path to the vault folder. * @param files - Map of relative file paths to content buffers. */ pushFiles?: (vaultPath: string, files: Record) => Promise; /** * Reads the native console/error output captured since the matching * {@link ObsidianTransport.beginConsoleCapture} call. * * A bounded, post-hoc, failure-path-only dump — not a live monitor. * * @param handle - The handle returned by {@link ObsidianTransport.beginConsoleCapture} (or `undefined` when capture was unsupported). * @returns The captured console/error text, or `undefined` when unsupported or nothing relevant was logged. */ readConsoleCaptureSince?: (handle: ConsoleCaptureHandle | undefined) => Promise; /** * Registers a vault path so Obsidian can target it. * * On desktop: uses Electron IPC to open the vault and polls for readiness. * On mobile: pushes only a minimal `.obsidian` marker so the folder is recognized as a vault, * points the app's `localStorage` at it, and reloads. * * It does **not** carry the vault's contents across on either platform — that is * {@link ObsidianTransport.pushFiles}' job, which `TemporaryVault.register` runs first. A caller * driving a transport directly has to push before registering, or the app opens an empty vault * with nothing raised to say so. * * @param vaultPath - The absolute path to the vault folder. */ registerVault: (vaultPath: string) => Promise; /** * Unregisters a vault path from the running Obsidian instance. * * On desktop: closes the vault window and removes it from the registry. * On mobile: removes vault files from the device. * * @param vaultPath - The absolute path to the vault folder. */ unregisterVault: (vaultPath: string) => Promise; } /** * Options for {@link ObsidianTransport.evaluate}. */ export interface TransportEvalOptions { /** * The working directory (vault path) for the evaluation. */ readonly cwd: string; /** * Timeout in milliseconds for the evaluation command. */ readonly timeoutInMilliseconds?: number; }