Files
reverse-engineering-anything/src/browser/CdpTargetSession.ts
T
Ysopwnable 16d9c5adef fix(ci): preserve cleanup boundaries and bounded failure assertions (#1618)
Interrupt optional CDP domain shutdown on caller cancellation while preserving an independent bounded detach attempt. Retain original overflow and cleanup failures, verify workspace recovery, preserve user skill files, and reject another unverified firmware worker.

Validation: 149 integrated tests; repeated 64 affected boundary tests; full local check. Standalone CI includes real Chrome CLI/stdio MCP and real Web runtime checks.

Refs #1613
2026-10-11 02:54:12 +08:00

110 lines
3.3 KiB
TypeScript

import { BrowserObservationError } from "../domain/browserObservationError.js";
import { type BrowserObservationOperation } from "../domain/browserObservationErrors.js";
import { CdpConnection } from "./CdpConnection.js";
import {
cdpTargetWebSocket,
type CdpEndpointDiscovery,
type CdpEndpointTarget,
} from "./CdpEndpoint.js";
/** One CDP connection scoped either by a flat browser session or page socket. */
export interface CdpTargetSession {
readonly connection: CdpConnection;
readonly sessionId: string | undefined;
}
/** Open one authorized target through its supported browser or page transport. */
export const openCdpTargetSession = async (
discovery: CdpEndpointDiscovery,
target: CdpEndpointTarget,
operation: BrowserObservationOperation,
signal?: AbortSignal,
limits?: { readonly maxPayloadBytes: number },
): Promise<CdpTargetSession> => {
const webSocket = cdpTargetWebSocket(discovery, target, operation);
const connection = await CdpConnection.connect(
webSocket.url,
operation,
signal,
limits,
);
if (webSocket.scope === "page") return { connection, sessionId: undefined };
try {
const attached = await connection.send(
"Target.attachToTarget",
{ targetId: target.id, flatten: true },
undefined,
signal,
);
return { connection, sessionId: attachedSessionId(attached, operation) };
} catch (cause: unknown) {
await connection.close();
throw cause;
}
};
/** Disable enabled domains, detach browser sessions, and close REA's socket. */
export const closeCdpTargetSession = async (
targetSession: CdpTargetSession,
enabledDomains: readonly string[],
signal?: AbortSignal,
): Promise<void> => {
const { connection, sessionId } = targetSession;
const cleanup = new AbortController();
const domainSignal =
signal === undefined
? cleanup.signal
: AbortSignal.any([cleanup.signal, signal]);
const timeout = setTimeout(() => cleanup.abort(), 1_000);
timeout.unref();
try {
if (signal?.aborted !== true) {
for (const domain of enabledDomains) {
if (domainSignal.aborted) break;
try {
await connection.send(
`${domain}.disable`,
{},
sessionId,
domainSignal,
);
} catch {
// Best-effort domain cleanup; transport close is the definitive boundary.
}
}
}
// Cancellation skips optional domain shutdown but still releases the
// attached session using its independent, bounded cleanup signal.
if (!cleanup.signal.aborted && sessionId !== undefined)
try {
await connection.send(
"Target.detachFromTarget",
{ sessionId },
undefined,
cleanup.signal,
);
} catch {
// Best-effort detach; transport close is the definitive boundary.
}
} finally {
clearTimeout(timeout);
cleanup.abort();
await connection.close();
}
};
const attachedSessionId = (
value: unknown,
operation: BrowserObservationOperation,
): string => {
if (
typeof value !== "object" ||
value === null ||
!("sessionId" in value) ||
typeof value.sessionId !== "string" ||
value.sessionId.length === 0
)
throw new BrowserObservationError(operation, "protocol_error");
return value.sessionId;
};