@lunora/browser 1.0.0-alpha.1 → 1.0.0-alpha.10

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 CHANGED
@@ -1,24 +1,45 @@
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. Typed as a non-empty marker so an arbitrary value (e.g. `{}`)
12
- * doesn't silently type-check where a binding is required.
13
- */
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
+ */
14
17
  interface BrowserBindingLike {
15
- readonly fetch?: (...args: never[]) => unknown;
18
+ readonly fetch: (...args: never[]) => unknown;
16
19
  }
17
20
  /**
18
- * Minimal projection of a Playwright `Page` — just the methods the helpers drive.
19
- * Declared structurally so a test can inject a plain stub instead of a real
20
- * headless page (which needs workerd + the Browser Rendering binding).
21
- */
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
+ interface RouteLike {
27
+ /** Reject the intercepted request (fail-closed); `errorCode` is a Playwright abort reason. */
28
+ abort: (errorCode?: string) => Promise<void>;
29
+ /** Allow the intercepted request to proceed. */
30
+ continue: () => Promise<void>;
31
+ /** The intercepted request: its URL and (when available) whether it is a top-level navigation. */
32
+ request: () => {
33
+ isNavigationRequest?: () => boolean;
34
+ url: () => string;
35
+ };
36
+ }
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
+ */
22
43
  interface PageLike {
23
44
  /** Return the page's serialized HTML after the navigation settles. */
24
45
  content: () => Promise<string>;
@@ -31,6 +52,13 @@ interface PageLike {
31
52
  }) => Promise<unknown>;
32
53
  /** Render the page to a PDF buffer. */
33
54
  pdf: (options?: Record<string, unknown>) => Promise<Uint8Array>;
55
+ /**
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
+ route?: (pattern: string, handler: (route: RouteLike) => unknown) => Promise<void>;
34
62
  /** Render the page to a PNG/JPEG buffer. */
35
63
  screenshot: (options?: Record<string, unknown>) => Promise<Uint8Array>;
36
64
  /** Constrain the page viewport (a hard cap so a hostile page can't pin the worker). */
@@ -40,132 +68,175 @@ interface PageLike {
40
68
  }) => Promise<void>;
41
69
  }
42
70
  /**
43
- * Minimal projection of a Playwright `BrowserContext`. Only `newPage` is used;
44
- * declared structurally for the same test-double reason as {@link PageLike}.
45
- */
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
+ */
46
75
  interface BrowserContextLike {
47
76
  newPage: () => Promise<PageLike>;
48
77
  }
49
78
  /**
50
- * Minimal projection of a Playwright `Browser` (the value `launch` resolves to).
51
- * Only `newContext`/`close` are used; declared structurally for the same
52
- * test-double reason as {@link PageLike}.
53
- */
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
+ */
54
84
  interface BrowserLike {
55
85
  close: () => Promise<void>;
56
86
  newContext: () => Promise<BrowserContextLike>;
57
87
  }
58
88
  /**
59
- * Structural projection of `@cloudflare/playwright`'s `launch` export
60
- * (`import { launch } from "@cloudflare/playwright"`). Injected via
61
- * {@link LunoraBrowserOptions.launch} so the factory never imports
62
- * `@cloudflare/playwright` at module top — that keeps the heavy optional peer
63
- * dep out of the bundle for apps that never screenshot, and lets tests pass a
64
- * fake. Calling it with the Browser Rendering binding resolves a {@link BrowserLike}.
65
- */
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
+ */
66
97
  type BrowserLaunchLike = (binding: BrowserBindingLike, options?: Record<string, unknown>) => Promise<BrowserLike>;
67
- /** Options shared by the page-driving helpers ({@link Browser.screenshot} etc.). */
98
+ /**
99
+ * Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
100
+ * @experimental
101
+ */
68
102
  interface NavigateOptions {
69
103
  /**
70
- * Hard timeout in milliseconds for the navigation + operation. Clamped to a
71
- * sane ceiling so a hung/hostile page can't pin the worker. Default 30000.
72
- */
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
+ */
73
107
  timeoutMs?: number;
74
108
  /**
75
- * Playwright navigation wait condition. Playwright's set differs from
76
- * Puppeteer's: `load`, `domcontentloaded`, `networkidle`, `commit`.
77
- * Default `load`.
78
- */
109
+ * Playwright navigation wait condition. Playwright's set differs from
110
+ * Puppeteer's: `load`, `domcontentloaded`, `networkidle`, `commit`.
111
+ * Default `load`.
112
+ */
79
113
  waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
80
114
  }
