@open-mercato/web-research-browser 0.7.1-develop.7194.1.ab4fc81f82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/build.mjs +10 -0
- package/dist/adapter.js +162 -0
- package/dist/adapter.js.map +7 -0
- package/dist/client.js +189 -0
- package/dist/client.js.map +7 -0
- package/dist/dispatch.js +161 -0
- package/dist/dispatch.js.map +7 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +7 -0
- package/dist/playwright.js +143 -0
- package/dist/playwright.js.map +7 -0
- package/dist/protocol.js +28 -0
- package/dist/protocol.js.map +7 -0
- package/dist/sidecar.js +79 -0
- package/dist/sidecar.js.map +7 -0
- package/jest.config.cjs +31 -0
- package/package.json +64 -0
- package/src/__tests__/browser.test.ts +242 -0
- package/src/__tests__/install.test.ts +72 -0
- package/src/__tests__/leases.test.ts +50 -0
- package/src/__tests__/sandbox.test.ts +67 -0
- package/src/adapter.ts +196 -0
- package/src/client.ts +245 -0
- package/src/dispatch.ts +205 -0
- package/src/index.ts +10 -0
- package/src/playwright.ts +265 -0
- package/src/protocol.ts +51 -0
- package/src/sidecar.ts +108 -0
- package/tsconfig.json +10 -0
- package/watch.mjs +7 -0
package/build.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { dirname } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { buildPackage } from '../../scripts/build-package.mjs'
|
|
4
|
+
|
|
5
|
+
const packageDir = dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
|
|
7
|
+
await buildPackage(packageDir, {
|
|
8
|
+
name: 'web-research-browser',
|
|
9
|
+
entryPoints: 'src/**/*.ts',
|
|
10
|
+
})
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
CONTRACT_VERSION,
|
|
4
|
+
READY,
|
|
5
|
+
blocked,
|
|
6
|
+
classifyPage,
|
|
7
|
+
defineAdapterModule,
|
|
8
|
+
extractMainContent,
|
|
9
|
+
extractTitle,
|
|
10
|
+
htmlToText,
|
|
11
|
+
notReady,
|
|
12
|
+
searchOk
|
|
13
|
+
} from "@open-mercato/web-research";
|
|
14
|
+
import { SERP_PROFILES, extractSerpResults } from "@open-mercato/web-research-serp";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { createSidecar } from "./client.js";
|
|
17
|
+
import { SidecarError } from "./protocol.js";
|
|
18
|
+
const browserOptionsSchema = z.object({
|
|
19
|
+
engines: z.array(z.enum(["ddg-html", "ddg-lite"])).min(1).optional(),
|
|
20
|
+
userAgent: z.string().min(1).optional(),
|
|
21
|
+
timeoutMs: z.number().int().min(1e3).max(12e4).optional(),
|
|
22
|
+
sidecar: z.custom((value) => typeof value === "object" && value !== null).optional()
|
|
23
|
+
});
|
|
24
|
+
const DEFAULT_ENGINES = ["ddg-html"];
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
26
|
+
function isRenderResult(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && typeof value.html === "string" && typeof value.url === "string";
|
|
28
|
+
}
|
|
29
|
+
function createBrowserAdapter(options) {
|
|
30
|
+
const engines = options.engines ?? DEFAULT_ENGINES;
|
|
31
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32
|
+
let sidecar = null;
|
|
33
|
+
let startupFailure = null;
|
|
34
|
+
const client = () => {
|
|
35
|
+
sidecar ??= createSidecar(options.sidecar ?? {});
|
|
36
|
+
return sidecar;
|
|
37
|
+
};
|
|
38
|
+
const render = async (url, deadlineAt, signal) => {
|
|
39
|
+
const leaseId = randomUUID();
|
|
40
|
+
const budget = Math.max(1e3, Math.min(timeoutMs, deadlineAt - Date.now()));
|
|
41
|
+
const active = client();
|
|
42
|
+
try {
|
|
43
|
+
const result = await active.call(
|
|
44
|
+
"render",
|
|
45
|
+
{ url, leaseId, timeoutMs: budget, ...options.userAgent ? { userAgent: options.userAgent } : {} },
|
|
46
|
+
signal
|
|
47
|
+
);
|
|
48
|
+
if (!isRenderResult(result)) throw new Error("sidecar returned a malformed render result");
|
|
49
|
+
startupFailure = null;
|
|
50
|
+
return result;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof SidecarError && (error.kind === "init" || error.kind === "needs-install")) {
|
|
53
|
+
startupFailure = error.message;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
} finally {
|
|
57
|
+
await active.call("release", { leaseId }).catch(() => void 0);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
id: "browser",
|
|
62
|
+
kind: "browser",
|
|
63
|
+
capabilities: {
|
|
64
|
+
search: true,
|
|
65
|
+
fetch: true,
|
|
66
|
+
snippets: true,
|
|
67
|
+
content: true,
|
|
68
|
+
freshness: true,
|
|
69
|
+
siteFilter: true,
|
|
70
|
+
// Roughly two orders of magnitude more expensive than an HTTP read, which
|
|
71
|
+
// is why the engine only reaches for this after a source reports blocked.
|
|
72
|
+
cost: "metered"
|
|
73
|
+
},
|
|
74
|
+
// Participation is governed solely by the policy row. The scheduler already
|
|
75
|
+
// keeps `kind: 'browser'` out of normal waves, so an enabled browser adapter
|
|
76
|
+
// is reached only for escalation — a second per-adapter switch just made the
|
|
77
|
+
// UI show two toggles meaning different things.
|
|
78
|
+
/** `heavy` — spawns and pings the sidecar process. */
|
|
79
|
+
probeCost: "heavy",
|
|
80
|
+
readiness: () => startupFailure === null ? READY : notReady(startupFailure),
|
|
81
|
+
async search(request, context) {
|
|
82
|
+
let lastReason = "no engine produced results";
|
|
83
|
+
for (const engine of engines) {
|
|
84
|
+
if (context.signal.aborted) break;
|
|
85
|
+
const profile = SERP_PROFILES[engine];
|
|
86
|
+
context.report("started", `rendering ${profile.label} in a browser`);
|
|
87
|
+
try {
|
|
88
|
+
const rendered = await render(profile.buildUrl(request), context.deadlineAt, context.signal);
|
|
89
|
+
const results = extractSerpResults(rendered.html, profile.selector, profile.baseUrl, request.limit);
|
|
90
|
+
if (results.length > 0) {
|
|
91
|
+
context.report("ok", `${profile.label} returned ${results.length} result(s)`, {
|
|
92
|
+
resultCount: results.length
|
|
93
|
+
});
|
|
94
|
+
return searchOk(results);
|
|
95
|
+
}
|
|
96
|
+
const verdict = classifyPage({
|
|
97
|
+
status: 200,
|
|
98
|
+
contentType: "text/html",
|
|
99
|
+
html: rendered.html,
|
|
100
|
+
text: ""
|
|
101
|
+
});
|
|
102
|
+
lastReason = verdict.verdict === "blocked" ? `${profile.label} served a challenge page even in a browser (${verdict.reason ?? "unknown"})` : `${profile.label} rendered but produced no parseable results`;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
lastReason = error instanceof Error ? error.message : String(error);
|
|
105
|
+
}
|
|
106
|
+
context.report("blocked", lastReason);
|
|
107
|
+
}
|
|
108
|
+
return blocked(lastReason);
|
|
109
|
+
},
|
|
110
|
+
async fetch(request, context) {
|
|
111
|
+
try {
|
|
112
|
+
const rendered = await render(request.url, context.deadlineAt, context.signal);
|
|
113
|
+
const text = extractMainContent(rendered.html) || htmlToText(rendered.html);
|
|
114
|
+
return {
|
|
115
|
+
status: "ok",
|
|
116
|
+
page: {
|
|
117
|
+
url: rendered.url,
|
|
118
|
+
title: extractTitle(rendered.html),
|
|
119
|
+
text,
|
|
120
|
+
contentType: "text/html",
|
|
121
|
+
status: 200,
|
|
122
|
+
truncated: false,
|
|
123
|
+
renderedWith: "browser"
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
status: "error",
|
|
129
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
130
|
+
retriable: false
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
async healthCheck() {
|
|
135
|
+
try {
|
|
136
|
+
await client().call("ping", {});
|
|
137
|
+
startupFailure = null;
|
|
138
|
+
return { ok: true };
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
async dispose() {
|
|
144
|
+
const active = sidecar;
|
|
145
|
+
sidecar = null;
|
|
146
|
+
if (active) await active.close();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const browserAdapterModule = defineAdapterModule({
|
|
151
|
+
id: "browser",
|
|
152
|
+
kind: "browser",
|
|
153
|
+
contractVersion: CONTRACT_VERSION,
|
|
154
|
+
optionsSchema: browserOptionsSchema,
|
|
155
|
+
createAdapter: createBrowserAdapter
|
|
156
|
+
});
|
|
157
|
+
export {
|
|
158
|
+
browserAdapterModule,
|
|
159
|
+
browserOptionsSchema,
|
|
160
|
+
createBrowserAdapter
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/adapter.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport {\n CONTRACT_VERSION,\n READY,\n blocked,\n classifyPage,\n defineAdapterModule,\n extractMainContent,\n extractTitle,\n htmlToText,\n notReady,\n searchOk,\n type FetchOutcome,\n type SearchAdapter,\n type SearchOutcome,\n} from '@open-mercato/web-research'\nimport { SERP_PROFILES, extractSerpResults, type SerpEngineId } from '@open-mercato/web-research-serp'\nimport { z } from 'zod'\nimport { createSidecar, type Sidecar, type SidecarOptions } from './client'\nimport { SidecarError } from './protocol'\n\nexport const browserOptionsSchema = z.object({\n engines: z.array(z.enum(['ddg-html', 'ddg-lite'])).min(1).optional(),\n userAgent: z.string().min(1).optional(),\n timeoutMs: z.number().int().min(1000).max(120_000).optional(),\n sidecar: z.custom<SidecarOptions>((value) => typeof value === 'object' && value !== null).optional(),\n})\n\nexport type BrowserOptions = z.infer<typeof browserOptionsSchema>\n\nconst DEFAULT_ENGINES: readonly SerpEngineId[] = ['ddg-html']\nconst DEFAULT_TIMEOUT_MS = 30_000\n\ntype RenderResult = { url: string; html: string }\n\nfunction isRenderResult(value: unknown): value is RenderResult {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { html?: unknown }).html === 'string' &&\n typeof (value as { url?: unknown }).url === 'string'\n )\n}\n\nexport function createBrowserAdapter(options: BrowserOptions): SearchAdapter {\n const engines = options.engines ?? DEFAULT_ENGINES\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n let sidecar: Sidecar | null = null\n // Remembered so `readiness()` stops promising a tier that cannot start. It is\n // sync and I/O-free by contract, so it cannot probe \u2014 but it can report what\n // the last attempt already proved, instead of letting the scheduler pay the\n // escalation cost again to rediscover it.\n let startupFailure: string | null = null\n\n const client = (): Sidecar => {\n sidecar ??= createSidecar(options.sidecar ?? {})\n return sidecar\n }\n\n /**\n * Every render takes its own lease, so concurrent work never shares a cookie jar.\n *\n * The budget is the smaller of the configured timeout and whatever the engine\n * has left: a render that outlives `deadlineAt` keeps a browser context open on\n * a page nobody is waiting for any more.\n */\n const render = async (url: string, deadlineAt: number, signal: AbortSignal): Promise<RenderResult> => {\n const leaseId = randomUUID()\n const budget = Math.max(1_000, Math.min(timeoutMs, deadlineAt - Date.now()))\n const active = client()\n try {\n const result = await active.call(\n 'render',\n { url, leaseId, timeoutMs: budget, ...(options.userAgent ? { userAgent: options.userAgent } : {}) },\n signal,\n )\n if (!isRenderResult(result)) throw new Error('sidecar returned a malformed render result')\n startupFailure = null\n return result\n } catch (error) {\n if (error instanceof SidecarError && (error.kind === 'init' || error.kind === 'needs-install')) {\n startupFailure = error.message\n }\n throw error\n } finally {\n await active.call('release', { leaseId }).catch(() => undefined)\n }\n }\n\n return {\n id: 'browser',\n kind: 'browser',\n capabilities: {\n search: true,\n fetch: true,\n snippets: true,\n content: true,\n freshness: true,\n siteFilter: true,\n // Roughly two orders of magnitude more expensive than an HTTP read, which\n // is why the engine only reaches for this after a source reports blocked.\n cost: 'metered',\n },\n // Participation is governed solely by the policy row. The scheduler already\n // keeps `kind: 'browser'` out of normal waves, so an enabled browser adapter\n // is reached only for escalation \u2014 a second per-adapter switch just made the\n // UI show two toggles meaning different things.\n /** `heavy` \u2014 spawns and pings the sidecar process. */\n probeCost: 'heavy' as const,\n readiness: () => (startupFailure === null ? READY : notReady(startupFailure)),\n\n async search(request, context): Promise<SearchOutcome> {\n let lastReason = 'no engine produced results'\n\n for (const engine of engines) {\n if (context.signal.aborted) break\n const profile = SERP_PROFILES[engine]\n context.report('started', `rendering ${profile.label} in a browser`)\n try {\n const rendered = await render(profile.buildUrl(request), context.deadlineAt, context.signal)\n const results = extractSerpResults(rendered.html, profile.selector, profile.baseUrl, request.limit)\n if (results.length > 0) {\n context.report('ok', `${profile.label} returned ${results.length} result(s)`, {\n resultCount: results.length,\n })\n return searchOk(results)\n }\n const verdict = classifyPage({\n status: 200,\n contentType: 'text/html',\n html: rendered.html,\n text: '',\n })\n lastReason =\n verdict.verdict === 'blocked'\n ? `${profile.label} served a challenge page even in a browser (${verdict.reason ?? 'unknown'})`\n : `${profile.label} rendered but produced no parseable results`\n } catch (error) {\n lastReason = error instanceof Error ? error.message : String(error)\n }\n context.report('blocked', lastReason)\n }\n\n return blocked(lastReason)\n },\n\n async fetch(request, context): Promise<FetchOutcome> {\n try {\n const rendered = await render(request.url, context.deadlineAt, context.signal)\n const text = extractMainContent(rendered.html) || htmlToText(rendered.html)\n return {\n status: 'ok',\n page: {\n url: rendered.url,\n title: extractTitle(rendered.html),\n text,\n contentType: 'text/html',\n status: 200,\n truncated: false,\n renderedWith: 'browser',\n },\n }\n } catch (error) {\n return {\n status: 'error',\n reason: error instanceof Error ? error.message : String(error),\n retriable: false,\n }\n }\n },\n\n async healthCheck() {\n try {\n await client().call('ping', {})\n startupFailure = null\n return { ok: true }\n } catch (error) {\n return { ok: false, detail: error instanceof Error ? error.message : String(error) }\n }\n },\n\n async dispose() {\n const active = sidecar\n sidecar = null\n if (active) await active.close()\n },\n }\n}\n\nexport const browserAdapterModule = defineAdapterModule({\n id: 'browser',\n kind: 'browser',\n contractVersion: CONTRACT_VERSION,\n optionsSchema: browserOptionsSchema,\n createAdapter: createBrowserAdapter,\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { SidecarError, decodeLine, encodeLine, isReply } from "./protocol.js";
|
|
6
|
+
const DEFAULT_CALL_TIMEOUT_MS = 15e4;
|
|
7
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 2e3;
|
|
8
|
+
const DEFAULT_MAX_PENDING = 128;
|
|
9
|
+
const SIGKILL_GRACE_MS = 2e3;
|
|
10
|
+
const MAX_STDOUT_BUFFER = 64 * 1024 * 1024;
|
|
11
|
+
const STDERR_RING = 24;
|
|
12
|
+
function defaultSidecarPath() {
|
|
13
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "sidecar.js");
|
|
14
|
+
}
|
|
15
|
+
function defaultSpawn(scriptPath) {
|
|
16
|
+
const child = spawn(process.execPath, [scriptPath], {
|
|
17
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
18
|
+
});
|
|
19
|
+
return child;
|
|
20
|
+
}
|
|
21
|
+
class Sidecar {
|
|
22
|
+
constructor(sidecarPath, spawnFn, callTimeoutMs, closeTimeoutMs, maxPending) {
|
|
23
|
+
this.sidecarPath = sidecarPath;
|
|
24
|
+
this.spawnFn = spawnFn;
|
|
25
|
+
this.callTimeoutMs = callTimeoutMs;
|
|
26
|
+
this.closeTimeoutMs = closeTimeoutMs;
|
|
27
|
+
this.maxPending = maxPending;
|
|
28
|
+
this.child = null;
|
|
29
|
+
this.startError = null;
|
|
30
|
+
this.buffer = "";
|
|
31
|
+
this.recentStderr = [];
|
|
32
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
33
|
+
}
|
|
34
|
+
ensure() {
|
|
35
|
+
if (this.child) return;
|
|
36
|
+
if (this.startError) throw this.startError;
|
|
37
|
+
try {
|
|
38
|
+
this.child = this.spawnFn(this.sidecarPath);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
this.startError = error instanceof Error ? error : new Error(String(error));
|
|
41
|
+
throw this.startError;
|
|
42
|
+
}
|
|
43
|
+
this.buffer = "";
|
|
44
|
+
this.recentStderr = [];
|
|
45
|
+
this.child.stdout.setEncoding?.("utf8");
|
|
46
|
+
this.child.stdout.on("data", (chunk) => {
|
|
47
|
+
this.buffer += chunk;
|
|
48
|
+
let newline = this.buffer.indexOf("\n");
|
|
49
|
+
while (newline !== -1) {
|
|
50
|
+
const line = this.buffer.slice(0, newline);
|
|
51
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
52
|
+
if (line.trim()) this.handleLine(line);
|
|
53
|
+
newline = this.buffer.indexOf("\n");
|
|
54
|
+
}
|
|
55
|
+
if (this.buffer.length > MAX_STDOUT_BUFFER) {
|
|
56
|
+
this.recentStderr.push(`dropped ${this.buffer.length} bytes of un-delimited sidecar output`);
|
|
57
|
+
this.buffer = "";
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
this.child.stdin.on?.("error", (error) => {
|
|
61
|
+
this.recentStderr.push(`sidecar stdin ${error.code ?? "error"}: ${error.message}`);
|
|
62
|
+
if (this.recentStderr.length > STDERR_RING) this.recentStderr.shift();
|
|
63
|
+
});
|
|
64
|
+
this.child.stderr.setEncoding?.("utf8");
|
|
65
|
+
this.child.stderr.on("data", (chunk) => {
|
|
66
|
+
for (const line of chunk.split("\n")) {
|
|
67
|
+
if (!line.trim()) continue;
|
|
68
|
+
this.recentStderr.push(line);
|
|
69
|
+
if (this.recentStderr.length > STDERR_RING) this.recentStderr.shift();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
this.child.once("exit", (code) => {
|
|
73
|
+
const tail = this.recentStderr.slice(-8).join("\n").trim();
|
|
74
|
+
const error = new SidecarError(
|
|
75
|
+
`browser sidecar exited unexpectedly (code=${code ?? "null"})${tail ? `:
|
|
76
|
+
${tail}` : ""}`,
|
|
77
|
+
"init"
|
|
78
|
+
);
|
|
79
|
+
for (const [, entry] of this.pending) {
|
|
80
|
+
clearTimeout(entry.timer);
|
|
81
|
+
entry.reject(error);
|
|
82
|
+
}
|
|
83
|
+
this.pending.clear();
|
|
84
|
+
this.child = null;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
handleLine(line) {
|
|
88
|
+
const parsed = decodeLine(line);
|
|
89
|
+
if (!isReply(parsed)) return;
|
|
90
|
+
const entry = this.pending.get(parsed.id);
|
|
91
|
+
if (!entry) return;
|
|
92
|
+
this.pending.delete(parsed.id);
|
|
93
|
+
clearTimeout(entry.timer);
|
|
94
|
+
if (parsed.ok) {
|
|
95
|
+
entry.resolve(parsed.result);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
entry.reject(new SidecarError(parsed.error.message, parsed.error.kind));
|
|
99
|
+
}
|
|
100
|
+
async call(method, params = {}, signal) {
|
|
101
|
+
this.ensure();
|
|
102
|
+
const child = this.child;
|
|
103
|
+
if (!child) throw new SidecarError("browser sidecar is not running", "init");
|
|
104
|
+
if (this.pending.size >= this.maxPending) {
|
|
105
|
+
throw new SidecarError(`browser sidecar busy (${this.pending.size} in flight)`, "runtime");
|
|
106
|
+
}
|
|
107
|
+
const id = randomUUID();
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
this.pending.delete(id);
|
|
111
|
+
signal?.removeEventListener("abort", onAbort);
|
|
112
|
+
reject(new SidecarError(`browser sidecar call "${method}" timed out`, "timeout"));
|
|
113
|
+
}, this.callTimeoutMs);
|
|
114
|
+
timer.unref?.();
|
|
115
|
+
const onAbort = () => {
|
|
116
|
+
if (!this.pending.delete(id)) return;
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
reject(new SidecarError(`browser sidecar call "${method}" was aborted`, "timeout"));
|
|
119
|
+
};
|
|
120
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
121
|
+
this.pending.set(id, {
|
|
122
|
+
resolve: (value) => {
|
|
123
|
+
signal?.removeEventListener("abort", onAbort);
|
|
124
|
+
resolve(value);
|
|
125
|
+
},
|
|
126
|
+
reject: (error) => {
|
|
127
|
+
signal?.removeEventListener("abort", onAbort);
|
|
128
|
+
reject(error);
|
|
129
|
+
},
|
|
130
|
+
timer
|
|
131
|
+
});
|
|
132
|
+
try {
|
|
133
|
+
child.stdin.write(encodeLine({ id, method, params }));
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.pending.delete(id);
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
signal?.removeEventListener("abort", onAbort);
|
|
138
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async close() {
|
|
143
|
+
const child = this.child;
|
|
144
|
+
if (!child) return;
|
|
145
|
+
try {
|
|
146
|
+
const closeSignal = AbortSignal.timeout(this.closeTimeoutMs);
|
|
147
|
+
await this.call("close", {}, closeSignal);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
await new Promise((resolve) => {
|
|
151
|
+
let settled = false;
|
|
152
|
+
const done = () => {
|
|
153
|
+
if (settled) return;
|
|
154
|
+
settled = true;
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
resolve();
|
|
157
|
+
};
|
|
158
|
+
child.once("exit", done);
|
|
159
|
+
const timer = setTimeout(() => {
|
|
160
|
+
try {
|
|
161
|
+
child.kill("SIGKILL");
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
done();
|
|
165
|
+
}, SIGKILL_GRACE_MS);
|
|
166
|
+
timer.unref?.();
|
|
167
|
+
try {
|
|
168
|
+
child.kill("SIGTERM");
|
|
169
|
+
} catch {
|
|
170
|
+
done();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
this.child = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function createSidecar(options = {}) {
|
|
177
|
+
return new Sidecar(
|
|
178
|
+
options.sidecarPath ?? defaultSidecarPath(),
|
|
179
|
+
options.spawnFn ?? defaultSpawn,
|
|
180
|
+
options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS,
|
|
181
|
+
options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS,
|
|
182
|
+
options.maxPending ?? DEFAULT_MAX_PENDING
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
Sidecar,
|
|
187
|
+
createSidecar
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client.ts"],
|
|
4
|
+
"sourcesContent": ["import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { SidecarError, decodeLine, encodeLine, isReply, type SidecarReply } from './protocol'\n\nexport type SidecarStream = {\n stdin: { write(chunk: string): unknown; on?(event: 'error', handler: (error: NodeJS.ErrnoException) => void): unknown }\n stdout: { setEncoding?(encoding: string): unknown; on(event: 'data', handler: (chunk: string) => void): unknown }\n stderr: { setEncoding?(encoding: string): unknown; on(event: 'data', handler: (chunk: string) => void): unknown }\n once(event: 'exit', handler: (code: number | null) => void): unknown\n kill(signal?: NodeJS.Signals): unknown\n}\n\nexport type SpawnFn = (scriptPath: string) => SidecarStream\n\nexport type SidecarOptions = {\n readonly sidecarPath?: string\n readonly spawnFn?: SpawnFn\n readonly callTimeoutMs?: number\n readonly closeTimeoutMs?: number\n readonly maxPending?: number\n}\n\nconst DEFAULT_CALL_TIMEOUT_MS = 150_000\n/**\n * Deliberately short and separate from the call timeout: shutdown must not block\n * for minutes on a wedged child before SIGTERM is even attempted.\n */\nconst DEFAULT_CLOSE_TIMEOUT_MS = 2_000\nconst DEFAULT_MAX_PENDING = 128\nconst SIGKILL_GRACE_MS = 2_000\n/** A full-page screenshot or a large DOM fits comfortably; a runaway does not. */\nconst MAX_STDOUT_BUFFER = 64 * 1024 * 1024\nconst STDERR_RING = 24\n\nfunction defaultSidecarPath(): string {\n return path.join(path.dirname(fileURLToPath(import.meta.url)), 'sidecar.js')\n}\n\nfunction defaultSpawn(scriptPath: string): SidecarStream {\n const child: ChildProcessWithoutNullStreams = spawn(process.execPath, [scriptPath], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n return child as unknown as SidecarStream\n}\n\ntype Pending = {\n resolve: (value: unknown) => void\n reject: (error: Error) => void\n timer: NodeJS.Timeout\n}\n\nexport class Sidecar {\n private child: SidecarStream | null = null\n private startError: Error | null = null\n private buffer = ''\n private recentStderr: string[] = []\n private readonly pending = new Map<string, Pending>()\n\n constructor(\n private readonly sidecarPath: string,\n private readonly spawnFn: SpawnFn,\n private readonly callTimeoutMs: number,\n private readonly closeTimeoutMs: number,\n private readonly maxPending: number,\n ) {}\n\n private ensure(): void {\n if (this.child) return\n if (this.startError) throw this.startError\n try {\n this.child = this.spawnFn(this.sidecarPath)\n } catch (error) {\n this.startError = error instanceof Error ? error : new Error(String(error))\n throw this.startError\n }\n // Reset per-process state on every spawn. A child that died mid-line would\n // otherwise leave a JSON fragment that corrupts the next spawn's first\n // reply \u2014 which then parses as garbage and hangs that call until timeout.\n this.buffer = ''\n this.recentStderr = []\n\n this.child.stdout.setEncoding?.('utf8')\n this.child.stdout.on('data', (chunk: string) => {\n this.buffer += chunk\n let newline = this.buffer.indexOf('\\n')\n while (newline !== -1) {\n const line = this.buffer.slice(0, newline)\n this.buffer = this.buffer.slice(newline + 1)\n if (line.trim()) this.handleLine(line)\n newline = this.buffer.indexOf('\\n')\n }\n if (this.buffer.length > MAX_STDOUT_BUFFER) {\n this.recentStderr.push(`dropped ${this.buffer.length} bytes of un-delimited sidecar output`)\n this.buffer = ''\n }\n })\n\n // A dead child leaves its stdin pipe broken, and Node reports that as an\n // asynchronous 'error' event (EPIPE) rather than as a throw from write() \u2014\n // so the try/catch around the write in `call` never sees it. Unhandled, a\n // stream 'error' escalates past the adapter and the call throws instead of\n // resolving to an outcome. Recording it keeps the event handled; the 'exit'\n // handler below is what rejects the calls still in flight.\n this.child.stdin.on?.('error', (error: NodeJS.ErrnoException) => {\n this.recentStderr.push(`sidecar stdin ${error.code ?? 'error'}: ${error.message}`)\n if (this.recentStderr.length > STDERR_RING) this.recentStderr.shift()\n })\n\n this.child.stderr.setEncoding?.('utf8')\n this.child.stderr.on('data', (chunk: string) => {\n for (const line of chunk.split('\\n')) {\n if (!line.trim()) continue\n this.recentStderr.push(line)\n if (this.recentStderr.length > STDERR_RING) this.recentStderr.shift()\n }\n })\n\n this.child.once('exit', (code) => {\n const tail = this.recentStderr.slice(-8).join('\\n').trim()\n const error = new SidecarError(\n `browser sidecar exited unexpectedly (code=${code ?? 'null'})${tail ? `:\\n${tail}` : ''}`,\n 'init',\n )\n for (const [, entry] of this.pending) {\n clearTimeout(entry.timer)\n entry.reject(error)\n }\n this.pending.clear()\n this.child = null\n })\n }\n\n private handleLine(line: string): void {\n const parsed = decodeLine(line)\n if (!isReply(parsed)) return\n const entry = this.pending.get(parsed.id)\n if (!entry) return\n this.pending.delete(parsed.id)\n clearTimeout(entry.timer)\n if (parsed.ok) {\n entry.resolve(parsed.result)\n return\n }\n entry.reject(new SidecarError(parsed.error.message, parsed.error.kind))\n }\n\n async call(method: string, params: Record<string, unknown> = {}, signal?: AbortSignal): Promise<unknown> {\n this.ensure()\n const child = this.child\n if (!child) throw new SidecarError('browser sidecar is not running', 'init')\n if (this.pending.size >= this.maxPending) {\n throw new SidecarError(`browser sidecar busy (${this.pending.size} in flight)`, 'runtime')\n }\n\n const id = randomUUID()\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id)\n signal?.removeEventListener('abort', onAbort)\n reject(new SidecarError(`browser sidecar call \"${method}\" timed out`, 'timeout'))\n }, this.callTimeoutMs)\n timer.unref?.()\n\n // Abort rejects THIS call only. The sidecar is shared, so killing it would\n // take healthy concurrent leases down with it; a late reply is ignored\n // because its id is no longer pending.\n const onAbort = (): void => {\n if (!this.pending.delete(id)) return\n clearTimeout(timer)\n reject(new SidecarError(`browser sidecar call \"${method}\" was aborted`, 'timeout'))\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n\n this.pending.set(id, {\n resolve: (value) => {\n signal?.removeEventListener('abort', onAbort)\n resolve(value)\n },\n reject: (error) => {\n signal?.removeEventListener('abort', onAbort)\n reject(error)\n },\n timer,\n })\n\n try {\n child.stdin.write(encodeLine({ id, method, params }))\n } catch (error) {\n this.pending.delete(id)\n clearTimeout(timer)\n signal?.removeEventListener('abort', onAbort)\n reject(error instanceof Error ? error : new Error(String(error)))\n }\n })\n }\n\n async close(): Promise<void> {\n const child = this.child\n if (!child) return\n try {\n const closeSignal = AbortSignal.timeout(this.closeTimeoutMs)\n await this.call('close', {}, closeSignal)\n } catch {\n // Wedged or already gone; escalate below.\n }\n await new Promise<void>((resolve) => {\n let settled = false\n const done = (): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n resolve()\n }\n child.once('exit', done)\n // Armed before the kill so a synchronous exit still finds the timer set.\n const timer = setTimeout(() => {\n try {\n child.kill('SIGKILL')\n } catch {\n // Already gone.\n }\n done()\n }, SIGKILL_GRACE_MS)\n timer.unref?.()\n try {\n child.kill('SIGTERM')\n } catch {\n done()\n }\n })\n this.child = null\n }\n}\n\nexport function createSidecar(options: SidecarOptions = {}): Sidecar {\n return new Sidecar(\n options.sidecarPath ?? defaultSidecarPath(),\n options.spawnFn ?? defaultSpawn,\n options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS,\n options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS,\n options.maxPending ?? DEFAULT_MAX_PENDING,\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/dispatch.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { assertPublicUrl } from "@open-mercato/web-research";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_USER_AGENT,
|
|
4
|
+
isSandboxDisabled,
|
|
5
|
+
resolveLaunchArgs,
|
|
6
|
+
importChromium,
|
|
7
|
+
installNavigationGuard,
|
|
8
|
+
launchChromium
|
|
9
|
+
} from "./playwright.js";
|
|
10
|
+
import { SidecarError } from "./protocol.js";
|
|
11
|
+
const LEASE_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
12
|
+
const DEFAULT_GOTO_TIMEOUT_MS = 3e4;
|
|
13
|
+
const MAX_GOTO_TIMEOUT_MS = 12e4;
|
|
14
|
+
function createState(overrides = {}) {
|
|
15
|
+
return {
|
|
16
|
+
browser: null,
|
|
17
|
+
leases: /* @__PURE__ */ new Map(),
|
|
18
|
+
filterSubresources: process.env.OM_WEB_RESEARCH_BROWSER_FILTER_SUBRESOURCES === "1",
|
|
19
|
+
warn: (message) => process.stderr.write(`om-web-research-sidecar: ${message}
|
|
20
|
+
`),
|
|
21
|
+
now: () => Date.now(),
|
|
22
|
+
...overrides
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function ensureBrowser(state) {
|
|
26
|
+
if (state.browser) return state.browser;
|
|
27
|
+
const chromium = await importChromium();
|
|
28
|
+
if (isSandboxDisabled()) {
|
|
29
|
+
state.warn(
|
|
30
|
+
"SECURITY: Chromium sandbox is disabled by OM_WEB_RESEARCH_BROWSER_NO_SANDBOX=1 \u2014 a renderer compromise reaches this host"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
state.browser = await launchChromium(
|
|
34
|
+
chromium,
|
|
35
|
+
{ headless: true, args: resolveLaunchArgs() },
|
|
36
|
+
{ onProgress: state.warn, ...state.installRunner ? { install: state.installRunner } : {} }
|
|
37
|
+
);
|
|
38
|
+
return state.browser;
|
|
39
|
+
}
|
|
40
|
+
async function openLease(state, leaseId, userAgent) {
|
|
41
|
+
await reapIdleLeases(state);
|
|
42
|
+
const existing = state.leases.get(leaseId);
|
|
43
|
+
if (existing) {
|
|
44
|
+
existing.touchedAt = state.now();
|
|
45
|
+
return existing;
|
|
46
|
+
}
|
|
47
|
+
const browser = await ensureBrowser(state);
|
|
48
|
+
const context = await browser.newContext({ userAgent, locale: "en-US" });
|
|
49
|
+
await installNavigationGuard(context, { filterSubresources: state.filterSubresources, warn: state.warn });
|
|
50
|
+
const page = await context.newPage();
|
|
51
|
+
const lease = { context, page, touchedAt: state.now() };
|
|
52
|
+
state.leases.set(leaseId, lease);
|
|
53
|
+
return lease;
|
|
54
|
+
}
|
|
55
|
+
async function reapIdleLeases(state) {
|
|
56
|
+
const cutoff = state.now() - LEASE_IDLE_TIMEOUT_MS;
|
|
57
|
+
for (const [leaseId, lease] of [...state.leases]) {
|
|
58
|
+
if (lease.touchedAt > cutoff) continue;
|
|
59
|
+
state.warn(`reaping lease ${leaseId} idle for over ${LEASE_IDLE_TIMEOUT_MS}ms`);
|
|
60
|
+
await closeLease(state, leaseId);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function closeLease(state, leaseId) {
|
|
64
|
+
const lease = state.leases.get(leaseId);
|
|
65
|
+
if (!lease) return;
|
|
66
|
+
state.leases.delete(leaseId);
|
|
67
|
+
try {
|
|
68
|
+
await lease.context.close();
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function teardown(state) {
|
|
73
|
+
for (const leaseId of [...state.leases.keys()]) await closeLease(state, leaseId);
|
|
74
|
+
const browser = state.browser;
|
|
75
|
+
state.browser = null;
|
|
76
|
+
if (browser) {
|
|
77
|
+
try {
|
|
78
|
+
await browser.close();
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function stringParam(request, key) {
|
|
84
|
+
const value = request.params?.[key];
|
|
85
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
86
|
+
throw new SidecarError(`missing "${key}" parameter`, "runtime");
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
async function dispatchInner(state, request) {
|
|
91
|
+
switch (request.method) {
|
|
92
|
+
case "ping":
|
|
93
|
+
return { id: request.id, ok: true, result: { ready: true } };
|
|
94
|
+
case "render": {
|
|
95
|
+
const url = stringParam(request, "url");
|
|
96
|
+
const leaseId = stringParam(request, "leaseId");
|
|
97
|
+
try {
|
|
98
|
+
await assertPublicUrl(url, "browser render", { failClosed: true });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
id: request.id,
|
|
102
|
+
ok: false,
|
|
103
|
+
error: { message: error instanceof Error ? error.message : String(error), kind: "navigation" }
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const userAgent = typeof request.params?.userAgent === "string" ? request.params.userAgent : DEFAULT_USER_AGENT;
|
|
107
|
+
const timeout = Math.min(
|
|
108
|
+
typeof request.params?.timeoutMs === "number" ? request.params.timeoutMs : DEFAULT_GOTO_TIMEOUT_MS,
|
|
109
|
+
MAX_GOTO_TIMEOUT_MS
|
|
110
|
+
);
|
|
111
|
+
const waitUntil = typeof request.params?.waitUntil === "string" ? request.params.waitUntil : "domcontentloaded";
|
|
112
|
+
const lease = await openLease(state, leaseId, userAgent);
|
|
113
|
+
try {
|
|
114
|
+
await lease.page.goto(url, { waitUntil, timeout });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
id: request.id,
|
|
118
|
+
ok: false,
|
|
119
|
+
error: { message: error instanceof Error ? error.message : String(error), kind: "navigation" }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const html = await lease.page.content();
|
|
123
|
+
return { id: request.id, ok: true, result: { url: lease.page.url(), html } };
|
|
124
|
+
}
|
|
125
|
+
case "release":
|
|
126
|
+
await closeLease(state, stringParam(request, "leaseId"));
|
|
127
|
+
return { id: request.id, ok: true };
|
|
128
|
+
case "close":
|
|
129
|
+
await teardown(state);
|
|
130
|
+
return { id: request.id, ok: true };
|
|
131
|
+
default:
|
|
132
|
+
return {
|
|
133
|
+
id: request.id,
|
|
134
|
+
ok: false,
|
|
135
|
+
error: { message: `unknown method: ${request.method}`, kind: "runtime" }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function dispatch(state, request) {
|
|
140
|
+
try {
|
|
141
|
+
return await dispatchInner(state, request);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
id: request.id,
|
|
145
|
+
ok: false,
|
|
146
|
+
error: {
|
|
147
|
+
message: error instanceof Error ? error.message : String(error),
|
|
148
|
+
kind: error instanceof SidecarError ? error.kind : "unknown"
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
DEFAULT_GOTO_TIMEOUT_MS,
|
|
155
|
+
LEASE_IDLE_TIMEOUT_MS,
|
|
156
|
+
createState,
|
|
157
|
+
dispatch,
|
|
158
|
+
reapIdleLeases,
|
|
159
|
+
teardown
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/dispatch.ts"],
|
|
4
|
+
"sourcesContent": ["import { assertPublicUrl } from '@open-mercato/web-research'\nimport {\n DEFAULT_USER_AGENT,\n isSandboxDisabled,\n resolveLaunchArgs,\n importChromium,\n installNavigationGuard,\n launchChromium,\n type InstallRunner,\n type BrowserHandle,\n type ContextHandle,\n type PageHandle,\n} from './playwright'\nimport { SidecarError, type SidecarReply, type SidecarRequest } from './protocol'\n\nexport type Lease = { context: ContextHandle; page: PageHandle; touchedAt: number }\n\n/**\n * How long an untouched lease may live.\n *\n * `release` normally closes a context, but the caller can die between `render`\n * and `release` \u2014 a killed parent, a dropped socket \u2014 and every orphan is a live\n * BrowserContext holding memory for as long as the sidecar runs.\n */\nexport const LEASE_IDLE_TIMEOUT_MS = 5 * 60_000\n\nexport type SidecarState = {\n browser: BrowserHandle | null\n leases: Map<string, Lease>\n filterSubresources: boolean\n warn: (message: string) => void\n /** Injectable so a lease-expiry test does not have to wait five minutes. */\n now: () => number\n /** Injectable so tests never shell out to `npx playwright install`. */\n installRunner?: InstallRunner\n}\n\nexport const DEFAULT_GOTO_TIMEOUT_MS = 30_000\nconst MAX_GOTO_TIMEOUT_MS = 120_000\n\nexport function createState(overrides: Partial<SidecarState> = {}): SidecarState {\n return {\n browser: null,\n leases: new Map(),\n filterSubresources: process.env.OM_WEB_RESEARCH_BROWSER_FILTER_SUBRESOURCES === '1',\n warn: (message) => process.stderr.write(`om-web-research-sidecar: ${message}\\n`),\n now: () => Date.now(),\n ...overrides,\n }\n}\n\nasync function ensureBrowser(state: SidecarState): Promise<BrowserHandle> {\n if (state.browser) return state.browser\n const chromium = await importChromium()\n if (isSandboxDisabled()) {\n // Said once per browser, not once per render, but said: this process renders\n // pages chosen by a search engine or a model with the one mitigation\n // Chromium offers against a hostile renderer turned off.\n state.warn(\n 'SECURITY: Chromium sandbox is disabled by OM_WEB_RESEARCH_BROWSER_NO_SANDBOX=1 \u2014 a renderer compromise reaches this host',\n )\n }\n state.browser = await launchChromium(\n chromium,\n { headless: true, args: resolveLaunchArgs() },\n { onProgress: state.warn, ...(state.installRunner ? { install: state.installRunner } : {}) },\n )\n return state.browser\n}\n\n/**\n * One BrowserContext per lease. Contexts are Playwright's real isolation boundary\n * (cookies, storage, cache) and cost ~10ms, so two concurrent searches never share\n * a cookie jar the way a single shared page would.\n */\nasync function openLease(state: SidecarState, leaseId: string, userAgent: string): Promise<Lease> {\n await reapIdleLeases(state)\n const existing = state.leases.get(leaseId)\n if (existing) {\n existing.touchedAt = state.now()\n return existing\n }\n const browser = await ensureBrowser(state)\n const context = await browser.newContext({ userAgent, locale: 'en-US' })\n await installNavigationGuard(context, { filterSubresources: state.filterSubresources, warn: state.warn })\n const page = (await context.newPage()) as unknown as PageHandle\n const lease: Lease = { context, page, touchedAt: state.now() }\n state.leases.set(leaseId, lease)\n return lease\n}\n\n/** Closes leases nobody released. Swept on open, so an idle sidecar stays idle. */\nexport async function reapIdleLeases(state: SidecarState): Promise<void> {\n const cutoff = state.now() - LEASE_IDLE_TIMEOUT_MS\n for (const [leaseId, lease] of [...state.leases]) {\n if (lease.touchedAt > cutoff) continue\n state.warn(`reaping lease ${leaseId} idle for over ${LEASE_IDLE_TIMEOUT_MS}ms`)\n await closeLease(state, leaseId)\n }\n}\n\nasync function closeLease(state: SidecarState, leaseId: string): Promise<void> {\n const lease = state.leases.get(leaseId)\n if (!lease) return\n state.leases.delete(leaseId)\n try {\n await lease.context.close()\n } catch {\n // A context that already died takes nothing with it.\n }\n}\n\nexport async function teardown(state: SidecarState): Promise<void> {\n for (const leaseId of [...state.leases.keys()]) await closeLease(state, leaseId)\n const browser = state.browser\n state.browser = null\n if (browser) {\n try {\n await browser.close()\n } catch {\n // Best effort; the parent escalates to SIGKILL if we hang.\n }\n }\n}\n\nfunction stringParam(request: SidecarRequest, key: string): string {\n const value = request.params?.[key]\n if (typeof value !== 'string' || value.length === 0) {\n throw new SidecarError(`missing \"${key}\" parameter`, 'runtime')\n }\n return value\n}\n\nasync function dispatchInner(state: SidecarState, request: SidecarRequest): Promise<SidecarReply> {\n switch (request.method) {\n case 'ping':\n return { id: request.id, ok: true, result: { ready: true } }\n\n case 'render': {\n const url = stringParam(request, 'url')\n const leaseId = stringParam(request, 'leaseId')\n // Re-checked here, in a distinct process from the caller, and BEFORE\n // Playwright is imported \u2014 a blocked URL never launches a browser.\n try {\n await assertPublicUrl(url, 'browser render', { failClosed: true })\n } catch (error) {\n return {\n id: request.id,\n ok: false,\n error: { message: error instanceof Error ? error.message : String(error), kind: 'navigation' },\n }\n }\n\n const userAgent = typeof request.params?.userAgent === 'string' ? request.params.userAgent : DEFAULT_USER_AGENT\n const timeout = Math.min(\n typeof request.params?.timeoutMs === 'number' ? request.params.timeoutMs : DEFAULT_GOTO_TIMEOUT_MS,\n MAX_GOTO_TIMEOUT_MS,\n )\n const waitUntil = typeof request.params?.waitUntil === 'string' ? request.params.waitUntil : 'domcontentloaded'\n\n const lease = await openLease(state, leaseId, userAgent)\n try {\n await lease.page.goto(url, { waitUntil, timeout })\n } catch (error) {\n return {\n id: request.id,\n ok: false,\n error: { message: error instanceof Error ? error.message : String(error), kind: 'navigation' },\n }\n }\n const html = await lease.page.content()\n return { id: request.id, ok: true, result: { url: lease.page.url(), html } }\n }\n\n case 'release':\n await closeLease(state, stringParam(request, 'leaseId'))\n return { id: request.id, ok: true }\n\n case 'close':\n await teardown(state)\n return { id: request.id, ok: true }\n\n default:\n return {\n id: request.id,\n ok: false,\n error: { message: `unknown method: ${request.method}`, kind: 'runtime' },\n }\n }\n}\n\nexport async function dispatch(state: SidecarState, request: SidecarRequest): Promise<SidecarReply> {\n try {\n return await dispatchInner(state, request)\n } catch (error) {\n return {\n id: request.id,\n ok: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n kind: error instanceof SidecarError ? error.kind : 'unknown',\n },\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
browserAdapterModule,
|
|
3
|
+
browserOptionsSchema,
|
|
4
|
+
createBrowserAdapter
|
|
5
|
+
} from "./adapter.js";
|
|
6
|
+
import { Sidecar, createSidecar } from "./client.js";
|
|
7
|
+
import { SidecarError } from "./protocol.js";
|
|
8
|
+
import { browserAdapterModule as browserAdapterModule2 } from "./adapter.js";
|
|
9
|
+
export {
|
|
10
|
+
Sidecar,
|
|
11
|
+
SidecarError,
|
|
12
|
+
browserAdapterModule,
|
|
13
|
+
browserOptionsSchema,
|
|
14
|
+
createBrowserAdapter,
|
|
15
|
+
createSidecar,
|
|
16
|
+
browserAdapterModule2 as default
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["export {\n browserAdapterModule,\n browserOptionsSchema,\n createBrowserAdapter,\n type BrowserOptions,\n} from './adapter'\nexport { Sidecar, createSidecar, type SidecarOptions, type SidecarStream, type SpawnFn } from './client'\nexport { SidecarError, type SidecarErrorKind, type SidecarReply, type SidecarRequest } from './protocol'\n\nexport { browserAdapterModule as default } from './adapter'\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,SAAS,qBAA4E;AAC9F,SAAS,oBAAmF;AAE5F,SAAiC,wBAAxBA,6BAAuC;",
|
|
6
|
+
"names": ["browserAdapterModule"]
|
|
7
|
+
}
|