@plurnk/plurnk-schemes-http 0.1.0 → 0.3.0
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/SPEC.md +25 -9
- package/dist/Browser.d.ts +50 -0
- package/dist/Browser.d.ts.map +1 -0
- package/dist/Browser.js +176 -0
- package/dist/Browser.js.map +1 -0
- package/dist/Http.d.ts +11 -3
- package/dist/Http.d.ts.map +1 -1
- package/dist/Http.js +62 -27
- package/dist/Http.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/SPEC.md
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
```ts
|
|
8
8
|
static manifest: SchemeManifest = {
|
|
9
9
|
name: "http",
|
|
10
|
-
|
|
10
|
+
// Seed defaults (pre-fetch placeholders). `body` is retyped per-call via
|
|
11
|
+
// notifyChunk's mimetype arg — to the response Content-Type, or text/html
|
|
12
|
+
// for a rendered page. `header` is always text/plain.
|
|
13
|
+
channels: { body: "application/octet-stream", header: "text/plain" },
|
|
11
14
|
defaultChannel: "body",
|
|
12
15
|
category: "data",
|
|
13
16
|
scope: "session",
|
|
@@ -35,13 +38,14 @@ Results use the `passthrough` family (read-only / network shape) — http entrie
|
|
|
35
38
|
|
|
36
39
|
READ and SEND[200] share one core:
|
|
37
40
|
|
|
38
|
-
1. `ctx.subscriptions.open(pathname, handle)` — registers the subscription for cancel routing; returns the run+teardown-composed `AbortSignal`. The handle's `cancel()` aborts a local `AbortController` wired to the `fetch
|
|
39
|
-
2. `fetch(url, { signal })` — GET (READ) or POST (SEND[200], body from `SendBody.raw`)
|
|
40
|
-
3.
|
|
41
|
-
4.
|
|
42
|
-
5. `
|
|
41
|
+
1. `ctx.subscriptions.open(pathname, handle)` — registers the subscription for cancel routing; returns the run+teardown-composed `AbortSignal`. The handle's `cancel()` aborts a local `AbortController` wired to the `fetch`/render.
|
|
42
|
+
2. `fetch(url, { signal })` — GET (READ) or POST (SEND[200], body from `SendBody.raw`); read the response `Content-Type`.
|
|
43
|
+
3. **Render gate (§6):** a GET whose response is HTML routes to the render path; everything else (POST responses, non-HTML bodies) streams raw.
|
|
44
|
+
4. Response status + headers → `notifyChunk("header", …, "text/plain")`.
|
|
45
|
+
5. Body → `notifyChunk("body", chunk, mimetype)` — labelled with its real type (the response Content-Type, or `text/html` rendered). Byte path streams chunks as they arrive; render path writes the serialized DOM in one chunk.
|
|
46
|
+
6. `close("done", …)` on clean end; `close("error", reason)` on failure.
|
|
43
47
|
|
|
44
|
-
Returns `102 Processing` on success (the subscription drives the channel content). The composed signal aborting (loop.cancel) and the local handle (SEND[499]) both tear the fetch down.
|
|
48
|
+
Returns `102 Processing` on success (the subscription drives the channel content). The composed signal aborting (loop.cancel) and the local handle (SEND[499]) both tear the fetch/render down.
|
|
45
49
|
|
|
46
50
|
## §4 Status mapping
|
|
47
51
|
|
|
@@ -57,6 +61,18 @@ Returns `102 Processing` on success (the subscription drives the channel content
|
|
|
57
61
|
|
|
58
62
|
Error results carry a `scheme:http` `TelemetryEvent` (via `Results.error`).
|
|
59
63
|
|
|
60
|
-
## §5
|
|
64
|
+
## §5 Dependencies
|
|
61
65
|
|
|
62
|
-
`fetch` / `AbortController` / `TextDecoder` / `ReadableStream` are Node ≥25 built-ins.
|
|
66
|
+
The **byte path** is dependency-free: `fetch` / `AbortController` / `TextDecoder` / `ReadableStream` are Node ≥25 built-ins.
|
|
67
|
+
|
|
68
|
+
The **render path** takes one runtime dependency, `playwright`, **lazy-imported** (`Browser.ts`) so only an actual render pays for it — a byte fetch never loads it. The chromium binary is optional: set `PLURNK_HTTP_PLAYWRIGHT_WS` to drive a remote CDP endpoint (shared chromium / Lightpanda / browserless) instead of launching locally. This is the conscious, scoped inversion of the original "no runtime deps" stance — rendering is acquisition, and acquisition is this scheme's job.
|
|
69
|
+
|
|
70
|
+
## §6 Render lifecycle
|
|
71
|
+
|
|
72
|
+
`Browser` (`export default class`, barrel-exported as a standalone foundation) is the headless-Chromium render engine — ported from rummy.web's WebFetcher, render-only.
|
|
73
|
+
|
|
74
|
+
- **Gate:** a GET whose response `Content-Type` is `text/html` / `application/xhtml+xml` renders; the probe-fetch body is discarded and the browser does its own navigation. POST never renders.
|
|
75
|
+
- **Render:** warm chromium (one per `Browser`), per-run `BrowserContext` keyed on `ctx.runId`, navigate with `waitUntil: "networkidle"` + a salvage path (timed-out-but-rendered pages with substantive body text), serialize the final DOM via `page.content()`.
|
|
76
|
+
- **Body:** the serialized DOM is delivered as one `body` chunk labelled `text/html`; the mimetype layer projects everything (`content`/`symbols`/`deepXml`/embedding) off it. http never cleans or extracts — the body is the faithful, final page (schemes-http#1).
|
|
77
|
+
- **Config:** `PLURNK_HTTP_FETCH_TIMEOUT`, `PLURNK_HTTP_NO_SANDBOX`, `PLURNK_HTTP_CHROMIUM_HEAP_MB`, `PLURNK_HTTP_PLAYWRIGHT_WS`. Idle teardown after 15 min.
|
|
78
|
+
- **Cancel:** the composed `AbortSignal` / SEND[499] handle aborts the render by closing the page (in-flight `goto` rejects promptly).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface PwResponse {
|
|
2
|
+
status(): number;
|
|
3
|
+
statusText(): string;
|
|
4
|
+
headers(): Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
interface PwPage {
|
|
7
|
+
goto(url: string, opts: {
|
|
8
|
+
waitUntil: "networkidle";
|
|
9
|
+
timeout: number;
|
|
10
|
+
}): Promise<PwResponse | null>;
|
|
11
|
+
content(): Promise<string>;
|
|
12
|
+
evaluate<T>(fn: () => T): Promise<T>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
interface PwContext {
|
|
16
|
+
newPage(): Promise<PwPage>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
interface PwBrowser {
|
|
20
|
+
newContext(): Promise<PwContext>;
|
|
21
|
+
on(event: "disconnected", cb: () => void): void;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface ChromiumEngine {
|
|
25
|
+
launch(opts: {
|
|
26
|
+
headless: boolean;
|
|
27
|
+
args: ReadonlyArray<string>;
|
|
28
|
+
}): Promise<PwBrowser>;
|
|
29
|
+
connect(wsEndpoint: string): Promise<PwBrowser>;
|
|
30
|
+
}
|
|
31
|
+
export type ChromiumFactory = () => Promise<ChromiumEngine>;
|
|
32
|
+
export interface RenderResult {
|
|
33
|
+
readonly status: number;
|
|
34
|
+
readonly statusText: string;
|
|
35
|
+
readonly headers: ReadonlyArray<readonly [string, string]>;
|
|
36
|
+
readonly html: string;
|
|
37
|
+
}
|
|
38
|
+
export default class Browser {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(factory?: ChromiumFactory);
|
|
41
|
+
render(url: string, { runId, signal, timeout }: {
|
|
42
|
+
runId: number;
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
timeout?: number;
|
|
45
|
+
}): Promise<RenderResult>;
|
|
46
|
+
closeContext(runId: number): void;
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=Browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Browser.d.ts","sourceRoot":"","sources":["../src/Browser.ts"],"names":[],"mappings":"AAmBA,UAAU,UAAU;IAChB,MAAM,IAAI,MAAM,CAAC;IACjB,UAAU,IAAI,MAAM,CAAC;IACrB,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AACD,UAAU,MAAM;IACZ,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AACD,UAAU,SAAS;IACf,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AACD,UAAU,SAAS;IACf,UAAU,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAChD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AACD,MAAM,WAAW,cAAc;IAC3B,MAAM,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACrF,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACnD;AACD,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;AAO5D,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAwBD,MAAM,CAAC,OAAO,OAAO,OAAO;;gBAWZ,OAAO,GAAE,eAAgC;IAO/C,MAAM,CACR,GAAG,EAAE,MAAM,EACX,EAAE,KAAK,EAAE,MAAM,EAAE,OAAiE,EAAE,EAChF;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9D,OAAO,CAAC,YAAY,CAAC;IAwFxB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAe3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ/B"}
|
package/dist/Browser.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Browser — the headless-Chromium render foundation, ported from
|
|
2
|
+
// rummy.web's WebFetcher (@possumtech/rummy.web, MIT, same author). A
|
|
3
|
+
// STANDALONE foundation, not Http-private: the render scheme drives it now,
|
|
4
|
+
// and a future plurnk browser-troubleshooting MCP sits on the same warm pool.
|
|
5
|
+
//
|
|
6
|
+
// Scope here is render-ONLY: navigate, let JS run + hydration settle, serialize
|
|
7
|
+
// the final DOM. It returns the true rendered page; it never cleans, strips,
|
|
8
|
+
// or extracts — projection (markdown/symbols/deepXml) is the mimetype layer's
|
|
9
|
+
// job, off the faithful body we hand over.
|
|
10
|
+
//
|
|
11
|
+
// Driver is Playwright, lazy-imported so only the render path pays for it (the
|
|
12
|
+
// raw-byte fetch path stays Node-builtin-only). The engine is reached through
|
|
13
|
+
// a minimal structural seam (`ChromiumEngine`) so it can be injected — fakes
|
|
14
|
+
// in unit tests, and a remote CDP endpoint (Lightpanda/browserless/shared
|
|
15
|
+
// chromium) swapped in via env with zero code change.
|
|
16
|
+
// Salvage threshold: a navigation that times out on networkidle but whose DOM
|
|
17
|
+
// already holds this much body text is the chatty-page case (long-poll, ad
|
|
18
|
+
// refresh, a server that never closes the stream) — the page rendered, the
|
|
19
|
+
// network just never settled. Below it we discard: too little to be sure the
|
|
20
|
+
// DOM ever rendered the article rather than a skeleton.
|
|
21
|
+
const SALVAGE_MIN_BODY_CHARS = 200;
|
|
22
|
+
const IDLE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
23
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
24
|
+
const numEnv = (key, fallback) => {
|
|
25
|
+
const raw = process.env[key];
|
|
26
|
+
if (raw === undefined)
|
|
27
|
+
return fallback;
|
|
28
|
+
const n = Number(raw);
|
|
29
|
+
if (Number.isNaN(n))
|
|
30
|
+
throw new Error(`Browser: ${key}=${raw} is not a number`);
|
|
31
|
+
return n;
|
|
32
|
+
};
|
|
33
|
+
// Default factory: lazy-import the real chromium. The cast bridges Playwright's
|
|
34
|
+
// full type to our structural view at the single trusted library boundary.
|
|
35
|
+
const defaultFactory = async () => (await import("playwright")).chromium;
|
|
36
|
+
export default class Browser {
|
|
37
|
+
#factory;
|
|
38
|
+
#browser = null;
|
|
39
|
+
#launching = null;
|
|
40
|
+
// One BrowserContext per run — cookies / cache / storage scoped to the run
|
|
41
|
+
// that opened it, no cross-run bleed. Closed by closeContext() on run end
|
|
42
|
+
// or abort. The browser process stays warm across all of them.
|
|
43
|
+
#contexts = new Map();
|
|
44
|
+
#idleTimer = null;
|
|
45
|
+
// Inject a factory in tests; production lazy-imports playwright.
|
|
46
|
+
constructor(factory = defaultFactory) {
|
|
47
|
+
this.#factory = factory;
|
|
48
|
+
}
|
|
49
|
+
// Render a URL to its final serialized DOM. Opens a page in the run's
|
|
50
|
+
// context, navigates with the settle+salvage strategy, serializes, closes
|
|
51
|
+
// the page. Throws on navigation failure (the caller maps it to a status).
|
|
52
|
+
async render(url, { runId, signal, timeout = numEnv("PLURNK_HTTP_FETCH_TIMEOUT", DEFAULT_TIMEOUT_MS) }) {
|
|
53
|
+
const context = await this.#getContext(runId);
|
|
54
|
+
const page = await context.newPage();
|
|
55
|
+
// Abort cascades by closing the page — an in-flight goto rejects with
|
|
56
|
+
// "Target closed", surfacing promptly instead of blocking on timeout.
|
|
57
|
+
const onAbort = () => { page.close().catch(() => { }); };
|
|
58
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
59
|
+
// Already aborted before the page opened: the listener won't fire
|
|
60
|
+
// retroactively, so close now — the navigation must not proceed.
|
|
61
|
+
if (signal?.aborted)
|
|
62
|
+
onAbort();
|
|
63
|
+
try {
|
|
64
|
+
const response = await this.#safeGoto(page, url, timeout);
|
|
65
|
+
const html = await page.content();
|
|
66
|
+
return {
|
|
67
|
+
status: response?.status() ?? 200,
|
|
68
|
+
statusText: response?.statusText() ?? "",
|
|
69
|
+
headers: response ? Object.entries(response.headers()) : [],
|
|
70
|
+
html,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
signal?.removeEventListener("abort", onAbort);
|
|
75
|
+
await page.close().catch(() => { });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// page.goto with the salvage path. networkidle timing out while the DOM has
|
|
79
|
+
// already rendered substantive body text is the chatty-page case: the
|
|
80
|
+
// content is there even though the network never settled. readyState is
|
|
81
|
+
// unreliable (a never-ending stream stays `loading` forever); the load-
|
|
82
|
+
// bearing signal is the body's innerText length. Returns the Response on
|
|
83
|
+
// normal completion, null on salvage, and re-throws every other error.
|
|
84
|
+
async #safeGoto(page, url, timeout) {
|
|
85
|
+
try {
|
|
86
|
+
return await page.goto(url, { waitUntil: "networkidle", timeout });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (!(err instanceof Error) || err.name !== "TimeoutError")
|
|
90
|
+
throw err;
|
|
91
|
+
const bodyLen = await page
|
|
92
|
+
.evaluate(() => document?.body?.innerText?.length ?? 0)
|
|
93
|
+
.catch(() => 0);
|
|
94
|
+
if (bodyLen < SALVAGE_MIN_BODY_CHARS)
|
|
95
|
+
throw err;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Get-or-launch the warm chromium. Connects to a remote CDP endpoint via
|
|
100
|
+
// PLURNK_HTTP_PLAYWRIGHT_WS if set (shared / Lightpanda / browserless),
|
|
101
|
+
// else launches locally. Single browser across all runs; per-run isolation
|
|
102
|
+
// is at the context layer. Relaunches if chromium dies (OOM/segfault/WS
|
|
103
|
+
// teardown) leaves the handle stale.
|
|
104
|
+
async #getBrowser() {
|
|
105
|
+
this.#touchIdle();
|
|
106
|
+
if (this.#browser)
|
|
107
|
+
return this.#browser;
|
|
108
|
+
this.#launching ??= (async () => {
|
|
109
|
+
const chromium = await this.#factory();
|
|
110
|
+
const ws = process.env.PLURNK_HTTP_PLAYWRIGHT_WS;
|
|
111
|
+
if (ws)
|
|
112
|
+
return chromium.connect(ws);
|
|
113
|
+
const args = [];
|
|
114
|
+
if (process.env.PLURNK_HTTP_NO_SANDBOX === "1")
|
|
115
|
+
args.push("--no-sandbox");
|
|
116
|
+
const heapMb = process.env.PLURNK_HTTP_CHROMIUM_HEAP_MB;
|
|
117
|
+
if (heapMb)
|
|
118
|
+
args.push(`--js-flags=--max-old-space-size=${heapMb}`);
|
|
119
|
+
return chromium.launch({ headless: true, args });
|
|
120
|
+
})();
|
|
121
|
+
const browser = await this.#launching;
|
|
122
|
+
this.#launching = null;
|
|
123
|
+
browser.on("disconnected", () => {
|
|
124
|
+
if (this.#browser === browser) {
|
|
125
|
+
this.#browser = null;
|
|
126
|
+
this.#contexts.clear();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
this.#browser = browser;
|
|
130
|
+
return browser;
|
|
131
|
+
}
|
|
132
|
+
// Get-or-create the run's BrowserContext. Desktop default (no device
|
|
133
|
+
// emulation) — we render the true page, not a mobile-extraction view.
|
|
134
|
+
async #getContext(runId) {
|
|
135
|
+
this.#touchIdle();
|
|
136
|
+
const existing = this.#contexts.get(runId);
|
|
137
|
+
if (existing)
|
|
138
|
+
return existing;
|
|
139
|
+
const browser = await this.#getBrowser();
|
|
140
|
+
const context = await browser.newContext();
|
|
141
|
+
this.#contexts.set(runId, context);
|
|
142
|
+
return context;
|
|
143
|
+
}
|
|
144
|
+
// Drop the run's context (run end or abort). Closing it cascades to any
|
|
145
|
+
// in-flight page in that context. Fire-and-forget.
|
|
146
|
+
closeContext(runId) {
|
|
147
|
+
const context = this.#contexts.get(runId);
|
|
148
|
+
if (!context)
|
|
149
|
+
return;
|
|
150
|
+
this.#contexts.delete(runId);
|
|
151
|
+
context.close().catch(() => { });
|
|
152
|
+
}
|
|
153
|
+
#touchIdle() {
|
|
154
|
+
if (this.#idleTimer)
|
|
155
|
+
clearTimeout(this.#idleTimer);
|
|
156
|
+
this.#idleTimer = setTimeout(() => { this.close().catch(() => { }); }, IDLE_TIMEOUT_MS);
|
|
157
|
+
this.#idleTimer.unref?.();
|
|
158
|
+
}
|
|
159
|
+
// Tear everything down: per-run contexts then the browser. In CDP mode
|
|
160
|
+
// close() disconnects the local handle without shutting the remote down.
|
|
161
|
+
async close() {
|
|
162
|
+
if (this.#idleTimer) {
|
|
163
|
+
clearTimeout(this.#idleTimer);
|
|
164
|
+
this.#idleTimer = null;
|
|
165
|
+
}
|
|
166
|
+
const contexts = [...this.#contexts.values()];
|
|
167
|
+
this.#contexts.clear();
|
|
168
|
+
await Promise.allSettled(contexts.map((c) => c.close()));
|
|
169
|
+
if (this.#browser) {
|
|
170
|
+
await this.#browser.close().catch(() => { });
|
|
171
|
+
this.#browser = null;
|
|
172
|
+
}
|
|
173
|
+
this.#launching = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=Browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Browser.js","sourceRoot":"","sources":["../src/Browser.ts"],"names":[],"mappings":"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"}
|
package/dist/Http.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import type { SchemeCtx, PassthroughResult, SchemeManifest } from "@plurnk/plurnk-schemes";
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import type { SchemeCtx, PassthroughResult, SchemeManifest, SchemeHandler, ReadStatement, SendStatement } from "@plurnk/plurnk-schemes";
|
|
2
|
+
import { type RenderResult } from "./Browser.ts";
|
|
3
|
+
interface Renderer {
|
|
4
|
+
render(url: string, opts: {
|
|
5
|
+
runId: number;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}): Promise<RenderResult>;
|
|
8
|
+
}
|
|
9
|
+
export default class Http implements SchemeHandler {
|
|
4
10
|
#private;
|
|
5
11
|
static manifest: SchemeManifest;
|
|
12
|
+
constructor(browser?: Renderer);
|
|
6
13
|
read(statement: ReadStatement, ctx: SchemeCtx): Promise<PassthroughResult>;
|
|
7
14
|
send(statement: SendStatement, ctx: SchemeCtx): Promise<PassthroughResult>;
|
|
8
15
|
}
|
|
16
|
+
export {};
|
|
9
17
|
//# sourceMappingURL=Http.d.ts.map
|
package/dist/Http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../src/Http.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACR,SAAS,EAET,iBAAiB,EACjB,cAAc,
|
|
1
|
+
{"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../src/Http.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACR,SAAS,EAET,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EAEhB,MAAM,wBAAwB,CAAC;AAEhC,OAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAO1D,UAAU,QAAQ;IACd,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC7F;AAED,MAAM,CAAC,OAAO,OAAO,IAAK,YAAW,aAAa;;IAC9C,MAAM,CAAC,QAAQ,EAAE,cAAc,CAiB7B;gBAKU,OAAO,GAAE,QAAwB;IAOvC,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAa1E,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAiHnF"}
|
package/dist/Http.js
CHANGED
|
@@ -17,17 +17,21 @@
|
|
|
17
17
|
// Network exception: SPEC §5 forbids opening connections "unless specifically
|
|
18
18
|
// a network scheme." This IS that scheme — `fetch` is the whole point. No
|
|
19
19
|
// runtime deps: `fetch` and `AbortController` are Node built-ins.
|
|
20
|
+
var _a;
|
|
20
21
|
import { Results } from "@plurnk/plurnk-schemes";
|
|
22
|
+
import Browser from "./Browser.js";
|
|
21
23
|
// The channel the response body streams into, and the header metadata channel.
|
|
22
24
|
const BODY = "body";
|
|
23
25
|
const HEADER = "header";
|
|
24
|
-
|
|
26
|
+
class Http {
|
|
25
27
|
static manifest = {
|
|
26
28
|
name: "http",
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
29
|
+
// Channel mimetypes here are SEED DEFAULTS (pre-fetch placeholders).
|
|
30
|
+
// body is retyped per-call via notifyChunk's mimetype arg — to the real
|
|
31
|
+
// response Content-Type, or text/html for a rendered page; octet-stream
|
|
32
|
+
// is the honest "unknown until fetched". header is always the status
|
|
33
|
+
// line + headers (text/plain).
|
|
34
|
+
channels: { [BODY]: "application/octet-stream", [HEADER]: "text/plain" },
|
|
31
35
|
defaultChannel: BODY,
|
|
32
36
|
category: "data",
|
|
33
37
|
scope: "session",
|
|
@@ -38,13 +42,20 @@ export default class Http {
|
|
|
38
42
|
requiresWeb: true, // excluded under the loop's noWeb flag
|
|
39
43
|
},
|
|
40
44
|
};
|
|
41
|
-
//
|
|
42
|
-
//
|
|
45
|
+
// The render foundation (lazy chromium). Injectable for tests; one warm
|
|
46
|
+
// pool per Http instance, shared across this scheme's fetches.
|
|
47
|
+
#browser;
|
|
48
|
+
constructor(browser = new Browser()) {
|
|
49
|
+
this.#browser = browser;
|
|
50
|
+
}
|
|
51
|
+
// READ → fetch; an HTML page is rendered, everything else streams raw.
|
|
52
|
+
// Returns 102 Processing; the subscription drives the channel content the
|
|
53
|
+
// model sees next turn.
|
|
43
54
|
async read(statement, ctx) {
|
|
44
55
|
if (statement.target === null || statement.target.kind !== "url") {
|
|
45
|
-
return
|
|
56
|
+
return _a.#bad(400, "http", "bad_target", "READ requires an http(s):// URL target");
|
|
46
57
|
}
|
|
47
|
-
return
|
|
58
|
+
return this.#fetchStream(statement.target, ctx, undefined);
|
|
48
59
|
}
|
|
49
60
|
// SEND dispatch — status-code-as-verb (SPEC §3.5).
|
|
50
61
|
// 200 → request with body (POST), stream response
|
|
@@ -54,12 +65,12 @@ export default class Http {
|
|
|
54
65
|
// scheme-level no-op here is correct — teardown already happened)
|
|
55
66
|
async send(statement, ctx) {
|
|
56
67
|
if (statement.target === null || statement.target.kind !== "url") {
|
|
57
|
-
return
|
|
68
|
+
return _a.#bad(400, "http", "bad_target", "SEND requires an http(s):// URL target");
|
|
58
69
|
}
|
|
59
70
|
const status = statement.signal;
|
|
60
71
|
if (status === 200) {
|
|
61
72
|
const body = statement.body?.raw ?? "";
|
|
62
|
-
return
|
|
73
|
+
return this.#fetchStream(statement.target, ctx, body);
|
|
63
74
|
}
|
|
64
75
|
if (status === 410) {
|
|
65
76
|
const { status: delStatus } = await ctx.entries.delete(statement.target.pathname);
|
|
@@ -72,20 +83,22 @@ export default class Http {
|
|
|
72
83
|
return { shape: "passthrough", status: 200 };
|
|
73
84
|
}
|
|
74
85
|
// Entry-bearing schemes return 501 for status codes they don't interpret.
|
|
75
|
-
return
|
|
86
|
+
return _a.#bad(501, "http", "unsupported_send", `SEND[${status}] not supported by http`);
|
|
76
87
|
}
|
|
77
88
|
// The streaming core, shared by READ and SEND[200]. Opens the subscription
|
|
78
|
-
// (registering the abort handle for SEND[499] routing), fetches,
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
// (registering the abort handle for SEND[499] routing), fetches headers,
|
|
90
|
+
// then EITHER renders (always-render: a GET of an HTML page is re-acquired
|
|
91
|
+
// through the browser and its final DOM becomes the body) OR streams the
|
|
92
|
+
// raw bytes (POST responses and every non-HTML body). Each chunk is labelled
|
|
93
|
+
// with its real mimetype via notifyChunk. Settles via close().
|
|
94
|
+
async #fetchStream(target, ctx, requestBody) {
|
|
95
|
+
const url = _a.#urlFrom(target);
|
|
83
96
|
const pathname = target.pathname;
|
|
84
97
|
// Local AbortController for force-cancel from outside (SEND[499]).
|
|
85
98
|
const local = new AbortController();
|
|
86
99
|
const handle = { cancel: () => local.abort() };
|
|
87
100
|
// open() returns the run+teardown-composed signal — fires on loop.cancel
|
|
88
|
-
// OR our local teardown. Wire it
|
|
101
|
+
// OR our local teardown. Wire it so either path aborts the fetch/render.
|
|
89
102
|
const composed = await ctx.subscriptions.open(pathname, handle);
|
|
90
103
|
const onAbort = () => local.abort();
|
|
91
104
|
composed.addEventListener("abort", onAbort, { once: true });
|
|
@@ -96,11 +109,24 @@ export default class Http {
|
|
|
96
109
|
signal: local.signal,
|
|
97
110
|
redirect: "follow",
|
|
98
111
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
113
|
+
// Always-render: a GET of an HTML page is re-acquired through the
|
|
114
|
+
// browser so the body is the final rendered DOM. The probe-fetch
|
|
115
|
+
// body is discarded — the browser does its own navigation. A POST
|
|
116
|
+
// never renders (it can't be replayed as a browser navigation).
|
|
117
|
+
const isHtml = requestBody === undefined
|
|
118
|
+
&& /^(?:text\/html|application\/xhtml\+xml)\b/i.test(contentType);
|
|
119
|
+
if (isHtml) {
|
|
120
|
+
await response.body?.cancel();
|
|
121
|
+
const result = await this.#browser.render(url, { runId: ctx.runId, signal: local.signal });
|
|
122
|
+
await _a.#writeHeader(ctx, result.status, result.statusText, result.headers);
|
|
123
|
+
await ctx.subscriptions.notifyChunk(BODY, result.html, "text/html");
|
|
124
|
+
await ctx.subscriptions.close("done", `rendered HTTP ${result.status}; ${result.html.length} chars`);
|
|
125
|
+
return { shape: "passthrough", status: 102 };
|
|
126
|
+
}
|
|
127
|
+
// Byte path: stream the body labelled with its real content type.
|
|
128
|
+
await _a.#writeHeader(ctx, response.status, response.statusText, [...response.headers]);
|
|
129
|
+
const bodyMime = contentType.split(";")[0].trim() || "application/octet-stream";
|
|
104
130
|
if (response.body === null) {
|
|
105
131
|
await ctx.subscriptions.close("done", `HTTP ${response.status}; empty body`);
|
|
106
132
|
return { shape: "passthrough", status: 102 };
|
|
@@ -109,11 +135,11 @@ export default class Http {
|
|
|
109
135
|
const decoder = new TextDecoder();
|
|
110
136
|
for await (const chunk of response.body) {
|
|
111
137
|
bytes += chunk.length;
|
|
112
|
-
await ctx.subscriptions.notifyChunk(BODY, decoder.decode(chunk, { stream: true }));
|
|
138
|
+
await ctx.subscriptions.notifyChunk(BODY, decoder.decode(chunk, { stream: true }), bodyMime);
|
|
113
139
|
}
|
|
114
140
|
const tail = decoder.decode();
|
|
115
141
|
if (tail.length > 0)
|
|
116
|
-
await ctx.subscriptions.notifyChunk(BODY, tail);
|
|
142
|
+
await ctx.subscriptions.notifyChunk(BODY, tail, bodyMime);
|
|
117
143
|
await ctx.subscriptions.close("done", `HTTP ${response.status}; ${bytes} bytes`);
|
|
118
144
|
return { shape: "passthrough", status: 102 };
|
|
119
145
|
}
|
|
@@ -121,13 +147,20 @@ export default class Http {
|
|
|
121
147
|
const aborted = local.signal.aborted;
|
|
122
148
|
const reason = aborted ? "aborted" : err instanceof Error ? err.message : String(err);
|
|
123
149
|
await ctx.subscriptions.close("error", reason);
|
|
124
|
-
// 499 for client-cancelled, 502 for upstream/network failure.
|
|
125
|
-
return
|
|
150
|
+
// 499 for client-cancelled, 502 for upstream/network/render failure.
|
|
151
|
+
return _a.#bad(aborted ? 499 : 502, "http", aborted ? "aborted" : "fetch_failed", reason);
|
|
126
152
|
}
|
|
127
153
|
finally {
|
|
128
154
|
composed.removeEventListener("abort", onAbort);
|
|
129
155
|
}
|
|
130
156
|
}
|
|
157
|
+
// Record the response status line + headers into the HEADER channel (text/plain).
|
|
158
|
+
static async #writeHeader(ctx, status, statusText, headers) {
|
|
159
|
+
const lines = [`HTTP ${status} ${statusText}`];
|
|
160
|
+
for (const [k, v] of headers)
|
|
161
|
+
lines.push(`${k}: ${v}`);
|
|
162
|
+
await ctx.subscriptions.notifyChunk(HEADER, lines.join("\n"), "text/plain");
|
|
163
|
+
}
|
|
131
164
|
// Reconstruct the absolute URL from the parsed UrlPath. `raw` is the
|
|
132
165
|
// grammar's verbatim URL — authoritative; the decomposed fields are a
|
|
133
166
|
// convenience. Use raw so query strings / auth / port survive exactly.
|
|
@@ -138,4 +171,6 @@ export default class Http {
|
|
|
138
171
|
return { shape: "passthrough", status, error: Results.error(scheme, kind, message) };
|
|
139
172
|
}
|
|
140
173
|
}
|
|
174
|
+
_a = Http;
|
|
175
|
+
export default Http;
|
|
141
176
|
//# sourceMappingURL=Http.js.map
|
package/dist/Http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Http.js","sourceRoot":"","sources":["../src/Http.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,uEAAuE;AACvE,EAAE;AACF,WAAW;AACX,+EAA+E;AAC/E,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,sEAAsE;AACtE,iFAAiF;AACjF,oEAAoE;AACpE,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,kEAAkE
|
|
1
|
+
{"version":3,"file":"Http.js","sourceRoot":"","sources":["../src/Http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAI5C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,7 @@
|
|
|
3
3
|
// (plugin discovery scans node_modules/@plurnk/* for `plurnk.kind === "scheme"`).
|
|
4
4
|
export { default } from "./Http.js";
|
|
5
5
|
export { default as Http } from "./Http.js";
|
|
6
|
+
// Standalone render foundation — exported so a future plurnk browser-
|
|
7
|
+
// troubleshooting MCP package can sit on the same warm-Chromium pool.
|
|
8
|
+
export { default as Browser } from "./Browser.js";
|
|
6
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,0EAA0E;AAC1E,kFAAkF;AAClF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,0EAA0E;AAC1E,kFAAkF;AAClF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAE5C,sEAAsE;AACtE,sEAAsE;AACtE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plurnk/plurnk-schemes-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "http(s):// URI scheme handler for the plurnk agent runtime — fetch + streaming response body.",
|
|
5
5
|
"keywords": ["plurnk", "scheme", "uri", "http", "https"],
|
|
6
6
|
"homepage": "https://github.com/plurnk/plurnk-schemes-http#readme",
|
|
@@ -44,13 +44,14 @@
|
|
|
44
44
|
"build": "npm run build:dist",
|
|
45
45
|
"prepare": "npm run build"
|
|
46
46
|
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"playwright": "^1.58.2"
|
|
49
|
+
},
|
|
47
50
|
"peerDependencies": {
|
|
48
|
-
"@plurnk/plurnk-
|
|
49
|
-
"@plurnk/plurnk-schemes": "0.4.4"
|
|
51
|
+
"@plurnk/plurnk-schemes": "0.8.0"
|
|
50
52
|
},
|
|
51
53
|
"devDependencies": {
|
|
52
|
-
"@plurnk/plurnk-
|
|
53
|
-
"@plurnk/plurnk-schemes": "0.4.4",
|
|
54
|
+
"@plurnk/plurnk-schemes": "0.8.0",
|
|
54
55
|
"@types/node": "^25.8.0",
|
|
55
56
|
"typescript": "^6.0.3"
|
|
56
57
|
}
|