81
- /** Options for {@link Browser.screenshot}. */
115
+ /**
116
+ * Options for {@link Browser.screenshot}.
117
+ * @experimental
118
+ */
82
119
  interface ScreenshotOptions extends NavigateOptions {
83
120
  /** Capture the full scrollable page rather than just the viewport. */
84
121
  fullPage?: boolean;
85
122
  /** Image encoding. Default `png`. */
86
123
  type?: "jpeg" | "png";
87
124
  /**
88
- * Viewport size. Each dimension is hard-capped (see the factory's
89
- * `MAX_VIEWPORT_*`) so a caller can't request a multi-million-pixel render.
90
- */
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
+ */
91
128
  viewport?: {
92
129
  height: number;
93
130
  width: number;
94
131
  };
95
132
  }
96
- /** Options for {@link Browser.pdf}. */
133
+ /**
134
+ * Options for {@link Browser.pdf}.
135
+ * @experimental
136
+ */
97
137
  interface PdfOptions extends NavigateOptions {
98
138
  /** Paper format (`A4`, `Letter`, …) forwarded to Playwright. */
99
139
  format?: string;
100
140
  /** Print background graphics. Default `false`. */
101
141
  printBackground?: boolean;
102
142
  /**
103
- * Viewport used while laying out the page before printing. Hard-capped like
104
- * {@link ScreenshotOptions.viewport}.
105
- */
143
+ * Viewport used while laying out the page before printing. Hard-capped like
144
+ * {@link ScreenshotOptions.viewport}.
145
+ */
106
146
  viewport?: {
107
147
  height: number;
108
148
  width: number;
109
149
  };
110
150
  }
151
+ /**
152
+ * `LunoraBrowserOptions` is part of the experimental `@lunora/browser` API and may change without a major version bump.
153
+ * @experimental
154
+ */
111
155
  interface LunoraBrowserOptions {
112
156
  /**
113
- * Opt out of the SSRF guard that, by default, refuses to navigate to a
114
- * private / internal / loopback / link-local host (RFC1918, `127.0.0.0/8`,
115
- * `169.254.0.0/16` incl. the cloud-metadata address, CGNAT, IPv6 ULA/
116
- * link-local, and `localhost` / `*.internal` / `*.local` literals). Leave it
117
- * `false` (the default) unless every caller-supplied URL is trusted e.g.
118
- * you deliberately drive the browser at an internal service reachable through
119
- * a Cloudflare Tunnel / private-network binding. Setting it `true` re-opens
120
- * the SSRF surface, so never combine it with caller-controlled URLs.
121
- */
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
+ allowedHosts?: string[];
166
+ /**
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
+ */
122
176
  allowPrivateTargets?: boolean;
123
177
  /** The Cloudflare Browser Rendering binding (`env.BROWSER`). Required. */
124
178
  binding: BrowserBindingLike;
125
179
  /**
126
- * The `@cloudflare/playwright` `launch` function. Injected rather than
127
- * imported at module top so the optional peer dep stays out of the bundle
128
- * for non-browser apps and tests can pass a double. The generated worker
129
- * passes the real function; omitting it makes the helper throw on first use
130
- * with a clear "install `@cloudflare/playwright`" error.
131
- */
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
+ */
132
186
  launch?: BrowserLaunchLike;
133
187
  /**
134
- * Default navigation timeout (ms) applied when a per-call `timeoutMs` is not
135
- * given. Clamped to the factory's `MAX_TIMEOUT_MS`. Default 30000.
136
- */
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
+ resolveDns?: boolean;
199
+ /**
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
+ */
137
203
  timeoutMs?: number;
138
204
  }
