feat(extension): auto-close automation window after command completes
- Add 'close-window' action to extension protocol and background.ts - Add Page.closeWindow() method to send close-window command - browserSession() now closes automation window in cleanup - Remove domain pre-navigation + 2s wait from main.ts (CDP handles cross-domain cookies natively, no same-origin workaround needed) - Net effect: commands run faster, no stale windows left behind
This commit is contained in:
1 parent
fde618063f
commit
2f6d28a3e9
7 files changed
+46
-13
No files matched your search
Vendored
+14
@@ -219,6 +219,7 @@ async function handleCommand(cmd) {
|
||||
case "tabs": return await handleTabs(cmd);
|
||||
case "cookies": return await handleCookies(cmd);
|
||||
case "screenshot": return await handleScreenshot(cmd);
|
||||
case "close-window": return await handleCloseWindow(cmd);
|
||||
default: return {
|
||||
id: cmd.id,
|
||||
ok: false,
|
||||
@@ -447,4 +448,17 @@ async function handleScreenshot(cmd) {
|
||||
};
|
||||
}
|
||||
}
|
||||
async function handleCloseWindow(cmd) {
|
||||
if (automationWindowId !== null) {
|
||||
try {
|
||||
await chrome.windows.remove(automationWindowId);
|
||||
} catch {}
|
||||
automationWindowId = null;
|
||||
}
|
||||
return {
|
||||
id: cmd.id,
|
||||
ok: true,
|
||||
data: { closed: true }
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
@@ -165,6 +165,8 @@ async function handleCommand(cmd: Command): Promise<Result> {
|
||||
return await handleCookies(cmd);
|
||||
case 'screenshot':
|
||||
return await handleScreenshot(cmd);
|
||||
case 'close-window':
|
||||
return await handleCloseWindow(cmd);
|
||||
default:
|
||||
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
|
||||
}
|
||||
@@ -332,3 +334,15 @@ async function handleScreenshot(cmd: Command): Promise<Result> {
|
||||
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCloseWindow(cmd: Command): Promise<Result> {
|
||||
if (automationWindowId !== null) {
|
||||
try {
|
||||
await chrome.windows.remove(automationWindowId);
|
||||
} catch {
|
||||
// Window may already be closed
|
||||
}
|
||||
automationWindowId = null;
|
||||
}
|
||||
return { id: cmd.id, ok: true, data: { closed: true } };
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* Everything else is just JS code sent via 'exec'.
|
||||
*/
|
||||
|
||||
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot';
|
||||
export type Action = 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window';
|
||||
|
||||
export interface Command {
|
||||
/** Unique request ID */
|
||||
|
||||
@@ -15,7 +15,7 @@ function generateId(): string {
|
||||
|
||||
export interface DaemonCommand {
|
||||
id: string;
|
||||
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot';
|
||||
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window';
|
||||
tabId?: number;
|
||||
code?: string;
|
||||
url?: string;
|
||||
|
||||
@@ -37,6 +37,15 @@ export class Page implements IPage {
|
||||
}
|
||||
}
|
||||
|
||||
/** Close the automation window in the extension */
|
||||
async closeWindow(): Promise<void> {
|
||||
try {
|
||||
await sendCommand('close-window', {});
|
||||
} catch {
|
||||
// Window may already be closed or daemon may be down
|
||||
}
|
||||
}
|
||||
|
||||
async evaluate(js: string): Promise<any> {
|
||||
const code = wrapForEval(js);
|
||||
return sendCommand('exec', { code, ...this._tabOpt() });
|
||||
|
||||
@@ -193,12 +193,6 @@ for (const [, cmd] of registry) {
|
||||
let result: any;
|
||||
if (cmd.browser) {
|
||||
result = await browserSession(BrowserBridge, async (page) => {
|
||||
// Cookie/header strategies require same-origin context for credentialed fetch.
|
||||
// In CDP mode the active tab may be on an unrelated domain, causing CORS failures.
|
||||
// Navigate to the command's domain first (mirrors cascade command behavior).
|
||||
if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
|
||||
try { await page.goto(`https://${cmd.domain}`); await page.wait(2); } catch {}
|
||||
}
|
||||
return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });
|
||||
});
|
||||
} else { result = await executeCommand(cmd, null, kwargs, actionOpts.verbose); }
|
||||
|
||||
+7
-5
@@ -1,8 +1,5 @@
|
||||
/**
|
||||
* Runtime utilities: timeouts and browser session management.
|
||||
*/
|
||||
|
||||
import type { IPage } from './types.js';
|
||||
import type { Page } from './browser/page.js';
|
||||
|
||||
export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
|
||||
export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '60', 10);
|
||||
@@ -45,7 +42,12 @@ export async function browserSession<T>(
|
||||
const mcp = new BrowserFactory();
|
||||
try {
|
||||
const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT });
|
||||
return await fn(page);
|
||||
const result = await fn(page);
|
||||
// Close the automation window after command completes
|
||||
if (typeof (page as Page).closeWindow === 'function') {
|
||||
await (page as Page).closeWindow();
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
await mcp.close().catch(() => {});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user