@lunora/browser 1.0.0-alpha.27 → 1.0.0-alpha.29

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
@@ -15,7 +15,7 @@
15
15
  * @experimental
16
16
  */
17
17
  interface BrowserBindingLike {
18
- readonly fetch: (...args: never[]) => unknown;
18
+ readonly fetch: typeof fetch;
19
19
  }
20
20
  /**
21
21
  * Minimal projection of a Playwright `Route` (the argument the `page.route`
@@ -44,11 +44,11 @@ interface PageLike {
44
44
  /** Return the page's serialized HTML after the navigation settles. */
45
45
  content: () => Promise<string>;
46
46
  /** Run a function in the page context and return its (serializable) result. */
47
- evaluate: <T>(function_: (...args: never[]) => T) => Promise<T>;
47
+ evaluate: <T>(function_: () => T) => Promise<T>;
48
48
  /** Navigate to a URL; resolves once the configured wait condition is met. */
49
49
  goto: (url: string, options?: {
50
50
  timeout?: number;
51
- waitUntil?: string;
51
+ waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
52
52
  }) => Promise<unknown>;
53
53
  /** Render the page to a PDF buffer. */
54
54
  pdf: (options?: Record<string, unknown>) => Promise<Uint8Array>;
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * @experimental
16
16
  */
17
17
  interface BrowserBindingLike {
18
- readonly fetch: (...args: never[]) => unknown;
18
+ readonly fetch: typeof fetch;
19
19
  }
20
20
  /**
21
21
  * Minimal projection of a Playwright `Route` (the argument the `page.route`
@@ -44,11 +44,11 @@ interface PageLike {
44
44
  /** Return the page's serialized HTML after the navigation settles. */
45
45
  content: () => Promise<string>;
46
46
  /** Run a function in the page context and return its (serializable) result. */
47
- evaluate: <T>(function_: (...args: never[]) => T) => Promise<T>;
47
+ evaluate: <T>(function_: () => T) => Promise<T>;
48
48
  /** Navigate to a URL; resolves once the configured wait condition is met. */
49
49
  goto: (url: string, options?: {
50
50
  timeout?: number;
51
- waitUntil?: string;
51
+ waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
52
52
  }) => Promise<unknown>;
53
53
  /** Render the page to a PDF buffer. */
54
54
  pdf: (options?: Record<string, unknown>) => Promise<Uint8Array>;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createBrowser as o}from"./packem_shared/createBrowser-elmnaxue.mjs";export{o as createBrowser};