139
205
  /**
140
- * The `ctx.browser` surface — Cloudflare Browser Rendering driven through
141
- * `@cloudflare/playwright`. **Action-only**: every method performs
142
- * non-deterministic network I/O (it navigates a real headless browser to a
143
- * URL), so codegen wires it onto `ActionCtx` exclusively — never `QueryCtx`/
144
- * `MutationCtx` — exactly like `ctx.ai` / `ctx.fetch`. Each helper launches a
145
- * browser, opens a context + page, navigates, performs the op, and always
146
- * closes the browser in a `finally` (a leaked session is billed and
147
- * rate-limited).
148
- */
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
+ */
149
216
  interface Browser {
150
217
  /** Serialized HTML of `url` after navigation settles. */
151
218
  content: (url: string, options?: NavigateOptions) => Promise<string>;
152
219
  /**
153
- * Low-level escape hatch: launch a raw Playwright `Browser` and hand it to
154
- * `fn` (e.g. for multi-page flows or APIs not surfaced here). The browser is
155
- * **always closed** when `fn` resolves or throws — do not retain references
156
- * to it past the callback.
157
- */
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
+ */
158
225
  launch: <T>(function_: (browser: BrowserLike) => Promise<T>) => Promise<T>;
159
226
  /** Render `url` to a PDF buffer. */
160
227
  pdf: (url: string, options?: PdfOptions) => Promise<Uint8Array>;
161
228
  /**
162
- * Navigate to `url`, run `fn` inside the page context, and return its
163
- * (serializable) result. `fn` runs in the browser, not the worker — it
164
- * cannot close over worker-side variables.
165
- */
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
+ */
166
233
  scrape: <T>(url: string, function_: (...args: never[]) => T, options?: NavigateOptions) => Promise<T>;
167
234
  /** Render `url` to an image buffer (PNG by default). */
168
235
  screenshot: (url: string, options?: ScreenshotOptions) => Promise<Uint8Array>;
169
236
  }
237
+ /**
238
+ * `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
239
+ * @experimental
240
+ */
170
241
  declare const createBrowser: (options: LunoraBrowserOptions) => Browser;
171
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,24 +1,45 @@
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. Typed as a non-empty marker so an arbitrary value (e.g. `{}`)
12
- * doesn't silently type-check where a binding is required.
13
- */
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
+ */
14
17
  interface BrowserBindingLike {
15
- readonly fetch?: (...args: never[]) => unknown;
18
+ readonly fetch: (...args: never[]) => unknown;
16
19
  }
17
20
  /**
18
- * Minimal projection of a Playwright `Page` — just the methods the helpers drive.
19
- * Declared structurally so a test can inject a plain stub instead of a real
20
- * headless page (which needs workerd + the Browser Rendering binding).
21
- */
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
+ interface RouteLike {
27
+ /** Reject the intercepted request (fail-closed); `errorCode` is a Playwright abort reason. */
28
+ abort: (errorCode?: string) => Promise<void>;
29
+ /** Allow the intercepted request to proceed. */
30
+ continue: () => Promise<void>;
31
+ /** The intercepted request: its URL and (when available) whether it is a top-level navigation. */
32
+ request: () => {
33
+ isNavigationRequest?: () => boolean;
34
+ url: () => string;
35
+ };
36
+ }
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
+ */
22
43
  interface PageLike {
23
44
  /** Return the page's serialized HTML after the navigation settles. */
24
45
  content: () => Promise<string>;
@@ -31,6 +52,13 @@ interface PageLike {
31
52
  }) => Promise<unknown>;
32
53
  /** Render the page to a PDF buffer. */
33
54
  pdf: (options?: Record<string, unknown>) => Promise<Uint8Array>;
55
+ /**
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
+ route?: (pattern: string, handler: (route: RouteLike) => unknown) => Promise<void>;
34
62
  /** Render the page to a PNG/JPEG buffer. */
35
63
  screenshot: (options?: Record<string, unknown>) => Promise<Uint8Array>;
36
64
  /** Constrain the page viewport (a hard cap so a hostile page can't pin the worker). */
@@ -40,132 +68,175 @@ interface PageLike {
40
68
  }) => Promise<void>;
41
69
  }
