@lunora/browser 1.0.0-alpha.8 → 1.0.0-alpha.9
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/dist/index.d.mts +130 -130
- package/dist/index.d.ts +130 -130
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Structural projection of the Cloudflare **Browser Rendering** binding
|
|
3
|
-
* (`env.BROWSER`). The binding is a `Fetcher` under the hood — `@cloudflare/playwright`
|
|
4
|
-
* drives it via `launch(env.BROWSER)`. Declared locally (an empty structural
|
|
5
|
-
* marker) so unit tests can pass a plain-object double and the real binding
|
|
6
|
-
* satisfies the same shape without importing `@cloudflare/workers-types` into
|
|
7
|
-
* the public surface. See https://developers.cloudflare.com/browser-rendering/.
|
|
8
|
-
*
|
|
9
|
-
* It is intentionally opaque: callers never touch the binding directly, they
|
|
10
|
-
* hand it to {@link LunoraBrowserOptions.binding} and the Playwright layer
|
|
11
|
-
* consumes it. `fetch` is REQUIRED (the real binding is a `Fetcher`, so it
|
|
12
|
-
* always has one) so the marker actually excludes an arbitrary value like `{}` —
|
|
13
|
-
* a bare object fails to type-check where a binding is required, catching the
|
|
14
|
-
* misuse at the call site instead of deferring to an opaque launch error.
|
|
15
|
-
* @experimental
|
|
16
|
-
*/
|
|
2
|
+
* Structural projection of the Cloudflare **Browser Rendering** binding
|
|
3
|
+
* (`env.BROWSER`). The binding is a `Fetcher` under the hood — `@cloudflare/playwright`
|
|
4
|
+
* drives it via `launch(env.BROWSER)`. Declared locally (an empty structural
|
|
5
|
+
* marker) so unit tests can pass a plain-object double and the real binding
|
|
6
|
+
* satisfies the same shape without importing `@cloudflare/workers-types` into
|
|
7
|
+
* the public surface. See https://developers.cloudflare.com/browser-rendering/.
|
|
8
|
+
*
|
|
9
|
+
* It is intentionally opaque: callers never touch the binding directly, they
|
|
10
|
+
* hand it to {@link LunoraBrowserOptions.binding} and the Playwright layer
|
|
11
|
+
* consumes it. `fetch` is REQUIRED (the real binding is a `Fetcher`, so it
|
|
12
|
+
* always has one) so the marker actually excludes an arbitrary value like `{}` —
|
|
13
|
+
* a bare object fails to type-check where a binding is required, catching the
|
|
14
|
+
* misuse at the call site instead of deferring to an opaque launch error.
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
17
17
|
interface BrowserBindingLike {
|
|
18
18
|
readonly fetch: (...args: never[]) => unknown;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Minimal projection of a Playwright `Route` (the argument the `page.route`
|
|
22
|
-
* handler receives). Only the members the SSRF redirect guard drives are
|
|
23
|
-
* declared: inspect the intercepted request's URL / navigation-ness, then either
|
|
24
|
-
* let it proceed ({@link RouteLike.continue}) or reject it ({@link RouteLike.abort}).
|
|
25
|
-
*/
|
|
21
|
+
* Minimal projection of a Playwright `Route` (the argument the `page.route`
|
|
22
|
+
* handler receives). Only the members the SSRF redirect guard drives are
|
|
23
|
+
* declared: inspect the intercepted request's URL / navigation-ness, then either
|
|
24
|
+
* let it proceed ({@link RouteLike.continue}) or reject it ({@link RouteLike.abort}).
|
|
25
|
+
*/
|
|
26
26
|
interface RouteLike {
|
|
27
27
|
/** Reject the intercepted request (fail-closed); `errorCode` is a Playwright abort reason. */
|
|
28
28
|
abort: (errorCode?: string) => Promise<void>;
|
|
@@ -35,11 +35,11 @@ interface RouteLike {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Minimal projection of a Playwright `Page` — just the methods the helpers drive.
|
|
39
|
-
* Declared structurally so a test can inject a plain stub instead of a real
|
|
40
|
-
* headless page (which needs workerd + the Browser Rendering binding).
|
|
41
|
-
* @experimental
|
|
42
|
-
*/
|
|
38
|
+
* Minimal projection of a Playwright `Page` — just the methods the helpers drive.
|
|
39
|
+
* Declared structurally so a test can inject a plain stub instead of a real
|
|
40
|
+
* headless page (which needs workerd + the Browser Rendering binding).
|
|
41
|
+
* @experimental
|
|
42
|
+
*/
|
|
43
43
|
interface PageLike {
|
|
44
44
|
/** Return the page's serialized HTML after the navigation settles. */
|
|
45
45
|
content: () => Promise<string>;
|
|
@@ -53,11 +53,11 @@ interface PageLike {
|
|
|
53
53
|
/** Render the page to a PDF buffer. */
|
|
54
54
|
pdf: (options?: Record<string, unknown>) => Promise<Uint8Array>;
|
|
55
55
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
* Register a request interceptor (Playwright `page.route`). Optional: a fake
|
|
57
|
+
* or older page double without it still works — the SSRF redirect guard only
|
|
58
|
+
* activates when interception is available, and the initial-URL guard applies
|
|
59
|
+
* regardless. `pattern` follows Playwright's glob/URL matcher.
|
|
60
|
+
*/
|
|
61
61
|
route?: (pattern: string, handler: (route: RouteLike) => unknown) => Promise<void>;
|
|
62
62
|
/** Render the page to a PNG/JPEG buffer. */
|
|
63
63
|
screenshot: (options?: Record<string, unknown>) => Promise<Uint8Array>;
|
|
@@ -68,175 +68,175 @@ interface PageLike {
|
|
|
68
68
|
}) => Promise<void>;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* Minimal projection of a Playwright `BrowserContext`. Only `newPage` is used;
|
|
72
|
-
* declared structurally for the same test-double reason as {@link PageLike}.
|
|
73
|
-
* @experimental
|
|
74
|
-
*/
|
|
71
|
+
* Minimal projection of a Playwright `BrowserContext`. Only `newPage` is used;
|
|
72
|
+
* declared structurally for the same test-double reason as {@link PageLike}.
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
75
75
|
interface BrowserContextLike {
|
|
76
76
|
newPage: () => Promise<PageLike>;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Minimal projection of a Playwright `Browser` (the value `launch` resolves to).
|
|
80
|
-
* Only `newContext`/`close` are used; declared structurally for the same
|
|
81
|
-
* test-double reason as {@link PageLike}.
|
|
82
|
-
* @experimental
|
|
83
|
-
*/
|
|
79
|
+
* Minimal projection of a Playwright `Browser` (the value `launch` resolves to).
|
|
80
|
+
* Only `newContext`/`close` are used; declared structurally for the same
|
|
81
|
+
* test-double reason as {@link PageLike}.
|
|
82
|
+
* @experimental
|
|
83
|
+
*/
|
|
84
84
|
interface BrowserLike {
|
|
85
85
|
close: () => Promise<void>;
|
|
86
86
|
newContext: () => Promise<BrowserContextLike>;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
|
-
* Structural projection of `@cloudflare/playwright`'s `launch` export
|
|
90
|
-
* (`import { launch } from "@cloudflare/playwright"`). Injected via
|
|
91
|
-
* {@link LunoraBrowserOptions.launch} so the factory never imports
|
|
92
|
-
* `@cloudflare/playwright` at module top — that keeps the heavy optional peer
|
|
93
|
-
* dep out of the bundle for apps that never screenshot, and lets tests pass a
|
|
94
|
-
* fake. Calling it with the Browser Rendering binding resolves a {@link BrowserLike}.
|
|
95
|
-
* @experimental
|
|
96
|
-
*/
|
|
89
|
+
* Structural projection of `@cloudflare/playwright`'s `launch` export
|
|
90
|
+
* (`import { launch } from "@cloudflare/playwright"`). Injected via
|
|
91
|
+
* {@link LunoraBrowserOptions.launch} so the factory never imports
|
|
92
|
+
* `@cloudflare/playwright` at module top — that keeps the heavy optional peer
|
|
93
|
+
* dep out of the bundle for apps that never screenshot, and lets tests pass a
|
|
94
|
+
* fake. Calling it with the Browser Rendering binding resolves a {@link BrowserLike}.
|
|
95
|
+
* @experimental
|
|
96
|
+
*/
|
|
97
97
|
type BrowserLaunchLike = (binding: BrowserBindingLike, options?: Record<string, unknown>) => Promise<BrowserLike>;
|
|
98
98
|
/**
|
|
99
|
-
* Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
|
|
100
|
-
* @experimental
|
|
101
|
-
*/
|
|
99
|
+
* Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
|
|
100
|
+
* @experimental
|
|
101
|
+
*/
|
|
102
102
|
interface NavigateOptions {
|
|
103
103
|
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
* Hard timeout in milliseconds for the navigation + operation. Clamped to a
|
|
105
|
+
* sane ceiling so a hung/hostile page can't pin the worker. Default 30000.
|
|
106
|
+
*/
|
|
107
107
|
timeoutMs?: number;
|
|
108
108
|
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
* Playwright navigation wait condition. Playwright's set differs from
|
|
110
|
+
* Puppeteer's: `load`, `domcontentloaded`, `networkidle`, `commit`.
|
|
111
|
+
* Default `load`.
|
|
112
|
+
*/
|
|
113
113
|
waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
-
* Options for {@link Browser.screenshot}.
|
|
117
|
-
* @experimental
|
|
118
|
-
*/
|
|
116
|
+
* Options for {@link Browser.screenshot}.
|
|
117
|
+
* @experimental
|
|
118
|
+
*/
|
|
119
119
|
interface ScreenshotOptions extends NavigateOptions {
|
|
120
120
|
/** Capture the full scrollable page rather than just the viewport. */
|
|
121
121
|
fullPage?: boolean;
|
|
122
122
|
/** Image encoding. Default `png`. */
|
|
123
123
|
type?: "jpeg" | "png";
|
|
124
124
|
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
* Viewport size. Each dimension is hard-capped (see the factory's
|
|
126
|
+
* `MAX_VIEWPORT_*`) so a caller can't request a multi-million-pixel render.
|
|
127
|
+
*/
|
|
128
128
|
viewport?: {
|
|
129
129
|
height: number;
|
|
130
130
|
width: number;
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
* Options for {@link Browser.pdf}.
|
|
135
|
-
* @experimental
|
|
136
|
-
*/
|
|
134
|
+
* Options for {@link Browser.pdf}.
|
|
135
|
+
* @experimental
|
|
136
|
+
*/
|
|
137
137
|
interface PdfOptions extends NavigateOptions {
|
|
138
138
|
/** Paper format (`A4`, `Letter`, …) forwarded to Playwright. */
|
|
139
139
|
format?: string;
|
|
140
140
|
/** Print background graphics. Default `false`. */
|
|
141
141
|
printBackground?: boolean;
|
|
142
142
|
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
* Viewport used while laying out the page before printing. Hard-capped like
|
|
144
|
+
* {@link ScreenshotOptions.viewport}.
|
|
145
|
+
*/
|
|
146
146
|
viewport?: {
|
|
147
147
|
height: number;
|
|
148
148
|
width: number;
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
* `LunoraBrowserOptions` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
153
|
-
* @experimental
|
|
154
|
-
*/
|
|
152
|
+
* `LunoraBrowserOptions` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
153
|
+
* @experimental
|
|
154
|
+
*/
|
|
155
155
|
interface LunoraBrowserOptions {
|
|
156
156
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
* Strict host allowlist. When set (non-empty), a navigation URL is refused
|
|
158
|
+
* unless its hostname exactly matches one of these entries (case-insensitive,
|
|
159
|
+
* trailing-dot-normalized, IPv6 brackets stripped). This is the only guard
|
|
160
|
+
* that fully closes DNS rebinding: a public hostname that resolves to a
|
|
161
|
+
* private/metadata IP can still be pinned out if it isn't on the list. Set it
|
|
162
|
+
* whenever you pass client-controlled URLs to the browser. Leave it unset (the
|
|
163
|
+
* default) to keep the previous behavior (only the string-based SSRF guard).
|
|
164
|
+
*/
|
|
165
165
|
allowedHosts?: string[];
|
|
166
166
|
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
* Opt out of the SSRF guard that, by default, refuses to navigate to a
|
|
168
|
+
* private / internal / loopback / link-local host (RFC1918, `127.0.0.0/8`,
|
|
169
|
+
* `169.254.0.0/16` incl. the cloud-metadata address, CGNAT, IPv6 ULA/
|
|
170
|
+
* link-local, and `localhost` / `*.internal` / `*.local` literals). Leave it
|
|
171
|
+
* `false` (the default) unless every caller-supplied URL is trusted — e.g.
|
|
172
|
+
* you deliberately drive the browser at an internal service reachable through
|
|
173
|
+
* a Cloudflare Tunnel / private-network binding. Setting it `true` re-opens
|
|
174
|
+
* the SSRF surface, so never combine it with caller-controlled URLs.
|
|
175
|
+
*/
|
|
176
176
|
allowPrivateTargets?: boolean;
|
|
177
177
|
/** The Cloudflare Browser Rendering binding (`env.BROWSER`). Required. */
|
|
178
178
|
binding: BrowserBindingLike;
|
|
179
179
|
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
* The `@cloudflare/playwright` `launch` function. Injected rather than
|
|
181
|
+
* imported at module top so the optional peer dep stays out of the bundle
|
|
182
|
+
* for non-browser apps and tests can pass a double. The generated worker
|
|
183
|
+
* passes the real function; omitting it makes the helper throw on first use
|
|
184
|
+
* with a clear "install `@cloudflare/playwright`" error.
|
|
185
|
+
*/
|
|
186
186
|
launch?: BrowserLaunchLike;
|
|
187
187
|
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
* Best-effort DNS-rebinding re-check. When `true` (and `allowPrivateTargets`
|
|
189
|
+
* is `false`), the factory resolves the URL's hostname over Cloudflare DoH
|
|
190
|
+
* (`https://cloudflare-dns.com/dns-query`) and refuses to navigate if any
|
|
191
|
+
* resolved A/AAAA record is a private/internal address — closing the gap
|
|
192
|
+
* where a public hostname resolves to a private IP after the string guard
|
|
193
|
+
* passes. Off by default: it adds a DNS round-trip and is TOCTOU-imperfect
|
|
194
|
+
* (the browser re-resolves independently). If the DoH lookup itself fails, it
|
|
195
|
+
* falls back to the string guard rather than allowing a resolved private IP.
|
|
196
|
+
* For a hard guarantee prefer {@link LunoraBrowserOptions.allowedHosts}.
|
|
197
|
+
*/
|
|
198
198
|
resolveDns?: boolean;
|
|
199
199
|
/**
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
* Default navigation timeout (ms) applied when a per-call `timeoutMs` is not
|
|
201
|
+
* given. Clamped to the factory's `MAX_TIMEOUT_MS`. Default 30000.
|
|
202
|
+
*/
|
|
203
203
|
timeoutMs?: number;
|
|
204
204
|
}
|
|
205
205
|
/**
|
|
206
|
-
* The `ctx.browser` surface — Cloudflare Browser Rendering driven through
|
|
207
|
-
* `@cloudflare/playwright`. **Action-only**: every method performs
|
|
208
|
-
* non-deterministic network I/O (it navigates a real headless browser to a
|
|
209
|
-
* URL), so codegen wires it onto `ActionCtx` exclusively — never `QueryCtx`/
|
|
210
|
-
* `MutationCtx` — exactly like `ctx.ai` / `ctx.fetch`. Each helper launches a
|
|
211
|
-
* browser, opens a context + page, navigates, performs the op, and always
|
|
212
|
-
* closes the browser in a `finally` (a leaked session is billed and
|
|
213
|
-
* rate-limited).
|
|
214
|
-
* @experimental
|
|
215
|
-
*/
|
|
206
|
+
* The `ctx.browser` surface — Cloudflare Browser Rendering driven through
|
|
207
|
+
* `@cloudflare/playwright`. **Action-only**: every method performs
|
|
208
|
+
* non-deterministic network I/O (it navigates a real headless browser to a
|
|
209
|
+
* URL), so codegen wires it onto `ActionCtx` exclusively — never `QueryCtx`/
|
|
210
|
+
* `MutationCtx` — exactly like `ctx.ai` / `ctx.fetch`. Each helper launches a
|
|
211
|
+
* browser, opens a context + page, navigates, performs the op, and always
|
|
212
|
+
* closes the browser in a `finally` (a leaked session is billed and
|
|
213
|
+
* rate-limited).
|
|
214
|
+
* @experimental
|
|
215
|
+
*/
|
|
216
216
|
interface Browser {
|
|
217
217
|
/** Serialized HTML of `url` after navigation settles. */
|
|
218
218
|
content: (url: string, options?: NavigateOptions) => Promise<string>;
|
|
219
219
|
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
* Low-level escape hatch: launch a raw Playwright `Browser` and hand it to
|
|
221
|
+
* `fn` (e.g. for multi-page flows or APIs not surfaced here). The browser is
|
|
222
|
+
* **always closed** when `fn` resolves or throws — do not retain references
|
|
223
|
+
* to it past the callback.
|
|
224
|
+
*/
|
|
225
225
|
launch: <T>(function_: (browser: BrowserLike) => Promise<T>) => Promise<T>;
|
|
226
226
|
/** Render `url` to a PDF buffer. */
|
|
227
227
|
pdf: (url: string, options?: PdfOptions) => Promise<Uint8Array>;
|
|
228
228
|
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
* Navigate to `url`, run `fn` inside the page context, and return its
|
|
230
|
+
* (serializable) result. `fn` runs in the browser, not the worker — it
|
|
231
|
+
* cannot close over worker-side variables.
|
|
232
|
+
*/
|
|
233
233
|
scrape: <T>(url: string, function_: (...args: never[]) => T, options?: NavigateOptions) => Promise<T>;
|
|
234
234
|
/** Render `url` to an image buffer (PNG by default). */
|
|
235
235
|
screenshot: (url: string, options?: ScreenshotOptions) => Promise<Uint8Array>;
|
|
236
236
|
}
|
|
237
237
|
/**
|
|
238
|
-
* `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
239
|
-
* @experimental
|
|
240
|
-
*/
|
|
238
|
+
* `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
239
|
+
* @experimental
|
|
240
|
+
*/
|
|
241
241
|
declare const createBrowser: (options: LunoraBrowserOptions) => Browser;
|
|
242
242
|
export { type Browser, type BrowserBindingLike, type BrowserContextLike, type BrowserLaunchLike, type BrowserLike, type LunoraBrowserOptions, type NavigateOptions, type PageLike, type PdfOptions, type ScreenshotOptions, createBrowser };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Structural projection of the Cloudflare **Browser Rendering** binding
|
|
3
|
-
* (`env.BROWSER`). The binding is a `Fetcher` under the hood — `@cloudflare/playwright`
|
|
4
|
-
* drives it via `launch(env.BROWSER)`. Declared locally (an empty structural
|
|
5
|
-
* marker) so unit tests can pass a plain-object double and the real binding
|
|
6
|
-
* satisfies the same shape without importing `@cloudflare/workers-types` into
|
|
7
|
-
* the public surface. See https://developers.cloudflare.com/browser-rendering/.
|
|
8
|
-
*
|
|
9
|
-
* It is intentionally opaque: callers never touch the binding directly, they
|
|
10
|
-
* hand it to {@link LunoraBrowserOptions.binding} and the Playwright layer
|
|
11
|
-
* consumes it. `fetch` is REQUIRED (the real binding is a `Fetcher`, so it
|
|
12
|
-
* always has one) so the marker actually excludes an arbitrary value like `{}` —
|
|
13
|
-
* a bare object fails to type-check where a binding is required, catching the
|
|
14
|
-
* misuse at the call site instead of deferring to an opaque launch error.
|
|
15
|
-
* @experimental
|
|
16
|
-
*/
|
|
2
|
+
* Structural projection of the Cloudflare **Browser Rendering** binding
|
|
3
|
+
* (`env.BROWSER`). The binding is a `Fetcher` under the hood — `@cloudflare/playwright`
|
|
4
|
+
* drives it via `launch(env.BROWSER)`. Declared locally (an empty structural
|
|
5
|
+
* marker) so unit tests can pass a plain-object double and the real binding
|
|
6
|
+
* satisfies the same shape without importing `@cloudflare/workers-types` into
|
|
7
|
+
* the public surface. See https://developers.cloudflare.com/browser-rendering/.
|
|
8
|
+
*
|
|
9
|
+
* It is intentionally opaque: callers never touch the binding directly, they
|
|
10
|
+
* hand it to {@link LunoraBrowserOptions.binding} and the Playwright layer
|
|
11
|
+
* consumes it. `fetch` is REQUIRED (the real binding is a `Fetcher`, so it
|
|
12
|
+
* always has one) so the marker actually excludes an arbitrary value like `{}` —
|
|
13
|
+
* a bare object fails to type-check where a binding is required, catching the
|
|
14
|
+
* misuse at the call site instead of deferring to an opaque launch error.
|
|
15
|
+
* @experimental
|
|
16
|
+
*/
|
|
17
17
|
interface BrowserBindingLike {
|
|
18
18
|
readonly fetch: (...args: never[]) => unknown;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Minimal projection of a Playwright `Route` (the argument the `page.route`
|
|
22
|
-
* handler receives). Only the members the SSRF redirect guard drives are
|
|
23
|
-
* declared: inspect the intercepted request's URL / navigation-ness, then either
|
|
24
|
-
* let it proceed ({@link RouteLike.continue}) or reject it ({@link RouteLike.abort}).
|
|
25
|
-
*/
|
|
21
|
+
* Minimal projection of a Playwright `Route` (the argument the `page.route`
|
|
22
|
+
* handler receives). Only the members the SSRF redirect guard drives are
|
|
23
|
+
* declared: inspect the intercepted request's URL / navigation-ness, then either
|
|
24
|
+
* let it proceed ({@link RouteLike.continue}) or reject it ({@link RouteLike.abort}).
|
|
25
|
+
*/
|
|
26
26
|
interface RouteLike {
|
|
27
27
|
/** Reject the intercepted request (fail-closed); `errorCode` is a Playwright abort reason. */
|
|
28
28
|
abort: (errorCode?: string) => Promise<void>;
|
|
@@ -35,11 +35,11 @@ interface RouteLike {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Minimal projection of a Playwright `Page` — just the methods the helpers drive.
|
|
39
|
-
* Declared structurally so a test can inject a plain stub instead of a real
|
|
40
|
-
* headless page (which needs workerd + the Browser Rendering binding).
|
|
41
|
-
* @experimental
|
|
42
|
-
*/
|
|
38
|
+
* Minimal projection of a Playwright `Page` — just the methods the helpers drive.
|
|
39
|
+
* Declared structurally so a test can inject a plain stub instead of a real
|
|
40
|
+
* headless page (which needs workerd + the Browser Rendering binding).
|
|
41
|
+
* @experimental
|
|
42
|
+
*/
|
|
43
43
|
interface PageLike {
|
|
44
44
|
/** Return the page's serialized HTML after the navigation settles. */
|
|
45
45
|
content: () => Promise<string>;
|
|
@@ -53,11 +53,11 @@ interface PageLike {
|
|
|
53
53
|
/** Render the page to a PDF buffer. */
|
|
54
54
|
pdf: (options?: Record<string, unknown>) => Promise<Uint8Array>;
|
|
55
55
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
* Register a request interceptor (Playwright `page.route`). Optional: a fake
|
|
57
|
+
* or older page double without it still works — the SSRF redirect guard only
|
|
58
|
+
* activates when interception is available, and the initial-URL guard applies
|
|
59
|
+
* regardless. `pattern` follows Playwright's glob/URL matcher.
|
|
60
|
+
*/
|
|
61
61
|
route?: (pattern: string, handler: (route: RouteLike) => unknown) => Promise<void>;
|
|
62
62
|
/** Render the page to a PNG/JPEG buffer. */
|
|
63
63
|
screenshot: (options?: Record<string, unknown>) => Promise<Uint8Array>;
|
|
@@ -68,175 +68,175 @@ interface PageLike {
|
|
|
68
68
|
}) => Promise<void>;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* Minimal projection of a Playwright `BrowserContext`. Only `newPage` is used;
|
|
72
|
-
* declared structurally for the same test-double reason as {@link PageLike}.
|
|
73
|
-
* @experimental
|
|
74
|
-
*/
|
|
71
|
+
* Minimal projection of a Playwright `BrowserContext`. Only `newPage` is used;
|
|
72
|
+
* declared structurally for the same test-double reason as {@link PageLike}.
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
75
75
|
interface BrowserContextLike {
|
|
76
76
|
newPage: () => Promise<PageLike>;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Minimal projection of a Playwright `Browser` (the value `launch` resolves to).
|
|
80
|
-
* Only `newContext`/`close` are used; declared structurally for the same
|
|
81
|
-
* test-double reason as {@link PageLike}.
|
|
82
|
-
* @experimental
|
|
83
|
-
*/
|
|
79
|
+
* Minimal projection of a Playwright `Browser` (the value `launch` resolves to).
|
|
80
|
+
* Only `newContext`/`close` are used; declared structurally for the same
|
|
81
|
+
* test-double reason as {@link PageLike}.
|
|
82
|
+
* @experimental
|
|
83
|
+
*/
|
|
84
84
|
interface BrowserLike {
|
|
85
85
|
close: () => Promise<void>;
|
|
86
86
|
newContext: () => Promise<BrowserContextLike>;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
|
-
* Structural projection of `@cloudflare/playwright`'s `launch` export
|
|
90
|
-
* (`import { launch } from "@cloudflare/playwright"`). Injected via
|
|
91
|
-
* {@link LunoraBrowserOptions.launch} so the factory never imports
|
|
92
|
-
* `@cloudflare/playwright` at module top — that keeps the heavy optional peer
|
|
93
|
-
* dep out of the bundle for apps that never screenshot, and lets tests pass a
|
|
94
|
-
* fake. Calling it with the Browser Rendering binding resolves a {@link BrowserLike}.
|
|
95
|
-
* @experimental
|
|
96
|
-
*/
|
|
89
|
+
* Structural projection of `@cloudflare/playwright`'s `launch` export
|
|
90
|
+
* (`import { launch } from "@cloudflare/playwright"`). Injected via
|
|
91
|
+
* {@link LunoraBrowserOptions.launch} so the factory never imports
|
|
92
|
+
* `@cloudflare/playwright` at module top — that keeps the heavy optional peer
|
|
93
|
+
* dep out of the bundle for apps that never screenshot, and lets tests pass a
|
|
94
|
+
* fake. Calling it with the Browser Rendering binding resolves a {@link BrowserLike}.
|
|
95
|
+
* @experimental
|
|
96
|
+
*/
|
|
97
97
|
type BrowserLaunchLike = (binding: BrowserBindingLike, options?: Record<string, unknown>) => Promise<BrowserLike>;
|
|
98
98
|
/**
|
|
99
|
-
* Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
|
|
100
|
-
* @experimental
|
|
101
|
-
*/
|
|
99
|
+
* Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
|
|
100
|
+
* @experimental
|
|
101
|
+
*/
|
|
102
102
|
interface NavigateOptions {
|
|
103
103
|
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
* Hard timeout in milliseconds for the navigation + operation. Clamped to a
|
|
105
|
+
* sane ceiling so a hung/hostile page can't pin the worker. Default 30000.
|
|
106
|
+
*/
|
|
107
107
|
timeoutMs?: number;
|
|
108
108
|
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
* Playwright navigation wait condition. Playwright's set differs from
|
|
110
|
+
* Puppeteer's: `load`, `domcontentloaded`, `networkidle`, `commit`.
|
|
111
|
+
* Default `load`.
|
|
112
|
+
*/
|
|
113
113
|
waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
-
* Options for {@link Browser.screenshot}.
|
|
117
|
-
* @experimental
|
|
118
|
-
*/
|
|
116
|
+
* Options for {@link Browser.screenshot}.
|
|
117
|
+
* @experimental
|
|
118
|
+
*/
|
|
119
119
|
interface ScreenshotOptions extends NavigateOptions {
|
|
120
120
|
/** Capture the full scrollable page rather than just the viewport. */
|
|
121
121
|
fullPage?: boolean;
|
|
122
122
|
/** Image encoding. Default `png`. */
|
|
123
123
|
type?: "jpeg" | "png";
|
|
124
124
|
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
* Viewport size. Each dimension is hard-capped (see the factory's
|
|
126
|
+
* `MAX_VIEWPORT_*`) so a caller can't request a multi-million-pixel render.
|
|
127
|
+
*/
|
|
128
128
|
viewport?: {
|
|
129
129
|
height: number;
|
|
130
130
|
width: number;
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
* Options for {@link Browser.pdf}.
|
|
135
|
-
* @experimental
|
|
136
|
-
*/
|
|
134
|
+
* Options for {@link Browser.pdf}.
|
|
135
|
+
* @experimental
|
|
136
|
+
*/
|
|
137
137
|
interface PdfOptions extends NavigateOptions {
|
|
138
138
|
/** Paper format (`A4`, `Letter`, …) forwarded to Playwright. */
|
|
139
139
|
format?: string;
|
|
140
140
|
/** Print background graphics. Default `false`. */
|
|
141
141
|
printBackground?: boolean;
|
|
142
142
|
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
* Viewport used while laying out the page before printing. Hard-capped like
|
|
144
|
+
* {@link ScreenshotOptions.viewport}.
|
|
145
|
+
*/
|
|
146
146
|
viewport?: {
|
|
147
147
|
height: number;
|
|
148
148
|
width: number;
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
* `LunoraBrowserOptions` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
153
|
-
* @experimental
|
|
154
|
-
*/
|
|
152
|
+
* `LunoraBrowserOptions` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
153
|
+
* @experimental
|
|
154
|
+
*/
|
|
155
155
|
interface LunoraBrowserOptions {
|
|
156
156
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
* Strict host allowlist. When set (non-empty), a navigation URL is refused
|
|
158
|
+
* unless its hostname exactly matches one of these entries (case-insensitive,
|
|
159
|
+
* trailing-dot-normalized, IPv6 brackets stripped). This is the only guard
|
|
160
|
+
* that fully closes DNS rebinding: a public hostname that resolves to a
|
|
161
|
+
* private/metadata IP can still be pinned out if it isn't on the list. Set it
|
|
162
|
+
* whenever you pass client-controlled URLs to the browser. Leave it unset (the
|
|
163
|
+
* default) to keep the previous behavior (only the string-based SSRF guard).
|
|
164
|
+
*/
|
|
165
165
|
allowedHosts?: string[];
|
|
166
166
|
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
* Opt out of the SSRF guard that, by default, refuses to navigate to a
|
|
168
|
+
* private / internal / loopback / link-local host (RFC1918, `127.0.0.0/8`,
|
|
169
|
+
* `169.254.0.0/16` incl. the cloud-metadata address, CGNAT, IPv6 ULA/
|
|
170
|
+
* link-local, and `localhost` / `*.internal` / `*.local` literals). Leave it
|
|
171
|
+
* `false` (the default) unless every caller-supplied URL is trusted — e.g.
|
|
172
|
+
* you deliberately drive the browser at an internal service reachable through
|
|
173
|
+
* a Cloudflare Tunnel / private-network binding. Setting it `true` re-opens
|
|
174
|
+
* the SSRF surface, so never combine it with caller-controlled URLs.
|
|
175
|
+
*/
|
|
176
176
|
allowPrivateTargets?: boolean;
|
|
177
177
|
/** The Cloudflare Browser Rendering binding (`env.BROWSER`). Required. */
|
|
178
178
|
binding: BrowserBindingLike;
|
|
179
179
|
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
* The `@cloudflare/playwright` `launch` function. Injected rather than
|
|
181
|
+
* imported at module top so the optional peer dep stays out of the bundle
|
|
182
|
+
* for non-browser apps and tests can pass a double. The generated worker
|
|
183
|
+
* passes the real function; omitting it makes the helper throw on first use
|
|
184
|
+
* with a clear "install `@cloudflare/playwright`" error.
|
|
185
|
+
*/
|
|
186
186
|
launch?: BrowserLaunchLike;
|
|
187
187
|
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
* Best-effort DNS-rebinding re-check. When `true` (and `allowPrivateTargets`
|
|
189
|
+
* is `false`), the factory resolves the URL's hostname over Cloudflare DoH
|
|
190
|
+
* (`https://cloudflare-dns.com/dns-query`) and refuses to navigate if any
|
|
191
|
+
* resolved A/AAAA record is a private/internal address — closing the gap
|
|
192
|
+
* where a public hostname resolves to a private IP after the string guard
|
|
193
|
+
* passes. Off by default: it adds a DNS round-trip and is TOCTOU-imperfect
|
|
194
|
+
* (the browser re-resolves independently). If the DoH lookup itself fails, it
|
|
195
|
+
* falls back to the string guard rather than allowing a resolved private IP.
|
|
196
|
+
* For a hard guarantee prefer {@link LunoraBrowserOptions.allowedHosts}.
|
|
197
|
+
*/
|
|
198
198
|
resolveDns?: boolean;
|
|
199
199
|
/**
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
* Default navigation timeout (ms) applied when a per-call `timeoutMs` is not
|
|
201
|
+
* given. Clamped to the factory's `MAX_TIMEOUT_MS`. Default 30000.
|
|
202
|
+
*/
|
|
203
203
|
timeoutMs?: number;
|
|
204
204
|
}
|
|
205
205
|
/**
|
|
206
|
-
* The `ctx.browser` surface — Cloudflare Browser Rendering driven through
|
|
207
|
-
* `@cloudflare/playwright`. **Action-only**: every method performs
|
|
208
|
-
* non-deterministic network I/O (it navigates a real headless browser to a
|
|
209
|
-
* URL), so codegen wires it onto `ActionCtx` exclusively — never `QueryCtx`/
|
|
210
|
-
* `MutationCtx` — exactly like `ctx.ai` / `ctx.fetch`. Each helper launches a
|
|
211
|
-
* browser, opens a context + page, navigates, performs the op, and always
|
|
212
|
-
* closes the browser in a `finally` (a leaked session is billed and
|
|
213
|
-
* rate-limited).
|
|
214
|
-
* @experimental
|
|
215
|
-
*/
|
|
206
|
+
* The `ctx.browser` surface — Cloudflare Browser Rendering driven through
|
|
207
|
+
* `@cloudflare/playwright`. **Action-only**: every method performs
|
|
208
|
+
* non-deterministic network I/O (it navigates a real headless browser to a
|
|
209
|
+
* URL), so codegen wires it onto `ActionCtx` exclusively — never `QueryCtx`/
|
|
210
|
+
* `MutationCtx` — exactly like `ctx.ai` / `ctx.fetch`. Each helper launches a
|
|
211
|
+
* browser, opens a context + page, navigates, performs the op, and always
|
|
212
|
+
* closes the browser in a `finally` (a leaked session is billed and
|
|
213
|
+
* rate-limited).
|
|
214
|
+
* @experimental
|
|
215
|
+
*/
|
|
216
216
|
interface Browser {
|
|
217
217
|
/** Serialized HTML of `url` after navigation settles. */
|
|
218
218
|
content: (url: string, options?: NavigateOptions) => Promise<string>;
|
|
219
219
|
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
220
|
+
* Low-level escape hatch: launch a raw Playwright `Browser` and hand it to
|
|
221
|
+
* `fn` (e.g. for multi-page flows or APIs not surfaced here). The browser is
|
|
222
|
+
* **always closed** when `fn` resolves or throws — do not retain references
|
|
223
|
+
* to it past the callback.
|
|
224
|
+
*/
|
|
225
225
|
launch: <T>(function_: (browser: BrowserLike) => Promise<T>) => Promise<T>;
|
|
226
226
|
/** Render `url` to a PDF buffer. */
|
|
227
227
|
pdf: (url: string, options?: PdfOptions) => Promise<Uint8Array>;
|
|
228
228
|
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
* Navigate to `url`, run `fn` inside the page context, and return its
|
|
230
|
+
* (serializable) result. `fn` runs in the browser, not the worker — it
|
|
231
|
+
* cannot close over worker-side variables.
|
|
232
|
+
*/
|
|
233
233
|
scrape: <T>(url: string, function_: (...args: never[]) => T, options?: NavigateOptions) => Promise<T>;
|
|
234
234
|
/** Render `url` to an image buffer (PNG by default). */
|
|
235
235
|
screenshot: (url: string, options?: ScreenshotOptions) => Promise<Uint8Array>;
|
|
236
236
|
}
|
|
237
237
|
/**
|
|
238
|
-
* `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
239
|
-
* @experimental
|
|
240
|
-
*/
|
|
238
|
+
* `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
|
239
|
+
* @experimental
|
|
240
|
+
*/
|
|
241
241
|
declare const createBrowser: (options: LunoraBrowserOptions) => Browser;
|
|
242
242
|
export { type Browser, type BrowserBindingLike, type BrowserContextLike, type BrowserLaunchLike, type BrowserLike, type LunoraBrowserOptions, type NavigateOptions, type PageLike, type PdfOptions, type ScreenshotOptions, createBrowser };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/browser",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.9",
|
|
4
4
|
"description": "Cloudflare Browser Rendering for Lunora: ctx.browser screenshots, PDF, and scraping in actions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"browser-rendering",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.6"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@cloudflare/playwright": ">=1.0.0"
|