1
+ import{createBrowser as o}from"./packem_shared/createBrowser-BicVf2xu.mjs";export{o as createBrowser};
@@ -0,0 +1 @@
1
+ import{LunoraError as l}from"@lunora/errors";const O=/^\d{1,3}$/u,x=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,V=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,C=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,F=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,q=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,X=/^\[|\]$/gu,Q=/\.$/u,v=t=>{const e=t.split(".");if(e.length!==4)return;const r=e.map(n=>O.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,D=(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])},$=t=>{const e=t.toLowerCase(),r=x.exec(e);if(r)return D(r[1],r[2]);const n=V.exec(e);if(n){const f=v(n[1]??"");return f===void 0||y(f)}const a=C.exec(e);if(a){const f=v(a[1]??"");return f===void 0||y(f)}const c=F.exec(e);return c?D(c[1],c[2]):q.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")},z=t=>t==="localhost"||t.endsWith(".localhost")||t.endsWith(".local")||t.endsWith(".internal")||t.endsWith(".home.arpa"),h=t=>t.replaceAll(X,"").replace(Q,"").toLowerCase(),L=t=>{const e=h(t);if(e.includes(":"))return $(e);const r=v(e);return r===void 0?z(e):y(r)},G="https://cloudflare-dns.com/dns-query",E=1,R=28,j=2e3,Y=(t,e)=>{if(e===E){const r=v(t);return r===void 0||y(r)}return $(t.toLowerCase())},N=async(t,e,r)=>{try{const n=await fetch(`${G}?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}},K=async(t,e=j)=>{const r=h(t);if(r.includes(":")||v(r)!==void 0)return{kind:"unknown"};const[n,a]=await Promise.all([N(r,E,e),N(r,R,e)]);if(n===void 0&&a===void 0)return{kind:"unknown"};for(const c of[...n??[],...a??[]])if((c.type===E||c.type===R)&&Y(c.data,c.type))return{address:c.data,kind:"private"};return{kind:"public"}},A=3e4,J=12e4,Z=3840,tt=4320,W=5e3,M=async(t,e=W)=>{const r=h(new URL(t).hostname),n=await K(r,e);if(n.kind==="private")throw new l("FORBIDDEN",`@lunora/browser: url host "${r}" resolves to a private/internal address (${n.address}); refusing to navigate (DNS-rebinding guard)`)},H=(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 n;try{n=new URL(t)}catch{throw new l("BAD_REQUEST",`@lunora/browser: url must be an absolute http(s) URL (got "${t}")`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new l("BAD_REQUEST",`@lunora/browser: url protocol must be http(s) (got "${n.protocol}")`);if(n.username!==""||n.password!=="")throw new l("BAD_REQUEST","@lunora/browser: url must not embed credentials (strip the `user:pass@` userinfo)");if(r&&r.length>0){const a=h(n.hostname);if(!r.some(c=>h(c)===a))throw new l("FORBIDDEN",`@lunora/browser: url host "${n.hostname}" is not in the configured allowedHosts allowlist`)}if(!e&&L(n.hostname))throw new l("FORBIDDEN",`@lunora/browser: url host "${n.hostname}" is a private/internal address; pass createBrowser({ …, allowPrivateTargets: true }) to allow it`);return n.toString()},S=(t,e)=>Number.isFinite(t)?Math.min(Math.max(1,Math.floor(t)),e):e,et=t=>({height:S(t.height,tt),width:S(t.width,Z)}),rt=(t,e)=>{const r=t??e??A,n=Number.isFinite(r)?r:A;return Math.min(Math.max(1,Math.floor(n)),J)},nt=async(t,e)=>{let r;try{return await Promise.race([t(),new Promise((n,a)=>{r=setTimeout(()=>{a(new l("BROWSER_TIMEOUT",`@lunora/browser: navigation + operation exceeded the ${String(e)}ms timeout budget`,{status:504}))},e)})])}finally{r!==void 0&&clearTimeout(r)}},B=async t=>{try{await t.close()}catch{}},dt=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=(o,s)=>{if(!o)throw new l("INTERNAL",`@lunora/browser: \`${s}\` is not available — install the \`@cloudflare/playwright\` peer dependency. The generated worker wires it for you; outside codegen pass it via createBrowser({ binding, ${s} }).`);return o},n=async(o,s)=>{const i=await e()(t.binding,s===void 0?void 0:{keep_alive:s*1e3});if(s!==void 0)return await o(i);try{return await o(i)}finally{await B(i)}},a=async(o,s,i,u)=>{const g=t.allowPrivateTargets??!1,I=H(o,g,t.allowedHosts),b=rt(s.timeoutMs,t.timeoutMs),P=t.resolveDns??(t.allowedHosts?.length??0)===0,_=Math.min(b,W);!g&&P&&await M(I,_);const k=async m=>{H(m,g,t.allowedHosts),!g&&P&&await M(m,_)},U=m=>{let p;try{p=new URL(m)}catch{return!1}if(p.protocol!=="http:"&&p.protocol!=="https:")return!1;if(t.allowedHosts&&t.allowedHosts.length>0){const d=h(p.hostname);if(!t.allowedHosts.some(w=>h(w)===d))return!0}return L(p.hostname)};return n(async m=>{const d=await(await m.newContext()).newPage();return d.route&&(!g||(t.allowedHosts?.length??0)>0)&&await d.route("**/*",async w=>{const T=w.request();if(!(T.isNavigationRequest?.()??!0)){if(U(T.url())){await w.abort("blockedbyclient");return}await w.continue();return}try{await k(T.url())}catch{await w.abort("blockedbyclient");return}await w.continue()}),u&&d.setViewportSize&&await d.setViewportSize(et(u)),nt(async()=>(await d.goto(I,{timeout:b,waitUntil:s.waitUntil??"load"}),i(d)),b)})};return{connect:async(o,s,i={})=>{const u=await r(t.connect,"connect")(t.binding,o);if(i.close!==!0)return await s(u);try{return await s(u)}finally{await B(u)}},content:async(o,s={})=>a(o,s,async i=>i.content()),launch:async(o,s={})=>n(o,s.keepAlive),pdf:async(o,s={})=>a(o,s,async i=>i.pdf({format:s.format,printBackground:s.printBackground??!1}),s.viewport),scrape:async(o,s,i={})=>a(o,i,async u=>u.evaluate(s)),screenshot:async(o,s={})=>a(o,s,async i=>i.screenshot({fullPage:s.fullPage??!1,type:s.type??"png"}),s.viewport),sessions:async()=>await r(t.sessions,"sessions")(t.binding)}};export{dt as createBrowser};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/browser",
3
- "version": "1.0.0-alpha.27",
3
+ "version": "1.0.0-alpha.29",
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.21"
49
+ "@lunora/errors": "1.0.0-alpha.22"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@cloudflare/playwright": ">=1.0.0"
@@ -1 +0,0 @@
1
- import{LunoraError as c}from"@lunora/errors";const P=/^\d{1,3}$/u,x=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,H=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,_=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,F=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,O=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,C=/^\[|\]$/gu,q=/\.$/u,m=t=>{const e=t.split(".");if(e.length!==4)return;const n=e.map(r=>P.test(r)?Number(r):-1);if(!n.some(r=>r<0||r>255))return[n[0],n[1],n[2],n[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,R=(t,e)=>{const n=Number.parseInt(t??"",16),r=Number.parseInt(e??"",16);return!Number.isFinite(n)||!Number.isFinite(r)?!0:y([Math.floor(n/256),n%256,Math.floor(r/256),r%256])},M=t=>{const e=t.toLowerCase(),n=x.exec(e);if(n)return R(n[1],n[2]);const r=H.exec(e);if(r){const a=m(r[1]??"");return a===void 0||y(a)}const i=_.exec(e);if(i){const a=m(i[1]??"");return a===void 0||y(a)}const o=F.exec(e);return o?R(o[1],o[2]):O.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"),w=t=>t.replaceAll(C,"").replace(q,"").toLowerCase(),U=t=>{const e=w(t);if(e.includes(":"))return M(e);const n=m(e);return n===void 0?Q(e):y(n)},j="https://cloudflare-dns.com/dns-query",b=1,N=28,z=2e3,V=(t,e)=>{if(e===b){const n=m(t);return n===void 0||y(n)}return M(t.toLowerCase())},k=async(t,e,n)=>{try{const r=await fetch(`${j}?name=${encodeURIComponent(t)}&type=${String(e)}`,{headers:{accept:"application/dns-json"},signal:AbortSignal.timeout(n)});return r.ok?(await r.json()).Answer??[]:void 0}catch{return}},G=async(t,e=z)=>{const n=w(t);if(n.includes(":")||m(n)!==void 0)return{kind:"unknown"};const[r,i]=await Promise.all([k(n,b,e),k(n,N,e)]);if(r===void 0&&i===void 0)return{kind:"unknown"};for(const o of[...r??[],...i??[]])if((o.type===b||o.type===N)&&V(o.data,o.type))return{address:o.data,kind:"private"};return{kind:"public"}},B=3e4,J=12e4,K=3840,X=4320,A=5e3,T=async(t,e=A)=>{const n=w(new URL(t).hostname),r=await G(n,e);if(r.kind==="private")throw new c("FORBIDDEN",`@lunora/browser: url host "${n}" resolves to a private/internal address (${r.address}); refusing to navigate (DNS-rebinding guard)`)},S=(t,e,n)=>{if(typeof t!="string"||t.length===0)throw new c("BAD_REQUEST","@lunora/browser: url must be a non-empty string");let r;try{r=new URL(t)}catch{throw new c("BAD_REQUEST",`@lunora/browser: url must be an absolute http(s) URL (got "${t}")`)}if(r.protocol!=="http:"&&r.protocol!=="https:")throw new c("BAD_REQUEST",`@lunora/browser: url protocol must be http(s) (got "${r.protocol}")`);if(r.username!==""||r.password!=="")throw new c("BAD_REQUEST","@lunora/browser: url must not embed credentials (strip the `user:pass@` userinfo)");if(n&&n.length>0){const i=w(r.hostname);if(!n.some(o=>w(o)===i))throw new c("FORBIDDEN",`@lunora/browser: url host "${r.hostname}" is not in the configured allowedHosts allowlist`)}if(!e&&U(r.hostname))throw new c("FORBIDDEN",`@lunora/browser: url host "${r.hostname}" is a private/internal address; pass createBrowser({ …, allowPrivateTargets: true }) to allow it`);return r.toString()},W=(t,e)=>Number.isFinite(t)?Math.min(Math.max(1,Math.floor(t)),e):e,Y=t=>({height:W(t.height,X),width:W(t.width,K)}),Z=(t,e)=>{const n=t??e??B,r=Number.isFinite(n)?n:B;return Math.min(Math.max(1,Math.floor(r)),J)},tt=async(t,e)=>{let n;try{return await Promise.race([t(),new Promise((r,i)=>{n=setTimeout(()=>{i(new c("BROWSER_TIMEOUT",`@lunora/browser: navigation + operation exceeded the ${String(e)}ms timeout budget`,{status:504}))},e)})])}finally{n!==void 0&&clearTimeout(n)}},D=async t=>{try{await t.close()}catch{}},nt=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},n=(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},r=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{await D(s)}},i=async(o,a,s,u)=>{const f=t.allowPrivateTargets??!1,v=S(o,f,t.allowedHosts),g=Z(a.timeoutMs,t.timeoutMs),$=t.resolveDns??(t.allowedHosts?.length??0)===0,E=Math.min(g,A);!f&&$&&await T(v,E);const I=async h=>{S(h,f,t.allowedHosts),!f&&$&&await T(h,E)},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 d=w(l.hostname);if(!t.allowedHosts.some(p=>w(p)===d))return!0}return U(l.hostname)};return r(async h=>{const l=await(await h.newContext()).newPage();return l.route&&(!f||(t.allowedHosts?.length??0)>0)&&await l.route("**/*",async d=>{const p=d.request();if(!(p.isNavigationRequest?.()??!0)){if(L(p.url())){await d.abort("blockedbyclient");return}await d.continue();return}try{await I(p.url())}catch{await d.abort("blockedbyclient");return}await d.continue()}),u&&l.setViewportSize&&await l.setViewportSize(Y(u)),tt(async()=>(await l.goto(v,{timeout:g,waitUntil:a.waitUntil??"load"}),s(l)),g)})};return{connect:async(o,a,s={})=>{const u=await n(t.connect,"connect")(t.binding,o);if(s.close!==!0)return await a(u);try{return await a(u)}finally{await D(u)}},content:async(o,a={})=>i(o,a,async s=>s.content()),launch:async(o,a={})=>r(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 n(t.sessions,"sessions")(t.binding)}};export{nt as createBrowser};