42
70
  /**
43
- * Minimal projection of a Playwright `BrowserContext`. Only `newPage` is used;
44
- * declared structurally for the same test-double reason as {@link PageLike}.
45
- */
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
+ */
46
75
  interface BrowserContextLike {
47
76
  newPage: () => Promise<PageLike>;
48
77
  }
49
78
  /**
50
- * Minimal projection of a Playwright `Browser` (the value `launch` resolves to).
51
- * Only `newContext`/`close` are used; declared structurally for the same
52
- * test-double reason as {@link PageLike}.
53
- */
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
+ */
54
84
  interface BrowserLike {
55
85
  close: () => Promise<void>;
56
86
  newContext: () => Promise<BrowserContextLike>;
57
87
  }
58
88
  /**
59
- * Structural projection of `@cloudflare/playwright`'s `launch` export
60
- * (`import { launch } from "@cloudflare/playwright"`). Injected via
61
- * {@link LunoraBrowserOptions.launch} so the factory never imports
62
- * `@cloudflare/playwright` at module top — that keeps the heavy optional peer
63
- * dep out of the bundle for apps that never screenshot, and lets tests pass a
64
- * fake. Calling it with the Browser Rendering binding resolves a {@link BrowserLike}.
65
- */
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
+ */
66
97
  type BrowserLaunchLike = (binding: BrowserBindingLike, options?: Record<string, unknown>) => Promise<BrowserLike>;
67
- /** Options shared by the page-driving helpers ({@link Browser.screenshot} etc.). */
98
+ /**
99
+ * Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
100
+ * @experimental
101
+ */
68
102
  interface NavigateOptions {
69
103
  /**
70
- * Hard timeout in milliseconds for the navigation + operation. Clamped to a
71
- * sane ceiling so a hung/hostile page can't pin the worker. Default 30000.
72
- */
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
+ */
73
107
  timeoutMs?: number;
74
108
  /**
75
- * Playwright navigation wait condition. Playwright's set differs from
76
- * Puppeteer's: `load`, `domcontentloaded`, `networkidle`, `commit`.
77
- * Default `load`.
78
- */
109
+ * Playwright navigation wait condition. Playwright's set differs from
110
+ * Puppeteer's: `load`, `domcontentloaded`, `networkidle`, `commit`.
111
+ * Default `load`.
112
+ */
79
113
  waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
80
114
  }
81
- /** Options for {@link Browser.screenshot}. */
115
+ /**
116
+ * Options for {@link Browser.screenshot}.
117
+ * @experimental
118
+ */
82
119
  interface ScreenshotOptions extends NavigateOptions {
83
120
  /** Capture the full scrollable page rather than just the viewport. */
84
121
  fullPage?: boolean;
85
122
  /** Image encoding. Default `png`. */
86
123
  type?: "jpeg" | "png";
87
124
  /**
88
- * Viewport size. Each dimension is hard-capped (see the factory's
89
- * `MAX_VIEWPORT_*`) so a caller can't request a multi-million-pixel render.
90
- */
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
+ */
91
128
  viewport?: {
92
129
  height: number;
93
130
  width: number;
94
131
  };
95
132
  }
96
- /** Options for {@link Browser.pdf}. */
133
+ /**
134
+ * Options for {@link Browser.pdf}.
135
+ * @experimental
136
+ */
97
137
  interface PdfOptions extends NavigateOptions {
98
138
  /** Paper format (`A4`, `Letter`, …) forwarded to Playwright. */
99
139
  format?: string;
100
140
  /** Print background graphics. Default `false`. */
101
141
  printBackground?: boolean;
102
142
  /**
103
- * Viewport used while laying out the page before printing. Hard-capped like
104
- * {@link ScreenshotOptions.viewport}.
105
- */
143
+ * Viewport used while laying out the page before printing. Hard-capped like
144
+ * {@link ScreenshotOptions.viewport}.
145
+ */
106
146
  viewport?: {
107
147
  height: number;
108
148
  width: number;
109
149
  };
110
150
  }
