feat: auto-start sandbox browser
This commit is contained in:
@@ -7,6 +7,7 @@ import { registerBrowserRoutes } from "./routes/index.js";
|
||||
import {
|
||||
type BrowserServerState,
|
||||
createBrowserRouteContext,
|
||||
type ProfileContext,
|
||||
} from "./server-context.js";
|
||||
|
||||
export type BrowserBridge = {
|
||||
@@ -20,6 +21,7 @@ export async function startBrowserBridgeServer(params: {
|
||||
resolved: ResolvedBrowserConfig;
|
||||
host?: string;
|
||||
port?: number;
|
||||
onEnsureAttachTarget?: (profile: ProfileContext["profile"]) => Promise<void>;
|
||||
}): Promise<BrowserBridge> {
|
||||
const host = params.host ?? "127.0.0.1";
|
||||
const port = params.port ?? 0;
|
||||
@@ -36,6 +38,7 @@ export async function startBrowserBridgeServer(params: {
|
||||
|
||||
const ctx = createBrowserRouteContext({
|
||||
getState: () => state,
|
||||
onEnsureAttachTarget: params.onEnsureAttachTarget,
|
||||
});
|
||||
registerBrowserRoutes(app, ctx);
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ export type ProfileStatus = {
|
||||
|
||||
type ContextOptions = {
|
||||
getState: () => BrowserServerState | null;
|
||||
onEnsureAttachTarget?: (profile: ResolvedBrowserProfile) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -259,6 +260,13 @@ function createProfileContext(
|
||||
const httpReachable = await isHttpReachable();
|
||||
|
||||
if (!httpReachable) {
|
||||
if (
|
||||
(current.resolved.attachOnly || remoteCdp) &&
|
||||
opts.onEnsureAttachTarget
|
||||
) {
|
||||
await opts.onEnsureAttachTarget(profile);
|
||||
if (await isHttpReachable(1200)) return;
|
||||
}
|
||||
if (current.resolved.attachOnly || remoteCdp) {
|
||||
throw new Error(
|
||||
remoteCdp
|
||||
@@ -284,6 +292,10 @@ function createProfileContext(
|
||||
|
||||
// We own it but WebSocket failed - restart
|
||||
if (current.resolved.attachOnly || remoteCdp) {
|
||||
if (opts.onEnsureAttachTarget) {
|
||||
await opts.onEnsureAttachTarget(profile);
|
||||
if (await isReachable(1200)) return;
|
||||
}
|
||||
throw new Error(
|
||||
remoteCdp
|
||||
? `Remote CDP websocket for profile "${profile.name}" is not reachable.`
|
||||
|
||||
@@ -707,6 +707,50 @@ describe("browser control server", () => {
|
||||
expect(started.error ?? "").toMatch(/attachOnly/i);
|
||||
});
|
||||
|
||||
it("allows attachOnly servers to ensure reachability via callback", async () => {
|
||||
cfgAttachOnly = true;
|
||||
reachable = false;
|
||||
const { startBrowserBridgeServer } = await import("./bridge-server.js");
|
||||
|
||||
const ensured = vi.fn(async () => {
|
||||
reachable = true;
|
||||
});
|
||||
|
||||
const bridge = await startBrowserBridgeServer({
|
||||
resolved: {
|
||||
enabled: true,
|
||||
controlUrl: "http://127.0.0.1:0",
|
||||
controlHost: "127.0.0.1",
|
||||
controlPort: 0,
|
||||
cdpProtocol: "http",
|
||||
cdpHost: "127.0.0.1",
|
||||
cdpIsLoopback: true,
|
||||
color: "#FF4500",
|
||||
headless: true,
|
||||
noSandbox: false,
|
||||
attachOnly: true,
|
||||
defaultProfile: "clawd",
|
||||
profiles: {
|
||||
clawd: { cdpPort: testPort + 1, color: "#FF4500" },
|
||||
},
|
||||
},
|
||||
onEnsureAttachTarget: ensured,
|
||||
});
|
||||
|
||||
const started = (await realFetch(`${bridge.baseUrl}/start`, {
|
||||
method: "POST",
|
||||
}).then((r) => r.json())) as { ok?: boolean; error?: string };
|
||||
expect(started.error).toBeUndefined();
|
||||
expect(started.ok).toBe(true);
|
||||
const status = (await realFetch(`${bridge.baseUrl}/`).then((r) =>
|
||||
r.json(),
|
||||
)) as { running?: boolean };
|
||||
expect(status.running).toBe(true);
|
||||
expect(ensured).toHaveBeenCalledTimes(1);
|
||||
|
||||
await new Promise<void>((resolve) => bridge.server.close(() => resolve()));
|
||||
});
|
||||
|
||||
it("opens tabs via CDP createTarget path", async () => {
|
||||
const { startBrowserControlServerFromConfig } = await import("./server.js");
|
||||
await startBrowserControlServerFromConfig();
|
||||
|
||||
Reference in New Issue
Block a user