@lunora/browser 1.0.0-alpha.13 → 1.0.0-alpha.14
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
|
@@ -84,6 +84,17 @@ interface BrowserContextLike {
|
|
|
84
84
|
interface BrowserLike {
|
|
85
85
|
close: () => Promise<void>;
|
|
86
86
|
newContext: () => Promise<BrowserContextLike>;
|
|
87
|
+
/**
|
|
88
|
+
* The Browser Rendering session this browser is attached to, when the
|
|
89
|
+
* runtime exposes it.
|
|
90
|
+
*
|
|
91
|
+
* Optional because this is a structural projection, not a re-declaration of
|
|
92
|
+
* the upstream Playwright type — but without it there is no way to learn
|
|
93
|
+
* the id of a session you just held open with `launch(fn, { keepAlive })`,
|
|
94
|
+
* which makes {@link Browser.connect} unreachable except by guessing from
|
|
95
|
+
* {@link Browser.sessions}.
|
|
96
|
+
*/
|
|
97
|
+
sessionId?: () => string | undefined;
|
|
87
98
|
}
|
|
88
99
|
/**
|
|
89
100
|
* Structural projection of `@cloudflare/playwright`'s `launch` export
|
|
@@ -95,6 +106,30 @@ interface BrowserLike {
|
|
|
95
106
|
* @experimental
|
|
96
107
|
*/
|
|
97
108
|
type BrowserLaunchLike = (binding: BrowserBindingLike, options?: Record<string, unknown>) => Promise<BrowserLike>;
|
|
109
|
+
/**
|
|
110
|
+
* One live Browser Rendering session, as `@cloudflare/playwright`'s `sessions()`
|
|
111
|
+
* reports it. `connectionId` is set while another worker holds the session — you
|
|
112
|
+
* can only {@link Browser.connect} to a free one.
|
|
113
|
+
* @experimental
|
|
114
|
+
*/
|
|
115
|
+
interface BrowserSession {
|
|
116
|
+
connectionId?: string;
|
|
117
|
+
sessionId: string;
|
|
118
|
+
startTime?: number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Structural projection of `@cloudflare/playwright`'s `connect` export —
|
|
122
|
+
* re-attaches to an existing session rather than starting a new browser.
|
|
123
|
+
* Injected like {@link BrowserLaunchLike} so the peer dep stays optional.
|
|
124
|
+
* @experimental
|
|
125
|
+
*/
|
|
126
|
+
type BrowserConnectLike = (binding: BrowserBindingLike, sessionId: string) => Promise<BrowserLike>;
|
|
127
|
+
/**
|
|
128
|
+
* Structural projection of `@cloudflare/playwright`'s `sessions` export — lists
|
|
129
|
+
* the account's live Browser Rendering sessions for this binding.
|
|
130
|
+
* @experimental
|
|
131
|
+
*/
|
|
132
|
+
type BrowserSessionsLike = (binding: BrowserBindingLike) => Promise<ReadonlyArray<BrowserSession>>;
|
|
98
133
|
/**
|
|
99
134
|
* Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
|
|
100
135
|
* @experimental
|
|
@@ -176,6 +211,11 @@ interface LunoraBrowserOptions {
|
|
|
176
211
|
allowPrivateTargets?: boolean;
|
|
177
212
|
/** The Cloudflare Browser Rendering binding (`env.BROWSER`). Required. */
|
|
178
213
|
binding: BrowserBindingLike;
|
|
214
|
+
/**
|
|
215
|
+
* The `@cloudflare/playwright` `connect` function, injected like
|
|
216
|
+
* {@link LunoraBrowserOptions.launch}. Required for {@link Browser.connect}.
|
|
217
|
+
*/
|
|
218
|
+
connect?: BrowserConnectLike;
|
|
179
219
|
/**
|
|
180
220
|
* The `@cloudflare/playwright` `launch` function. Injected rather than
|
|
181
221
|
* imported at module top so the optional peer dep stays out of the bundle
|
|
@@ -196,6 +236,11 @@ interface LunoraBrowserOptions {
|
|
|
196
236
|
* For a hard guarantee prefer {@link LunoraBrowserOptions.allowedHosts}.
|
|
197
237
|
*/
|
|
198
238
|
resolveDns?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* The `@cloudflare/playwright` `sessions` function, injected like
|
|
241
|
+
* {@link LunoraBrowserOptions.launch}. Required for {@link Browser.sessions}.
|
|
242
|
+
*/
|
|
243
|
+
sessions?: BrowserSessionsLike;
|
|
199
244
|
/**
|
|
200
245
|
* Default navigation timeout (ms) applied when a per-call `timeoutMs` is not
|
|
201
246
|
* given. Clamped to the factory's `MAX_TIMEOUT_MS`. Default 30000.
|
|
@@ -214,15 +259,42 @@ interface LunoraBrowserOptions {
|
|
|
214
259
|
* @experimental
|
|
215
260
|
*/
|
|
216
261
|
interface Browser {
|
|
262
|
+
/**
|
|
263
|
+
* Re-attach to an existing session and hand the browser to `fn`.
|
|
264
|
+
*
|
|
265
|
+
* Get the id either by reading it inside the call that opened the session
|
|
266
|
+
* (`launch(async (browser) => browser.sessionId?.(), { keepAlive: 600 })`)
|
|
267
|
+
* and persisting it, or by picking a free one out of
|
|
268
|
+
* {@link Browser.sessions} — an entry with a `connectionId` is already held
|
|
269
|
+
* by another worker.
|
|
270
|
+
*
|
|
271
|
+
* The session is deliberately **left open** afterwards — closing it is the
|
|
272
|
+
* whole thing you are avoiding. Close it when the flow is done by passing
|
|
273
|
+
* `close: true`, or let `keepAlive` lapse.
|
|
274
|
+
*
|
|
275
|
+
* This is what makes agent-style browsing possible: a model calls
|
|
276
|
+
* `navigate`, then `click`, then `extract` as three separate action
|
|
277
|
+
* invocations, and the page has to survive between them. With only the
|
|
278
|
+
* per-call lifecycle each step got a fresh browser, so `click` ran against
|
|
279
|
+
* a blank page — silently, which is the worst shape for that bug.
|
|
280
|
+
*/
|
|
281
|
+
connect: <T>(sessionId: string, function_: (browser: BrowserLike) => Promise<T>, options?: {
|
|
282
|
+
close?: boolean;
|
|
283
|
+
}) => Promise<T>;
|
|
217
284
|
/** Serialized HTML of `url` after navigation settles. */
|
|
218
285
|
content: (url: string, options?: NavigateOptions) => Promise<string>;
|
|
219
286
|
/**
|
|
220
287
|
* 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).
|
|
222
|
-
*
|
|
223
|
-
*
|
|
288
|
+
* `fn` (e.g. for multi-page flows or APIs not surfaced here).
|
|
289
|
+
*
|
|
290
|
+
* The browser is **always closed** when `fn` resolves or throws — unless
|
|
291
|
+
* `keepAlive` is set, which holds the session open for that many seconds so
|
|
292
|
+
* a later {@link Browser.connect} can re-attach. Do not retain references to
|
|
293
|
+
* the browser past the callback either way.
|
|
224
294
|
*/
|
|
225
|
-
launch: <T>(function_: (browser: BrowserLike) => Promise<T
|
|
295
|
+
launch: <T>(function_: (browser: BrowserLike) => Promise<T>, options?: {
|
|
296
|
+
keepAlive?: number;
|
|
297
|
+
}) => Promise<T>;
|
|
226
298
|
/** Render `url` to a PDF buffer. */
|
|
227
299
|
pdf: (url: string, options?: PdfOptions) => Promise<Uint8Array>;
|
|
228
300
|
/**
|
|
@@ -233,6 +305,12 @@ interface Browser {
|
|
|
233
305
|
scrape: <T>(url: string, function_: (...args: never[]) => T, options?: NavigateOptions) => Promise<T>;
|
|
234
306
|
/** Render `url` to an image buffer (PNG by default). */
|
|
235
307
|
screenshot: (url: string, options?: ScreenshotOptions) => Promise<Uint8Array>;
|
|
308
|
+
/**
|
|
309
|
+
* List the live Browser Rendering sessions for this binding, so a caller can
|
|
310
|
+
* pick a free one to {@link Browser.connect} to. An entry with a
|
|
311
|
+
* `connectionId` is already held by another worker.
|
|
312
|
+
*/
|
|
313
|
+
sessions: () => Promise<ReadonlyArray<BrowserSession>>;
|
|
236
314
|
}
|
|
237
315
|
/**
|
|
238
316
|
* `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,17 @@ interface BrowserContextLike {
|
|
|
84
84
|
interface BrowserLike {
|
|
85
85
|
close: () => Promise<void>;
|
|
86
86
|
newContext: () => Promise<BrowserContextLike>;
|
|
87
|
+
/**
|
|
88
|
+
* The Browser Rendering session this browser is attached to, when the
|
|
89
|
+
* runtime exposes it.
|
|
90
|
+
*
|
|
91
|
+
* Optional because this is a structural projection, not a re-declaration of
|
|
92
|
+
* the upstream Playwright type — but without it there is no way to learn
|
|
93
|
+
* the id of a session you just held open with `launch(fn, { keepAlive })`,
|
|
94
|
+
* which makes {@link Browser.connect} unreachable except by guessing from
|
|
95
|
+
* {@link Browser.sessions}.
|
|
96
|
+
*/
|
|
97
|
+
sessionId?: () => string | undefined;
|
|
87
98
|
}
|
|
88
99
|
/**
|
|
89
100
|
* Structural projection of `@cloudflare/playwright`'s `launch` export
|
|
@@ -95,6 +106,30 @@ interface BrowserLike {
|
|
|
95
106
|
* @experimental
|
|
96
107
|
*/
|
|
97
108
|
type BrowserLaunchLike = (binding: BrowserBindingLike, options?: Record<string, unknown>) => Promise<BrowserLike>;
|
|
109
|
+
/**
|
|
110
|
+
* One live Browser Rendering session, as `@cloudflare/playwright`'s `sessions()`
|
|
111
|
+
* reports it. `connectionId` is set while another worker holds the session — you
|
|
112
|
+
* can only {@link Browser.connect} to a free one.
|
|
113
|
+
* @experimental
|
|
114
|
+
*/
|
|
115
|
+
interface BrowserSession {
|
|
116
|
+
connectionId?: string;
|
|
117
|
+
sessionId: string;
|
|
118
|
+
startTime?: number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Structural projection of `@cloudflare/playwright`'s `connect` export —
|
|
122
|
+
* re-attaches to an existing session rather than starting a new browser.
|
|
123
|
+
* Injected like {@link BrowserLaunchLike} so the peer dep stays optional.
|
|
124
|
+
* @experimental
|
|
125
|
+
*/
|
|
126
|
+
type BrowserConnectLike = (binding: BrowserBindingLike, sessionId: string) => Promise<BrowserLike>;
|
|
127
|
+
/**
|
|
128
|
+
* Structural projection of `@cloudflare/playwright`'s `sessions` export — lists
|
|
129
|
+
* the account's live Browser Rendering sessions for this binding.
|
|
130
|
+
* @experimental
|
|
131
|
+
*/
|
|
132
|
+
type BrowserSessionsLike = (binding: BrowserBindingLike) => Promise<ReadonlyArray<BrowserSession>>;
|
|
98
133
|
/**
|
|
99
134
|
* Options shared by the page-driving helpers ({@link Browser.screenshot} etc.).
|
|
100
135
|
* @experimental
|
|
@@ -176,6 +211,11 @@ interface LunoraBrowserOptions {
|
|
|
176
211
|
allowPrivateTargets?: boolean;
|
|
177
212
|
/** The Cloudflare Browser Rendering binding (`env.BROWSER`). Required. */
|
|
178
213
|
binding: BrowserBindingLike;
|
|
214
|
+
/**
|
|
215
|
+
* The `@cloudflare/playwright` `connect` function, injected like
|
|
216
|
+
* {@link LunoraBrowserOptions.launch}. Required for {@link Browser.connect}.
|
|
217
|
+
*/
|
|
218
|
+
connect?: BrowserConnectLike;
|
|
179
219
|
/**
|
|
180
220
|
* The `@cloudflare/playwright` `launch` function. Injected rather than
|
|
181
221
|
* imported at module top so the optional peer dep stays out of the bundle
|
|
@@ -196,6 +236,11 @@ interface LunoraBrowserOptions {
|
|
|
196
236
|
* For a hard guarantee prefer {@link LunoraBrowserOptions.allowedHosts}.
|
|
197
237
|
*/
|
|
198
238
|
resolveDns?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* The `@cloudflare/playwright` `sessions` function, injected like
|
|
241
|
+
* {@link LunoraBrowserOptions.launch}. Required for {@link Browser.sessions}.
|
|
242
|
+
*/
|
|
243
|
+
sessions?: BrowserSessionsLike;
|
|
199
244
|
/**
|
|
200
245
|
* Default navigation timeout (ms) applied when a per-call `timeoutMs` is not
|
|
201
246
|
* given. Clamped to the factory's `MAX_TIMEOUT_MS`. Default 30000.
|
|
@@ -214,15 +259,42 @@ interface LunoraBrowserOptions {
|
|
|
214
259
|
* @experimental
|
|
215
260
|
*/
|
|
216
261
|
interface Browser {
|
|
262
|
+
/**
|
|
263
|
+
* Re-attach to an existing session and hand the browser to `fn`.
|
|
264
|
+
*
|
|
265
|
+
* Get the id either by reading it inside the call that opened the session
|
|
266
|
+
* (`launch(async (browser) => browser.sessionId?.(), { keepAlive: 600 })`)
|
|
267
|
+
* and persisting it, or by picking a free one out of
|
|
268
|
+
* {@link Browser.sessions} — an entry with a `connectionId` is already held
|
|
269
|
+
* by another worker.
|
|
270
|
+
*
|
|
271
|
+
* The session is deliberately **left open** afterwards — closing it is the
|
|
272
|
+
* whole thing you are avoiding. Close it when the flow is done by passing
|
|
273
|
+
* `close: true`, or let `keepAlive` lapse.
|
|
274
|
+
*
|
|
275
|
+
* This is what makes agent-style browsing possible: a model calls
|
|
276
|
+
* `navigate`, then `click`, then `extract` as three separate action
|
|
277
|
+
* invocations, and the page has to survive between them. With only the
|
|
278
|
+
* per-call lifecycle each step got a fresh browser, so `click` ran against
|
|
279
|
+
* a blank page — silently, which is the worst shape for that bug.
|
|
280
|
+
*/
|
|
281
|
+
connect: <T>(sessionId: string, function_: (browser: BrowserLike) => Promise<T>, options?: {
|
|
282
|
+
close?: boolean;
|
|
283
|
+
}) => Promise<T>;
|
|
217
284
|
/** Serialized HTML of `url` after navigation settles. */
|
|
218
285
|
content: (url: string, options?: NavigateOptions) => Promise<string>;
|
|
219
286
|
/**
|
|
220
287
|
* 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).
|
|
222
|
-
*
|
|
223
|
-
*
|
|
288
|
+
* `fn` (e.g. for multi-page flows or APIs not surfaced here).
|
|
289
|
+
*
|
|
290
|
+
* The browser is **always closed** when `fn` resolves or throws — unless
|
|
291
|
+
* `keepAlive` is set, which holds the session open for that many seconds so
|
|
292
|
+
* a later {@link Browser.connect} can re-attach. Do not retain references to
|
|
293
|
+
* the browser past the callback either way.
|
|
224
294
|
*/
|
|
225
|
-
launch: <T>(function_: (browser: BrowserLike) => Promise<T
|
|
295
|
+
launch: <T>(function_: (browser: BrowserLike) => Promise<T>, options?: {
|
|
296
|
+
keepAlive?: number;
|
|
297
|
+
}) => Promise<T>;
|
|
226
298
|
/** Render `url` to a PDF buffer. */
|
|
227
299
|
pdf: (url: string, options?: PdfOptions) => Promise<Uint8Array>;
|
|
228
300
|
/**
|
|
@@ -233,6 +305,12 @@ interface Browser {
|
|
|
233
305
|
scrape: <T>(url: string, function_: (...args: never[]) => T, options?: NavigateOptions) => Promise<T>;
|
|
234
306
|
/** Render `url` to an image buffer (PNG by default). */
|
|
235
307
|
screenshot: (url: string, options?: ScreenshotOptions) => Promise<Uint8Array>;
|
|
308
|
+
/**
|
|
309
|
+
* List the live Browser Rendering sessions for this binding, so a caller can
|
|
310
|
+
* pick a free one to {@link Browser.connect} to. An entry with a
|
|
311
|
+
* `connectionId` is already held by another worker.
|
|
312
|
+
*/
|
|
313
|
+
sessions: () => Promise<ReadonlyArray<BrowserSession>>;
|
|
236
314
|
}
|
|
237
315
|
/**
|
|
238
316
|
* `createBrowser` is part of the experimental `@lunora/browser` API and may change without a major version bump.
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createBrowser as o}from"./packem_shared/createBrowser-
|
|
1
|
+
import{createBrowser as o}from"./packem_shared/createBrowser-DrmNpKuh.mjs";export{o as createBrowser};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as c}from"@lunora/errors";const k=/^\d{1,3}$/u,x=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,P=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,F=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,H=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,_=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,O=/^\[|\]$/gu,q=/\.$/u,m=t=>{const e=t.split(".");if(e.length!==4)return;const r=e.map(n=>k.test(n)?Number(n):-1);if(!r.some(n=>n<0||n>255))return[r[0],r[1],r[2],r[3]]},y=([t,e])=>t===0||t===10||t===127||t===100&&e>=64&&e<=127||t===169&&e===254||t===172&&e>=16&&e<=31||t===192&&e===168||t>=224,B=(t,e)=>{const r=Number.parseInt(t??"",16),n=Number.parseInt(e??"",16);return!Number.isFinite(r)||!Number.isFinite(n)?!0:y([Math.floor(r/256),r%256,Math.floor(n/256),n%256])},U=t=>{const e=t.toLowerCase(),r=x.exec(e);if(r)return B(r[1],r[2]);const n=P.exec(e);if(n){const a=m(n[1]??"");return a===void 0||y(a)}const i=F.exec(e);if(i){const a=m(i[1]??"");return a===void 0||y(a)}const o=H.exec(e);return o?B(o[1],o[2]):_.test(e)||e.startsWith("2002:")||e.startsWith("2001:0:")?!0:e==="::"||e==="::1"||e.startsWith("fc")||e.startsWith("fd")||e.startsWith("fe8")||e.startsWith("fe9")||e.startsWith("fea")||e.startsWith("feb")},C=t=>t==="localhost"||t.endsWith(".localhost")||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".home.arpa"),d=t=>t.replaceAll(O,"").replace(q,"").toLowerCase(),A=t=>{const e=d(t);if(e.includes(":"))return U(e);const r=m(e);return r===void 0?C(e):y(r)},N=3e4,Q=12e4,V=3840,j=4320,z="https://cloudflare-dns.com/dns-query",v=5e3,b=1,T=28,G=(t,e)=>{if(e===b){const r=m(t);return r===void 0||y(r)}return U(t.toLowerCase())},W=async(t,e,r=v)=>{try{const n=await fetch(`${z}?name=${encodeURIComponent(t)}&type=${String(e)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(r)});return n.ok?(await n.json()).Answer??[]:void 0}catch{return}},S=async(t,e=v)=>{const r=d(new URL(t).hostname);if(r.includes(":")||m(r)!==void 0)return;const[n,i]=await Promise.all([W(r,b,e),W(r,T,e)]);if(!(n===void 0&&i===void 0)){for(const o of[...n??[],...i??[]])if((o.type===b||o.type===T)&&G(o.data,o.type))throw new c("FORBIDDEN",`@lunora/browser: url host "${r}" resolves to a private/internal address (${o.data}); refusing to navigate (DNS-rebinding guard)`)}},D=(t,e,r)=>{if(typeof t!="string"||t.length===0)throw new c("BAD_REQUEST","@lunora/browser: url must be a non-empty string");let n;try{n=new URL(t)}catch{throw new c("BAD_REQUEST",`@lunora/browser: url must be an absolute http(s) URL (got "${t}")`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new c("BAD_REQUEST",`@lunora/browser: url protocol must be http(s) (got "${n.protocol}")`);if(n.username!==""||n.password!=="")throw new c("BAD_REQUEST","@lunora/browser: url must not embed credentials (strip the `user:pass@` userinfo)");if(r&&r.length>0){const i=d(n.hostname);if(!r.some(o=>d(o)===i))throw new c("FORBIDDEN",`@lunora/browser: url host "${n.hostname}" is not in the configured allowedHosts allowlist`)}if(!e&&A(n.hostname))throw new c("FORBIDDEN",`@lunora/browser: url host "${n.hostname}" is a private/internal address; pass createBrowser({ …, allowPrivateTargets: true }) to allow it`);return n.toString()},M=(t,e)=>Number.isFinite(t)?Math.min(Math.max(1,Math.floor(t)),e):e,J=t=>({height:M(t.height,j),width:M(t.width,V)}),K=(t,e)=>{const r=t??e??N,n=Number.isFinite(r)?r:N;return Math.min(Math.max(1,Math.floor(n)),Q)},X=async(t,e)=>{let r;try{return await Promise.race([t(),new Promise((n,i)=>{r=setTimeout(()=>{i(new c("BROWSER_TIMEOUT",`@lunora/browser: navigation + operation exceeded the ${String(e)}ms timeout budget`,{status:504}))},e)})])}finally{r!==void 0&&clearTimeout(r)}},Z=t=>{if(!t.binding)throw new TypeError("@lunora/browser: `binding` is required (env.BROWSER)");const e=()=>{if(!t.launch)throw new c("INTERNAL",'@lunora/browser: `launch` is not available — install the `@cloudflare/playwright` peer dependency. The generated worker wires it for you; outside codegen pass it via createBrowser({ binding, launch }) (import { launch } from "@cloudflare/playwright").');return t.launch},r=(o,a)=>{if(!o)throw new c("INTERNAL",`@lunora/browser: \`${a}\` is not available — install the \`@cloudflare/playwright\` peer dependency. The generated worker wires it for you; outside codegen pass it via createBrowser({ binding, ${a} }).`);return o},n=async(o,a)=>{const s=await e()(t.binding,a===void 0?void 0:{keep_alive:a*1e3});if(a!==void 0)return await o(s);try{return await o(s)}finally{try{await s.close()}catch{}}},i=async(o,a,s,u)=>{const f=t.allowPrivateTargets??!1,$=D(o,f,t.allowedHosts),g=K(a.timeoutMs,t.timeoutMs),E=t.resolveDns??!1,R=Math.min(g,v);!f&&E&&await S($,R);const I=async h=>{D(h,f,t.allowedHosts),!f&&E&&await S(h,R)},L=h=>{let l;try{l=new URL(h)}catch{return!1}if(l.protocol!=="http:"&&l.protocol!=="https:")return!1;if(t.allowedHosts&&t.allowedHosts.length>0){const w=d(l.hostname);if(!t.allowedHosts.some(p=>d(p)===w))return!0}return A(l.hostname)};return n(async h=>{const l=await(await h.newContext()).newPage();return l.route&&(!f||(t.allowedHosts?.length??0)>0)&&await l.route("**/*",async w=>{const p=w.request();if(!(p.isNavigationRequest?.()??!0)){if(L(p.url())){await w.abort("blockedbyclient");return}await w.continue();return}try{await I(p.url())}catch{await w.abort("blockedbyclient");return}await w.continue()}),u&&l.setViewportSize&&await l.setViewportSize(J(u)),X(async()=>(await l.goto($,{timeout:g,waitUntil:a.waitUntil??"load"}),s(l)),g)})};return{connect:async(o,a,s={})=>{const u=await r(t.connect,"connect")(t.binding,o);if(s.close!==!0)return await a(u);try{return await a(u)}finally{try{await u.close()}catch{}}},content:async(o,a={})=>i(o,a,async s=>s.content()),launch:async(o,a={})=>n(o,a.keepAlive),pdf:async(o,a={})=>i(o,a,async s=>s.pdf({format:a.format,printBackground:a.printBackground??!1}),a.viewport),scrape:async(o,a,s={})=>i(o,s,async u=>u.evaluate(a)),screenshot:async(o,a={})=>i(o,a,async s=>s.screenshot({fullPage:a.fullPage??!1,type:a.type??"png"}),a.viewport),sessions:async()=>await r(t.sessions,"sessions")(t.binding)}};export{Z as createBrowser};
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as l}from"@lunora/errors";const L=/^\d{1,3}$/u,P=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,A=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,F=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,H=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,_=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,k=/^\[|\]$/gu,O=/\.$/u,f=t=>{const e=t.split(".");if(e.length!==4)return;const r=e.map(o=>L.test(o)?Number(o):-1);if(!r.some(o=>o<0||o>255))return[r[0],r[1],r[2],r[3]]},p=([t,e])=>t===0||t===10||t===127||t===100&&e>=64&&e<=127||t===169&&e===254||t===172&&e>=16&&e<=31||t===192&&e===168||t>=224,R=(t,e)=>{const r=Number.parseInt(t??"",16),o=Number.parseInt(e??"",16);return!Number.isFinite(r)||!Number.isFinite(o)?!0:p([Math.floor(r/256),r%256,Math.floor(o/256),o%256])},S=t=>{const e=t.toLowerCase(),r=P.exec(e);if(r)return R(r[1],r[2]);const o=A.exec(e);if(o){const s=f(o[1]??"");return s===void 0||p(s)}const n=F.exec(e);if(n){const s=f(n[1]??"");return s===void 0||p(s)}const a=H.exec(e);return a?R(a[1],a[2]):_.test(e)||e.startsWith("2002:")||e.startsWith("2001:0:")?!0:e==="::"||e==="::1"||e.startsWith("fc")||e.startsWith("fd")||e.startsWith("fe8")||e.startsWith("fe9")||e.startsWith("fea")||e.startsWith("feb")},q=t=>t==="localhost"||t.endsWith(".localhost")||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".home.arpa"),h=t=>t.replaceAll(k,"").replace(O,"").toLowerCase(),U=t=>{const e=h(t);if(e.includes(":"))return S(e);const r=f(e);return r===void 0?q(e):p(r)},B=3e4,C=12e4,Q=3840,V=4320,j="https://cloudflare-dns.com/dns-query",b=5e3,g=1,W=28,z=(t,e)=>{if(e===g){const r=f(t);return r===void 0||p(r)}return S(t.toLowerCase())},N=async(t,e,r=b)=>{try{const o=await fetch(`${j}?name=${encodeURIComponent(t)}&type=${String(e)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(r)});return o.ok?(await o.json()).Answer??[]:void 0}catch{return}},T=async(t,e=b)=>{const r=h(new URL(t).hostname);if(r.includes(":")||f(r)!==void 0)return;const[o,n]=await Promise.all([N(r,g,e),N(r,W,e)]);if(!(o===void 0&&n===void 0)){for(const a of[...o??[],...n??[]])if((a.type===g||a.type===W)&&z(a.data,a.type))throw new l("FORBIDDEN",`@lunora/browser: url host "${r}" resolves to a private/internal address (${a.data}); refusing to navigate (DNS-rebinding guard)`)}},D=(t,e,r)=>{if(typeof t!="string"||t.length===0)throw new l("BAD_REQUEST","@lunora/browser: url must be a non-empty string");let o;try{o=new URL(t)}catch{throw new l("BAD_REQUEST",`@lunora/browser: url must be an absolute http(s) URL (got "${t}")`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new l("BAD_REQUEST",`@lunora/browser: url protocol must be http(s) (got "${o.protocol}")`);if(o.username!==""||o.password!=="")throw new l("BAD_REQUEST","@lunora/browser: url must not embed credentials (strip the `user:pass@` userinfo)");if(r&&r.length>0){const n=h(o.hostname);if(!r.some(a=>h(a)===n))throw new l("FORBIDDEN",`@lunora/browser: url host "${o.hostname}" is not in the configured allowedHosts allowlist`)}if(!e&&U(o.hostname))throw new l("FORBIDDEN",`@lunora/browser: url host "${o.hostname}" is a private/internal address; pass createBrowser({ …, allowPrivateTargets: true }) to allow it`);return o.toString()},M=(t,e)=>Number.isFinite(t)?Math.min(Math.max(1,Math.floor(t)),e):e,G=t=>({height:M(t.height,V),width:M(t.width,Q)}),J=(t,e)=>{const r=t??e??B,o=Number.isFinite(r)?r:B;return Math.min(Math.max(1,Math.floor(o)),C)},K=async(t,e)=>{let r;try{return await Promise.race([t(),new Promise((o,n)=>{r=setTimeout(()=>{n(new l("BROWSER_TIMEOUT",`@lunora/browser: navigation + operation exceeded the ${String(e)}ms timeout budget`,{status:504}))},e)})])}finally{r!==void 0&&clearTimeout(r)}},Y=t=>{if(!t.binding)throw new TypeError("@lunora/browser: `binding` is required (env.BROWSER)");const e=()=>{if(!t.launch)throw new l("INTERNAL",'@lunora/browser: `launch` is not available — install the `@cloudflare/playwright` peer dependency. The generated worker wires it for you; outside codegen pass it via createBrowser({ binding, launch }) (import { launch } from "@cloudflare/playwright").');return t.launch},r=async n=>{const a=await e()(t.binding);try{return await n(a)}finally{try{await a.close()}catch{}}},o=async(n,a,s,m)=>{const w=t.allowPrivateTargets??!1,v=D(n,w,t.allowedHosts),y=J(a.timeoutMs,t.timeoutMs),$=t.resolveDns??!1,E=Math.min(y,b);!w&&$&&await T(v,E);const x=async c=>{D(c,w,t.allowedHosts),!w&&$&&await T(c,E)},I=c=>{let i;try{i=new URL(c)}catch{return!1}if(i.protocol!=="http:"&&i.protocol!=="https:")return!1;if(t.allowedHosts&&t.allowedHosts.length>0){const u=h(i.hostname);if(!t.allowedHosts.some(d=>h(d)===u))return!0}return U(i.hostname)};return r(async c=>{const i=await(await c.newContext()).newPage();return i.route&&(!w||(t.allowedHosts?.length??0)>0)&&await i.route("**/*",async u=>{const d=u.request();if(!(d.isNavigationRequest?.()??!0)){if(I(d.url())){await u.abort("blockedbyclient");return}await u.continue();return}try{await x(d.url())}catch{await u.abort("blockedbyclient");return}await u.continue()}),m&&i.setViewportSize&&await i.setViewportSize(G(m)),K(async()=>(await i.goto(v,{timeout:y,waitUntil:a.waitUntil??"load"}),s(i)),y)})};return{content:async(n,a={})=>o(n,a,async s=>s.content()),launch:async n=>r(n),pdf:async(n,a={})=>o(n,a,async s=>s.pdf({format:a.format,printBackground:a.printBackground??!1}),a.viewport),scrape:async(n,a,s={})=>o(n,s,async m=>m.evaluate(a)),screenshot:async(n,a={})=>o(n,a,async s=>s.screenshot({fullPage:a.fullPage??!1,type:a.type??"png"}),a.viewport)}};export{Y as createBrowser};
|