151
+ /**
152
+ * `LunoraBrowserOptions` is part of the experimental `@lunora/browser` API and may change without a major version bump.
153
+ * @experimental
154
+ */
111
155
  interface LunoraBrowserOptions {
112
156
  /**
113
- * Opt out of the SSRF guard that, by default, refuses to navigate to a
114
- * private / internal / loopback / link-local host (RFC1918, `127.0.0.0/8`,
115
- * `169.254.0.0/16` incl. the cloud-metadata address, CGNAT, IPv6 ULA/
116
- * link-local, and `localhost` / `*.internal` / `*.local` literals). Leave it
117
- * `false` (the default) unless every caller-supplied URL is trusted e.g.
118
- * you deliberately drive the browser at an internal service reachable through
119
- * a Cloudflare Tunnel / private-network binding. Setting it `true` re-opens
120
- * the SSRF surface, so never combine it with caller-controlled URLs.
121
- */
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
+ allowedHosts?: string[];
166
+ /**
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
+ */
122
176
  allowPrivateTargets?: boolean;
123
177
  /** The Cloudflare Browser Rendering binding (`env.BROWSER`). Required. */
124
178
  binding: BrowserBindingLike;
125
179
  /**
126
- * The `@cloudflare/playwright` `launch` function. Injected rather than
127
- * imported at module top so the optional peer dep stays out of the bundle
128
- * for non-browser apps and tests can pass a double. The generated worker
129
- * passes the real function; omitting it makes the helper throw on first use
130
- * with a clear "install `@cloudflare/playwright`" error.
131
- */
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
+ */
132
186
  launch?: BrowserLaunchLike;
133
187
  /**
134
- * Default navigation timeout (ms) applied when a per-call `timeoutMs` is not
135
- * given. Clamped to the factory's `MAX_TIMEOUT_MS`. Default 30000.
136
- */
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
+ resolveDns?: boolean;
199
+ /**
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
+ */
137
203
  timeoutMs?: number;
138
204
  }
139
205
  /**
140
- * The `ctx.browser` surface — Cloudflare Browser Rendering driven through
141
- * `@cloudflare/playwright`. **Action-only**: every method performs
142
- * non-deterministic network I/O (it navigates a real headless browser to a
143
- * URL), so codegen wires it onto `ActionCtx` exclusively — never `QueryCtx`/
144
- * `MutationCtx` — exactly like `ctx.ai` / `ctx.fetch`. Each helper launches a
145
- * browser, opens a context + page, navigates, performs the op, and always
146
- * closes the browser in a `finally` (a leaked session is billed and
147
- * rate-limited).
148
- */
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
+ */
149
216
  interface Browser {
150
217
  /** Serialized HTML of `url` after navigation settles. */
151
218
  content: (url: string, options?: NavigateOptions) => Promise<string>;
152
219
  /**
153
- * Low-level escape hatch: launch a raw Playwright `Browser` and hand it to
154
- * `fn` (e.g. for multi-page flows or APIs not surfaced here). The browser is
155
- * **always closed** when `fn` resolves or throws — do not retain references
156
- * to it past the callback.
157
- */
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
+ */
158
225
  launch: <T>(function_: (browser: BrowserLike) => Promise<T>) => Promise<T>;
159
226
  /** Render `url` to a PDF buffer. */
160
227
  pdf: (url: string, options?: PdfOptions) => Promise<Uint8Array>;
161
228
  /**
162
- * Navigate to `url`, run `fn` inside the page context, and return its
163
- * (serializable) result. `fn` runs in the browser, not the worker — it
164
- * cannot close over worker-side variables.
165
- */
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
+ */
166
233
  scrape: <T>(url: string, function_: (...args: never[]) => T, options?: NavigateOptions) => Promise<T>;
167
234
  /** Render `url` to an image buffer (PNG by default). */
168
235
  screenshot: (url: string, options?: ScreenshotOptions) => Promise<Uint8Array>;
169
236
  }
237
+ /**
238
+ * `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
239
+ * @experimental
240
+ */
170
241
  declare const createBrowser: (options: LunoraBrowserOptions) => Browser;
171
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.mjs CHANGED
@@ -1 +1 @@
1
- export { createBrowser } from './packem_shared/createBrowser-6RQ-H1k9.mjs';
1
+ export { createBrowser } from './packem_shared/createBrowser-CZmfQ38r.mjs';