@metamask/device-mcp
Advanced tools
| "use strict"; | ||
| /** | ||
| * Pure Hermes Chrome DevTools Protocol (CDP) core for `@metamask/device-mcp`. | ||
| * | ||
| * Ported from `@metamask/client-mcp-core`'s `src/tools/hermes-cdp.ts`, decoupled | ||
| * from that repo's `ToolContext`/`ErrorCodes`/`createToolError|Success`. Every | ||
| * function here takes EXPLICIT parameters (port, appId, pin) and returns a | ||
| * discriminated {@link HermesCdpResult} so this module is MCP-free and | ||
| * unit-testable. The tools layer is responsible for resolving session state and | ||
| * for PERSISTING the device pin — this file never imports `HermesSession`. | ||
| * | ||
| * Mechanism: the React Native Hermes runtime connects out to Metro's inspector | ||
| * proxy; Metro exposes debuggable targets over `http://localhost:<metroPort>/json` | ||
| * and a per-target `webSocketDebuggerUrl`. We speak real CDP over that WebSocket | ||
| * using the GLOBAL `WebSocket` (no library) and the global `fetch` for discovery. | ||
| * | ||
| * Five fail-closed safety checks are preserved VERBATIM from the reference: | ||
| * 1. Strict `appId` match (`===`, no substring, no `targets[0]` fallback). | ||
| * 2. `HermesInternal.getRuntimeProperties()` identity probe before the user method. | ||
| * 3. Device pin on `reactNative.logicalDeviceId` (set on first success). | ||
| * 4. Multi-device fail-closed (missing or >1 distinct `logicalDeviceId`). | ||
| * 5. WebSocket URL validation (protocol/hostname/port). | ||
| */ | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.IDENTITY_PROBE_EXPR = exports.LEGACY_SYNTHETIC_TITLE = exports.HERMES_WEBSOCKET_UNAVAILABLE = exports.HERMES_CDP_FAILED = exports.HERMES_TIMEOUT = exports.HERMES_CONNECTION_FAILED = exports.HERMES_INVALID_WS_URL = exports.HERMES_BLOCKED_METHOD = exports.HERMES_DEVICE_PIN_MISMATCH = exports.HERMES_MULTIPLE_DEVICES = exports.HERMES_NOT_VERIFIED = exports.HERMES_TARGET_NOT_FOUND = void 0; | ||
| exports.runHermesCdp = runHermesCdp; | ||
| exports.fetchDiscoveryTargets = fetchDiscoveryTargets; | ||
| exports.selectHermesTarget = selectHermesTarget; | ||
| exports.hasAmbiguousTarget = hasAmbiguousTarget; | ||
| exports.validateWebSocketUrl = validateWebSocketUrl; | ||
| exports.executeVerifiedCdpCommand = executeVerifiedCdpCommand; | ||
| /** | ||
| * No Hermes debug target matched the expected appId (after synthetic-title and | ||
| * pin filtering). Often means Metro is up but the app is not registered. | ||
| */ | ||
| exports.HERMES_TARGET_NOT_FOUND = 'HERMES_TARGET_NOT_FOUND'; | ||
| /** | ||
| * A target was found but could not be verified as the expected Hermes runtime | ||
| * (identity probe failed, missing `logicalDeviceId`, or non-JSON probe payload). | ||
| */ | ||
| exports.HERMES_NOT_VERIFIED = 'HERMES_NOT_VERIFIED'; | ||
| /** | ||
| * Multiple distinct logical devices are registered without a session pin (or a | ||
| * candidate is missing its `logicalDeviceId`); routing would be a guess, so we | ||
| * fail closed. | ||
| */ | ||
| exports.HERMES_MULTIPLE_DEVICES = 'HERMES_MULTIPLE_DEVICES'; | ||
| /** | ||
| * The resolved target's `logicalDeviceId` does not match the session pin. | ||
| */ | ||
| exports.HERMES_DEVICE_PIN_MISMATCH = 'HERMES_DEVICE_PIN_MISMATCH'; | ||
| /** | ||
| * The requested CDP method is in the destructive blocked set. | ||
| */ | ||
| exports.HERMES_BLOCKED_METHOD = 'HERMES_BLOCKED_METHOD'; | ||
| /** | ||
| * The target's `webSocketDebuggerUrl` failed protocol/hostname/port validation. | ||
| */ | ||
| exports.HERMES_INVALID_WS_URL = 'HERMES_INVALID_WS_URL'; | ||
| /** | ||
| * The underlying connection (discovery fetch or WebSocket) failed. | ||
| */ | ||
| exports.HERMES_CONNECTION_FAILED = 'HERMES_CONNECTION_FAILED'; | ||
| /** | ||
| * A discovery, socket-open, or CDP round-trip exceeded the configured timeout. | ||
| */ | ||
| exports.HERMES_TIMEOUT = 'HERMES_TIMEOUT'; | ||
| /** | ||
| * The user's CDP method returned a CDP-level error. | ||
| */ | ||
| exports.HERMES_CDP_FAILED = 'HERMES_CDP_FAILED'; | ||
| /** | ||
| * The global `WebSocket` constructor is unavailable in this Node runtime. | ||
| */ | ||
| exports.HERMES_WEBSOCKET_UNAVAILABLE = 'HERMES_WEBSOCKET_UNAVAILABLE'; | ||
| /** | ||
| * CDP methods blocked for safety. Ported VERBATIM from the reference — do not | ||
| * weaken. | ||
| */ | ||
| const HERMES_BLOCKED_METHODS = new Set([ | ||
| 'Runtime.terminateExecution', | ||
| 'Inspector.detached', | ||
| ]); | ||
| const DISCOVERY_PATHS = ['/json', '/json/list']; | ||
| const ALLOWED_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']); | ||
| /** | ||
| * Title of the synthetic legacy "Improved Chrome Reloads" page that Metro | ||
| * registers alongside the real Hermes target. Filtered out during selection. | ||
| * Ported VERBATIM from the reference. | ||
| */ | ||
| exports.LEGACY_SYNTHETIC_TITLE = 'React Native Experimental (Improved Chrome Reloads)'; | ||
| /** | ||
| * JavaScript expression evaluated against a candidate target BEFORE the user's | ||
| * method, to verify it is a genuine Hermes runtime. Copied VERBATIM from the | ||
| * reference — do not alter the verification semantics. | ||
| */ | ||
| exports.IDENTITY_PROBE_EXPR = `(function () { | ||
| try { | ||
| var hi = typeof HermesInternal !== 'undefined' ? HermesInternal : null; | ||
| var p = | ||
| hi && typeof hi.getRuntimeProperties === 'function' | ||
| ? hi.getRuntimeProperties() | ||
| : null; | ||
| return JSON.stringify({ | ||
| isHermes: !!hi, | ||
| ossVersion: p ? p['OSS Release Version'] : null, | ||
| debuggerEnabled: p ? p['Debugger Enabled'] : null, | ||
| }); | ||
| } catch (e) { | ||
| return JSON.stringify({ error: String(e) }); | ||
| } | ||
| })();`; | ||
| /** | ||
| * Orchestrates a single verified Hermes CDP command end to end. | ||
| * | ||
| * Resolves a target from Metro discovery, enforces all five fail-closed safety | ||
| * checks, runs the identity probe, persists the device pin via `onPin` on first | ||
| * success, sends the user's method, and returns the raw CDP `result`. The caller | ||
| * supplies the resolved port/appId/pin and persists the pin — this function is | ||
| * pure with respect to session state. | ||
| * | ||
| * @param input - Method, params, timeout, resolved port/appId/pin, and the pin | ||
| * persistence callback. See {@link RunHermesCdpInput}. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| async function runHermesCdp(input) { | ||
| if (HERMES_BLOCKED_METHODS.has(input.method)) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_BLOCKED_METHOD, | ||
| message: `CDP method "${input.method}" is blocked for safety. ` + | ||
| `Blocked methods: ${[...HERMES_BLOCKED_METHODS].join(', ')}`, | ||
| }; | ||
| } | ||
| if (typeof WebSocket !== 'function') { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_WEBSOCKET_UNAVAILABLE, | ||
| message: 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' + | ||
| 'NODE_OPTIONS="--experimental-websocket" (or use Node 22+).', | ||
| }; | ||
| } | ||
| try { | ||
| const targets = await fetchDiscoveryTargets(input.metroPort, input.timeoutMs); | ||
| const selection = selectHermesTarget(targets, input.appId, input.pinnedDeviceId); | ||
| if (!selection.ok) { | ||
| return { ok: false, code: selection.code, message: selection.message }; | ||
| } | ||
| const validation = validateWebSocketUrl(selection.target.webSocketDebuggerUrl, input.metroPort); | ||
| if (!validation.ok) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_INVALID_WS_URL, | ||
| message: validation.message, | ||
| }; | ||
| } | ||
| return await executeVerifiedCdpCommand(selection.target, input, input.pinnedDeviceId, input.onPin); | ||
| } | ||
| catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_CONNECTION_FAILED, | ||
| message: `Hermes CDP connection failed: ${message}`, | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Fetches debuggable Hermes targets from Metro, trying `/json` then falling | ||
| * back to `/json/list`. Each attempt is bounded by an `AbortController` | ||
| * timeout. The payload must be an array; non-object entries are filtered out. | ||
| * | ||
| * @param metroPort - The Metro inspector proxy port. | ||
| * @param timeoutMs - Per-attempt timeout in milliseconds. | ||
| * @returns The discovered targets (objects only). | ||
| * @throws The last error encountered if all discovery paths fail. | ||
| */ | ||
| async function fetchDiscoveryTargets(metroPort, timeoutMs) { | ||
| let lastError; | ||
| for (const path of DISCOVERY_PATHS) { | ||
| const controller = new AbortController(); | ||
| const timer = setTimeout(() => controller.abort(), timeoutMs); | ||
| try { | ||
| const response = await fetch(`http://localhost:${metroPort}${path}`, { | ||
| signal: controller.signal, | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`Metro ${path} returned HTTP ${response.status}`); | ||
| } | ||
| const payload = await response.json(); | ||
| if (!Array.isArray(payload)) { | ||
| throw new Error(`Metro ${path} returned a non-array response`); | ||
| } | ||
| return payload.filter(isHermesTarget); | ||
| } | ||
| catch (error) { | ||
| lastError = error; | ||
| } | ||
| finally { | ||
| clearTimeout(timer); | ||
| } | ||
| } | ||
| throw lastError instanceof Error ? lastError : new Error(String(lastError)); | ||
| } | ||
| /** | ||
| * Narrows an unknown discovery entry to a {@link HermesTarget} (object guard). | ||
| * | ||
| * @param value - A raw entry from the Metro discovery payload. | ||
| * @returns True when `value` is a non-null object. | ||
| */ | ||
| function isHermesTarget(value) { | ||
| return typeof value === 'object' && value !== null; | ||
| } | ||
| /** | ||
| * Selects the single safe Hermes target for the expected app, applying the | ||
| * fail-closed selection algorithm VERBATIM from the reference: | ||
| * | ||
| * `webSocketDebuggerUrl` present → drop the synthetic legacy title → strict | ||
| * `appId` match (`===`) → pin filter → ambiguity check ({@link hasAmbiguousTarget}) | ||
| * → `nativePageReloads` tiebreak → last-in-array. | ||
| * | ||
| * @param targets - All discovered targets. | ||
| * @param expectedAppId - The strict app bundle identifier to match. | ||
| * @param pinnedDeviceId - The session pin, if set, used to filter candidates. | ||
| * @returns The selected target or a classified failure. | ||
| */ | ||
| function selectHermesTarget(targets, expectedAppId, pinnedDeviceId) { | ||
| const seenAppIds = [...new Set(targets.map((target) => target.appId))].filter((appId) => typeof appId === 'string'); | ||
| let candidates = targets | ||
| .filter((target) => Boolean(target.webSocketDebuggerUrl)) | ||
| .filter((target) => target.title !== exports.LEGACY_SYNTHETIC_TITLE) | ||
| .filter((target) => target.appId === expectedAppId); | ||
| if (pinnedDeviceId) { | ||
| candidates = candidates.filter((target) => target.reactNative?.logicalDeviceId === pinnedDeviceId); | ||
| } | ||
| if (candidates.length === 0) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_TARGET_NOT_FOUND, | ||
| message: `No Hermes debug target found for appId ${expectedAppId}. Saw appIds: ${JSON.stringify(seenAppIds)}`, | ||
| }; | ||
| } | ||
| if (hasAmbiguousTarget(candidates)) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_MULTIPLE_DEVICES, | ||
| message: `Ambiguous Hermes target after pin filtering. Candidates: ${candidates | ||
| .map((target) => `${target.id ?? '<unknown>'} (device=${target.reactNative?.logicalDeviceId ?? '<missing>'})`) | ||
| .join(', ')}`, | ||
| }; | ||
| } | ||
| // Same-device tiebreak: prefer pages with nativePageReloads (newer RN page | ||
| // registrations) over stale entries left behind after in-app reloads. | ||
| const nativeReloadTargets = candidates.filter((target) => target.reactNative?.capabilities?.nativePageReloads === true); | ||
| if (nativeReloadTargets.length > 0) { | ||
| candidates = nativeReloadTargets; | ||
| } | ||
| return { ok: true, target: candidates[candidates.length - 1] }; | ||
| } | ||
| /** | ||
| * Determines whether a multi-candidate target list is too ambiguous to choose | ||
| * from safely. Returns true when: | ||
| * - any candidate is missing a `reactNative.logicalDeviceId` (we can't route safely), or | ||
| * - candidates resolve to more than one distinct `logicalDeviceId` (multi-device | ||
| * without a session pin — picking any one would silently bind the wrong device). | ||
| * | ||
| * Returns false when all candidates share the same logical device ID. In that | ||
| * case the caller falls through to the "last-in-array" tiebreak, matching the | ||
| * React Native convention that the most recent page registration wins (e.g., | ||
| * stale + fresh page after an in-app reload). | ||
| * | ||
| * @param candidates - Targets remaining after appId/pin/capability filtering. | ||
| * @returns True when the caller must fail closed; false when tiebreak is safe. | ||
| */ | ||
| function hasAmbiguousTarget(candidates) { | ||
| if (candidates.length <= 1) { | ||
| return false; | ||
| } | ||
| const deviceIds = candidates.map((target) => target.reactNative?.logicalDeviceId); | ||
| // Any candidate missing a logicalDeviceId → can't safely route. | ||
| if (deviceIds.some((id) => !id)) { | ||
| return true; | ||
| } | ||
| // Multiple distinct device IDs → multi-device without pin. | ||
| return new Set(deviceIds).size > 1; | ||
| } | ||
| /** | ||
| * Validates a target's `webSocketDebuggerUrl` before connecting. The protocol | ||
| * must be `ws:`, the hostname must be loopback (`localhost`, `127.0.0.1`, `::1`, | ||
| * or `[::1]`), and the port must equal the resolved Metro port. Ported VERBATIM | ||
| * from the reference. | ||
| * | ||
| * @param rawUrl - The candidate `webSocketDebuggerUrl`. | ||
| * @param expectedPort - The resolved Metro port the URL must point at. | ||
| * @returns `{ ok: true }` when valid, otherwise a failure with a message. | ||
| */ | ||
| function validateWebSocketUrl(rawUrl, expectedPort) { | ||
| if (!rawUrl) { | ||
| return { ok: false, message: 'webSocketDebuggerUrl is missing' }; | ||
| } | ||
| let parsed; | ||
| try { | ||
| parsed = new URL(rawUrl); | ||
| } | ||
| catch { | ||
| return { | ||
| ok: false, | ||
| message: `webSocketDebuggerUrl is not a valid URL: ${rawUrl}`, | ||
| }; | ||
| } | ||
| if (parsed.protocol !== 'ws:') { | ||
| return { ok: false, message: `Unexpected protocol '${parsed.protocol}'` }; | ||
| } | ||
| if (!ALLOWED_HOSTNAMES.has(parsed.hostname)) { | ||
| return { ok: false, message: `Unexpected hostname '${parsed.hostname}'` }; | ||
| } | ||
| if (Number(parsed.port) !== expectedPort) { | ||
| return { | ||
| ok: false, | ||
| message: `Port mismatch: target=${parsed.port} expected=${expectedPort}`, | ||
| }; | ||
| } | ||
| return { ok: true }; | ||
| } | ||
| /** | ||
| * Opens the target WebSocket, runs the identity probe, enforces the device pin | ||
| * (persisting it via `onPin` on first success, failing closed on mismatch), | ||
| * sends the user's method, matches the response by `id`, and always closes the | ||
| * socket in a `finally`. Ported faithfully from the reference. | ||
| * | ||
| * @param target - The validated, selected target. | ||
| * @param input - The original {@link RunHermesCdpInput} (method/params/timeout). | ||
| * @param pinnedDeviceId - The session pin, if set. | ||
| * @param onPin - Callback to persist the resolved pin on first success. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| async function executeVerifiedCdpCommand(target, input, pinnedDeviceId, onPin) { | ||
| if (!target.webSocketDebuggerUrl) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_INVALID_WS_URL, | ||
| message: 'Target is missing webSocketDebuggerUrl', | ||
| }; | ||
| } | ||
| if (typeof WebSocket !== 'function') { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_WEBSOCKET_UNAVAILABLE, | ||
| message: 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' + | ||
| 'NODE_OPTIONS="--experimental-websocket" (or use Node 22+).', | ||
| }; | ||
| } | ||
| const socket = new WebSocket(target.webSocketDebuggerUrl); | ||
| let nextId = 1; | ||
| try { | ||
| await waitForSocketOpen(socket, input.timeoutMs); | ||
| const probe = await runIdentityProbe(socket, input.timeoutMs, nextId); | ||
| nextId += 1; | ||
| if (!probe.ok) { | ||
| return { ok: false, code: probe.code, message: probe.message }; | ||
| } | ||
| const targetDeviceId = target.reactNative?.logicalDeviceId; | ||
| if (!targetDeviceId) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_NOT_VERIFIED, | ||
| message: 'Target is missing reactNative.logicalDeviceId', | ||
| }; | ||
| } | ||
| if (!pinnedDeviceId) { | ||
| onPin?.(targetDeviceId); | ||
| } | ||
| else if (pinnedDeviceId !== targetDeviceId) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_DEVICE_PIN_MISMATCH, | ||
| message: `Hermes target device ${targetDeviceId} does not match session pin ${pinnedDeviceId}`, | ||
| }; | ||
| } | ||
| const response = await sendUserMethod(socket, input, nextId); | ||
| nextId += 1; | ||
| if (isCdpErrorResponse(response)) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_CDP_FAILED, | ||
| message: `Hermes CDP "${input.method}" failed: ${formatCdpError(response)}`, | ||
| }; | ||
| } | ||
| return { ok: true, result: response.result }; | ||
| } | ||
| finally { | ||
| closeSocket(socket); | ||
| } | ||
| } | ||
| /** | ||
| * Runs the {@link IDENTITY_PROBE_EXPR} against an open socket and verifies the | ||
| * response confirms a Hermes runtime. Distinguishes timeout from connection | ||
| * failure and unverified payloads. | ||
| * | ||
| * @param socket - The open target WebSocket. | ||
| * @param timeoutMs - Round-trip timeout in milliseconds. | ||
| * @param id - The CDP message id to use for this probe. | ||
| * @returns A {@link ProbeResult}. | ||
| */ | ||
| async function runIdentityProbe(socket, timeoutMs, id) { | ||
| socket.send(JSON.stringify({ | ||
| id, | ||
| method: 'Runtime.evaluate', | ||
| params: { expression: exports.IDENTITY_PROBE_EXPR, returnByValue: true }, | ||
| })); | ||
| let response; | ||
| try { | ||
| response = await waitForCdpResponse(socket, id, timeoutMs); | ||
| } | ||
| catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| if (message.includes('timed out')) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_TIMEOUT, | ||
| message: `Identity probe timed out after ${timeoutMs}ms`, | ||
| }; | ||
| } | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_CONNECTION_FAILED, | ||
| message: `Hermes CDP connection failed: ${message}`, | ||
| }; | ||
| } | ||
| if (isCdpErrorResponse(response)) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_NOT_VERIFIED, | ||
| message: `Identity probe failed: ${formatCdpError(response)}`, | ||
| }; | ||
| } | ||
| const remoteResult = getProbeRemoteResult(response); | ||
| if (remoteResult.subtype === 'error') { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_NOT_VERIFIED, | ||
| message: 'Identity probe evaluation returned an error subtype', | ||
| }; | ||
| } | ||
| if (remoteResult.value === undefined || remoteResult.value === null) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_NOT_VERIFIED, | ||
| message: 'Identity probe response missing result.value', | ||
| }; | ||
| } | ||
| const raw = typeof remoteResult.value === 'string' | ||
| ? remoteResult.value | ||
| : JSON.stringify(remoteResult.value); | ||
| let parsed; | ||
| try { | ||
| parsed = JSON.parse(raw); | ||
| } | ||
| catch { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_NOT_VERIFIED, | ||
| message: `Identity probe returned non-JSON: ${raw}`, | ||
| }; | ||
| } | ||
| if (!isHermesProbePayload(parsed)) { | ||
| return { | ||
| ok: false, | ||
| code: exports.HERMES_NOT_VERIFIED, | ||
| message: `Identity probe did not verify Hermes runtime: ${raw}`, | ||
| }; | ||
| } | ||
| return { ok: true }; | ||
| } | ||
| /** | ||
| * Safely extracts the nested CDP `result.result` (the remote object) from a | ||
| * `Runtime.evaluate` success response. | ||
| * | ||
| * @param response - A CDP success response. | ||
| * @returns The remote object's `subtype`/`value`, or an empty object. | ||
| */ | ||
| function getProbeRemoteResult(response) { | ||
| if (typeof response.result !== 'object' || response.result === null) { | ||
| return {}; | ||
| } | ||
| const outer = response.result; | ||
| if (typeof outer.result !== 'object' || outer.result === null) { | ||
| return {}; | ||
| } | ||
| return outer.result; | ||
| } | ||
| /** | ||
| * Narrows a parsed probe payload to one confirming a Hermes runtime. | ||
| * | ||
| * @param value - The parsed probe JSON payload. | ||
| * @returns True when `isHermes === true`. | ||
| */ | ||
| function isHermesProbePayload(value) { | ||
| return (typeof value === 'object' && | ||
| value !== null && | ||
| value.isHermes === true); | ||
| } | ||
| /** | ||
| * Sends the user's CDP method over an open, verified socket and awaits the | ||
| * matching response by id. | ||
| * | ||
| * @param socket - The open, verified target WebSocket. | ||
| * @param input - The original input carrying method/params/timeout. | ||
| * @param id - The CDP message id to use. | ||
| * @returns The matching CDP success or error response. | ||
| */ | ||
| async function sendUserMethod(socket, input, id) { | ||
| socket.send(JSON.stringify({ | ||
| id, | ||
| method: input.method, | ||
| params: input.params ?? {}, | ||
| })); | ||
| return await waitForCdpResponse(socket, id, input.timeoutMs); | ||
| } | ||
| /** | ||
| * Resolves once the socket reaches the OPEN state, rejecting on error or | ||
| * timeout. Listeners are always cleaned up. | ||
| * | ||
| * @param socket - The connecting target WebSocket. | ||
| * @param timeoutMs - Connection timeout in milliseconds. | ||
| * @returns A promise that resolves when the socket is open. | ||
| */ | ||
| async function waitForSocketOpen(socket, timeoutMs) { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| return Promise.resolve(); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| let cleanup = () => undefined; | ||
| const handleOpen = () => { | ||
| cleanup(); | ||
| resolve(); | ||
| }; | ||
| const handleError = () => { | ||
| cleanup(); | ||
| reject(new Error('WebSocket connection error')); | ||
| }; | ||
| const timer = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error(`WebSocket connection timed out after ${timeoutMs}ms`)); | ||
| }, timeoutMs); | ||
| cleanup = () => { | ||
| clearTimeout(timer); | ||
| socket.removeEventListener('open', handleOpen); | ||
| socket.removeEventListener('error', handleError); | ||
| }; | ||
| socket.addEventListener('open', handleOpen); | ||
| socket.addEventListener('error', handleError); | ||
| }); | ||
| } | ||
| /** | ||
| * Resolves with the CDP response whose `id` matches, rejecting on socket error, | ||
| * premature close, or timeout. Listeners are always cleaned up. | ||
| * | ||
| * @param socket - The open target WebSocket. | ||
| * @param id - The CDP message id to match. | ||
| * @param timeoutMs - Round-trip timeout in milliseconds. | ||
| * @returns The matching CDP success or error response. | ||
| */ | ||
| async function waitForCdpResponse(socket, id, timeoutMs) { | ||
| return new Promise((resolve, reject) => { | ||
| let cleanup = () => undefined; | ||
| const handleMessage = (event) => { | ||
| try { | ||
| const parsed = parseCdpResponse(event.data); | ||
| if (parsed?.id !== id) { | ||
| return; | ||
| } | ||
| cleanup(); | ||
| resolve(parsed); | ||
| } | ||
| catch (error) { | ||
| cleanup(); | ||
| reject(error); | ||
| } | ||
| }; | ||
| const handleError = () => { | ||
| cleanup(); | ||
| reject(new Error('WebSocket message error')); | ||
| }; | ||
| const handleClose = () => { | ||
| cleanup(); | ||
| reject(new Error('WebSocket closed before CDP response')); | ||
| }; | ||
| const timer = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error(`Hermes CDP call timed out after ${timeoutMs}ms`)); | ||
| }, timeoutMs); | ||
| cleanup = () => { | ||
| clearTimeout(timer); | ||
| socket.removeEventListener('message', handleMessage); | ||
| socket.removeEventListener('error', handleError); | ||
| socket.removeEventListener('close', handleClose); | ||
| }; | ||
| socket.addEventListener('message', handleMessage); | ||
| socket.addEventListener('error', handleError); | ||
| socket.addEventListener('close', handleClose); | ||
| }); | ||
| } | ||
| /** | ||
| * Parses a raw WebSocket frame into a CDP response, returning undefined when it | ||
| * is not an addressable (`id`-bearing) message. | ||
| * | ||
| * @param data - The raw `MessageEvent.data`. | ||
| * @returns A CDP success/error response, or undefined when not addressable. | ||
| * @throws When the frame is not a text frame. | ||
| */ | ||
| function parseCdpResponse(data) { | ||
| if (typeof data !== 'string') { | ||
| throw new Error('Hermes CDP returned a non-text WebSocket frame'); | ||
| } | ||
| const parsed = JSON.parse(data); | ||
| if (typeof parsed !== 'object' || parsed === null) { | ||
| return undefined; | ||
| } | ||
| const candidate = parsed; | ||
| if (typeof candidate.id !== 'number') { | ||
| return undefined; | ||
| } | ||
| if (isCdpError(candidate.error)) { | ||
| return { id: candidate.id, error: candidate.error }; | ||
| } | ||
| return { id: candidate.id, result: candidate.result }; | ||
| } | ||
| /** | ||
| * Narrows an unknown value to a CDP error object. | ||
| * | ||
| * @param value - The candidate `error` field. | ||
| * @returns True when `value` is a non-null object. | ||
| */ | ||
| function isCdpError(value) { | ||
| return typeof value === 'object' && value !== null; | ||
| } | ||
| /** | ||
| * Discriminates a CDP response as an error response. | ||
| * | ||
| * @param response - A CDP success or error response. | ||
| * @returns True when the response carries an `error` field. | ||
| */ | ||
| function isCdpErrorResponse(response) { | ||
| return 'error' in response; | ||
| } | ||
| /** | ||
| * Formats a CDP error response into a single human-readable line. | ||
| * | ||
| * @param response - The CDP error response. | ||
| * @returns A combined message including code and data when present. | ||
| */ | ||
| function formatCdpError(response) { | ||
| const parts = [response.error.message ?? 'Unknown CDP error']; | ||
| if (typeof response.error.code === 'number') { | ||
| parts.push(`code ${response.error.code}`); | ||
| } | ||
| if (response.error.data !== undefined) { | ||
| parts.push(JSON.stringify(response.error.data)); | ||
| } | ||
| return parts.join(' - '); | ||
| } | ||
| /** | ||
| * Closes the socket if it is still connecting or open. | ||
| * | ||
| * @param socket - The target WebSocket. | ||
| */ | ||
| function closeSocket(socket) { | ||
| if (socket.readyState === WebSocket.CONNECTING || | ||
| socket.readyState === WebSocket.OPEN) { | ||
| socket.close(); | ||
| } | ||
| } | ||
| //# sourceMappingURL=hermes-cdp.cjs.map |
| {"version":3,"file":"hermes-cdp.cjs","sourceRoot":"","sources":["../../src/hermes/hermes-cdp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure Hermes Chrome DevTools Protocol (CDP) core for `@metamask/device-mcp`.\n *\n * Ported from `@metamask/client-mcp-core`'s `src/tools/hermes-cdp.ts`, decoupled\n * from that repo's `ToolContext`/`ErrorCodes`/`createToolError|Success`. Every\n * function here takes EXPLICIT parameters (port, appId, pin) and returns a\n * discriminated {@link HermesCdpResult} so this module is MCP-free and\n * unit-testable. The tools layer is responsible for resolving session state and\n * for PERSISTING the device pin — this file never imports `HermesSession`.\n *\n * Mechanism: the React Native Hermes runtime connects out to Metro's inspector\n * proxy; Metro exposes debuggable targets over `http://localhost:<metroPort>/json`\n * and a per-target `webSocketDebuggerUrl`. We speak real CDP over that WebSocket\n * using the GLOBAL `WebSocket` (no library) and the global `fetch` for discovery.\n *\n * Five fail-closed safety checks are preserved VERBATIM from the reference:\n * 1. Strict `appId` match (`===`, no substring, no `targets[0]` fallback).\n * 2. `HermesInternal.getRuntimeProperties()` identity probe before the user method.\n * 3. Device pin on `reactNative.logicalDeviceId` (set on first success).\n * 4. Multi-device fail-closed (missing or >1 distinct `logicalDeviceId`).\n * 5. WebSocket URL validation (protocol/hostname/port).\n */\n\n/**\n * Reported capability flags on a Metro Hermes target.\n */\ntype HermesCapabilities = {\n /** Whether the target supports native page reloads (newer RN registrations). */\n nativePageReloads?: boolean;\n /** Whether the target supports native source-code fetching. */\n nativeSourceCodeFetching?: boolean;\n /** Whether the target supports multiple concurrent debuggers. */\n supportsMultipleDebuggers?: boolean;\n};\n\n/**\n * React Native-specific metadata attached to a Metro Hermes target.\n */\ntype HermesReactNative = {\n /** Stable logical device identifier used for session pinning. */\n logicalDeviceId?: string;\n /** Reported capability flags for this target. */\n capabilities?: HermesCapabilities;\n};\n\n/**\n * A debuggable Hermes target as reported by Metro's `/json` (or `/json/list`)\n * discovery endpoint. Shape matches the reference implementation exactly.\n */\nexport type HermesTarget = {\n /** Metro's per-target identifier. */\n id?: string;\n /** Human-readable target title (used to filter the legacy synthetic page). */\n title?: string;\n /** Human-readable target description. */\n description?: string;\n /** The app bundle identifier this target belongs to (strict-matched). */\n appId?: string;\n /** The `ws://` URL used to speak CDP with this target. */\n webSocketDebuggerUrl?: string;\n /** React Native-specific metadata (logical device id + capabilities). */\n reactNative?: HermesReactNative;\n};\n\n/**\n * Discriminated result of a Hermes CDP operation.\n *\n * On success `result` carries the raw CDP `result` payload. On failure `code`\n * is a STABLE string error code (see the `HERMES_*` constants) and `message`\n * is a human-readable diagnostic preserving the reference's wording.\n */\nexport type HermesCdpResult =\n | { ok: true; result: unknown }\n | { ok: false; code: string; message: string };\n\n/**\n * Parameters for {@link runHermesCdp}.\n */\nexport type RunHermesCdpInput = {\n /** The CDP method to invoke (e.g. `Runtime.evaluate`). */\n method: string;\n /** Optional CDP method parameters. */\n params?: Record<string, unknown>;\n /** Per-call timeout in milliseconds for discovery and each CDP round-trip. */\n timeoutMs: number;\n /** The resolved Metro inspector proxy port. */\n metroPort: number;\n /** The expected app bundle identifier (strict-matched against targets). */\n appId: string;\n /** The pinned `logicalDeviceId` for this session, if one has been set. */\n pinnedDeviceId?: string;\n /**\n * Callback invoked with the resolved `logicalDeviceId` on the FIRST\n * successful identity probe so the caller can persist the pin. This file\n * stays pure: it never imports or mutates session state itself.\n */\n onPin?: (logicalDeviceId: string) => void;\n};\n\ntype CdpSuccessResponse = {\n id: number;\n result?: unknown;\n};\n\ntype CdpErrorResponse = {\n id: number;\n error: {\n message?: string;\n code?: number;\n data?: unknown;\n };\n};\n\ntype TargetSelection =\n | { ok: true; target: HermesTarget }\n | { ok: false; code: string; message: string };\n\ntype ProbeResult = { ok: true } | { ok: false; code: string; message: string };\n\n/**\n * No Hermes debug target matched the expected appId (after synthetic-title and\n * pin filtering). Often means Metro is up but the app is not registered.\n */\nexport const HERMES_TARGET_NOT_FOUND = 'HERMES_TARGET_NOT_FOUND';\n\n/**\n * A target was found but could not be verified as the expected Hermes runtime\n * (identity probe failed, missing `logicalDeviceId`, or non-JSON probe payload).\n */\nexport const HERMES_NOT_VERIFIED = 'HERMES_NOT_VERIFIED';\n\n/**\n * Multiple distinct logical devices are registered without a session pin (or a\n * candidate is missing its `logicalDeviceId`); routing would be a guess, so we\n * fail closed.\n */\nexport const HERMES_MULTIPLE_DEVICES = 'HERMES_MULTIPLE_DEVICES';\n\n/**\n * The resolved target's `logicalDeviceId` does not match the session pin.\n */\nexport const HERMES_DEVICE_PIN_MISMATCH = 'HERMES_DEVICE_PIN_MISMATCH';\n\n/**\n * The requested CDP method is in the destructive blocked set.\n */\nexport const HERMES_BLOCKED_METHOD = 'HERMES_BLOCKED_METHOD';\n\n/**\n * The target's `webSocketDebuggerUrl` failed protocol/hostname/port validation.\n */\nexport const HERMES_INVALID_WS_URL = 'HERMES_INVALID_WS_URL';\n\n/**\n * The underlying connection (discovery fetch or WebSocket) failed.\n */\nexport const HERMES_CONNECTION_FAILED = 'HERMES_CONNECTION_FAILED';\n\n/**\n * A discovery, socket-open, or CDP round-trip exceeded the configured timeout.\n */\nexport const HERMES_TIMEOUT = 'HERMES_TIMEOUT';\n\n/**\n * The user's CDP method returned a CDP-level error.\n */\nexport const HERMES_CDP_FAILED = 'HERMES_CDP_FAILED';\n\n/**\n * The global `WebSocket` constructor is unavailable in this Node runtime.\n */\nexport const HERMES_WEBSOCKET_UNAVAILABLE = 'HERMES_WEBSOCKET_UNAVAILABLE';\n\n/**\n * CDP methods blocked for safety. Ported VERBATIM from the reference — do not\n * weaken.\n */\nconst HERMES_BLOCKED_METHODS = new Set([\n 'Runtime.terminateExecution',\n 'Inspector.detached',\n]);\n\nconst DISCOVERY_PATHS = ['/json', '/json/list'];\nconst ALLOWED_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);\n\n/**\n * Title of the synthetic legacy \"Improved Chrome Reloads\" page that Metro\n * registers alongside the real Hermes target. Filtered out during selection.\n * Ported VERBATIM from the reference.\n */\nexport const LEGACY_SYNTHETIC_TITLE =\n 'React Native Experimental (Improved Chrome Reloads)';\n\n/**\n * JavaScript expression evaluated against a candidate target BEFORE the user's\n * method, to verify it is a genuine Hermes runtime. Copied VERBATIM from the\n * reference — do not alter the verification semantics.\n */\nexport const IDENTITY_PROBE_EXPR = `(function () {\n try {\n var hi = typeof HermesInternal !== 'undefined' ? HermesInternal : null;\n var p =\n hi && typeof hi.getRuntimeProperties === 'function'\n ? hi.getRuntimeProperties()\n : null;\n return JSON.stringify({\n isHermes: !!hi,\n ossVersion: p ? p['OSS Release Version'] : null,\n debuggerEnabled: p ? p['Debugger Enabled'] : null,\n });\n } catch (e) {\n return JSON.stringify({ error: String(e) });\n }\n})();`;\n\n/**\n * Orchestrates a single verified Hermes CDP command end to end.\n *\n * Resolves a target from Metro discovery, enforces all five fail-closed safety\n * checks, runs the identity probe, persists the device pin via `onPin` on first\n * success, sends the user's method, and returns the raw CDP `result`. The caller\n * supplies the resolved port/appId/pin and persists the pin — this function is\n * pure with respect to session state.\n *\n * @param input - Method, params, timeout, resolved port/appId/pin, and the pin\n * persistence callback. See {@link RunHermesCdpInput}.\n * @returns A discriminated {@link HermesCdpResult}.\n */\nexport async function runHermesCdp(\n input: RunHermesCdpInput,\n): Promise<HermesCdpResult> {\n if (HERMES_BLOCKED_METHODS.has(input.method)) {\n return {\n ok: false,\n code: HERMES_BLOCKED_METHOD,\n message:\n `CDP method \"${input.method}\" is blocked for safety. ` +\n `Blocked methods: ${[...HERMES_BLOCKED_METHODS].join(', ')}`,\n };\n }\n\n if (typeof WebSocket !== 'function') {\n return {\n ok: false,\n code: HERMES_WEBSOCKET_UNAVAILABLE,\n message:\n 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' +\n 'NODE_OPTIONS=\"--experimental-websocket\" (or use Node 22+).',\n };\n }\n\n try {\n const targets = await fetchDiscoveryTargets(\n input.metroPort,\n input.timeoutMs,\n );\n const selection = selectHermesTarget(\n targets,\n input.appId,\n input.pinnedDeviceId,\n );\n if (!selection.ok) {\n return { ok: false, code: selection.code, message: selection.message };\n }\n\n const validation = validateWebSocketUrl(\n selection.target.webSocketDebuggerUrl,\n input.metroPort,\n );\n if (!validation.ok) {\n return {\n ok: false,\n code: HERMES_INVALID_WS_URL,\n message: validation.message,\n };\n }\n\n return await executeVerifiedCdpCommand(\n selection.target,\n input,\n input.pinnedDeviceId,\n input.onPin,\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n ok: false,\n code: HERMES_CONNECTION_FAILED,\n message: `Hermes CDP connection failed: ${message}`,\n };\n }\n}\n\n/**\n * Fetches debuggable Hermes targets from Metro, trying `/json` then falling\n * back to `/json/list`. Each attempt is bounded by an `AbortController`\n * timeout. The payload must be an array; non-object entries are filtered out.\n *\n * @param metroPort - The Metro inspector proxy port.\n * @param timeoutMs - Per-attempt timeout in milliseconds.\n * @returns The discovered targets (objects only).\n * @throws The last error encountered if all discovery paths fail.\n */\nexport async function fetchDiscoveryTargets(\n metroPort: number,\n timeoutMs: number,\n): Promise<HermesTarget[]> {\n let lastError: unknown;\n\n for (const path of DISCOVERY_PATHS) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(`http://localhost:${metroPort}${path}`, {\n signal: controller.signal,\n });\n if (!response.ok) {\n throw new Error(`Metro ${path} returned HTTP ${response.status}`);\n }\n\n const payload: unknown = await response.json();\n if (!Array.isArray(payload)) {\n throw new Error(`Metro ${path} returned a non-array response`);\n }\n\n return payload.filter(isHermesTarget);\n } catch (error) {\n lastError = error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n\n/**\n * Narrows an unknown discovery entry to a {@link HermesTarget} (object guard).\n *\n * @param value - A raw entry from the Metro discovery payload.\n * @returns True when `value` is a non-null object.\n */\nfunction isHermesTarget(value: unknown): value is HermesTarget {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Selects the single safe Hermes target for the expected app, applying the\n * fail-closed selection algorithm VERBATIM from the reference:\n *\n * `webSocketDebuggerUrl` present → drop the synthetic legacy title → strict\n * `appId` match (`===`) → pin filter → ambiguity check ({@link hasAmbiguousTarget})\n * → `nativePageReloads` tiebreak → last-in-array.\n *\n * @param targets - All discovered targets.\n * @param expectedAppId - The strict app bundle identifier to match.\n * @param pinnedDeviceId - The session pin, if set, used to filter candidates.\n * @returns The selected target or a classified failure.\n */\nexport function selectHermesTarget(\n targets: HermesTarget[],\n expectedAppId: string,\n pinnedDeviceId: string | undefined,\n): TargetSelection {\n const seenAppIds = [...new Set(targets.map((target) => target.appId))].filter(\n (appId): appId is string => typeof appId === 'string',\n );\n\n let candidates = targets\n .filter((target) => Boolean(target.webSocketDebuggerUrl))\n .filter((target) => target.title !== LEGACY_SYNTHETIC_TITLE)\n .filter((target) => target.appId === expectedAppId);\n\n if (pinnedDeviceId) {\n candidates = candidates.filter(\n (target) => target.reactNative?.logicalDeviceId === pinnedDeviceId,\n );\n }\n\n if (candidates.length === 0) {\n return {\n ok: false,\n code: HERMES_TARGET_NOT_FOUND,\n message: `No Hermes debug target found for appId ${expectedAppId}. Saw appIds: ${JSON.stringify(\n seenAppIds,\n )}`,\n };\n }\n\n if (hasAmbiguousTarget(candidates)) {\n return {\n ok: false,\n code: HERMES_MULTIPLE_DEVICES,\n message: `Ambiguous Hermes target after pin filtering. Candidates: ${candidates\n .map(\n (target) =>\n `${target.id ?? '<unknown>'} (device=${\n target.reactNative?.logicalDeviceId ?? '<missing>'\n })`,\n )\n .join(', ')}`,\n };\n }\n\n // Same-device tiebreak: prefer pages with nativePageReloads (newer RN page\n // registrations) over stale entries left behind after in-app reloads.\n const nativeReloadTargets = candidates.filter(\n (target) => target.reactNative?.capabilities?.nativePageReloads === true,\n );\n if (nativeReloadTargets.length > 0) {\n candidates = nativeReloadTargets;\n }\n\n return { ok: true, target: candidates[candidates.length - 1] };\n}\n\n/**\n * Determines whether a multi-candidate target list is too ambiguous to choose\n * from safely. Returns true when:\n * - any candidate is missing a `reactNative.logicalDeviceId` (we can't route safely), or\n * - candidates resolve to more than one distinct `logicalDeviceId` (multi-device\n * without a session pin — picking any one would silently bind the wrong device).\n *\n * Returns false when all candidates share the same logical device ID. In that\n * case the caller falls through to the \"last-in-array\" tiebreak, matching the\n * React Native convention that the most recent page registration wins (e.g.,\n * stale + fresh page after an in-app reload).\n *\n * @param candidates - Targets remaining after appId/pin/capability filtering.\n * @returns True when the caller must fail closed; false when tiebreak is safe.\n */\nexport function hasAmbiguousTarget(candidates: HermesTarget[]): boolean {\n if (candidates.length <= 1) {\n return false;\n }\n const deviceIds = candidates.map(\n (target) => target.reactNative?.logicalDeviceId,\n );\n // Any candidate missing a logicalDeviceId → can't safely route.\n if (deviceIds.some((id) => !id)) {\n return true;\n }\n // Multiple distinct device IDs → multi-device without pin.\n return new Set(deviceIds).size > 1;\n}\n\n/**\n * Validates a target's `webSocketDebuggerUrl` before connecting. The protocol\n * must be `ws:`, the hostname must be loopback (`localhost`, `127.0.0.1`, `::1`,\n * or `[::1]`), and the port must equal the resolved Metro port. Ported VERBATIM\n * from the reference.\n *\n * @param rawUrl - The candidate `webSocketDebuggerUrl`.\n * @param expectedPort - The resolved Metro port the URL must point at.\n * @returns `{ ok: true }` when valid, otherwise a failure with a message.\n */\nexport function validateWebSocketUrl(\n rawUrl: string | undefined,\n expectedPort: number,\n): { ok: true } | { ok: false; message: string } {\n if (!rawUrl) {\n return { ok: false, message: 'webSocketDebuggerUrl is missing' };\n }\n\n let parsed: URL;\n try {\n parsed = new URL(rawUrl);\n } catch {\n return {\n ok: false,\n message: `webSocketDebuggerUrl is not a valid URL: ${rawUrl}`,\n };\n }\n\n if (parsed.protocol !== 'ws:') {\n return { ok: false, message: `Unexpected protocol '${parsed.protocol}'` };\n }\n if (!ALLOWED_HOSTNAMES.has(parsed.hostname)) {\n return { ok: false, message: `Unexpected hostname '${parsed.hostname}'` };\n }\n if (Number(parsed.port) !== expectedPort) {\n return {\n ok: false,\n message: `Port mismatch: target=${parsed.port} expected=${expectedPort}`,\n };\n }\n\n return { ok: true };\n}\n\n/**\n * Opens the target WebSocket, runs the identity probe, enforces the device pin\n * (persisting it via `onPin` on first success, failing closed on mismatch),\n * sends the user's method, matches the response by `id`, and always closes the\n * socket in a `finally`. Ported faithfully from the reference.\n *\n * @param target - The validated, selected target.\n * @param input - The original {@link RunHermesCdpInput} (method/params/timeout).\n * @param pinnedDeviceId - The session pin, if set.\n * @param onPin - Callback to persist the resolved pin on first success.\n * @returns A discriminated {@link HermesCdpResult}.\n */\nexport async function executeVerifiedCdpCommand(\n target: HermesTarget,\n input: RunHermesCdpInput,\n pinnedDeviceId: string | undefined,\n onPin: ((logicalDeviceId: string) => void) | undefined,\n): Promise<HermesCdpResult> {\n if (!target.webSocketDebuggerUrl) {\n return {\n ok: false,\n code: HERMES_INVALID_WS_URL,\n message: 'Target is missing webSocketDebuggerUrl',\n };\n }\n if (typeof WebSocket !== 'function') {\n return {\n ok: false,\n code: HERMES_WEBSOCKET_UNAVAILABLE,\n message:\n 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' +\n 'NODE_OPTIONS=\"--experimental-websocket\" (or use Node 22+).',\n };\n }\n\n const socket = new WebSocket(target.webSocketDebuggerUrl);\n let nextId = 1;\n\n try {\n await waitForSocketOpen(socket, input.timeoutMs);\n\n const probe = await runIdentityProbe(socket, input.timeoutMs, nextId);\n nextId += 1;\n if (!probe.ok) {\n return { ok: false, code: probe.code, message: probe.message };\n }\n\n const targetDeviceId = target.reactNative?.logicalDeviceId;\n if (!targetDeviceId) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: 'Target is missing reactNative.logicalDeviceId',\n };\n }\n if (!pinnedDeviceId) {\n onPin?.(targetDeviceId);\n } else if (pinnedDeviceId !== targetDeviceId) {\n return {\n ok: false,\n code: HERMES_DEVICE_PIN_MISMATCH,\n message: `Hermes target device ${targetDeviceId} does not match session pin ${pinnedDeviceId}`,\n };\n }\n\n const response = await sendUserMethod(socket, input, nextId);\n nextId += 1;\n if (isCdpErrorResponse(response)) {\n return {\n ok: false,\n code: HERMES_CDP_FAILED,\n message: `Hermes CDP \"${input.method}\" failed: ${formatCdpError(\n response,\n )}`,\n };\n }\n\n return { ok: true, result: response.result };\n } finally {\n closeSocket(socket);\n }\n}\n\n/**\n * Runs the {@link IDENTITY_PROBE_EXPR} against an open socket and verifies the\n * response confirms a Hermes runtime. Distinguishes timeout from connection\n * failure and unverified payloads.\n *\n * @param socket - The open target WebSocket.\n * @param timeoutMs - Round-trip timeout in milliseconds.\n * @param id - The CDP message id to use for this probe.\n * @returns A {@link ProbeResult}.\n */\nasync function runIdentityProbe(\n socket: WebSocket,\n timeoutMs: number,\n id: number,\n): Promise<ProbeResult> {\n socket.send(\n JSON.stringify({\n id,\n method: 'Runtime.evaluate',\n params: { expression: IDENTITY_PROBE_EXPR, returnByValue: true },\n }),\n );\n\n let response: CdpSuccessResponse | CdpErrorResponse;\n try {\n response = await waitForCdpResponse(socket, id, timeoutMs);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('timed out')) {\n return {\n ok: false,\n code: HERMES_TIMEOUT,\n message: `Identity probe timed out after ${timeoutMs}ms`,\n };\n }\n return {\n ok: false,\n code: HERMES_CONNECTION_FAILED,\n message: `Hermes CDP connection failed: ${message}`,\n };\n }\n\n if (isCdpErrorResponse(response)) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: `Identity probe failed: ${formatCdpError(response)}`,\n };\n }\n\n const remoteResult = getProbeRemoteResult(response);\n if (remoteResult.subtype === 'error') {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: 'Identity probe evaluation returned an error subtype',\n };\n }\n if (remoteResult.value === undefined || remoteResult.value === null) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: 'Identity probe response missing result.value',\n };\n }\n\n const raw =\n typeof remoteResult.value === 'string'\n ? remoteResult.value\n : JSON.stringify(remoteResult.value);\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: `Identity probe returned non-JSON: ${raw}`,\n };\n }\n\n if (!isHermesProbePayload(parsed)) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: `Identity probe did not verify Hermes runtime: ${raw}`,\n };\n }\n\n return { ok: true };\n}\n\n/**\n * Safely extracts the nested CDP `result.result` (the remote object) from a\n * `Runtime.evaluate` success response.\n *\n * @param response - A CDP success response.\n * @returns The remote object's `subtype`/`value`, or an empty object.\n */\nfunction getProbeRemoteResult(response: CdpSuccessResponse): {\n subtype?: unknown;\n value?: unknown;\n} {\n if (typeof response.result !== 'object' || response.result === null) {\n return {};\n }\n const outer = response.result as Record<string, unknown>;\n if (typeof outer.result !== 'object' || outer.result === null) {\n return {};\n }\n return outer.result;\n}\n\n/**\n * Narrows a parsed probe payload to one confirming a Hermes runtime.\n *\n * @param value - The parsed probe JSON payload.\n * @returns True when `isHermes === true`.\n */\nfunction isHermesProbePayload(value: unknown): value is { isHermes: true } {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).isHermes === true\n );\n}\n\n/**\n * Sends the user's CDP method over an open, verified socket and awaits the\n * matching response by id.\n *\n * @param socket - The open, verified target WebSocket.\n * @param input - The original input carrying method/params/timeout.\n * @param id - The CDP message id to use.\n * @returns The matching CDP success or error response.\n */\nasync function sendUserMethod(\n socket: WebSocket,\n input: RunHermesCdpInput,\n id: number,\n): Promise<CdpSuccessResponse | CdpErrorResponse> {\n socket.send(\n JSON.stringify({\n id,\n method: input.method,\n params: input.params ?? {},\n }),\n );\n return await waitForCdpResponse(socket, id, input.timeoutMs);\n}\n\n/**\n * Resolves once the socket reaches the OPEN state, rejecting on error or\n * timeout. Listeners are always cleaned up.\n *\n * @param socket - The connecting target WebSocket.\n * @param timeoutMs - Connection timeout in milliseconds.\n * @returns A promise that resolves when the socket is open.\n */\nasync function waitForSocketOpen(\n socket: WebSocket,\n timeoutMs: number,\n): Promise<void> {\n if (socket.readyState === WebSocket.OPEN) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n let cleanup = (): void => undefined;\n const handleOpen = (): void => {\n cleanup();\n resolve();\n };\n const handleError = (): void => {\n cleanup();\n reject(new Error('WebSocket connection error'));\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`WebSocket connection timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n cleanup = (): void => {\n clearTimeout(timer);\n socket.removeEventListener('open', handleOpen);\n socket.removeEventListener('error', handleError);\n };\n\n socket.addEventListener('open', handleOpen);\n socket.addEventListener('error', handleError);\n });\n}\n\n/**\n * Resolves with the CDP response whose `id` matches, rejecting on socket error,\n * premature close, or timeout. Listeners are always cleaned up.\n *\n * @param socket - The open target WebSocket.\n * @param id - The CDP message id to match.\n * @param timeoutMs - Round-trip timeout in milliseconds.\n * @returns The matching CDP success or error response.\n */\nasync function waitForCdpResponse(\n socket: WebSocket,\n id: number,\n timeoutMs: number,\n): Promise<CdpSuccessResponse | CdpErrorResponse> {\n return new Promise((resolve, reject) => {\n let cleanup = (): void => undefined;\n const handleMessage = (event: MessageEvent): void => {\n try {\n const parsed = parseCdpResponse(event.data);\n if (parsed?.id !== id) {\n return;\n }\n cleanup();\n resolve(parsed);\n } catch (error) {\n cleanup();\n reject(error);\n }\n };\n const handleError = (): void => {\n cleanup();\n reject(new Error('WebSocket message error'));\n };\n const handleClose = (): void => {\n cleanup();\n reject(new Error('WebSocket closed before CDP response'));\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`Hermes CDP call timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n cleanup = (): void => {\n clearTimeout(timer);\n socket.removeEventListener('message', handleMessage);\n socket.removeEventListener('error', handleError);\n socket.removeEventListener('close', handleClose);\n };\n\n socket.addEventListener('message', handleMessage);\n socket.addEventListener('error', handleError);\n socket.addEventListener('close', handleClose);\n });\n}\n\n/**\n * Parses a raw WebSocket frame into a CDP response, returning undefined when it\n * is not an addressable (`id`-bearing) message.\n *\n * @param data - The raw `MessageEvent.data`.\n * @returns A CDP success/error response, or undefined when not addressable.\n * @throws When the frame is not a text frame.\n */\nfunction parseCdpResponse(\n data: MessageEvent['data'],\n): CdpSuccessResponse | CdpErrorResponse | undefined {\n if (typeof data !== 'string') {\n throw new Error('Hermes CDP returned a non-text WebSocket frame');\n }\n\n const parsed: unknown = JSON.parse(data);\n if (typeof parsed !== 'object' || parsed === null) {\n return undefined;\n }\n const candidate = parsed as Record<string, unknown>;\n if (typeof candidate.id !== 'number') {\n return undefined;\n }\n if (isCdpError(candidate.error)) {\n return { id: candidate.id, error: candidate.error };\n }\n return { id: candidate.id, result: candidate.result };\n}\n\n/**\n * Narrows an unknown value to a CDP error object.\n *\n * @param value - The candidate `error` field.\n * @returns True when `value` is a non-null object.\n */\nfunction isCdpError(value: unknown): value is CdpErrorResponse['error'] {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Discriminates a CDP response as an error response.\n *\n * @param response - A CDP success or error response.\n * @returns True when the response carries an `error` field.\n */\nfunction isCdpErrorResponse(\n response: CdpSuccessResponse | CdpErrorResponse,\n): response is CdpErrorResponse {\n return 'error' in response;\n}\n\n/**\n * Formats a CDP error response into a single human-readable line.\n *\n * @param response - The CDP error response.\n * @returns A combined message including code and data when present.\n */\nfunction formatCdpError(response: CdpErrorResponse): string {\n const parts = [response.error.message ?? 'Unknown CDP error'];\n if (typeof response.error.code === 'number') {\n parts.push(`code ${response.error.code}`);\n }\n if (response.error.data !== undefined) {\n parts.push(JSON.stringify(response.error.data));\n }\n return parts.join(' - ');\n}\n\n/**\n * Closes the socket if it is still connecting or open.\n *\n * @param socket - The target WebSocket.\n */\nfunction closeSocket(socket: WebSocket): void {\n if (\n socket.readyState === WebSocket.CONNECTING ||\n socket.readyState === WebSocket.OPEN\n ) {\n socket.close();\n }\n}\n"]} |
| /** | ||
| * Pure Hermes Chrome DevTools Protocol (CDP) core for `@metamask/device-mcp`. | ||
| * | ||
| * Ported from `@metamask/client-mcp-core`'s `src/tools/hermes-cdp.ts`, decoupled | ||
| * from that repo's `ToolContext`/`ErrorCodes`/`createToolError|Success`. Every | ||
| * function here takes EXPLICIT parameters (port, appId, pin) and returns a | ||
| * discriminated {@link HermesCdpResult} so this module is MCP-free and | ||
| * unit-testable. The tools layer is responsible for resolving session state and | ||
| * for PERSISTING the device pin — this file never imports `HermesSession`. | ||
| * | ||
| * Mechanism: the React Native Hermes runtime connects out to Metro's inspector | ||
| * proxy; Metro exposes debuggable targets over `http://localhost:<metroPort>/json` | ||
| * and a per-target `webSocketDebuggerUrl`. We speak real CDP over that WebSocket | ||
| * using the GLOBAL `WebSocket` (no library) and the global `fetch` for discovery. | ||
| * | ||
| * Five fail-closed safety checks are preserved VERBATIM from the reference: | ||
| * 1. Strict `appId` match (`===`, no substring, no `targets[0]` fallback). | ||
| * 2. `HermesInternal.getRuntimeProperties()` identity probe before the user method. | ||
| * 3. Device pin on `reactNative.logicalDeviceId` (set on first success). | ||
| * 4. Multi-device fail-closed (missing or >1 distinct `logicalDeviceId`). | ||
| * 5. WebSocket URL validation (protocol/hostname/port). | ||
| */ | ||
| /** | ||
| * Reported capability flags on a Metro Hermes target. | ||
| */ | ||
| type HermesCapabilities = { | ||
| /** Whether the target supports native page reloads (newer RN registrations). */ | ||
| nativePageReloads?: boolean; | ||
| /** Whether the target supports native source-code fetching. */ | ||
| nativeSourceCodeFetching?: boolean; | ||
| /** Whether the target supports multiple concurrent debuggers. */ | ||
| supportsMultipleDebuggers?: boolean; | ||
| }; | ||
| /** | ||
| * React Native-specific metadata attached to a Metro Hermes target. | ||
| */ | ||
| type HermesReactNative = { | ||
| /** Stable logical device identifier used for session pinning. */ | ||
| logicalDeviceId?: string; | ||
| /** Reported capability flags for this target. */ | ||
| capabilities?: HermesCapabilities; | ||
| }; | ||
| /** | ||
| * A debuggable Hermes target as reported by Metro's `/json` (or `/json/list`) | ||
| * discovery endpoint. Shape matches the reference implementation exactly. | ||
| */ | ||
| export type HermesTarget = { | ||
| /** Metro's per-target identifier. */ | ||
| id?: string; | ||
| /** Human-readable target title (used to filter the legacy synthetic page). */ | ||
| title?: string; | ||
| /** Human-readable target description. */ | ||
| description?: string; | ||
| /** The app bundle identifier this target belongs to (strict-matched). */ | ||
| appId?: string; | ||
| /** The `ws://` URL used to speak CDP with this target. */ | ||
| webSocketDebuggerUrl?: string; | ||
| /** React Native-specific metadata (logical device id + capabilities). */ | ||
| reactNative?: HermesReactNative; | ||
| }; | ||
| /** | ||
| * Discriminated result of a Hermes CDP operation. | ||
| * | ||
| * On success `result` carries the raw CDP `result` payload. On failure `code` | ||
| * is a STABLE string error code (see the `HERMES_*` constants) and `message` | ||
| * is a human-readable diagnostic preserving the reference's wording. | ||
| */ | ||
| export type HermesCdpResult = { | ||
| ok: true; | ||
| result: unknown; | ||
| } | { | ||
| ok: false; | ||
| code: string; | ||
| message: string; | ||
| }; | ||
| /** | ||
| * Parameters for {@link runHermesCdp}. | ||
| */ | ||
| export type RunHermesCdpInput = { | ||
| /** The CDP method to invoke (e.g. `Runtime.evaluate`). */ | ||
| method: string; | ||
| /** Optional CDP method parameters. */ | ||
| params?: Record<string, unknown>; | ||
| /** Per-call timeout in milliseconds for discovery and each CDP round-trip. */ | ||
| timeoutMs: number; | ||
| /** The resolved Metro inspector proxy port. */ | ||
| metroPort: number; | ||
| /** The expected app bundle identifier (strict-matched against targets). */ | ||
| appId: string; | ||
| /** The pinned `logicalDeviceId` for this session, if one has been set. */ | ||
| pinnedDeviceId?: string; | ||
| /** | ||
| * Callback invoked with the resolved `logicalDeviceId` on the FIRST | ||
| * successful identity probe so the caller can persist the pin. This file | ||
| * stays pure: it never imports or mutates session state itself. | ||
| */ | ||
| onPin?: (logicalDeviceId: string) => void; | ||
| }; | ||
| type TargetSelection = { | ||
| ok: true; | ||
| target: HermesTarget; | ||
| } | { | ||
| ok: false; | ||
| code: string; | ||
| message: string; | ||
| }; | ||
| /** | ||
| * No Hermes debug target matched the expected appId (after synthetic-title and | ||
| * pin filtering). Often means Metro is up but the app is not registered. | ||
| */ | ||
| export declare const HERMES_TARGET_NOT_FOUND = "HERMES_TARGET_NOT_FOUND"; | ||
| /** | ||
| * A target was found but could not be verified as the expected Hermes runtime | ||
| * (identity probe failed, missing `logicalDeviceId`, or non-JSON probe payload). | ||
| */ | ||
| export declare const HERMES_NOT_VERIFIED = "HERMES_NOT_VERIFIED"; | ||
| /** | ||
| * Multiple distinct logical devices are registered without a session pin (or a | ||
| * candidate is missing its `logicalDeviceId`); routing would be a guess, so we | ||
| * fail closed. | ||
| */ | ||
| export declare const HERMES_MULTIPLE_DEVICES = "HERMES_MULTIPLE_DEVICES"; | ||
| /** | ||
| * The resolved target's `logicalDeviceId` does not match the session pin. | ||
| */ | ||
| export declare const HERMES_DEVICE_PIN_MISMATCH = "HERMES_DEVICE_PIN_MISMATCH"; | ||
| /** | ||
| * The requested CDP method is in the destructive blocked set. | ||
| */ | ||
| export declare const HERMES_BLOCKED_METHOD = "HERMES_BLOCKED_METHOD"; | ||
| /** | ||
| * The target's `webSocketDebuggerUrl` failed protocol/hostname/port validation. | ||
| */ | ||
| export declare const HERMES_INVALID_WS_URL = "HERMES_INVALID_WS_URL"; | ||
| /** | ||
| * The underlying connection (discovery fetch or WebSocket) failed. | ||
| */ | ||
| export declare const HERMES_CONNECTION_FAILED = "HERMES_CONNECTION_FAILED"; | ||
| /** | ||
| * A discovery, socket-open, or CDP round-trip exceeded the configured timeout. | ||
| */ | ||
| export declare const HERMES_TIMEOUT = "HERMES_TIMEOUT"; | ||
| /** | ||
| * The user's CDP method returned a CDP-level error. | ||
| */ | ||
| export declare const HERMES_CDP_FAILED = "HERMES_CDP_FAILED"; | ||
| /** | ||
| * The global `WebSocket` constructor is unavailable in this Node runtime. | ||
| */ | ||
| export declare const HERMES_WEBSOCKET_UNAVAILABLE = "HERMES_WEBSOCKET_UNAVAILABLE"; | ||
| /** | ||
| * Title of the synthetic legacy "Improved Chrome Reloads" page that Metro | ||
| * registers alongside the real Hermes target. Filtered out during selection. | ||
| * Ported VERBATIM from the reference. | ||
| */ | ||
| export declare const LEGACY_SYNTHETIC_TITLE = "React Native Experimental (Improved Chrome Reloads)"; | ||
| /** | ||
| * JavaScript expression evaluated against a candidate target BEFORE the user's | ||
| * method, to verify it is a genuine Hermes runtime. Copied VERBATIM from the | ||
| * reference — do not alter the verification semantics. | ||
| */ | ||
| export declare const IDENTITY_PROBE_EXPR = "(function () {\n try {\n var hi = typeof HermesInternal !== 'undefined' ? HermesInternal : null;\n var p =\n hi && typeof hi.getRuntimeProperties === 'function'\n ? hi.getRuntimeProperties()\n : null;\n return JSON.stringify({\n isHermes: !!hi,\n ossVersion: p ? p['OSS Release Version'] : null,\n debuggerEnabled: p ? p['Debugger Enabled'] : null,\n });\n } catch (e) {\n return JSON.stringify({ error: String(e) });\n }\n})();"; | ||
| /** | ||
| * Orchestrates a single verified Hermes CDP command end to end. | ||
| * | ||
| * Resolves a target from Metro discovery, enforces all five fail-closed safety | ||
| * checks, runs the identity probe, persists the device pin via `onPin` on first | ||
| * success, sends the user's method, and returns the raw CDP `result`. The caller | ||
| * supplies the resolved port/appId/pin and persists the pin — this function is | ||
| * pure with respect to session state. | ||
| * | ||
| * @param input - Method, params, timeout, resolved port/appId/pin, and the pin | ||
| * persistence callback. See {@link RunHermesCdpInput}. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| export declare function runHermesCdp(input: RunHermesCdpInput): Promise<HermesCdpResult>; | ||
| /** | ||
| * Fetches debuggable Hermes targets from Metro, trying `/json` then falling | ||
| * back to `/json/list`. Each attempt is bounded by an `AbortController` | ||
| * timeout. The payload must be an array; non-object entries are filtered out. | ||
| * | ||
| * @param metroPort - The Metro inspector proxy port. | ||
| * @param timeoutMs - Per-attempt timeout in milliseconds. | ||
| * @returns The discovered targets (objects only). | ||
| * @throws The last error encountered if all discovery paths fail. | ||
| */ | ||
| export declare function fetchDiscoveryTargets(metroPort: number, timeoutMs: number): Promise<HermesTarget[]>; | ||
| /** | ||
| * Selects the single safe Hermes target for the expected app, applying the | ||
| * fail-closed selection algorithm VERBATIM from the reference: | ||
| * | ||
| * `webSocketDebuggerUrl` present → drop the synthetic legacy title → strict | ||
| * `appId` match (`===`) → pin filter → ambiguity check ({@link hasAmbiguousTarget}) | ||
| * → `nativePageReloads` tiebreak → last-in-array. | ||
| * | ||
| * @param targets - All discovered targets. | ||
| * @param expectedAppId - The strict app bundle identifier to match. | ||
| * @param pinnedDeviceId - The session pin, if set, used to filter candidates. | ||
| * @returns The selected target or a classified failure. | ||
| */ | ||
| export declare function selectHermesTarget(targets: HermesTarget[], expectedAppId: string, pinnedDeviceId: string | undefined): TargetSelection; | ||
| /** | ||
| * Determines whether a multi-candidate target list is too ambiguous to choose | ||
| * from safely. Returns true when: | ||
| * - any candidate is missing a `reactNative.logicalDeviceId` (we can't route safely), or | ||
| * - candidates resolve to more than one distinct `logicalDeviceId` (multi-device | ||
| * without a session pin — picking any one would silently bind the wrong device). | ||
| * | ||
| * Returns false when all candidates share the same logical device ID. In that | ||
| * case the caller falls through to the "last-in-array" tiebreak, matching the | ||
| * React Native convention that the most recent page registration wins (e.g., | ||
| * stale + fresh page after an in-app reload). | ||
| * | ||
| * @param candidates - Targets remaining after appId/pin/capability filtering. | ||
| * @returns True when the caller must fail closed; false when tiebreak is safe. | ||
| */ | ||
| export declare function hasAmbiguousTarget(candidates: HermesTarget[]): boolean; | ||
| /** | ||
| * Validates a target's `webSocketDebuggerUrl` before connecting. The protocol | ||
| * must be `ws:`, the hostname must be loopback (`localhost`, `127.0.0.1`, `::1`, | ||
| * or `[::1]`), and the port must equal the resolved Metro port. Ported VERBATIM | ||
| * from the reference. | ||
| * | ||
| * @param rawUrl - The candidate `webSocketDebuggerUrl`. | ||
| * @param expectedPort - The resolved Metro port the URL must point at. | ||
| * @returns `{ ok: true }` when valid, otherwise a failure with a message. | ||
| */ | ||
| export declare function validateWebSocketUrl(rawUrl: string | undefined, expectedPort: number): { | ||
| ok: true; | ||
| } | { | ||
| ok: false; | ||
| message: string; | ||
| }; | ||
| /** | ||
| * Opens the target WebSocket, runs the identity probe, enforces the device pin | ||
| * (persisting it via `onPin` on first success, failing closed on mismatch), | ||
| * sends the user's method, matches the response by `id`, and always closes the | ||
| * socket in a `finally`. Ported faithfully from the reference. | ||
| * | ||
| * @param target - The validated, selected target. | ||
| * @param input - The original {@link RunHermesCdpInput} (method/params/timeout). | ||
| * @param pinnedDeviceId - The session pin, if set. | ||
| * @param onPin - Callback to persist the resolved pin on first success. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| export declare function executeVerifiedCdpCommand(target: HermesTarget, input: RunHermesCdpInput, pinnedDeviceId: string | undefined, onPin: ((logicalDeviceId: string) => void) | undefined): Promise<HermesCdpResult>; | ||
| export {}; | ||
| //# sourceMappingURL=hermes-cdp.d.cts.map |
| {"version":3,"file":"hermes-cdp.d.cts","sourceRoot":"","sources":["../../src/hermes/hermes-cdp.ts"],"names":[],"mappings":"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"} |
| /** | ||
| * Pure Hermes Chrome DevTools Protocol (CDP) core for `@metamask/device-mcp`. | ||
| * | ||
| * Ported from `@metamask/client-mcp-core`'s `src/tools/hermes-cdp.ts`, decoupled | ||
| * from that repo's `ToolContext`/`ErrorCodes`/`createToolError|Success`. Every | ||
| * function here takes EXPLICIT parameters (port, appId, pin) and returns a | ||
| * discriminated {@link HermesCdpResult} so this module is MCP-free and | ||
| * unit-testable. The tools layer is responsible for resolving session state and | ||
| * for PERSISTING the device pin — this file never imports `HermesSession`. | ||
| * | ||
| * Mechanism: the React Native Hermes runtime connects out to Metro's inspector | ||
| * proxy; Metro exposes debuggable targets over `http://localhost:<metroPort>/json` | ||
| * and a per-target `webSocketDebuggerUrl`. We speak real CDP over that WebSocket | ||
| * using the GLOBAL `WebSocket` (no library) and the global `fetch` for discovery. | ||
| * | ||
| * Five fail-closed safety checks are preserved VERBATIM from the reference: | ||
| * 1. Strict `appId` match (`===`, no substring, no `targets[0]` fallback). | ||
| * 2. `HermesInternal.getRuntimeProperties()` identity probe before the user method. | ||
| * 3. Device pin on `reactNative.logicalDeviceId` (set on first success). | ||
| * 4. Multi-device fail-closed (missing or >1 distinct `logicalDeviceId`). | ||
| * 5. WebSocket URL validation (protocol/hostname/port). | ||
| */ | ||
| /** | ||
| * Reported capability flags on a Metro Hermes target. | ||
| */ | ||
| type HermesCapabilities = { | ||
| /** Whether the target supports native page reloads (newer RN registrations). */ | ||
| nativePageReloads?: boolean; | ||
| /** Whether the target supports native source-code fetching. */ | ||
| nativeSourceCodeFetching?: boolean; | ||
| /** Whether the target supports multiple concurrent debuggers. */ | ||
| supportsMultipleDebuggers?: boolean; | ||
| }; | ||
| /** | ||
| * React Native-specific metadata attached to a Metro Hermes target. | ||
| */ | ||
| type HermesReactNative = { | ||
| /** Stable logical device identifier used for session pinning. */ | ||
| logicalDeviceId?: string; | ||
| /** Reported capability flags for this target. */ | ||
| capabilities?: HermesCapabilities; | ||
| }; | ||
| /** | ||
| * A debuggable Hermes target as reported by Metro's `/json` (or `/json/list`) | ||
| * discovery endpoint. Shape matches the reference implementation exactly. | ||
| */ | ||
| export type HermesTarget = { | ||
| /** Metro's per-target identifier. */ | ||
| id?: string; | ||
| /** Human-readable target title (used to filter the legacy synthetic page). */ | ||
| title?: string; | ||
| /** Human-readable target description. */ | ||
| description?: string; | ||
| /** The app bundle identifier this target belongs to (strict-matched). */ | ||
| appId?: string; | ||
| /** The `ws://` URL used to speak CDP with this target. */ | ||
| webSocketDebuggerUrl?: string; | ||
| /** React Native-specific metadata (logical device id + capabilities). */ | ||
| reactNative?: HermesReactNative; | ||
| }; | ||
| /** | ||
| * Discriminated result of a Hermes CDP operation. | ||
| * | ||
| * On success `result` carries the raw CDP `result` payload. On failure `code` | ||
| * is a STABLE string error code (see the `HERMES_*` constants) and `message` | ||
| * is a human-readable diagnostic preserving the reference's wording. | ||
| */ | ||
| export type HermesCdpResult = { | ||
| ok: true; | ||
| result: unknown; | ||
| } | { | ||
| ok: false; | ||
| code: string; | ||
| message: string; | ||
| }; | ||
| /** | ||
| * Parameters for {@link runHermesCdp}. | ||
| */ | ||
| export type RunHermesCdpInput = { | ||
| /** The CDP method to invoke (e.g. `Runtime.evaluate`). */ | ||
| method: string; | ||
| /** Optional CDP method parameters. */ | ||
| params?: Record<string, unknown>; | ||
| /** Per-call timeout in milliseconds for discovery and each CDP round-trip. */ | ||
| timeoutMs: number; | ||
| /** The resolved Metro inspector proxy port. */ | ||
| metroPort: number; | ||
| /** The expected app bundle identifier (strict-matched against targets). */ | ||
| appId: string; | ||
| /** The pinned `logicalDeviceId` for this session, if one has been set. */ | ||
| pinnedDeviceId?: string; | ||
| /** | ||
| * Callback invoked with the resolved `logicalDeviceId` on the FIRST | ||
| * successful identity probe so the caller can persist the pin. This file | ||
| * stays pure: it never imports or mutates session state itself. | ||
| */ | ||
| onPin?: (logicalDeviceId: string) => void; | ||
| }; | ||
| type TargetSelection = { | ||
| ok: true; | ||
| target: HermesTarget; | ||
| } | { | ||
| ok: false; | ||
| code: string; | ||
| message: string; | ||
| }; | ||
| /** | ||
| * No Hermes debug target matched the expected appId (after synthetic-title and | ||
| * pin filtering). Often means Metro is up but the app is not registered. | ||
| */ | ||
| export declare const HERMES_TARGET_NOT_FOUND = "HERMES_TARGET_NOT_FOUND"; | ||
| /** | ||
| * A target was found but could not be verified as the expected Hermes runtime | ||
| * (identity probe failed, missing `logicalDeviceId`, or non-JSON probe payload). | ||
| */ | ||
| export declare const HERMES_NOT_VERIFIED = "HERMES_NOT_VERIFIED"; | ||
| /** | ||
| * Multiple distinct logical devices are registered without a session pin (or a | ||
| * candidate is missing its `logicalDeviceId`); routing would be a guess, so we | ||
| * fail closed. | ||
| */ | ||
| export declare const HERMES_MULTIPLE_DEVICES = "HERMES_MULTIPLE_DEVICES"; | ||
| /** | ||
| * The resolved target's `logicalDeviceId` does not match the session pin. | ||
| */ | ||
| export declare const HERMES_DEVICE_PIN_MISMATCH = "HERMES_DEVICE_PIN_MISMATCH"; | ||
| /** | ||
| * The requested CDP method is in the destructive blocked set. | ||
| */ | ||
| export declare const HERMES_BLOCKED_METHOD = "HERMES_BLOCKED_METHOD"; | ||
| /** | ||
| * The target's `webSocketDebuggerUrl` failed protocol/hostname/port validation. | ||
| */ | ||
| export declare const HERMES_INVALID_WS_URL = "HERMES_INVALID_WS_URL"; | ||
| /** | ||
| * The underlying connection (discovery fetch or WebSocket) failed. | ||
| */ | ||
| export declare const HERMES_CONNECTION_FAILED = "HERMES_CONNECTION_FAILED"; | ||
| /** | ||
| * A discovery, socket-open, or CDP round-trip exceeded the configured timeout. | ||
| */ | ||
| export declare const HERMES_TIMEOUT = "HERMES_TIMEOUT"; | ||
| /** | ||
| * The user's CDP method returned a CDP-level error. | ||
| */ | ||
| export declare const HERMES_CDP_FAILED = "HERMES_CDP_FAILED"; | ||
| /** | ||
| * The global `WebSocket` constructor is unavailable in this Node runtime. | ||
| */ | ||
| export declare const HERMES_WEBSOCKET_UNAVAILABLE = "HERMES_WEBSOCKET_UNAVAILABLE"; | ||
| /** | ||
| * Title of the synthetic legacy "Improved Chrome Reloads" page that Metro | ||
| * registers alongside the real Hermes target. Filtered out during selection. | ||
| * Ported VERBATIM from the reference. | ||
| */ | ||
| export declare const LEGACY_SYNTHETIC_TITLE = "React Native Experimental (Improved Chrome Reloads)"; | ||
| /** | ||
| * JavaScript expression evaluated against a candidate target BEFORE the user's | ||
| * method, to verify it is a genuine Hermes runtime. Copied VERBATIM from the | ||
| * reference — do not alter the verification semantics. | ||
| */ | ||
| export declare const IDENTITY_PROBE_EXPR = "(function () {\n try {\n var hi = typeof HermesInternal !== 'undefined' ? HermesInternal : null;\n var p =\n hi && typeof hi.getRuntimeProperties === 'function'\n ? hi.getRuntimeProperties()\n : null;\n return JSON.stringify({\n isHermes: !!hi,\n ossVersion: p ? p['OSS Release Version'] : null,\n debuggerEnabled: p ? p['Debugger Enabled'] : null,\n });\n } catch (e) {\n return JSON.stringify({ error: String(e) });\n }\n})();"; | ||
| /** | ||
| * Orchestrates a single verified Hermes CDP command end to end. | ||
| * | ||
| * Resolves a target from Metro discovery, enforces all five fail-closed safety | ||
| * checks, runs the identity probe, persists the device pin via `onPin` on first | ||
| * success, sends the user's method, and returns the raw CDP `result`. The caller | ||
| * supplies the resolved port/appId/pin and persists the pin — this function is | ||
| * pure with respect to session state. | ||
| * | ||
| * @param input - Method, params, timeout, resolved port/appId/pin, and the pin | ||
| * persistence callback. See {@link RunHermesCdpInput}. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| export declare function runHermesCdp(input: RunHermesCdpInput): Promise<HermesCdpResult>; | ||
| /** | ||
| * Fetches debuggable Hermes targets from Metro, trying `/json` then falling | ||
| * back to `/json/list`. Each attempt is bounded by an `AbortController` | ||
| * timeout. The payload must be an array; non-object entries are filtered out. | ||
| * | ||
| * @param metroPort - The Metro inspector proxy port. | ||
| * @param timeoutMs - Per-attempt timeout in milliseconds. | ||
| * @returns The discovered targets (objects only). | ||
| * @throws The last error encountered if all discovery paths fail. | ||
| */ | ||
| export declare function fetchDiscoveryTargets(metroPort: number, timeoutMs: number): Promise<HermesTarget[]>; | ||
| /** | ||
| * Selects the single safe Hermes target for the expected app, applying the | ||
| * fail-closed selection algorithm VERBATIM from the reference: | ||
| * | ||
| * `webSocketDebuggerUrl` present → drop the synthetic legacy title → strict | ||
| * `appId` match (`===`) → pin filter → ambiguity check ({@link hasAmbiguousTarget}) | ||
| * → `nativePageReloads` tiebreak → last-in-array. | ||
| * | ||
| * @param targets - All discovered targets. | ||
| * @param expectedAppId - The strict app bundle identifier to match. | ||
| * @param pinnedDeviceId - The session pin, if set, used to filter candidates. | ||
| * @returns The selected target or a classified failure. | ||
| */ | ||
| export declare function selectHermesTarget(targets: HermesTarget[], expectedAppId: string, pinnedDeviceId: string | undefined): TargetSelection; | ||
| /** | ||
| * Determines whether a multi-candidate target list is too ambiguous to choose | ||
| * from safely. Returns true when: | ||
| * - any candidate is missing a `reactNative.logicalDeviceId` (we can't route safely), or | ||
| * - candidates resolve to more than one distinct `logicalDeviceId` (multi-device | ||
| * without a session pin — picking any one would silently bind the wrong device). | ||
| * | ||
| * Returns false when all candidates share the same logical device ID. In that | ||
| * case the caller falls through to the "last-in-array" tiebreak, matching the | ||
| * React Native convention that the most recent page registration wins (e.g., | ||
| * stale + fresh page after an in-app reload). | ||
| * | ||
| * @param candidates - Targets remaining after appId/pin/capability filtering. | ||
| * @returns True when the caller must fail closed; false when tiebreak is safe. | ||
| */ | ||
| export declare function hasAmbiguousTarget(candidates: HermesTarget[]): boolean; | ||
| /** | ||
| * Validates a target's `webSocketDebuggerUrl` before connecting. The protocol | ||
| * must be `ws:`, the hostname must be loopback (`localhost`, `127.0.0.1`, `::1`, | ||
| * or `[::1]`), and the port must equal the resolved Metro port. Ported VERBATIM | ||
| * from the reference. | ||
| * | ||
| * @param rawUrl - The candidate `webSocketDebuggerUrl`. | ||
| * @param expectedPort - The resolved Metro port the URL must point at. | ||
| * @returns `{ ok: true }` when valid, otherwise a failure with a message. | ||
| */ | ||
| export declare function validateWebSocketUrl(rawUrl: string | undefined, expectedPort: number): { | ||
| ok: true; | ||
| } | { | ||
| ok: false; | ||
| message: string; | ||
| }; | ||
| /** | ||
| * Opens the target WebSocket, runs the identity probe, enforces the device pin | ||
| * (persisting it via `onPin` on first success, failing closed on mismatch), | ||
| * sends the user's method, matches the response by `id`, and always closes the | ||
| * socket in a `finally`. Ported faithfully from the reference. | ||
| * | ||
| * @param target - The validated, selected target. | ||
| * @param input - The original {@link RunHermesCdpInput} (method/params/timeout). | ||
| * @param pinnedDeviceId - The session pin, if set. | ||
| * @param onPin - Callback to persist the resolved pin on first success. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| export declare function executeVerifiedCdpCommand(target: HermesTarget, input: RunHermesCdpInput, pinnedDeviceId: string | undefined, onPin: ((logicalDeviceId: string) => void) | undefined): Promise<HermesCdpResult>; | ||
| export {}; | ||
| //# sourceMappingURL=hermes-cdp.d.mts.map |
| {"version":3,"file":"hermes-cdp.d.mts","sourceRoot":"","sources":["../../src/hermes/hermes-cdp.ts"],"names":[],"mappings":"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"} |
| /** | ||
| * Pure Hermes Chrome DevTools Protocol (CDP) core for `@metamask/device-mcp`. | ||
| * | ||
| * Ported from `@metamask/client-mcp-core`'s `src/tools/hermes-cdp.ts`, decoupled | ||
| * from that repo's `ToolContext`/`ErrorCodes`/`createToolError|Success`. Every | ||
| * function here takes EXPLICIT parameters (port, appId, pin) and returns a | ||
| * discriminated {@link HermesCdpResult} so this module is MCP-free and | ||
| * unit-testable. The tools layer is responsible for resolving session state and | ||
| * for PERSISTING the device pin — this file never imports `HermesSession`. | ||
| * | ||
| * Mechanism: the React Native Hermes runtime connects out to Metro's inspector | ||
| * proxy; Metro exposes debuggable targets over `http://localhost:<metroPort>/json` | ||
| * and a per-target `webSocketDebuggerUrl`. We speak real CDP over that WebSocket | ||
| * using the GLOBAL `WebSocket` (no library) and the global `fetch` for discovery. | ||
| * | ||
| * Five fail-closed safety checks are preserved VERBATIM from the reference: | ||
| * 1. Strict `appId` match (`===`, no substring, no `targets[0]` fallback). | ||
| * 2. `HermesInternal.getRuntimeProperties()` identity probe before the user method. | ||
| * 3. Device pin on `reactNative.logicalDeviceId` (set on first success). | ||
| * 4. Multi-device fail-closed (missing or >1 distinct `logicalDeviceId`). | ||
| * 5. WebSocket URL validation (protocol/hostname/port). | ||
| */ | ||
| /** | ||
| * No Hermes debug target matched the expected appId (after synthetic-title and | ||
| * pin filtering). Often means Metro is up but the app is not registered. | ||
| */ | ||
| export const HERMES_TARGET_NOT_FOUND = 'HERMES_TARGET_NOT_FOUND'; | ||
| /** | ||
| * A target was found but could not be verified as the expected Hermes runtime | ||
| * (identity probe failed, missing `logicalDeviceId`, or non-JSON probe payload). | ||
| */ | ||
| export const HERMES_NOT_VERIFIED = 'HERMES_NOT_VERIFIED'; | ||
| /** | ||
| * Multiple distinct logical devices are registered without a session pin (or a | ||
| * candidate is missing its `logicalDeviceId`); routing would be a guess, so we | ||
| * fail closed. | ||
| */ | ||
| export const HERMES_MULTIPLE_DEVICES = 'HERMES_MULTIPLE_DEVICES'; | ||
| /** | ||
| * The resolved target's `logicalDeviceId` does not match the session pin. | ||
| */ | ||
| export const HERMES_DEVICE_PIN_MISMATCH = 'HERMES_DEVICE_PIN_MISMATCH'; | ||
| /** | ||
| * The requested CDP method is in the destructive blocked set. | ||
| */ | ||
| export const HERMES_BLOCKED_METHOD = 'HERMES_BLOCKED_METHOD'; | ||
| /** | ||
| * The target's `webSocketDebuggerUrl` failed protocol/hostname/port validation. | ||
| */ | ||
| export const HERMES_INVALID_WS_URL = 'HERMES_INVALID_WS_URL'; | ||
| /** | ||
| * The underlying connection (discovery fetch or WebSocket) failed. | ||
| */ | ||
| export const HERMES_CONNECTION_FAILED = 'HERMES_CONNECTION_FAILED'; | ||
| /** | ||
| * A discovery, socket-open, or CDP round-trip exceeded the configured timeout. | ||
| */ | ||
| export const HERMES_TIMEOUT = 'HERMES_TIMEOUT'; | ||
| /** | ||
| * The user's CDP method returned a CDP-level error. | ||
| */ | ||
| export const HERMES_CDP_FAILED = 'HERMES_CDP_FAILED'; | ||
| /** | ||
| * The global `WebSocket` constructor is unavailable in this Node runtime. | ||
| */ | ||
| export const HERMES_WEBSOCKET_UNAVAILABLE = 'HERMES_WEBSOCKET_UNAVAILABLE'; | ||
| /** | ||
| * CDP methods blocked for safety. Ported VERBATIM from the reference — do not | ||
| * weaken. | ||
| */ | ||
| const HERMES_BLOCKED_METHODS = new Set([ | ||
| 'Runtime.terminateExecution', | ||
| 'Inspector.detached', | ||
| ]); | ||
| const DISCOVERY_PATHS = ['/json', '/json/list']; | ||
| const ALLOWED_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']); | ||
| /** | ||
| * Title of the synthetic legacy "Improved Chrome Reloads" page that Metro | ||
| * registers alongside the real Hermes target. Filtered out during selection. | ||
| * Ported VERBATIM from the reference. | ||
| */ | ||
| export const LEGACY_SYNTHETIC_TITLE = 'React Native Experimental (Improved Chrome Reloads)'; | ||
| /** | ||
| * JavaScript expression evaluated against a candidate target BEFORE the user's | ||
| * method, to verify it is a genuine Hermes runtime. Copied VERBATIM from the | ||
| * reference — do not alter the verification semantics. | ||
| */ | ||
| export const IDENTITY_PROBE_EXPR = `(function () { | ||
| try { | ||
| var hi = typeof HermesInternal !== 'undefined' ? HermesInternal : null; | ||
| var p = | ||
| hi && typeof hi.getRuntimeProperties === 'function' | ||
| ? hi.getRuntimeProperties() | ||
| : null; | ||
| return JSON.stringify({ | ||
| isHermes: !!hi, | ||
| ossVersion: p ? p['OSS Release Version'] : null, | ||
| debuggerEnabled: p ? p['Debugger Enabled'] : null, | ||
| }); | ||
| } catch (e) { | ||
| return JSON.stringify({ error: String(e) }); | ||
| } | ||
| })();`; | ||
| /** | ||
| * Orchestrates a single verified Hermes CDP command end to end. | ||
| * | ||
| * Resolves a target from Metro discovery, enforces all five fail-closed safety | ||
| * checks, runs the identity probe, persists the device pin via `onPin` on first | ||
| * success, sends the user's method, and returns the raw CDP `result`. The caller | ||
| * supplies the resolved port/appId/pin and persists the pin — this function is | ||
| * pure with respect to session state. | ||
| * | ||
| * @param input - Method, params, timeout, resolved port/appId/pin, and the pin | ||
| * persistence callback. See {@link RunHermesCdpInput}. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| export async function runHermesCdp(input) { | ||
| if (HERMES_BLOCKED_METHODS.has(input.method)) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_BLOCKED_METHOD, | ||
| message: `CDP method "${input.method}" is blocked for safety. ` + | ||
| `Blocked methods: ${[...HERMES_BLOCKED_METHODS].join(', ')}`, | ||
| }; | ||
| } | ||
| if (typeof WebSocket !== 'function') { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_WEBSOCKET_UNAVAILABLE, | ||
| message: 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' + | ||
| 'NODE_OPTIONS="--experimental-websocket" (or use Node 22+).', | ||
| }; | ||
| } | ||
| try { | ||
| const targets = await fetchDiscoveryTargets(input.metroPort, input.timeoutMs); | ||
| const selection = selectHermesTarget(targets, input.appId, input.pinnedDeviceId); | ||
| if (!selection.ok) { | ||
| return { ok: false, code: selection.code, message: selection.message }; | ||
| } | ||
| const validation = validateWebSocketUrl(selection.target.webSocketDebuggerUrl, input.metroPort); | ||
| if (!validation.ok) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_INVALID_WS_URL, | ||
| message: validation.message, | ||
| }; | ||
| } | ||
| return await executeVerifiedCdpCommand(selection.target, input, input.pinnedDeviceId, input.onPin); | ||
| } | ||
| catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| return { | ||
| ok: false, | ||
| code: HERMES_CONNECTION_FAILED, | ||
| message: `Hermes CDP connection failed: ${message}`, | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Fetches debuggable Hermes targets from Metro, trying `/json` then falling | ||
| * back to `/json/list`. Each attempt is bounded by an `AbortController` | ||
| * timeout. The payload must be an array; non-object entries are filtered out. | ||
| * | ||
| * @param metroPort - The Metro inspector proxy port. | ||
| * @param timeoutMs - Per-attempt timeout in milliseconds. | ||
| * @returns The discovered targets (objects only). | ||
| * @throws The last error encountered if all discovery paths fail. | ||
| */ | ||
| export async function fetchDiscoveryTargets(metroPort, timeoutMs) { | ||
| let lastError; | ||
| for (const path of DISCOVERY_PATHS) { | ||
| const controller = new AbortController(); | ||
| const timer = setTimeout(() => controller.abort(), timeoutMs); | ||
| try { | ||
| const response = await fetch(`http://localhost:${metroPort}${path}`, { | ||
| signal: controller.signal, | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`Metro ${path} returned HTTP ${response.status}`); | ||
| } | ||
| const payload = await response.json(); | ||
| if (!Array.isArray(payload)) { | ||
| throw new Error(`Metro ${path} returned a non-array response`); | ||
| } | ||
| return payload.filter(isHermesTarget); | ||
| } | ||
| catch (error) { | ||
| lastError = error; | ||
| } | ||
| finally { | ||
| clearTimeout(timer); | ||
| } | ||
| } | ||
| throw lastError instanceof Error ? lastError : new Error(String(lastError)); | ||
| } | ||
| /** | ||
| * Narrows an unknown discovery entry to a {@link HermesTarget} (object guard). | ||
| * | ||
| * @param value - A raw entry from the Metro discovery payload. | ||
| * @returns True when `value` is a non-null object. | ||
| */ | ||
| function isHermesTarget(value) { | ||
| return typeof value === 'object' && value !== null; | ||
| } | ||
| /** | ||
| * Selects the single safe Hermes target for the expected app, applying the | ||
| * fail-closed selection algorithm VERBATIM from the reference: | ||
| * | ||
| * `webSocketDebuggerUrl` present → drop the synthetic legacy title → strict | ||
| * `appId` match (`===`) → pin filter → ambiguity check ({@link hasAmbiguousTarget}) | ||
| * → `nativePageReloads` tiebreak → last-in-array. | ||
| * | ||
| * @param targets - All discovered targets. | ||
| * @param expectedAppId - The strict app bundle identifier to match. | ||
| * @param pinnedDeviceId - The session pin, if set, used to filter candidates. | ||
| * @returns The selected target or a classified failure. | ||
| */ | ||
| export function selectHermesTarget(targets, expectedAppId, pinnedDeviceId) { | ||
| const seenAppIds = [...new Set(targets.map((target) => target.appId))].filter((appId) => typeof appId === 'string'); | ||
| let candidates = targets | ||
| .filter((target) => Boolean(target.webSocketDebuggerUrl)) | ||
| .filter((target) => target.title !== LEGACY_SYNTHETIC_TITLE) | ||
| .filter((target) => target.appId === expectedAppId); | ||
| if (pinnedDeviceId) { | ||
| candidates = candidates.filter((target) => target.reactNative?.logicalDeviceId === pinnedDeviceId); | ||
| } | ||
| if (candidates.length === 0) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_TARGET_NOT_FOUND, | ||
| message: `No Hermes debug target found for appId ${expectedAppId}. Saw appIds: ${JSON.stringify(seenAppIds)}`, | ||
| }; | ||
| } | ||
| if (hasAmbiguousTarget(candidates)) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_MULTIPLE_DEVICES, | ||
| message: `Ambiguous Hermes target after pin filtering. Candidates: ${candidates | ||
| .map((target) => `${target.id ?? '<unknown>'} (device=${target.reactNative?.logicalDeviceId ?? '<missing>'})`) | ||
| .join(', ')}`, | ||
| }; | ||
| } | ||
| // Same-device tiebreak: prefer pages with nativePageReloads (newer RN page | ||
| // registrations) over stale entries left behind after in-app reloads. | ||
| const nativeReloadTargets = candidates.filter((target) => target.reactNative?.capabilities?.nativePageReloads === true); | ||
| if (nativeReloadTargets.length > 0) { | ||
| candidates = nativeReloadTargets; | ||
| } | ||
| return { ok: true, target: candidates[candidates.length - 1] }; | ||
| } | ||
| /** | ||
| * Determines whether a multi-candidate target list is too ambiguous to choose | ||
| * from safely. Returns true when: | ||
| * - any candidate is missing a `reactNative.logicalDeviceId` (we can't route safely), or | ||
| * - candidates resolve to more than one distinct `logicalDeviceId` (multi-device | ||
| * without a session pin — picking any one would silently bind the wrong device). | ||
| * | ||
| * Returns false when all candidates share the same logical device ID. In that | ||
| * case the caller falls through to the "last-in-array" tiebreak, matching the | ||
| * React Native convention that the most recent page registration wins (e.g., | ||
| * stale + fresh page after an in-app reload). | ||
| * | ||
| * @param candidates - Targets remaining after appId/pin/capability filtering. | ||
| * @returns True when the caller must fail closed; false when tiebreak is safe. | ||
| */ | ||
| export function hasAmbiguousTarget(candidates) { | ||
| if (candidates.length <= 1) { | ||
| return false; | ||
| } | ||
| const deviceIds = candidates.map((target) => target.reactNative?.logicalDeviceId); | ||
| // Any candidate missing a logicalDeviceId → can't safely route. | ||
| if (deviceIds.some((id) => !id)) { | ||
| return true; | ||
| } | ||
| // Multiple distinct device IDs → multi-device without pin. | ||
| return new Set(deviceIds).size > 1; | ||
| } | ||
| /** | ||
| * Validates a target's `webSocketDebuggerUrl` before connecting. The protocol | ||
| * must be `ws:`, the hostname must be loopback (`localhost`, `127.0.0.1`, `::1`, | ||
| * or `[::1]`), and the port must equal the resolved Metro port. Ported VERBATIM | ||
| * from the reference. | ||
| * | ||
| * @param rawUrl - The candidate `webSocketDebuggerUrl`. | ||
| * @param expectedPort - The resolved Metro port the URL must point at. | ||
| * @returns `{ ok: true }` when valid, otherwise a failure with a message. | ||
| */ | ||
| export function validateWebSocketUrl(rawUrl, expectedPort) { | ||
| if (!rawUrl) { | ||
| return { ok: false, message: 'webSocketDebuggerUrl is missing' }; | ||
| } | ||
| let parsed; | ||
| try { | ||
| parsed = new URL(rawUrl); | ||
| } | ||
| catch { | ||
| return { | ||
| ok: false, | ||
| message: `webSocketDebuggerUrl is not a valid URL: ${rawUrl}`, | ||
| }; | ||
| } | ||
| if (parsed.protocol !== 'ws:') { | ||
| return { ok: false, message: `Unexpected protocol '${parsed.protocol}'` }; | ||
| } | ||
| if (!ALLOWED_HOSTNAMES.has(parsed.hostname)) { | ||
| return { ok: false, message: `Unexpected hostname '${parsed.hostname}'` }; | ||
| } | ||
| if (Number(parsed.port) !== expectedPort) { | ||
| return { | ||
| ok: false, | ||
| message: `Port mismatch: target=${parsed.port} expected=${expectedPort}`, | ||
| }; | ||
| } | ||
| return { ok: true }; | ||
| } | ||
| /** | ||
| * Opens the target WebSocket, runs the identity probe, enforces the device pin | ||
| * (persisting it via `onPin` on first success, failing closed on mismatch), | ||
| * sends the user's method, matches the response by `id`, and always closes the | ||
| * socket in a `finally`. Ported faithfully from the reference. | ||
| * | ||
| * @param target - The validated, selected target. | ||
| * @param input - The original {@link RunHermesCdpInput} (method/params/timeout). | ||
| * @param pinnedDeviceId - The session pin, if set. | ||
| * @param onPin - Callback to persist the resolved pin on first success. | ||
| * @returns A discriminated {@link HermesCdpResult}. | ||
| */ | ||
| export async function executeVerifiedCdpCommand(target, input, pinnedDeviceId, onPin) { | ||
| if (!target.webSocketDebuggerUrl) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_INVALID_WS_URL, | ||
| message: 'Target is missing webSocketDebuggerUrl', | ||
| }; | ||
| } | ||
| if (typeof WebSocket !== 'function') { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_WEBSOCKET_UNAVAILABLE, | ||
| message: 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' + | ||
| 'NODE_OPTIONS="--experimental-websocket" (or use Node 22+).', | ||
| }; | ||
| } | ||
| const socket = new WebSocket(target.webSocketDebuggerUrl); | ||
| let nextId = 1; | ||
| try { | ||
| await waitForSocketOpen(socket, input.timeoutMs); | ||
| const probe = await runIdentityProbe(socket, input.timeoutMs, nextId); | ||
| nextId += 1; | ||
| if (!probe.ok) { | ||
| return { ok: false, code: probe.code, message: probe.message }; | ||
| } | ||
| const targetDeviceId = target.reactNative?.logicalDeviceId; | ||
| if (!targetDeviceId) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_NOT_VERIFIED, | ||
| message: 'Target is missing reactNative.logicalDeviceId', | ||
| }; | ||
| } | ||
| if (!pinnedDeviceId) { | ||
| onPin?.(targetDeviceId); | ||
| } | ||
| else if (pinnedDeviceId !== targetDeviceId) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_DEVICE_PIN_MISMATCH, | ||
| message: `Hermes target device ${targetDeviceId} does not match session pin ${pinnedDeviceId}`, | ||
| }; | ||
| } | ||
| const response = await sendUserMethod(socket, input, nextId); | ||
| nextId += 1; | ||
| if (isCdpErrorResponse(response)) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_CDP_FAILED, | ||
| message: `Hermes CDP "${input.method}" failed: ${formatCdpError(response)}`, | ||
| }; | ||
| } | ||
| return { ok: true, result: response.result }; | ||
| } | ||
| finally { | ||
| closeSocket(socket); | ||
| } | ||
| } | ||
| /** | ||
| * Runs the {@link IDENTITY_PROBE_EXPR} against an open socket and verifies the | ||
| * response confirms a Hermes runtime. Distinguishes timeout from connection | ||
| * failure and unverified payloads. | ||
| * | ||
| * @param socket - The open target WebSocket. | ||
| * @param timeoutMs - Round-trip timeout in milliseconds. | ||
| * @param id - The CDP message id to use for this probe. | ||
| * @returns A {@link ProbeResult}. | ||
| */ | ||
| async function runIdentityProbe(socket, timeoutMs, id) { | ||
| socket.send(JSON.stringify({ | ||
| id, | ||
| method: 'Runtime.evaluate', | ||
| params: { expression: IDENTITY_PROBE_EXPR, returnByValue: true }, | ||
| })); | ||
| let response; | ||
| try { | ||
| response = await waitForCdpResponse(socket, id, timeoutMs); | ||
| } | ||
| catch (error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| if (message.includes('timed out')) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_TIMEOUT, | ||
| message: `Identity probe timed out after ${timeoutMs}ms`, | ||
| }; | ||
| } | ||
| return { | ||
| ok: false, | ||
| code: HERMES_CONNECTION_FAILED, | ||
| message: `Hermes CDP connection failed: ${message}`, | ||
| }; | ||
| } | ||
| if (isCdpErrorResponse(response)) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_NOT_VERIFIED, | ||
| message: `Identity probe failed: ${formatCdpError(response)}`, | ||
| }; | ||
| } | ||
| const remoteResult = getProbeRemoteResult(response); | ||
| if (remoteResult.subtype === 'error') { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_NOT_VERIFIED, | ||
| message: 'Identity probe evaluation returned an error subtype', | ||
| }; | ||
| } | ||
| if (remoteResult.value === undefined || remoteResult.value === null) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_NOT_VERIFIED, | ||
| message: 'Identity probe response missing result.value', | ||
| }; | ||
| } | ||
| const raw = typeof remoteResult.value === 'string' | ||
| ? remoteResult.value | ||
| : JSON.stringify(remoteResult.value); | ||
| let parsed; | ||
| try { | ||
| parsed = JSON.parse(raw); | ||
| } | ||
| catch { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_NOT_VERIFIED, | ||
| message: `Identity probe returned non-JSON: ${raw}`, | ||
| }; | ||
| } | ||
| if (!isHermesProbePayload(parsed)) { | ||
| return { | ||
| ok: false, | ||
| code: HERMES_NOT_VERIFIED, | ||
| message: `Identity probe did not verify Hermes runtime: ${raw}`, | ||
| }; | ||
| } | ||
| return { ok: true }; | ||
| } | ||
| /** | ||
| * Safely extracts the nested CDP `result.result` (the remote object) from a | ||
| * `Runtime.evaluate` success response. | ||
| * | ||
| * @param response - A CDP success response. | ||
| * @returns The remote object's `subtype`/`value`, or an empty object. | ||
| */ | ||
| function getProbeRemoteResult(response) { | ||
| if (typeof response.result !== 'object' || response.result === null) { | ||
| return {}; | ||
| } | ||
| const outer = response.result; | ||
| if (typeof outer.result !== 'object' || outer.result === null) { | ||
| return {}; | ||
| } | ||
| return outer.result; | ||
| } | ||
| /** | ||
| * Narrows a parsed probe payload to one confirming a Hermes runtime. | ||
| * | ||
| * @param value - The parsed probe JSON payload. | ||
| * @returns True when `isHermes === true`. | ||
| */ | ||
| function isHermesProbePayload(value) { | ||
| return (typeof value === 'object' && | ||
| value !== null && | ||
| value.isHermes === true); | ||
| } | ||
| /** | ||
| * Sends the user's CDP method over an open, verified socket and awaits the | ||
| * matching response by id. | ||
| * | ||
| * @param socket - The open, verified target WebSocket. | ||
| * @param input - The original input carrying method/params/timeout. | ||
| * @param id - The CDP message id to use. | ||
| * @returns The matching CDP success or error response. | ||
| */ | ||
| async function sendUserMethod(socket, input, id) { | ||
| socket.send(JSON.stringify({ | ||
| id, | ||
| method: input.method, | ||
| params: input.params ?? {}, | ||
| })); | ||
| return await waitForCdpResponse(socket, id, input.timeoutMs); | ||
| } | ||
| /** | ||
| * Resolves once the socket reaches the OPEN state, rejecting on error or | ||
| * timeout. Listeners are always cleaned up. | ||
| * | ||
| * @param socket - The connecting target WebSocket. | ||
| * @param timeoutMs - Connection timeout in milliseconds. | ||
| * @returns A promise that resolves when the socket is open. | ||
| */ | ||
| async function waitForSocketOpen(socket, timeoutMs) { | ||
| if (socket.readyState === WebSocket.OPEN) { | ||
| return Promise.resolve(); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| let cleanup = () => undefined; | ||
| const handleOpen = () => { | ||
| cleanup(); | ||
| resolve(); | ||
| }; | ||
| const handleError = () => { | ||
| cleanup(); | ||
| reject(new Error('WebSocket connection error')); | ||
| }; | ||
| const timer = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error(`WebSocket connection timed out after ${timeoutMs}ms`)); | ||
| }, timeoutMs); | ||
| cleanup = () => { | ||
| clearTimeout(timer); | ||
| socket.removeEventListener('open', handleOpen); | ||
| socket.removeEventListener('error', handleError); | ||
| }; | ||
| socket.addEventListener('open', handleOpen); | ||
| socket.addEventListener('error', handleError); | ||
| }); | ||
| } | ||
| /** | ||
| * Resolves with the CDP response whose `id` matches, rejecting on socket error, | ||
| * premature close, or timeout. Listeners are always cleaned up. | ||
| * | ||
| * @param socket - The open target WebSocket. | ||
| * @param id - The CDP message id to match. | ||
| * @param timeoutMs - Round-trip timeout in milliseconds. | ||
| * @returns The matching CDP success or error response. | ||
| */ | ||
| async function waitForCdpResponse(socket, id, timeoutMs) { | ||
| return new Promise((resolve, reject) => { | ||
| let cleanup = () => undefined; | ||
| const handleMessage = (event) => { | ||
| try { | ||
| const parsed = parseCdpResponse(event.data); | ||
| if (parsed?.id !== id) { | ||
| return; | ||
| } | ||
| cleanup(); | ||
| resolve(parsed); | ||
| } | ||
| catch (error) { | ||
| cleanup(); | ||
| reject(error); | ||
| } | ||
| }; | ||
| const handleError = () => { | ||
| cleanup(); | ||
| reject(new Error('WebSocket message error')); | ||
| }; | ||
| const handleClose = () => { | ||
| cleanup(); | ||
| reject(new Error('WebSocket closed before CDP response')); | ||
| }; | ||
| const timer = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error(`Hermes CDP call timed out after ${timeoutMs}ms`)); | ||
| }, timeoutMs); | ||
| cleanup = () => { | ||
| clearTimeout(timer); | ||
| socket.removeEventListener('message', handleMessage); | ||
| socket.removeEventListener('error', handleError); | ||
| socket.removeEventListener('close', handleClose); | ||
| }; | ||
| socket.addEventListener('message', handleMessage); | ||
| socket.addEventListener('error', handleError); | ||
| socket.addEventListener('close', handleClose); | ||
| }); | ||
| } | ||
| /** | ||
| * Parses a raw WebSocket frame into a CDP response, returning undefined when it | ||
| * is not an addressable (`id`-bearing) message. | ||
| * | ||
| * @param data - The raw `MessageEvent.data`. | ||
| * @returns A CDP success/error response, or undefined when not addressable. | ||
| * @throws When the frame is not a text frame. | ||
| */ | ||
| function parseCdpResponse(data) { | ||
| if (typeof data !== 'string') { | ||
| throw new Error('Hermes CDP returned a non-text WebSocket frame'); | ||
| } | ||
| const parsed = JSON.parse(data); | ||
| if (typeof parsed !== 'object' || parsed === null) { | ||
| return undefined; | ||
| } | ||
| const candidate = parsed; | ||
| if (typeof candidate.id !== 'number') { | ||
| return undefined; | ||
| } | ||
| if (isCdpError(candidate.error)) { | ||
| return { id: candidate.id, error: candidate.error }; | ||
| } | ||
| return { id: candidate.id, result: candidate.result }; | ||
| } | ||
| /** | ||
| * Narrows an unknown value to a CDP error object. | ||
| * | ||
| * @param value - The candidate `error` field. | ||
| * @returns True when `value` is a non-null object. | ||
| */ | ||
| function isCdpError(value) { | ||
| return typeof value === 'object' && value !== null; | ||
| } | ||
| /** | ||
| * Discriminates a CDP response as an error response. | ||
| * | ||
| * @param response - A CDP success or error response. | ||
| * @returns True when the response carries an `error` field. | ||
| */ | ||
| function isCdpErrorResponse(response) { | ||
| return 'error' in response; | ||
| } | ||
| /** | ||
| * Formats a CDP error response into a single human-readable line. | ||
| * | ||
| * @param response - The CDP error response. | ||
| * @returns A combined message including code and data when present. | ||
| */ | ||
| function formatCdpError(response) { | ||
| const parts = [response.error.message ?? 'Unknown CDP error']; | ||
| if (typeof response.error.code === 'number') { | ||
| parts.push(`code ${response.error.code}`); | ||
| } | ||
| if (response.error.data !== undefined) { | ||
| parts.push(JSON.stringify(response.error.data)); | ||
| } | ||
| return parts.join(' - '); | ||
| } | ||
| /** | ||
| * Closes the socket if it is still connecting or open. | ||
| * | ||
| * @param socket - The target WebSocket. | ||
| */ | ||
| function closeSocket(socket) { | ||
| if (socket.readyState === WebSocket.CONNECTING || | ||
| socket.readyState === WebSocket.OPEN) { | ||
| socket.close(); | ||
| } | ||
| } | ||
| //# sourceMappingURL=hermes-cdp.mjs.map |
| {"version":3,"file":"hermes-cdp.mjs","sourceRoot":"","sources":["../../src/hermes/hermes-cdp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure Hermes Chrome DevTools Protocol (CDP) core for `@metamask/device-mcp`.\n *\n * Ported from `@metamask/client-mcp-core`'s `src/tools/hermes-cdp.ts`, decoupled\n * from that repo's `ToolContext`/`ErrorCodes`/`createToolError|Success`. Every\n * function here takes EXPLICIT parameters (port, appId, pin) and returns a\n * discriminated {@link HermesCdpResult} so this module is MCP-free and\n * unit-testable. The tools layer is responsible for resolving session state and\n * for PERSISTING the device pin — this file never imports `HermesSession`.\n *\n * Mechanism: the React Native Hermes runtime connects out to Metro's inspector\n * proxy; Metro exposes debuggable targets over `http://localhost:<metroPort>/json`\n * and a per-target `webSocketDebuggerUrl`. We speak real CDP over that WebSocket\n * using the GLOBAL `WebSocket` (no library) and the global `fetch` for discovery.\n *\n * Five fail-closed safety checks are preserved VERBATIM from the reference:\n * 1. Strict `appId` match (`===`, no substring, no `targets[0]` fallback).\n * 2. `HermesInternal.getRuntimeProperties()` identity probe before the user method.\n * 3. Device pin on `reactNative.logicalDeviceId` (set on first success).\n * 4. Multi-device fail-closed (missing or >1 distinct `logicalDeviceId`).\n * 5. WebSocket URL validation (protocol/hostname/port).\n */\n\n/**\n * Reported capability flags on a Metro Hermes target.\n */\ntype HermesCapabilities = {\n /** Whether the target supports native page reloads (newer RN registrations). */\n nativePageReloads?: boolean;\n /** Whether the target supports native source-code fetching. */\n nativeSourceCodeFetching?: boolean;\n /** Whether the target supports multiple concurrent debuggers. */\n supportsMultipleDebuggers?: boolean;\n};\n\n/**\n * React Native-specific metadata attached to a Metro Hermes target.\n */\ntype HermesReactNative = {\n /** Stable logical device identifier used for session pinning. */\n logicalDeviceId?: string;\n /** Reported capability flags for this target. */\n capabilities?: HermesCapabilities;\n};\n\n/**\n * A debuggable Hermes target as reported by Metro's `/json` (or `/json/list`)\n * discovery endpoint. Shape matches the reference implementation exactly.\n */\nexport type HermesTarget = {\n /** Metro's per-target identifier. */\n id?: string;\n /** Human-readable target title (used to filter the legacy synthetic page). */\n title?: string;\n /** Human-readable target description. */\n description?: string;\n /** The app bundle identifier this target belongs to (strict-matched). */\n appId?: string;\n /** The `ws://` URL used to speak CDP with this target. */\n webSocketDebuggerUrl?: string;\n /** React Native-specific metadata (logical device id + capabilities). */\n reactNative?: HermesReactNative;\n};\n\n/**\n * Discriminated result of a Hermes CDP operation.\n *\n * On success `result` carries the raw CDP `result` payload. On failure `code`\n * is a STABLE string error code (see the `HERMES_*` constants) and `message`\n * is a human-readable diagnostic preserving the reference's wording.\n */\nexport type HermesCdpResult =\n | { ok: true; result: unknown }\n | { ok: false; code: string; message: string };\n\n/**\n * Parameters for {@link runHermesCdp}.\n */\nexport type RunHermesCdpInput = {\n /** The CDP method to invoke (e.g. `Runtime.evaluate`). */\n method: string;\n /** Optional CDP method parameters. */\n params?: Record<string, unknown>;\n /** Per-call timeout in milliseconds for discovery and each CDP round-trip. */\n timeoutMs: number;\n /** The resolved Metro inspector proxy port. */\n metroPort: number;\n /** The expected app bundle identifier (strict-matched against targets). */\n appId: string;\n /** The pinned `logicalDeviceId` for this session, if one has been set. */\n pinnedDeviceId?: string;\n /**\n * Callback invoked with the resolved `logicalDeviceId` on the FIRST\n * successful identity probe so the caller can persist the pin. This file\n * stays pure: it never imports or mutates session state itself.\n */\n onPin?: (logicalDeviceId: string) => void;\n};\n\ntype CdpSuccessResponse = {\n id: number;\n result?: unknown;\n};\n\ntype CdpErrorResponse = {\n id: number;\n error: {\n message?: string;\n code?: number;\n data?: unknown;\n };\n};\n\ntype TargetSelection =\n | { ok: true; target: HermesTarget }\n | { ok: false; code: string; message: string };\n\ntype ProbeResult = { ok: true } | { ok: false; code: string; message: string };\n\n/**\n * No Hermes debug target matched the expected appId (after synthetic-title and\n * pin filtering). Often means Metro is up but the app is not registered.\n */\nexport const HERMES_TARGET_NOT_FOUND = 'HERMES_TARGET_NOT_FOUND';\n\n/**\n * A target was found but could not be verified as the expected Hermes runtime\n * (identity probe failed, missing `logicalDeviceId`, or non-JSON probe payload).\n */\nexport const HERMES_NOT_VERIFIED = 'HERMES_NOT_VERIFIED';\n\n/**\n * Multiple distinct logical devices are registered without a session pin (or a\n * candidate is missing its `logicalDeviceId`); routing would be a guess, so we\n * fail closed.\n */\nexport const HERMES_MULTIPLE_DEVICES = 'HERMES_MULTIPLE_DEVICES';\n\n/**\n * The resolved target's `logicalDeviceId` does not match the session pin.\n */\nexport const HERMES_DEVICE_PIN_MISMATCH = 'HERMES_DEVICE_PIN_MISMATCH';\n\n/**\n * The requested CDP method is in the destructive blocked set.\n */\nexport const HERMES_BLOCKED_METHOD = 'HERMES_BLOCKED_METHOD';\n\n/**\n * The target's `webSocketDebuggerUrl` failed protocol/hostname/port validation.\n */\nexport const HERMES_INVALID_WS_URL = 'HERMES_INVALID_WS_URL';\n\n/**\n * The underlying connection (discovery fetch or WebSocket) failed.\n */\nexport const HERMES_CONNECTION_FAILED = 'HERMES_CONNECTION_FAILED';\n\n/**\n * A discovery, socket-open, or CDP round-trip exceeded the configured timeout.\n */\nexport const HERMES_TIMEOUT = 'HERMES_TIMEOUT';\n\n/**\n * The user's CDP method returned a CDP-level error.\n */\nexport const HERMES_CDP_FAILED = 'HERMES_CDP_FAILED';\n\n/**\n * The global `WebSocket` constructor is unavailable in this Node runtime.\n */\nexport const HERMES_WEBSOCKET_UNAVAILABLE = 'HERMES_WEBSOCKET_UNAVAILABLE';\n\n/**\n * CDP methods blocked for safety. Ported VERBATIM from the reference — do not\n * weaken.\n */\nconst HERMES_BLOCKED_METHODS = new Set([\n 'Runtime.terminateExecution',\n 'Inspector.detached',\n]);\n\nconst DISCOVERY_PATHS = ['/json', '/json/list'];\nconst ALLOWED_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);\n\n/**\n * Title of the synthetic legacy \"Improved Chrome Reloads\" page that Metro\n * registers alongside the real Hermes target. Filtered out during selection.\n * Ported VERBATIM from the reference.\n */\nexport const LEGACY_SYNTHETIC_TITLE =\n 'React Native Experimental (Improved Chrome Reloads)';\n\n/**\n * JavaScript expression evaluated against a candidate target BEFORE the user's\n * method, to verify it is a genuine Hermes runtime. Copied VERBATIM from the\n * reference — do not alter the verification semantics.\n */\nexport const IDENTITY_PROBE_EXPR = `(function () {\n try {\n var hi = typeof HermesInternal !== 'undefined' ? HermesInternal : null;\n var p =\n hi && typeof hi.getRuntimeProperties === 'function'\n ? hi.getRuntimeProperties()\n : null;\n return JSON.stringify({\n isHermes: !!hi,\n ossVersion: p ? p['OSS Release Version'] : null,\n debuggerEnabled: p ? p['Debugger Enabled'] : null,\n });\n } catch (e) {\n return JSON.stringify({ error: String(e) });\n }\n})();`;\n\n/**\n * Orchestrates a single verified Hermes CDP command end to end.\n *\n * Resolves a target from Metro discovery, enforces all five fail-closed safety\n * checks, runs the identity probe, persists the device pin via `onPin` on first\n * success, sends the user's method, and returns the raw CDP `result`. The caller\n * supplies the resolved port/appId/pin and persists the pin — this function is\n * pure with respect to session state.\n *\n * @param input - Method, params, timeout, resolved port/appId/pin, and the pin\n * persistence callback. See {@link RunHermesCdpInput}.\n * @returns A discriminated {@link HermesCdpResult}.\n */\nexport async function runHermesCdp(\n input: RunHermesCdpInput,\n): Promise<HermesCdpResult> {\n if (HERMES_BLOCKED_METHODS.has(input.method)) {\n return {\n ok: false,\n code: HERMES_BLOCKED_METHOD,\n message:\n `CDP method \"${input.method}\" is blocked for safety. ` +\n `Blocked methods: ${[...HERMES_BLOCKED_METHODS].join(', ')}`,\n };\n }\n\n if (typeof WebSocket !== 'function') {\n return {\n ok: false,\n code: HERMES_WEBSOCKET_UNAVAILABLE,\n message:\n 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' +\n 'NODE_OPTIONS=\"--experimental-websocket\" (or use Node 22+).',\n };\n }\n\n try {\n const targets = await fetchDiscoveryTargets(\n input.metroPort,\n input.timeoutMs,\n );\n const selection = selectHermesTarget(\n targets,\n input.appId,\n input.pinnedDeviceId,\n );\n if (!selection.ok) {\n return { ok: false, code: selection.code, message: selection.message };\n }\n\n const validation = validateWebSocketUrl(\n selection.target.webSocketDebuggerUrl,\n input.metroPort,\n );\n if (!validation.ok) {\n return {\n ok: false,\n code: HERMES_INVALID_WS_URL,\n message: validation.message,\n };\n }\n\n return await executeVerifiedCdpCommand(\n selection.target,\n input,\n input.pinnedDeviceId,\n input.onPin,\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n ok: false,\n code: HERMES_CONNECTION_FAILED,\n message: `Hermes CDP connection failed: ${message}`,\n };\n }\n}\n\n/**\n * Fetches debuggable Hermes targets from Metro, trying `/json` then falling\n * back to `/json/list`. Each attempt is bounded by an `AbortController`\n * timeout. The payload must be an array; non-object entries are filtered out.\n *\n * @param metroPort - The Metro inspector proxy port.\n * @param timeoutMs - Per-attempt timeout in milliseconds.\n * @returns The discovered targets (objects only).\n * @throws The last error encountered if all discovery paths fail.\n */\nexport async function fetchDiscoveryTargets(\n metroPort: number,\n timeoutMs: number,\n): Promise<HermesTarget[]> {\n let lastError: unknown;\n\n for (const path of DISCOVERY_PATHS) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(`http://localhost:${metroPort}${path}`, {\n signal: controller.signal,\n });\n if (!response.ok) {\n throw new Error(`Metro ${path} returned HTTP ${response.status}`);\n }\n\n const payload: unknown = await response.json();\n if (!Array.isArray(payload)) {\n throw new Error(`Metro ${path} returned a non-array response`);\n }\n\n return payload.filter(isHermesTarget);\n } catch (error) {\n lastError = error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n\n/**\n * Narrows an unknown discovery entry to a {@link HermesTarget} (object guard).\n *\n * @param value - A raw entry from the Metro discovery payload.\n * @returns True when `value` is a non-null object.\n */\nfunction isHermesTarget(value: unknown): value is HermesTarget {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Selects the single safe Hermes target for the expected app, applying the\n * fail-closed selection algorithm VERBATIM from the reference:\n *\n * `webSocketDebuggerUrl` present → drop the synthetic legacy title → strict\n * `appId` match (`===`) → pin filter → ambiguity check ({@link hasAmbiguousTarget})\n * → `nativePageReloads` tiebreak → last-in-array.\n *\n * @param targets - All discovered targets.\n * @param expectedAppId - The strict app bundle identifier to match.\n * @param pinnedDeviceId - The session pin, if set, used to filter candidates.\n * @returns The selected target or a classified failure.\n */\nexport function selectHermesTarget(\n targets: HermesTarget[],\n expectedAppId: string,\n pinnedDeviceId: string | undefined,\n): TargetSelection {\n const seenAppIds = [...new Set(targets.map((target) => target.appId))].filter(\n (appId): appId is string => typeof appId === 'string',\n );\n\n let candidates = targets\n .filter((target) => Boolean(target.webSocketDebuggerUrl))\n .filter((target) => target.title !== LEGACY_SYNTHETIC_TITLE)\n .filter((target) => target.appId === expectedAppId);\n\n if (pinnedDeviceId) {\n candidates = candidates.filter(\n (target) => target.reactNative?.logicalDeviceId === pinnedDeviceId,\n );\n }\n\n if (candidates.length === 0) {\n return {\n ok: false,\n code: HERMES_TARGET_NOT_FOUND,\n message: `No Hermes debug target found for appId ${expectedAppId}. Saw appIds: ${JSON.stringify(\n seenAppIds,\n )}`,\n };\n }\n\n if (hasAmbiguousTarget(candidates)) {\n return {\n ok: false,\n code: HERMES_MULTIPLE_DEVICES,\n message: `Ambiguous Hermes target after pin filtering. Candidates: ${candidates\n .map(\n (target) =>\n `${target.id ?? '<unknown>'} (device=${\n target.reactNative?.logicalDeviceId ?? '<missing>'\n })`,\n )\n .join(', ')}`,\n };\n }\n\n // Same-device tiebreak: prefer pages with nativePageReloads (newer RN page\n // registrations) over stale entries left behind after in-app reloads.\n const nativeReloadTargets = candidates.filter(\n (target) => target.reactNative?.capabilities?.nativePageReloads === true,\n );\n if (nativeReloadTargets.length > 0) {\n candidates = nativeReloadTargets;\n }\n\n return { ok: true, target: candidates[candidates.length - 1] };\n}\n\n/**\n * Determines whether a multi-candidate target list is too ambiguous to choose\n * from safely. Returns true when:\n * - any candidate is missing a `reactNative.logicalDeviceId` (we can't route safely), or\n * - candidates resolve to more than one distinct `logicalDeviceId` (multi-device\n * without a session pin — picking any one would silently bind the wrong device).\n *\n * Returns false when all candidates share the same logical device ID. In that\n * case the caller falls through to the \"last-in-array\" tiebreak, matching the\n * React Native convention that the most recent page registration wins (e.g.,\n * stale + fresh page after an in-app reload).\n *\n * @param candidates - Targets remaining after appId/pin/capability filtering.\n * @returns True when the caller must fail closed; false when tiebreak is safe.\n */\nexport function hasAmbiguousTarget(candidates: HermesTarget[]): boolean {\n if (candidates.length <= 1) {\n return false;\n }\n const deviceIds = candidates.map(\n (target) => target.reactNative?.logicalDeviceId,\n );\n // Any candidate missing a logicalDeviceId → can't safely route.\n if (deviceIds.some((id) => !id)) {\n return true;\n }\n // Multiple distinct device IDs → multi-device without pin.\n return new Set(deviceIds).size > 1;\n}\n\n/**\n * Validates a target's `webSocketDebuggerUrl` before connecting. The protocol\n * must be `ws:`, the hostname must be loopback (`localhost`, `127.0.0.1`, `::1`,\n * or `[::1]`), and the port must equal the resolved Metro port. Ported VERBATIM\n * from the reference.\n *\n * @param rawUrl - The candidate `webSocketDebuggerUrl`.\n * @param expectedPort - The resolved Metro port the URL must point at.\n * @returns `{ ok: true }` when valid, otherwise a failure with a message.\n */\nexport function validateWebSocketUrl(\n rawUrl: string | undefined,\n expectedPort: number,\n): { ok: true } | { ok: false; message: string } {\n if (!rawUrl) {\n return { ok: false, message: 'webSocketDebuggerUrl is missing' };\n }\n\n let parsed: URL;\n try {\n parsed = new URL(rawUrl);\n } catch {\n return {\n ok: false,\n message: `webSocketDebuggerUrl is not a valid URL: ${rawUrl}`,\n };\n }\n\n if (parsed.protocol !== 'ws:') {\n return { ok: false, message: `Unexpected protocol '${parsed.protocol}'` };\n }\n if (!ALLOWED_HOSTNAMES.has(parsed.hostname)) {\n return { ok: false, message: `Unexpected hostname '${parsed.hostname}'` };\n }\n if (Number(parsed.port) !== expectedPort) {\n return {\n ok: false,\n message: `Port mismatch: target=${parsed.port} expected=${expectedPort}`,\n };\n }\n\n return { ok: true };\n}\n\n/**\n * Opens the target WebSocket, runs the identity probe, enforces the device pin\n * (persisting it via `onPin` on first success, failing closed on mismatch),\n * sends the user's method, matches the response by `id`, and always closes the\n * socket in a `finally`. Ported faithfully from the reference.\n *\n * @param target - The validated, selected target.\n * @param input - The original {@link RunHermesCdpInput} (method/params/timeout).\n * @param pinnedDeviceId - The session pin, if set.\n * @param onPin - Callback to persist the resolved pin on first success.\n * @returns A discriminated {@link HermesCdpResult}.\n */\nexport async function executeVerifiedCdpCommand(\n target: HermesTarget,\n input: RunHermesCdpInput,\n pinnedDeviceId: string | undefined,\n onPin: ((logicalDeviceId: string) => void) | undefined,\n): Promise<HermesCdpResult> {\n if (!target.webSocketDebuggerUrl) {\n return {\n ok: false,\n code: HERMES_INVALID_WS_URL,\n message: 'Target is missing webSocketDebuggerUrl',\n };\n }\n if (typeof WebSocket !== 'function') {\n return {\n ok: false,\n code: HERMES_WEBSOCKET_UNAVAILABLE,\n message:\n 'Global WebSocket is unavailable. On Node 20 launch device-mcp with ' +\n 'NODE_OPTIONS=\"--experimental-websocket\" (or use Node 22+).',\n };\n }\n\n const socket = new WebSocket(target.webSocketDebuggerUrl);\n let nextId = 1;\n\n try {\n await waitForSocketOpen(socket, input.timeoutMs);\n\n const probe = await runIdentityProbe(socket, input.timeoutMs, nextId);\n nextId += 1;\n if (!probe.ok) {\n return { ok: false, code: probe.code, message: probe.message };\n }\n\n const targetDeviceId = target.reactNative?.logicalDeviceId;\n if (!targetDeviceId) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: 'Target is missing reactNative.logicalDeviceId',\n };\n }\n if (!pinnedDeviceId) {\n onPin?.(targetDeviceId);\n } else if (pinnedDeviceId !== targetDeviceId) {\n return {\n ok: false,\n code: HERMES_DEVICE_PIN_MISMATCH,\n message: `Hermes target device ${targetDeviceId} does not match session pin ${pinnedDeviceId}`,\n };\n }\n\n const response = await sendUserMethod(socket, input, nextId);\n nextId += 1;\n if (isCdpErrorResponse(response)) {\n return {\n ok: false,\n code: HERMES_CDP_FAILED,\n message: `Hermes CDP \"${input.method}\" failed: ${formatCdpError(\n response,\n )}`,\n };\n }\n\n return { ok: true, result: response.result };\n } finally {\n closeSocket(socket);\n }\n}\n\n/**\n * Runs the {@link IDENTITY_PROBE_EXPR} against an open socket and verifies the\n * response confirms a Hermes runtime. Distinguishes timeout from connection\n * failure and unverified payloads.\n *\n * @param socket - The open target WebSocket.\n * @param timeoutMs - Round-trip timeout in milliseconds.\n * @param id - The CDP message id to use for this probe.\n * @returns A {@link ProbeResult}.\n */\nasync function runIdentityProbe(\n socket: WebSocket,\n timeoutMs: number,\n id: number,\n): Promise<ProbeResult> {\n socket.send(\n JSON.stringify({\n id,\n method: 'Runtime.evaluate',\n params: { expression: IDENTITY_PROBE_EXPR, returnByValue: true },\n }),\n );\n\n let response: CdpSuccessResponse | CdpErrorResponse;\n try {\n response = await waitForCdpResponse(socket, id, timeoutMs);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('timed out')) {\n return {\n ok: false,\n code: HERMES_TIMEOUT,\n message: `Identity probe timed out after ${timeoutMs}ms`,\n };\n }\n return {\n ok: false,\n code: HERMES_CONNECTION_FAILED,\n message: `Hermes CDP connection failed: ${message}`,\n };\n }\n\n if (isCdpErrorResponse(response)) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: `Identity probe failed: ${formatCdpError(response)}`,\n };\n }\n\n const remoteResult = getProbeRemoteResult(response);\n if (remoteResult.subtype === 'error') {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: 'Identity probe evaluation returned an error subtype',\n };\n }\n if (remoteResult.value === undefined || remoteResult.value === null) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: 'Identity probe response missing result.value',\n };\n }\n\n const raw =\n typeof remoteResult.value === 'string'\n ? remoteResult.value\n : JSON.stringify(remoteResult.value);\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: `Identity probe returned non-JSON: ${raw}`,\n };\n }\n\n if (!isHermesProbePayload(parsed)) {\n return {\n ok: false,\n code: HERMES_NOT_VERIFIED,\n message: `Identity probe did not verify Hermes runtime: ${raw}`,\n };\n }\n\n return { ok: true };\n}\n\n/**\n * Safely extracts the nested CDP `result.result` (the remote object) from a\n * `Runtime.evaluate` success response.\n *\n * @param response - A CDP success response.\n * @returns The remote object's `subtype`/`value`, or an empty object.\n */\nfunction getProbeRemoteResult(response: CdpSuccessResponse): {\n subtype?: unknown;\n value?: unknown;\n} {\n if (typeof response.result !== 'object' || response.result === null) {\n return {};\n }\n const outer = response.result as Record<string, unknown>;\n if (typeof outer.result !== 'object' || outer.result === null) {\n return {};\n }\n return outer.result;\n}\n\n/**\n * Narrows a parsed probe payload to one confirming a Hermes runtime.\n *\n * @param value - The parsed probe JSON payload.\n * @returns True when `isHermes === true`.\n */\nfunction isHermesProbePayload(value: unknown): value is { isHermes: true } {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).isHermes === true\n );\n}\n\n/**\n * Sends the user's CDP method over an open, verified socket and awaits the\n * matching response by id.\n *\n * @param socket - The open, verified target WebSocket.\n * @param input - The original input carrying method/params/timeout.\n * @param id - The CDP message id to use.\n * @returns The matching CDP success or error response.\n */\nasync function sendUserMethod(\n socket: WebSocket,\n input: RunHermesCdpInput,\n id: number,\n): Promise<CdpSuccessResponse | CdpErrorResponse> {\n socket.send(\n JSON.stringify({\n id,\n method: input.method,\n params: input.params ?? {},\n }),\n );\n return await waitForCdpResponse(socket, id, input.timeoutMs);\n}\n\n/**\n * Resolves once the socket reaches the OPEN state, rejecting on error or\n * timeout. Listeners are always cleaned up.\n *\n * @param socket - The connecting target WebSocket.\n * @param timeoutMs - Connection timeout in milliseconds.\n * @returns A promise that resolves when the socket is open.\n */\nasync function waitForSocketOpen(\n socket: WebSocket,\n timeoutMs: number,\n): Promise<void> {\n if (socket.readyState === WebSocket.OPEN) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n let cleanup = (): void => undefined;\n const handleOpen = (): void => {\n cleanup();\n resolve();\n };\n const handleError = (): void => {\n cleanup();\n reject(new Error('WebSocket connection error'));\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`WebSocket connection timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n cleanup = (): void => {\n clearTimeout(timer);\n socket.removeEventListener('open', handleOpen);\n socket.removeEventListener('error', handleError);\n };\n\n socket.addEventListener('open', handleOpen);\n socket.addEventListener('error', handleError);\n });\n}\n\n/**\n * Resolves with the CDP response whose `id` matches, rejecting on socket error,\n * premature close, or timeout. Listeners are always cleaned up.\n *\n * @param socket - The open target WebSocket.\n * @param id - The CDP message id to match.\n * @param timeoutMs - Round-trip timeout in milliseconds.\n * @returns The matching CDP success or error response.\n */\nasync function waitForCdpResponse(\n socket: WebSocket,\n id: number,\n timeoutMs: number,\n): Promise<CdpSuccessResponse | CdpErrorResponse> {\n return new Promise((resolve, reject) => {\n let cleanup = (): void => undefined;\n const handleMessage = (event: MessageEvent): void => {\n try {\n const parsed = parseCdpResponse(event.data);\n if (parsed?.id !== id) {\n return;\n }\n cleanup();\n resolve(parsed);\n } catch (error) {\n cleanup();\n reject(error);\n }\n };\n const handleError = (): void => {\n cleanup();\n reject(new Error('WebSocket message error'));\n };\n const handleClose = (): void => {\n cleanup();\n reject(new Error('WebSocket closed before CDP response'));\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(`Hermes CDP call timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n cleanup = (): void => {\n clearTimeout(timer);\n socket.removeEventListener('message', handleMessage);\n socket.removeEventListener('error', handleError);\n socket.removeEventListener('close', handleClose);\n };\n\n socket.addEventListener('message', handleMessage);\n socket.addEventListener('error', handleError);\n socket.addEventListener('close', handleClose);\n });\n}\n\n/**\n * Parses a raw WebSocket frame into a CDP response, returning undefined when it\n * is not an addressable (`id`-bearing) message.\n *\n * @param data - The raw `MessageEvent.data`.\n * @returns A CDP success/error response, or undefined when not addressable.\n * @throws When the frame is not a text frame.\n */\nfunction parseCdpResponse(\n data: MessageEvent['data'],\n): CdpSuccessResponse | CdpErrorResponse | undefined {\n if (typeof data !== 'string') {\n throw new Error('Hermes CDP returned a non-text WebSocket frame');\n }\n\n const parsed: unknown = JSON.parse(data);\n if (typeof parsed !== 'object' || parsed === null) {\n return undefined;\n }\n const candidate = parsed as Record<string, unknown>;\n if (typeof candidate.id !== 'number') {\n return undefined;\n }\n if (isCdpError(candidate.error)) {\n return { id: candidate.id, error: candidate.error };\n }\n return { id: candidate.id, result: candidate.result };\n}\n\n/**\n * Narrows an unknown value to a CDP error object.\n *\n * @param value - The candidate `error` field.\n * @returns True when `value` is a non-null object.\n */\nfunction isCdpError(value: unknown): value is CdpErrorResponse['error'] {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Discriminates a CDP response as an error response.\n *\n * @param response - A CDP success or error response.\n * @returns True when the response carries an `error` field.\n */\nfunction isCdpErrorResponse(\n response: CdpSuccessResponse | CdpErrorResponse,\n): response is CdpErrorResponse {\n return 'error' in response;\n}\n\n/**\n * Formats a CDP error response into a single human-readable line.\n *\n * @param response - The CDP error response.\n * @returns A combined message including code and data when present.\n */\nfunction formatCdpError(response: CdpErrorResponse): string {\n const parts = [response.error.message ?? 'Unknown CDP error'];\n if (typeof response.error.code === 'number') {\n parts.push(`code ${response.error.code}`);\n }\n if (response.error.data !== undefined) {\n parts.push(JSON.stringify(response.error.data));\n }\n return parts.join(' - ');\n}\n\n/**\n * Closes the socket if it is still connecting or open.\n *\n * @param socket - The target WebSocket.\n */\nfunction closeSocket(socket: WebSocket): void {\n if (\n socket.readyState === WebSocket.CONNECTING ||\n socket.readyState === WebSocket.OPEN\n ) {\n socket.close();\n }\n}\n"]} |
| "use strict"; | ||
| /** | ||
| * Standalone state module for Hermes CDP sessions. | ||
| * | ||
| * Holds the resolved Metro inspector proxy port, the expected app bundle ID, | ||
| * and the pinned Hermes `logicalDeviceId`. This module is intentionally | ||
| * INDEPENDENT of `DeviceBackend` — Hermes/Metro access is orthogonal to the | ||
| * idb/adb/Appium UI automation backends, and it stores no live connection | ||
| * (the MVP opens a fresh WebSocket per tool call). | ||
| * | ||
| * Resolution precedence for both port and appId is exactly: | ||
| * per-call param > environment variable > default. | ||
| */ | ||
| var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
| if (kind === "m") throw new TypeError("Private method is not writable"); | ||
| if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
| if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
| return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
| }; | ||
| var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
| if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
| if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
| return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
| }; | ||
| var _HermesSession_instances, _HermesSession_metroPort, _HermesSession_expectedAppId, _HermesSession_appIdFromEnv, _HermesSession_pinnedDeviceId, _HermesSession_resolveAppId; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.HermesSession = exports.DEFAULT_METRO_PORT = exports.DEFAULT_ANDROID_APP_ID = exports.DEFAULT_IOS_APP_ID = void 0; | ||
| exports.defaultAppIdForPlatform = defaultAppIdForPlatform; | ||
| exports.getHermesSession = getHermesSession; | ||
| exports.resetHermesSession = resetHermesSession; | ||
| /** Default MetaMask app bundle ID for iOS (`CFBundleIdentifier`). */ | ||
| exports.DEFAULT_IOS_APP_ID = 'io.metamask.MetaMask'; | ||
| /** Default MetaMask app bundle ID for Android (`applicationId`). */ | ||
| exports.DEFAULT_ANDROID_APP_ID = 'io.metamask'; | ||
| /** Default Metro inspector proxy port when none is configured. */ | ||
| exports.DEFAULT_METRO_PORT = 8081; | ||
| /** | ||
| * Resolve the platform default app bundle ID. | ||
| * | ||
| * Defaults to the iOS id when the platform is unknown. Agents running against | ||
| * Android should pass `appId` per call or set `HERMES_APP_ID` if they are not | ||
| * using the default Android bundle ID. | ||
| * | ||
| * @param platform - The active platform, if known. | ||
| * @returns The default app bundle ID for the platform. | ||
| */ | ||
| function defaultAppIdForPlatform(platform) { | ||
| return platform === 'android' ? exports.DEFAULT_ANDROID_APP_ID : exports.DEFAULT_IOS_APP_ID; | ||
| } | ||
| /** | ||
| * Parse a Metro port from an environment variable value. | ||
| * | ||
| * @param value - The raw environment variable value (or undefined). | ||
| * @returns The parsed integer port, or undefined when unset/invalid. | ||
| */ | ||
| function parseEnvPort(value) { | ||
| if (value === undefined) { | ||
| return undefined; | ||
| } | ||
| const parsed = Number.parseInt(value, 10); | ||
| return Number.isInteger(parsed) ? parsed : undefined; | ||
| } | ||
| /** | ||
| * Holds the Metro port, expected appId, and pinned Hermes deviceId for a | ||
| * process-lifetime Hermes session. | ||
| * | ||
| * The stored port and appId are resolved once at construction time using | ||
| * `param ?? env ?? default` precedence (with `param` being undefined at | ||
| * construction — those are supplied per call to {@link resolve}). The pinned | ||
| * deviceId starts unset and is assigned on the first successful identity probe. | ||
| */ | ||
| class HermesSession { | ||
| constructor(options = {}) { | ||
| _HermesSession_instances.add(this); | ||
| _HermesSession_metroPort.set(this, void 0); | ||
| _HermesSession_expectedAppId.set(this, void 0); | ||
| /** | ||
| * Whether {@link #expectedAppId} came from an explicit source (the | ||
| * `HERMES_APP_ID` env var) rather than the platform fallback. When true, the | ||
| * stored appId is a deliberate override and {@link resolve} must not let a | ||
| * per-call `platform` recompute it; when false, the stored value is only the | ||
| * construction-time platform default and `resolve` may recompute it from the | ||
| * live platform. | ||
| */ | ||
| _HermesSession_appIdFromEnv.set(this, void 0); | ||
| _HermesSession_pinnedDeviceId.set(this, void 0); | ||
| const env = options.env ?? process.env; | ||
| __classPrivateFieldSet(this, _HermesSession_metroPort, parseEnvPort(env.HERMES_METRO_PORT) ?? exports.DEFAULT_METRO_PORT, "f"); | ||
| __classPrivateFieldSet(this, _HermesSession_appIdFromEnv, env.HERMES_APP_ID !== undefined, "f"); | ||
| __classPrivateFieldSet(this, _HermesSession_expectedAppId, env.HERMES_APP_ID ?? defaultAppIdForPlatform(options.platform), "f"); | ||
| __classPrivateFieldSet(this, _HermesSession_pinnedDeviceId, undefined, "f"); | ||
| } | ||
| /** | ||
| * Returns the session-default Metro inspector proxy port. | ||
| * | ||
| * @returns The resolved Metro port (env `HERMES_METRO_PORT`, else 8081). | ||
| */ | ||
| getMetroPort() { | ||
| return __classPrivateFieldGet(this, _HermesSession_metroPort, "f"); | ||
| } | ||
| /** | ||
| * Returns the session-default expected app bundle ID. | ||
| * | ||
| * @returns The resolved app bundle ID (env `HERMES_APP_ID`, else the | ||
| * platform default). | ||
| */ | ||
| getAppId() { | ||
| return __classPrivateFieldGet(this, _HermesSession_expectedAppId, "f"); | ||
| } | ||
| /** | ||
| * Returns the pinned Hermes `logicalDeviceId` for this session, if any. | ||
| * | ||
| * Set on the first successful identity probe via | ||
| * {@link setPinnedHermesDeviceId}. Persists for the lifetime of this | ||
| * instance. | ||
| * | ||
| * @returns The pinned `logicalDeviceId`, or undefined when no pin is set. | ||
| */ | ||
| getPinnedHermesDeviceId() { | ||
| return __classPrivateFieldGet(this, _HermesSession_pinnedDeviceId, "f"); | ||
| } | ||
| /** | ||
| * Pin the Hermes `logicalDeviceId` for this session. | ||
| * | ||
| * Called after a successful identity probe on the first call. Subsequent | ||
| * calls then enforce this pin to prevent target switching mid-session. | ||
| * | ||
| * @param id - The `logicalDeviceId` from Metro's | ||
| * `reactNative.logicalDeviceId` field. | ||
| */ | ||
| setPinnedHermesDeviceId(id) { | ||
| __classPrivateFieldSet(this, _HermesSession_pinnedDeviceId, id, "f"); | ||
| } | ||
| /** | ||
| * Resolve the effective parameters for a single tool call by merging | ||
| * per-call overrides over the stored session defaults. | ||
| * | ||
| * This is PURE with respect to stored state: it never mutates the stored | ||
| * port, appId, or pin. Only {@link setPinnedHermesDeviceId} mutates the pin. | ||
| * | ||
| * Port precedence is `input.metroPort ?? this.getMetroPort()` (which itself | ||
| * already encodes `env ?? default`), mirroring the reference driver's | ||
| * `input.metroPort ?? driver.getMetroPort() ?? 8081`. | ||
| * | ||
| * AppId precedence is `input.appId ?? <HERMES_APP_ID env if set> ?? | ||
| * defaultAppIdForPlatform(input.platform) ?? <stored default>`. An explicitly | ||
| * set `HERMES_APP_ID` always WINS over the per-call platform default (env is a | ||
| * deliberate override): in that case the stored appId is returned unchanged. | ||
| * Only when the stored appId is the construction-time platform fallback does | ||
| * a per-call `platform` recompute it — without mutating stored state. When no | ||
| * `platform` is passed this preserves the original iOS-default behavior. | ||
| * | ||
| * @param input - Optional per-call overrides for port, appId, and platform. | ||
| * @returns The effective `{ metroPort, appId, pinnedDeviceId }`. | ||
| */ | ||
| resolve(input = {}) { | ||
| return { | ||
| metroPort: input.metroPort ?? __classPrivateFieldGet(this, _HermesSession_metroPort, "f"), | ||
| appId: input.appId ?? __classPrivateFieldGet(this, _HermesSession_instances, "m", _HermesSession_resolveAppId).call(this, input.platform), | ||
| pinnedDeviceId: __classPrivateFieldGet(this, _HermesSession_pinnedDeviceId, "f"), | ||
| }; | ||
| } | ||
| } | ||
| exports.HermesSession = HermesSession; | ||
| _HermesSession_metroPort = new WeakMap(), _HermesSession_expectedAppId = new WeakMap(), _HermesSession_appIdFromEnv = new WeakMap(), _HermesSession_pinnedDeviceId = new WeakMap(), _HermesSession_instances = new WeakSet(), _HermesSession_resolveAppId = function _HermesSession_resolveAppId(platform) { | ||
| if (__classPrivateFieldGet(this, _HermesSession_appIdFromEnv, "f") || platform === undefined) { | ||
| return __classPrivateFieldGet(this, _HermesSession_expectedAppId, "f"); | ||
| } | ||
| return defaultAppIdForPlatform(platform); | ||
| }; | ||
| /** | ||
| * Module-level lazily-created singleton instance. | ||
| */ | ||
| let sessionInstance; | ||
| /** | ||
| * Returns the process-singleton {@link HermesSession}, creating it on first | ||
| * access. | ||
| * | ||
| * @param options - Options applied only when the singleton is first created. | ||
| * @returns The shared `HermesSession` instance. | ||
| */ | ||
| function getHermesSession(options) { | ||
| if (sessionInstance === undefined) { | ||
| sessionInstance = new HermesSession(options); | ||
| } | ||
| return sessionInstance; | ||
| } | ||
| /** | ||
| * Resets the process-singleton {@link HermesSession}. | ||
| * | ||
| * Intended for test isolation so each test can start from a clean session. | ||
| */ | ||
| function resetHermesSession() { | ||
| sessionInstance = undefined; | ||
| } | ||
| //# sourceMappingURL=session.cjs.map |
| {"version":3,"file":"session.cjs","sourceRoot":"","sources":["../../src/hermes/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Standalone state module for Hermes CDP sessions.\n *\n * Holds the resolved Metro inspector proxy port, the expected app bundle ID,\n * and the pinned Hermes `logicalDeviceId`. This module is intentionally\n * INDEPENDENT of `DeviceBackend` — Hermes/Metro access is orthogonal to the\n * idb/adb/Appium UI automation backends, and it stores no live connection\n * (the MVP opens a fresh WebSocket per tool call).\n *\n * Resolution precedence for both port and appId is exactly:\n * per-call param > environment variable > default.\n */\n\n/**\n * Platform identifier used to pick the default app bundle ID.\n *\n * Kept structurally compatible with the backend's `Platform` type without\n * importing it, so this module stays decoupled from `DeviceBackend`.\n */\nexport type HermesPlatform = 'ios' | 'android';\n\n/** Default MetaMask app bundle ID for iOS (`CFBundleIdentifier`). */\nexport const DEFAULT_IOS_APP_ID = 'io.metamask.MetaMask';\n\n/** Default MetaMask app bundle ID for Android (`applicationId`). */\nexport const DEFAULT_ANDROID_APP_ID = 'io.metamask';\n\n/** Default Metro inspector proxy port when none is configured. */\nexport const DEFAULT_METRO_PORT = 8081;\n\n/**\n * Resolve the platform default app bundle ID.\n *\n * Defaults to the iOS id when the platform is unknown. Agents running against\n * Android should pass `appId` per call or set `HERMES_APP_ID` if they are not\n * using the default Android bundle ID.\n *\n * @param platform - The active platform, if known.\n * @returns The default app bundle ID for the platform.\n */\nexport function defaultAppIdForPlatform(platform?: HermesPlatform): string {\n return platform === 'android' ? DEFAULT_ANDROID_APP_ID : DEFAULT_IOS_APP_ID;\n}\n\n/**\n * Parse a Metro port from an environment variable value.\n *\n * @param value - The raw environment variable value (or undefined).\n * @returns The parsed integer port, or undefined when unset/invalid.\n */\nfunction parseEnvPort(value: string | undefined): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number.parseInt(value, 10);\n return Number.isInteger(parsed) ? parsed : undefined;\n}\n\n/**\n * Per-call overrides accepted by {@link HermesSession.resolve}.\n *\n * Any field left undefined falls back to the session default (which itself\n * falls back to the relevant environment variable, then the hardcoded default).\n */\nexport type HermesSessionResolveInput = {\n /** Override the Metro inspector proxy port for this call. */\n metroPort?: number;\n /** Override the expected app bundle ID for this call. */\n appId?: string;\n /**\n * The active platform, used to compute the default app bundle ID AT CALL\n * TIME when neither a per-call `appId` nor the `HERMES_APP_ID` env var is set.\n * This lets a session created before a device connects (when the platform is\n * not yet known) still resolve the correct Android default once the backend's\n * live platform becomes `'android'`.\n */\n platform?: HermesPlatform;\n};\n\n/**\n * The effective Hermes session parameters for a single tool call.\n */\nexport type HermesSessionResolved = {\n /** The resolved Metro inspector proxy port. */\n metroPort: number;\n /** The resolved expected app bundle ID. */\n appId: string;\n /** The pinned Hermes `logicalDeviceId`, if one has been set. */\n pinnedDeviceId: string | undefined;\n};\n\n/**\n * Options for constructing a {@link HermesSession}.\n */\nexport type HermesSessionOptions = {\n /** The active platform, used only to pick the default app bundle ID. */\n platform?: HermesPlatform;\n /**\n * Environment source. Defaults to `process.env`. Injectable for tests so\n * resolution precedence can be exercised without mutating the real env.\n */\n env?: NodeJS.ProcessEnv;\n};\n\n/**\n * Holds the Metro port, expected appId, and pinned Hermes deviceId for a\n * process-lifetime Hermes session.\n *\n * The stored port and appId are resolved once at construction time using\n * `param ?? env ?? default` precedence (with `param` being undefined at\n * construction — those are supplied per call to {@link resolve}). The pinned\n * deviceId starts unset and is assigned on the first successful identity probe.\n */\nexport class HermesSession {\n readonly #metroPort: number;\n\n readonly #expectedAppId: string;\n\n /**\n * Whether {@link #expectedAppId} came from an explicit source (the\n * `HERMES_APP_ID` env var) rather than the platform fallback. When true, the\n * stored appId is a deliberate override and {@link resolve} must not let a\n * per-call `platform` recompute it; when false, the stored value is only the\n * construction-time platform default and `resolve` may recompute it from the\n * live platform.\n */\n readonly #appIdFromEnv: boolean;\n\n #pinnedDeviceId: string | undefined;\n\n constructor(options: HermesSessionOptions = {}) {\n const env = options.env ?? process.env;\n\n this.#metroPort = parseEnvPort(env.HERMES_METRO_PORT) ?? DEFAULT_METRO_PORT;\n\n this.#appIdFromEnv = env.HERMES_APP_ID !== undefined;\n this.#expectedAppId =\n env.HERMES_APP_ID ?? defaultAppIdForPlatform(options.platform);\n\n this.#pinnedDeviceId = undefined;\n }\n\n /**\n * Returns the session-default Metro inspector proxy port.\n *\n * @returns The resolved Metro port (env `HERMES_METRO_PORT`, else 8081).\n */\n getMetroPort(): number {\n return this.#metroPort;\n }\n\n /**\n * Returns the session-default expected app bundle ID.\n *\n * @returns The resolved app bundle ID (env `HERMES_APP_ID`, else the\n * platform default).\n */\n getAppId(): string {\n return this.#expectedAppId;\n }\n\n /**\n * Returns the pinned Hermes `logicalDeviceId` for this session, if any.\n *\n * Set on the first successful identity probe via\n * {@link setPinnedHermesDeviceId}. Persists for the lifetime of this\n * instance.\n *\n * @returns The pinned `logicalDeviceId`, or undefined when no pin is set.\n */\n getPinnedHermesDeviceId(): string | undefined {\n return this.#pinnedDeviceId;\n }\n\n /**\n * Pin the Hermes `logicalDeviceId` for this session.\n *\n * Called after a successful identity probe on the first call. Subsequent\n * calls then enforce this pin to prevent target switching mid-session.\n *\n * @param id - The `logicalDeviceId` from Metro's\n * `reactNative.logicalDeviceId` field.\n */\n setPinnedHermesDeviceId(id: string): void {\n this.#pinnedDeviceId = id;\n }\n\n /**\n * Resolve the effective parameters for a single tool call by merging\n * per-call overrides over the stored session defaults.\n *\n * This is PURE with respect to stored state: it never mutates the stored\n * port, appId, or pin. Only {@link setPinnedHermesDeviceId} mutates the pin.\n *\n * Port precedence is `input.metroPort ?? this.getMetroPort()` (which itself\n * already encodes `env ?? default`), mirroring the reference driver's\n * `input.metroPort ?? driver.getMetroPort() ?? 8081`.\n *\n * AppId precedence is `input.appId ?? <HERMES_APP_ID env if set> ??\n * defaultAppIdForPlatform(input.platform) ?? <stored default>`. An explicitly\n * set `HERMES_APP_ID` always WINS over the per-call platform default (env is a\n * deliberate override): in that case the stored appId is returned unchanged.\n * Only when the stored appId is the construction-time platform fallback does\n * a per-call `platform` recompute it — without mutating stored state. When no\n * `platform` is passed this preserves the original iOS-default behavior.\n *\n * @param input - Optional per-call overrides for port, appId, and platform.\n * @returns The effective `{ metroPort, appId, pinnedDeviceId }`.\n */\n resolve(input: HermesSessionResolveInput = {}): HermesSessionResolved {\n return {\n metroPort: input.metroPort ?? this.#metroPort,\n appId: input.appId ?? this.#resolveAppId(input.platform),\n pinnedDeviceId: this.#pinnedDeviceId,\n };\n }\n\n /**\n * Compute the effective default appId for a call, honouring the\n * env-over-platform-default precedence documented on {@link resolve}.\n *\n * @param platform - The live platform passed for this call, if any.\n * @returns The stored env/explicit appId, or the platform default.\n */\n #resolveAppId(platform: HermesPlatform | undefined): string {\n if (this.#appIdFromEnv || platform === undefined) {\n return this.#expectedAppId;\n }\n return defaultAppIdForPlatform(platform);\n }\n}\n\n/**\n * Module-level lazily-created singleton instance.\n */\nlet sessionInstance: HermesSession | undefined;\n\n/**\n * Returns the process-singleton {@link HermesSession}, creating it on first\n * access.\n *\n * @param options - Options applied only when the singleton is first created.\n * @returns The shared `HermesSession` instance.\n */\nexport function getHermesSession(\n options?: HermesSessionOptions,\n): HermesSession {\n if (sessionInstance === undefined) {\n sessionInstance = new HermesSession(options);\n }\n return sessionInstance;\n}\n\n/**\n * Resets the process-singleton {@link HermesSession}.\n *\n * Intended for test isolation so each test can start from a clean session.\n */\nexport function resetHermesSession(): void {\n sessionInstance = undefined;\n}\n"]} |
| /** | ||
| * Standalone state module for Hermes CDP sessions. | ||
| * | ||
| * Holds the resolved Metro inspector proxy port, the expected app bundle ID, | ||
| * and the pinned Hermes `logicalDeviceId`. This module is intentionally | ||
| * INDEPENDENT of `DeviceBackend` — Hermes/Metro access is orthogonal to the | ||
| * idb/adb/Appium UI automation backends, and it stores no live connection | ||
| * (the MVP opens a fresh WebSocket per tool call). | ||
| * | ||
| * Resolution precedence for both port and appId is exactly: | ||
| * per-call param > environment variable > default. | ||
| */ | ||
| /** | ||
| * Platform identifier used to pick the default app bundle ID. | ||
| * | ||
| * Kept structurally compatible with the backend's `Platform` type without | ||
| * importing it, so this module stays decoupled from `DeviceBackend`. | ||
| */ | ||
| export type HermesPlatform = 'ios' | 'android'; | ||
| /** Default MetaMask app bundle ID for iOS (`CFBundleIdentifier`). */ | ||
| export declare const DEFAULT_IOS_APP_ID = "io.metamask.MetaMask"; | ||
| /** Default MetaMask app bundle ID for Android (`applicationId`). */ | ||
| export declare const DEFAULT_ANDROID_APP_ID = "io.metamask"; | ||
| /** Default Metro inspector proxy port when none is configured. */ | ||
| export declare const DEFAULT_METRO_PORT = 8081; | ||
| /** | ||
| * Resolve the platform default app bundle ID. | ||
| * | ||
| * Defaults to the iOS id when the platform is unknown. Agents running against | ||
| * Android should pass `appId` per call or set `HERMES_APP_ID` if they are not | ||
| * using the default Android bundle ID. | ||
| * | ||
| * @param platform - The active platform, if known. | ||
| * @returns The default app bundle ID for the platform. | ||
| */ | ||
| export declare function defaultAppIdForPlatform(platform?: HermesPlatform): string; | ||
| /** | ||
| * Per-call overrides accepted by {@link HermesSession.resolve}. | ||
| * | ||
| * Any field left undefined falls back to the session default (which itself | ||
| * falls back to the relevant environment variable, then the hardcoded default). | ||
| */ | ||
| export type HermesSessionResolveInput = { | ||
| /** Override the Metro inspector proxy port for this call. */ | ||
| metroPort?: number; | ||
| /** Override the expected app bundle ID for this call. */ | ||
| appId?: string; | ||
| /** | ||
| * The active platform, used to compute the default app bundle ID AT CALL | ||
| * TIME when neither a per-call `appId` nor the `HERMES_APP_ID` env var is set. | ||
| * This lets a session created before a device connects (when the platform is | ||
| * not yet known) still resolve the correct Android default once the backend's | ||
| * live platform becomes `'android'`. | ||
| */ | ||
| platform?: HermesPlatform; | ||
| }; | ||
| /** | ||
| * The effective Hermes session parameters for a single tool call. | ||
| */ | ||
| export type HermesSessionResolved = { | ||
| /** The resolved Metro inspector proxy port. */ | ||
| metroPort: number; | ||
| /** The resolved expected app bundle ID. */ | ||
| appId: string; | ||
| /** The pinned Hermes `logicalDeviceId`, if one has been set. */ | ||
| pinnedDeviceId: string | undefined; | ||
| }; | ||
| /** | ||
| * Options for constructing a {@link HermesSession}. | ||
| */ | ||
| export type HermesSessionOptions = { | ||
| /** The active platform, used only to pick the default app bundle ID. */ | ||
| platform?: HermesPlatform; | ||
| /** | ||
| * Environment source. Defaults to `process.env`. Injectable for tests so | ||
| * resolution precedence can be exercised without mutating the real env. | ||
| */ | ||
| env?: NodeJS.ProcessEnv; | ||
| }; | ||
| /** | ||
| * Holds the Metro port, expected appId, and pinned Hermes deviceId for a | ||
| * process-lifetime Hermes session. | ||
| * | ||
| * The stored port and appId are resolved once at construction time using | ||
| * `param ?? env ?? default` precedence (with `param` being undefined at | ||
| * construction — those are supplied per call to {@link resolve}). The pinned | ||
| * deviceId starts unset and is assigned on the first successful identity probe. | ||
| */ | ||
| export declare class HermesSession { | ||
| #private; | ||
| constructor(options?: HermesSessionOptions); | ||
| /** | ||
| * Returns the session-default Metro inspector proxy port. | ||
| * | ||
| * @returns The resolved Metro port (env `HERMES_METRO_PORT`, else 8081). | ||
| */ | ||
| getMetroPort(): number; | ||
| /** | ||
| * Returns the session-default expected app bundle ID. | ||
| * | ||
| * @returns The resolved app bundle ID (env `HERMES_APP_ID`, else the | ||
| * platform default). | ||
| */ | ||
| getAppId(): string; | ||
| /** | ||
| * Returns the pinned Hermes `logicalDeviceId` for this session, if any. | ||
| * | ||
| * Set on the first successful identity probe via | ||
| * {@link setPinnedHermesDeviceId}. Persists for the lifetime of this | ||
| * instance. | ||
| * | ||
| * @returns The pinned `logicalDeviceId`, or undefined when no pin is set. | ||
| */ | ||
| getPinnedHermesDeviceId(): string | undefined; | ||
| /** | ||
| * Pin the Hermes `logicalDeviceId` for this session. | ||
| * | ||
| * Called after a successful identity probe on the first call. Subsequent | ||
| * calls then enforce this pin to prevent target switching mid-session. | ||
| * | ||
| * @param id - The `logicalDeviceId` from Metro's | ||
| * `reactNative.logicalDeviceId` field. | ||
| */ | ||
| setPinnedHermesDeviceId(id: string): void; | ||
| /** | ||
| * Resolve the effective parameters for a single tool call by merging | ||
| * per-call overrides over the stored session defaults. | ||
| * | ||
| * This is PURE with respect to stored state: it never mutates the stored | ||
| * port, appId, or pin. Only {@link setPinnedHermesDeviceId} mutates the pin. | ||
| * | ||
| * Port precedence is `input.metroPort ?? this.getMetroPort()` (which itself | ||
| * already encodes `env ?? default`), mirroring the reference driver's | ||
| * `input.metroPort ?? driver.getMetroPort() ?? 8081`. | ||
| * | ||
| * AppId precedence is `input.appId ?? <HERMES_APP_ID env if set> ?? | ||
| * defaultAppIdForPlatform(input.platform) ?? <stored default>`. An explicitly | ||
| * set `HERMES_APP_ID` always WINS over the per-call platform default (env is a | ||
| * deliberate override): in that case the stored appId is returned unchanged. | ||
| * Only when the stored appId is the construction-time platform fallback does | ||
| * a per-call `platform` recompute it — without mutating stored state. When no | ||
| * `platform` is passed this preserves the original iOS-default behavior. | ||
| * | ||
| * @param input - Optional per-call overrides for port, appId, and platform. | ||
| * @returns The effective `{ metroPort, appId, pinnedDeviceId }`. | ||
| */ | ||
| resolve(input?: HermesSessionResolveInput): HermesSessionResolved; | ||
| } | ||
| /** | ||
| * Returns the process-singleton {@link HermesSession}, creating it on first | ||
| * access. | ||
| * | ||
| * @param options - Options applied only when the singleton is first created. | ||
| * @returns The shared `HermesSession` instance. | ||
| */ | ||
| export declare function getHermesSession(options?: HermesSessionOptions): HermesSession; | ||
| /** | ||
| * Resets the process-singleton {@link HermesSession}. | ||
| * | ||
| * Intended for test isolation so each test can start from a clean session. | ||
| */ | ||
| export declare function resetHermesSession(): void; | ||
| //# sourceMappingURL=session.d.cts.map |
| {"version":3,"file":"session.d.cts","sourceRoot":"","sources":["../../src/hermes/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AAEzD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAEzE;AAgBD;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,aAAa;;gBAiBZ,OAAO,GAAE,oBAAyB;IAY9C;;;;OAIG;IACH,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;IACH,QAAQ,IAAI,MAAM;IAIlB;;;;;;;;OAQG;IACH,uBAAuB,IAAI,MAAM,GAAG,SAAS;IAI7C;;;;;;;;OAQG;IACH,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIzC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,KAAK,GAAE,yBAA8B,GAAG,qBAAqB;CAqBtE;AAOD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,aAAa,CAKf;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"} |
| /** | ||
| * Standalone state module for Hermes CDP sessions. | ||
| * | ||
| * Holds the resolved Metro inspector proxy port, the expected app bundle ID, | ||
| * and the pinned Hermes `logicalDeviceId`. This module is intentionally | ||
| * INDEPENDENT of `DeviceBackend` — Hermes/Metro access is orthogonal to the | ||
| * idb/adb/Appium UI automation backends, and it stores no live connection | ||
| * (the MVP opens a fresh WebSocket per tool call). | ||
| * | ||
| * Resolution precedence for both port and appId is exactly: | ||
| * per-call param > environment variable > default. | ||
| */ | ||
| /** | ||
| * Platform identifier used to pick the default app bundle ID. | ||
| * | ||
| * Kept structurally compatible with the backend's `Platform` type without | ||
| * importing it, so this module stays decoupled from `DeviceBackend`. | ||
| */ | ||
| export type HermesPlatform = 'ios' | 'android'; | ||
| /** Default MetaMask app bundle ID for iOS (`CFBundleIdentifier`). */ | ||
| export declare const DEFAULT_IOS_APP_ID = "io.metamask.MetaMask"; | ||
| /** Default MetaMask app bundle ID for Android (`applicationId`). */ | ||
| export declare const DEFAULT_ANDROID_APP_ID = "io.metamask"; | ||
| /** Default Metro inspector proxy port when none is configured. */ | ||
| export declare const DEFAULT_METRO_PORT = 8081; | ||
| /** | ||
| * Resolve the platform default app bundle ID. | ||
| * | ||
| * Defaults to the iOS id when the platform is unknown. Agents running against | ||
| * Android should pass `appId` per call or set `HERMES_APP_ID` if they are not | ||
| * using the default Android bundle ID. | ||
| * | ||
| * @param platform - The active platform, if known. | ||
| * @returns The default app bundle ID for the platform. | ||
| */ | ||
| export declare function defaultAppIdForPlatform(platform?: HermesPlatform): string; | ||
| /** | ||
| * Per-call overrides accepted by {@link HermesSession.resolve}. | ||
| * | ||
| * Any field left undefined falls back to the session default (which itself | ||
| * falls back to the relevant environment variable, then the hardcoded default). | ||
| */ | ||
| export type HermesSessionResolveInput = { | ||
| /** Override the Metro inspector proxy port for this call. */ | ||
| metroPort?: number; | ||
| /** Override the expected app bundle ID for this call. */ | ||
| appId?: string; | ||
| /** | ||
| * The active platform, used to compute the default app bundle ID AT CALL | ||
| * TIME when neither a per-call `appId` nor the `HERMES_APP_ID` env var is set. | ||
| * This lets a session created before a device connects (when the platform is | ||
| * not yet known) still resolve the correct Android default once the backend's | ||
| * live platform becomes `'android'`. | ||
| */ | ||
| platform?: HermesPlatform; | ||
| }; | ||
| /** | ||
| * The effective Hermes session parameters for a single tool call. | ||
| */ | ||
| export type HermesSessionResolved = { | ||
| /** The resolved Metro inspector proxy port. */ | ||
| metroPort: number; | ||
| /** The resolved expected app bundle ID. */ | ||
| appId: string; | ||
| /** The pinned Hermes `logicalDeviceId`, if one has been set. */ | ||
| pinnedDeviceId: string | undefined; | ||
| }; | ||
| /** | ||
| * Options for constructing a {@link HermesSession}. | ||
| */ | ||
| export type HermesSessionOptions = { | ||
| /** The active platform, used only to pick the default app bundle ID. */ | ||
| platform?: HermesPlatform; | ||
| /** | ||
| * Environment source. Defaults to `process.env`. Injectable for tests so | ||
| * resolution precedence can be exercised without mutating the real env. | ||
| */ | ||
| env?: NodeJS.ProcessEnv; | ||
| }; | ||
| /** | ||
| * Holds the Metro port, expected appId, and pinned Hermes deviceId for a | ||
| * process-lifetime Hermes session. | ||
| * | ||
| * The stored port and appId are resolved once at construction time using | ||
| * `param ?? env ?? default` precedence (with `param` being undefined at | ||
| * construction — those are supplied per call to {@link resolve}). The pinned | ||
| * deviceId starts unset and is assigned on the first successful identity probe. | ||
| */ | ||
| export declare class HermesSession { | ||
| #private; | ||
| constructor(options?: HermesSessionOptions); | ||
| /** | ||
| * Returns the session-default Metro inspector proxy port. | ||
| * | ||
| * @returns The resolved Metro port (env `HERMES_METRO_PORT`, else 8081). | ||
| */ | ||
| getMetroPort(): number; | ||
| /** | ||
| * Returns the session-default expected app bundle ID. | ||
| * | ||
| * @returns The resolved app bundle ID (env `HERMES_APP_ID`, else the | ||
| * platform default). | ||
| */ | ||
| getAppId(): string; | ||
| /** | ||
| * Returns the pinned Hermes `logicalDeviceId` for this session, if any. | ||
| * | ||
| * Set on the first successful identity probe via | ||
| * {@link setPinnedHermesDeviceId}. Persists for the lifetime of this | ||
| * instance. | ||
| * | ||
| * @returns The pinned `logicalDeviceId`, or undefined when no pin is set. | ||
| */ | ||
| getPinnedHermesDeviceId(): string | undefined; | ||
| /** | ||
| * Pin the Hermes `logicalDeviceId` for this session. | ||
| * | ||
| * Called after a successful identity probe on the first call. Subsequent | ||
| * calls then enforce this pin to prevent target switching mid-session. | ||
| * | ||
| * @param id - The `logicalDeviceId` from Metro's | ||
| * `reactNative.logicalDeviceId` field. | ||
| */ | ||
| setPinnedHermesDeviceId(id: string): void; | ||
| /** | ||
| * Resolve the effective parameters for a single tool call by merging | ||
| * per-call overrides over the stored session defaults. | ||
| * | ||
| * This is PURE with respect to stored state: it never mutates the stored | ||
| * port, appId, or pin. Only {@link setPinnedHermesDeviceId} mutates the pin. | ||
| * | ||
| * Port precedence is `input.metroPort ?? this.getMetroPort()` (which itself | ||
| * already encodes `env ?? default`), mirroring the reference driver's | ||
| * `input.metroPort ?? driver.getMetroPort() ?? 8081`. | ||
| * | ||
| * AppId precedence is `input.appId ?? <HERMES_APP_ID env if set> ?? | ||
| * defaultAppIdForPlatform(input.platform) ?? <stored default>`. An explicitly | ||
| * set `HERMES_APP_ID` always WINS over the per-call platform default (env is a | ||
| * deliberate override): in that case the stored appId is returned unchanged. | ||
| * Only when the stored appId is the construction-time platform fallback does | ||
| * a per-call `platform` recompute it — without mutating stored state. When no | ||
| * `platform` is passed this preserves the original iOS-default behavior. | ||
| * | ||
| * @param input - Optional per-call overrides for port, appId, and platform. | ||
| * @returns The effective `{ metroPort, appId, pinnedDeviceId }`. | ||
| */ | ||
| resolve(input?: HermesSessionResolveInput): HermesSessionResolved; | ||
| } | ||
| /** | ||
| * Returns the process-singleton {@link HermesSession}, creating it on first | ||
| * access. | ||
| * | ||
| * @param options - Options applied only when the singleton is first created. | ||
| * @returns The shared `HermesSession` instance. | ||
| */ | ||
| export declare function getHermesSession(options?: HermesSessionOptions): HermesSession; | ||
| /** | ||
| * Resets the process-singleton {@link HermesSession}. | ||
| * | ||
| * Intended for test isolation so each test can start from a clean session. | ||
| */ | ||
| export declare function resetHermesSession(): void; | ||
| //# sourceMappingURL=session.d.mts.map |
| {"version":3,"file":"session.d.mts","sourceRoot":"","sources":["../../src/hermes/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AAEzD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAEzE;AAgBD;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,aAAa;;gBAiBZ,OAAO,GAAE,oBAAyB;IAY9C;;;;OAIG;IACH,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;IACH,QAAQ,IAAI,MAAM;IAIlB;;;;;;;;OAQG;IACH,uBAAuB,IAAI,MAAM,GAAG,SAAS;IAI7C;;;;;;;;OAQG;IACH,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIzC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,KAAK,GAAE,yBAA8B,GAAG,qBAAqB;CAqBtE;AAOD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,aAAa,CAKf;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"} |
| /** | ||
| * Standalone state module for Hermes CDP sessions. | ||
| * | ||
| * Holds the resolved Metro inspector proxy port, the expected app bundle ID, | ||
| * and the pinned Hermes `logicalDeviceId`. This module is intentionally | ||
| * INDEPENDENT of `DeviceBackend` — Hermes/Metro access is orthogonal to the | ||
| * idb/adb/Appium UI automation backends, and it stores no live connection | ||
| * (the MVP opens a fresh WebSocket per tool call). | ||
| * | ||
| * Resolution precedence for both port and appId is exactly: | ||
| * per-call param > environment variable > default. | ||
| */ | ||
| var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
| if (kind === "m") throw new TypeError("Private method is not writable"); | ||
| if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
| if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
| return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
| }; | ||
| var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
| if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
| if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
| return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
| }; | ||
| var _HermesSession_instances, _HermesSession_metroPort, _HermesSession_expectedAppId, _HermesSession_appIdFromEnv, _HermesSession_pinnedDeviceId, _HermesSession_resolveAppId; | ||
| /** Default MetaMask app bundle ID for iOS (`CFBundleIdentifier`). */ | ||
| export const DEFAULT_IOS_APP_ID = 'io.metamask.MetaMask'; | ||
| /** Default MetaMask app bundle ID for Android (`applicationId`). */ | ||
| export const DEFAULT_ANDROID_APP_ID = 'io.metamask'; | ||
| /** Default Metro inspector proxy port when none is configured. */ | ||
| export const DEFAULT_METRO_PORT = 8081; | ||
| /** | ||
| * Resolve the platform default app bundle ID. | ||
| * | ||
| * Defaults to the iOS id when the platform is unknown. Agents running against | ||
| * Android should pass `appId` per call or set `HERMES_APP_ID` if they are not | ||
| * using the default Android bundle ID. | ||
| * | ||
| * @param platform - The active platform, if known. | ||
| * @returns The default app bundle ID for the platform. | ||
| */ | ||
| export function defaultAppIdForPlatform(platform) { | ||
| return platform === 'android' ? DEFAULT_ANDROID_APP_ID : DEFAULT_IOS_APP_ID; | ||
| } | ||
| /** | ||
| * Parse a Metro port from an environment variable value. | ||
| * | ||
| * @param value - The raw environment variable value (or undefined). | ||
| * @returns The parsed integer port, or undefined when unset/invalid. | ||
| */ | ||
| function parseEnvPort(value) { | ||
| if (value === undefined) { | ||
| return undefined; | ||
| } | ||
| const parsed = Number.parseInt(value, 10); | ||
| return Number.isInteger(parsed) ? parsed : undefined; | ||
| } | ||
| /** | ||
| * Holds the Metro port, expected appId, and pinned Hermes deviceId for a | ||
| * process-lifetime Hermes session. | ||
| * | ||
| * The stored port and appId are resolved once at construction time using | ||
| * `param ?? env ?? default` precedence (with `param` being undefined at | ||
| * construction — those are supplied per call to {@link resolve}). The pinned | ||
| * deviceId starts unset and is assigned on the first successful identity probe. | ||
| */ | ||
| export class HermesSession { | ||
| constructor(options = {}) { | ||
| _HermesSession_instances.add(this); | ||
| _HermesSession_metroPort.set(this, void 0); | ||
| _HermesSession_expectedAppId.set(this, void 0); | ||
| /** | ||
| * Whether {@link #expectedAppId} came from an explicit source (the | ||
| * `HERMES_APP_ID` env var) rather than the platform fallback. When true, the | ||
| * stored appId is a deliberate override and {@link resolve} must not let a | ||
| * per-call `platform` recompute it; when false, the stored value is only the | ||
| * construction-time platform default and `resolve` may recompute it from the | ||
| * live platform. | ||
| */ | ||
| _HermesSession_appIdFromEnv.set(this, void 0); | ||
| _HermesSession_pinnedDeviceId.set(this, void 0); | ||
| const env = options.env ?? process.env; | ||
| __classPrivateFieldSet(this, _HermesSession_metroPort, parseEnvPort(env.HERMES_METRO_PORT) ?? DEFAULT_METRO_PORT, "f"); | ||
| __classPrivateFieldSet(this, _HermesSession_appIdFromEnv, env.HERMES_APP_ID !== undefined, "f"); | ||
| __classPrivateFieldSet(this, _HermesSession_expectedAppId, env.HERMES_APP_ID ?? defaultAppIdForPlatform(options.platform), "f"); | ||
| __classPrivateFieldSet(this, _HermesSession_pinnedDeviceId, undefined, "f"); | ||
| } | ||
| /** | ||
| * Returns the session-default Metro inspector proxy port. | ||
| * | ||
| * @returns The resolved Metro port (env `HERMES_METRO_PORT`, else 8081). | ||
| */ | ||
| getMetroPort() { | ||
| return __classPrivateFieldGet(this, _HermesSession_metroPort, "f"); | ||
| } | ||
| /** | ||
| * Returns the session-default expected app bundle ID. | ||
| * | ||
| * @returns The resolved app bundle ID (env `HERMES_APP_ID`, else the | ||
| * platform default). | ||
| */ | ||
| getAppId() { | ||
| return __classPrivateFieldGet(this, _HermesSession_expectedAppId, "f"); | ||
| } | ||
| /** | ||
| * Returns the pinned Hermes `logicalDeviceId` for this session, if any. | ||
| * | ||
| * Set on the first successful identity probe via | ||
| * {@link setPinnedHermesDeviceId}. Persists for the lifetime of this | ||
| * instance. | ||
| * | ||
| * @returns The pinned `logicalDeviceId`, or undefined when no pin is set. | ||
| */ | ||
| getPinnedHermesDeviceId() { | ||
| return __classPrivateFieldGet(this, _HermesSession_pinnedDeviceId, "f"); | ||
| } | ||
| /** | ||
| * Pin the Hermes `logicalDeviceId` for this session. | ||
| * | ||
| * Called after a successful identity probe on the first call. Subsequent | ||
| * calls then enforce this pin to prevent target switching mid-session. | ||
| * | ||
| * @param id - The `logicalDeviceId` from Metro's | ||
| * `reactNative.logicalDeviceId` field. | ||
| */ | ||
| setPinnedHermesDeviceId(id) { | ||
| __classPrivateFieldSet(this, _HermesSession_pinnedDeviceId, id, "f"); | ||
| } | ||
| /** | ||
| * Resolve the effective parameters for a single tool call by merging | ||
| * per-call overrides over the stored session defaults. | ||
| * | ||
| * This is PURE with respect to stored state: it never mutates the stored | ||
| * port, appId, or pin. Only {@link setPinnedHermesDeviceId} mutates the pin. | ||
| * | ||
| * Port precedence is `input.metroPort ?? this.getMetroPort()` (which itself | ||
| * already encodes `env ?? default`), mirroring the reference driver's | ||
| * `input.metroPort ?? driver.getMetroPort() ?? 8081`. | ||
| * | ||
| * AppId precedence is `input.appId ?? <HERMES_APP_ID env if set> ?? | ||
| * defaultAppIdForPlatform(input.platform) ?? <stored default>`. An explicitly | ||
| * set `HERMES_APP_ID` always WINS over the per-call platform default (env is a | ||
| * deliberate override): in that case the stored appId is returned unchanged. | ||
| * Only when the stored appId is the construction-time platform fallback does | ||
| * a per-call `platform` recompute it — without mutating stored state. When no | ||
| * `platform` is passed this preserves the original iOS-default behavior. | ||
| * | ||
| * @param input - Optional per-call overrides for port, appId, and platform. | ||
| * @returns The effective `{ metroPort, appId, pinnedDeviceId }`. | ||
| */ | ||
| resolve(input = {}) { | ||
| return { | ||
| metroPort: input.metroPort ?? __classPrivateFieldGet(this, _HermesSession_metroPort, "f"), | ||
| appId: input.appId ?? __classPrivateFieldGet(this, _HermesSession_instances, "m", _HermesSession_resolveAppId).call(this, input.platform), | ||
| pinnedDeviceId: __classPrivateFieldGet(this, _HermesSession_pinnedDeviceId, "f"), | ||
| }; | ||
| } | ||
| } | ||
| _HermesSession_metroPort = new WeakMap(), _HermesSession_expectedAppId = new WeakMap(), _HermesSession_appIdFromEnv = new WeakMap(), _HermesSession_pinnedDeviceId = new WeakMap(), _HermesSession_instances = new WeakSet(), _HermesSession_resolveAppId = function _HermesSession_resolveAppId(platform) { | ||
| if (__classPrivateFieldGet(this, _HermesSession_appIdFromEnv, "f") || platform === undefined) { | ||
| return __classPrivateFieldGet(this, _HermesSession_expectedAppId, "f"); | ||
| } | ||
| return defaultAppIdForPlatform(platform); | ||
| }; | ||
| /** | ||
| * Module-level lazily-created singleton instance. | ||
| */ | ||
| let sessionInstance; | ||
| /** | ||
| * Returns the process-singleton {@link HermesSession}, creating it on first | ||
| * access. | ||
| * | ||
| * @param options - Options applied only when the singleton is first created. | ||
| * @returns The shared `HermesSession` instance. | ||
| */ | ||
| export function getHermesSession(options) { | ||
| if (sessionInstance === undefined) { | ||
| sessionInstance = new HermesSession(options); | ||
| } | ||
| return sessionInstance; | ||
| } | ||
| /** | ||
| * Resets the process-singleton {@link HermesSession}. | ||
| * | ||
| * Intended for test isolation so each test can start from a clean session. | ||
| */ | ||
| export function resetHermesSession() { | ||
| sessionInstance = undefined; | ||
| } | ||
| //# sourceMappingURL=session.mjs.map |
| {"version":3,"file":"session.mjs","sourceRoot":"","sources":["../../src/hermes/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Standalone state module for Hermes CDP sessions.\n *\n * Holds the resolved Metro inspector proxy port, the expected app bundle ID,\n * and the pinned Hermes `logicalDeviceId`. This module is intentionally\n * INDEPENDENT of `DeviceBackend` — Hermes/Metro access is orthogonal to the\n * idb/adb/Appium UI automation backends, and it stores no live connection\n * (the MVP opens a fresh WebSocket per tool call).\n *\n * Resolution precedence for both port and appId is exactly:\n * per-call param > environment variable > default.\n */\n\n/**\n * Platform identifier used to pick the default app bundle ID.\n *\n * Kept structurally compatible with the backend's `Platform` type without\n * importing it, so this module stays decoupled from `DeviceBackend`.\n */\nexport type HermesPlatform = 'ios' | 'android';\n\n/** Default MetaMask app bundle ID for iOS (`CFBundleIdentifier`). */\nexport const DEFAULT_IOS_APP_ID = 'io.metamask.MetaMask';\n\n/** Default MetaMask app bundle ID for Android (`applicationId`). */\nexport const DEFAULT_ANDROID_APP_ID = 'io.metamask';\n\n/** Default Metro inspector proxy port when none is configured. */\nexport const DEFAULT_METRO_PORT = 8081;\n\n/**\n * Resolve the platform default app bundle ID.\n *\n * Defaults to the iOS id when the platform is unknown. Agents running against\n * Android should pass `appId` per call or set `HERMES_APP_ID` if they are not\n * using the default Android bundle ID.\n *\n * @param platform - The active platform, if known.\n * @returns The default app bundle ID for the platform.\n */\nexport function defaultAppIdForPlatform(platform?: HermesPlatform): string {\n return platform === 'android' ? DEFAULT_ANDROID_APP_ID : DEFAULT_IOS_APP_ID;\n}\n\n/**\n * Parse a Metro port from an environment variable value.\n *\n * @param value - The raw environment variable value (or undefined).\n * @returns The parsed integer port, or undefined when unset/invalid.\n */\nfunction parseEnvPort(value: string | undefined): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number.parseInt(value, 10);\n return Number.isInteger(parsed) ? parsed : undefined;\n}\n\n/**\n * Per-call overrides accepted by {@link HermesSession.resolve}.\n *\n * Any field left undefined falls back to the session default (which itself\n * falls back to the relevant environment variable, then the hardcoded default).\n */\nexport type HermesSessionResolveInput = {\n /** Override the Metro inspector proxy port for this call. */\n metroPort?: number;\n /** Override the expected app bundle ID for this call. */\n appId?: string;\n /**\n * The active platform, used to compute the default app bundle ID AT CALL\n * TIME when neither a per-call `appId` nor the `HERMES_APP_ID` env var is set.\n * This lets a session created before a device connects (when the platform is\n * not yet known) still resolve the correct Android default once the backend's\n * live platform becomes `'android'`.\n */\n platform?: HermesPlatform;\n};\n\n/**\n * The effective Hermes session parameters for a single tool call.\n */\nexport type HermesSessionResolved = {\n /** The resolved Metro inspector proxy port. */\n metroPort: number;\n /** The resolved expected app bundle ID. */\n appId: string;\n /** The pinned Hermes `logicalDeviceId`, if one has been set. */\n pinnedDeviceId: string | undefined;\n};\n\n/**\n * Options for constructing a {@link HermesSession}.\n */\nexport type HermesSessionOptions = {\n /** The active platform, used only to pick the default app bundle ID. */\n platform?: HermesPlatform;\n /**\n * Environment source. Defaults to `process.env`. Injectable for tests so\n * resolution precedence can be exercised without mutating the real env.\n */\n env?: NodeJS.ProcessEnv;\n};\n\n/**\n * Holds the Metro port, expected appId, and pinned Hermes deviceId for a\n * process-lifetime Hermes session.\n *\n * The stored port and appId are resolved once at construction time using\n * `param ?? env ?? default` precedence (with `param` being undefined at\n * construction — those are supplied per call to {@link resolve}). The pinned\n * deviceId starts unset and is assigned on the first successful identity probe.\n */\nexport class HermesSession {\n readonly #metroPort: number;\n\n readonly #expectedAppId: string;\n\n /**\n * Whether {@link #expectedAppId} came from an explicit source (the\n * `HERMES_APP_ID` env var) rather than the platform fallback. When true, the\n * stored appId is a deliberate override and {@link resolve} must not let a\n * per-call `platform` recompute it; when false, the stored value is only the\n * construction-time platform default and `resolve` may recompute it from the\n * live platform.\n */\n readonly #appIdFromEnv: boolean;\n\n #pinnedDeviceId: string | undefined;\n\n constructor(options: HermesSessionOptions = {}) {\n const env = options.env ?? process.env;\n\n this.#metroPort = parseEnvPort(env.HERMES_METRO_PORT) ?? DEFAULT_METRO_PORT;\n\n this.#appIdFromEnv = env.HERMES_APP_ID !== undefined;\n this.#expectedAppId =\n env.HERMES_APP_ID ?? defaultAppIdForPlatform(options.platform);\n\n this.#pinnedDeviceId = undefined;\n }\n\n /**\n * Returns the session-default Metro inspector proxy port.\n *\n * @returns The resolved Metro port (env `HERMES_METRO_PORT`, else 8081).\n */\n getMetroPort(): number {\n return this.#metroPort;\n }\n\n /**\n * Returns the session-default expected app bundle ID.\n *\n * @returns The resolved app bundle ID (env `HERMES_APP_ID`, else the\n * platform default).\n */\n getAppId(): string {\n return this.#expectedAppId;\n }\n\n /**\n * Returns the pinned Hermes `logicalDeviceId` for this session, if any.\n *\n * Set on the first successful identity probe via\n * {@link setPinnedHermesDeviceId}. Persists for the lifetime of this\n * instance.\n *\n * @returns The pinned `logicalDeviceId`, or undefined when no pin is set.\n */\n getPinnedHermesDeviceId(): string | undefined {\n return this.#pinnedDeviceId;\n }\n\n /**\n * Pin the Hermes `logicalDeviceId` for this session.\n *\n * Called after a successful identity probe on the first call. Subsequent\n * calls then enforce this pin to prevent target switching mid-session.\n *\n * @param id - The `logicalDeviceId` from Metro's\n * `reactNative.logicalDeviceId` field.\n */\n setPinnedHermesDeviceId(id: string): void {\n this.#pinnedDeviceId = id;\n }\n\n /**\n * Resolve the effective parameters for a single tool call by merging\n * per-call overrides over the stored session defaults.\n *\n * This is PURE with respect to stored state: it never mutates the stored\n * port, appId, or pin. Only {@link setPinnedHermesDeviceId} mutates the pin.\n *\n * Port precedence is `input.metroPort ?? this.getMetroPort()` (which itself\n * already encodes `env ?? default`), mirroring the reference driver's\n * `input.metroPort ?? driver.getMetroPort() ?? 8081`.\n *\n * AppId precedence is `input.appId ?? <HERMES_APP_ID env if set> ??\n * defaultAppIdForPlatform(input.platform) ?? <stored default>`. An explicitly\n * set `HERMES_APP_ID` always WINS over the per-call platform default (env is a\n * deliberate override): in that case the stored appId is returned unchanged.\n * Only when the stored appId is the construction-time platform fallback does\n * a per-call `platform` recompute it — without mutating stored state. When no\n * `platform` is passed this preserves the original iOS-default behavior.\n *\n * @param input - Optional per-call overrides for port, appId, and platform.\n * @returns The effective `{ metroPort, appId, pinnedDeviceId }`.\n */\n resolve(input: HermesSessionResolveInput = {}): HermesSessionResolved {\n return {\n metroPort: input.metroPort ?? this.#metroPort,\n appId: input.appId ?? this.#resolveAppId(input.platform),\n pinnedDeviceId: this.#pinnedDeviceId,\n };\n }\n\n /**\n * Compute the effective default appId for a call, honouring the\n * env-over-platform-default precedence documented on {@link resolve}.\n *\n * @param platform - The live platform passed for this call, if any.\n * @returns The stored env/explicit appId, or the platform default.\n */\n #resolveAppId(platform: HermesPlatform | undefined): string {\n if (this.#appIdFromEnv || platform === undefined) {\n return this.#expectedAppId;\n }\n return defaultAppIdForPlatform(platform);\n }\n}\n\n/**\n * Module-level lazily-created singleton instance.\n */\nlet sessionInstance: HermesSession | undefined;\n\n/**\n * Returns the process-singleton {@link HermesSession}, creating it on first\n * access.\n *\n * @param options - Options applied only when the singleton is first created.\n * @returns The shared `HermesSession` instance.\n */\nexport function getHermesSession(\n options?: HermesSessionOptions,\n): HermesSession {\n if (sessionInstance === undefined) {\n sessionInstance = new HermesSession(options);\n }\n return sessionInstance;\n}\n\n/**\n * Resets the process-singleton {@link HermesSession}.\n *\n * Intended for test isolation so each test can start from a clean session.\n */\nexport function resetHermesSession(): void {\n sessionInstance = undefined;\n}\n"]} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.registerHermesCdpTool = registerHermesCdpTool; | ||
| const zod_1 = require("zod"); | ||
| const shared_js_1 = require("./shared.cjs"); | ||
| const hermes_cdp_js_1 = require("../hermes/hermes-cdp.cjs"); | ||
| const session_js_1 = require("../hermes/session.cjs"); | ||
| const DEFAULT_TIMEOUT_MS = 30000; | ||
| const MIN_TIMEOUT_MS = 1000; | ||
| const MAX_TIMEOUT_MS = 120000; | ||
| /** | ||
| * Registers the `hermes_cdp` MCP tool, which speaks raw Chrome DevTools | ||
| * Protocol (CDP) to the React Native Hermes runtime of a running app via | ||
| * Metro's inspector proxy (iOS and Android). | ||
| * | ||
| * The handler is intentionally THIN: it resolves the Metro port/appId and the | ||
| * current device pin from {@link getHermesSession}, delegates ALL discovery, | ||
| * verification, and execution logic to {@link runHermesCdp}, and persists the | ||
| * resolved pin via the core's `onPin` callback. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| function registerHermesCdpTool(server, backend) { | ||
| server.registerTool('hermes_cdp', { | ||
| title: 'Hermes CDP', | ||
| description: 'Speak raw Chrome DevTools Protocol (CDP) to the React Native Hermes ' + | ||
| 'JS runtime of a running app via Metro\u2019s inspector proxy (iOS and ' + | ||
| 'Android). Requires a DEBUG build with Metro running; release builds ' + | ||
| 'expose no inspector. Blocked methods (for safety): ' + | ||
| 'Runtime.terminateExecution, Inspector.detached. Node 20 must be ' + | ||
| 'launched with NODE_OPTIONS="--experimental-websocket" (Node 22+ works ' + | ||
| 'out of the box). Example: method "Runtime.evaluate" with params ' + | ||
| '{"expression":"1+1","returnByValue":true} \u2014 the raw response nests ' + | ||
| 'the value at result.result.value.', | ||
| inputSchema: { | ||
| method: zod_1.z | ||
| .string() | ||
| .describe('The CDP method to invoke, e.g. "Runtime.evaluate".'), | ||
| params: zod_1.z | ||
| .record(zod_1.z.string(), zod_1.z.unknown()) | ||
| .optional() | ||
| .describe('Optional CDP method parameters.'), | ||
| timeoutMs: zod_1.z | ||
| .number() | ||
| .int() | ||
| .positive() | ||
| .optional() | ||
| .describe(`Timeout in milliseconds applied to each phase independently: ` + | ||
| `discovery, socket open, identity probe, and the CDP call ` + | ||
| `(default ${DEFAULT_TIMEOUT_MS}, floored to ${MIN_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}).`), | ||
| metroPort: zod_1.z | ||
| .number() | ||
| .optional() | ||
| .describe('Override the Metro inspector proxy port for this call.'), | ||
| appId: zod_1.z | ||
| .string() | ||
| .optional() | ||
| .describe('Override the expected app bundle identifier.'), | ||
| }, | ||
| }, async ({ method, params, timeoutMs, metroPort, appId }) => { | ||
| try { | ||
| const session = (0, session_js_1.getHermesSession)(); | ||
| const resolved = session.resolve({ | ||
| metroPort, | ||
| appId, | ||
| platform: backend.platform, | ||
| }); | ||
| const clampedTimeoutMs = Math.min(Math.max(timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS); | ||
| const result = await (0, hermes_cdp_js_1.runHermesCdp)({ | ||
| method, | ||
| params, | ||
| timeoutMs: clampedTimeoutMs, | ||
| metroPort: resolved.metroPort, | ||
| appId: resolved.appId, | ||
| pinnedDeviceId: resolved.pinnedDeviceId, | ||
| onPin: (id) => { | ||
| const currentPin = session.getPinnedHermesDeviceId(); | ||
| if (currentPin === undefined) { | ||
| session.setPinnedHermesDeviceId(id); | ||
| } | ||
| }, | ||
| }); | ||
| if (!result.ok) { | ||
| return (0, shared_js_1.errorResult)(new Error(`[${result.code}] ${result.message}`)); | ||
| } | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: JSON.stringify(result.result, null, 2), | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| catch (error) { | ||
| return (0, shared_js_1.errorResult)(error); | ||
| } | ||
| }); | ||
| } | ||
| //# sourceMappingURL=hermes-cdp.cjs.map |
| {"version":3,"file":"hermes-cdp.cjs","sourceRoot":"","sources":["../../src/tools/hermes-cdp.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\nimport { runHermesCdp } from '../hermes/hermes-cdp.js';\nimport { getHermesSession } from '../hermes/session.js';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MIN_TIMEOUT_MS = 1_000;\nconst MAX_TIMEOUT_MS = 120_000;\n\n/**\n * Registers the `hermes_cdp` MCP tool, which speaks raw Chrome DevTools\n * Protocol (CDP) to the React Native Hermes runtime of a running app via\n * Metro's inspector proxy (iOS and Android).\n *\n * The handler is intentionally THIN: it resolves the Metro port/appId and the\n * current device pin from {@link getHermesSession}, delegates ALL discovery,\n * verification, and execution logic to {@link runHermesCdp}, and persists the\n * resolved pin via the core's `onPin` callback.\n *\n * @param server - The MCP server to register the tool on.\n * @param backend - The lazy device backend, used at call time only to read the\n * live platform so the session can resolve the correct default app bundle ID.\n */\nexport function registerHermesCdpTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'hermes_cdp',\n {\n title: 'Hermes CDP',\n description:\n 'Speak raw Chrome DevTools Protocol (CDP) to the React Native Hermes ' +\n 'JS runtime of a running app via Metro\\u2019s inspector proxy (iOS and ' +\n 'Android). Requires a DEBUG build with Metro running; release builds ' +\n 'expose no inspector. Blocked methods (for safety): ' +\n 'Runtime.terminateExecution, Inspector.detached. Node 20 must be ' +\n 'launched with NODE_OPTIONS=\"--experimental-websocket\" (Node 22+ works ' +\n 'out of the box). Example: method \"Runtime.evaluate\" with params ' +\n '{\"expression\":\"1+1\",\"returnByValue\":true} \\u2014 the raw response nests ' +\n 'the value at result.result.value.',\n inputSchema: {\n method: z\n .string()\n .describe('The CDP method to invoke, e.g. \"Runtime.evaluate\".'),\n params: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Optional CDP method parameters.'),\n timeoutMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n `Timeout in milliseconds applied to each phase independently: ` +\n `discovery, socket open, identity probe, and the CDP call ` +\n `(default ${DEFAULT_TIMEOUT_MS}, floored to ${MIN_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}).`,\n ),\n metroPort: z\n .number()\n .optional()\n .describe('Override the Metro inspector proxy port for this call.'),\n appId: z\n .string()\n .optional()\n .describe('Override the expected app bundle identifier.'),\n },\n },\n async ({ method, params, timeoutMs, metroPort, appId }) => {\n try {\n const session = getHermesSession();\n const resolved = session.resolve({\n metroPort,\n appId,\n platform: backend.platform,\n });\n const clampedTimeoutMs = Math.min(\n Math.max(timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS),\n MAX_TIMEOUT_MS,\n );\n\n const result = await runHermesCdp({\n method,\n params,\n timeoutMs: clampedTimeoutMs,\n metroPort: resolved.metroPort,\n appId: resolved.appId,\n pinnedDeviceId: resolved.pinnedDeviceId,\n onPin: (id) => {\n const currentPin = session.getPinnedHermesDeviceId();\n if (currentPin === undefined) {\n session.setPinnedHermesDeviceId(id);\n }\n },\n });\n\n if (!result.ok) {\n return errorResult(new Error(`[${result.code}] ${result.message}`));\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]} |
| import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import type { DeviceBackend } from "../backends/types.cjs"; | ||
| /** | ||
| * Registers the `hermes_cdp` MCP tool, which speaks raw Chrome DevTools | ||
| * Protocol (CDP) to the React Native Hermes runtime of a running app via | ||
| * Metro's inspector proxy (iOS and Android). | ||
| * | ||
| * The handler is intentionally THIN: it resolves the Metro port/appId and the | ||
| * current device pin from {@link getHermesSession}, delegates ALL discovery, | ||
| * verification, and execution logic to {@link runHermesCdp}, and persists the | ||
| * resolved pin via the core's `onPin` callback. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| export declare function registerHermesCdpTool(server: McpServer, backend: DeviceBackend): void; | ||
| //# sourceMappingURL=hermes-cdp.d.cts.map |
| {"version":3,"file":"hermes-cdp.d.cts","sourceRoot":"","sources":["../../src/tools/hermes-cdp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAQ1D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAwFN"} |
| import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import type { DeviceBackend } from "../backends/types.mjs"; | ||
| /** | ||
| * Registers the `hermes_cdp` MCP tool, which speaks raw Chrome DevTools | ||
| * Protocol (CDP) to the React Native Hermes runtime of a running app via | ||
| * Metro's inspector proxy (iOS and Android). | ||
| * | ||
| * The handler is intentionally THIN: it resolves the Metro port/appId and the | ||
| * current device pin from {@link getHermesSession}, delegates ALL discovery, | ||
| * verification, and execution logic to {@link runHermesCdp}, and persists the | ||
| * resolved pin via the core's `onPin` callback. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| export declare function registerHermesCdpTool(server: McpServer, backend: DeviceBackend): void; | ||
| //# sourceMappingURL=hermes-cdp.d.mts.map |
| {"version":3,"file":"hermes-cdp.d.mts","sourceRoot":"","sources":["../../src/tools/hermes-cdp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAQ1D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAwFN"} |
| import { z } from "zod"; | ||
| import { errorResult } from "./shared.mjs"; | ||
| import { runHermesCdp } from "../hermes/hermes-cdp.mjs"; | ||
| import { getHermesSession } from "../hermes/session.mjs"; | ||
| const DEFAULT_TIMEOUT_MS = 30000; | ||
| const MIN_TIMEOUT_MS = 1000; | ||
| const MAX_TIMEOUT_MS = 120000; | ||
| /** | ||
| * Registers the `hermes_cdp` MCP tool, which speaks raw Chrome DevTools | ||
| * Protocol (CDP) to the React Native Hermes runtime of a running app via | ||
| * Metro's inspector proxy (iOS and Android). | ||
| * | ||
| * The handler is intentionally THIN: it resolves the Metro port/appId and the | ||
| * current device pin from {@link getHermesSession}, delegates ALL discovery, | ||
| * verification, and execution logic to {@link runHermesCdp}, and persists the | ||
| * resolved pin via the core's `onPin` callback. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| export function registerHermesCdpTool(server, backend) { | ||
| server.registerTool('hermes_cdp', { | ||
| title: 'Hermes CDP', | ||
| description: 'Speak raw Chrome DevTools Protocol (CDP) to the React Native Hermes ' + | ||
| 'JS runtime of a running app via Metro\u2019s inspector proxy (iOS and ' + | ||
| 'Android). Requires a DEBUG build with Metro running; release builds ' + | ||
| 'expose no inspector. Blocked methods (for safety): ' + | ||
| 'Runtime.terminateExecution, Inspector.detached. Node 20 must be ' + | ||
| 'launched with NODE_OPTIONS="--experimental-websocket" (Node 22+ works ' + | ||
| 'out of the box). Example: method "Runtime.evaluate" with params ' + | ||
| '{"expression":"1+1","returnByValue":true} \u2014 the raw response nests ' + | ||
| 'the value at result.result.value.', | ||
| inputSchema: { | ||
| method: z | ||
| .string() | ||
| .describe('The CDP method to invoke, e.g. "Runtime.evaluate".'), | ||
| params: z | ||
| .record(z.string(), z.unknown()) | ||
| .optional() | ||
| .describe('Optional CDP method parameters.'), | ||
| timeoutMs: z | ||
| .number() | ||
| .int() | ||
| .positive() | ||
| .optional() | ||
| .describe(`Timeout in milliseconds applied to each phase independently: ` + | ||
| `discovery, socket open, identity probe, and the CDP call ` + | ||
| `(default ${DEFAULT_TIMEOUT_MS}, floored to ${MIN_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}).`), | ||
| metroPort: z | ||
| .number() | ||
| .optional() | ||
| .describe('Override the Metro inspector proxy port for this call.'), | ||
| appId: z | ||
| .string() | ||
| .optional() | ||
| .describe('Override the expected app bundle identifier.'), | ||
| }, | ||
| }, async ({ method, params, timeoutMs, metroPort, appId }) => { | ||
| try { | ||
| const session = getHermesSession(); | ||
| const resolved = session.resolve({ | ||
| metroPort, | ||
| appId, | ||
| platform: backend.platform, | ||
| }); | ||
| const clampedTimeoutMs = Math.min(Math.max(timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS); | ||
| const result = await runHermesCdp({ | ||
| method, | ||
| params, | ||
| timeoutMs: clampedTimeoutMs, | ||
| metroPort: resolved.metroPort, | ||
| appId: resolved.appId, | ||
| pinnedDeviceId: resolved.pinnedDeviceId, | ||
| onPin: (id) => { | ||
| const currentPin = session.getPinnedHermesDeviceId(); | ||
| if (currentPin === undefined) { | ||
| session.setPinnedHermesDeviceId(id); | ||
| } | ||
| }, | ||
| }); | ||
| if (!result.ok) { | ||
| return errorResult(new Error(`[${result.code}] ${result.message}`)); | ||
| } | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: JSON.stringify(result.result, null, 2), | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| catch (error) { | ||
| return errorResult(error); | ||
| } | ||
| }); | ||
| } | ||
| //# sourceMappingURL=hermes-cdp.mjs.map |
| {"version":3,"file":"hermes-cdp.mjs","sourceRoot":"","sources":["../../src/tools/hermes-cdp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAE1C,OAAO,EAAE,YAAY,EAAE,iCAAgC;AACvD,OAAO,EAAE,gBAAgB,EAAE,8BAA6B;AAExD,MAAM,kBAAkB,GAAG,KAAM,CAAC;AAClC,MAAM,cAAc,GAAG,IAAK,CAAC;AAC7B,MAAM,cAAc,GAAG,MAAO,CAAC;AAE/B;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,sEAAsE;YACtE,wEAAwE;YACxE,sEAAsE;YACtE,qDAAqD;YACrD,kEAAkE;YAClE,wEAAwE;YACxE,kEAAkE;YAClE,0EAA0E;YAC1E,mCAAmC;QACrC,WAAW,EAAE;YACX,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,QAAQ,CAAC,oDAAoD,CAAC;YACjE,MAAM,EAAE,CAAC;iBACN,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;iBAC/B,QAAQ,EAAE;iBACV,QAAQ,CAAC,iCAAiC,CAAC;YAC9C,SAAS,EAAE,CAAC;iBACT,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,EAAE;iBACV,QAAQ,EAAE;iBACV,QAAQ,CACP,+DAA+D;gBAC7D,2DAA2D;gBAC3D,YAAY,kBAAkB,gBAAgB,cAAc,SAAS,cAAc,IAAI,CAC1F;YACH,SAAS,EAAE,CAAC;iBACT,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,wDAAwD,CAAC;YACrE,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,8CAA8C,CAAC;SAC5D;KACF,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;QACxD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;gBAC/B,SAAS;gBACT,KAAK;gBACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;aAC3B,CAAC,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAC/B,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,kBAAkB,EAAE,cAAc,CAAC,EACzD,cAAc,CACf,CAAC;YAEF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;gBAChC,MAAM;gBACN,MAAM;gBACN,SAAS,EAAE,gBAAgB;gBAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,cAAc,EAAE,QAAQ,CAAC,cAAc;gBACvC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;oBACZ,MAAM,UAAU,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;oBACrD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;wBAC7B,OAAO,CAAC,uBAAuB,CAAC,EAAE,CAAC,CAAC;oBACtC,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,OAAO,WAAW,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACtE,CAAC;YAED,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;qBAC7C;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\nimport { runHermesCdp } from '../hermes/hermes-cdp.js';\nimport { getHermesSession } from '../hermes/session.js';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MIN_TIMEOUT_MS = 1_000;\nconst MAX_TIMEOUT_MS = 120_000;\n\n/**\n * Registers the `hermes_cdp` MCP tool, which speaks raw Chrome DevTools\n * Protocol (CDP) to the React Native Hermes runtime of a running app via\n * Metro's inspector proxy (iOS and Android).\n *\n * The handler is intentionally THIN: it resolves the Metro port/appId and the\n * current device pin from {@link getHermesSession}, delegates ALL discovery,\n * verification, and execution logic to {@link runHermesCdp}, and persists the\n * resolved pin via the core's `onPin` callback.\n *\n * @param server - The MCP server to register the tool on.\n * @param backend - The lazy device backend, used at call time only to read the\n * live platform so the session can resolve the correct default app bundle ID.\n */\nexport function registerHermesCdpTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'hermes_cdp',\n {\n title: 'Hermes CDP',\n description:\n 'Speak raw Chrome DevTools Protocol (CDP) to the React Native Hermes ' +\n 'JS runtime of a running app via Metro\\u2019s inspector proxy (iOS and ' +\n 'Android). Requires a DEBUG build with Metro running; release builds ' +\n 'expose no inspector. Blocked methods (for safety): ' +\n 'Runtime.terminateExecution, Inspector.detached. Node 20 must be ' +\n 'launched with NODE_OPTIONS=\"--experimental-websocket\" (Node 22+ works ' +\n 'out of the box). Example: method \"Runtime.evaluate\" with params ' +\n '{\"expression\":\"1+1\",\"returnByValue\":true} \\u2014 the raw response nests ' +\n 'the value at result.result.value.',\n inputSchema: {\n method: z\n .string()\n .describe('The CDP method to invoke, e.g. \"Runtime.evaluate\".'),\n params: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Optional CDP method parameters.'),\n timeoutMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n `Timeout in milliseconds applied to each phase independently: ` +\n `discovery, socket open, identity probe, and the CDP call ` +\n `(default ${DEFAULT_TIMEOUT_MS}, floored to ${MIN_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}).`,\n ),\n metroPort: z\n .number()\n .optional()\n .describe('Override the Metro inspector proxy port for this call.'),\n appId: z\n .string()\n .optional()\n .describe('Override the expected app bundle identifier.'),\n },\n },\n async ({ method, params, timeoutMs, metroPort, appId }) => {\n try {\n const session = getHermesSession();\n const resolved = session.resolve({\n metroPort,\n appId,\n platform: backend.platform,\n });\n const clampedTimeoutMs = Math.min(\n Math.max(timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS),\n MAX_TIMEOUT_MS,\n );\n\n const result = await runHermesCdp({\n method,\n params,\n timeoutMs: clampedTimeoutMs,\n metroPort: resolved.metroPort,\n appId: resolved.appId,\n pinnedDeviceId: resolved.pinnedDeviceId,\n onPin: (id) => {\n const currentPin = session.getPinnedHermesDeviceId();\n if (currentPin === undefined) {\n session.setPinnedHermesDeviceId(id);\n }\n },\n });\n\n if (!result.ok) {\n return errorResult(new Error(`[${result.code}] ${result.message}`));\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result.result, null, 2),\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.registerHermesTargetsTool = registerHermesTargetsTool; | ||
| const zod_1 = require("zod"); | ||
| const shared_js_1 = require("./shared.cjs"); | ||
| const hermes_cdp_js_1 = require("../hermes/hermes-cdp.cjs"); | ||
| const session_js_1 = require("../hermes/session.cjs"); | ||
| const DISCOVERY_TIMEOUT_MS = 10000; | ||
| function formatTarget(target) { | ||
| const { reactNative } = target; | ||
| return [ | ||
| ` id: ${target.id ?? '<unknown>'}`, | ||
| ` title: ${target.title ?? '<unknown>'}`, | ||
| ` appId: ${target.appId ?? '<unknown>'}`, | ||
| ` logicalDeviceId: ${reactNative?.logicalDeviceId ?? '<missing>'}`, | ||
| ` nativePageReloads: ${reactNative?.capabilities?.nativePageReloads ?? '<unset>'}`, | ||
| ` wsUrl: ${target.webSocketDebuggerUrl ?? '<missing>'}`, | ||
| ].join('\n'); | ||
| } | ||
| function describeMetroDown(error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| if (message.includes('ECONNREFUSED') || message.includes('fetch failed')) { | ||
| return 'Metro not running or no debuggable app registered.'; | ||
| } | ||
| return undefined; | ||
| } | ||
| /** | ||
| * Registers the `hermes_targets` MCP tool, which lists and diagnoses the | ||
| * debuggable Hermes targets that Metro's inspector proxy currently exposes | ||
| * (iOS and Android). | ||
| * | ||
| * The handler is THIN: it resolves the Metro port/appId from | ||
| * {@link getHermesSession}, delegates discovery to {@link fetchDiscoveryTargets} | ||
| * and selection/ambiguity reporting to {@link selectHermesTarget} / | ||
| * {@link hasAmbiguousTarget}, and formats a readable report. With `all` set, | ||
| * it bypasses the appId filter so agents can discover the real appId / confirm | ||
| * Metro is up. No discovery or selection LOGIC lives in this file. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| function registerHermesTargetsTool(server, backend) { | ||
| server.registerTool('hermes_targets', { | ||
| title: 'Hermes Targets', | ||
| description: 'List and diagnose the debuggable React Native Hermes targets that ' + | ||
| 'Metro\u2019s inspector proxy currently exposes (iOS and Android). ' + | ||
| 'Reports each candidate (id / title / appId / logicalDeviceId / ' + | ||
| 'nativePageReloads / wsUrl) plus which target would be chosen or why ' + | ||
| 'it is ambiguous. Use it to confirm Metro is running and your app is ' + | ||
| 'registered. Requires a DEBUG build with Metro running.', | ||
| inputSchema: { | ||
| metroPort: zod_1.z | ||
| .number() | ||
| .optional() | ||
| .describe('Override the Metro inspector proxy port for this call.'), | ||
| appId: zod_1.z | ||
| .string() | ||
| .optional() | ||
| .describe('Override the expected app bundle identifier.'), | ||
| all: zod_1.z | ||
| .boolean() | ||
| .optional() | ||
| .describe('Bypass the appId filter and list ALL candidates (diagnostics).'), | ||
| }, | ||
| annotations: { readOnlyHint: true }, | ||
| }, async ({ metroPort, appId, all }) => { | ||
| try { | ||
| const resolved = (0, session_js_1.getHermesSession)().resolve({ | ||
| metroPort, | ||
| appId, | ||
| platform: backend.platform, | ||
| }); | ||
| const targets = await (0, hermes_cdp_js_1.fetchDiscoveryTargets)(resolved.metroPort, DISCOVERY_TIMEOUT_MS); | ||
| if (targets.length === 0) { | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: 'Metro not running or no debuggable app registered.', | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| const candidates = all | ||
| ? targets | ||
| : targets | ||
| .filter((target) => Boolean(target.webSocketDebuggerUrl)) | ||
| .filter((target) => target.title !== hermes_cdp_js_1.LEGACY_SYNTHETIC_TITLE) | ||
| .filter((target) => target.appId === resolved.appId); | ||
| const lines = [ | ||
| `Metro port: ${resolved.metroPort}`, | ||
| `Expected appId: ${resolved.appId}${all ? ' (filter bypassed by all=true)' : ''}`, | ||
| `Targets discovered: ${targets.length}`, | ||
| `Candidates listed: ${candidates.length}`, | ||
| '', | ||
| ]; | ||
| candidates.forEach((target, index) => { | ||
| lines.push(`Candidate ${index + 1}:`, formatTarget(target), ''); | ||
| }); | ||
| const selection = (0, hermes_cdp_js_1.selectHermesTarget)(targets, resolved.appId, resolved.pinnedDeviceId); | ||
| if (selection.ok) { | ||
| lines.push(`Chosen target: ${selection.target.id ?? '<unknown>'} (device=${selection.target.reactNative?.logicalDeviceId ?? '<missing>'})`); | ||
| } | ||
| else if (selection.code === 'HERMES_MULTIPLE_DEVICES' && | ||
| (0, hermes_cdp_js_1.hasAmbiguousTarget)(candidates)) { | ||
| lines.push(`Ambiguous: ${selection.message}`); | ||
| } | ||
| else { | ||
| lines.push(`No target chosen: [${selection.code}] ${selection.message}`); | ||
| } | ||
| return { | ||
| content: [{ type: 'text', text: lines.join('\n') }], | ||
| }; | ||
| } | ||
| catch (error) { | ||
| const metroDown = describeMetroDown(error); | ||
| if (metroDown) { | ||
| return { | ||
| content: [{ type: 'text', text: metroDown }], | ||
| }; | ||
| } | ||
| return (0, shared_js_1.errorResult)(error); | ||
| } | ||
| }); | ||
| } | ||
| //# sourceMappingURL=hermes-targets.cjs.map |
| {"version":3,"file":"hermes-targets.cjs","sourceRoot":"","sources":["../../src/tools/hermes-targets.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\nimport {\n fetchDiscoveryTargets,\n hasAmbiguousTarget,\n LEGACY_SYNTHETIC_TITLE,\n selectHermesTarget,\n} from '../hermes/hermes-cdp.js';\nimport type { HermesTarget } from '../hermes/hermes-cdp.js';\nimport { getHermesSession } from '../hermes/session.js';\n\nconst DISCOVERY_TIMEOUT_MS = 10_000;\n\nfunction formatTarget(target: HermesTarget): string {\n const { reactNative } = target;\n return [\n ` id: ${target.id ?? '<unknown>'}`,\n ` title: ${target.title ?? '<unknown>'}`,\n ` appId: ${target.appId ?? '<unknown>'}`,\n ` logicalDeviceId: ${reactNative?.logicalDeviceId ?? '<missing>'}`,\n ` nativePageReloads: ${\n reactNative?.capabilities?.nativePageReloads ?? '<unset>'\n }`,\n ` wsUrl: ${target.webSocketDebuggerUrl ?? '<missing>'}`,\n ].join('\\n');\n}\n\nfunction describeMetroDown(error: unknown): string | undefined {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('ECONNREFUSED') || message.includes('fetch failed')) {\n return 'Metro not running or no debuggable app registered.';\n }\n return undefined;\n}\n\n/**\n * Registers the `hermes_targets` MCP tool, which lists and diagnoses the\n * debuggable Hermes targets that Metro's inspector proxy currently exposes\n * (iOS and Android).\n *\n * The handler is THIN: it resolves the Metro port/appId from\n * {@link getHermesSession}, delegates discovery to {@link fetchDiscoveryTargets}\n * and selection/ambiguity reporting to {@link selectHermesTarget} /\n * {@link hasAmbiguousTarget}, and formats a readable report. With `all` set,\n * it bypasses the appId filter so agents can discover the real appId / confirm\n * Metro is up. No discovery or selection LOGIC lives in this file.\n *\n * @param server - The MCP server to register the tool on.\n * @param backend - The lazy device backend, used at call time only to read the\n * live platform so the session can resolve the correct default app bundle ID.\n */\nexport function registerHermesTargetsTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'hermes_targets',\n {\n title: 'Hermes Targets',\n description:\n 'List and diagnose the debuggable React Native Hermes targets that ' +\n 'Metro\\u2019s inspector proxy currently exposes (iOS and Android). ' +\n 'Reports each candidate (id / title / appId / logicalDeviceId / ' +\n 'nativePageReloads / wsUrl) plus which target would be chosen or why ' +\n 'it is ambiguous. Use it to confirm Metro is running and your app is ' +\n 'registered. Requires a DEBUG build with Metro running.',\n inputSchema: {\n metroPort: z\n .number()\n .optional()\n .describe('Override the Metro inspector proxy port for this call.'),\n appId: z\n .string()\n .optional()\n .describe('Override the expected app bundle identifier.'),\n all: z\n .boolean()\n .optional()\n .describe(\n 'Bypass the appId filter and list ALL candidates (diagnostics).',\n ),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ metroPort, appId, all }) => {\n try {\n const resolved = getHermesSession().resolve({\n metroPort,\n appId,\n platform: backend.platform,\n });\n const targets = await fetchDiscoveryTargets(\n resolved.metroPort,\n DISCOVERY_TIMEOUT_MS,\n );\n\n if (targets.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Metro not running or no debuggable app registered.',\n },\n ],\n };\n }\n\n const candidates = all\n ? targets\n : targets\n .filter((target) => Boolean(target.webSocketDebuggerUrl))\n .filter((target) => target.title !== LEGACY_SYNTHETIC_TITLE)\n .filter((target) => target.appId === resolved.appId);\n\n const lines: string[] = [\n `Metro port: ${resolved.metroPort}`,\n `Expected appId: ${resolved.appId}${all ? ' (filter bypassed by all=true)' : ''}`,\n `Targets discovered: ${targets.length}`,\n `Candidates listed: ${candidates.length}`,\n '',\n ];\n\n candidates.forEach((target, index) => {\n lines.push(`Candidate ${index + 1}:`, formatTarget(target), '');\n });\n\n const selection = selectHermesTarget(\n targets,\n resolved.appId,\n resolved.pinnedDeviceId,\n );\n if (selection.ok) {\n lines.push(\n `Chosen target: ${selection.target.id ?? '<unknown>'} (device=${\n selection.target.reactNative?.logicalDeviceId ?? '<missing>'\n })`,\n );\n } else if (\n selection.code === 'HERMES_MULTIPLE_DEVICES' &&\n hasAmbiguousTarget(candidates)\n ) {\n lines.push(`Ambiguous: ${selection.message}`);\n } else {\n lines.push(\n `No target chosen: [${selection.code}] ${selection.message}`,\n );\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n } catch (error) {\n const metroDown = describeMetroDown(error);\n if (metroDown) {\n return {\n content: [{ type: 'text' as const, text: metroDown }],\n };\n }\n return errorResult(error);\n }\n },\n );\n}\n"]} |
| import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import type { DeviceBackend } from "../backends/types.cjs"; | ||
| /** | ||
| * Registers the `hermes_targets` MCP tool, which lists and diagnoses the | ||
| * debuggable Hermes targets that Metro's inspector proxy currently exposes | ||
| * (iOS and Android). | ||
| * | ||
| * The handler is THIN: it resolves the Metro port/appId from | ||
| * {@link getHermesSession}, delegates discovery to {@link fetchDiscoveryTargets} | ||
| * and selection/ambiguity reporting to {@link selectHermesTarget} / | ||
| * {@link hasAmbiguousTarget}, and formats a readable report. With `all` set, | ||
| * it bypasses the appId filter so agents can discover the real appId / confirm | ||
| * Metro is up. No discovery or selection LOGIC lives in this file. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| export declare function registerHermesTargetsTool(server: McpServer, backend: DeviceBackend): void; | ||
| //# sourceMappingURL=hermes-targets.d.cts.map |
| {"version":3,"file":"hermes-targets.d.cts","sourceRoot":"","sources":["../../src/tools/hermes-targets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAkC1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CA4GN"} |
| import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import type { DeviceBackend } from "../backends/types.mjs"; | ||
| /** | ||
| * Registers the `hermes_targets` MCP tool, which lists and diagnoses the | ||
| * debuggable Hermes targets that Metro's inspector proxy currently exposes | ||
| * (iOS and Android). | ||
| * | ||
| * The handler is THIN: it resolves the Metro port/appId from | ||
| * {@link getHermesSession}, delegates discovery to {@link fetchDiscoveryTargets} | ||
| * and selection/ambiguity reporting to {@link selectHermesTarget} / | ||
| * {@link hasAmbiguousTarget}, and formats a readable report. With `all` set, | ||
| * it bypasses the appId filter so agents can discover the real appId / confirm | ||
| * Metro is up. No discovery or selection LOGIC lives in this file. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| export declare function registerHermesTargetsTool(server: McpServer, backend: DeviceBackend): void; | ||
| //# sourceMappingURL=hermes-targets.d.mts.map |
| {"version":3,"file":"hermes-targets.d.mts","sourceRoot":"","sources":["../../src/tools/hermes-targets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAkC1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CA4GN"} |
| import { z } from "zod"; | ||
| import { errorResult } from "./shared.mjs"; | ||
| import { fetchDiscoveryTargets, hasAmbiguousTarget, LEGACY_SYNTHETIC_TITLE, selectHermesTarget } from "../hermes/hermes-cdp.mjs"; | ||
| import { getHermesSession } from "../hermes/session.mjs"; | ||
| const DISCOVERY_TIMEOUT_MS = 10000; | ||
| function formatTarget(target) { | ||
| const { reactNative } = target; | ||
| return [ | ||
| ` id: ${target.id ?? '<unknown>'}`, | ||
| ` title: ${target.title ?? '<unknown>'}`, | ||
| ` appId: ${target.appId ?? '<unknown>'}`, | ||
| ` logicalDeviceId: ${reactNative?.logicalDeviceId ?? '<missing>'}`, | ||
| ` nativePageReloads: ${reactNative?.capabilities?.nativePageReloads ?? '<unset>'}`, | ||
| ` wsUrl: ${target.webSocketDebuggerUrl ?? '<missing>'}`, | ||
| ].join('\n'); | ||
| } | ||
| function describeMetroDown(error) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| if (message.includes('ECONNREFUSED') || message.includes('fetch failed')) { | ||
| return 'Metro not running or no debuggable app registered.'; | ||
| } | ||
| return undefined; | ||
| } | ||
| /** | ||
| * Registers the `hermes_targets` MCP tool, which lists and diagnoses the | ||
| * debuggable Hermes targets that Metro's inspector proxy currently exposes | ||
| * (iOS and Android). | ||
| * | ||
| * The handler is THIN: it resolves the Metro port/appId from | ||
| * {@link getHermesSession}, delegates discovery to {@link fetchDiscoveryTargets} | ||
| * and selection/ambiguity reporting to {@link selectHermesTarget} / | ||
| * {@link hasAmbiguousTarget}, and formats a readable report. With `all` set, | ||
| * it bypasses the appId filter so agents can discover the real appId / confirm | ||
| * Metro is up. No discovery or selection LOGIC lives in this file. | ||
| * | ||
| * @param server - The MCP server to register the tool on. | ||
| * @param backend - The lazy device backend, used at call time only to read the | ||
| * live platform so the session can resolve the correct default app bundle ID. | ||
| */ | ||
| export function registerHermesTargetsTool(server, backend) { | ||
| server.registerTool('hermes_targets', { | ||
| title: 'Hermes Targets', | ||
| description: 'List and diagnose the debuggable React Native Hermes targets that ' + | ||
| 'Metro\u2019s inspector proxy currently exposes (iOS and Android). ' + | ||
| 'Reports each candidate (id / title / appId / logicalDeviceId / ' + | ||
| 'nativePageReloads / wsUrl) plus which target would be chosen or why ' + | ||
| 'it is ambiguous. Use it to confirm Metro is running and your app is ' + | ||
| 'registered. Requires a DEBUG build with Metro running.', | ||
| inputSchema: { | ||
| metroPort: z | ||
| .number() | ||
| .optional() | ||
| .describe('Override the Metro inspector proxy port for this call.'), | ||
| appId: z | ||
| .string() | ||
| .optional() | ||
| .describe('Override the expected app bundle identifier.'), | ||
| all: z | ||
| .boolean() | ||
| .optional() | ||
| .describe('Bypass the appId filter and list ALL candidates (diagnostics).'), | ||
| }, | ||
| annotations: { readOnlyHint: true }, | ||
| }, async ({ metroPort, appId, all }) => { | ||
| try { | ||
| const resolved = getHermesSession().resolve({ | ||
| metroPort, | ||
| appId, | ||
| platform: backend.platform, | ||
| }); | ||
| const targets = await fetchDiscoveryTargets(resolved.metroPort, DISCOVERY_TIMEOUT_MS); | ||
| if (targets.length === 0) { | ||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: 'Metro not running or no debuggable app registered.', | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| const candidates = all | ||
| ? targets | ||
| : targets | ||
| .filter((target) => Boolean(target.webSocketDebuggerUrl)) | ||
| .filter((target) => target.title !== LEGACY_SYNTHETIC_TITLE) | ||
| .filter((target) => target.appId === resolved.appId); | ||
| const lines = [ | ||
| `Metro port: ${resolved.metroPort}`, | ||
| `Expected appId: ${resolved.appId}${all ? ' (filter bypassed by all=true)' : ''}`, | ||
| `Targets discovered: ${targets.length}`, | ||
| `Candidates listed: ${candidates.length}`, | ||
| '', | ||
| ]; | ||
| candidates.forEach((target, index) => { | ||
| lines.push(`Candidate ${index + 1}:`, formatTarget(target), ''); | ||
| }); | ||
| const selection = selectHermesTarget(targets, resolved.appId, resolved.pinnedDeviceId); | ||
| if (selection.ok) { | ||
| lines.push(`Chosen target: ${selection.target.id ?? '<unknown>'} (device=${selection.target.reactNative?.logicalDeviceId ?? '<missing>'})`); | ||
| } | ||
| else if (selection.code === 'HERMES_MULTIPLE_DEVICES' && | ||
| hasAmbiguousTarget(candidates)) { | ||
| lines.push(`Ambiguous: ${selection.message}`); | ||
| } | ||
| else { | ||
| lines.push(`No target chosen: [${selection.code}] ${selection.message}`); | ||
| } | ||
| return { | ||
| content: [{ type: 'text', text: lines.join('\n') }], | ||
| }; | ||
| } | ||
| catch (error) { | ||
| const metroDown = describeMetroDown(error); | ||
| if (metroDown) { | ||
| return { | ||
| content: [{ type: 'text', text: metroDown }], | ||
| }; | ||
| } | ||
| return errorResult(error); | ||
| } | ||
| }); | ||
| } | ||
| //# sourceMappingURL=hermes-targets.mjs.map |
| {"version":3,"file":"hermes-targets.mjs","sourceRoot":"","sources":["../../src/tools/hermes-targets.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\nimport {\n fetchDiscoveryTargets,\n hasAmbiguousTarget,\n LEGACY_SYNTHETIC_TITLE,\n selectHermesTarget,\n} from '../hermes/hermes-cdp.js';\nimport type { HermesTarget } from '../hermes/hermes-cdp.js';\nimport { getHermesSession } from '../hermes/session.js';\n\nconst DISCOVERY_TIMEOUT_MS = 10_000;\n\nfunction formatTarget(target: HermesTarget): string {\n const { reactNative } = target;\n return [\n ` id: ${target.id ?? '<unknown>'}`,\n ` title: ${target.title ?? '<unknown>'}`,\n ` appId: ${target.appId ?? '<unknown>'}`,\n ` logicalDeviceId: ${reactNative?.logicalDeviceId ?? '<missing>'}`,\n ` nativePageReloads: ${\n reactNative?.capabilities?.nativePageReloads ?? '<unset>'\n }`,\n ` wsUrl: ${target.webSocketDebuggerUrl ?? '<missing>'}`,\n ].join('\\n');\n}\n\nfunction describeMetroDown(error: unknown): string | undefined {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('ECONNREFUSED') || message.includes('fetch failed')) {\n return 'Metro not running or no debuggable app registered.';\n }\n return undefined;\n}\n\n/**\n * Registers the `hermes_targets` MCP tool, which lists and diagnoses the\n * debuggable Hermes targets that Metro's inspector proxy currently exposes\n * (iOS and Android).\n *\n * The handler is THIN: it resolves the Metro port/appId from\n * {@link getHermesSession}, delegates discovery to {@link fetchDiscoveryTargets}\n * and selection/ambiguity reporting to {@link selectHermesTarget} /\n * {@link hasAmbiguousTarget}, and formats a readable report. With `all` set,\n * it bypasses the appId filter so agents can discover the real appId / confirm\n * Metro is up. No discovery or selection LOGIC lives in this file.\n *\n * @param server - The MCP server to register the tool on.\n * @param backend - The lazy device backend, used at call time only to read the\n * live platform so the session can resolve the correct default app bundle ID.\n */\nexport function registerHermesTargetsTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'hermes_targets',\n {\n title: 'Hermes Targets',\n description:\n 'List and diagnose the debuggable React Native Hermes targets that ' +\n 'Metro\\u2019s inspector proxy currently exposes (iOS and Android). ' +\n 'Reports each candidate (id / title / appId / logicalDeviceId / ' +\n 'nativePageReloads / wsUrl) plus which target would be chosen or why ' +\n 'it is ambiguous. Use it to confirm Metro is running and your app is ' +\n 'registered. Requires a DEBUG build with Metro running.',\n inputSchema: {\n metroPort: z\n .number()\n .optional()\n .describe('Override the Metro inspector proxy port for this call.'),\n appId: z\n .string()\n .optional()\n .describe('Override the expected app bundle identifier.'),\n all: z\n .boolean()\n .optional()\n .describe(\n 'Bypass the appId filter and list ALL candidates (diagnostics).',\n ),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ metroPort, appId, all }) => {\n try {\n const resolved = getHermesSession().resolve({\n metroPort,\n appId,\n platform: backend.platform,\n });\n const targets = await fetchDiscoveryTargets(\n resolved.metroPort,\n DISCOVERY_TIMEOUT_MS,\n );\n\n if (targets.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Metro not running or no debuggable app registered.',\n },\n ],\n };\n }\n\n const candidates = all\n ? targets\n : targets\n .filter((target) => Boolean(target.webSocketDebuggerUrl))\n .filter((target) => target.title !== LEGACY_SYNTHETIC_TITLE)\n .filter((target) => target.appId === resolved.appId);\n\n const lines: string[] = [\n `Metro port: ${resolved.metroPort}`,\n `Expected appId: ${resolved.appId}${all ? ' (filter bypassed by all=true)' : ''}`,\n `Targets discovered: ${targets.length}`,\n `Candidates listed: ${candidates.length}`,\n '',\n ];\n\n candidates.forEach((target, index) => {\n lines.push(`Candidate ${index + 1}:`, formatTarget(target), '');\n });\n\n const selection = selectHermesTarget(\n targets,\n resolved.appId,\n resolved.pinnedDeviceId,\n );\n if (selection.ok) {\n lines.push(\n `Chosen target: ${selection.target.id ?? '<unknown>'} (device=${\n selection.target.reactNative?.logicalDeviceId ?? '<missing>'\n })`,\n );\n } else if (\n selection.code === 'HERMES_MULTIPLE_DEVICES' &&\n hasAmbiguousTarget(candidates)\n ) {\n lines.push(`Ambiguous: ${selection.message}`);\n } else {\n lines.push(\n `No target chosen: [${selection.code}] ${selection.message}`,\n );\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n } catch (error) {\n const metroDown = describeMetroDown(error);\n if (metroDown) {\n return {\n content: [{ type: 'text' as const, text: metroDown }],\n };\n }\n return errorResult(error);\n }\n },\n );\n}\n"]} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.resolveArtifactPath = resolveArtifactPath; | ||
| exports.writeArtifactFile = writeArtifactFile; | ||
| exports.hardenArtifactFile = hardenArtifactFile; | ||
| const node_crypto_1 = require("node:crypto"); | ||
| const node_fs_1 = require("node:fs"); | ||
| const promises_1 = require("node:fs/promises"); | ||
| const node_os_1 = require("node:os"); | ||
| const node_path_1 = require("node:path"); | ||
| /** Kinds of on-disk device artifact and their file extension. */ | ||
| const ARTIFACT_EXTENSION = { | ||
| screenshot: 'png', | ||
| recording: 'mp4', | ||
| }; | ||
| /** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */ | ||
| const ARTIFACT_MODE = 0o600; | ||
| /** | ||
| * Open flags for writing an artifact: create/truncate, write-only, and | ||
| * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed | ||
| * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on | ||
| * platforms that do not define it. | ||
| */ | ||
| /* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */ | ||
| const ARTIFACT_WRITE_FLAGS = node_fs_1.constants.O_WRONLY | | ||
| node_fs_1.constants.O_CREAT | | ||
| node_fs_1.constants.O_TRUNC | | ||
| (node_fs_1.constants.O_NOFOLLOW ?? 0); | ||
| /* eslint-enable no-bitwise */ | ||
| let sessionTempDir = null; | ||
| /** | ||
| * Lazily create (once per process) a private temporary directory with | ||
| * owner-only permissions (`0700`) to hold default artifacts. Because the | ||
| * directory itself is unreadable to other users, files placed inside it are | ||
| * protected even when written by an external tool (idb/adb) that we cannot | ||
| * pass secure open flags to. | ||
| * | ||
| * @returns The absolute path to the per-process private temp directory. | ||
| */ | ||
| function getSessionTempDir() { | ||
| if (!sessionTempDir) { | ||
| sessionTempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'device-mcp-')); | ||
| } | ||
| return sessionTempDir; | ||
| } | ||
| /** | ||
| * Resolve the optional output-directory allowlist from the environment. | ||
| * | ||
| * @returns The absolute allowed base directory, or `null` when unrestricted. | ||
| */ | ||
| function allowedBaseDir() { | ||
| const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim(); | ||
| return configured ? (0, node_path_1.resolve)(configured) : null; | ||
| } | ||
| /** | ||
| * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that | ||
| * sandbox is configured. No-op when the env var is unset. | ||
| * | ||
| * @param target - The already-resolved absolute destination path. | ||
| */ | ||
| function assertWithinAllowedBase(target) { | ||
| const base = allowedBaseDir(); | ||
| if (!base) { | ||
| return; | ||
| } | ||
| const rel = (0, node_path_1.relative)(base, target); | ||
| if (rel.length === 0 || rel.startsWith('..') || (0, node_path_1.isAbsolute)(rel)) { | ||
| throw new Error(`Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`); | ||
| } | ||
| } | ||
| /** | ||
| * Compute a safe absolute path for a device artifact. | ||
| * | ||
| * - When no `outputPath` is given, returns an unpredictable file name inside a | ||
| * private per-process temp directory. This replaces the previous predictable | ||
| * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink | ||
| * and information-disclosure attacks in the shared temp directory. | ||
| * - When an `outputPath` is given, it is rejected if it contains a NUL byte, | ||
| * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set — | ||
| * confined to that directory. | ||
| * | ||
| * @param outputPath - Optional caller-supplied destination path. | ||
| * @param kind - The artifact kind, used to pick the default file extension. | ||
| * @returns The resolved, validated absolute path to write to. | ||
| */ | ||
| function resolveArtifactPath(outputPath, kind) { | ||
| if (outputPath === undefined) { | ||
| const token = (0, node_crypto_1.randomBytes)(8).toString('hex'); | ||
| return (0, node_path_1.join)(getSessionTempDir(), `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`); | ||
| } | ||
| if (outputPath.includes('\0')) { | ||
| throw new Error('Invalid output path: contains a NUL byte'); | ||
| } | ||
| const target = (0, node_path_1.resolve)(outputPath); | ||
| assertWithinAllowedBase(target); | ||
| return target; | ||
| } | ||
| /** | ||
| * Write bytes to an artifact path with hardened permissions and open flags: | ||
| * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly | ||
| * in-process (e.g. the Appium backend decoding a base64 screenshot). | ||
| * | ||
| * @param path - The destination path (should come from `resolveArtifactPath`). | ||
| * @param data - The bytes to write. | ||
| */ | ||
| async function writeArtifactFile(path, data) { | ||
| await (0, promises_1.writeFile)(path, data, { | ||
| mode: ARTIFACT_MODE, | ||
| flag: ARTIFACT_WRITE_FLAGS, | ||
| }); | ||
| } | ||
| /** | ||
| * Best-effort tightening of permissions on a file written by an external tool | ||
| * (idb/adb create the file themselves, so we cannot pass secure open flags). | ||
| * Silently ignored when unsupported or when the file is absent. | ||
| * | ||
| * @param path - The path of the file to restrict to owner-only access. | ||
| */ | ||
| function hardenArtifactFile(path) { | ||
| try { | ||
| (0, node_fs_1.chmodSync)(path, ARTIFACT_MODE); | ||
| } | ||
| catch { | ||
| // Best-effort: the filesystem may not support chmod (e.g. some mounts on | ||
| // Windows) or the external tool may have failed to create the file. | ||
| } | ||
| } | ||
| //# sourceMappingURL=output-path.cjs.map |
| {"version":3,"file":"output-path.cjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]} |
| /** Kinds of on-disk device artifact and their file extension. */ | ||
| declare const ARTIFACT_EXTENSION: { | ||
| readonly screenshot: "png"; | ||
| readonly recording: "mp4"; | ||
| }; | ||
| export type ArtifactKind = keyof typeof ARTIFACT_EXTENSION; | ||
| /** | ||
| * Compute a safe absolute path for a device artifact. | ||
| * | ||
| * - When no `outputPath` is given, returns an unpredictable file name inside a | ||
| * private per-process temp directory. This replaces the previous predictable | ||
| * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink | ||
| * and information-disclosure attacks in the shared temp directory. | ||
| * - When an `outputPath` is given, it is rejected if it contains a NUL byte, | ||
| * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set — | ||
| * confined to that directory. | ||
| * | ||
| * @param outputPath - Optional caller-supplied destination path. | ||
| * @param kind - The artifact kind, used to pick the default file extension. | ||
| * @returns The resolved, validated absolute path to write to. | ||
| */ | ||
| export declare function resolveArtifactPath(outputPath: string | undefined, kind: ArtifactKind): string; | ||
| /** | ||
| * Write bytes to an artifact path with hardened permissions and open flags: | ||
| * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly | ||
| * in-process (e.g. the Appium backend decoding a base64 screenshot). | ||
| * | ||
| * @param path - The destination path (should come from `resolveArtifactPath`). | ||
| * @param data - The bytes to write. | ||
| */ | ||
| export declare function writeArtifactFile(path: string, data: Buffer): Promise<void>; | ||
| /** | ||
| * Best-effort tightening of permissions on a file written by an external tool | ||
| * (idb/adb create the file themselves, so we cannot pass secure open flags). | ||
| * Silently ignored when unsupported or when the file is absent. | ||
| * | ||
| * @param path - The path of the file to restrict to owner-only access. | ||
| */ | ||
| export declare function hardenArtifactFile(path: string): void; | ||
| export {}; | ||
| //# sourceMappingURL=output-path.d.cts.map |
| {"version":3,"file":"output-path.d.cts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAkE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"} |
| /** Kinds of on-disk device artifact and their file extension. */ | ||
| declare const ARTIFACT_EXTENSION: { | ||
| readonly screenshot: "png"; | ||
| readonly recording: "mp4"; | ||
| }; | ||
| export type ArtifactKind = keyof typeof ARTIFACT_EXTENSION; | ||
| /** | ||
| * Compute a safe absolute path for a device artifact. | ||
| * | ||
| * - When no `outputPath` is given, returns an unpredictable file name inside a | ||
| * private per-process temp directory. This replaces the previous predictable | ||
| * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink | ||
| * and information-disclosure attacks in the shared temp directory. | ||
| * - When an `outputPath` is given, it is rejected if it contains a NUL byte, | ||
| * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set — | ||
| * confined to that directory. | ||
| * | ||
| * @param outputPath - Optional caller-supplied destination path. | ||
| * @param kind - The artifact kind, used to pick the default file extension. | ||
| * @returns The resolved, validated absolute path to write to. | ||
| */ | ||
| export declare function resolveArtifactPath(outputPath: string | undefined, kind: ArtifactKind): string; | ||
| /** | ||
| * Write bytes to an artifact path with hardened permissions and open flags: | ||
| * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly | ||
| * in-process (e.g. the Appium backend decoding a base64 screenshot). | ||
| * | ||
| * @param path - The destination path (should come from `resolveArtifactPath`). | ||
| * @param data - The bytes to write. | ||
| */ | ||
| export declare function writeArtifactFile(path: string, data: Buffer): Promise<void>; | ||
| /** | ||
| * Best-effort tightening of permissions on a file written by an external tool | ||
| * (idb/adb create the file themselves, so we cannot pass secure open flags). | ||
| * Silently ignored when unsupported or when the file is absent. | ||
| * | ||
| * @param path - The path of the file to restrict to owner-only access. | ||
| */ | ||
| export declare function hardenArtifactFile(path: string): void; | ||
| export {}; | ||
| //# sourceMappingURL=output-path.d.mts.map |
| {"version":3,"file":"output-path.d.mts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAkE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"} |
| import { randomBytes } from "node:crypto"; | ||
| import { chmodSync, constants as fsConstants, mkdtempSync } from "node:fs"; | ||
| import { writeFile } from "node:fs/promises"; | ||
| import { tmpdir } from "node:os"; | ||
| import { isAbsolute, join, relative, resolve } from "node:path"; | ||
| /** Kinds of on-disk device artifact and their file extension. */ | ||
| const ARTIFACT_EXTENSION = { | ||
| screenshot: 'png', | ||
| recording: 'mp4', | ||
| }; | ||
| /** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */ | ||
| const ARTIFACT_MODE = 0o600; | ||
| /** | ||
| * Open flags for writing an artifact: create/truncate, write-only, and | ||
| * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed | ||
| * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on | ||
| * platforms that do not define it. | ||
| */ | ||
| /* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */ | ||
| const ARTIFACT_WRITE_FLAGS = fsConstants.O_WRONLY | | ||
| fsConstants.O_CREAT | | ||
| fsConstants.O_TRUNC | | ||
| (fsConstants.O_NOFOLLOW ?? 0); | ||
| /* eslint-enable no-bitwise */ | ||
| let sessionTempDir = null; | ||
| /** | ||
| * Lazily create (once per process) a private temporary directory with | ||
| * owner-only permissions (`0700`) to hold default artifacts. Because the | ||
| * directory itself is unreadable to other users, files placed inside it are | ||
| * protected even when written by an external tool (idb/adb) that we cannot | ||
| * pass secure open flags to. | ||
| * | ||
| * @returns The absolute path to the per-process private temp directory. | ||
| */ | ||
| function getSessionTempDir() { | ||
| if (!sessionTempDir) { | ||
| sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-')); | ||
| } | ||
| return sessionTempDir; | ||
| } | ||
| /** | ||
| * Resolve the optional output-directory allowlist from the environment. | ||
| * | ||
| * @returns The absolute allowed base directory, or `null` when unrestricted. | ||
| */ | ||
| function allowedBaseDir() { | ||
| const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim(); | ||
| return configured ? resolve(configured) : null; | ||
| } | ||
| /** | ||
| * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that | ||
| * sandbox is configured. No-op when the env var is unset. | ||
| * | ||
| * @param target - The already-resolved absolute destination path. | ||
| */ | ||
| function assertWithinAllowedBase(target) { | ||
| const base = allowedBaseDir(); | ||
| if (!base) { | ||
| return; | ||
| } | ||
| const rel = relative(base, target); | ||
| if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) { | ||
| throw new Error(`Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`); | ||
| } | ||
| } | ||
| /** | ||
| * Compute a safe absolute path for a device artifact. | ||
| * | ||
| * - When no `outputPath` is given, returns an unpredictable file name inside a | ||
| * private per-process temp directory. This replaces the previous predictable | ||
| * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink | ||
| * and information-disclosure attacks in the shared temp directory. | ||
| * - When an `outputPath` is given, it is rejected if it contains a NUL byte, | ||
| * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set — | ||
| * confined to that directory. | ||
| * | ||
| * @param outputPath - Optional caller-supplied destination path. | ||
| * @param kind - The artifact kind, used to pick the default file extension. | ||
| * @returns The resolved, validated absolute path to write to. | ||
| */ | ||
| export function resolveArtifactPath(outputPath, kind) { | ||
| if (outputPath === undefined) { | ||
| const token = randomBytes(8).toString('hex'); | ||
| return join(getSessionTempDir(), `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`); | ||
| } | ||
| if (outputPath.includes('\0')) { | ||
| throw new Error('Invalid output path: contains a NUL byte'); | ||
| } | ||
| const target = resolve(outputPath); | ||
| assertWithinAllowedBase(target); | ||
| return target; | ||
| } | ||
| /** | ||
| * Write bytes to an artifact path with hardened permissions and open flags: | ||
| * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly | ||
| * in-process (e.g. the Appium backend decoding a base64 screenshot). | ||
| * | ||
| * @param path - The destination path (should come from `resolveArtifactPath`). | ||
| * @param data - The bytes to write. | ||
| */ | ||
| export async function writeArtifactFile(path, data) { | ||
| await writeFile(path, data, { | ||
| mode: ARTIFACT_MODE, | ||
| flag: ARTIFACT_WRITE_FLAGS, | ||
| }); | ||
| } | ||
| /** | ||
| * Best-effort tightening of permissions on a file written by an external tool | ||
| * (idb/adb create the file themselves, so we cannot pass secure open flags). | ||
| * Silently ignored when unsupported or when the file is absent. | ||
| * | ||
| * @param path - The path of the file to restrict to owner-only access. | ||
| */ | ||
| export function hardenArtifactFile(path) { | ||
| try { | ||
| chmodSync(path, ARTIFACT_MODE); | ||
| } | ||
| catch { | ||
| // Best-effort: the filesystem may not support chmod (e.g. some mounts on | ||
| // Windows) or the external tool may have failed to create the file. | ||
| } | ||
| } | ||
| //# sourceMappingURL=output-path.mjs.map |
| {"version":3,"file":"output-path.mjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]} |
+21
-1
@@ -10,2 +10,21 @@ # Changelog | ||
| ## [0.3.0] | ||
| ### Added | ||
| - Add Hermes CDP tools (`hermes_cdp`, `hermes_targets`) for inspecting the React Native Hermes JS runtime via Metro ([#14](https://github.com/MetaMask/device-mcp/pull/14)) | ||
| ### Changed | ||
| - Make screenshot base64 encoding optional — `DeviceBackend.screenshot(outputPath, { encode })` accepts `{ encode: false }` to return only the file `path` and skip base64 encoding. `ScreenshotResult.data` is now optional; the default (`encode: true`) preserves existing behavior ([#15](https://github.com/MetaMask/device-mcp/pull/15)) | ||
| - The Appium backend now saves the screenshot to `outputPath` when one is provided (previously the argument was ignored) ([#15](https://github.com/MetaMask/device-mcp/pull/15)) | ||
| ### Fixed | ||
| - Fix `device_screenshot` failing on macOS — the captured PNG is now read in-process via `fs.readFile(path, 'base64')` instead of shelling out to the `base64` CLI, which is not portable (BSD `base64` on macOS rejects the GNU positional-file syntax that was used previously) ([#15](https://github.com/MetaMask/device-mcp/pull/15)) | ||
| ### Security | ||
| - Harden screenshot and screen-recording file writes across the ADB, IDB, and Appium backends — default captures now use a private per-process temporary directory (`0700`) with unpredictable names instead of the predictable `/tmp/device-mcp-*-<timestamp>` paths, files are written owner-only (`0600`), and a symlink at the destination is never followed. Caller-supplied output paths can be confined to a single directory via the new `DEVICE_MCP_OUTPUT_DIR` environment variable | ||
| ## [0.2.0] | ||
@@ -28,4 +47,5 @@ | ||
| [Unreleased]: https://github.com/MetaMask/device-mcp/compare/v0.2.0...HEAD | ||
| [Unreleased]: https://github.com/MetaMask/device-mcp/compare/v0.3.0...HEAD | ||
| [0.3.0]: https://github.com/MetaMask/device-mcp/compare/v0.2.0...v0.3.0 | ||
| [0.2.0]: https://github.com/MetaMask/device-mcp/compare/v0.1.0...v0.2.0 | ||
| [0.1.0]: https://github.com/MetaMask/device-mcp/releases/tag/v0.1.0 |
@@ -19,5 +19,7 @@ "use strict"; | ||
| const node_child_process_1 = require("node:child_process"); | ||
| const promises_1 = require("node:fs/promises"); | ||
| const alert_labels_js_1 = require("../utils/alert-labels.cjs"); | ||
| const element_js_1 = require("../utils/element.cjs"); | ||
| const exec_js_1 = require("../utils/exec.cjs"); | ||
| const output_path_js_1 = require("../utils/output-path.cjs"); | ||
| class AdbBackend { | ||
@@ -162,4 +164,4 @@ constructor(serial) { | ||
| } | ||
| async screenshot(outputPath) { | ||
| const localPath = outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`; | ||
| async screenshot(outputPath, options) { | ||
| const localPath = (0, output_path_js_1.resolveArtifactPath)(outputPath, 'screenshot'); | ||
| await __classPrivateFieldGet(this, _AdbBackend_instances, "m", _AdbBackend_adb).call(this, ['shell', 'screencap', '-p', '/sdcard/screenshot.png']); | ||
@@ -174,4 +176,8 @@ await (0, exec_js_1.execStrict)('adb', [ | ||
| await __classPrivateFieldGet(this, _AdbBackend_instances, "m", _AdbBackend_adb).call(this, ['shell', 'rm', '-f', '/sdcard/screenshot.png']); | ||
| const data = await (0, exec_js_1.execStrict)('base64', [localPath]); | ||
| return { data: data.trim(), format: 'png', path: localPath }; | ||
| (0, output_path_js_1.hardenArtifactFile)(localPath); | ||
| if (options?.encode === false) { | ||
| return { data: undefined, format: 'png', path: localPath }; | ||
| } | ||
| const data = await (0, promises_1.readFile)(localPath, 'base64'); | ||
| return { data, format: 'png', path: localPath }; | ||
| } | ||
@@ -326,3 +332,3 @@ async openApp(bundleId) { | ||
| } | ||
| __classPrivateFieldSet(this, _AdbBackend_recordingPath, outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`, "f"); | ||
| __classPrivateFieldSet(this, _AdbBackend_recordingPath, (0, output_path_js_1.resolveArtifactPath)(outputPath, 'recording'), "f"); | ||
| __classPrivateFieldSet(this, _AdbBackend_recordingProcess, (0, node_child_process_1.spawn)('adb', [ | ||
@@ -357,2 +363,3 @@ '-s', | ||
| await __classPrivateFieldGet(this, _AdbBackend_instances, "m", _AdbBackend_adb).call(this, ['shell', 'rm', '-f', __classPrivateFieldGet(this, _AdbBackend_recordingRemotePath, "f")]); | ||
| (0, output_path_js_1.hardenArtifactFile)(localPath); | ||
| return localPath; | ||
@@ -359,0 +366,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"adb-backend.cjs","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, isCommandAvailable } from '../utils/exec.js';\n\nexport class AdbBackend implements DeviceBackend {\n readonly platform = 'android' as const;\n\n readonly #serial: string;\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n readonly #recordingRemotePath = '/sdcard/device-mcp-recording.mp4';\n\n constructor(serial: string) {\n this.#serial = serial;\n }\n\n async #adb(args: string[]): Promise<string> {\n return execStrict('adb', ['-s', this.#serial, ...args]);\n }\n\n async ensureConnected(): Promise<void> {\n if (!(await isCommandAvailable('adb'))) {\n throw new Error(\n 'adb is not installed or not on $PATH.\\n' +\n 'Install Android SDK platform-tools.',\n );\n }\n const state = await this.#adb(['get-state']);\n if (state.trim() !== 'device') {\n throw new Error(\n `Device ${this.#serial} is not ready (state: ${state.trim()})`,\n );\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const [model, version] = await Promise.all([\n this.#adb(['shell', 'getprop', 'ro.product.model']),\n this.#adb(['shell', 'getprop', 'ro.build.version.release']),\n ]);\n return {\n platform: 'android',\n deviceId: this.#serial,\n name: model.trim(),\n osVersion: version.trim(),\n state: 'device',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const raw = await this.#dumpUiHierarchy();\n const hierarchy = parseAndroidHierarchy(raw);\n return {\n platform: 'android',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.#adb(['shell', 'input', 'tap', String(x), String(y)]);\n }\n\n async typeText(text: string): Promise<void> {\n // Android keyevent 29+shift = Ctrl+A (select all), 67 = DEL\n await this.#adb(['shell', 'input', 'keyevent', 'KEYCODE_MOVE_HOME']);\n await this.#adb([\n 'shell',\n 'input',\n 'keyevent',\n '--longpress',\n 'KEYCODE_SHIFT_LEFT',\n 'KEYCODE_MOVE_END',\n ]);\n await this.#adb(['shell', 'input', 'keyevent', '67']);\n // Android input text requires escaping spaces and special chars\n const escaped = text.replace(/ /gu, '%s').replace(/[&|;<>]/gu, '\\\\$&');\n await this.#adb(['shell', 'input', 'text', escaped]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const d = distance ?? 500;\n const sx = startX ?? 540;\n const sy = startY ?? 960;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n const durationMs = 300;\n\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n String(durationMs),\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const raw = await this.#adb(['shell', 'dumpsys', 'activity', 'processes']);\n\n const pidMatch = raw.match(\n new RegExp(`PID #(\\\\d+):.*${bundleId.replace(/\\./gu, '\\\\.')}`, 'u'),\n );\n\n if (pidMatch) {\n return {\n bundleId,\n state: 'Running',\n pid: parseInt(pidMatch[1], 10),\n };\n }\n\n const pmResult = await this.#adb([\n 'shell',\n 'pm',\n 'list',\n 'packages',\n bundleId,\n ]);\n if (pmResult.includes(bundleId)) {\n return { bundleId, state: 'Installed (not running)' };\n }\n\n return { bundleId, state: 'Not Installed' };\n }\n\n async #dumpUiHierarchy(): Promise<string> {\n const remotePath = '/sdcard/window_dump.xml';\n await this.#adb(['shell', 'uiautomator', 'dump', remotePath]);\n const xml = await this.#adb(['shell', 'cat', remotePath]);\n await this.#adb(['shell', 'rm', '-f', remotePath]);\n return xml;\n }\n\n async screenshot(outputPath?: string): Promise<ScreenshotResult> {\n const localPath =\n outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`;\n await this.#adb(['shell', 'screencap', '-p', '/sdcard/screenshot.png']);\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n '/sdcard/screenshot.png',\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', '/sdcard/screenshot.png']);\n const data = await execStrict('base64', [localPath]);\n return { data: data.trim(), format: 'png', path: localPath };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.#adb([\n 'shell',\n 'monkey',\n '-p',\n bundleId,\n '-c',\n 'android.intent.category.LAUNCHER',\n '1',\n ]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.#adb(['shell', 'am', 'force-stop', bundleId]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n // Android keyevent codes\n const keyMap: Record<DeviceButton, string> = {\n home: '3',\n back: '4',\n enter: '66',\n lock: '26',\n };\n await this.#adb(['shell', 'input', 'keyevent', keyMap[button]]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.#adb(['shell', 'input', 'keyevent', '111']);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { text: label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const args = ['shell', 'logcat', '-d', '-t', String(durationSeconds)];\n if (filter) {\n args.push('-e', filter);\n }\n const raw = await this.#adb(args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const match = line.match(/^(\\d{2}-\\d{2}\\s+\\S+)\\s+(\\w)\\/.*?:\\s*(.*)/u);\n if (match) {\n return {\n timestamp: match[1],\n level: match[2],\n message: match[3],\n };\n }\n return { timestamp: '', level: 'info', message: line };\n });\n return { entries, source: 'logcat' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(cx),\n String(cy),\n String(cx),\n String(cy),\n String(durationMs),\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const snap = await this.snapshot();\n const texts = collectAndroidAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const raw = await this.#adb(['shell', 'wm', 'size']);\n const match = raw.match(/(\\d+)x(\\d+)/u);\n if (match) {\n return {\n width: parseInt(match[1], 10),\n height: parseInt(match[2], 10),\n };\n }\n throw new Error(`Unable to parse window size from: ${raw.trim()}`);\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. ADB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async setClipboard(_text: string): Promise<void> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n this.#recordingPath =\n outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`;\n this.#recordingProcess = spawn('adb', [\n '-s',\n this.#serial,\n 'shell',\n 'screenrecord',\n this.#recordingRemotePath,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const localPath = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 2000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n this.#recordingRemotePath,\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', this.#recordingRemotePath]);\n return localPath;\n }\n}\n\nfunction collectAndroidAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isDialog =\n el.type.includes('Dialog') || el.type.includes('AlertDialog');\n if (isDialog) {\n collectTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAndroidAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n if (el.type.includes('TextView') && el.value) {\n texts.push(el.value);\n }\n if (el.children) {\n collectTextValues(el.children, texts);\n }\n }\n}\n\nexport function parseAndroidHierarchy(xml: string): UIElement[] {\n const stack: UIElement[][] = [[]];\n\n // Matches all <node ...>, </node>, and <node .../> tags in document order\n const tagRegex = /<(\\/?)node(?:\\s+([^>]*?))?\\s*(\\/?)>/gu;\n let match: RegExpExecArray | null;\n\n while ((match = tagRegex.exec(xml)) !== null) {\n const isClosing = match[1] === '/';\n const attrs = match[2];\n const isSelfClosing = match[3] === '/';\n\n if (isClosing) {\n if (stack.length > 1) {\n stack.pop();\n }\n } else if (isSelfClosing && attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n stack[stack.length - 1].push(element);\n }\n } else if (attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n element.children = [];\n stack[stack.length - 1].push(element);\n stack.push(element.children);\n }\n }\n }\n\n return stack[0];\n}\n\nexport function parseNodeAttributes(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} | ||
| {"version":3,"file":"adb-backend.cjs","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, isCommandAvailable } from '../utils/exec.js';\nimport {\n hardenArtifactFile,\n resolveArtifactPath,\n} from '../utils/output-path.js';\n\nexport class AdbBackend implements DeviceBackend {\n readonly platform = 'android' as const;\n\n readonly #serial: string;\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n readonly #recordingRemotePath = '/sdcard/device-mcp-recording.mp4';\n\n constructor(serial: string) {\n this.#serial = serial;\n }\n\n async #adb(args: string[]): Promise<string> {\n return execStrict('adb', ['-s', this.#serial, ...args]);\n }\n\n async ensureConnected(): Promise<void> {\n if (!(await isCommandAvailable('adb'))) {\n throw new Error(\n 'adb is not installed or not on $PATH.\\n' +\n 'Install Android SDK platform-tools.',\n );\n }\n const state = await this.#adb(['get-state']);\n if (state.trim() !== 'device') {\n throw new Error(\n `Device ${this.#serial} is not ready (state: ${state.trim()})`,\n );\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const [model, version] = await Promise.all([\n this.#adb(['shell', 'getprop', 'ro.product.model']),\n this.#adb(['shell', 'getprop', 'ro.build.version.release']),\n ]);\n return {\n platform: 'android',\n deviceId: this.#serial,\n name: model.trim(),\n osVersion: version.trim(),\n state: 'device',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const raw = await this.#dumpUiHierarchy();\n const hierarchy = parseAndroidHierarchy(raw);\n return {\n platform: 'android',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.#adb(['shell', 'input', 'tap', String(x), String(y)]);\n }\n\n async typeText(text: string): Promise<void> {\n // Android keyevent 29+shift = Ctrl+A (select all), 67 = DEL\n await this.#adb(['shell', 'input', 'keyevent', 'KEYCODE_MOVE_HOME']);\n await this.#adb([\n 'shell',\n 'input',\n 'keyevent',\n '--longpress',\n 'KEYCODE_SHIFT_LEFT',\n 'KEYCODE_MOVE_END',\n ]);\n await this.#adb(['shell', 'input', 'keyevent', '67']);\n // Android input text requires escaping spaces and special chars\n const escaped = text.replace(/ /gu, '%s').replace(/[&|;<>]/gu, '\\\\$&');\n await this.#adb(['shell', 'input', 'text', escaped]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const d = distance ?? 500;\n const sx = startX ?? 540;\n const sy = startY ?? 960;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n const durationMs = 300;\n\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n String(durationMs),\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const raw = await this.#adb(['shell', 'dumpsys', 'activity', 'processes']);\n\n const pidMatch = raw.match(\n new RegExp(`PID #(\\\\d+):.*${bundleId.replace(/\\./gu, '\\\\.')}`, 'u'),\n );\n\n if (pidMatch) {\n return {\n bundleId,\n state: 'Running',\n pid: parseInt(pidMatch[1], 10),\n };\n }\n\n const pmResult = await this.#adb([\n 'shell',\n 'pm',\n 'list',\n 'packages',\n bundleId,\n ]);\n if (pmResult.includes(bundleId)) {\n return { bundleId, state: 'Installed (not running)' };\n }\n\n return { bundleId, state: 'Not Installed' };\n }\n\n async #dumpUiHierarchy(): Promise<string> {\n const remotePath = '/sdcard/window_dump.xml';\n await this.#adb(['shell', 'uiautomator', 'dump', remotePath]);\n const xml = await this.#adb(['shell', 'cat', remotePath]);\n await this.#adb(['shell', 'rm', '-f', remotePath]);\n return xml;\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n const localPath = resolveArtifactPath(outputPath, 'screenshot');\n await this.#adb(['shell', 'screencap', '-p', '/sdcard/screenshot.png']);\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n '/sdcard/screenshot.png',\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', '/sdcard/screenshot.png']);\n hardenArtifactFile(localPath);\n if (options?.encode === false) {\n return { data: undefined, format: 'png', path: localPath };\n }\n const data = await readFile(localPath, 'base64');\n return { data, format: 'png', path: localPath };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.#adb([\n 'shell',\n 'monkey',\n '-p',\n bundleId,\n '-c',\n 'android.intent.category.LAUNCHER',\n '1',\n ]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.#adb(['shell', 'am', 'force-stop', bundleId]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n // Android keyevent codes\n const keyMap: Record<DeviceButton, string> = {\n home: '3',\n back: '4',\n enter: '66',\n lock: '26',\n };\n await this.#adb(['shell', 'input', 'keyevent', keyMap[button]]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.#adb(['shell', 'input', 'keyevent', '111']);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { text: label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const args = ['shell', 'logcat', '-d', '-t', String(durationSeconds)];\n if (filter) {\n args.push('-e', filter);\n }\n const raw = await this.#adb(args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const match = line.match(/^(\\d{2}-\\d{2}\\s+\\S+)\\s+(\\w)\\/.*?:\\s*(.*)/u);\n if (match) {\n return {\n timestamp: match[1],\n level: match[2],\n message: match[3],\n };\n }\n return { timestamp: '', level: 'info', message: line };\n });\n return { entries, source: 'logcat' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(cx),\n String(cy),\n String(cx),\n String(cy),\n String(durationMs),\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const snap = await this.snapshot();\n const texts = collectAndroidAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const raw = await this.#adb(['shell', 'wm', 'size']);\n const match = raw.match(/(\\d+)x(\\d+)/u);\n if (match) {\n return {\n width: parseInt(match[1], 10),\n height: parseInt(match[2], 10),\n };\n }\n throw new Error(`Unable to parse window size from: ${raw.trim()}`);\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. ADB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async setClipboard(_text: string): Promise<void> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n this.#recordingPath = resolveArtifactPath(outputPath, 'recording');\n this.#recordingProcess = spawn('adb', [\n '-s',\n this.#serial,\n 'shell',\n 'screenrecord',\n this.#recordingRemotePath,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const localPath = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 2000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n this.#recordingRemotePath,\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', this.#recordingRemotePath]);\n hardenArtifactFile(localPath);\n return localPath;\n }\n}\n\nfunction collectAndroidAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isDialog =\n el.type.includes('Dialog') || el.type.includes('AlertDialog');\n if (isDialog) {\n collectTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAndroidAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n if (el.type.includes('TextView') && el.value) {\n texts.push(el.value);\n }\n if (el.children) {\n collectTextValues(el.children, texts);\n }\n }\n}\n\nexport function parseAndroidHierarchy(xml: string): UIElement[] {\n const stack: UIElement[][] = [[]];\n\n // Matches all <node ...>, </node>, and <node .../> tags in document order\n const tagRegex = /<(\\/?)node(?:\\s+([^>]*?))?\\s*(\\/?)>/gu;\n let match: RegExpExecArray | null;\n\n while ((match = tagRegex.exec(xml)) !== null) {\n const isClosing = match[1] === '/';\n const attrs = match[2];\n const isSelfClosing = match[3] === '/';\n\n if (isClosing) {\n if (stack.length > 1) {\n stack.pop();\n }\n } else if (isSelfClosing && attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n stack[stack.length - 1].push(element);\n }\n } else if (attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n element.children = [];\n stack[stack.length - 1].push(element);\n stack.push(element.children);\n }\n }\n }\n\n return stack[0];\n}\n\nexport function parseNodeAttributes(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} |
@@ -1,2 +0,2 @@ | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.cjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.cjs"; | ||
| export declare class AdbBackend implements DeviceBackend { | ||
@@ -16,3 +16,9 @@ #private; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(outputPath?: string): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -19,0 +25,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"adb-backend.d.cts","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,UAAU,EACX,oBAAmB;AASpB,qBAAa,UAAW,YAAW,aAAa;;IAC9C,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;gBAU3B,MAAM,EAAE,MAAM;IAQpB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAehC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAcpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAWnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAYpD,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAmBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAqCtD,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgB1D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAIhC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAwBnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IA+BrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAqBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAY/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAYpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAO/B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1C,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAoB7C;AAgCD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAgC9D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA8BnE"} | ||
| {"version":3,"file":"adb-backend.d.cts","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"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"} |
@@ -1,2 +0,2 @@ | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.mjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.mjs"; | ||
| export declare class AdbBackend implements DeviceBackend { | ||
@@ -16,3 +16,9 @@ #private; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(outputPath?: string): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -19,0 +25,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"adb-backend.d.mts","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,UAAU,EACX,oBAAmB;AASpB,qBAAa,UAAW,YAAW,aAAa;;IAC9C,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;gBAU3B,MAAM,EAAE,MAAM;IAQpB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAehC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAcpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAWnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAYpD,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAmBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAqCtD,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgB1D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAIhC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAwBnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IA+BrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAqBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAY/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAYpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAO/B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1C,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAoB7C;AAgCD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAgC9D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA8BnE"} | ||
| {"version":3,"file":"adb-backend.d.mts","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"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"} |
@@ -14,5 +14,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
| import { spawn } from "node:child_process"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { ACCEPT_LABELS, DENY_LABELS } from "../utils/alert-labels.mjs"; | ||
| import { findElement, describeElement, computeSwipeEnd } from "../utils/element.mjs"; | ||
| import { execStrict, isCommandAvailable } from "../utils/exec.mjs"; | ||
| import { hardenArtifactFile, resolveArtifactPath } from "../utils/output-path.mjs"; | ||
| export class AdbBackend { | ||
@@ -157,4 +159,4 @@ constructor(serial) { | ||
| } | ||
| async screenshot(outputPath) { | ||
| const localPath = outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`; | ||
| async screenshot(outputPath, options) { | ||
| const localPath = resolveArtifactPath(outputPath, 'screenshot'); | ||
| await __classPrivateFieldGet(this, _AdbBackend_instances, "m", _AdbBackend_adb).call(this, ['shell', 'screencap', '-p', '/sdcard/screenshot.png']); | ||
@@ -169,4 +171,8 @@ await execStrict('adb', [ | ||
| await __classPrivateFieldGet(this, _AdbBackend_instances, "m", _AdbBackend_adb).call(this, ['shell', 'rm', '-f', '/sdcard/screenshot.png']); | ||
| const data = await execStrict('base64', [localPath]); | ||
| return { data: data.trim(), format: 'png', path: localPath }; | ||
| hardenArtifactFile(localPath); | ||
| if (options?.encode === false) { | ||
| return { data: undefined, format: 'png', path: localPath }; | ||
| } | ||
| const data = await readFile(localPath, 'base64'); | ||
| return { data, format: 'png', path: localPath }; | ||
| } | ||
@@ -321,3 +327,3 @@ async openApp(bundleId) { | ||
| } | ||
| __classPrivateFieldSet(this, _AdbBackend_recordingPath, outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`, "f"); | ||
| __classPrivateFieldSet(this, _AdbBackend_recordingPath, resolveArtifactPath(outputPath, 'recording'), "f"); | ||
| __classPrivateFieldSet(this, _AdbBackend_recordingProcess, spawn('adb', [ | ||
@@ -352,2 +358,3 @@ '-s', | ||
| await __classPrivateFieldGet(this, _AdbBackend_instances, "m", _AdbBackend_adb).call(this, ['shell', 'rm', '-f', __classPrivateFieldGet(this, _AdbBackend_recordingRemotePath, "f")]); | ||
| hardenArtifactFile(localPath); | ||
| return localPath; | ||
@@ -354,0 +361,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"adb-backend.mjs","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, isCommandAvailable } from '../utils/exec.js';\n\nexport class AdbBackend implements DeviceBackend {\n readonly platform = 'android' as const;\n\n readonly #serial: string;\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n readonly #recordingRemotePath = '/sdcard/device-mcp-recording.mp4';\n\n constructor(serial: string) {\n this.#serial = serial;\n }\n\n async #adb(args: string[]): Promise<string> {\n return execStrict('adb', ['-s', this.#serial, ...args]);\n }\n\n async ensureConnected(): Promise<void> {\n if (!(await isCommandAvailable('adb'))) {\n throw new Error(\n 'adb is not installed or not on $PATH.\\n' +\n 'Install Android SDK platform-tools.',\n );\n }\n const state = await this.#adb(['get-state']);\n if (state.trim() !== 'device') {\n throw new Error(\n `Device ${this.#serial} is not ready (state: ${state.trim()})`,\n );\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const [model, version] = await Promise.all([\n this.#adb(['shell', 'getprop', 'ro.product.model']),\n this.#adb(['shell', 'getprop', 'ro.build.version.release']),\n ]);\n return {\n platform: 'android',\n deviceId: this.#serial,\n name: model.trim(),\n osVersion: version.trim(),\n state: 'device',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const raw = await this.#dumpUiHierarchy();\n const hierarchy = parseAndroidHierarchy(raw);\n return {\n platform: 'android',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.#adb(['shell', 'input', 'tap', String(x), String(y)]);\n }\n\n async typeText(text: string): Promise<void> {\n // Android keyevent 29+shift = Ctrl+A (select all), 67 = DEL\n await this.#adb(['shell', 'input', 'keyevent', 'KEYCODE_MOVE_HOME']);\n await this.#adb([\n 'shell',\n 'input',\n 'keyevent',\n '--longpress',\n 'KEYCODE_SHIFT_LEFT',\n 'KEYCODE_MOVE_END',\n ]);\n await this.#adb(['shell', 'input', 'keyevent', '67']);\n // Android input text requires escaping spaces and special chars\n const escaped = text.replace(/ /gu, '%s').replace(/[&|;<>]/gu, '\\\\$&');\n await this.#adb(['shell', 'input', 'text', escaped]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const d = distance ?? 500;\n const sx = startX ?? 540;\n const sy = startY ?? 960;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n const durationMs = 300;\n\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n String(durationMs),\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const raw = await this.#adb(['shell', 'dumpsys', 'activity', 'processes']);\n\n const pidMatch = raw.match(\n new RegExp(`PID #(\\\\d+):.*${bundleId.replace(/\\./gu, '\\\\.')}`, 'u'),\n );\n\n if (pidMatch) {\n return {\n bundleId,\n state: 'Running',\n pid: parseInt(pidMatch[1], 10),\n };\n }\n\n const pmResult = await this.#adb([\n 'shell',\n 'pm',\n 'list',\n 'packages',\n bundleId,\n ]);\n if (pmResult.includes(bundleId)) {\n return { bundleId, state: 'Installed (not running)' };\n }\n\n return { bundleId, state: 'Not Installed' };\n }\n\n async #dumpUiHierarchy(): Promise<string> {\n const remotePath = '/sdcard/window_dump.xml';\n await this.#adb(['shell', 'uiautomator', 'dump', remotePath]);\n const xml = await this.#adb(['shell', 'cat', remotePath]);\n await this.#adb(['shell', 'rm', '-f', remotePath]);\n return xml;\n }\n\n async screenshot(outputPath?: string): Promise<ScreenshotResult> {\n const localPath =\n outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`;\n await this.#adb(['shell', 'screencap', '-p', '/sdcard/screenshot.png']);\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n '/sdcard/screenshot.png',\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', '/sdcard/screenshot.png']);\n const data = await execStrict('base64', [localPath]);\n return { data: data.trim(), format: 'png', path: localPath };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.#adb([\n 'shell',\n 'monkey',\n '-p',\n bundleId,\n '-c',\n 'android.intent.category.LAUNCHER',\n '1',\n ]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.#adb(['shell', 'am', 'force-stop', bundleId]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n // Android keyevent codes\n const keyMap: Record<DeviceButton, string> = {\n home: '3',\n back: '4',\n enter: '66',\n lock: '26',\n };\n await this.#adb(['shell', 'input', 'keyevent', keyMap[button]]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.#adb(['shell', 'input', 'keyevent', '111']);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { text: label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const args = ['shell', 'logcat', '-d', '-t', String(durationSeconds)];\n if (filter) {\n args.push('-e', filter);\n }\n const raw = await this.#adb(args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const match = line.match(/^(\\d{2}-\\d{2}\\s+\\S+)\\s+(\\w)\\/.*?:\\s*(.*)/u);\n if (match) {\n return {\n timestamp: match[1],\n level: match[2],\n message: match[3],\n };\n }\n return { timestamp: '', level: 'info', message: line };\n });\n return { entries, source: 'logcat' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(cx),\n String(cy),\n String(cx),\n String(cy),\n String(durationMs),\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const snap = await this.snapshot();\n const texts = collectAndroidAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const raw = await this.#adb(['shell', 'wm', 'size']);\n const match = raw.match(/(\\d+)x(\\d+)/u);\n if (match) {\n return {\n width: parseInt(match[1], 10),\n height: parseInt(match[2], 10),\n };\n }\n throw new Error(`Unable to parse window size from: ${raw.trim()}`);\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. ADB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async setClipboard(_text: string): Promise<void> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n this.#recordingPath =\n outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`;\n this.#recordingProcess = spawn('adb', [\n '-s',\n this.#serial,\n 'shell',\n 'screenrecord',\n this.#recordingRemotePath,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const localPath = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 2000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n this.#recordingRemotePath,\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', this.#recordingRemotePath]);\n return localPath;\n }\n}\n\nfunction collectAndroidAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isDialog =\n el.type.includes('Dialog') || el.type.includes('AlertDialog');\n if (isDialog) {\n collectTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAndroidAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n if (el.type.includes('TextView') && el.value) {\n texts.push(el.value);\n }\n if (el.children) {\n collectTextValues(el.children, texts);\n }\n }\n}\n\nexport function parseAndroidHierarchy(xml: string): UIElement[] {\n const stack: UIElement[][] = [[]];\n\n // Matches all <node ...>, </node>, and <node .../> tags in document order\n const tagRegex = /<(\\/?)node(?:\\s+([^>]*?))?\\s*(\\/?)>/gu;\n let match: RegExpExecArray | null;\n\n while ((match = tagRegex.exec(xml)) !== null) {\n const isClosing = match[1] === '/';\n const attrs = match[2];\n const isSelfClosing = match[3] === '/';\n\n if (isClosing) {\n if (stack.length > 1) {\n stack.pop();\n }\n } else if (isSelfClosing && attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n stack[stack.length - 1].push(element);\n }\n } else if (attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n element.children = [];\n stack[stack.length - 1].push(element);\n stack.push(element.children);\n }\n }\n }\n\n return stack[0];\n}\n\nexport function parseNodeAttributes(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} | ||
| {"version":3,"file":"adb-backend.mjs","sourceRoot":"","sources":["../../src/backends/adb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, isCommandAvailable } from '../utils/exec.js';\nimport {\n hardenArtifactFile,\n resolveArtifactPath,\n} from '../utils/output-path.js';\n\nexport class AdbBackend implements DeviceBackend {\n readonly platform = 'android' as const;\n\n readonly #serial: string;\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n readonly #recordingRemotePath = '/sdcard/device-mcp-recording.mp4';\n\n constructor(serial: string) {\n this.#serial = serial;\n }\n\n async #adb(args: string[]): Promise<string> {\n return execStrict('adb', ['-s', this.#serial, ...args]);\n }\n\n async ensureConnected(): Promise<void> {\n if (!(await isCommandAvailable('adb'))) {\n throw new Error(\n 'adb is not installed or not on $PATH.\\n' +\n 'Install Android SDK platform-tools.',\n );\n }\n const state = await this.#adb(['get-state']);\n if (state.trim() !== 'device') {\n throw new Error(\n `Device ${this.#serial} is not ready (state: ${state.trim()})`,\n );\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const [model, version] = await Promise.all([\n this.#adb(['shell', 'getprop', 'ro.product.model']),\n this.#adb(['shell', 'getprop', 'ro.build.version.release']),\n ]);\n return {\n platform: 'android',\n deviceId: this.#serial,\n name: model.trim(),\n osVersion: version.trim(),\n state: 'device',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const raw = await this.#dumpUiHierarchy();\n const hierarchy = parseAndroidHierarchy(raw);\n return {\n platform: 'android',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.#adb(['shell', 'input', 'tap', String(x), String(y)]);\n }\n\n async typeText(text: string): Promise<void> {\n // Android keyevent 29+shift = Ctrl+A (select all), 67 = DEL\n await this.#adb(['shell', 'input', 'keyevent', 'KEYCODE_MOVE_HOME']);\n await this.#adb([\n 'shell',\n 'input',\n 'keyevent',\n '--longpress',\n 'KEYCODE_SHIFT_LEFT',\n 'KEYCODE_MOVE_END',\n ]);\n await this.#adb(['shell', 'input', 'keyevent', '67']);\n // Android input text requires escaping spaces and special chars\n const escaped = text.replace(/ /gu, '%s').replace(/[&|;<>]/gu, '\\\\$&');\n await this.#adb(['shell', 'input', 'text', escaped]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const d = distance ?? 500;\n const sx = startX ?? 540;\n const sy = startY ?? 960;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n const durationMs = 300;\n\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n String(durationMs),\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const raw = await this.#adb(['shell', 'dumpsys', 'activity', 'processes']);\n\n const pidMatch = raw.match(\n new RegExp(`PID #(\\\\d+):.*${bundleId.replace(/\\./gu, '\\\\.')}`, 'u'),\n );\n\n if (pidMatch) {\n return {\n bundleId,\n state: 'Running',\n pid: parseInt(pidMatch[1], 10),\n };\n }\n\n const pmResult = await this.#adb([\n 'shell',\n 'pm',\n 'list',\n 'packages',\n bundleId,\n ]);\n if (pmResult.includes(bundleId)) {\n return { bundleId, state: 'Installed (not running)' };\n }\n\n return { bundleId, state: 'Not Installed' };\n }\n\n async #dumpUiHierarchy(): Promise<string> {\n const remotePath = '/sdcard/window_dump.xml';\n await this.#adb(['shell', 'uiautomator', 'dump', remotePath]);\n const xml = await this.#adb(['shell', 'cat', remotePath]);\n await this.#adb(['shell', 'rm', '-f', remotePath]);\n return xml;\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n const localPath = resolveArtifactPath(outputPath, 'screenshot');\n await this.#adb(['shell', 'screencap', '-p', '/sdcard/screenshot.png']);\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n '/sdcard/screenshot.png',\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', '/sdcard/screenshot.png']);\n hardenArtifactFile(localPath);\n if (options?.encode === false) {\n return { data: undefined, format: 'png', path: localPath };\n }\n const data = await readFile(localPath, 'base64');\n return { data, format: 'png', path: localPath };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.#adb([\n 'shell',\n 'monkey',\n '-p',\n bundleId,\n '-c',\n 'android.intent.category.LAUNCHER',\n '1',\n ]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.#adb(['shell', 'am', 'force-stop', bundleId]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n // Android keyevent codes\n const keyMap: Record<DeviceButton, string> = {\n home: '3',\n back: '4',\n enter: '66',\n lock: '26',\n };\n await this.#adb(['shell', 'input', 'keyevent', keyMap[button]]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.#adb(['shell', 'input', 'keyevent', '111']);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { text: label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const args = ['shell', 'logcat', '-d', '-t', String(durationSeconds)];\n if (filter) {\n args.push('-e', filter);\n }\n const raw = await this.#adb(args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const match = line.match(/^(\\d{2}-\\d{2}\\s+\\S+)\\s+(\\w)\\/.*?:\\s*(.*)/u);\n if (match) {\n return {\n timestamp: match[1],\n level: match[2],\n message: match[3],\n };\n }\n return { timestamp: '', level: 'info', message: line };\n });\n return { entries, source: 'logcat' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.#adb([\n 'shell',\n 'input',\n 'swipe',\n String(cx),\n String(cy),\n String(cx),\n String(cy),\n String(durationMs),\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const snap = await this.snapshot();\n const texts = collectAndroidAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const raw = await this.#adb(['shell', 'wm', 'size']);\n const match = raw.match(/(\\d+)x(\\d+)/u);\n if (match) {\n return {\n width: parseInt(match[1], 10),\n height: parseInt(match[2], 10),\n };\n }\n throw new Error(`Unable to parse window size from: ${raw.trim()}`);\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. ADB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async setClipboard(_text: string): Promise<void> {\n throw new Error(\n 'Clipboard access via ADB is not supported on modern Android. ' +\n 'Use the Appium backend for clipboard operations.',\n );\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n this.#recordingPath = resolveArtifactPath(outputPath, 'recording');\n this.#recordingProcess = spawn('adb', [\n '-s',\n this.#serial,\n 'shell',\n 'screenrecord',\n this.#recordingRemotePath,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const localPath = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 2000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n\n await execStrict('adb', [\n '-s',\n this.#serial,\n 'pull',\n this.#recordingRemotePath,\n localPath,\n ]);\n await this.#adb(['shell', 'rm', '-f', this.#recordingRemotePath]);\n hardenArtifactFile(localPath);\n return localPath;\n }\n}\n\nfunction collectAndroidAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isDialog =\n el.type.includes('Dialog') || el.type.includes('AlertDialog');\n if (isDialog) {\n collectTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAndroidAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n if (el.type.includes('TextView') && el.value) {\n texts.push(el.value);\n }\n if (el.children) {\n collectTextValues(el.children, texts);\n }\n }\n}\n\nexport function parseAndroidHierarchy(xml: string): UIElement[] {\n const stack: UIElement[][] = [[]];\n\n // Matches all <node ...>, </node>, and <node .../> tags in document order\n const tagRegex = /<(\\/?)node(?:\\s+([^>]*?))?\\s*(\\/?)>/gu;\n let match: RegExpExecArray | null;\n\n while ((match = tagRegex.exec(xml)) !== null) {\n const isClosing = match[1] === '/';\n const attrs = match[2];\n const isSelfClosing = match[3] === '/';\n\n if (isClosing) {\n if (stack.length > 1) {\n stack.pop();\n }\n } else if (isSelfClosing && attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n stack[stack.length - 1].push(element);\n }\n } else if (attrs) {\n const element = parseNodeAttributes(attrs);\n if (element) {\n element.children = [];\n stack[stack.length - 1].push(element);\n stack.push(element.children);\n }\n }\n }\n\n return stack[0];\n}\n\nexport function parseNodeAttributes(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} |
@@ -18,6 +18,5 @@ "use strict"; | ||
| exports.parseAppiumIosHierarchy = parseAppiumIosHierarchy; | ||
| const node_fs_1 = require("node:fs"); | ||
| const node_path_1 = require("node:path"); | ||
| const webdriver_client_js_1 = require("./webdriver-client.cjs"); | ||
| const element_js_1 = require("../utils/element.cjs"); | ||
| const output_path_js_1 = require("../utils/output-path.cjs"); | ||
| class AppiumBackend { | ||
@@ -176,6 +175,16 @@ constructor(config) { | ||
| } | ||
| async screenshot() { | ||
| async screenshot(outputPath, options) { | ||
| const client = __classPrivateFieldGet(this, _AppiumBackend_instances, "m", _AppiumBackend_requireClient).call(this); | ||
| const data = await client.execute('mobile: getScreenshot', []); | ||
| return { data, format: 'png' }; | ||
| if (options?.encode === false) { | ||
| const path = (0, output_path_js_1.resolveArtifactPath)(outputPath, 'screenshot'); | ||
| await (0, output_path_js_1.writeArtifactFile)(path, Buffer.from(data, 'base64')); | ||
| return { data: undefined, format: 'png', path }; | ||
| } | ||
| let path; | ||
| if (outputPath) { | ||
| path = (0, output_path_js_1.resolveArtifactPath)(outputPath, 'screenshot'); | ||
| await (0, output_path_js_1.writeArtifactFile)(path, Buffer.from(data, 'base64')); | ||
| } | ||
| return { data, format: 'png', path }; | ||
| } | ||
@@ -359,3 +368,3 @@ async openApp(bundleId) { | ||
| __classPrivateFieldSet(this, _AppiumBackend_recording, true, "f"); | ||
| __classPrivateFieldSet(this, _AppiumBackend_recordingOutputPath, (0, node_path_1.resolve)(outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`), "f"); | ||
| __classPrivateFieldSet(this, _AppiumBackend_recordingOutputPath, (0, output_path_js_1.resolveArtifactPath)(outputPath, 'recording'), "f"); | ||
| } | ||
@@ -372,4 +381,4 @@ async stopScreenRecording() { | ||
| } | ||
| const outputPath = (0, node_path_1.resolve)(__classPrivateFieldGet(this, _AppiumBackend_recordingOutputPath, "f")); | ||
| (0, node_fs_1.writeFileSync)(outputPath, data); | ||
| const outputPath = __classPrivateFieldGet(this, _AppiumBackend_recordingOutputPath, "f"); | ||
| await (0, output_path_js_1.writeArtifactFile)(outputPath, data); | ||
| __classPrivateFieldSet(this, _AppiumBackend_recording, false, "f"); | ||
@@ -376,0 +385,0 @@ __classPrivateFieldSet(this, _AppiumBackend_recordingOutputPath, null, "f"); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"appium-backend.cjs","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFileSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\n\nimport type { SessionConfig } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport { WebDriverClient, createSession } from './webdriver-client.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\n\nexport class AppiumBackend implements DeviceBackend {\n readonly platform: Platform;\n\n #client: WebDriverClient | null = null;\n\n readonly #config: SessionConfig;\n\n #recording = false;\n\n #recordingOutputPath: string | null = null;\n\n constructor(config: SessionConfig) {\n this.#config = config;\n this.platform = config.platform;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#client) {\n return;\n }\n\n if (this.#config.mode === 'attach') {\n this.#client = new WebDriverClient(\n this.#config.appiumUrl,\n this.#config.sessionId,\n this.#config.auth,\n );\n await this.#client.getStatus();\n } else {\n const { client } = await createSession(\n this.#config.appiumUrl,\n this.#config.capabilities,\n this.#config.auth,\n );\n this.#client = client;\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const client = this.#requireClient();\n const details = await client.getSessionDetails();\n const caps = (details.capabilities ?? details) as Record<string, unknown>;\n\n return {\n platform: this.platform,\n deviceId: (caps.udid as string) ?? client.sessionId,\n name: (caps.deviceName as string) ?? 'Appium Device',\n osVersion: (caps.platformVersion as string) ?? 'Unknown',\n state: 'connected',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const client = this.#requireClient();\n const raw = await this.#getHierarchy(client);\n const hierarchy =\n this.platform === 'ios'\n ? parseAppiumIosHierarchy(raw)\n : parseAppiumAndroidHierarchy(raw);\n\n return {\n platform: this.platform,\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const targetX = Math.round(element.frame.x + element.frame.width / 2);\n const targetY = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(targetX, targetY);\n\n return {\n success: true,\n x: targetX,\n y: targetY,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(targetX: number, targetY: number): Promise<void> {\n const client = this.#requireClient();\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: targetX, y: targetY },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: 50 },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async typeText(text: string): Promise<void> {\n const client = this.#requireClient();\n const activeElement = await client.findElement(\n 'xpath',\n '//*[@focused=\"true\"]',\n );\n const elementId = extractElementId(activeElement);\n await client.elementClear(elementId);\n await client.elementSendKeys(elementId, text);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const client = this.#requireClient();\n const dist = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, dist);\n\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: sx, y: sy },\n { type: 'pointerDown', button: 0 },\n { type: 'pointerMove', duration: 300, x: endX, y: endY },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const client = this.#requireClient();\n\n if (this.platform === 'ios') {\n // iOS app state: 0=not installed, 1=not running, 2=background, 3=suspended, 4=foreground\n const state = await client.queryAppState(bundleId);\n const stateMap: Record<number, string> = {\n 0: 'Not Installed',\n 1: 'Not Running',\n 2: 'Running (background)',\n 3: 'Suspended',\n 4: 'Running (foreground)',\n };\n return { bundleId, state: stateMap[state] ?? `Unknown (${state})` };\n }\n\n try {\n const activity = await client.getCurrentActivity();\n return { bundleId, state: `Running (${activity})` };\n } catch {\n return { bundleId, state: 'Not Running' };\n }\n }\n\n async #getHierarchy(client: WebDriverClient): Promise<string> {\n if (this.platform === 'ios') {\n return this.#getIosHierarchy(client);\n }\n return client.getPageSource();\n }\n\n async #getIosHierarchy(client: WebDriverClient): Promise<string> {\n // Limit hierarchy depth to avoid \"JS stack is too deep\" on complex iOS apps\n try {\n await client.updateSettings({ snapshotMaxDepth: 30 });\n } catch {\n // snapshotMaxDepth not supported on older XCUITest drivers\n }\n\n try {\n return await client.execute<string>('mobile: source', [\n { format: 'xml' },\n ]);\n } catch {\n return client.getPageSource();\n }\n }\n\n async screenshot(): Promise<ScreenshotResult> {\n const client = this.#requireClient();\n const data = await client.execute<string>('mobile: getScreenshot', []);\n return { data, format: 'png' };\n }\n\n async openApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: activateApp', [{ bundleId }]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: terminateApp', [{ bundleId }]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n const iosMap: Record<DeviceButton, string> = {\n home: 'home',\n lock: 'lock',\n back: 'home',\n enter: '\\n',\n };\n await client.execute('mobile: pressButton', [{ name: iosMap[button] }]);\n } else {\n // Android keyevent codes\n const androidMap: Record<DeviceButton, number> = {\n home: 3,\n back: 4,\n enter: 66,\n lock: 26,\n };\n await client.execute('mobile: pressKey', [\n { keycode: androidMap[button] },\n ]);\n }\n }\n\n async dismissKeyboard(): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n await client.execute('mobile: hideKeyboard', []);\n } else {\n await client.execute('mobile: hideKeyboard', []);\n }\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const client = this.#requireClient();\n const action = accept ? 'accept' : 'dismiss';\n await client.execute(`mobile: ${action}Alert`, []);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const client = this.#requireClient();\n const logType = this.platform === 'ios' ? 'syslog' : 'logcat';\n const raw = await client.execute<Record<string, unknown>[]>(\n 'mobile: getLog',\n [{ type: logType }],\n );\n\n let entries = raw.map((entry) => ({\n timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : '',\n level: typeof entry.level === 'string' ? entry.level : 'info',\n message: typeof entry.message === 'string' ? entry.message : '',\n }));\n\n if (filter) {\n const pattern = new RegExp(filter, 'iu');\n entries = entries.filter((entry) => pattern.test(entry.message));\n }\n\n const cutoff = Date.now() - durationSeconds * 1000;\n entries = entries.filter((entry) => {\n const ts = Number(entry.timestamp);\n return isNaN(ts) || ts >= cutoff;\n });\n\n return { entries, source: `appium ${logType}` };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const client = this.#requireClient();\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: cx, y: cy },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: durationMs },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const client = this.#requireClient();\n try {\n return await client.execute<string>('mobile: getAlertText', []);\n } catch {\n const snap = await this.snapshot();\n const texts = collectAppiumAlertTexts(snap.hierarchy, this.platform);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const client = this.#requireClient();\n try {\n const rect = await client.getWindowRect();\n return { width: rect.width, height: rect.height };\n } catch {\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size');\n }\n }\n\n async getContexts(): Promise<string[]> {\n const client = this.#requireClient();\n return client.getContexts();\n }\n\n async setContext(context: string): Promise<void> {\n const client = this.#requireClient();\n await client.setCurrentContext(context);\n }\n\n async getClipboard(): Promise<string> {\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: getClipboard', [\n { contentType: 'plaintext' },\n ]);\n return Buffer.from(b64, 'base64').toString('utf-8');\n }\n\n async setClipboard(text: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: setClipboard', [\n {\n content: Buffer.from(text).toString('base64'),\n contentType: 'plaintext',\n },\n ]);\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recording) {\n throw new Error('Screen recording is already in progress');\n }\n const client = this.#requireClient();\n await client.execute('mobile: startRecordingScreen', [{}]);\n this.#recording = true;\n this.#recordingOutputPath = resolvePath(\n outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`,\n );\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recording || !this.#recordingOutputPath) {\n throw new Error('No screen recording in progress');\n }\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: stopRecordingScreen', []);\n const data = Buffer.from(b64, 'base64');\n if (data.length === 0) {\n throw new Error('Screen recording returned empty data');\n }\n const outputPath = resolvePath(this.#recordingOutputPath);\n writeFileSync(outputPath, data);\n this.#recording = false;\n this.#recordingOutputPath = null;\n return outputPath;\n }\n\n #requireClient(): WebDriverClient {\n if (!this.#client) {\n throw new Error(\n 'AppiumBackend not connected. Call ensureConnected() first.',\n );\n }\n return this.#client;\n }\n}\n\nfunction collectAppiumAlertTexts(\n elements: UIElement[],\n platform: Platform,\n): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlert =\n platform === 'ios'\n ? el.type === 'Alert' || el.type === 'Sheet'\n : el.type.includes('Dialog');\n if (isAlert) {\n collectAppiumTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAppiumAlertTexts(el.children, platform);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectAppiumTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n const text = el.value ?? el.label;\n if (\n text &&\n (el.type === 'StaticText' ||\n el.type.includes('TextView') ||\n el.type === 'Text')\n ) {\n texts.push(text);\n }\n if (el.children) {\n collectAppiumTextValues(el.children, texts);\n }\n }\n}\n\nfunction extractElementId(element: Record<string, string>): string {\n // W3C uses 'element-6066-11e4-a52e-4f735466cecf', JSONWP uses 'ELEMENT'\n const id =\n element['element-6066-11e4-a52e-4f735466cecf'] ||\n element.ELEMENT ||\n Object.values(element)[0];\n\n if (!id) {\n throw new Error(\n `Failed to extract element ID from Appium response: ${JSON.stringify(element)}`,\n );\n }\n return id;\n}\n\nexport function parseAppiumAndroidHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n const nodeRegex = /<node\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const element = parseAndroidNodeAttrs(match[1]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseAndroidNodeAttrs(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n\nexport function parseAppiumIosHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n // XCUITest XML uses <XCUIElementType* ...> elements\n const nodeRegex = /<(XCUIElementType\\w+)\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const typeName = match[1];\n const element = parseIosNodeAttrs(typeName, match[2]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseIosNodeAttrs(typeName: string, attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const xStr = get('x');\n const yStr = get('y');\n const widthStr = get('width');\n const heightStr = get('height');\n\n if (!xStr || !yStr || !widthStr || !heightStr) {\n return null;\n }\n\n const shortType = typeName.replace(/^XCUIElementType/u, '');\n\n return {\n type: shortType,\n label: get('label') || undefined,\n value: get('value') || undefined,\n identifier: get('name') || undefined,\n frame: {\n x: parseInt(xStr, 10),\n y: parseInt(yStr, 10),\n width: parseInt(widthStr, 10),\n height: parseInt(heightStr, 10),\n },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} | ||
| {"version":3,"file":"appium-backend.cjs","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionConfig } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport { WebDriverClient, createSession } from './webdriver-client.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport {\n resolveArtifactPath,\n writeArtifactFile,\n} from '../utils/output-path.js';\n\nexport class AppiumBackend implements DeviceBackend {\n readonly platform: Platform;\n\n #client: WebDriverClient | null = null;\n\n readonly #config: SessionConfig;\n\n #recording = false;\n\n #recordingOutputPath: string | null = null;\n\n constructor(config: SessionConfig) {\n this.#config = config;\n this.platform = config.platform;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#client) {\n return;\n }\n\n if (this.#config.mode === 'attach') {\n this.#client = new WebDriverClient(\n this.#config.appiumUrl,\n this.#config.sessionId,\n this.#config.auth,\n );\n await this.#client.getStatus();\n } else {\n const { client } = await createSession(\n this.#config.appiumUrl,\n this.#config.capabilities,\n this.#config.auth,\n );\n this.#client = client;\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const client = this.#requireClient();\n const details = await client.getSessionDetails();\n const caps = (details.capabilities ?? details) as Record<string, unknown>;\n\n return {\n platform: this.platform,\n deviceId: (caps.udid as string) ?? client.sessionId,\n name: (caps.deviceName as string) ?? 'Appium Device',\n osVersion: (caps.platformVersion as string) ?? 'Unknown',\n state: 'connected',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const client = this.#requireClient();\n const raw = await this.#getHierarchy(client);\n const hierarchy =\n this.platform === 'ios'\n ? parseAppiumIosHierarchy(raw)\n : parseAppiumAndroidHierarchy(raw);\n\n return {\n platform: this.platform,\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const targetX = Math.round(element.frame.x + element.frame.width / 2);\n const targetY = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(targetX, targetY);\n\n return {\n success: true,\n x: targetX,\n y: targetY,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(targetX: number, targetY: number): Promise<void> {\n const client = this.#requireClient();\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: targetX, y: targetY },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: 50 },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async typeText(text: string): Promise<void> {\n const client = this.#requireClient();\n const activeElement = await client.findElement(\n 'xpath',\n '//*[@focused=\"true\"]',\n );\n const elementId = extractElementId(activeElement);\n await client.elementClear(elementId);\n await client.elementSendKeys(elementId, text);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const client = this.#requireClient();\n const dist = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, dist);\n\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: sx, y: sy },\n { type: 'pointerDown', button: 0 },\n { type: 'pointerMove', duration: 300, x: endX, y: endY },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const client = this.#requireClient();\n\n if (this.platform === 'ios') {\n // iOS app state: 0=not installed, 1=not running, 2=background, 3=suspended, 4=foreground\n const state = await client.queryAppState(bundleId);\n const stateMap: Record<number, string> = {\n 0: 'Not Installed',\n 1: 'Not Running',\n 2: 'Running (background)',\n 3: 'Suspended',\n 4: 'Running (foreground)',\n };\n return { bundleId, state: stateMap[state] ?? `Unknown (${state})` };\n }\n\n try {\n const activity = await client.getCurrentActivity();\n return { bundleId, state: `Running (${activity})` };\n } catch {\n return { bundleId, state: 'Not Running' };\n }\n }\n\n async #getHierarchy(client: WebDriverClient): Promise<string> {\n if (this.platform === 'ios') {\n return this.#getIosHierarchy(client);\n }\n return client.getPageSource();\n }\n\n async #getIosHierarchy(client: WebDriverClient): Promise<string> {\n // Limit hierarchy depth to avoid \"JS stack is too deep\" on complex iOS apps\n try {\n await client.updateSettings({ snapshotMaxDepth: 30 });\n } catch {\n // snapshotMaxDepth not supported on older XCUITest drivers\n }\n\n try {\n return await client.execute<string>('mobile: source', [\n { format: 'xml' },\n ]);\n } catch {\n return client.getPageSource();\n }\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n const client = this.#requireClient();\n const data = await client.execute<string>('mobile: getScreenshot', []);\n if (options?.encode === false) {\n const path = resolveArtifactPath(outputPath, 'screenshot');\n await writeArtifactFile(path, Buffer.from(data, 'base64'));\n return { data: undefined, format: 'png', path };\n }\n let path: string | undefined;\n if (outputPath) {\n path = resolveArtifactPath(outputPath, 'screenshot');\n await writeArtifactFile(path, Buffer.from(data, 'base64'));\n }\n return { data, format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: activateApp', [{ bundleId }]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: terminateApp', [{ bundleId }]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n const iosMap: Record<DeviceButton, string> = {\n home: 'home',\n lock: 'lock',\n back: 'home',\n enter: '\\n',\n };\n await client.execute('mobile: pressButton', [{ name: iosMap[button] }]);\n } else {\n // Android keyevent codes\n const androidMap: Record<DeviceButton, number> = {\n home: 3,\n back: 4,\n enter: 66,\n lock: 26,\n };\n await client.execute('mobile: pressKey', [\n { keycode: androidMap[button] },\n ]);\n }\n }\n\n async dismissKeyboard(): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n await client.execute('mobile: hideKeyboard', []);\n } else {\n await client.execute('mobile: hideKeyboard', []);\n }\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const client = this.#requireClient();\n const action = accept ? 'accept' : 'dismiss';\n await client.execute(`mobile: ${action}Alert`, []);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const client = this.#requireClient();\n const logType = this.platform === 'ios' ? 'syslog' : 'logcat';\n const raw = await client.execute<Record<string, unknown>[]>(\n 'mobile: getLog',\n [{ type: logType }],\n );\n\n let entries = raw.map((entry) => ({\n timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : '',\n level: typeof entry.level === 'string' ? entry.level : 'info',\n message: typeof entry.message === 'string' ? entry.message : '',\n }));\n\n if (filter) {\n const pattern = new RegExp(filter, 'iu');\n entries = entries.filter((entry) => pattern.test(entry.message));\n }\n\n const cutoff = Date.now() - durationSeconds * 1000;\n entries = entries.filter((entry) => {\n const ts = Number(entry.timestamp);\n return isNaN(ts) || ts >= cutoff;\n });\n\n return { entries, source: `appium ${logType}` };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const client = this.#requireClient();\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: cx, y: cy },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: durationMs },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const client = this.#requireClient();\n try {\n return await client.execute<string>('mobile: getAlertText', []);\n } catch {\n const snap = await this.snapshot();\n const texts = collectAppiumAlertTexts(snap.hierarchy, this.platform);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const client = this.#requireClient();\n try {\n const rect = await client.getWindowRect();\n return { width: rect.width, height: rect.height };\n } catch {\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size');\n }\n }\n\n async getContexts(): Promise<string[]> {\n const client = this.#requireClient();\n return client.getContexts();\n }\n\n async setContext(context: string): Promise<void> {\n const client = this.#requireClient();\n await client.setCurrentContext(context);\n }\n\n async getClipboard(): Promise<string> {\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: getClipboard', [\n { contentType: 'plaintext' },\n ]);\n return Buffer.from(b64, 'base64').toString('utf-8');\n }\n\n async setClipboard(text: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: setClipboard', [\n {\n content: Buffer.from(text).toString('base64'),\n contentType: 'plaintext',\n },\n ]);\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recording) {\n throw new Error('Screen recording is already in progress');\n }\n const client = this.#requireClient();\n await client.execute('mobile: startRecordingScreen', [{}]);\n this.#recording = true;\n this.#recordingOutputPath = resolveArtifactPath(outputPath, 'recording');\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recording || !this.#recordingOutputPath) {\n throw new Error('No screen recording in progress');\n }\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: stopRecordingScreen', []);\n const data = Buffer.from(b64, 'base64');\n if (data.length === 0) {\n throw new Error('Screen recording returned empty data');\n }\n const outputPath = this.#recordingOutputPath;\n await writeArtifactFile(outputPath, data);\n this.#recording = false;\n this.#recordingOutputPath = null;\n return outputPath;\n }\n\n #requireClient(): WebDriverClient {\n if (!this.#client) {\n throw new Error(\n 'AppiumBackend not connected. Call ensureConnected() first.',\n );\n }\n return this.#client;\n }\n}\n\nfunction collectAppiumAlertTexts(\n elements: UIElement[],\n platform: Platform,\n): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlert =\n platform === 'ios'\n ? el.type === 'Alert' || el.type === 'Sheet'\n : el.type.includes('Dialog');\n if (isAlert) {\n collectAppiumTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAppiumAlertTexts(el.children, platform);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectAppiumTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n const text = el.value ?? el.label;\n if (\n text &&\n (el.type === 'StaticText' ||\n el.type.includes('TextView') ||\n el.type === 'Text')\n ) {\n texts.push(text);\n }\n if (el.children) {\n collectAppiumTextValues(el.children, texts);\n }\n }\n}\n\nfunction extractElementId(element: Record<string, string>): string {\n // W3C uses 'element-6066-11e4-a52e-4f735466cecf', JSONWP uses 'ELEMENT'\n const id =\n element['element-6066-11e4-a52e-4f735466cecf'] ||\n element.ELEMENT ||\n Object.values(element)[0];\n\n if (!id) {\n throw new Error(\n `Failed to extract element ID from Appium response: ${JSON.stringify(element)}`,\n );\n }\n return id;\n}\n\nexport function parseAppiumAndroidHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n const nodeRegex = /<node\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const element = parseAndroidNodeAttrs(match[1]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseAndroidNodeAttrs(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n\nexport function parseAppiumIosHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n // XCUITest XML uses <XCUIElementType* ...> elements\n const nodeRegex = /<(XCUIElementType\\w+)\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const typeName = match[1];\n const element = parseIosNodeAttrs(typeName, match[2]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseIosNodeAttrs(typeName: string, attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const xStr = get('x');\n const yStr = get('y');\n const widthStr = get('width');\n const heightStr = get('height');\n\n if (!xStr || !yStr || !widthStr || !heightStr) {\n return null;\n }\n\n const shortType = typeName.replace(/^XCUIElementType/u, '');\n\n return {\n type: shortType,\n label: get('label') || undefined,\n value: get('value') || undefined,\n identifier: get('name') || undefined,\n frame: {\n x: parseInt(xStr, 10),\n y: parseInt(yStr, 10),\n width: parseInt(widthStr, 10),\n height: parseInt(heightStr, 10),\n },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} |
| import type { SessionConfig } from "./session-file.cjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, Platform, WindowSize } from "./types.cjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, Platform, WindowSize } from "./types.cjs"; | ||
| export declare class AppiumBackend implements DeviceBackend { | ||
@@ -17,3 +17,9 @@ #private; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -20,0 +26,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"appium-backend.d.cts","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,2BAA0B;AACvD,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,UAAU,EACX,oBAAmB;AAQpB,qBAAa,aAAc,YAAW,aAAa;;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;gBAUhB,MAAM,EAAE,aAAa;IAK3B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAcpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAgBnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAYpD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAuBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgDtD,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAMvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA4BnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAoCrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAqBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAiB/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAiBpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ/B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzC,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAyB7C;AA0DD,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAapE;AAkCD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAehE"} | ||
| {"version":3,"file":"appium-backend.d.cts","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,2BAA0B;AACvD,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,UAAU,EACX,oBAAmB;AAYpB,qBAAa,aAAc,YAAW,aAAa;;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;gBAUhB,MAAM,EAAE,aAAa;IAK3B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAcpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAgBnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAYpD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAuBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgD5D,UAAU,CACR,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,IAAI,CAAA;KAAE,GAC1B,OAAO,CAAC,gBAAgB,CAAC;IAE5B,UAAU,CACR,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE,GACzB,OAAO,CAAC,oBAAoB,CAAC;IAEhC,UAAU,CACR,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,gBAAgB,GAAG,oBAAoB,CAAC;IAqB7C,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA4BnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAoCrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAqBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAiB/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAiBpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ/B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzC,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAyB7C;AA0DD,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAapE;AAkCD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAehE"} |
| import type { SessionConfig } from "./session-file.mjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, Platform, WindowSize } from "./types.mjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, Platform, WindowSize } from "./types.mjs"; | ||
| export declare class AppiumBackend implements DeviceBackend { | ||
@@ -17,3 +17,9 @@ #private; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -20,0 +26,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"appium-backend.d.mts","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,2BAA0B;AACvD,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,UAAU,EACX,oBAAmB;AAQpB,qBAAa,aAAc,YAAW,aAAa;;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;gBAUhB,MAAM,EAAE,aAAa;IAK3B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAcpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAgBnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAYpD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAuBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgDtD,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAMvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA4BnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAoCrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAqBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAiB/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAiBpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ/B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUzC,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAyB7C;AA0DD,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAapE;AAkCD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAehE"} | ||
| {"version":3,"file":"appium-backend.d.mts","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"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"} |
@@ -13,6 +13,5 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
| var _AppiumBackend_instances, _AppiumBackend_client, _AppiumBackend_config, _AppiumBackend_recording, _AppiumBackend_recordingOutputPath, _AppiumBackend_getHierarchy, _AppiumBackend_getIosHierarchy, _AppiumBackend_requireClient; | ||
| import { writeFileSync } from "node:fs"; | ||
| import { resolve as resolvePath } from "node:path"; | ||
| import { WebDriverClient, createSession } from "./webdriver-client.mjs"; | ||
| import { findElement, describeElement, computeSwipeEnd } from "../utils/element.mjs"; | ||
| import { resolveArtifactPath, writeArtifactFile } from "../utils/output-path.mjs"; | ||
| export class AppiumBackend { | ||
@@ -171,6 +170,16 @@ constructor(config) { | ||
| } | ||
| async screenshot() { | ||
| async screenshot(outputPath, options) { | ||
| const client = __classPrivateFieldGet(this, _AppiumBackend_instances, "m", _AppiumBackend_requireClient).call(this); | ||
| const data = await client.execute('mobile: getScreenshot', []); | ||
| return { data, format: 'png' }; | ||
| if (options?.encode === false) { | ||
| const path = resolveArtifactPath(outputPath, 'screenshot'); | ||
| await writeArtifactFile(path, Buffer.from(data, 'base64')); | ||
| return { data: undefined, format: 'png', path }; | ||
| } | ||
| let path; | ||
| if (outputPath) { | ||
| path = resolveArtifactPath(outputPath, 'screenshot'); | ||
| await writeArtifactFile(path, Buffer.from(data, 'base64')); | ||
| } | ||
| return { data, format: 'png', path }; | ||
| } | ||
@@ -354,3 +363,3 @@ async openApp(bundleId) { | ||
| __classPrivateFieldSet(this, _AppiumBackend_recording, true, "f"); | ||
| __classPrivateFieldSet(this, _AppiumBackend_recordingOutputPath, resolvePath(outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`), "f"); | ||
| __classPrivateFieldSet(this, _AppiumBackend_recordingOutputPath, resolveArtifactPath(outputPath, 'recording'), "f"); | ||
| } | ||
@@ -367,4 +376,4 @@ async stopScreenRecording() { | ||
| } | ||
| const outputPath = resolvePath(__classPrivateFieldGet(this, _AppiumBackend_recordingOutputPath, "f")); | ||
| writeFileSync(outputPath, data); | ||
| const outputPath = __classPrivateFieldGet(this, _AppiumBackend_recordingOutputPath, "f"); | ||
| await writeArtifactFile(outputPath, data); | ||
| __classPrivateFieldSet(this, _AppiumBackend_recording, false, "f"); | ||
@@ -371,0 +380,0 @@ __classPrivateFieldSet(this, _AppiumBackend_recordingOutputPath, null, "f"); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"appium-backend.mjs","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFileSync } from 'node:fs';\nimport { resolve as resolvePath } from 'node:path';\n\nimport type { SessionConfig } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport { WebDriverClient, createSession } from './webdriver-client.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\n\nexport class AppiumBackend implements DeviceBackend {\n readonly platform: Platform;\n\n #client: WebDriverClient | null = null;\n\n readonly #config: SessionConfig;\n\n #recording = false;\n\n #recordingOutputPath: string | null = null;\n\n constructor(config: SessionConfig) {\n this.#config = config;\n this.platform = config.platform;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#client) {\n return;\n }\n\n if (this.#config.mode === 'attach') {\n this.#client = new WebDriverClient(\n this.#config.appiumUrl,\n this.#config.sessionId,\n this.#config.auth,\n );\n await this.#client.getStatus();\n } else {\n const { client } = await createSession(\n this.#config.appiumUrl,\n this.#config.capabilities,\n this.#config.auth,\n );\n this.#client = client;\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const client = this.#requireClient();\n const details = await client.getSessionDetails();\n const caps = (details.capabilities ?? details) as Record<string, unknown>;\n\n return {\n platform: this.platform,\n deviceId: (caps.udid as string) ?? client.sessionId,\n name: (caps.deviceName as string) ?? 'Appium Device',\n osVersion: (caps.platformVersion as string) ?? 'Unknown',\n state: 'connected',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const client = this.#requireClient();\n const raw = await this.#getHierarchy(client);\n const hierarchy =\n this.platform === 'ios'\n ? parseAppiumIosHierarchy(raw)\n : parseAppiumAndroidHierarchy(raw);\n\n return {\n platform: this.platform,\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const targetX = Math.round(element.frame.x + element.frame.width / 2);\n const targetY = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(targetX, targetY);\n\n return {\n success: true,\n x: targetX,\n y: targetY,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(targetX: number, targetY: number): Promise<void> {\n const client = this.#requireClient();\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: targetX, y: targetY },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: 50 },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async typeText(text: string): Promise<void> {\n const client = this.#requireClient();\n const activeElement = await client.findElement(\n 'xpath',\n '//*[@focused=\"true\"]',\n );\n const elementId = extractElementId(activeElement);\n await client.elementClear(elementId);\n await client.elementSendKeys(elementId, text);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const client = this.#requireClient();\n const dist = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, dist);\n\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: sx, y: sy },\n { type: 'pointerDown', button: 0 },\n { type: 'pointerMove', duration: 300, x: endX, y: endY },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const client = this.#requireClient();\n\n if (this.platform === 'ios') {\n // iOS app state: 0=not installed, 1=not running, 2=background, 3=suspended, 4=foreground\n const state = await client.queryAppState(bundleId);\n const stateMap: Record<number, string> = {\n 0: 'Not Installed',\n 1: 'Not Running',\n 2: 'Running (background)',\n 3: 'Suspended',\n 4: 'Running (foreground)',\n };\n return { bundleId, state: stateMap[state] ?? `Unknown (${state})` };\n }\n\n try {\n const activity = await client.getCurrentActivity();\n return { bundleId, state: `Running (${activity})` };\n } catch {\n return { bundleId, state: 'Not Running' };\n }\n }\n\n async #getHierarchy(client: WebDriverClient): Promise<string> {\n if (this.platform === 'ios') {\n return this.#getIosHierarchy(client);\n }\n return client.getPageSource();\n }\n\n async #getIosHierarchy(client: WebDriverClient): Promise<string> {\n // Limit hierarchy depth to avoid \"JS stack is too deep\" on complex iOS apps\n try {\n await client.updateSettings({ snapshotMaxDepth: 30 });\n } catch {\n // snapshotMaxDepth not supported on older XCUITest drivers\n }\n\n try {\n return await client.execute<string>('mobile: source', [\n { format: 'xml' },\n ]);\n } catch {\n return client.getPageSource();\n }\n }\n\n async screenshot(): Promise<ScreenshotResult> {\n const client = this.#requireClient();\n const data = await client.execute<string>('mobile: getScreenshot', []);\n return { data, format: 'png' };\n }\n\n async openApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: activateApp', [{ bundleId }]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: terminateApp', [{ bundleId }]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n const iosMap: Record<DeviceButton, string> = {\n home: 'home',\n lock: 'lock',\n back: 'home',\n enter: '\\n',\n };\n await client.execute('mobile: pressButton', [{ name: iosMap[button] }]);\n } else {\n // Android keyevent codes\n const androidMap: Record<DeviceButton, number> = {\n home: 3,\n back: 4,\n enter: 66,\n lock: 26,\n };\n await client.execute('mobile: pressKey', [\n { keycode: androidMap[button] },\n ]);\n }\n }\n\n async dismissKeyboard(): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n await client.execute('mobile: hideKeyboard', []);\n } else {\n await client.execute('mobile: hideKeyboard', []);\n }\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const client = this.#requireClient();\n const action = accept ? 'accept' : 'dismiss';\n await client.execute(`mobile: ${action}Alert`, []);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const client = this.#requireClient();\n const logType = this.platform === 'ios' ? 'syslog' : 'logcat';\n const raw = await client.execute<Record<string, unknown>[]>(\n 'mobile: getLog',\n [{ type: logType }],\n );\n\n let entries = raw.map((entry) => ({\n timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : '',\n level: typeof entry.level === 'string' ? entry.level : 'info',\n message: typeof entry.message === 'string' ? entry.message : '',\n }));\n\n if (filter) {\n const pattern = new RegExp(filter, 'iu');\n entries = entries.filter((entry) => pattern.test(entry.message));\n }\n\n const cutoff = Date.now() - durationSeconds * 1000;\n entries = entries.filter((entry) => {\n const ts = Number(entry.timestamp);\n return isNaN(ts) || ts >= cutoff;\n });\n\n return { entries, source: `appium ${logType}` };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const client = this.#requireClient();\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: cx, y: cy },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: durationMs },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const client = this.#requireClient();\n try {\n return await client.execute<string>('mobile: getAlertText', []);\n } catch {\n const snap = await this.snapshot();\n const texts = collectAppiumAlertTexts(snap.hierarchy, this.platform);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const client = this.#requireClient();\n try {\n const rect = await client.getWindowRect();\n return { width: rect.width, height: rect.height };\n } catch {\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size');\n }\n }\n\n async getContexts(): Promise<string[]> {\n const client = this.#requireClient();\n return client.getContexts();\n }\n\n async setContext(context: string): Promise<void> {\n const client = this.#requireClient();\n await client.setCurrentContext(context);\n }\n\n async getClipboard(): Promise<string> {\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: getClipboard', [\n { contentType: 'plaintext' },\n ]);\n return Buffer.from(b64, 'base64').toString('utf-8');\n }\n\n async setClipboard(text: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: setClipboard', [\n {\n content: Buffer.from(text).toString('base64'),\n contentType: 'plaintext',\n },\n ]);\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recording) {\n throw new Error('Screen recording is already in progress');\n }\n const client = this.#requireClient();\n await client.execute('mobile: startRecordingScreen', [{}]);\n this.#recording = true;\n this.#recordingOutputPath = resolvePath(\n outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`,\n );\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recording || !this.#recordingOutputPath) {\n throw new Error('No screen recording in progress');\n }\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: stopRecordingScreen', []);\n const data = Buffer.from(b64, 'base64');\n if (data.length === 0) {\n throw new Error('Screen recording returned empty data');\n }\n const outputPath = resolvePath(this.#recordingOutputPath);\n writeFileSync(outputPath, data);\n this.#recording = false;\n this.#recordingOutputPath = null;\n return outputPath;\n }\n\n #requireClient(): WebDriverClient {\n if (!this.#client) {\n throw new Error(\n 'AppiumBackend not connected. Call ensureConnected() first.',\n );\n }\n return this.#client;\n }\n}\n\nfunction collectAppiumAlertTexts(\n elements: UIElement[],\n platform: Platform,\n): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlert =\n platform === 'ios'\n ? el.type === 'Alert' || el.type === 'Sheet'\n : el.type.includes('Dialog');\n if (isAlert) {\n collectAppiumTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAppiumAlertTexts(el.children, platform);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectAppiumTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n const text = el.value ?? el.label;\n if (\n text &&\n (el.type === 'StaticText' ||\n el.type.includes('TextView') ||\n el.type === 'Text')\n ) {\n texts.push(text);\n }\n if (el.children) {\n collectAppiumTextValues(el.children, texts);\n }\n }\n}\n\nfunction extractElementId(element: Record<string, string>): string {\n // W3C uses 'element-6066-11e4-a52e-4f735466cecf', JSONWP uses 'ELEMENT'\n const id =\n element['element-6066-11e4-a52e-4f735466cecf'] ||\n element.ELEMENT ||\n Object.values(element)[0];\n\n if (!id) {\n throw new Error(\n `Failed to extract element ID from Appium response: ${JSON.stringify(element)}`,\n );\n }\n return id;\n}\n\nexport function parseAppiumAndroidHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n const nodeRegex = /<node\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const element = parseAndroidNodeAttrs(match[1]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseAndroidNodeAttrs(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n\nexport function parseAppiumIosHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n // XCUITest XML uses <XCUIElementType* ...> elements\n const nodeRegex = /<(XCUIElementType\\w+)\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const typeName = match[1];\n const element = parseIosNodeAttrs(typeName, match[2]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseIosNodeAttrs(typeName: string, attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const xStr = get('x');\n const yStr = get('y');\n const widthStr = get('width');\n const heightStr = get('height');\n\n if (!xStr || !yStr || !widthStr || !heightStr) {\n return null;\n }\n\n const shortType = typeName.replace(/^XCUIElementType/u, '');\n\n return {\n type: shortType,\n label: get('label') || undefined,\n value: get('value') || undefined,\n identifier: get('name') || undefined,\n frame: {\n x: parseInt(xStr, 10),\n y: parseInt(yStr, 10),\n width: parseInt(widthStr, 10),\n height: parseInt(heightStr, 10),\n },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} | ||
| {"version":3,"file":"appium-backend.mjs","sourceRoot":"","sources":["../../src/backends/appium-backend.ts"],"names":[],"mappings":";;;;;;;;;;;;AAiBA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,+BAA8B;AACvE,OAAO,EACL,WAAW,EACX,eAAe,EACf,eAAe,EAChB,6BAA4B;AAC7B,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EAClB,iCAAgC;AAEjC,MAAM,OAAO,aAAa;IAWxB,YAAY,MAAqB;;QARjC,gCAAkC,IAAI,EAAC;QAE9B,wCAAuB;QAEhC,mCAAa,KAAK,EAAC;QAEnB,6CAAsC,IAAI,EAAC;QAGzC,uBAAA,IAAI,yBAAW,MAAM,MAAA,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,uBAAA,IAAI,6BAAQ,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,uBAAA,IAAI,6BAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,uBAAA,IAAI,yBAAW,IAAI,eAAe,CAChC,uBAAA,IAAI,6BAAQ,CAAC,SAAS,EACtB,uBAAA,IAAI,6BAAQ,CAAC,SAAS,EACtB,uBAAA,IAAI,6BAAQ,CAAC,IAAI,CAClB,MAAA,CAAC;YACF,MAAM,uBAAA,IAAI,6BAAQ,CAAC,SAAS,EAAE,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,uBAAA,IAAI,6BAAQ,CAAC,SAAS,EACtB,uBAAA,IAAI,6BAAQ,CAAC,YAAY,EACzB,uBAAA,IAAI,6BAAQ,CAAC,IAAI,CAClB,CAAC;YACF,uBAAA,IAAI,yBAAW,MAAM,MAAA,CAAC;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,OAAO,CAA4B,CAAC;QAE1E,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAG,IAAI,CAAC,IAAe,IAAI,MAAM,CAAC,SAAS;YACnD,IAAI,EAAG,IAAI,CAAC,UAAqB,IAAI,eAAe;YACpD,SAAS,EAAG,IAAI,CAAC,eAA0B,IAAI,SAAS;YACxD,KAAK,EAAE,WAAW;SACnB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,uBAAA,IAAI,6DAAc,MAAlB,IAAI,EAAe,MAAM,CAAC,CAAC;QAC7C,MAAM,SAAS,GACb,IAAI,CAAC,QAAQ,KAAK,KAAK;YACrB,CAAC,CAAC,uBAAuB,CAAC,GAAG,CAAC;YAC9B,CAAC,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC;QAEvC,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS;YACT,GAAG;YACH,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAmB;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI;gBAC7C,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvE,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAE5C,OAAO;YACL,OAAO,EAAE,IAAI;YACb,CAAC,EAAE,OAAO;YACV,CAAC,EAAE,OAAO;YACV,iBAAiB,EAAE,eAAe,CAAC,OAAO,CAAC;SAC5C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,KAAmB;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI;gBAC7C,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAAe,EAAE,OAAe;QACnD,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,CAAC,cAAc,CAAC;YAC1B;gBACE,IAAI,EAAE,SAAS;gBACf,EAAE,EAAE,SAAS;gBACb,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;gBACpC,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE;oBAC5D,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,EAAE;oBAClC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;oBAC/B,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE;iBACjC;aACF;SACF,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,WAAW,CAC5C,OAAO,EACP,sBAAsB,CACvB,CAAC;QACF,MAAM,SAAS,GAAG,gBAAgB,CAAC,aAAa,CAAC,CAAC;QAClD,MAAM,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,KAAK,CACT,SAA2C,EAC3C,MAAe,EACf,MAAe,EACf,QAAiB;QAEjB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,IAAI,GAAG,QAAQ,IAAI,GAAG,CAAC;QAC7B,MAAM,EAAE,GAAG,MAAM,IAAI,GAAG,CAAC;QACzB,MAAM,EAAE,GAAG,MAAM,IAAI,GAAG,CAAC;QACzB,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAE9D,MAAM,MAAM,CAAC,cAAc,CAAC;YAC1B;gBACE,IAAI,EAAE,SAAS;gBACf,EAAE,EAAE,SAAS;gBACb,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;gBACpC,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;oBAClD,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,EAAE;oBAClC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE;oBACxD,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE;iBACjC;aACF;SACF,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,KAAmB,EACnB,SAAS,GAAG,KAAM,EAClB,UAAU,GAAG,GAAG;QAEhB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,SAAS,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACnD,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,OAAO,CAAC;YACjB,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kCAAkC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,SAAS,KAAK,CAC3E,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,QAAgB;QAChC,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QAErC,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC5B,yFAAyF;YACzF,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YACnD,MAAM,QAAQ,GAA2B;gBACvC,CAAC,EAAE,eAAe;gBAClB,CAAC,EAAE,aAAa;gBAChB,CAAC,EAAE,sBAAsB;gBACzB,CAAC,EAAE,WAAW;gBACd,CAAC,EAAE,sBAAsB;aAC1B,CAAC;YACF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,YAAY,KAAK,GAAG,EAAE,CAAC;QACtE,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,QAAQ,GAAG,EAAE,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QAC5C,CAAC;IACH,CAAC;IAyCD,KAAK,CAAC,UAAU,CACd,UAAmB,EACnB,OAA2B;QAE3B,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAS,uBAAuB,EAAE,EAAE,CAAC,CAAC;QACvE,IAAI,OAAO,EAAE,MAAM,KAAK,KAAK,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;YAC3D,MAAM,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC3D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,IAAwB,CAAC;QAC7B,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,GAAG,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;YACrD,MAAM,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,QAAgB;QAC5B,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAgB;QAC7B,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAoB;QACpC,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAiC;gBAC3C,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,IAAI;aACZ,CAAC;YACF,MAAM,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,CAAC;aAAM,CAAC;YACN,yBAAyB;YACzB,MAAM,UAAU,GAAiC;gBAC/C,IAAI,EAAE,CAAC;gBACP,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,EAAE;gBACT,IAAI,EAAE,EAAE;aACT,CAAC;YACF,MAAM,MAAM,CAAC,OAAO,CAAC,kBAAkB,EAAE;gBACvC,EAAE,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE;aAChC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC5B,MAAM,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;QACnD,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAe;QAChC,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,MAAM,OAAO,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,eAAe,GAAG,EAAE,EAAE,MAAe;QACjD,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAC9B,gBAAgB,EAChB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CACpB,CAAC;QAEF,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAChC,SAAS,EAAE,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;YACrE,KAAK,EAAE,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAC7D,OAAO,EAAE,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;SAChE,CAAC,CAAC,CAAC;QAEJ,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACzC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC;QACnD,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACnC,OAAO,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAmB,EAAE,UAAU,GAAG,IAAI;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI;gBAC7C,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAClE,MAAM,MAAM,CAAC,cAAc,CAAC;YAC1B;gBACE,IAAI,EAAE,SAAS;gBACf,EAAE,EAAE,SAAS;gBACb,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;gBACpC,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;oBAClD,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,EAAE;oBAClC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE;oBACvC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE;iBACjC;aACF;SACF,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;QAE9B,OAAO;YACL,OAAO,EAAE,IAAI;YACb,CAAC,EAAE,EAAE;YACL,CAAC,EAAE,EAAE;YACL,iBAAiB,EAAE,eAAe,CAAC,OAAO,CAAC;SAC5C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,KAAmB,EACnB,YAA2B,MAAM,EACjC,WAAW,GAAG,EAAE;QAEhB,IAAI,WAAW,GAAG,EAAE,CAAC;QAErB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACnD,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,OAAO,CAAC;YACjB,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;gBAC7B,MAAM;YACR,CAAC;YACD,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC;YACvB,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,sCAAsC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI;YAC7D,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,OAAO,CAAS,sBAAsB,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YACrE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CACb,oCAAoC;oBAClC,0DAA0D,CAC7D,CAAC;YACJ,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1C,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBAC/B,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAClD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAChE,CAAC;YACH,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAe;QAC9B,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAS,sBAAsB,EAAE;YAC/D,EAAE,WAAW,EAAE,WAAW,EAAE;SAC7B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,IAAY;QAC7B,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE;YAC3C;gBACE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC7C,WAAW,EAAE,WAAW;aACzB;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,UAAmB;QAC5C,IAAI,uBAAA,IAAI,gCAAW,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QACD,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,MAAM,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3D,uBAAA,IAAI,4BAAc,IAAI,MAAA,CAAC;QACvB,uBAAA,IAAI,sCAAwB,mBAAmB,CAAC,UAAU,EAAE,WAAW,CAAC,MAAA,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,mBAAmB;QACvB,IAAI,CAAC,uBAAA,IAAI,gCAAW,IAAI,CAAC,uBAAA,IAAI,0CAAqB,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;QACD,MAAM,MAAM,GAAG,uBAAA,IAAI,8DAAe,MAAnB,IAAI,CAAiB,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAS,6BAA6B,EAAE,EAAE,CAAC,CAAC;QAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,UAAU,GAAG,uBAAA,IAAI,0CAAqB,CAAC;QAC7C,MAAM,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC1C,uBAAA,IAAI,4BAAc,KAAK,MAAA,CAAC;QACxB,uBAAA,IAAI,sCAAwB,IAAI,MAAA,CAAC;QACjC,OAAO,UAAU,CAAC;IACpB,CAAC;CAUF;oPArSC,KAAK,sCAAe,MAAuB;IACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC5B,OAAO,uBAAA,IAAI,gEAAiB,MAArB,IAAI,EAAkB,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC;AAChC,CAAC,mCAED,KAAK,yCAAkB,MAAuB;IAC5C,4EAA4E;IAC5E,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,2DAA2D;IAC7D,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,OAAO,CAAS,gBAAgB,EAAE;YACpD,EAAE,MAAM,EAAE,KAAK,EAAE;SAClB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC;IAChC,CAAC;AACH,CAAC;IAwQC,IAAI,CAAC,uBAAA,IAAI,6BAAQ,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,uBAAA,IAAI,6BAAQ,CAAC;AACtB,CAAC;AAGH,SAAS,uBAAuB,CAC9B,QAAqB,EACrB,QAAkB;IAElB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,OAAO,GACX,QAAQ,KAAK,KAAK;YAChB,CAAC,CAAC,EAAE,CAAC,IAAI,KAAK,OAAO,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO;YAC5C,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,OAAO,EAAE,CAAC;YACZ,uBAAuB,CAAC,EAAE,CAAC,QAAQ,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;YAChB,MAAM,KAAK,GAAG,uBAAuB,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC7D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,uBAAuB,CAAC,QAAqB,EAAE,KAAe;IACrE,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC;QAClC,IACE,IAAI;YACJ,CAAC,EAAE,CAAC,IAAI,KAAK,YAAY;gBACvB,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAC5B,EAAE,CAAC,IAAI,KAAK,MAAM,CAAC,EACrB,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;YAChB,uBAAuB,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,OAA+B;IACvD,wEAAwE;IACxE,MAAM,EAAE,GACN,OAAO,CAAC,qCAAqC,CAAC;QAC9C,OAAO,CAAC,OAAO;QACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAE5B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CACb,sDAAsD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,GAAW;IACrD,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,uBAAuB,CAAC;IAC1C,IAAI,KAAK,CAAC;IAEV,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa;IAC1C,MAAM,GAAG,GAAG,CAAC,IAAY,EAAsB,EAAE;QAC/C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,GAAG,IAAI,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC;QACpE,OAAO,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;IACrC,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gCAAgC;IAChC,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACvE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAExC,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS;QAC/B,KAAK,EAAE,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS;QACvC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;QAC/B,UAAU,EAAE,GAAG,CAAC,aAAa,CAAC,IAAI,SAAS;QAC3C,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE;QACxD,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,KAAK,OAAO;KACpC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,GAAW;IACjD,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,oDAAoD;IACpD,MAAM,SAAS,GAAG,uCAAuC,CAAC;IAC1D,IAAI,KAAK,CAAC;IAEV,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,OAAO,GAAG,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB,EAAE,KAAa;IACxD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAsB,EAAE;QAC/C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,GAAG,IAAI,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC;QACpE,OAAO,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;IACrC,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEhC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAE5D,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS;QAChC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS;QAChC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;QACpC,KAAK,EAAE;YACL,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACrB,CAAC,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACrB,KAAK,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC7B,MAAM,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;SAChC;QACD,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,KAAK,OAAO;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type { SessionConfig } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport { WebDriverClient, createSession } from './webdriver-client.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport {\n resolveArtifactPath,\n writeArtifactFile,\n} from '../utils/output-path.js';\n\nexport class AppiumBackend implements DeviceBackend {\n readonly platform: Platform;\n\n #client: WebDriverClient | null = null;\n\n readonly #config: SessionConfig;\n\n #recording = false;\n\n #recordingOutputPath: string | null = null;\n\n constructor(config: SessionConfig) {\n this.#config = config;\n this.platform = config.platform;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#client) {\n return;\n }\n\n if (this.#config.mode === 'attach') {\n this.#client = new WebDriverClient(\n this.#config.appiumUrl,\n this.#config.sessionId,\n this.#config.auth,\n );\n await this.#client.getStatus();\n } else {\n const { client } = await createSession(\n this.#config.appiumUrl,\n this.#config.capabilities,\n this.#config.auth,\n );\n this.#client = client;\n }\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n const client = this.#requireClient();\n const details = await client.getSessionDetails();\n const caps = (details.capabilities ?? details) as Record<string, unknown>;\n\n return {\n platform: this.platform,\n deviceId: (caps.udid as string) ?? client.sessionId,\n name: (caps.deviceName as string) ?? 'Appium Device',\n osVersion: (caps.platformVersion as string) ?? 'Unknown',\n state: 'connected',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n const client = this.#requireClient();\n const raw = await this.#getHierarchy(client);\n const hierarchy =\n this.platform === 'ios'\n ? parseAppiumIosHierarchy(raw)\n : parseAppiumAndroidHierarchy(raw);\n\n return {\n platform: this.platform,\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const targetX = Math.round(element.frame.x + element.frame.width / 2);\n const targetY = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(targetX, targetY);\n\n return {\n success: true,\n x: targetX,\n y: targetY,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(targetX: number, targetY: number): Promise<void> {\n const client = this.#requireClient();\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: targetX, y: targetY },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: 50 },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async typeText(text: string): Promise<void> {\n const client = this.#requireClient();\n const activeElement = await client.findElement(\n 'xpath',\n '//*[@focused=\"true\"]',\n );\n const elementId = extractElementId(activeElement);\n await client.elementClear(elementId);\n await client.elementSendKeys(elementId, text);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n const client = this.#requireClient();\n const dist = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, dist);\n\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: sx, y: sy },\n { type: 'pointerDown', button: 0 },\n { type: 'pointerMove', duration: 300, x: endX, y: endY },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n const client = this.#requireClient();\n\n if (this.platform === 'ios') {\n // iOS app state: 0=not installed, 1=not running, 2=background, 3=suspended, 4=foreground\n const state = await client.queryAppState(bundleId);\n const stateMap: Record<number, string> = {\n 0: 'Not Installed',\n 1: 'Not Running',\n 2: 'Running (background)',\n 3: 'Suspended',\n 4: 'Running (foreground)',\n };\n return { bundleId, state: stateMap[state] ?? `Unknown (${state})` };\n }\n\n try {\n const activity = await client.getCurrentActivity();\n return { bundleId, state: `Running (${activity})` };\n } catch {\n return { bundleId, state: 'Not Running' };\n }\n }\n\n async #getHierarchy(client: WebDriverClient): Promise<string> {\n if (this.platform === 'ios') {\n return this.#getIosHierarchy(client);\n }\n return client.getPageSource();\n }\n\n async #getIosHierarchy(client: WebDriverClient): Promise<string> {\n // Limit hierarchy depth to avoid \"JS stack is too deep\" on complex iOS apps\n try {\n await client.updateSettings({ snapshotMaxDepth: 30 });\n } catch {\n // snapshotMaxDepth not supported on older XCUITest drivers\n }\n\n try {\n return await client.execute<string>('mobile: source', [\n { format: 'xml' },\n ]);\n } catch {\n return client.getPageSource();\n }\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n const client = this.#requireClient();\n const data = await client.execute<string>('mobile: getScreenshot', []);\n if (options?.encode === false) {\n const path = resolveArtifactPath(outputPath, 'screenshot');\n await writeArtifactFile(path, Buffer.from(data, 'base64'));\n return { data: undefined, format: 'png', path };\n }\n let path: string | undefined;\n if (outputPath) {\n path = resolveArtifactPath(outputPath, 'screenshot');\n await writeArtifactFile(path, Buffer.from(data, 'base64'));\n }\n return { data, format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: activateApp', [{ bundleId }]);\n }\n\n async closeApp(bundleId: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: terminateApp', [{ bundleId }]);\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n const iosMap: Record<DeviceButton, string> = {\n home: 'home',\n lock: 'lock',\n back: 'home',\n enter: '\\n',\n };\n await client.execute('mobile: pressButton', [{ name: iosMap[button] }]);\n } else {\n // Android keyevent codes\n const androidMap: Record<DeviceButton, number> = {\n home: 3,\n back: 4,\n enter: 66,\n lock: 26,\n };\n await client.execute('mobile: pressKey', [\n { keycode: androidMap[button] },\n ]);\n }\n }\n\n async dismissKeyboard(): Promise<void> {\n const client = this.#requireClient();\n if (this.platform === 'ios') {\n await client.execute('mobile: hideKeyboard', []);\n } else {\n await client.execute('mobile: hideKeyboard', []);\n }\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n const client = this.#requireClient();\n const action = accept ? 'accept' : 'dismiss';\n await client.execute(`mobile: ${action}Alert`, []);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n const client = this.#requireClient();\n const logType = this.platform === 'ios' ? 'syslog' : 'logcat';\n const raw = await client.execute<Record<string, unknown>[]>(\n 'mobile: getLog',\n [{ type: logType }],\n );\n\n let entries = raw.map((entry) => ({\n timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : '',\n level: typeof entry.level === 'string' ? entry.level : 'info',\n message: typeof entry.message === 'string' ? entry.message : '',\n }));\n\n if (filter) {\n const pattern = new RegExp(filter, 'iu');\n entries = entries.filter((entry) => pattern.test(entry.message));\n }\n\n const cutoff = Date.now() - durationSeconds * 1000;\n entries = entries.filter((entry) => {\n const ts = Number(entry.timestamp);\n return isNaN(ts) || ts >= cutoff;\n });\n\n return { entries, source: `appium ${logType}` };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const client = this.#requireClient();\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await client.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: cx, y: cy },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: durationMs },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await client.releaseActions();\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n const client = this.#requireClient();\n try {\n return await client.execute<string>('mobile: getAlertText', []);\n } catch {\n const snap = await this.snapshot();\n const texts = collectAppiumAlertTexts(snap.hierarchy, this.platform);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n }\n\n async getWindowSize(): Promise<WindowSize> {\n const client = this.#requireClient();\n try {\n const rect = await client.getWindowRect();\n return { width: rect.width, height: rect.height };\n } catch {\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size');\n }\n }\n\n async getContexts(): Promise<string[]> {\n const client = this.#requireClient();\n return client.getContexts();\n }\n\n async setContext(context: string): Promise<void> {\n const client = this.#requireClient();\n await client.setCurrentContext(context);\n }\n\n async getClipboard(): Promise<string> {\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: getClipboard', [\n { contentType: 'plaintext' },\n ]);\n return Buffer.from(b64, 'base64').toString('utf-8');\n }\n\n async setClipboard(text: string): Promise<void> {\n const client = this.#requireClient();\n await client.execute('mobile: setClipboard', [\n {\n content: Buffer.from(text).toString('base64'),\n contentType: 'plaintext',\n },\n ]);\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n if (this.#recording) {\n throw new Error('Screen recording is already in progress');\n }\n const client = this.#requireClient();\n await client.execute('mobile: startRecordingScreen', [{}]);\n this.#recording = true;\n this.#recordingOutputPath = resolveArtifactPath(outputPath, 'recording');\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recording || !this.#recordingOutputPath) {\n throw new Error('No screen recording in progress');\n }\n const client = this.#requireClient();\n const b64 = await client.execute<string>('mobile: stopRecordingScreen', []);\n const data = Buffer.from(b64, 'base64');\n if (data.length === 0) {\n throw new Error('Screen recording returned empty data');\n }\n const outputPath = this.#recordingOutputPath;\n await writeArtifactFile(outputPath, data);\n this.#recording = false;\n this.#recordingOutputPath = null;\n return outputPath;\n }\n\n #requireClient(): WebDriverClient {\n if (!this.#client) {\n throw new Error(\n 'AppiumBackend not connected. Call ensureConnected() first.',\n );\n }\n return this.#client;\n }\n}\n\nfunction collectAppiumAlertTexts(\n elements: UIElement[],\n platform: Platform,\n): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlert =\n platform === 'ios'\n ? el.type === 'Alert' || el.type === 'Sheet'\n : el.type.includes('Dialog');\n if (isAlert) {\n collectAppiumTextValues(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAppiumAlertTexts(el.children, platform);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectAppiumTextValues(elements: UIElement[], texts: string[]): void {\n for (const el of elements) {\n const text = el.value ?? el.label;\n if (\n text &&\n (el.type === 'StaticText' ||\n el.type.includes('TextView') ||\n el.type === 'Text')\n ) {\n texts.push(text);\n }\n if (el.children) {\n collectAppiumTextValues(el.children, texts);\n }\n }\n}\n\nfunction extractElementId(element: Record<string, string>): string {\n // W3C uses 'element-6066-11e4-a52e-4f735466cecf', JSONWP uses 'ELEMENT'\n const id =\n element['element-6066-11e4-a52e-4f735466cecf'] ||\n element.ELEMENT ||\n Object.values(element)[0];\n\n if (!id) {\n throw new Error(\n `Failed to extract element ID from Appium response: ${JSON.stringify(element)}`,\n );\n }\n return id;\n}\n\nexport function parseAppiumAndroidHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n const nodeRegex = /<node\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const element = parseAndroidNodeAttrs(match[1]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseAndroidNodeAttrs(attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const boundsStr = get('bounds');\n if (!boundsStr) {\n return null;\n }\n\n // bounds format: [x1,y1][x2,y2]\n const boundsMatch = boundsStr.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/u);\n if (!boundsMatch) {\n return null;\n }\n\n const x1 = parseInt(boundsMatch[1], 10);\n const y1 = parseInt(boundsMatch[2], 10);\n const x2 = parseInt(boundsMatch[3], 10);\n const y2 = parseInt(boundsMatch[4], 10);\n\n return {\n type: get('class') ?? 'Unknown',\n label: get('content-desc') || undefined,\n value: get('text') || undefined,\n identifier: get('resource-id') || undefined,\n frame: { x: x1, y: y1, width: x2 - x1, height: y2 - y1 },\n enabled: get('enabled') !== 'false',\n };\n}\n\nexport function parseAppiumIosHierarchy(xml: string): UIElement[] {\n const elements: UIElement[] = [];\n // XCUITest XML uses <XCUIElementType* ...> elements\n const nodeRegex = /<(XCUIElementType\\w+)\\s+([^>]+)\\/?>/gu;\n let match;\n\n while ((match = nodeRegex.exec(xml)) !== null) {\n const typeName = match[1];\n const element = parseIosNodeAttrs(typeName, match[2]);\n if (element) {\n elements.push(element);\n }\n }\n\n return elements;\n}\n\nfunction parseIosNodeAttrs(typeName: string, attrs: string): UIElement | null {\n const get = (name: string): string | undefined => {\n const attrMatch = attrs.match(new RegExp(`${name}=\"([^\"]*)\"`, 'u'));\n return attrMatch?.[1] || undefined;\n };\n\n const xStr = get('x');\n const yStr = get('y');\n const widthStr = get('width');\n const heightStr = get('height');\n\n if (!xStr || !yStr || !widthStr || !heightStr) {\n return null;\n }\n\n const shortType = typeName.replace(/^XCUIElementType/u, '');\n\n return {\n type: shortType,\n label: get('label') || undefined,\n value: get('value') || undefined,\n identifier: get('name') || undefined,\n frame: {\n x: parseInt(xStr, 10),\n y: parseInt(yStr, 10),\n width: parseInt(widthStr, 10),\n height: parseInt(heightStr, 10),\n },\n enabled: get('enabled') !== 'false',\n };\n}\n"]} |
@@ -19,5 +19,7 @@ "use strict"; | ||
| const node_child_process_1 = require("node:child_process"); | ||
| const promises_1 = require("node:fs/promises"); | ||
| const alert_labels_js_1 = require("../utils/alert-labels.cjs"); | ||
| const element_js_1 = require("../utils/element.cjs"); | ||
| const exec_js_1 = require("../utils/exec.cjs"); | ||
| const output_path_js_1 = require("../utils/output-path.cjs"); | ||
| const platform_js_1 = require("../utils/platform.cjs"); | ||
@@ -187,8 +189,12 @@ class IdbBackend { | ||
| } | ||
| async screenshot(outputPath) { | ||
| async screenshot(outputPath, options) { | ||
| await this.ensureConnected(); | ||
| const path = outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`; | ||
| const path = (0, output_path_js_1.resolveArtifactPath)(outputPath, 'screenshot'); | ||
| await (0, exec_js_1.execStrict)(__classPrivateFieldGet(this, _IdbBackend_idbPath, "f"), ['screenshot', path, '--udid', __classPrivateFieldGet(this, _IdbBackend_udid, "f")]); | ||
| const data = await (0, exec_js_1.execStrict)('base64', [path]); | ||
| return { data: data.trim(), format: 'png', path }; | ||
| (0, output_path_js_1.hardenArtifactFile)(path); | ||
| if (options?.encode === false) { | ||
| return { data: undefined, format: 'png', path }; | ||
| } | ||
| const data = await (0, promises_1.readFile)(path, 'base64'); | ||
| return { data, format: 'png', path }; | ||
| } | ||
@@ -396,3 +402,3 @@ async openApp(bundleId) { | ||
| } | ||
| const path = outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`; | ||
| const path = (0, output_path_js_1.resolveArtifactPath)(outputPath, 'recording'); | ||
| __classPrivateFieldSet(this, _IdbBackend_recordingPath, path, "f"); | ||
@@ -419,2 +425,3 @@ __classPrivateFieldSet(this, _IdbBackend_recordingProcess, (0, node_child_process_1.spawn)(__classPrivateFieldGet(this, _IdbBackend_idbPath, "f"), [ | ||
| __classPrivateFieldSet(this, _IdbBackend_recordingPath, null, "f"); | ||
| (0, output_path_js_1.hardenArtifactFile)(path); | ||
| return path; | ||
@@ -421,0 +428,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"idb-backend.cjs","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as execFileCb, spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, exec } from '../utils/exec.js';\nimport { resolveIdbPath } from '../utils/platform.js';\n\nexport class IdbBackend implements DeviceBackend {\n readonly platform = 'ios' as const;\n\n #connected = false;\n\n readonly #udid: string;\n\n #idbPath = 'idb';\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n constructor(udid: string) {\n this.#udid = udid;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#connected) {\n return;\n }\n\n this.#idbPath = await resolveIdbPath();\n await exec(this.#idbPath, ['connect', this.#udid]);\n this.#connected = true;\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n let info: Record<string, unknown>;\n try {\n info = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(\n `idb describe returned invalid JSON for device ${this.#udid}: ${raw.slice(0, 200)}`,\n );\n }\n return {\n platform: 'ios',\n deviceId: this.#udid,\n name: (info.name as string) ?? 'Unknown',\n osVersion: (info.os_version as string) ?? 'Unknown',\n state: (info.state as string) ?? 'Unknown',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'ui',\n 'describe-all',\n '--udid',\n this.#udid,\n ]);\n const hierarchy = parseIdbHierarchy(raw);\n return {\n platform: 'ios',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(x),\n String(y),\n '--udid',\n this.#udid,\n ]);\n }\n\n async typeText(text: string): Promise<void> {\n await this.ensureConnected();\n await exec(this.#idbPath, [\n 'ui',\n 'key-sequence',\n '--',\n 'cmd+a',\n '--udid',\n this.#udid,\n ]);\n await exec(this.#idbPath, ['ui', 'key', 'DELETE', '--udid', this.#udid]);\n await execStrict(this.#idbPath, ['ui', 'text', text, '--udid', this.#udid]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n await this.ensureConnected();\n const d = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n\n await execStrict(this.#idbPath, [\n 'ui',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n '--udid',\n this.#udid,\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n await this.ensureConnected();\n try {\n const raw = await execStrict(this.#idbPath, [\n 'list-apps',\n '--udid',\n this.#udid,\n ]);\n\n for (const line of raw.trim().split('\\n')) {\n if (line.includes(bundleId)) {\n const parts = line.split('|').map((s) => s.trim());\n return {\n bundleId,\n state: parts[2] ?? 'Unknown',\n pid: parts[3] ? parseInt(parts[3], 10) : undefined,\n };\n }\n }\n\n return { bundleId, state: 'Not Installed' };\n } catch {\n return this.#getAppStateViaSimctl(bundleId);\n }\n }\n\n async #getAppStateViaSimctl(bundleId: string): Promise<AppStateResult> {\n const result = await exec('xcrun', ['simctl', 'listapps', this.#udid]);\n if (result.exitCode === 0 && result.stdout.includes(bundleId)) {\n return { bundleId, state: 'Running' };\n }\n return { bundleId, state: 'Not Installed' };\n }\n\n async screenshot(outputPath?: string): Promise<ScreenshotResult> {\n await this.ensureConnected();\n const path = outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`;\n await execStrict(this.#idbPath, ['screenshot', path, '--udid', this.#udid]);\n const data = await execStrict('base64', [path]);\n return { data: data.trim(), format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'launch',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'launch', this.#udid, bundleId]);\n }\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'terminate',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'terminate', this.#udid, bundleId]);\n }\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n await this.ensureConnected();\n const keyMap: Record<DeviceButton, string> = {\n home: 'HOME',\n lock: 'LOCK',\n back: 'HOME',\n enter: 'RETURN',\n };\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n keyMap[button],\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n 'RETURN',\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n await this.ensureConnected();\n const args = [\n 'log',\n '--udid',\n this.#udid,\n '--',\n '--last',\n `${durationSeconds}s`,\n ];\n if (filter) {\n args.push('--predicate', `eventMessage CONTAINS \"${filter}\"`);\n }\n const raw = await execStrict(this.#idbPath, args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => ({\n timestamp: line.substring(0, 31).trim(),\n level: 'info',\n message: line.substring(31).trim(),\n }));\n return { entries, source: 'idb log' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(cx),\n String(cy),\n '--duration',\n String(durationMs / 1000),\n '--udid',\n this.#udid,\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n await this.ensureConnected();\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const texts = collectAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n try {\n const info = JSON.parse(raw) as Record<string, unknown>;\n const dims = info.screen_dimensions as\n | { width: number; height: number }\n | undefined;\n if (dims?.width && dims?.height) {\n return { width: dims.width, height: dims.height };\n }\n } catch {\n // fall through to snapshot-based approach\n }\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size from device');\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. IDB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n await this.ensureConnected();\n return execStrict('xcrun', ['simctl', 'pbpaste', this.#udid]);\n }\n\n async setClipboard(text: string): Promise<void> {\n await this.ensureConnected();\n await new Promise<void>((resolve, reject) => {\n const proc = execFileCb(\n 'xcrun',\n ['simctl', 'pbcopy', this.#udid],\n { timeout: 30_000 },\n (error) => (error ? reject(new Error(error.message)) : resolve()),\n );\n proc.stdin?.end(text);\n });\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n await this.ensureConnected();\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n const path = outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`;\n this.#recordingPath = path;\n this.#recordingProcess = spawn(this.#idbPath, [\n 'record-video',\n path,\n '--udid',\n this.#udid,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const path = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 1000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n return path;\n }\n}\n\nfunction collectAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlertContainer =\n el.type === 'Alert' || el.type === 'Sheet' || el.type.includes('Alert');\n if (isAlertContainer) {\n collectTextsFromChildren(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextsFromChildren(\n elements: UIElement[],\n texts: string[],\n): void {\n for (const el of elements) {\n if (el.type === 'StaticText' || el.type === 'TextView') {\n const text = el.value ?? el.label;\n if (text) {\n texts.push(text);\n }\n }\n if (el.children) {\n collectTextsFromChildren(el.children, texts);\n }\n }\n}\n\nexport function parseIdbHierarchy(raw: string): UIElement[] {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.map(mapIdbElement);\n }\n if (parsed.children) {\n return parsed.children.map(mapIdbElement);\n }\n return [mapIdbElement(parsed)];\n } catch {\n return [\n {\n type: 'raw',\n label: raw.slice(0, 200),\n frame: { x: 0, y: 0, width: 0, height: 0 },\n enabled: true,\n },\n ];\n }\n}\n\nexport function mapIdbElement(node: Record<string, unknown>): UIElement {\n const frame = node.frame as\n | { x: number; y: number; width: number; height: number }\n | undefined;\n\n return {\n type: (node.type as string) ?? (node.AXType as string) ?? 'Unknown',\n label: (node.AXLabel as string) ?? (node.label as string) ?? undefined,\n value: (node.AXValue as string) ?? (node.value as string) ?? undefined,\n identifier:\n (node.AXUniqueId as string) ?? (node.identifier as string) ?? undefined,\n frame: frame ?? { x: 0, y: 0, width: 0, height: 0 },\n enabled: (node.enabled as boolean) ?? true,\n children: Array.isArray(node.children)\n ? node.children.map(mapIdbElement)\n : undefined,\n };\n}\n"]} | ||
| {"version":3,"file":"idb-backend.cjs","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as execFileCb, spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, exec } from '../utils/exec.js';\nimport {\n hardenArtifactFile,\n resolveArtifactPath,\n} from '../utils/output-path.js';\nimport { resolveIdbPath } from '../utils/platform.js';\n\nexport class IdbBackend implements DeviceBackend {\n readonly platform = 'ios' as const;\n\n #connected = false;\n\n readonly #udid: string;\n\n #idbPath = 'idb';\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n constructor(udid: string) {\n this.#udid = udid;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#connected) {\n return;\n }\n\n this.#idbPath = await resolveIdbPath();\n await exec(this.#idbPath, ['connect', this.#udid]);\n this.#connected = true;\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n let info: Record<string, unknown>;\n try {\n info = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(\n `idb describe returned invalid JSON for device ${this.#udid}: ${raw.slice(0, 200)}`,\n );\n }\n return {\n platform: 'ios',\n deviceId: this.#udid,\n name: (info.name as string) ?? 'Unknown',\n osVersion: (info.os_version as string) ?? 'Unknown',\n state: (info.state as string) ?? 'Unknown',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'ui',\n 'describe-all',\n '--udid',\n this.#udid,\n ]);\n const hierarchy = parseIdbHierarchy(raw);\n return {\n platform: 'ios',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(x),\n String(y),\n '--udid',\n this.#udid,\n ]);\n }\n\n async typeText(text: string): Promise<void> {\n await this.ensureConnected();\n await exec(this.#idbPath, [\n 'ui',\n 'key-sequence',\n '--',\n 'cmd+a',\n '--udid',\n this.#udid,\n ]);\n await exec(this.#idbPath, ['ui', 'key', 'DELETE', '--udid', this.#udid]);\n await execStrict(this.#idbPath, ['ui', 'text', text, '--udid', this.#udid]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n await this.ensureConnected();\n const d = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n\n await execStrict(this.#idbPath, [\n 'ui',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n '--udid',\n this.#udid,\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n await this.ensureConnected();\n try {\n const raw = await execStrict(this.#idbPath, [\n 'list-apps',\n '--udid',\n this.#udid,\n ]);\n\n for (const line of raw.trim().split('\\n')) {\n if (line.includes(bundleId)) {\n const parts = line.split('|').map((s) => s.trim());\n return {\n bundleId,\n state: parts[2] ?? 'Unknown',\n pid: parts[3] ? parseInt(parts[3], 10) : undefined,\n };\n }\n }\n\n return { bundleId, state: 'Not Installed' };\n } catch {\n return this.#getAppStateViaSimctl(bundleId);\n }\n }\n\n async #getAppStateViaSimctl(bundleId: string): Promise<AppStateResult> {\n const result = await exec('xcrun', ['simctl', 'listapps', this.#udid]);\n if (result.exitCode === 0 && result.stdout.includes(bundleId)) {\n return { bundleId, state: 'Running' };\n }\n return { bundleId, state: 'Not Installed' };\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n await this.ensureConnected();\n const path = resolveArtifactPath(outputPath, 'screenshot');\n await execStrict(this.#idbPath, ['screenshot', path, '--udid', this.#udid]);\n hardenArtifactFile(path);\n if (options?.encode === false) {\n return { data: undefined, format: 'png', path };\n }\n const data = await readFile(path, 'base64');\n return { data, format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'launch',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'launch', this.#udid, bundleId]);\n }\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'terminate',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'terminate', this.#udid, bundleId]);\n }\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n await this.ensureConnected();\n const keyMap: Record<DeviceButton, string> = {\n home: 'HOME',\n lock: 'LOCK',\n back: 'HOME',\n enter: 'RETURN',\n };\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n keyMap[button],\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n 'RETURN',\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n await this.ensureConnected();\n const args = [\n 'log',\n '--udid',\n this.#udid,\n '--',\n '--last',\n `${durationSeconds}s`,\n ];\n if (filter) {\n args.push('--predicate', `eventMessage CONTAINS \"${filter}\"`);\n }\n const raw = await execStrict(this.#idbPath, args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => ({\n timestamp: line.substring(0, 31).trim(),\n level: 'info',\n message: line.substring(31).trim(),\n }));\n return { entries, source: 'idb log' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(cx),\n String(cy),\n '--duration',\n String(durationMs / 1000),\n '--udid',\n this.#udid,\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n await this.ensureConnected();\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const texts = collectAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n try {\n const info = JSON.parse(raw) as Record<string, unknown>;\n const dims = info.screen_dimensions as\n | { width: number; height: number }\n | undefined;\n if (dims?.width && dims?.height) {\n return { width: dims.width, height: dims.height };\n }\n } catch {\n // fall through to snapshot-based approach\n }\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size from device');\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. IDB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n await this.ensureConnected();\n return execStrict('xcrun', ['simctl', 'pbpaste', this.#udid]);\n }\n\n async setClipboard(text: string): Promise<void> {\n await this.ensureConnected();\n await new Promise<void>((resolve, reject) => {\n const proc = execFileCb(\n 'xcrun',\n ['simctl', 'pbcopy', this.#udid],\n { timeout: 30_000 },\n (error) => (error ? reject(new Error(error.message)) : resolve()),\n );\n proc.stdin?.end(text);\n });\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n await this.ensureConnected();\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n const path = resolveArtifactPath(outputPath, 'recording');\n this.#recordingPath = path;\n this.#recordingProcess = spawn(this.#idbPath, [\n 'record-video',\n path,\n '--udid',\n this.#udid,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const path = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 1000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n hardenArtifactFile(path);\n return path;\n }\n}\n\nfunction collectAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlertContainer =\n el.type === 'Alert' || el.type === 'Sheet' || el.type.includes('Alert');\n if (isAlertContainer) {\n collectTextsFromChildren(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextsFromChildren(\n elements: UIElement[],\n texts: string[],\n): void {\n for (const el of elements) {\n if (el.type === 'StaticText' || el.type === 'TextView') {\n const text = el.value ?? el.label;\n if (text) {\n texts.push(text);\n }\n }\n if (el.children) {\n collectTextsFromChildren(el.children, texts);\n }\n }\n}\n\nexport function parseIdbHierarchy(raw: string): UIElement[] {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.map(mapIdbElement);\n }\n if (parsed.children) {\n return parsed.children.map(mapIdbElement);\n }\n return [mapIdbElement(parsed)];\n } catch {\n return [\n {\n type: 'raw',\n label: raw.slice(0, 200),\n frame: { x: 0, y: 0, width: 0, height: 0 },\n enabled: true,\n },\n ];\n }\n}\n\nexport function mapIdbElement(node: Record<string, unknown>): UIElement {\n const frame = node.frame as\n | { x: number; y: number; width: number; height: number }\n | undefined;\n\n return {\n type: (node.type as string) ?? (node.AXType as string) ?? 'Unknown',\n label: (node.AXLabel as string) ?? (node.label as string) ?? undefined,\n value: (node.AXValue as string) ?? (node.value as string) ?? undefined,\n identifier:\n (node.AXUniqueId as string) ?? (node.identifier as string) ?? undefined,\n frame: frame ?? { x: 0, y: 0, width: 0, height: 0 },\n enabled: (node.enabled as boolean) ?? true,\n children: Array.isArray(node.children)\n ? node.children.map(mapIdbElement)\n : undefined,\n };\n}\n"]} |
@@ -1,2 +0,2 @@ | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.cjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.cjs"; | ||
| export declare class IdbBackend implements DeviceBackend { | ||
@@ -16,3 +16,9 @@ #private; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(outputPath?: string): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -19,0 +25,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"idb-backend.d.cts","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,UAAU,EACX,oBAAmB;AAUpB,qBAAa,UAAW,YAAW,aAAa;;IAC9C,QAAQ,CAAC,QAAQ,EAAG,KAAK,CAAU;gBAYvB,IAAI,EAAE,MAAM;IAIlB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAUhC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAyBpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAiBnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAuBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAapD,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAmBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkCtD,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAQ1D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAWhC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAgCrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAsBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAa/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IA6BpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAK/B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazC,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAW7C;AAsCD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAoB1D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAiBtE"} | ||
| {"version":3,"file":"idb-backend.d.cts","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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"} |
@@ -1,2 +0,2 @@ | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.mjs"; | ||
| import type { DeviceBackend, DeviceButton, DeviceInfo, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, ElementQuery, UIElement, WindowSize } from "./types.mjs"; | ||
| export declare class IdbBackend implements DeviceBackend { | ||
@@ -16,3 +16,9 @@ #private; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(outputPath?: string): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -19,0 +25,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"idb-backend.d.mts","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,YAAY,EACZ,SAAS,EACT,UAAU,EACX,oBAAmB;AAUpB,qBAAa,UAAW,YAAW,aAAa;;IAC9C,QAAQ,CAAC,QAAQ,EAAG,KAAK,CAAU;gBAYvB,IAAI,EAAE,MAAM;IAIlB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAUhC,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IAyBpC,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAiBnC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAuBnD,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAapD,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrC,KAAK,CACT,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAmBV,cAAc,CAClB,KAAK,EAAE,YAAY,EACnB,SAAS,SAAS,EAClB,UAAU,SAAM,GACf,OAAO,CAAC,SAAS,CAAC;IAef,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkCtD,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAQ1D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczC,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBhD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAWhC,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5C,OAAO,CAAC,eAAe,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BnE,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,SAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAgCrE,eAAe,CACnB,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,IAAI,GAAG,MAAe,EACjC,WAAW,SAAK,GACf,OAAO,CAAC,SAAS,CAAC;IAsBf,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAa/B,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;IA6BpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAK/B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazC,oBAAoB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAW7C;AAsCD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAoB1D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAiBtE"} | ||
| {"version":3,"file":"idb-backend.d.mts","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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"} |
@@ -14,5 +14,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
| import { execFile as execFileCb, spawn } from "node:child_process"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { ACCEPT_LABELS, DENY_LABELS } from "../utils/alert-labels.mjs"; | ||
| import { findElement, describeElement, computeSwipeEnd } from "../utils/element.mjs"; | ||
| import { execStrict, exec } from "../utils/exec.mjs"; | ||
| import { hardenArtifactFile, resolveArtifactPath } from "../utils/output-path.mjs"; | ||
| import { resolveIdbPath } from "../utils/platform.mjs"; | ||
@@ -182,8 +184,12 @@ export class IdbBackend { | ||
| } | ||
| async screenshot(outputPath) { | ||
| async screenshot(outputPath, options) { | ||
| await this.ensureConnected(); | ||
| const path = outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`; | ||
| const path = resolveArtifactPath(outputPath, 'screenshot'); | ||
| await execStrict(__classPrivateFieldGet(this, _IdbBackend_idbPath, "f"), ['screenshot', path, '--udid', __classPrivateFieldGet(this, _IdbBackend_udid, "f")]); | ||
| const data = await execStrict('base64', [path]); | ||
| return { data: data.trim(), format: 'png', path }; | ||
| hardenArtifactFile(path); | ||
| if (options?.encode === false) { | ||
| return { data: undefined, format: 'png', path }; | ||
| } | ||
| const data = await readFile(path, 'base64'); | ||
| return { data, format: 'png', path }; | ||
| } | ||
@@ -391,3 +397,3 @@ async openApp(bundleId) { | ||
| } | ||
| const path = outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`; | ||
| const path = resolveArtifactPath(outputPath, 'recording'); | ||
| __classPrivateFieldSet(this, _IdbBackend_recordingPath, path, "f"); | ||
@@ -414,2 +420,3 @@ __classPrivateFieldSet(this, _IdbBackend_recordingProcess, spawn(__classPrivateFieldGet(this, _IdbBackend_idbPath, "f"), [ | ||
| __classPrivateFieldSet(this, _IdbBackend_recordingPath, null, "f"); | ||
| hardenArtifactFile(path); | ||
| return path; | ||
@@ -416,0 +423,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"idb-backend.mjs","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as execFileCb, spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, exec } from '../utils/exec.js';\nimport { resolveIdbPath } from '../utils/platform.js';\n\nexport class IdbBackend implements DeviceBackend {\n readonly platform = 'ios' as const;\n\n #connected = false;\n\n readonly #udid: string;\n\n #idbPath = 'idb';\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n constructor(udid: string) {\n this.#udid = udid;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#connected) {\n return;\n }\n\n this.#idbPath = await resolveIdbPath();\n await exec(this.#idbPath, ['connect', this.#udid]);\n this.#connected = true;\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n let info: Record<string, unknown>;\n try {\n info = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(\n `idb describe returned invalid JSON for device ${this.#udid}: ${raw.slice(0, 200)}`,\n );\n }\n return {\n platform: 'ios',\n deviceId: this.#udid,\n name: (info.name as string) ?? 'Unknown',\n osVersion: (info.os_version as string) ?? 'Unknown',\n state: (info.state as string) ?? 'Unknown',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'ui',\n 'describe-all',\n '--udid',\n this.#udid,\n ]);\n const hierarchy = parseIdbHierarchy(raw);\n return {\n platform: 'ios',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(x),\n String(y),\n '--udid',\n this.#udid,\n ]);\n }\n\n async typeText(text: string): Promise<void> {\n await this.ensureConnected();\n await exec(this.#idbPath, [\n 'ui',\n 'key-sequence',\n '--',\n 'cmd+a',\n '--udid',\n this.#udid,\n ]);\n await exec(this.#idbPath, ['ui', 'key', 'DELETE', '--udid', this.#udid]);\n await execStrict(this.#idbPath, ['ui', 'text', text, '--udid', this.#udid]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n await this.ensureConnected();\n const d = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n\n await execStrict(this.#idbPath, [\n 'ui',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n '--udid',\n this.#udid,\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n await this.ensureConnected();\n try {\n const raw = await execStrict(this.#idbPath, [\n 'list-apps',\n '--udid',\n this.#udid,\n ]);\n\n for (const line of raw.trim().split('\\n')) {\n if (line.includes(bundleId)) {\n const parts = line.split('|').map((s) => s.trim());\n return {\n bundleId,\n state: parts[2] ?? 'Unknown',\n pid: parts[3] ? parseInt(parts[3], 10) : undefined,\n };\n }\n }\n\n return { bundleId, state: 'Not Installed' };\n } catch {\n return this.#getAppStateViaSimctl(bundleId);\n }\n }\n\n async #getAppStateViaSimctl(bundleId: string): Promise<AppStateResult> {\n const result = await exec('xcrun', ['simctl', 'listapps', this.#udid]);\n if (result.exitCode === 0 && result.stdout.includes(bundleId)) {\n return { bundleId, state: 'Running' };\n }\n return { bundleId, state: 'Not Installed' };\n }\n\n async screenshot(outputPath?: string): Promise<ScreenshotResult> {\n await this.ensureConnected();\n const path = outputPath ?? `/tmp/device-mcp-screenshot-${Date.now()}.png`;\n await execStrict(this.#idbPath, ['screenshot', path, '--udid', this.#udid]);\n const data = await execStrict('base64', [path]);\n return { data: data.trim(), format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'launch',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'launch', this.#udid, bundleId]);\n }\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'terminate',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'terminate', this.#udid, bundleId]);\n }\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n await this.ensureConnected();\n const keyMap: Record<DeviceButton, string> = {\n home: 'HOME',\n lock: 'LOCK',\n back: 'HOME',\n enter: 'RETURN',\n };\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n keyMap[button],\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n 'RETURN',\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n await this.ensureConnected();\n const args = [\n 'log',\n '--udid',\n this.#udid,\n '--',\n '--last',\n `${durationSeconds}s`,\n ];\n if (filter) {\n args.push('--predicate', `eventMessage CONTAINS \"${filter}\"`);\n }\n const raw = await execStrict(this.#idbPath, args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => ({\n timestamp: line.substring(0, 31).trim(),\n level: 'info',\n message: line.substring(31).trim(),\n }));\n return { entries, source: 'idb log' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(cx),\n String(cy),\n '--duration',\n String(durationMs / 1000),\n '--udid',\n this.#udid,\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n await this.ensureConnected();\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const texts = collectAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n try {\n const info = JSON.parse(raw) as Record<string, unknown>;\n const dims = info.screen_dimensions as\n | { width: number; height: number }\n | undefined;\n if (dims?.width && dims?.height) {\n return { width: dims.width, height: dims.height };\n }\n } catch {\n // fall through to snapshot-based approach\n }\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size from device');\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. IDB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n await this.ensureConnected();\n return execStrict('xcrun', ['simctl', 'pbpaste', this.#udid]);\n }\n\n async setClipboard(text: string): Promise<void> {\n await this.ensureConnected();\n await new Promise<void>((resolve, reject) => {\n const proc = execFileCb(\n 'xcrun',\n ['simctl', 'pbcopy', this.#udid],\n { timeout: 30_000 },\n (error) => (error ? reject(new Error(error.message)) : resolve()),\n );\n proc.stdin?.end(text);\n });\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n await this.ensureConnected();\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n const path = outputPath ?? `/tmp/device-mcp-recording-${Date.now()}.mp4`;\n this.#recordingPath = path;\n this.#recordingProcess = spawn(this.#idbPath, [\n 'record-video',\n path,\n '--udid',\n this.#udid,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const path = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 1000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n return path;\n }\n}\n\nfunction collectAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlertContainer =\n el.type === 'Alert' || el.type === 'Sheet' || el.type.includes('Alert');\n if (isAlertContainer) {\n collectTextsFromChildren(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextsFromChildren(\n elements: UIElement[],\n texts: string[],\n): void {\n for (const el of elements) {\n if (el.type === 'StaticText' || el.type === 'TextView') {\n const text = el.value ?? el.label;\n if (text) {\n texts.push(text);\n }\n }\n if (el.children) {\n collectTextsFromChildren(el.children, texts);\n }\n }\n}\n\nexport function parseIdbHierarchy(raw: string): UIElement[] {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.map(mapIdbElement);\n }\n if (parsed.children) {\n return parsed.children.map(mapIdbElement);\n }\n return [mapIdbElement(parsed)];\n } catch {\n return [\n {\n type: 'raw',\n label: raw.slice(0, 200),\n frame: { x: 0, y: 0, width: 0, height: 0 },\n enabled: true,\n },\n ];\n }\n}\n\nexport function mapIdbElement(node: Record<string, unknown>): UIElement {\n const frame = node.frame as\n | { x: number; y: number; width: number; height: number }\n | undefined;\n\n return {\n type: (node.type as string) ?? (node.AXType as string) ?? 'Unknown',\n label: (node.AXLabel as string) ?? (node.label as string) ?? undefined,\n value: (node.AXValue as string) ?? (node.value as string) ?? undefined,\n identifier:\n (node.AXUniqueId as string) ?? (node.identifier as string) ?? undefined,\n frame: frame ?? { x: 0, y: 0, width: 0, height: 0 },\n enabled: (node.enabled as boolean) ?? true,\n children: Array.isArray(node.children)\n ? node.children.map(mapIdbElement)\n : undefined,\n };\n}\n"]} | ||
| {"version":3,"file":"idb-backend.mjs","sourceRoot":"","sources":["../../src/backends/idb-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile as execFileCb, spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\n\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n WindowSize,\n} from './types.js';\nimport { ACCEPT_LABELS, DENY_LABELS } from '../utils/alert-labels.js';\nimport {\n findElement,\n describeElement,\n computeSwipeEnd,\n} from '../utils/element.js';\nimport { execStrict, exec } from '../utils/exec.js';\nimport {\n hardenArtifactFile,\n resolveArtifactPath,\n} from '../utils/output-path.js';\nimport { resolveIdbPath } from '../utils/platform.js';\n\nexport class IdbBackend implements DeviceBackend {\n readonly platform = 'ios' as const;\n\n #connected = false;\n\n readonly #udid: string;\n\n #idbPath = 'idb';\n\n #recordingProcess: ChildProcess | null = null;\n\n #recordingPath: string | null = null;\n\n constructor(udid: string) {\n this.#udid = udid;\n }\n\n async ensureConnected(): Promise<void> {\n if (this.#connected) {\n return;\n }\n\n this.#idbPath = await resolveIdbPath();\n await exec(this.#idbPath, ['connect', this.#udid]);\n this.#connected = true;\n }\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n let info: Record<string, unknown>;\n try {\n info = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(\n `idb describe returned invalid JSON for device ${this.#udid}: ${raw.slice(0, 200)}`,\n );\n }\n return {\n platform: 'ios',\n deviceId: this.#udid,\n name: (info.name as string) ?? 'Unknown',\n osVersion: (info.os_version as string) ?? 'Unknown',\n state: (info.state as string) ?? 'Unknown',\n };\n }\n\n async snapshot(): Promise<SnapshotResult> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'ui',\n 'describe-all',\n '--udid',\n this.#udid,\n ]);\n const hierarchy = parseIdbHierarchy(raw);\n return {\n platform: 'ios',\n hierarchy,\n raw,\n timestamp: Date.now(),\n };\n }\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const x = Math.round(element.frame.x + element.frame.width / 2);\n const y = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(x, y);\n\n return {\n success: true,\n x,\n y,\n targetDescription: describeElement(element),\n };\n }\n\n async getElementText(query: ElementQuery): Promise<string> {\n await this.ensureConnected();\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return element.label ?? element.value ?? '';\n }\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(x),\n String(y),\n '--udid',\n this.#udid,\n ]);\n }\n\n async typeText(text: string): Promise<void> {\n await this.ensureConnected();\n await exec(this.#idbPath, [\n 'ui',\n 'key-sequence',\n '--',\n 'cmd+a',\n '--udid',\n this.#udid,\n ]);\n await exec(this.#idbPath, ['ui', 'key', 'DELETE', '--udid', this.#udid]);\n await execStrict(this.#idbPath, ['ui', 'text', text, '--udid', this.#udid]);\n }\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n await this.ensureConnected();\n const d = distance ?? 500;\n const sx = startX ?? 200;\n const sy = startY ?? 400;\n const [endX, endY] = computeSwipeEnd(sx, sy, direction, d);\n\n await execStrict(this.#idbPath, [\n 'ui',\n 'swipe',\n String(sx),\n String(sy),\n String(endX),\n String(endY),\n '--udid',\n this.#udid,\n ]);\n }\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs = 10_000,\n intervalMs = 500,\n ): Promise<UIElement> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const snapshot = await this.snapshot();\n const element = findElement(snapshot.hierarchy, query);\n if (element) {\n return element;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n throw new Error(\n `Timed out waiting for element: ${JSON.stringify(query)} (${timeoutMs}ms)`,\n );\n }\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n await this.ensureConnected();\n try {\n const raw = await execStrict(this.#idbPath, [\n 'list-apps',\n '--udid',\n this.#udid,\n ]);\n\n for (const line of raw.trim().split('\\n')) {\n if (line.includes(bundleId)) {\n const parts = line.split('|').map((s) => s.trim());\n return {\n bundleId,\n state: parts[2] ?? 'Unknown',\n pid: parts[3] ? parseInt(parts[3], 10) : undefined,\n };\n }\n }\n\n return { bundleId, state: 'Not Installed' };\n } catch {\n return this.#getAppStateViaSimctl(bundleId);\n }\n }\n\n async #getAppStateViaSimctl(bundleId: string): Promise<AppStateResult> {\n const result = await exec('xcrun', ['simctl', 'listapps', this.#udid]);\n if (result.exitCode === 0 && result.stdout.includes(bundleId)) {\n return { bundleId, state: 'Running' };\n }\n return { bundleId, state: 'Not Installed' };\n }\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n async screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n await this.ensureConnected();\n const path = resolveArtifactPath(outputPath, 'screenshot');\n await execStrict(this.#idbPath, ['screenshot', path, '--udid', this.#udid]);\n hardenArtifactFile(path);\n if (options?.encode === false) {\n return { data: undefined, format: 'png', path };\n }\n const data = await readFile(path, 'base64');\n return { data, format: 'png', path };\n }\n\n async openApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'launch',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'launch', this.#udid, bundleId]);\n }\n }\n\n async closeApp(bundleId: string): Promise<void> {\n await this.ensureConnected();\n try {\n await execStrict(this.#idbPath, [\n 'terminate',\n bundleId,\n '--udid',\n this.#udid,\n ]);\n } catch {\n await execStrict('xcrun', ['simctl', 'terminate', this.#udid, bundleId]);\n }\n }\n\n async pressButton(button: DeviceButton): Promise<void> {\n await this.ensureConnected();\n const keyMap: Record<DeviceButton, string> = {\n home: 'HOME',\n lock: 'LOCK',\n back: 'HOME',\n enter: 'RETURN',\n };\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n keyMap[button],\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissKeyboard(): Promise<void> {\n await this.ensureConnected();\n await execStrict(this.#idbPath, [\n 'ui',\n 'key',\n 'RETURN',\n '--udid',\n this.#udid,\n ]);\n }\n\n async dismissAlert(accept: boolean): Promise<void> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const candidates = accept ? ACCEPT_LABELS : DENY_LABELS;\n\n for (const label of candidates) {\n const element = findElement(snap.hierarchy, { label });\n if (element) {\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await this.tapCoordinates(cx, cy);\n return;\n }\n }\n\n throw new Error(`No alert button found. Tried: ${candidates.join(', ')}`);\n }\n\n async getLogs(durationSeconds = 30, filter?: string): Promise<LogsResult> {\n await this.ensureConnected();\n const args = [\n 'log',\n '--udid',\n this.#udid,\n '--',\n '--last',\n `${durationSeconds}s`,\n ];\n if (filter) {\n args.push('--predicate', `eventMessage CONTAINS \"${filter}\"`);\n }\n const raw = await execStrict(this.#idbPath, args);\n const entries = raw\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => ({\n timestamp: line.substring(0, 31).trim(),\n level: 'info',\n message: line.substring(31).trim(),\n }));\n return { entries, source: 'idb log' };\n }\n\n async longPress(query: ElementQuery, durationMs = 1000): Promise<TapResult> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (!element) {\n throw new Error(\n `Element not found: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n const cx = Math.round(element.frame.x + element.frame.width / 2);\n const cy = Math.round(element.frame.y + element.frame.height / 2);\n await execStrict(this.#idbPath, [\n 'ui',\n 'tap',\n String(cx),\n String(cy),\n '--duration',\n String(durationMs / 1000),\n '--udid',\n this.#udid,\n ]);\n\n return {\n success: true,\n x: cx,\n y: cy,\n targetDescription: describeElement(element),\n };\n }\n\n async scrollToElement(\n query: ElementQuery,\n direction: 'up' | 'down' = 'down',\n maxAttempts = 10,\n ): Promise<UIElement> {\n await this.ensureConnected();\n let previousRaw = '';\n\n for (let i = 0; i < maxAttempts; i++) {\n const snap = await this.snapshot();\n const element = findElement(snap.hierarchy, query);\n if (element) {\n return element;\n }\n if (snap.raw === previousRaw) {\n break;\n }\n previousRaw = snap.raw;\n await this.swipe(direction);\n }\n throw new Error(\n `Element not found after scrolling: ${JSON.stringify(query)}\\n` +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n\n async getAlertText(): Promise<string> {\n await this.ensureConnected();\n const snap = await this.snapshot();\n const texts = collectAlertTexts(snap.hierarchy);\n if (texts.length === 0) {\n throw new Error(\n 'No alert is currently displayed.\\n' +\n 'Use device_snapshot to inspect the current UI hierarchy.',\n );\n }\n return texts.join('\\n');\n }\n\n async getWindowSize(): Promise<WindowSize> {\n await this.ensureConnected();\n const raw = await execStrict(this.#idbPath, [\n 'describe',\n '--udid',\n this.#udid,\n '--json',\n ]);\n try {\n const info = JSON.parse(raw) as Record<string, unknown>;\n const dims = info.screen_dimensions as\n | { width: number; height: number }\n | undefined;\n if (dims?.width && dims?.height) {\n return { width: dims.width, height: dims.height };\n }\n } catch {\n // fall through to snapshot-based approach\n }\n const snap = await this.snapshot();\n if (snap.hierarchy.length > 0) {\n const root = snap.hierarchy[0];\n if (root.frame.width > 0 && root.frame.height > 0) {\n return { width: root.frame.width, height: root.frame.height };\n }\n }\n throw new Error('Unable to determine window size from device');\n }\n\n async getContexts(): Promise<string[]> {\n return ['NATIVE_APP'];\n }\n\n async setContext(context: string): Promise<void> {\n if (context === 'NATIVE_APP') {\n return;\n }\n throw new Error(\n 'Context switching requires Appium backend. IDB only supports NATIVE_APP.',\n );\n }\n\n async getClipboard(): Promise<string> {\n await this.ensureConnected();\n return execStrict('xcrun', ['simctl', 'pbpaste', this.#udid]);\n }\n\n async setClipboard(text: string): Promise<void> {\n await this.ensureConnected();\n await new Promise<void>((resolve, reject) => {\n const proc = execFileCb(\n 'xcrun',\n ['simctl', 'pbcopy', this.#udid],\n { timeout: 30_000 },\n (error) => (error ? reject(new Error(error.message)) : resolve()),\n );\n proc.stdin?.end(text);\n });\n }\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n await this.ensureConnected();\n if (this.#recordingProcess) {\n throw new Error('Screen recording is already in progress');\n }\n const path = resolveArtifactPath(outputPath, 'recording');\n this.#recordingPath = path;\n this.#recordingProcess = spawn(this.#idbPath, [\n 'record-video',\n path,\n '--udid',\n this.#udid,\n ]);\n this.#recordingProcess.on('error', () => {\n this.#recordingProcess = null;\n this.#recordingPath = null;\n });\n }\n\n async stopScreenRecording(): Promise<string> {\n if (!this.#recordingProcess || !this.#recordingPath) {\n throw new Error('No screen recording in progress');\n }\n const path = this.#recordingPath;\n this.#recordingProcess.kill('SIGINT');\n await new Promise((resolve) => setTimeout(resolve, 1000));\n this.#recordingProcess = null;\n this.#recordingPath = null;\n hardenArtifactFile(path);\n return path;\n }\n}\n\nfunction collectAlertTexts(elements: UIElement[]): string[] {\n const texts: string[] = [];\n for (const el of elements) {\n const isAlertContainer =\n el.type === 'Alert' || el.type === 'Sheet' || el.type.includes('Alert');\n if (isAlertContainer) {\n collectTextsFromChildren(el.children ?? [], texts);\n return texts;\n }\n if (el.children) {\n const found = collectAlertTexts(el.children);\n if (found.length > 0) {\n return found;\n }\n }\n }\n return texts;\n}\n\nfunction collectTextsFromChildren(\n elements: UIElement[],\n texts: string[],\n): void {\n for (const el of elements) {\n if (el.type === 'StaticText' || el.type === 'TextView') {\n const text = el.value ?? el.label;\n if (text) {\n texts.push(text);\n }\n }\n if (el.children) {\n collectTextsFromChildren(el.children, texts);\n }\n }\n}\n\nexport function parseIdbHierarchy(raw: string): UIElement[] {\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.map(mapIdbElement);\n }\n if (parsed.children) {\n return parsed.children.map(mapIdbElement);\n }\n return [mapIdbElement(parsed)];\n } catch {\n return [\n {\n type: 'raw',\n label: raw.slice(0, 200),\n frame: { x: 0, y: 0, width: 0, height: 0 },\n enabled: true,\n },\n ];\n }\n}\n\nexport function mapIdbElement(node: Record<string, unknown>): UIElement {\n const frame = node.frame as\n | { x: number; y: number; width: number; height: number }\n | undefined;\n\n return {\n type: (node.type as string) ?? (node.AXType as string) ?? 'Unknown',\n label: (node.AXLabel as string) ?? (node.label as string) ?? undefined,\n value: (node.AXValue as string) ?? (node.value as string) ?? undefined,\n identifier:\n (node.AXUniqueId as string) ?? (node.identifier as string) ?? undefined,\n frame: frame ?? { x: 0, y: 0, width: 0, height: 0 },\n enabled: (node.enabled as boolean) ?? true,\n children: Array.isArray(node.children)\n ? node.children.map(mapIdbElement)\n : undefined,\n };\n}\n"]} |
@@ -60,2 +60,5 @@ "use strict"; | ||
| } | ||
| async function screenshot(outputPath, options) { | ||
| return (await resolve()).screenshot(outputPath, options); | ||
| } | ||
| return { | ||
@@ -100,5 +103,3 @@ get platform() { | ||
| }, | ||
| async screenshot(outputPath) { | ||
| return (await resolve()).screenshot(outputPath); | ||
| }, | ||
| screenshot, | ||
| async openApp(bundleId) { | ||
@@ -105,0 +106,0 @@ return (await resolve()).openApp(bundleId); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AdbBackend } from './adb-backend.js';\nimport { AppiumBackend } from './appium-backend.js';\nimport { IdbBackend } from './idb-backend.js';\nimport { readSessionFile } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n LogsResult,\n ScreenshotResult,\n SnapshotResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport {\n detectPlatform,\n detectAllDevices,\n MultipleDevicesError,\n} from '../utils/platform.js';\nimport type { DetectedDevice } from '../utils/platform.js';\n\nexport type { DeviceBackend } from './types.js';\nexport { AdbBackend } from './adb-backend.js';\nexport { AppiumBackend } from './appium-backend.js';\nexport { IdbBackend } from './idb-backend.js';\n\nexport type LazyDeviceBackend = DeviceBackend & {\n selectDevice(deviceId: string): void;\n listDevices(): Promise<DetectedDevice[]>;\n};\n\nexport async function createBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DeviceBackend> {\n const sessionConfig = await readSessionFile();\n\n if (sessionConfig) {\n console.error(\n `Found .device-session (${sessionConfig.mode}, ${sessionConfig.platform})`,\n );\n const backend = new AppiumBackend(sessionConfig);\n await backend.ensureConnected();\n return backend;\n }\n\n const { platform, deviceId } = await detectPlatform(\n explicitDeviceId,\n explicitPlatform,\n );\n\n const backend =\n platform === 'ios' ? new IdbBackend(deviceId) : new AdbBackend(deviceId);\n\n await backend.ensureConnected();\n return backend;\n}\n\nexport function createLazyBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): LazyDeviceBackend {\n let inner: DeviceBackend | null = null;\n let connecting: Promise<DeviceBackend> | null = null;\n let pendingDevices: DetectedDevice[] | null = null;\n let selectedOverride: string | null = null;\n\n async function resolve(): Promise<DeviceBackend> {\n if (inner) {\n return inner;\n }\n\n if (pendingDevices && !selectedOverride) {\n throw new MultipleDevicesError(pendingDevices);\n }\n\n if (!connecting) {\n const deviceId = selectedOverride ?? explicitDeviceId;\n connecting = createBackend(deviceId, explicitPlatform)\n .then((backend) => {\n inner = backend;\n pendingDevices = null;\n return backend;\n })\n .catch((error: unknown) => {\n connecting = null;\n if (error instanceof MultipleDevicesError) {\n pendingDevices = error.devices;\n }\n throw error;\n });\n }\n return connecting;\n }\n\n return {\n get platform(): Platform {\n return inner?.platform ?? 'ios';\n },\n\n selectDevice(deviceId: string): void {\n selectedOverride = deviceId;\n inner = null;\n connecting = null;\n },\n\n async listDevices(): Promise<DetectedDevice[]> {\n if (pendingDevices) {\n return pendingDevices;\n }\n return detectAllDevices(explicitPlatform);\n },\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n return (await resolve()).getDeviceInfo();\n },\n\n async snapshot(): Promise<SnapshotResult> {\n return (await resolve()).snapshot();\n },\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n return (await resolve()).tapElement(query);\n },\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n return (await resolve()).tapCoordinates(x, y);\n },\n\n async typeText(text: string): Promise<void> {\n return (await resolve()).typeText(text);\n },\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n return (await resolve()).swipe(direction, startX, startY, distance);\n },\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement> {\n return (await resolve()).waitForElement(query, timeoutMs, intervalMs);\n },\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n return (await resolve()).getAppState(bundleId);\n },\n\n async screenshot(outputPath?: string): Promise<ScreenshotResult> {\n return (await resolve()).screenshot(outputPath);\n },\n\n async openApp(bundleId: string): Promise<void> {\n return (await resolve()).openApp(bundleId);\n },\n\n async closeApp(bundleId: string): Promise<void> {\n return (await resolve()).closeApp(bundleId);\n },\n\n async pressButton(button: DeviceButton): Promise<void> {\n return (await resolve()).pressButton(button);\n },\n\n async dismissKeyboard(): Promise<void> {\n return (await resolve()).dismissKeyboard();\n },\n\n async dismissAlert(accept: boolean): Promise<void> {\n return (await resolve()).dismissAlert(accept);\n },\n\n async getLogs(\n durationSeconds?: number,\n filter?: string,\n ): Promise<LogsResult> {\n return (await resolve()).getLogs(durationSeconds, filter);\n },\n\n async longPress(\n query: ElementQuery,\n durationMs?: number,\n ): Promise<TapResult> {\n return (await resolve()).longPress(query, durationMs);\n },\n\n async scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement> {\n return (await resolve()).scrollToElement(query, direction, maxAttempts);\n },\n\n async getAlertText(): Promise<string> {\n return (await resolve()).getAlertText();\n },\n\n async getWindowSize(): Promise<WindowSize> {\n return (await resolve()).getWindowSize();\n },\n\n async getContexts(): Promise<string[]> {\n return (await resolve()).getContexts();\n },\n\n async setContext(context: string): Promise<void> {\n return (await resolve()).setContext(context);\n },\n\n async getClipboard(): Promise<string> {\n return (await resolve()).getClipboard();\n },\n\n async setClipboard(text: string): Promise<void> {\n return (await resolve()).setClipboard(text);\n },\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n return (await resolve()).startScreenRecording(outputPath);\n },\n\n async stopScreenRecording(): Promise<string> {\n return (await resolve()).stopScreenRecording();\n },\n\n async getElementText(query: ElementQuery): Promise<string> {\n return (await resolve()).getElementText(query);\n },\n };\n}\n"]} | ||
| {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AdbBackend } from './adb-backend.js';\nimport { AppiumBackend } from './appium-backend.js';\nimport { IdbBackend } from './idb-backend.js';\nimport { readSessionFile } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n LogsResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n SnapshotResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport {\n detectPlatform,\n detectAllDevices,\n MultipleDevicesError,\n} from '../utils/platform.js';\nimport type { DetectedDevice } from '../utils/platform.js';\n\nexport type { DeviceBackend } from './types.js';\nexport { AdbBackend } from './adb-backend.js';\nexport { AppiumBackend } from './appium-backend.js';\nexport { IdbBackend } from './idb-backend.js';\n\nexport type LazyDeviceBackend = DeviceBackend & {\n selectDevice(deviceId: string): void;\n listDevices(): Promise<DetectedDevice[]>;\n};\n\nexport async function createBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DeviceBackend> {\n const sessionConfig = await readSessionFile();\n\n if (sessionConfig) {\n console.error(\n `Found .device-session (${sessionConfig.mode}, ${sessionConfig.platform})`,\n );\n const backend = new AppiumBackend(sessionConfig);\n await backend.ensureConnected();\n return backend;\n }\n\n const { platform, deviceId } = await detectPlatform(\n explicitDeviceId,\n explicitPlatform,\n );\n\n const backend =\n platform === 'ios' ? new IdbBackend(deviceId) : new AdbBackend(deviceId);\n\n await backend.ensureConnected();\n return backend;\n}\n\nexport function createLazyBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): LazyDeviceBackend {\n let inner: DeviceBackend | null = null;\n let connecting: Promise<DeviceBackend> | null = null;\n let pendingDevices: DetectedDevice[] | null = null;\n let selectedOverride: string | null = null;\n\n async function resolve(): Promise<DeviceBackend> {\n if (inner) {\n return inner;\n }\n\n if (pendingDevices && !selectedOverride) {\n throw new MultipleDevicesError(pendingDevices);\n }\n\n if (!connecting) {\n const deviceId = selectedOverride ?? explicitDeviceId;\n connecting = createBackend(deviceId, explicitPlatform)\n .then((backend) => {\n inner = backend;\n pendingDevices = null;\n return backend;\n })\n .catch((error: unknown) => {\n connecting = null;\n if (error instanceof MultipleDevicesError) {\n pendingDevices = error.devices;\n }\n throw error;\n });\n }\n return connecting;\n }\n\n function screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n function screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n function screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n async function screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n return (await resolve()).screenshot(outputPath, options);\n }\n\n return {\n get platform(): Platform {\n return inner?.platform ?? 'ios';\n },\n\n selectDevice(deviceId: string): void {\n selectedOverride = deviceId;\n inner = null;\n connecting = null;\n },\n\n async listDevices(): Promise<DetectedDevice[]> {\n if (pendingDevices) {\n return pendingDevices;\n }\n return detectAllDevices(explicitPlatform);\n },\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n return (await resolve()).getDeviceInfo();\n },\n\n async snapshot(): Promise<SnapshotResult> {\n return (await resolve()).snapshot();\n },\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n return (await resolve()).tapElement(query);\n },\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n return (await resolve()).tapCoordinates(x, y);\n },\n\n async typeText(text: string): Promise<void> {\n return (await resolve()).typeText(text);\n },\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n return (await resolve()).swipe(direction, startX, startY, distance);\n },\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement> {\n return (await resolve()).waitForElement(query, timeoutMs, intervalMs);\n },\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n return (await resolve()).getAppState(bundleId);\n },\n\n screenshot,\n\n async openApp(bundleId: string): Promise<void> {\n return (await resolve()).openApp(bundleId);\n },\n\n async closeApp(bundleId: string): Promise<void> {\n return (await resolve()).closeApp(bundleId);\n },\n\n async pressButton(button: DeviceButton): Promise<void> {\n return (await resolve()).pressButton(button);\n },\n\n async dismissKeyboard(): Promise<void> {\n return (await resolve()).dismissKeyboard();\n },\n\n async dismissAlert(accept: boolean): Promise<void> {\n return (await resolve()).dismissAlert(accept);\n },\n\n async getLogs(\n durationSeconds?: number,\n filter?: string,\n ): Promise<LogsResult> {\n return (await resolve()).getLogs(durationSeconds, filter);\n },\n\n async longPress(\n query: ElementQuery,\n durationMs?: number,\n ): Promise<TapResult> {\n return (await resolve()).longPress(query, durationMs);\n },\n\n async scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement> {\n return (await resolve()).scrollToElement(query, direction, maxAttempts);\n },\n\n async getAlertText(): Promise<string> {\n return (await resolve()).getAlertText();\n },\n\n async getWindowSize(): Promise<WindowSize> {\n return (await resolve()).getWindowSize();\n },\n\n async getContexts(): Promise<string[]> {\n return (await resolve()).getContexts();\n },\n\n async setContext(context: string): Promise<void> {\n return (await resolve()).setContext(context);\n },\n\n async getClipboard(): Promise<string> {\n return (await resolve()).getClipboard();\n },\n\n async setClipboard(text: string): Promise<void> {\n return (await resolve()).setClipboard(text);\n },\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n return (await resolve()).startScreenRecording(outputPath);\n },\n\n async stopScreenRecording(): Promise<string> {\n return (await resolve()).stopScreenRecording();\n },\n\n async getElementText(query: ElementQuery): Promise<string> {\n return (await resolve()).getElementText(query);\n },\n };\n}\n"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EAUb,QAAQ,EAET,oBAAmB;AAMpB,OAAO,KAAK,EAAE,cAAc,EAAE,8BAA6B;AAE3D,YAAY,EAAE,aAAa,EAAE,oBAAmB;AAChD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAC9C,OAAO,EAAE,aAAa,EAAE,6BAA4B;AACpD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAE9C,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAsB,aAAa,CACjC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,aAAa,CAAC,CAsBxB;AAED,wBAAgB,iBAAiB,CAC/B,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,iBAAiB,CA+KnB"} | ||
| {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EAYb,QAAQ,EAET,oBAAmB;AAMpB,OAAO,KAAK,EAAE,cAAc,EAAE,8BAA6B;AAE3D,YAAY,EAAE,aAAa,EAAE,oBAAmB;AAChD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAC9C,OAAO,EAAE,aAAa,EAAE,6BAA4B;AACpD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAE9C,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAsB,aAAa,CACjC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,aAAa,CAAC,CAsBxB;AAED,wBAAgB,iBAAiB,CAC/B,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,iBAAiB,CAgMnB"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EAUb,QAAQ,EAET,oBAAmB;AAMpB,OAAO,KAAK,EAAE,cAAc,EAAE,8BAA6B;AAE3D,YAAY,EAAE,aAAa,EAAE,oBAAmB;AAChD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAC9C,OAAO,EAAE,aAAa,EAAE,6BAA4B;AACpD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAE9C,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAsB,aAAa,CACjC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,aAAa,CAAC,CAsBxB;AAED,wBAAgB,iBAAiB,CAC/B,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,iBAAiB,CA+KnB"} | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EAYb,QAAQ,EAET,oBAAmB;AAMpB,OAAO,KAAK,EAAE,cAAc,EAAE,8BAA6B;AAE3D,YAAY,EAAE,aAAa,EAAE,oBAAmB;AAChD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAC9C,OAAO,EAAE,aAAa,EAAE,6BAA4B;AACpD,OAAO,EAAE,UAAU,EAAE,0BAAyB;AAE9C,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG;IAC9C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAsB,aAAa,CACjC,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,OAAO,CAAC,aAAa,CAAC,CAsBxB;AAED,wBAAgB,iBAAiB,CAC/B,gBAAgB,CAAC,EAAE,MAAM,EACzB,gBAAgB,CAAC,EAAE,QAAQ,GAC1B,iBAAiB,CAgMnB"} |
@@ -52,2 +52,5 @@ import { AdbBackend } from "./adb-backend.mjs"; | ||
| } | ||
| async function screenshot(outputPath, options) { | ||
| return (await resolve()).screenshot(outputPath, options); | ||
| } | ||
| return { | ||
@@ -92,5 +95,3 @@ get platform() { | ||
| }, | ||
| async screenshot(outputPath) { | ||
| return (await resolve()).screenshot(outputPath); | ||
| }, | ||
| screenshot, | ||
| async openApp(bundleId) { | ||
@@ -97,0 +98,0 @@ return (await resolve()).openApp(bundleId); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AdbBackend } from './adb-backend.js';\nimport { AppiumBackend } from './appium-backend.js';\nimport { IdbBackend } from './idb-backend.js';\nimport { readSessionFile } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n LogsResult,\n ScreenshotResult,\n SnapshotResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport {\n detectPlatform,\n detectAllDevices,\n MultipleDevicesError,\n} from '../utils/platform.js';\nimport type { DetectedDevice } from '../utils/platform.js';\n\nexport type { DeviceBackend } from './types.js';\nexport { AdbBackend } from './adb-backend.js';\nexport { AppiumBackend } from './appium-backend.js';\nexport { IdbBackend } from './idb-backend.js';\n\nexport type LazyDeviceBackend = DeviceBackend & {\n selectDevice(deviceId: string): void;\n listDevices(): Promise<DetectedDevice[]>;\n};\n\nexport async function createBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DeviceBackend> {\n const sessionConfig = await readSessionFile();\n\n if (sessionConfig) {\n console.error(\n `Found .device-session (${sessionConfig.mode}, ${sessionConfig.platform})`,\n );\n const backend = new AppiumBackend(sessionConfig);\n await backend.ensureConnected();\n return backend;\n }\n\n const { platform, deviceId } = await detectPlatform(\n explicitDeviceId,\n explicitPlatform,\n );\n\n const backend =\n platform === 'ios' ? new IdbBackend(deviceId) : new AdbBackend(deviceId);\n\n await backend.ensureConnected();\n return backend;\n}\n\nexport function createLazyBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): LazyDeviceBackend {\n let inner: DeviceBackend | null = null;\n let connecting: Promise<DeviceBackend> | null = null;\n let pendingDevices: DetectedDevice[] | null = null;\n let selectedOverride: string | null = null;\n\n async function resolve(): Promise<DeviceBackend> {\n if (inner) {\n return inner;\n }\n\n if (pendingDevices && !selectedOverride) {\n throw new MultipleDevicesError(pendingDevices);\n }\n\n if (!connecting) {\n const deviceId = selectedOverride ?? explicitDeviceId;\n connecting = createBackend(deviceId, explicitPlatform)\n .then((backend) => {\n inner = backend;\n pendingDevices = null;\n return backend;\n })\n .catch((error: unknown) => {\n connecting = null;\n if (error instanceof MultipleDevicesError) {\n pendingDevices = error.devices;\n }\n throw error;\n });\n }\n return connecting;\n }\n\n return {\n get platform(): Platform {\n return inner?.platform ?? 'ios';\n },\n\n selectDevice(deviceId: string): void {\n selectedOverride = deviceId;\n inner = null;\n connecting = null;\n },\n\n async listDevices(): Promise<DetectedDevice[]> {\n if (pendingDevices) {\n return pendingDevices;\n }\n return detectAllDevices(explicitPlatform);\n },\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n return (await resolve()).getDeviceInfo();\n },\n\n async snapshot(): Promise<SnapshotResult> {\n return (await resolve()).snapshot();\n },\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n return (await resolve()).tapElement(query);\n },\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n return (await resolve()).tapCoordinates(x, y);\n },\n\n async typeText(text: string): Promise<void> {\n return (await resolve()).typeText(text);\n },\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n return (await resolve()).swipe(direction, startX, startY, distance);\n },\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement> {\n return (await resolve()).waitForElement(query, timeoutMs, intervalMs);\n },\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n return (await resolve()).getAppState(bundleId);\n },\n\n async screenshot(outputPath?: string): Promise<ScreenshotResult> {\n return (await resolve()).screenshot(outputPath);\n },\n\n async openApp(bundleId: string): Promise<void> {\n return (await resolve()).openApp(bundleId);\n },\n\n async closeApp(bundleId: string): Promise<void> {\n return (await resolve()).closeApp(bundleId);\n },\n\n async pressButton(button: DeviceButton): Promise<void> {\n return (await resolve()).pressButton(button);\n },\n\n async dismissKeyboard(): Promise<void> {\n return (await resolve()).dismissKeyboard();\n },\n\n async dismissAlert(accept: boolean): Promise<void> {\n return (await resolve()).dismissAlert(accept);\n },\n\n async getLogs(\n durationSeconds?: number,\n filter?: string,\n ): Promise<LogsResult> {\n return (await resolve()).getLogs(durationSeconds, filter);\n },\n\n async longPress(\n query: ElementQuery,\n durationMs?: number,\n ): Promise<TapResult> {\n return (await resolve()).longPress(query, durationMs);\n },\n\n async scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement> {\n return (await resolve()).scrollToElement(query, direction, maxAttempts);\n },\n\n async getAlertText(): Promise<string> {\n return (await resolve()).getAlertText();\n },\n\n async getWindowSize(): Promise<WindowSize> {\n return (await resolve()).getWindowSize();\n },\n\n async getContexts(): Promise<string[]> {\n return (await resolve()).getContexts();\n },\n\n async setContext(context: string): Promise<void> {\n return (await resolve()).setContext(context);\n },\n\n async getClipboard(): Promise<string> {\n return (await resolve()).getClipboard();\n },\n\n async setClipboard(text: string): Promise<void> {\n return (await resolve()).setClipboard(text);\n },\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n return (await resolve()).startScreenRecording(outputPath);\n },\n\n async stopScreenRecording(): Promise<string> {\n return (await resolve()).stopScreenRecording();\n },\n\n async getElementText(query: ElementQuery): Promise<string> {\n return (await resolve()).getElementText(query);\n },\n };\n}\n"]} | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/backends/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AdbBackend } from './adb-backend.js';\nimport { AppiumBackend } from './appium-backend.js';\nimport { IdbBackend } from './idb-backend.js';\nimport { readSessionFile } from './session-file.js';\nimport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n LogsResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n SnapshotResult,\n TapResult,\n AppStateResult,\n ElementQuery,\n UIElement,\n Platform,\n WindowSize,\n} from './types.js';\nimport {\n detectPlatform,\n detectAllDevices,\n MultipleDevicesError,\n} from '../utils/platform.js';\nimport type { DetectedDevice } from '../utils/platform.js';\n\nexport type { DeviceBackend } from './types.js';\nexport { AdbBackend } from './adb-backend.js';\nexport { AppiumBackend } from './appium-backend.js';\nexport { IdbBackend } from './idb-backend.js';\n\nexport type LazyDeviceBackend = DeviceBackend & {\n selectDevice(deviceId: string): void;\n listDevices(): Promise<DetectedDevice[]>;\n};\n\nexport async function createBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): Promise<DeviceBackend> {\n const sessionConfig = await readSessionFile();\n\n if (sessionConfig) {\n console.error(\n `Found .device-session (${sessionConfig.mode}, ${sessionConfig.platform})`,\n );\n const backend = new AppiumBackend(sessionConfig);\n await backend.ensureConnected();\n return backend;\n }\n\n const { platform, deviceId } = await detectPlatform(\n explicitDeviceId,\n explicitPlatform,\n );\n\n const backend =\n platform === 'ios' ? new IdbBackend(deviceId) : new AdbBackend(deviceId);\n\n await backend.ensureConnected();\n return backend;\n}\n\nexport function createLazyBackend(\n explicitDeviceId?: string,\n explicitPlatform?: Platform,\n): LazyDeviceBackend {\n let inner: DeviceBackend | null = null;\n let connecting: Promise<DeviceBackend> | null = null;\n let pendingDevices: DetectedDevice[] | null = null;\n let selectedOverride: string | null = null;\n\n async function resolve(): Promise<DeviceBackend> {\n if (inner) {\n return inner;\n }\n\n if (pendingDevices && !selectedOverride) {\n throw new MultipleDevicesError(pendingDevices);\n }\n\n if (!connecting) {\n const deviceId = selectedOverride ?? explicitDeviceId;\n connecting = createBackend(deviceId, explicitPlatform)\n .then((backend) => {\n inner = backend;\n pendingDevices = null;\n return backend;\n })\n .catch((error: unknown) => {\n connecting = null;\n if (error instanceof MultipleDevicesError) {\n pendingDevices = error.devices;\n }\n throw error;\n });\n }\n return connecting;\n }\n\n function screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n function screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n function screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n async function screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult> {\n return (await resolve()).screenshot(outputPath, options);\n }\n\n return {\n get platform(): Platform {\n return inner?.platform ?? 'ios';\n },\n\n selectDevice(deviceId: string): void {\n selectedOverride = deviceId;\n inner = null;\n connecting = null;\n },\n\n async listDevices(): Promise<DetectedDevice[]> {\n if (pendingDevices) {\n return pendingDevices;\n }\n return detectAllDevices(explicitPlatform);\n },\n\n async getDeviceInfo(): Promise<DeviceInfo> {\n return (await resolve()).getDeviceInfo();\n },\n\n async snapshot(): Promise<SnapshotResult> {\n return (await resolve()).snapshot();\n },\n\n async tapElement(query: ElementQuery): Promise<TapResult> {\n return (await resolve()).tapElement(query);\n },\n\n async tapCoordinates(x: number, y: number): Promise<void> {\n return (await resolve()).tapCoordinates(x, y);\n },\n\n async typeText(text: string): Promise<void> {\n return (await resolve()).typeText(text);\n },\n\n async swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void> {\n return (await resolve()).swipe(direction, startX, startY, distance);\n },\n\n async waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement> {\n return (await resolve()).waitForElement(query, timeoutMs, intervalMs);\n },\n\n async getAppState(bundleId: string): Promise<AppStateResult> {\n return (await resolve()).getAppState(bundleId);\n },\n\n screenshot,\n\n async openApp(bundleId: string): Promise<void> {\n return (await resolve()).openApp(bundleId);\n },\n\n async closeApp(bundleId: string): Promise<void> {\n return (await resolve()).closeApp(bundleId);\n },\n\n async pressButton(button: DeviceButton): Promise<void> {\n return (await resolve()).pressButton(button);\n },\n\n async dismissKeyboard(): Promise<void> {\n return (await resolve()).dismissKeyboard();\n },\n\n async dismissAlert(accept: boolean): Promise<void> {\n return (await resolve()).dismissAlert(accept);\n },\n\n async getLogs(\n durationSeconds?: number,\n filter?: string,\n ): Promise<LogsResult> {\n return (await resolve()).getLogs(durationSeconds, filter);\n },\n\n async longPress(\n query: ElementQuery,\n durationMs?: number,\n ): Promise<TapResult> {\n return (await resolve()).longPress(query, durationMs);\n },\n\n async scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement> {\n return (await resolve()).scrollToElement(query, direction, maxAttempts);\n },\n\n async getAlertText(): Promise<string> {\n return (await resolve()).getAlertText();\n },\n\n async getWindowSize(): Promise<WindowSize> {\n return (await resolve()).getWindowSize();\n },\n\n async getContexts(): Promise<string[]> {\n return (await resolve()).getContexts();\n },\n\n async setContext(context: string): Promise<void> {\n return (await resolve()).setContext(context);\n },\n\n async getClipboard(): Promise<string> {\n return (await resolve()).getClipboard();\n },\n\n async setClipboard(text: string): Promise<void> {\n return (await resolve()).setClipboard(text);\n },\n\n async startScreenRecording(outputPath?: string): Promise<void> {\n return (await resolve()).startScreenRecording(outputPath);\n },\n\n async stopScreenRecording(): Promise<string> {\n return (await resolve()).stopScreenRecording();\n },\n\n async getElementText(query: ElementQuery): Promise<string> {\n return (await resolve()).getElementText(query);\n },\n };\n}\n"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Platform = 'ios' | 'android';\n\nexport type DeviceInfo = {\n platform: Platform;\n deviceId: string;\n name: string;\n osVersion: string;\n state: string;\n};\n\nexport type UIElement = {\n type: string;\n label?: string;\n value?: string;\n identifier?: string;\n frame: { x: number; y: number; width: number; height: number };\n enabled: boolean;\n children?: UIElement[];\n};\n\nexport type SnapshotResult = {\n platform: Platform;\n hierarchy: UIElement[];\n raw: string;\n timestamp: number;\n};\n\nexport type TapResult = {\n success: boolean;\n x: number;\n y: number;\n targetDescription: string;\n};\n\nexport type AppStateResult = {\n bundleId: string;\n state: string;\n pid?: number;\n};\n\nexport type ScreenshotResult = {\n data: string;\n format: 'png' | 'jpeg';\n path?: string;\n};\n\nexport type LogEntry = {\n timestamp: string;\n level: string;\n message: string;\n};\n\nexport type LogsResult = {\n entries: LogEntry[];\n source: string;\n};\n\nexport type ElementQuery = {\n label?: string;\n identifier?: string;\n text?: string;\n type?: string;\n};\n\nexport type DeviceButton = 'home' | 'back' | 'enter' | 'lock';\n\nexport type WindowSize = {\n width: number;\n height: number;\n};\n\nexport type DeviceBackend = {\n readonly platform: Platform;\n\n getDeviceInfo(): Promise<DeviceInfo>;\n\n snapshot(): Promise<SnapshotResult>;\n\n tapElement(query: ElementQuery): Promise<TapResult>;\n\n tapCoordinates(x: number, y: number): Promise<void>;\n\n typeText(text: string): Promise<void>;\n\n swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void>;\n\n waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement>;\n\n getAppState(bundleId: string): Promise<AppStateResult>;\n\n screenshot(outputPath?: string): Promise<ScreenshotResult>;\n\n openApp(bundleId: string): Promise<void>;\n\n closeApp(bundleId: string): Promise<void>;\n\n pressButton(button: DeviceButton): Promise<void>;\n\n dismissKeyboard(): Promise<void>;\n\n dismissAlert(accept: boolean): Promise<void>;\n\n getLogs(durationSeconds?: number, filter?: string): Promise<LogsResult>;\n\n longPress(query: ElementQuery, durationMs?: number): Promise<TapResult>;\n\n scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement>;\n\n getAlertText(): Promise<string>;\n\n getWindowSize(): Promise<WindowSize>;\n\n getContexts(): Promise<string[]>;\n\n setContext(context: string): Promise<void>;\n\n getClipboard(): Promise<string>;\n\n setClipboard(text: string): Promise<void>;\n\n startScreenRecording(outputPath?: string): Promise<void>;\n\n stopScreenRecording(): Promise<string>;\n\n getElementText(query: ElementQuery): Promise<string>;\n};\n"]} | ||
| {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Platform = 'ios' | 'android';\n\nexport type DeviceInfo = {\n platform: Platform;\n deviceId: string;\n name: string;\n osVersion: string;\n state: string;\n};\n\nexport type UIElement = {\n type: string;\n label?: string;\n value?: string;\n identifier?: string;\n frame: { x: number; y: number; width: number; height: number };\n enabled: boolean;\n children?: UIElement[];\n};\n\nexport type SnapshotResult = {\n platform: Platform;\n hierarchy: UIElement[];\n raw: string;\n timestamp: number;\n};\n\nexport type TapResult = {\n success: boolean;\n x: number;\n y: number;\n targetDescription: string;\n};\n\nexport type AppStateResult = {\n bundleId: string;\n state: string;\n pid?: number;\n};\n\nexport type ScreenshotResult = {\n /** Base64-encoded image data. */\n data: string;\n format: 'png' | 'jpeg';\n /** File path the screenshot was written to, when captured to disk. */\n path?: string;\n};\n\n/** Result of a screenshot captured with `encode: false`: file `path` only, no base64 `data`. */\nexport type ScreenshotFileResult = {\n data?: undefined;\n format: 'png' | 'jpeg';\n path: string;\n};\n\nexport type ScreenshotOptions = {\n /** Include base64 image data in the result. Defaults to `true`. */\n encode?: boolean;\n};\n\nexport type LogEntry = {\n timestamp: string;\n level: string;\n message: string;\n};\n\nexport type LogsResult = {\n entries: LogEntry[];\n source: string;\n};\n\nexport type ElementQuery = {\n label?: string;\n identifier?: string;\n text?: string;\n type?: string;\n};\n\nexport type DeviceButton = 'home' | 'back' | 'enter' | 'lock';\n\nexport type WindowSize = {\n width: number;\n height: number;\n};\n\nexport type DeviceBackend = {\n readonly platform: Platform;\n\n getDeviceInfo(): Promise<DeviceInfo>;\n\n snapshot(): Promise<SnapshotResult>;\n\n tapElement(query: ElementQuery): Promise<TapResult>;\n\n tapCoordinates(x: number, y: number): Promise<void>;\n\n typeText(text: string): Promise<void>;\n\n swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void>;\n\n waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement>;\n\n getAppState(bundleId: string): Promise<AppStateResult>;\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n openApp(bundleId: string): Promise<void>;\n\n closeApp(bundleId: string): Promise<void>;\n\n pressButton(button: DeviceButton): Promise<void>;\n\n dismissKeyboard(): Promise<void>;\n\n dismissAlert(accept: boolean): Promise<void>;\n\n getLogs(durationSeconds?: number, filter?: string): Promise<LogsResult>;\n\n longPress(query: ElementQuery, durationMs?: number): Promise<TapResult>;\n\n scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement>;\n\n getAlertText(): Promise<string>;\n\n getWindowSize(): Promise<WindowSize>;\n\n getContexts(): Promise<string[]>;\n\n setContext(context: string): Promise<void>;\n\n getClipboard(): Promise<string>;\n\n setClipboard(text: string): Promise<void>;\n\n startScreenRecording(outputPath?: string): Promise<void>;\n\n stopScreenRecording(): Promise<string>;\n\n getElementText(query: ElementQuery): Promise<string>;\n};\n"]} |
@@ -41,6 +41,18 @@ export type Platform = 'ios' | 'android'; | ||
| export type ScreenshotResult = { | ||
| /** Base64-encoded image data. */ | ||
| data: string; | ||
| format: 'png' | 'jpeg'; | ||
| /** File path the screenshot was written to, when captured to disk. */ | ||
| path?: string; | ||
| }; | ||
| /** Result of a screenshot captured with `encode: false`: file `path` only, no base64 `data`. */ | ||
| export type ScreenshotFileResult = { | ||
| data?: undefined; | ||
| format: 'png' | 'jpeg'; | ||
| path: string; | ||
| }; | ||
| export type ScreenshotOptions = { | ||
| /** Include base64 image data in the result. Defaults to `true`. */ | ||
| encode?: boolean; | ||
| }; | ||
| export type LogEntry = { | ||
@@ -76,3 +88,9 @@ timestamp: string; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(outputPath?: string): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -79,0 +97,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"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"} |
@@ -41,6 +41,18 @@ export type Platform = 'ios' | 'android'; | ||
| export type ScreenshotResult = { | ||
| /** Base64-encoded image data. */ | ||
| data: string; | ||
| format: 'png' | 'jpeg'; | ||
| /** File path the screenshot was written to, when captured to disk. */ | ||
| path?: string; | ||
| }; | ||
| /** Result of a screenshot captured with `encode: false`: file `path` only, no base64 `data`. */ | ||
| export type ScreenshotFileResult = { | ||
| data?: undefined; | ||
| format: 'png' | 'jpeg'; | ||
| path: string; | ||
| }; | ||
| export type ScreenshotOptions = { | ||
| /** Include base64 image data in the result. Defaults to `true`. */ | ||
| encode?: boolean; | ||
| }; | ||
| export type LogEntry = { | ||
@@ -76,3 +88,9 @@ timestamp: string; | ||
| getAppState(bundleId: string): Promise<AppStateResult>; | ||
| screenshot(outputPath?: string): Promise<ScreenshotResult>; | ||
| screenshot(outputPath?: string, options?: { | ||
| encode?: true; | ||
| }): Promise<ScreenshotResult>; | ||
| screenshot(outputPath: string | undefined, options: { | ||
| encode: false; | ||
| }): Promise<ScreenshotFileResult>; | ||
| screenshot(outputPath?: string, options?: ScreenshotOptions): Promise<ScreenshotResult | ScreenshotFileResult>; | ||
| openApp(bundleId: string): Promise<void>; | ||
@@ -79,0 +97,0 @@ closeApp(bundleId: string): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Platform = 'ios' | 'android';\n\nexport type DeviceInfo = {\n platform: Platform;\n deviceId: string;\n name: string;\n osVersion: string;\n state: string;\n};\n\nexport type UIElement = {\n type: string;\n label?: string;\n value?: string;\n identifier?: string;\n frame: { x: number; y: number; width: number; height: number };\n enabled: boolean;\n children?: UIElement[];\n};\n\nexport type SnapshotResult = {\n platform: Platform;\n hierarchy: UIElement[];\n raw: string;\n timestamp: number;\n};\n\nexport type TapResult = {\n success: boolean;\n x: number;\n y: number;\n targetDescription: string;\n};\n\nexport type AppStateResult = {\n bundleId: string;\n state: string;\n pid?: number;\n};\n\nexport type ScreenshotResult = {\n data: string;\n format: 'png' | 'jpeg';\n path?: string;\n};\n\nexport type LogEntry = {\n timestamp: string;\n level: string;\n message: string;\n};\n\nexport type LogsResult = {\n entries: LogEntry[];\n source: string;\n};\n\nexport type ElementQuery = {\n label?: string;\n identifier?: string;\n text?: string;\n type?: string;\n};\n\nexport type DeviceButton = 'home' | 'back' | 'enter' | 'lock';\n\nexport type WindowSize = {\n width: number;\n height: number;\n};\n\nexport type DeviceBackend = {\n readonly platform: Platform;\n\n getDeviceInfo(): Promise<DeviceInfo>;\n\n snapshot(): Promise<SnapshotResult>;\n\n tapElement(query: ElementQuery): Promise<TapResult>;\n\n tapCoordinates(x: number, y: number): Promise<void>;\n\n typeText(text: string): Promise<void>;\n\n swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void>;\n\n waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement>;\n\n getAppState(bundleId: string): Promise<AppStateResult>;\n\n screenshot(outputPath?: string): Promise<ScreenshotResult>;\n\n openApp(bundleId: string): Promise<void>;\n\n closeApp(bundleId: string): Promise<void>;\n\n pressButton(button: DeviceButton): Promise<void>;\n\n dismissKeyboard(): Promise<void>;\n\n dismissAlert(accept: boolean): Promise<void>;\n\n getLogs(durationSeconds?: number, filter?: string): Promise<LogsResult>;\n\n longPress(query: ElementQuery, durationMs?: number): Promise<TapResult>;\n\n scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement>;\n\n getAlertText(): Promise<string>;\n\n getWindowSize(): Promise<WindowSize>;\n\n getContexts(): Promise<string[]>;\n\n setContext(context: string): Promise<void>;\n\n getClipboard(): Promise<string>;\n\n setClipboard(text: string): Promise<void>;\n\n startScreenRecording(outputPath?: string): Promise<void>;\n\n stopScreenRecording(): Promise<string>;\n\n getElementText(query: ElementQuery): Promise<string>;\n};\n"]} | ||
| {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Platform = 'ios' | 'android';\n\nexport type DeviceInfo = {\n platform: Platform;\n deviceId: string;\n name: string;\n osVersion: string;\n state: string;\n};\n\nexport type UIElement = {\n type: string;\n label?: string;\n value?: string;\n identifier?: string;\n frame: { x: number; y: number; width: number; height: number };\n enabled: boolean;\n children?: UIElement[];\n};\n\nexport type SnapshotResult = {\n platform: Platform;\n hierarchy: UIElement[];\n raw: string;\n timestamp: number;\n};\n\nexport type TapResult = {\n success: boolean;\n x: number;\n y: number;\n targetDescription: string;\n};\n\nexport type AppStateResult = {\n bundleId: string;\n state: string;\n pid?: number;\n};\n\nexport type ScreenshotResult = {\n /** Base64-encoded image data. */\n data: string;\n format: 'png' | 'jpeg';\n /** File path the screenshot was written to, when captured to disk. */\n path?: string;\n};\n\n/** Result of a screenshot captured with `encode: false`: file `path` only, no base64 `data`. */\nexport type ScreenshotFileResult = {\n data?: undefined;\n format: 'png' | 'jpeg';\n path: string;\n};\n\nexport type ScreenshotOptions = {\n /** Include base64 image data in the result. Defaults to `true`. */\n encode?: boolean;\n};\n\nexport type LogEntry = {\n timestamp: string;\n level: string;\n message: string;\n};\n\nexport type LogsResult = {\n entries: LogEntry[];\n source: string;\n};\n\nexport type ElementQuery = {\n label?: string;\n identifier?: string;\n text?: string;\n type?: string;\n};\n\nexport type DeviceButton = 'home' | 'back' | 'enter' | 'lock';\n\nexport type WindowSize = {\n width: number;\n height: number;\n};\n\nexport type DeviceBackend = {\n readonly platform: Platform;\n\n getDeviceInfo(): Promise<DeviceInfo>;\n\n snapshot(): Promise<SnapshotResult>;\n\n tapElement(query: ElementQuery): Promise<TapResult>;\n\n tapCoordinates(x: number, y: number): Promise<void>;\n\n typeText(text: string): Promise<void>;\n\n swipe(\n direction: 'up' | 'down' | 'left' | 'right',\n startX?: number,\n startY?: number,\n distance?: number,\n ): Promise<void>;\n\n waitForElement(\n query: ElementQuery,\n timeoutMs?: number,\n intervalMs?: number,\n ): Promise<UIElement>;\n\n getAppState(bundleId: string): Promise<AppStateResult>;\n\n screenshot(\n outputPath?: string,\n options?: { encode?: true },\n ): Promise<ScreenshotResult>;\n screenshot(\n outputPath: string | undefined,\n options: { encode: false },\n ): Promise<ScreenshotFileResult>;\n screenshot(\n outputPath?: string,\n options?: ScreenshotOptions,\n ): Promise<ScreenshotResult | ScreenshotFileResult>;\n\n openApp(bundleId: string): Promise<void>;\n\n closeApp(bundleId: string): Promise<void>;\n\n pressButton(button: DeviceButton): Promise<void>;\n\n dismissKeyboard(): Promise<void>;\n\n dismissAlert(accept: boolean): Promise<void>;\n\n getLogs(durationSeconds?: number, filter?: string): Promise<LogsResult>;\n\n longPress(query: ElementQuery, durationMs?: number): Promise<TapResult>;\n\n scrollToElement(\n query: ElementQuery,\n direction?: 'up' | 'down',\n maxAttempts?: number,\n ): Promise<UIElement>;\n\n getAlertText(): Promise<string>;\n\n getWindowSize(): Promise<WindowSize>;\n\n getContexts(): Promise<string[]>;\n\n setContext(context: string): Promise<void>;\n\n getClipboard(): Promise<string>;\n\n setClipboard(text: string): Promise<void>;\n\n startScreenRecording(outputPath?: string): Promise<void>;\n\n stopScreenRecording(): Promise<string>;\n\n getElementText(query: ElementQuery): Promise<string>;\n};\n"]} |
+16
-1
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.MultipleDevicesError = exports.detectAllDevices = exports.createMcpServer = exports.createLazyBackend = exports.createBackend = void 0; | ||
| exports.DEFAULT_METRO_PORT = exports.DEFAULT_ANDROID_APP_ID = exports.DEFAULT_IOS_APP_ID = exports.defaultAppIdForPlatform = exports.resetHermesSession = exports.getHermesSession = exports.HermesSession = exports.LEGACY_SYNTHETIC_TITLE = exports.validateWebSocketUrl = exports.hasAmbiguousTarget = exports.selectHermesTarget = exports.fetchDiscoveryTargets = exports.runHermesCdp = exports.MultipleDevicesError = exports.detectAllDevices = exports.createMcpServer = exports.createLazyBackend = exports.createBackend = void 0; | ||
| var index_js_1 = require("./backends/index.cjs"); | ||
@@ -12,2 +12,17 @@ Object.defineProperty(exports, "createBackend", { enumerable: true, get: function () { return index_js_1.createBackend; } }); | ||
| Object.defineProperty(exports, "MultipleDevicesError", { enumerable: true, get: function () { return platform_js_1.MultipleDevicesError; } }); | ||
| var hermes_cdp_js_1 = require("./hermes/hermes-cdp.cjs"); | ||
| Object.defineProperty(exports, "runHermesCdp", { enumerable: true, get: function () { return hermes_cdp_js_1.runHermesCdp; } }); | ||
| Object.defineProperty(exports, "fetchDiscoveryTargets", { enumerable: true, get: function () { return hermes_cdp_js_1.fetchDiscoveryTargets; } }); | ||
| Object.defineProperty(exports, "selectHermesTarget", { enumerable: true, get: function () { return hermes_cdp_js_1.selectHermesTarget; } }); | ||
| Object.defineProperty(exports, "hasAmbiguousTarget", { enumerable: true, get: function () { return hermes_cdp_js_1.hasAmbiguousTarget; } }); | ||
| Object.defineProperty(exports, "validateWebSocketUrl", { enumerable: true, get: function () { return hermes_cdp_js_1.validateWebSocketUrl; } }); | ||
| Object.defineProperty(exports, "LEGACY_SYNTHETIC_TITLE", { enumerable: true, get: function () { return hermes_cdp_js_1.LEGACY_SYNTHETIC_TITLE; } }); | ||
| var session_js_1 = require("./hermes/session.cjs"); | ||
| Object.defineProperty(exports, "HermesSession", { enumerable: true, get: function () { return session_js_1.HermesSession; } }); | ||
| Object.defineProperty(exports, "getHermesSession", { enumerable: true, get: function () { return session_js_1.getHermesSession; } }); | ||
| Object.defineProperty(exports, "resetHermesSession", { enumerable: true, get: function () { return session_js_1.resetHermesSession; } }); | ||
| Object.defineProperty(exports, "defaultAppIdForPlatform", { enumerable: true, get: function () { return session_js_1.defaultAppIdForPlatform; } }); | ||
| Object.defineProperty(exports, "DEFAULT_IOS_APP_ID", { enumerable: true, get: function () { return session_js_1.DEFAULT_IOS_APP_ID; } }); | ||
| Object.defineProperty(exports, "DEFAULT_ANDROID_APP_ID", { enumerable: true, get: function () { return session_js_1.DEFAULT_ANDROID_APP_ID; } }); | ||
| Object.defineProperty(exports, "DEFAULT_METRO_PORT", { enumerable: true, get: function () { return session_js_1.DEFAULT_METRO_PORT; } }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAuE;AAA9D,yGAAA,aAAa,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAEzC,0CAA8C;AAArC,4GAAA,eAAe,OAAA;AAExB,oDAA6E;AAApE,+GAAA,gBAAgB,OAAA;AAAE,mHAAA,oBAAoB,OAAA","sourcesContent":["export { createBackend, createLazyBackend } from './backends/index.js';\nexport type { LazyDeviceBackend } from './backends/index.js';\nexport { createMcpServer } from './server.js';\n\nexport { detectAllDevices, MultipleDevicesError } from './utils/platform.js';\nexport type { DetectedDevice } from './utils/platform.js';\n\nexport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n UIElement,\n ElementQuery,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n Platform,\n WindowSize,\n} from './backends/types.js';\n\nexport type { SessionConfig } from './backends/session-file.js';\n"]} | ||
| {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAAuE;AAA9D,yGAAA,aAAa,OAAA;AAAE,6GAAA,iBAAiB,OAAA;AAEzC,0CAA8C;AAArC,4GAAA,eAAe,OAAA;AAExB,oDAA6E;AAApE,+GAAA,gBAAgB,OAAA;AAAE,mHAAA,oBAAoB,OAAA;AAsB/C,yDAOgC;AAN9B,6GAAA,YAAY,OAAA;AACZ,sHAAA,qBAAqB,OAAA;AACrB,mHAAA,kBAAkB,OAAA;AAClB,mHAAA,kBAAkB,OAAA;AAClB,qHAAA,oBAAoB,OAAA;AACpB,uHAAA,sBAAsB,OAAA;AAQxB,mDAQ6B;AAP3B,2GAAA,aAAa,OAAA;AACb,8GAAA,gBAAgB,OAAA;AAChB,gHAAA,kBAAkB,OAAA;AAClB,qHAAA,uBAAuB,OAAA;AACvB,gHAAA,kBAAkB,OAAA;AAClB,oHAAA,sBAAsB,OAAA;AACtB,gHAAA,kBAAkB,OAAA","sourcesContent":["export { createBackend, createLazyBackend } from './backends/index.js';\nexport type { LazyDeviceBackend } from './backends/index.js';\nexport { createMcpServer } from './server.js';\n\nexport { detectAllDevices, MultipleDevicesError } from './utils/platform.js';\nexport type { DetectedDevice } from './utils/platform.js';\n\nexport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n UIElement,\n ElementQuery,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n Platform,\n WindowSize,\n} from './backends/types.js';\n\nexport type { SessionConfig } from './backends/session-file.js';\n\nexport {\n runHermesCdp,\n fetchDiscoveryTargets,\n selectHermesTarget,\n hasAmbiguousTarget,\n validateWebSocketUrl,\n LEGACY_SYNTHETIC_TITLE,\n} from './hermes/hermes-cdp.js';\nexport type {\n HermesTarget,\n HermesCdpResult,\n RunHermesCdpInput,\n} from './hermes/hermes-cdp.js';\n\nexport {\n HermesSession,\n getHermesSession,\n resetHermesSession,\n defaultAppIdForPlatform,\n DEFAULT_IOS_APP_ID,\n DEFAULT_ANDROID_APP_ID,\n DEFAULT_METRO_PORT,\n} from './hermes/session.js';\nexport type {\n HermesPlatform,\n HermesSessionOptions,\n HermesSessionResolved,\n HermesSessionResolveInput,\n} from './hermes/session.js';\n"]} |
+5
-1
@@ -6,4 +6,8 @@ export { createBackend, createLazyBackend } from "./backends/index.cjs"; | ||
| export type { DetectedDevice } from "./utils/platform.cjs"; | ||
| export type { DeviceBackend, DeviceButton, DeviceInfo, UIElement, ElementQuery, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, Platform, WindowSize, } from "./backends/types.cjs"; | ||
| export type { DeviceBackend, DeviceButton, DeviceInfo, UIElement, ElementQuery, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, Platform, WindowSize, } from "./backends/types.cjs"; | ||
| export type { SessionConfig } from "./backends/session-file.cjs"; | ||
| export { runHermesCdp, fetchDiscoveryTargets, selectHermesTarget, hasAmbiguousTarget, validateWebSocketUrl, LEGACY_SYNTHETIC_TITLE, } from "./hermes/hermes-cdp.cjs"; | ||
| export type { HermesTarget, HermesCdpResult, RunHermesCdpInput, } from "./hermes/hermes-cdp.cjs"; | ||
| export { HermesSession, getHermesSession, resetHermesSession, defaultAppIdForPlatform, DEFAULT_IOS_APP_ID, DEFAULT_ANDROID_APP_ID, DEFAULT_METRO_PORT, } from "./hermes/session.cjs"; | ||
| export type { HermesPlatform, HermesSessionOptions, HermesSessionResolved, HermesSessionResolveInput, } from "./hermes/session.cjs"; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,6BAA4B;AACvE,YAAY,EAAE,iBAAiB,EAAE,6BAA4B;AAC7D,OAAO,EAAE,eAAe,EAAE,qBAAoB;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,6BAA4B;AAC7E,YAAY,EAAE,cAAc,EAAE,6BAA4B;AAE1D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,UAAU,GACX,6BAA4B;AAE7B,YAAY,EAAE,aAAa,EAAE,oCAAmC"} | ||
| {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,6BAA4B;AACvE,YAAY,EAAE,iBAAiB,EAAE,6BAA4B;AAC7D,OAAO,EAAE,eAAe,EAAE,qBAAoB;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,6BAA4B;AAC7E,YAAY,EAAE,cAAc,EAAE,6BAA4B;AAE1D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,UAAU,GACX,6BAA4B;AAE7B,YAAY,EAAE,aAAa,EAAE,oCAAmC;AAEhE,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,gCAA+B;AAChC,YAAY,EACV,YAAY,EACZ,eAAe,EACf,iBAAiB,GAClB,gCAA+B;AAEhC,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,GACnB,6BAA4B;AAC7B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,6BAA4B"} |
+5
-1
@@ -6,4 +6,8 @@ export { createBackend, createLazyBackend } from "./backends/index.mjs"; | ||
| export type { DetectedDevice } from "./utils/platform.mjs"; | ||
| export type { DeviceBackend, DeviceButton, DeviceInfo, UIElement, ElementQuery, SnapshotResult, ScreenshotResult, LogsResult, TapResult, AppStateResult, Platform, WindowSize, } from "./backends/types.mjs"; | ||
| export type { DeviceBackend, DeviceButton, DeviceInfo, UIElement, ElementQuery, SnapshotResult, ScreenshotResult, ScreenshotFileResult, ScreenshotOptions, LogsResult, TapResult, AppStateResult, Platform, WindowSize, } from "./backends/types.mjs"; | ||
| export type { SessionConfig } from "./backends/session-file.mjs"; | ||
| export { runHermesCdp, fetchDiscoveryTargets, selectHermesTarget, hasAmbiguousTarget, validateWebSocketUrl, LEGACY_SYNTHETIC_TITLE, } from "./hermes/hermes-cdp.mjs"; | ||
| export type { HermesTarget, HermesCdpResult, RunHermesCdpInput, } from "./hermes/hermes-cdp.mjs"; | ||
| export { HermesSession, getHermesSession, resetHermesSession, defaultAppIdForPlatform, DEFAULT_IOS_APP_ID, DEFAULT_ANDROID_APP_ID, DEFAULT_METRO_PORT, } from "./hermes/session.mjs"; | ||
| export type { HermesPlatform, HermesSessionOptions, HermesSessionResolved, HermesSessionResolveInput, } from "./hermes/session.mjs"; | ||
| //# sourceMappingURL=index.d.mts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,6BAA4B;AACvE,YAAY,EAAE,iBAAiB,EAAE,6BAA4B;AAC7D,OAAO,EAAE,eAAe,EAAE,qBAAoB;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,6BAA4B;AAC7E,YAAY,EAAE,cAAc,EAAE,6BAA4B;AAE1D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,UAAU,GACX,6BAA4B;AAE7B,YAAY,EAAE,aAAa,EAAE,oCAAmC"} | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,6BAA4B;AACvE,YAAY,EAAE,iBAAiB,EAAE,6BAA4B;AAC7D,OAAO,EAAE,eAAe,EAAE,qBAAoB;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,6BAA4B;AAC7E,YAAY,EAAE,cAAc,EAAE,6BAA4B;AAE1D,YAAY,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,UAAU,GACX,6BAA4B;AAE7B,YAAY,EAAE,aAAa,EAAE,oCAAmC;AAEhE,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,gCAA+B;AAChC,YAAY,EACV,YAAY,EACZ,eAAe,EACf,iBAAiB,GAClB,gCAA+B;AAEhC,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,GACnB,6BAA4B;AAC7B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,6BAA4B"} |
+2
-0
| export { createBackend, createLazyBackend } from "./backends/index.mjs"; | ||
| export { createMcpServer } from "./server.mjs"; | ||
| export { detectAllDevices, MultipleDevicesError } from "./utils/platform.mjs"; | ||
| export { runHermesCdp, fetchDiscoveryTargets, selectHermesTarget, hasAmbiguousTarget, validateWebSocketUrl, LEGACY_SYNTHETIC_TITLE } from "./hermes/hermes-cdp.mjs"; | ||
| export { HermesSession, getHermesSession, resetHermesSession, defaultAppIdForPlatform, DEFAULT_IOS_APP_ID, DEFAULT_ANDROID_APP_ID, DEFAULT_METRO_PORT } from "./hermes/session.mjs"; | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,6BAA4B;AAEvE,OAAO,EAAE,eAAe,EAAE,qBAAoB;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,6BAA4B","sourcesContent":["export { createBackend, createLazyBackend } from './backends/index.js';\nexport type { LazyDeviceBackend } from './backends/index.js';\nexport { createMcpServer } from './server.js';\n\nexport { detectAllDevices, MultipleDevicesError } from './utils/platform.js';\nexport type { DetectedDevice } from './utils/platform.js';\n\nexport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n UIElement,\n ElementQuery,\n SnapshotResult,\n ScreenshotResult,\n LogsResult,\n TapResult,\n AppStateResult,\n Platform,\n WindowSize,\n} from './backends/types.js';\n\nexport type { SessionConfig } from './backends/session-file.js';\n"]} | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,6BAA4B;AAEvE,OAAO,EAAE,eAAe,EAAE,qBAAoB;AAE9C,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,6BAA4B;AAsB7E,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACvB,gCAA+B;AAOhC,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EACnB,6BAA4B","sourcesContent":["export { createBackend, createLazyBackend } from './backends/index.js';\nexport type { LazyDeviceBackend } from './backends/index.js';\nexport { createMcpServer } from './server.js';\n\nexport { detectAllDevices, MultipleDevicesError } from './utils/platform.js';\nexport type { DetectedDevice } from './utils/platform.js';\n\nexport type {\n DeviceBackend,\n DeviceButton,\n DeviceInfo,\n UIElement,\n ElementQuery,\n SnapshotResult,\n ScreenshotResult,\n ScreenshotFileResult,\n ScreenshotOptions,\n LogsResult,\n TapResult,\n AppStateResult,\n Platform,\n WindowSize,\n} from './backends/types.js';\n\nexport type { SessionConfig } from './backends/session-file.js';\n\nexport {\n runHermesCdp,\n fetchDiscoveryTargets,\n selectHermesTarget,\n hasAmbiguousTarget,\n validateWebSocketUrl,\n LEGACY_SYNTHETIC_TITLE,\n} from './hermes/hermes-cdp.js';\nexport type {\n HermesTarget,\n HermesCdpResult,\n RunHermesCdpInput,\n} from './hermes/hermes-cdp.js';\n\nexport {\n HermesSession,\n getHermesSession,\n resetHermesSession,\n defaultAppIdForPlatform,\n DEFAULT_IOS_APP_ID,\n DEFAULT_ANDROID_APP_ID,\n DEFAULT_METRO_PORT,\n} from './hermes/session.js';\nexport type {\n HermesPlatform,\n HermesSessionOptions,\n HermesSessionResolved,\n HermesSessionResolveInput,\n} from './hermes/session.js';\n"]} |
+3
-0
@@ -18,2 +18,3 @@ "use strict"; | ||
| 'Call device_list_devices to see available devices, then device_select_device to choose one.', | ||
| 'The Hermes tools (hermes_cdp, hermes_targets) talk to the React Native Hermes JS runtime via Metro and require a DEBUG build with Metro running (Node 20 needs NODE_OPTIONS="--experimental-websocket").', | ||
| ].join('\n'), | ||
@@ -47,4 +48,6 @@ }); | ||
| (0, index_js_1.registerSelectDeviceTool)(server, backend); | ||
| (0, index_js_1.registerHermesCdpTool)(server, backend); | ||
| (0, index_js_1.registerHermesTargetsTool)(server, backend); | ||
| return server; | ||
| } | ||
| //# sourceMappingURL=server.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.cjs","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;AAgCA,0CA8CC;AA9ED,oEAAoE;AAGpE,gDA2B0B;AAE1B,SAAgB,eAAe,CAAC,OAA0B;IACxD,MAAM,MAAM,GAAG,IAAI,kBAAS,CAC1B,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,EAClD;QACE,YAAY,EAAE;YACZ,kEAAkE;YAClE,kFAAkF;YAClF,6DAA6D;YAC7D,4FAA4F;YAC5F,4EAA4E;YAC5E,yFAAyF;YACzF,oFAAoF;YACpF,iGAAiG;YACjG,6FAA6F;SAC9F,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CACF,CAAC;IAEF,IAAA,+BAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,qCAA0B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAA,2BAAgB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAA,4BAAiB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,8BAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAA,+BAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAA,8BAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAA,+BAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAA,kCAAuB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAA,sCAA2B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAA,mCAAwB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,IAAA,2BAAgB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAA,sCAA2B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,8BAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,sCAA2B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAA,uCAA4B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAA,qCAA0B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAA,kCAAuB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAA,mCAAwB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport type { LazyDeviceBackend } from './backends/index.js';\nimport {\n registerSnapshotTool,\n registerScreenshotTool,\n registerTapElementTool,\n registerTapCoordinatesTool,\n registerTypeTool,\n registerSwipeTool,\n registerLongPressTool,\n registerWaitForTool,\n registerAppStateTool,\n registerDeviceInfoTool,\n registerOpenAppTool,\n registerCloseAppTool,\n registerPressButtonTool,\n registerDismissKeyboardTool,\n registerDismissAlertTool,\n registerLogsTool,\n registerScrollToElementTool,\n registerAlertTextTool,\n registerWindowSizeTool,\n registerContextTool,\n registerClipboardTool,\n registerScreenRecordingTool,\n registerGenerateLocatorsTool,\n registerGetElementTextTool,\n registerListDevicesTool,\n registerSelectDeviceTool,\n} from './tools/index.js';\n\nexport function createMcpServer(backend: LazyDeviceBackend): McpServer {\n const server = new McpServer(\n { name: '@metamask/device-mcp', version: '0.0.0' },\n {\n instructions: [\n 'Mobile device interaction tools for iOS (IDB) and Android (ADB).',\n 'ALWAYS call device_snapshot before device_tap_element to see the current screen.',\n 'Never call device_snapshot twice without acting in between.',\n 'Elements are identified by accessibility label, resource-id, content-description, or text.',\n 'Element matching is fuzzy: partial text and case-insensitive matches work.',\n 'After typing, call device_dismiss_keyboard to reveal elements obscured by the keyboard.',\n 'Check for system alerts with device_snapshot before interacting with app elements.',\n 'If multiple devices are connected and no device is selected, tools will return the device list.',\n 'Call device_list_devices to see available devices, then device_select_device to choose one.',\n ].join('\\n'),\n },\n );\n\n registerSnapshotTool(server, backend);\n registerScreenshotTool(server, backend);\n registerDeviceInfoTool(server, backend);\n registerTapElementTool(server, backend);\n registerTapCoordinatesTool(server, backend);\n registerTypeTool(server, backend);\n registerSwipeTool(server, backend);\n registerLongPressTool(server, backend);\n registerWaitForTool(server, backend);\n registerAppStateTool(server, backend);\n registerOpenAppTool(server, backend);\n registerCloseAppTool(server, backend);\n registerPressButtonTool(server, backend);\n registerDismissKeyboardTool(server, backend);\n registerDismissAlertTool(server, backend);\n registerLogsTool(server, backend);\n registerScrollToElementTool(server, backend);\n registerAlertTextTool(server, backend);\n registerWindowSizeTool(server, backend);\n registerContextTool(server, backend);\n registerClipboardTool(server, backend);\n registerScreenRecordingTool(server, backend);\n registerGenerateLocatorsTool(server, backend);\n registerGetElementTextTool(server, backend);\n registerListDevicesTool(server, backend);\n registerSelectDeviceTool(server, backend);\n\n return server;\n}\n"]} | ||
| {"version":3,"file":"server.cjs","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;AAkCA,0CAiDC;AAnFD,oEAAoE;AAGpE,gDA6B0B;AAE1B,SAAgB,eAAe,CAAC,OAA0B;IACxD,MAAM,MAAM,GAAG,IAAI,kBAAS,CAC1B,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,EAClD;QACE,YAAY,EAAE;YACZ,kEAAkE;YAClE,kFAAkF;YAClF,6DAA6D;YAC7D,4FAA4F;YAC5F,4EAA4E;YAC5E,yFAAyF;YACzF,oFAAoF;YACpF,iGAAiG;YACjG,6FAA6F;YAC7F,0MAA0M;SAC3M,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CACF,CAAC;IAEF,IAAA,+BAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,qCAA0B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAA,2BAAgB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAA,4BAAiB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,8BAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAA,+BAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAA,8BAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAA,+BAAoB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAA,kCAAuB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAA,sCAA2B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAA,mCAAwB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,IAAA,2BAAgB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAA,sCAA2B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,iCAAsB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,IAAA,8BAAmB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,sCAA2B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAA,uCAA4B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAA,qCAA0B,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAA,kCAAuB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,IAAA,mCAAwB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,IAAA,gCAAqB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,IAAA,oCAAyB,EAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE3C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport type { LazyDeviceBackend } from './backends/index.js';\nimport {\n registerSnapshotTool,\n registerScreenshotTool,\n registerTapElementTool,\n registerTapCoordinatesTool,\n registerTypeTool,\n registerSwipeTool,\n registerLongPressTool,\n registerWaitForTool,\n registerAppStateTool,\n registerDeviceInfoTool,\n registerOpenAppTool,\n registerCloseAppTool,\n registerPressButtonTool,\n registerDismissKeyboardTool,\n registerDismissAlertTool,\n registerLogsTool,\n registerScrollToElementTool,\n registerAlertTextTool,\n registerWindowSizeTool,\n registerContextTool,\n registerClipboardTool,\n registerScreenRecordingTool,\n registerGenerateLocatorsTool,\n registerGetElementTextTool,\n registerListDevicesTool,\n registerSelectDeviceTool,\n registerHermesCdpTool,\n registerHermesTargetsTool,\n} from './tools/index.js';\n\nexport function createMcpServer(backend: LazyDeviceBackend): McpServer {\n const server = new McpServer(\n { name: '@metamask/device-mcp', version: '0.0.0' },\n {\n instructions: [\n 'Mobile device interaction tools for iOS (IDB) and Android (ADB).',\n 'ALWAYS call device_snapshot before device_tap_element to see the current screen.',\n 'Never call device_snapshot twice without acting in between.',\n 'Elements are identified by accessibility label, resource-id, content-description, or text.',\n 'Element matching is fuzzy: partial text and case-insensitive matches work.',\n 'After typing, call device_dismiss_keyboard to reveal elements obscured by the keyboard.',\n 'Check for system alerts with device_snapshot before interacting with app elements.',\n 'If multiple devices are connected and no device is selected, tools will return the device list.',\n 'Call device_list_devices to see available devices, then device_select_device to choose one.',\n 'The Hermes tools (hermes_cdp, hermes_targets) talk to the React Native Hermes JS runtime via Metro and require a DEBUG build with Metro running (Node 20 needs NODE_OPTIONS=\"--experimental-websocket\").',\n ].join('\\n'),\n },\n );\n\n registerSnapshotTool(server, backend);\n registerScreenshotTool(server, backend);\n registerDeviceInfoTool(server, backend);\n registerTapElementTool(server, backend);\n registerTapCoordinatesTool(server, backend);\n registerTypeTool(server, backend);\n registerSwipeTool(server, backend);\n registerLongPressTool(server, backend);\n registerWaitForTool(server, backend);\n registerAppStateTool(server, backend);\n registerOpenAppTool(server, backend);\n registerCloseAppTool(server, backend);\n registerPressButtonTool(server, backend);\n registerDismissKeyboardTool(server, backend);\n registerDismissAlertTool(server, backend);\n registerLogsTool(server, backend);\n registerScrollToElementTool(server, backend);\n registerAlertTextTool(server, backend);\n registerWindowSizeTool(server, backend);\n registerContextTool(server, backend);\n registerClipboardTool(server, backend);\n registerScreenRecordingTool(server, backend);\n registerGenerateLocatorsTool(server, backend);\n registerGetElementTextTool(server, backend);\n registerListDevicesTool(server, backend);\n registerSelectDeviceTool(server, backend);\n registerHermesCdpTool(server, backend);\n registerHermesTargetsTool(server, backend);\n\n return server;\n}\n"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.d.cts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gDAAgD;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,6BAA4B;AA8B7D,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CA8CrE"} | ||
| {"version":3,"file":"server.d.cts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gDAAgD;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,6BAA4B;AAgC7D,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAiDrE"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.d.mts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gDAAgD;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,6BAA4B;AA8B7D,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CA8CrE"} | ||
| {"version":3,"file":"server.d.mts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gDAAgD;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,6BAA4B;AAgC7D,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAiDrE"} |
+4
-1
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { registerSnapshotTool, registerScreenshotTool, registerTapElementTool, registerTapCoordinatesTool, registerTypeTool, registerSwipeTool, registerLongPressTool, registerWaitForTool, registerAppStateTool, registerDeviceInfoTool, registerOpenAppTool, registerCloseAppTool, registerPressButtonTool, registerDismissKeyboardTool, registerDismissAlertTool, registerLogsTool, registerScrollToElementTool, registerAlertTextTool, registerWindowSizeTool, registerContextTool, registerClipboardTool, registerScreenRecordingTool, registerGenerateLocatorsTool, registerGetElementTextTool, registerListDevicesTool, registerSelectDeviceTool } from "./tools/index.mjs"; | ||
| import { registerSnapshotTool, registerScreenshotTool, registerTapElementTool, registerTapCoordinatesTool, registerTypeTool, registerSwipeTool, registerLongPressTool, registerWaitForTool, registerAppStateTool, registerDeviceInfoTool, registerOpenAppTool, registerCloseAppTool, registerPressButtonTool, registerDismissKeyboardTool, registerDismissAlertTool, registerLogsTool, registerScrollToElementTool, registerAlertTextTool, registerWindowSizeTool, registerContextTool, registerClipboardTool, registerScreenRecordingTool, registerGenerateLocatorsTool, registerGetElementTextTool, registerListDevicesTool, registerSelectDeviceTool, registerHermesCdpTool, registerHermesTargetsTool } from "./tools/index.mjs"; | ||
| export function createMcpServer(backend) { | ||
@@ -15,2 +15,3 @@ const server = new McpServer({ name: '@metamask/device-mcp', version: '0.0.0' }, { | ||
| 'Call device_list_devices to see available devices, then device_select_device to choose one.', | ||
| 'The Hermes tools (hermes_cdp, hermes_targets) talk to the React Native Hermes JS runtime via Metro and require a DEBUG build with Metro running (Node 20 needs NODE_OPTIONS="--experimental-websocket").', | ||
| ].join('\n'), | ||
@@ -44,4 +45,6 @@ }); | ||
| registerSelectDeviceTool(server, backend); | ||
| registerHermesCdpTool(server, backend); | ||
| registerHermesTargetsTool(server, backend); | ||
| return server; | ||
| } | ||
| //# sourceMappingURL=server.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"server.mjs","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gDAAgD;AAGpE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,0BAAyB;AAE1B,MAAM,UAAU,eAAe,CAAC,OAA0B;IACxD,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,EAClD;QACE,YAAY,EAAE;YACZ,kEAAkE;YAClE,kFAAkF;YAClF,6DAA6D;YAC7D,4FAA4F;YAC5F,4EAA4E;YAC5E,yFAAyF;YACzF,oFAAoF;YACpF,iGAAiG;YACjG,6FAA6F;SAC9F,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CACF,CAAC;IAEF,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,4BAA4B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport type { LazyDeviceBackend } from './backends/index.js';\nimport {\n registerSnapshotTool,\n registerScreenshotTool,\n registerTapElementTool,\n registerTapCoordinatesTool,\n registerTypeTool,\n registerSwipeTool,\n registerLongPressTool,\n registerWaitForTool,\n registerAppStateTool,\n registerDeviceInfoTool,\n registerOpenAppTool,\n registerCloseAppTool,\n registerPressButtonTool,\n registerDismissKeyboardTool,\n registerDismissAlertTool,\n registerLogsTool,\n registerScrollToElementTool,\n registerAlertTextTool,\n registerWindowSizeTool,\n registerContextTool,\n registerClipboardTool,\n registerScreenRecordingTool,\n registerGenerateLocatorsTool,\n registerGetElementTextTool,\n registerListDevicesTool,\n registerSelectDeviceTool,\n} from './tools/index.js';\n\nexport function createMcpServer(backend: LazyDeviceBackend): McpServer {\n const server = new McpServer(\n { name: '@metamask/device-mcp', version: '0.0.0' },\n {\n instructions: [\n 'Mobile device interaction tools for iOS (IDB) and Android (ADB).',\n 'ALWAYS call device_snapshot before device_tap_element to see the current screen.',\n 'Never call device_snapshot twice without acting in between.',\n 'Elements are identified by accessibility label, resource-id, content-description, or text.',\n 'Element matching is fuzzy: partial text and case-insensitive matches work.',\n 'After typing, call device_dismiss_keyboard to reveal elements obscured by the keyboard.',\n 'Check for system alerts with device_snapshot before interacting with app elements.',\n 'If multiple devices are connected and no device is selected, tools will return the device list.',\n 'Call device_list_devices to see available devices, then device_select_device to choose one.',\n ].join('\\n'),\n },\n );\n\n registerSnapshotTool(server, backend);\n registerScreenshotTool(server, backend);\n registerDeviceInfoTool(server, backend);\n registerTapElementTool(server, backend);\n registerTapCoordinatesTool(server, backend);\n registerTypeTool(server, backend);\n registerSwipeTool(server, backend);\n registerLongPressTool(server, backend);\n registerWaitForTool(server, backend);\n registerAppStateTool(server, backend);\n registerOpenAppTool(server, backend);\n registerCloseAppTool(server, backend);\n registerPressButtonTool(server, backend);\n registerDismissKeyboardTool(server, backend);\n registerDismissAlertTool(server, backend);\n registerLogsTool(server, backend);\n registerScrollToElementTool(server, backend);\n registerAlertTextTool(server, backend);\n registerWindowSizeTool(server, backend);\n registerContextTool(server, backend);\n registerClipboardTool(server, backend);\n registerScreenRecordingTool(server, backend);\n registerGenerateLocatorsTool(server, backend);\n registerGetElementTextTool(server, backend);\n registerListDevicesTool(server, backend);\n registerSelectDeviceTool(server, backend);\n\n return server;\n}\n"]} | ||
| {"version":3,"file":"server.mjs","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gDAAgD;AAGpE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,2BAA2B,EAC3B,wBAAwB,EACxB,gBAAgB,EAChB,2BAA2B,EAC3B,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EAC1B,0BAAyB;AAE1B,MAAM,UAAU,eAAe,CAAC,OAA0B;IACxD,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,EAClD;QACE,YAAY,EAAE;YACZ,kEAAkE;YAClE,kFAAkF;YAClF,6DAA6D;YAC7D,4FAA4F;YAC5F,4EAA4E;YAC5E,yFAAyF;YACzF,oFAAoF;YACpF,iGAAiG;YACjG,6FAA6F;YAC7F,0MAA0M;SAC3M,CAAC,IAAI,CAAC,IAAI,CAAC;KACb,CACF,CAAC;IAEF,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,4BAA4B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,yBAAyB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE3C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport type { LazyDeviceBackend } from './backends/index.js';\nimport {\n registerSnapshotTool,\n registerScreenshotTool,\n registerTapElementTool,\n registerTapCoordinatesTool,\n registerTypeTool,\n registerSwipeTool,\n registerLongPressTool,\n registerWaitForTool,\n registerAppStateTool,\n registerDeviceInfoTool,\n registerOpenAppTool,\n registerCloseAppTool,\n registerPressButtonTool,\n registerDismissKeyboardTool,\n registerDismissAlertTool,\n registerLogsTool,\n registerScrollToElementTool,\n registerAlertTextTool,\n registerWindowSizeTool,\n registerContextTool,\n registerClipboardTool,\n registerScreenRecordingTool,\n registerGenerateLocatorsTool,\n registerGetElementTextTool,\n registerListDevicesTool,\n registerSelectDeviceTool,\n registerHermesCdpTool,\n registerHermesTargetsTool,\n} from './tools/index.js';\n\nexport function createMcpServer(backend: LazyDeviceBackend): McpServer {\n const server = new McpServer(\n { name: '@metamask/device-mcp', version: '0.0.0' },\n {\n instructions: [\n 'Mobile device interaction tools for iOS (IDB) and Android (ADB).',\n 'ALWAYS call device_snapshot before device_tap_element to see the current screen.',\n 'Never call device_snapshot twice without acting in between.',\n 'Elements are identified by accessibility label, resource-id, content-description, or text.',\n 'Element matching is fuzzy: partial text and case-insensitive matches work.',\n 'After typing, call device_dismiss_keyboard to reveal elements obscured by the keyboard.',\n 'Check for system alerts with device_snapshot before interacting with app elements.',\n 'If multiple devices are connected and no device is selected, tools will return the device list.',\n 'Call device_list_devices to see available devices, then device_select_device to choose one.',\n 'The Hermes tools (hermes_cdp, hermes_targets) talk to the React Native Hermes JS runtime via Metro and require a DEBUG build with Metro running (Node 20 needs NODE_OPTIONS=\"--experimental-websocket\").',\n ].join('\\n'),\n },\n );\n\n registerSnapshotTool(server, backend);\n registerScreenshotTool(server, backend);\n registerDeviceInfoTool(server, backend);\n registerTapElementTool(server, backend);\n registerTapCoordinatesTool(server, backend);\n registerTypeTool(server, backend);\n registerSwipeTool(server, backend);\n registerLongPressTool(server, backend);\n registerWaitForTool(server, backend);\n registerAppStateTool(server, backend);\n registerOpenAppTool(server, backend);\n registerCloseAppTool(server, backend);\n registerPressButtonTool(server, backend);\n registerDismissKeyboardTool(server, backend);\n registerDismissAlertTool(server, backend);\n registerLogsTool(server, backend);\n registerScrollToElementTool(server, backend);\n registerAlertTextTool(server, backend);\n registerWindowSizeTool(server, backend);\n registerContextTool(server, backend);\n registerClipboardTool(server, backend);\n registerScreenRecordingTool(server, backend);\n registerGenerateLocatorsTool(server, backend);\n registerGetElementTextTool(server, backend);\n registerListDevicesTool(server, backend);\n registerSelectDeviceTool(server, backend);\n registerHermesCdpTool(server, backend);\n registerHermesTargetsTool(server, backend);\n\n return server;\n}\n"]} |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.registerSelectDeviceTool = exports.registerListDevicesTool = exports.registerGetElementTextTool = exports.registerGenerateLocatorsTool = exports.registerScreenRecordingTool = exports.registerClipboardTool = exports.registerContextTool = exports.registerWindowSizeTool = exports.registerAlertTextTool = exports.registerScrollToElementTool = exports.registerLogsTool = exports.registerDismissAlertTool = exports.registerDismissKeyboardTool = exports.registerPressButtonTool = exports.registerCloseAppTool = exports.registerOpenAppTool = exports.registerDeviceInfoTool = exports.registerAppStateTool = exports.registerWaitForTool = exports.registerLongPressTool = exports.registerSwipeTool = exports.registerTypeTool = exports.registerTapCoordinatesTool = exports.registerTapElementTool = exports.registerScreenshotTool = exports.registerSnapshotTool = void 0; | ||
| exports.registerHermesTargetsTool = exports.registerHermesCdpTool = exports.registerSelectDeviceTool = exports.registerListDevicesTool = exports.registerGetElementTextTool = exports.registerGenerateLocatorsTool = exports.registerScreenRecordingTool = exports.registerClipboardTool = exports.registerContextTool = exports.registerWindowSizeTool = exports.registerAlertTextTool = exports.registerScrollToElementTool = exports.registerLogsTool = exports.registerDismissAlertTool = exports.registerDismissKeyboardTool = exports.registerPressButtonTool = exports.registerCloseAppTool = exports.registerOpenAppTool = exports.registerDeviceInfoTool = exports.registerAppStateTool = exports.registerWaitForTool = exports.registerLongPressTool = exports.registerSwipeTool = exports.registerTypeTool = exports.registerTapCoordinatesTool = exports.registerTapElementTool = exports.registerScreenshotTool = exports.registerSnapshotTool = void 0; | ||
| var snapshot_js_1 = require("./snapshot.cjs"); | ||
@@ -56,2 +56,6 @@ Object.defineProperty(exports, "registerSnapshotTool", { enumerable: true, get: function () { return snapshot_js_1.registerSnapshotTool; } }); | ||
| Object.defineProperty(exports, "registerSelectDeviceTool", { enumerable: true, get: function () { return select_device_js_1.registerSelectDeviceTool; } }); | ||
| var hermes_cdp_js_1 = require("./hermes-cdp.cjs"); | ||
| Object.defineProperty(exports, "registerHermesCdpTool", { enumerable: true, get: function () { return hermes_cdp_js_1.registerHermesCdpTool; } }); | ||
| var hermes_targets_js_1 = require("./hermes-targets.cjs"); | ||
| Object.defineProperty(exports, "registerHermesTargetsTool", { enumerable: true, get: function () { return hermes_targets_js_1.registerHermesTargetsTool; } }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":";;;AAAA,8CAAqD;AAA5C,mHAAA,oBAAoB,OAAA;AAC7B,kDAAyD;AAAhD,uHAAA,sBAAsB,OAAA;AAC/B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4DAAkE;AAAzD,gIAAA,0BAA0B,OAAA;AACnC,gDAAkD;AAAzC,gHAAA,gBAAgB,OAAA;AACzB,wCAA+C;AAAtC,6GAAA,iBAAiB,OAAA;AAC1B,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA;AACjC,sCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AACzB,gEAAqE;AAA5D,mIAAA,2BAA2B,OAAA;AACpC,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4CAAmD;AAA1C,iHAAA,mBAAmB,OAAA;AAC5B,gDAAuD;AAA9C,qHAAA,qBAAqB,OAAA;AAC9B,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,gEAAsE;AAA7D,oIAAA,4BAA4B,OAAA;AACrC,8DAAmE;AAA1D,iIAAA,0BAA0B,OAAA;AACnC,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\n"]} | ||
| {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":";;;AAAA,8CAAqD;AAA5C,mHAAA,oBAAoB,OAAA;AAC7B,kDAAyD;AAAhD,uHAAA,sBAAsB,OAAA;AAC/B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4DAAkE;AAAzD,gIAAA,0BAA0B,OAAA;AACnC,gDAAkD;AAAzC,gHAAA,gBAAgB,OAAA;AACzB,wCAA+C;AAAtC,6GAAA,iBAAiB,OAAA;AAC1B,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA;AACjC,sCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AACzB,gEAAqE;AAA5D,mIAAA,2BAA2B,OAAA;AACpC,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4CAAmD;AAA1C,iHAAA,mBAAmB,OAAA;AAC5B,gDAAuD;AAA9C,qHAAA,qBAAqB,OAAA;AAC9B,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,gEAAsE;AAA7D,oIAAA,4BAA4B,OAAA;AACrC,8DAAmE;AAA1D,iIAAA,0BAA0B,OAAA;AACnC,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA;AACjC,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,0DAAgE;AAAvD,8HAAA,yBAAyB,OAAA","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\nexport { registerHermesCdpTool } from './hermes-cdp.js';\nexport { registerHermesTargetsTool } from './hermes-targets.js';\n"]} |
@@ -27,2 +27,4 @@ export { registerSnapshotTool } from "./snapshot.cjs"; | ||
| export { registerSelectDeviceTool } from "./select-device.cjs"; | ||
| export { registerHermesCdpTool } from "./hermes-cdp.cjs"; | ||
| export { registerHermesTargetsTool } from "./hermes-targets.cjs"; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B"} | ||
| {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,yBAAyB,EAAE,6BAA4B"} |
@@ -27,2 +27,4 @@ export { registerSnapshotTool } from "./snapshot.mjs"; | ||
| export { registerSelectDeviceTool } from "./select-device.mjs"; | ||
| export { registerHermesCdpTool } from "./hermes-cdp.mjs"; | ||
| export { registerHermesTargetsTool } from "./hermes-targets.mjs"; | ||
| //# sourceMappingURL=index.d.mts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B"} | ||
| {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,yBAAyB,EAAE,6BAA4B"} |
@@ -27,2 +27,4 @@ export { registerSnapshotTool } from "./snapshot.mjs"; | ||
| export { registerSelectDeviceTool } from "./select-device.mjs"; | ||
| export { registerHermesCdpTool } from "./hermes-cdp.mjs"; | ||
| export { registerHermesTargetsTool } from "./hermes-targets.mjs"; | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\n"]} | ||
| {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,yBAAyB,EAAE,6BAA4B","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\nexport { registerHermesCdpTool } from './hermes-cdp.js';\nexport { registerHermesTargetsTool } from './hermes-targets.js';\n"]} |
@@ -20,5 +20,5 @@ "use strict"; | ||
| try { | ||
| const result = await backend.screenshot(outputPath); | ||
| return { | ||
| content: [ | ||
| const result = await backend.screenshot(outputPath, { encode: true }); | ||
| const imageContent = result.data | ||
| ? [ | ||
| { | ||
@@ -29,2 +29,7 @@ type: 'image', | ||
| }, | ||
| ] | ||
| : []; | ||
| return { | ||
| content: [ | ||
| ...imageContent, | ||
| { | ||
@@ -31,0 +36,0 @@ type: 'text', |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"screenshot.cjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":";;AAMA,wDA0CC;AA/CD,6BAAwB;AAExB,4CAA0C;AAG1C,SAAgB,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,OAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YACpD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE;qBACnC;oBACD;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;4BACf,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,EAAE;4BACtC,CAAC,CAAC,qBAAqB;qBAC1B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,uBAAW,EAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerScreenshotTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_screenshot',\n {\n title: 'Screenshot',\n description:\n 'Capture a screenshot of the current device screen. ' +\n 'Returns base64-encoded PNG data. Optionally saves to a file path.',\n inputSchema: {\n outputPath: z\n .string()\n .optional()\n .describe('File path to save the screenshot (optional)'),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ outputPath }) => {\n try {\n const result = await backend.screenshot(outputPath);\n return {\n content: [\n {\n type: 'image' as const,\n data: result.data,\n mimeType: `image/${result.format}`,\n },\n {\n type: 'text' as const,\n text: result.path\n ? `Screenshot saved to ${result.path}`\n : 'Screenshot captured',\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]} | ||
| {"version":3,"file":"screenshot.cjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":";;AAMA,wDA+CC;AApDD,6BAAwB;AAExB,4CAA0C;AAG1C,SAAgB,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,OAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI;gBAC9B,CAAC,CAAC;oBACE;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE;qBACnC;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,EAAE;oBACP,GAAG,YAAY;oBACf;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;4BACf,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,EAAE;4BACtC,CAAC,CAAC,qBAAqB;qBAC1B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,uBAAW,EAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerScreenshotTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_screenshot',\n {\n title: 'Screenshot',\n description:\n 'Capture a screenshot of the current device screen. ' +\n 'Returns base64-encoded PNG data. Optionally saves to a file path.',\n inputSchema: {\n outputPath: z\n .string()\n .optional()\n .describe('File path to save the screenshot (optional)'),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ outputPath }) => {\n try {\n const result = await backend.screenshot(outputPath, { encode: true });\n const imageContent = result.data\n ? [\n {\n type: 'image' as const,\n data: result.data,\n mimeType: `image/${result.format}`,\n },\n ]\n : [];\n return {\n content: [\n ...imageContent,\n {\n type: 'text' as const,\n text: result.path\n ? `Screenshot saved to ${result.path}`\n : 'Screenshot captured',\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"screenshot.d.cts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAuCN"} | ||
| {"version":3,"file":"screenshot.d.cts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CA4CN"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"screenshot.d.mts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CAuCN"} | ||
| {"version":3,"file":"screenshot.d.mts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CA4CN"} |
@@ -17,5 +17,5 @@ import { z } from "zod"; | ||
| try { | ||
| const result = await backend.screenshot(outputPath); | ||
| return { | ||
| content: [ | ||
| const result = await backend.screenshot(outputPath, { encode: true }); | ||
| const imageContent = result.data | ||
| ? [ | ||
| { | ||
@@ -26,2 +26,7 @@ type: 'image', | ||
| }, | ||
| ] | ||
| : []; | ||
| return { | ||
| content: [ | ||
| ...imageContent, | ||
| { | ||
@@ -28,0 +33,0 @@ type: 'text', |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"screenshot.mjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAG1C,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YACpD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE;qBACnC;oBACD;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;4BACf,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,EAAE;4BACtC,CAAC,CAAC,qBAAqB;qBAC1B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerScreenshotTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_screenshot',\n {\n title: 'Screenshot',\n description:\n 'Capture a screenshot of the current device screen. ' +\n 'Returns base64-encoded PNG data. Optionally saves to a file path.',\n inputSchema: {\n outputPath: z\n .string()\n .optional()\n .describe('File path to save the screenshot (optional)'),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ outputPath }) => {\n try {\n const result = await backend.screenshot(outputPath);\n return {\n content: [\n {\n type: 'image' as const,\n data: result.data,\n mimeType: `image/${result.format}`,\n },\n {\n type: 'text' as const,\n text: result.path\n ? `Screenshot saved to ${result.path}`\n : 'Screenshot captured',\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]} | ||
| {"version":3,"file":"screenshot.mjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAG1C,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI;gBAC9B,CAAC,CAAC;oBACE;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE;qBACnC;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,EAAE;oBACP,GAAG,YAAY;oBACf;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;4BACf,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,EAAE;4BACtC,CAAC,CAAC,qBAAqB;qBAC1B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerScreenshotTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_screenshot',\n {\n title: 'Screenshot',\n description:\n 'Capture a screenshot of the current device screen. ' +\n 'Returns base64-encoded PNG data. Optionally saves to a file path.',\n inputSchema: {\n outputPath: z\n .string()\n .optional()\n .describe('File path to save the screenshot (optional)'),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ outputPath }) => {\n try {\n const result = await backend.screenshot(outputPath, { encode: true });\n const imageContent = result.data\n ? [\n {\n type: 'image' as const,\n data: result.data,\n mimeType: `image/${result.format}`,\n },\n ]\n : [];\n return {\n content: [\n ...imageContent,\n {\n type: 'text' as const,\n text: result.path\n ? `Screenshot saved to ${result.path}`\n : 'Screenshot captured',\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]} |
+1
-1
| { | ||
| "name": "@metamask/device-mcp", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "description": "MCP server for mobile device interaction — iOS (IDB), Android (ADB), and Appium/BrowserStack", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/MetaMask/device-mcp#readme", |
+55
-2
@@ -21,2 +21,3 @@ # @metamask/device-mcp | ||
| - **Remote/BrowserStack**: No local tools needed — connects via Appium W3C WebDriver HTTP | ||
| - **Hermes CDP** (debugging the React Native JS runtime): uses the global `WebSocket` API. Node 22+ works out of the box; **Node 20 requires launching with `NODE_OPTIONS="--experimental-websocket"`**. | ||
@@ -161,2 +162,9 @@ ## Installation | ||
| ### Hermes CDP | ||
| | Tool | Description | | ||
| | ---------------- | ------------------------------------------------------------------------- | | ||
| | `hermes_cdp` | Speak raw Chrome DevTools Protocol to the React Native Hermes JS runtime. | | ||
| | `hermes_targets` | List and diagnose the debuggable Hermes targets exposed by Metro. | | ||
| ### Element Identification | ||
@@ -192,2 +200,47 @@ | ||
| ## Hermes CDP | ||
| Beyond native UI automation (idb/adb), the server can speak [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) (CDP) directly to the **React Native Hermes** JS runtime of a running app via Metro's inspector proxy. This lets an agent evaluate JavaScript, inspect runtime state, and diagnose the app's JS layer — complementing the native tools. Works on **both iOS and Android** (the transport is identical HTTP/WebSocket to Metro; only the default `appId` differs). | ||
| This is _React Native Hermes CDP via Metro's inspector proxy_ — not WebView/browser CDP, and not the iOS WebKit Inspector Protocol. The app's Hermes runtime connects out to Metro; Metro exposes debuggable targets over `http://localhost:<metroPort>/json` and a per-target `webSocketDebuggerUrl`, and the server speaks real CDP over that WebSocket. | ||
| ### Prerequisites | ||
| - A **DEBUG build with Metro running** on the inspector port. Release builds expose no inspector (`/json` returns `[]`). | ||
| - The global `WebSocket` API: **Node 22+ works out of the box**; **Node 20 requires launching with `NODE_OPTIONS="--experimental-websocket"`** (see [Requirements](#requirements)). | ||
| ### Tools | ||
| | Tool | Description | | ||
| | ---------------- | ------------------------------------------------------------------------- | | ||
| | `hermes_cdp` | Speak raw Chrome DevTools Protocol to the React Native Hermes JS runtime. | | ||
| | `hermes_targets` | List and diagnose the debuggable Hermes targets exposed by Metro. | | ||
| Example `hermes_cdp` call: method `Runtime.evaluate` with params `{"expression":"1+1","returnByValue":true}` — the raw response nests the value at `result.result.value`. | ||
| ### Safety model | ||
| A single Metro can have multiple apps/simulators registered, so executing CDP against the wrong target could run code in the wrong workspace. The server applies five fail-closed checks before executing: | ||
| 1. **Strict `appId` match** — only targets whose `appId` equals the expected id (no substring, no `targets[0]` fallback). | ||
| 2. **`HermesInternal` identity probe** — evaluates `HermesInternal.getRuntimeProperties()` before the user's method; non-Hermes targets fail closed. | ||
| 3. **Device pin** — pins `reactNative.logicalDeviceId` on first success; later calls are filtered to the pin. | ||
| 4. **Multi-device fail-closed** — multiple distinct logical devices (or any missing the field) fail closed rather than guess. | ||
| 5. **WebSocket URL validation** — protocol must be `ws:`, hostname must be loopback, and port must equal the resolved Metro port. | ||
| The synthetic legacy page (`React Native Experimental (Improved Chrome Reloads)`) is filtered out, and destructive methods `Runtime.terminateExecution` and `Inspector.detached` are blocked. | ||
| ### Configuration | ||
| - **appId** defaults to `io.metamask.MetaMask` (iOS) / `io.metamask` (Android). Override globally via the `HERMES_APP_ID` env var or per-call via the tool's `appId` param. Android users not on the default must pass `appId` or set `HERMES_APP_ID`; `hermes_targets` with `all: true` aids discovery of the real appId. | ||
| - **Metro port** defaults to `8081`. Override via the `HERMES_METRO_PORT` env var or per-call via the tool's `metroPort` param. | ||
| ## File Output & Safety | ||
| `device_screenshot` and `device_screen_recording` write image/video files to disk. These artifacts can contain sensitive on-screen content (seed phrases, private keys, balances), so writes are hardened: | ||
| - **No `outputPath`** — the file is written into a private, per-process temporary directory (`0700`) with an unpredictable name, rather than a predictable `/tmp/device-mcp-*-<timestamp>` path (which is exposed to symlink and information-disclosure attacks on shared hosts). | ||
| - **With `outputPath`** — the path is resolved to an absolute path and written owner-only (`0600`); a symlink already present at the destination is never followed. | ||
| - **`DEVICE_MCP_OUTPUT_DIR`** — set this to confine every caller-supplied `outputPath` to a single directory. Any path resolving outside it is rejected. Leave it unset to allow writing to any path the server process can access. | ||
| ## MCP Client Configuration | ||
@@ -266,3 +319,3 @@ | ||
| │ ├── index.ts # Entry point — lazy backend, stdio MCP server | ||
| │ ├── server.ts # MCP server — registers 25 tools | ||
| │ ├── server.ts # MCP server — registers 28 tools | ||
| │ ├── backends/ | ||
@@ -276,3 +329,3 @@ │ │ ├── types.ts # DeviceBackend interface | ||
| │ │ └── index.ts # createBackend() + createLazyBackend() factory | ||
| │ ├── tools/ # One file per MCP tool (25 tools) | ||
| │ ├── tools/ # One file per MCP tool (28 tools) | ||
| │ │ ├── list-devices.ts # device_list_devices — enumerate connected devices | ||
@@ -279,0 +332,0 @@ │ │ ├── select-device.ts # device_select_device — choose device for session |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
1235311
39.4%382
11.7%9181
37.58%348
17.97%33
50%20
11.11%