mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document browser server context
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Browser profile availability operations: reachability probes, managed Chrome
|
||||
* launch/restart, Chrome MCP attach, and profile stop handling.
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import { resolveCdpReachabilityPolicy } from "./cdp-reachability-policy.js";
|
||||
import {
|
||||
@@ -133,6 +137,7 @@ function assertManagedLaunchNotCoolingDown(profileName: string, profileState: Pr
|
||||
);
|
||||
}
|
||||
|
||||
/** Builds reachability, ensure, and stop operations for one resolved browser profile. */
|
||||
export function createProfileAvailability({
|
||||
opts,
|
||||
profile,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Shared Chrome module mocks for Browser server-context tests.
|
||||
*/
|
||||
import { vi } from "vitest";
|
||||
import { installChromeUserDataDirHooks } from "./chrome-user-data-dir.test-harness.js";
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* Timing and size constants for Browser profile/tab runtime operations.
|
||||
*/
|
||||
import { DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS } from "./constants.js";
|
||||
|
||||
/** Maximum managed page tabs kept open before best-effort cleanup starts. */
|
||||
export const MANAGED_BROWSER_PAGE_TAB_LIMIT = 8;
|
||||
|
||||
export const OPEN_TAB_DISCOVERY_WINDOW_MS = 2000;
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* Browser profile lifecycle helpers shared by availability, reset, and runtime
|
||||
* teardown.
|
||||
*/
|
||||
import type { ResolvedBrowserProfile } from "./config.js";
|
||||
import { getBrowserProfileCapabilities } from "./profile-capabilities.js";
|
||||
import { getPwAiModule } from "./pw-ai-module.js";
|
||||
|
||||
/** Resolves how an idle stop should behave for local, remote, or attach-only profiles. */
|
||||
export function resolveIdleProfileStopOutcome(profile: ResolvedBrowserProfile): {
|
||||
stopped: boolean;
|
||||
closePlaywright: boolean;
|
||||
@@ -19,6 +24,7 @@ export function resolveIdleProfileStopOutcome(profile: ResolvedBrowserProfile):
|
||||
};
|
||||
}
|
||||
|
||||
/** Closes cached Playwright CDP connections for one profile without requiring the module. */
|
||||
export async function closePlaywrightBrowserConnectionForProfile(cdpUrl?: string): Promise<void> {
|
||||
try {
|
||||
const mod = await getPwAiModule({ mode: "soft" });
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* Lazy-loaded dependency bundle for remote-profile tab operation tests.
|
||||
*/
|
||||
import { afterEach, beforeEach, vi } from "vitest";
|
||||
|
||||
/** Modules and helpers shared by remote-profile tab operation tests. */
|
||||
export type RemoteProfileTestDeps = {
|
||||
cdpModule: typeof import("./cdp.js");
|
||||
chromeModule: typeof import("./chrome.js");
|
||||
@@ -16,6 +20,7 @@ export type RemoteProfileTestDeps = {
|
||||
|
||||
let remoteProfileTestDepsPromise: Promise<RemoteProfileTestDeps> | undefined;
|
||||
|
||||
/** Loads remote-profile tab operation dependencies after Chrome mocks are installed. */
|
||||
export async function loadRemoteProfileTestDeps(): Promise<RemoteProfileTestDeps> {
|
||||
remoteProfileTestDepsPromise ??= (async () => {
|
||||
await import("./server-context.chrome-test-harness.js");
|
||||
@@ -49,6 +54,7 @@ export async function loadRemoteProfileTestDeps(): Promise<RemoteProfileTestDeps
|
||||
return await remoteProfileTestDepsPromise;
|
||||
}
|
||||
|
||||
/** Installs per-test mock reset and Playwright connection cleanup. */
|
||||
export function installRemoteProfileTestLifecycle(deps: RemoteProfileTestDeps): void {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Remote-tab operation harness for Browser server-context tests.
|
||||
*/
|
||||
import { vi } from "vitest";
|
||||
import { withBrowserFetchPreconnect } from "../../test-fetch.js";
|
||||
import { resolveCdpControlPolicy } from "./cdp-reachability-policy.js";
|
||||
@@ -6,8 +9,10 @@ import { createProfileSelectionOps } from "./server-context.selection.js";
|
||||
import { createProfileTabOps } from "./server-context.tab-ops.js";
|
||||
import type { BrowserServerState, ProfileRuntimeState } from "./server-context.types.js";
|
||||
|
||||
/** Original global fetch restored between remote-tab harness tests. */
|
||||
export const originalFetch = globalThis.fetch;
|
||||
|
||||
/** Creates Browser server state for remote or local profile tab tests. */
|
||||
export function makeState(
|
||||
profile: "remote" | "openclaw",
|
||||
): BrowserServerState & { profiles: Map<string, { lastTargetId?: string | null }> } {
|
||||
@@ -95,6 +100,7 @@ function resolveProfileForTest(
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates a minimal Browser route context for profile operation tests. */
|
||||
export function createTestBrowserRouteContext(opts: { getState: () => BrowserServerState }) {
|
||||
const forProfile = (profileName?: string) => {
|
||||
const state = opts.getState();
|
||||
@@ -125,6 +131,7 @@ export function createTestBrowserRouteContext(opts: { getState: () => BrowserSer
|
||||
return { forProfile };
|
||||
}
|
||||
|
||||
/** Creates a remote profile context with a preconnected fetch mock. */
|
||||
export function createRemoteRouteHarness(fetchMock?: (url: unknown) => Promise<Response>) {
|
||||
const activeFetchMock = fetchMock ?? makeUnexpectedFetchMock();
|
||||
global.fetch = withBrowserFetchPreconnect(activeFetchMock);
|
||||
@@ -133,6 +140,7 @@ export function createRemoteRouteHarness(fetchMock?: (url: unknown) => Promise<R
|
||||
return { state, remote: ctx.forProfile("remote"), fetchMock: activeFetchMock };
|
||||
}
|
||||
|
||||
/** Returns a page lister that yields prepared responses in order. */
|
||||
export function createSequentialPageLister<T>(responses: T[]) {
|
||||
return async () => {
|
||||
const next = responses.shift();
|
||||
@@ -151,6 +159,7 @@ type JsonListEntry = {
|
||||
type: "page";
|
||||
};
|
||||
|
||||
/** Creates a /json/list fetch mock with static entries. */
|
||||
export function createJsonListFetchMock(entries: JsonListEntry[]) {
|
||||
return async (url: unknown) => {
|
||||
const u = String(url);
|
||||
@@ -174,6 +183,7 @@ function makeManagedTab(id: string, ordinal: number): JsonListEntry {
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates eight old managed tabs plus one new tab for cleanup-limit tests. */
|
||||
export function makeManagedTabsWithNew(params?: { newFirst?: boolean }): JsonListEntry[] {
|
||||
const oldTabs = Array.from({ length: 8 }, (_, index) =>
|
||||
makeManagedTab(`OLD${index + 1}`, index + 1),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Browser profile reset operations for local managed profiles.
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import type { ResolvedBrowserProfile } from "./config.js";
|
||||
import { BrowserResetUnsupportedError } from "./errors.js";
|
||||
@@ -18,6 +21,7 @@ type ResetOps = {
|
||||
resetProfile: () => Promise<{ moved: boolean; from: string; to?: string }>;
|
||||
};
|
||||
|
||||
/** Builds the reset-profile operation for one resolved browser profile. */
|
||||
export function createProfileResetOps({
|
||||
profile,
|
||||
getProfileState,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Browser tab selection operations for default tab choice, focus, and close.
|
||||
*/
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { fetchOk, normalizeCdpHttpBaseForJsonEndpoints } from "./cdp.helpers.js";
|
||||
@@ -26,6 +29,7 @@ type SelectionOps = {
|
||||
closeTab: (targetId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
/** Builds tab selection/focus/close operations for one resolved browser profile. */
|
||||
export function createProfileSelectionOps({
|
||||
profile,
|
||||
getProfileState,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Browser tab listing, opening, labeling, and alias management for one profile.
|
||||
*/
|
||||
import { resolveBrowserNavigationProxyMode } from "./browser-proxy-mode.js";
|
||||
import { resolveCdpControlPolicy } from "./cdp-reachability-policy.js";
|
||||
import { isSelectableCdpBrowserTarget } from "./cdp-target-filter.js";
|
||||
@@ -170,6 +173,7 @@ function assignTabAliases(profileState: ProfileRuntimeState, tabs: BrowserTab[])
|
||||
return tabs.map((tab) => assignTabAlias({ profileState, tab }));
|
||||
}
|
||||
|
||||
/** Builds list/open/label tab operations for one resolved browser profile. */
|
||||
export function createProfileTabOps({
|
||||
profile,
|
||||
state,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
/**
|
||||
* Test factories for Browser profile/runtime state and launched Chrome mocks.
|
||||
*/
|
||||
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { RunningChrome } from "./chrome.js";
|
||||
import type { ResolvedBrowserProfile } from "./config.js";
|
||||
import type { BrowserServerState } from "./server-context.js";
|
||||
|
||||
/** Creates a resolved Browser profile for unit tests. */
|
||||
export function makeBrowserProfile(
|
||||
overrides: Partial<ResolvedBrowserProfile> = {},
|
||||
): ResolvedBrowserProfile {
|
||||
@@ -21,6 +25,7 @@ export function makeBrowserProfile(
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates Browser server state around a test profile. */
|
||||
export function makeBrowserServerState(params?: {
|
||||
profile?: ResolvedBrowserProfile;
|
||||
resolvedOverrides?: Partial<BrowserServerState["resolved"]>;
|
||||
@@ -69,6 +74,7 @@ export function makeBrowserServerState(params?: {
|
||||
};
|
||||
}
|
||||
|
||||
/** Mocks a launched OpenClaw Chrome process with the supplied pid. */
|
||||
export function mockLaunchedChrome(
|
||||
launchOpenClawChrome: { mockResolvedValue: (value: RunningChrome) => unknown },
|
||||
pid: number,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Browser route context factory that wires profile-scoped runtime operations for
|
||||
* the Browser control server.
|
||||
*/
|
||||
import {
|
||||
resolveCdpControlPolicy,
|
||||
resolveCdpReachabilityPolicy,
|
||||
@@ -37,6 +41,7 @@ export type {
|
||||
ProfileStatus,
|
||||
} from "./server-context.types.js";
|
||||
|
||||
/** Lists configured and runtime-known Browser profile names without duplicates. */
|
||||
export function listKnownProfileNames(state: BrowserServerState): string[] {
|
||||
const names = new Set(Object.keys(state.resolved.profiles));
|
||||
for (const name of state.profiles.keys()) {
|
||||
@@ -129,6 +134,7 @@ function createProfileContext(
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates the Browser route context used by control-server route handlers. */
|
||||
export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteContext {
|
||||
const refreshConfigFromDisk = opts.refreshConfigFromDisk === true;
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Shared Browser server context types used by route handlers and profile
|
||||
* operation factories.
|
||||
*/
|
||||
import type { Server } from "node:http";
|
||||
import type { RunningChrome } from "./chrome.js";
|
||||
import type { BrowserTab, BrowserTransport } from "./client.types.js";
|
||||
@@ -5,9 +9,7 @@ import type { ResolvedBrowserConfig, ResolvedBrowserProfile } from "./config.js"
|
||||
|
||||
export type { BrowserTab };
|
||||
|
||||
/**
|
||||
* Runtime state for a single profile's Chrome instance.
|
||||
*/
|
||||
/** Runtime state for a single profile's Chrome instance. */
|
||||
export type ProfileRuntimeState = {
|
||||
profile: ResolvedBrowserProfile;
|
||||
running: RunningChrome | null;
|
||||
@@ -31,6 +33,7 @@ export type ProfileRuntimeState = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
/** Runtime state for the Browser control server. */
|
||||
export type BrowserServerState = {
|
||||
server?: Server | null;
|
||||
port: number;
|
||||
@@ -58,6 +61,7 @@ type BrowserProfileActions = {
|
||||
resetProfile: () => Promise<{ moved: boolean; from: string; to?: string }>;
|
||||
};
|
||||
|
||||
/** Profile-aware operations exposed to Browser route handlers. */
|
||||
export type BrowserRouteContext = {
|
||||
state: () => BrowserServerState;
|
||||
forProfile: (profileName?: string) => ProfileContext;
|
||||
@@ -66,10 +70,12 @@ export type BrowserRouteContext = {
|
||||
mapTabError: (err: unknown) => { status: number; message: string } | null;
|
||||
} & BrowserProfileActions;
|
||||
|
||||
/** Operations scoped to a single resolved Browser profile. */
|
||||
export type ProfileContext = {
|
||||
profile: ResolvedBrowserProfile;
|
||||
} & BrowserProfileActions;
|
||||
|
||||
/** Status payload returned by Browser profile listing. */
|
||||
export type ProfileStatus = {
|
||||
name: string;
|
||||
transport: BrowserTransport;
|
||||
@@ -85,6 +91,7 @@ export type ProfileStatus = {
|
||||
reconcileReason?: string | null;
|
||||
};
|
||||
|
||||
/** Inputs for creating a Browser route context. */
|
||||
export type ContextOptions = {
|
||||
getState: () => BrowserServerState | null;
|
||||
onEnsureAttachTarget?: (profile: ResolvedBrowserProfile) => Promise<void>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Browser server lifecycle helpers for relay setup and profile shutdown.
|
||||
*/
|
||||
import { stopOpenClawChrome } from "./chrome.js";
|
||||
import type { ResolvedBrowserConfig } from "./config.js";
|
||||
import {
|
||||
@@ -6,6 +9,7 @@ import {
|
||||
listKnownProfileNames,
|
||||
} from "./server-context.js";
|
||||
|
||||
/** Ensures extension relay compatibility hooks for configured profiles. */
|
||||
export async function ensureExtensionRelayForProfiles(_params: {
|
||||
resolved: ResolvedBrowserConfig;
|
||||
onWarn: (message: string) => void;
|
||||
@@ -15,6 +19,7 @@ export async function ensureExtensionRelayForProfiles(_params: {
|
||||
// breaking cleanup rather than changing the call graph in a patch release.
|
||||
}
|
||||
|
||||
/** Stops every known Browser profile during runtime shutdown. */
|
||||
export async function stopKnownBrowserProfiles(params: {
|
||||
getState: () => BrowserServerState | null;
|
||||
onWarn: (message: string) => void;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Shared Express middleware for Browser control routes, including auth marking,
|
||||
* JSON parsing, abort signals, and mutation CSRF checks.
|
||||
*/
|
||||
import type { Express, Request } from "express";
|
||||
import express from "express";
|
||||
import { browserMutationGuardMiddleware } from "./csrf.js";
|
||||
@@ -9,6 +13,7 @@ type BrowserAuthMarkedRequest = Request & {
|
||||
[BROWSER_AUTH_VERIFIED_FLAG]?: boolean;
|
||||
};
|
||||
|
||||
/** Returns whether Browser auth middleware already verified this request. */
|
||||
export function hasVerifiedBrowserAuth(req: Request): boolean {
|
||||
return (req as BrowserAuthMarkedRequest)[BROWSER_AUTH_VERIFIED_FLAG] === true;
|
||||
}
|
||||
@@ -17,6 +22,7 @@ function markVerifiedBrowserAuth(req: Request) {
|
||||
(req as BrowserAuthMarkedRequest)[BROWSER_AUTH_VERIFIED_FLAG] = true;
|
||||
}
|
||||
|
||||
/** Installs common Browser control-server middleware. */
|
||||
export function installBrowserCommonMiddleware(app: Express) {
|
||||
app.use((req, res, next) => {
|
||||
const ctrl = new AbortController();
|
||||
@@ -42,6 +48,7 @@ export function installBrowserCommonMiddleware(app: Express) {
|
||||
app.use(browserMutationGuardMiddleware());
|
||||
}
|
||||
|
||||
/** Installs optional token/password auth for Browser control-server requests. */
|
||||
export function installBrowserAuthMiddleware(
|
||||
app: Express,
|
||||
auth: { token?: string; password?: string },
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Agent-contract test harness for starting the Browser control server and
|
||||
* posting JSON through a real fetch implementation.
|
||||
*/
|
||||
import {
|
||||
getBrowserControlServerBaseUrl,
|
||||
installBrowserControlServerHooks,
|
||||
@@ -5,6 +9,7 @@ import {
|
||||
} from "./server.control-server.test-harness.js";
|
||||
import { getBrowserTestFetch } from "./test-support/fetch.js";
|
||||
|
||||
/** Installs Browser control-server hooks for agent-contract tests. */
|
||||
export function installAgentContractHooks() {
|
||||
installBrowserControlServerHooks();
|
||||
}
|
||||
@@ -50,6 +55,7 @@ async function postStartWithRetry(params: {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/** Starts the Browser control server and returns its base URL. */
|
||||
export async function startServerAndBase(): Promise<string> {
|
||||
await startBrowserControlServerFromConfig();
|
||||
const base = getBrowserControlServerBaseUrl();
|
||||
@@ -58,6 +64,7 @@ export async function startServerAndBase(): Promise<string> {
|
||||
return base;
|
||||
}
|
||||
|
||||
/** Posts JSON to a Browser control-server route and parses the JSON response. */
|
||||
export async function postJson<T>(url: string, body?: unknown): Promise<T> {
|
||||
const realFetch = getBrowserTestFetch();
|
||||
const res = await realFetch(url, {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Shared Browser control-server test harness with mocked Chrome, CDP,
|
||||
* Playwright, Chrome MCP, config, and media dependencies.
|
||||
*/
|
||||
import { afterEach, beforeEach, vi } from "vitest";
|
||||
import { deriveDefaultBrowserCdpPortRange } from "../config/port-defaults.js";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
@@ -44,10 +48,12 @@ const state: HarnessState = {
|
||||
prevGatewayPassword: undefined,
|
||||
};
|
||||
|
||||
/** Returns mutable Browser control-server harness state. */
|
||||
export function getBrowserControlServerTestState(): HarnessState {
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Returns the loopback base URL for the current test server. */
|
||||
export function getBrowserControlServerBaseUrl(): string {
|
||||
return `http://127.0.0.1:${state.testPort}`;
|
||||
}
|
||||
@@ -60,22 +66,27 @@ function restoreGatewayPortEnv(prevGatewayPort: string | undefined): void {
|
||||
process.env.OPENCLAW_GATEWAY_PORT = prevGatewayPort;
|
||||
}
|
||||
|
||||
/** Sets the mocked browser.evaluateEnabled config flag. */
|
||||
export function setBrowserControlServerEvaluateEnabled(enabled: boolean): void {
|
||||
state.cfgEvaluateEnabled = enabled;
|
||||
}
|
||||
|
||||
/** Sets the mocked Browser SSRF policy. */
|
||||
export function setBrowserControlServerSsrFPolicy(policy: SsrFPolicy | undefined): void {
|
||||
state.cfgSsrfPolicy = policy;
|
||||
}
|
||||
|
||||
/** Sets whether mocked Chrome/CDP probes should report reachable. */
|
||||
export function setBrowserControlServerReachable(reachable: boolean): void {
|
||||
state.reachable = reachable;
|
||||
}
|
||||
|
||||
/** Sets the URL returned by mocked /json/list tab responses. */
|
||||
export function setBrowserControlServerTabUrl(url: string): void {
|
||||
state.tabUrl = url;
|
||||
}
|
||||
|
||||
/** Sets mocked Browser profiles and default profile for config reload tests. */
|
||||
export function setBrowserControlServerProfiles(
|
||||
profiles: HarnessState["cfgProfiles"],
|
||||
defaultProfile = Object.keys(profiles)[0] ?? "openclaw",
|
||||
@@ -98,6 +109,7 @@ const cdpMocks = vi.hoisted(() => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
/** Returns mocked CDP functions used by Browser control-server tests. */
|
||||
export function getCdpMocks(): {
|
||||
createTargetViaCdp: MockFn;
|
||||
snapshotAria: MockFn;
|
||||
@@ -333,6 +345,7 @@ pwMocks.executeActViaPlaywright.mockImplementation(
|
||||
},
|
||||
);
|
||||
|
||||
/** Returns mocked Playwright-backed Browser tool functions. */
|
||||
export function getPwMocks(): Record<string, MockFn> {
|
||||
return pwMocks as unknown as Record<string, MockFn>;
|
||||
}
|
||||
@@ -528,6 +541,7 @@ async function loadBrowserServerModule() {
|
||||
return await browserServerModulePromise;
|
||||
}
|
||||
|
||||
/** Starts the Browser control server from the mocked config module. */
|
||||
export async function startBrowserControlServerFromConfig() {
|
||||
return await (await loadBrowserServerModule()).startBrowserControlServerFromConfig();
|
||||
}
|
||||
@@ -536,6 +550,7 @@ async function stopBrowserControlServer(): Promise<void> {
|
||||
await (await loadBrowserServerModule()).stopBrowserControlServer();
|
||||
}
|
||||
|
||||
/** Creates a minimal Response-like object for mocked fetch handlers. */
|
||||
export function makeResponse(
|
||||
body: unknown,
|
||||
init?: { ok?: boolean; status?: number; text?: string },
|
||||
@@ -557,6 +572,7 @@ function mockClearAll(obj: Record<string, { mockClear: () => unknown }>) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets harness state, env, and mocks before one Browser control-server test. */
|
||||
export async function resetBrowserControlServerTestContext(): Promise<void> {
|
||||
state.reachable = false;
|
||||
state.cfgAttachOnly = false;
|
||||
@@ -599,6 +615,7 @@ function restoreGatewayAuthEnv(
|
||||
}
|
||||
}
|
||||
|
||||
/** Restores globals/env and stops the Browser control server after one test. */
|
||||
export async function cleanupBrowserControlServerTestContext(): Promise<void> {
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
@@ -607,6 +624,7 @@ export async function cleanupBrowserControlServerTestContext(): Promise<void> {
|
||||
await stopBrowserControlServer();
|
||||
}
|
||||
|
||||
/** Installs beforeEach/afterEach hooks for Browser control-server tests. */
|
||||
export function installBrowserControlServerHooks() {
|
||||
const hookTimeoutMs = process.platform === "win32" ? 300_000 : 240_000;
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Periodic cleanup for browser tabs tracked to primary OpenClaw sessions.
|
||||
*/
|
||||
import {
|
||||
isAcpSessionKey,
|
||||
isCronSessionKey,
|
||||
@@ -13,6 +16,7 @@ function minutesToMs(minutes: number): number {
|
||||
return Math.max(0, Math.floor(minutes * 60_000));
|
||||
}
|
||||
|
||||
/** Returns true for user-facing sessions whose tabs should be tracked for cleanup. */
|
||||
export function isPrimaryTrackedBrowserSessionKey(sessionKey: string): boolean {
|
||||
return (
|
||||
!isSubagentSessionKey(sessionKey) &&
|
||||
@@ -26,6 +30,7 @@ function resolveBrowserTabCleanupRuntimeConfig(): ResolvedBrowserTabCleanupConfi
|
||||
return resolveBrowserConfig(cfg.browser, cfg).tabCleanup;
|
||||
}
|
||||
|
||||
/** Runs one Browser tab cleanup sweep from runtime config or injected test config. */
|
||||
export async function runTrackedBrowserTabCleanupOnce(params?: {
|
||||
now?: number;
|
||||
cleanup?: ResolvedBrowserTabCleanupConfig;
|
||||
@@ -46,6 +51,7 @@ export async function runTrackedBrowserTabCleanupOnce(params?: {
|
||||
});
|
||||
}
|
||||
|
||||
/** Starts the recurring Browser tab cleanup timer and returns its disposer. */
|
||||
export function startTrackedBrowserTabCleanupTimer(params: {
|
||||
onWarn: (message: string) => void;
|
||||
}): () => void {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* In-memory registry that associates browser tabs with OpenClaw sessions for
|
||||
* cleanup on session end or idle sweeps.
|
||||
*/
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
@@ -91,6 +95,7 @@ function isIgnorableCloseError(err: unknown): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/** Starts tracking a browser tab for later session cleanup. */
|
||||
export function trackSessionBrowserTab(params: SessionBrowserTabIdentityParams): void {
|
||||
const identity = resolveTrackedTabIdentity(params);
|
||||
if (!identity) {
|
||||
@@ -115,6 +120,7 @@ export function trackSessionBrowserTab(params: SessionBrowserTabIdentityParams):
|
||||
});
|
||||
}
|
||||
|
||||
/** Updates last-used time for a tracked browser tab. */
|
||||
export function touchSessionBrowserTab(
|
||||
params: SessionBrowserTabIdentityParams & { now?: number },
|
||||
): void {
|
||||
@@ -137,6 +143,7 @@ export function touchSessionBrowserTab(
|
||||
});
|
||||
}
|
||||
|
||||
/** Removes a browser tab from session cleanup tracking. */
|
||||
export function untrackSessionBrowserTab(params: SessionBrowserTabIdentityParams): void {
|
||||
const identity = resolveTrackedTabIdentity(params);
|
||||
if (!identity) {
|
||||
@@ -211,6 +218,7 @@ async function closeTrackedTabs(params: {
|
||||
return closed;
|
||||
}
|
||||
|
||||
/** Closes and untracks tabs for the supplied session keys. */
|
||||
export async function closeTrackedBrowserTabsForSessions(params: {
|
||||
sessionKeys: Array<string | undefined>;
|
||||
closeTab?: (tab: { targetId: string; baseUrl?: string; profile?: string }) => Promise<void>;
|
||||
@@ -289,6 +297,7 @@ function takeStaleTrackedTabs(params: {
|
||||
return tabsToClose;
|
||||
}
|
||||
|
||||
/** Closes and untracks stale or excess browser tabs across tracked sessions. */
|
||||
export async function sweepTrackedBrowserTabs(params: {
|
||||
now?: number;
|
||||
idleMs?: number;
|
||||
@@ -309,10 +318,12 @@ export async function sweepTrackedBrowserTabs(params: {
|
||||
});
|
||||
}
|
||||
|
||||
/** Clears tracked tab state for tests. */
|
||||
export function resetTrackedSessionBrowserTabsForTests(): void {
|
||||
trackedTabsBySession.clear();
|
||||
}
|
||||
|
||||
/** Counts tracked tabs for one session or all sessions in tests. */
|
||||
export function countTrackedSessionBrowserTabsForTests(sessionKey?: string): number {
|
||||
if (typeof sessionKey === "string" && sessionKey.trim()) {
|
||||
return trackedTabsBySession.get(normalizeSessionKey(sessionKey))?.size ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user