// Download orchestration: validate session/tab ownership, then delegate one // browser-global chrome.downloads transaction to download-capture.ts. import type { SessionManager } from "@/session-manager/manager"; import type { ClickParams, DownloadParams, DownloadResult, RpcError } from "@/transport/types"; import { captureBrowserDownload, chromeDownloadsApi, type DownloadsApi } from "./download-capture"; import { handleClick, type InteractionDeps, resolveBackendNode } from "./interaction"; import { enforceAgentWindow, isRpcError, lookupSession, resolveTargetTab } from "./shared"; let downloadActive = false; export type { DownloadsApi } from "./download-capture"; export interface DownloadDeps extends InteractionDeps { downloads?: DownloadsApi; } export async function handleDownload( manager: SessionManager, params: DownloadParams, deps: DownloadDeps, ): Promise { if (downloadActive) return { code: "invalid_params", message: "another bsk download is active" }; downloadActive = true; try { const ctx = lookupSession(manager, params, "download"); if (isRpcError(ctx)) return ctx; const target = await resolveTargetTab(manager, ctx, params.tab_id, deps.tabsApi); if (isRpcError(target)) return target; const denied = enforceAgentWindow(ctx, target, "download"); if (denied) return denied; if (!params.browser_relative_dir) { return { code: "invalid_params", message: "download requires a daemon capability directory" }; } const address = await resolveBackendNode(deps.cdp, ctx, target, params, "download"); if (isRpcError(address)) return address; const capture = await captureBrowserDownload({ cdp: deps.cdp, target: address.cdpTarget, downloads: deps.downloads ?? chromeDownloadsApi, browserRelativeDir: params.browser_relative_dir, timeoutMs: params.timeout_ms ?? 120_000, signal: deps.signal, trigger: () => { const clickParams: ClickParams = { session_id: params.session_id, ref: params.ref, selector: params.selector, tab_id: params.tab_id, timeout_ms: params.timeout_ms, }; return handleClick(manager, clickParams, deps); }, }); if (isRpcError(capture)) return capture; const { click, item } = capture; return { tab_id: target.tabId, used_ref: click.used_ref, used_selector: click.used_selector, suggested_filename: item.filename.split(/[\\/]/).pop() ?? "download", byte_size: item.fileSize >= 0 ? item.fileSize : item.totalBytes, mime: item.mime || undefined, danger: item.danger, browser_path: item.filename, }; } finally { downloadActive = false; } }