@lunora/browser 1.0.0-alpha.37 → 1.0.0-alpha.39
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
|
@@ -304,9 +304,14 @@ interface Browser {
|
|
|
304
304
|
* `fn` (e.g. for multi-page flows or APIs not surfaced here).
|
|
305
305
|
*
|
|
306
306
|
* The browser is **always closed** when `fn` resolves or throws — unless
|
|
307
|
-
* `keepAlive` is
|
|
308
|
-
* a later {@link Browser.connect} can
|
|
309
|
-
*
|
|
307
|
+
* `keepAlive` is a number of seconds **between 10 and 600**, which holds the
|
|
308
|
+
* session open for that long so a later {@link Browser.connect} can
|
|
309
|
+
* re-attach. `0`, a negative value and `NaN` all mean "do not keep alive"
|
|
310
|
+
* and take the always-close path (a held session is billed, so the
|
|
311
|
+
* ambiguous values fall to the safe side); a positive value outside the
|
|
312
|
+
* 10–600s window Browser Rendering accepts throws `BAD_REQUEST` rather than
|
|
313
|
+
* being sent on for the provider to refuse. Do not retain references to the
|
|
314
|
+
* browser past the callback either way.
|
|
310
315
|
*/
|
|
311
316
|
launch: <T>(function_: (browser: BrowserLike) => Promise<T>, options?: {
|
|
312
317
|
keepAlive?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -304,9 +304,14 @@ interface Browser {
|
|
|
304
304
|
* `fn` (e.g. for multi-page flows or APIs not surfaced here).
|
|
305
305
|
*
|
|
306
306
|
* The browser is **always closed** when `fn` resolves or throws — unless
|
|
307
|
-
* `keepAlive` is
|
|
308
|
-
* a later {@link Browser.connect} can
|
|
309
|
-
*
|
|
307
|
+
* `keepAlive` is a number of seconds **between 10 and 600**, which holds the
|
|
308
|
+
* session open for that long so a later {@link Browser.connect} can
|
|
309
|
+
* re-attach. `0`, a negative value and `NaN` all mean "do not keep alive"
|
|
310
|
+
* and take the always-close path (a held session is billed, so the
|
|
311
|
+
* ambiguous values fall to the safe side); a positive value outside the
|
|
312
|
+
* 10–600s window Browser Rendering accepts throws `BAD_REQUEST` rather than
|
|
313
|
+
* being sent on for the provider to refuse. Do not retain references to the
|
|
314
|
+
* browser past the callback either way.
|
|
310
315
|
*/
|
|
311
316
|
launch: <T>(function_: (browser: BrowserLike) => Promise<T>, options?: {
|
|
312
317
|
keepAlive?: number;
|
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-CN6x72HL.mjs";export{o as createBrowser};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";const x=/^\d{1,3}$/u,C=/^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u,F=/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/u,X=/^::(\d{1,3}(?:\.\d{1,3}){3})$/u,Q=/^::([\da-f]{1,4}):([\da-f]{1,4})$/u,z=/^64:ff9b::[\da-f]{1,4}:[\da-f]{1,4}$/u,G=/^\[|\]$/gu,j=/\.$/u,v=t=>{const e=t.split(".");if(e.length!==4)return;const r=e.map(n=>x.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])},L=t=>{const e=t.toLowerCase(),r=C.exec(e);if(r)return D(r[1],r[2]);const n=F.exec(e);if(n){const f=v(n[1]??"");return f===void 0||y(f)}const i=X.exec(e);if(i){const f=v(i[1]??"");return f===void 0||y(f)}const c=Q.exec(e);return c?D(c[1],c[2]):z.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(G,"").replace(j,"").toLowerCase(),O=t=>{const e=h(t);if(e.includes(":"))return L(e);const r=v(e);return r===void 0?q(e):y(r)},Y="https://cloudflare-dns.com/dns-query",T=1,N=28,J=2e3,Z=(t,e)=>{if(e===T){const r=v(t);return r===void 0||y(r)}return L(t.toLowerCase())},S=async(t,e,r)=>{try{const n=await fetch(`${Y}?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,i]=await Promise.all([S(r,T,e),S(r,N,e)]);if(n===void 0&&i===void 0)return{kind:"unknown"};for(const c of[...n??[],...i??[]])if((c.type===T||c.type===N)&&Z(c.data,c.type))return{address:c.data,kind:"private"};return{kind:"public"}},A=3e4,tt=12e4,et=3840,rt=4320,R=10,M=600,U=5e3,$=async(t,e=U)=>{const r=h(new URL(t).hostname),n=await K(r,e);if(n.kind==="private")throw new u("FORBIDDEN",`@lunora/browser: url host "${r}" resolves to a private/internal address (${n.address}); refusing to navigate (DNS-rebinding guard)`)},B=(t,e,r)=>{if(typeof t!="string"||t.length===0)throw new u("BAD_REQUEST","@lunora/browser: url must be a non-empty string");let n;try{n=new URL(t)}catch{throw new u("BAD_REQUEST",`@lunora/browser: url must be an absolute http(s) URL (got "${t}")`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new u("BAD_REQUEST",`@lunora/browser: url protocol must be http(s) (got "${n.protocol}")`);if(n.username!==""||n.password!=="")throw new u("BAD_REQUEST","@lunora/browser: url must not embed credentials (strip the `user:pass@` userinfo)");if(r&&r.length>0){const i=h(n.hostname);if(!r.some(c=>h(c)===i))throw new u("FORBIDDEN",`@lunora/browser: url host "${n.hostname}" is not in the configured allowedHosts allowlist`)}if(!e&&O(n.hostname))throw new u("FORBIDDEN",`@lunora/browser: url host "${n.hostname}" is a private/internal address; pass createBrowser({ …, allowPrivateTargets: true }) to allow it`);return n.toString()},H=(t,e)=>Number.isFinite(t)?Math.min(Math.max(1,Math.floor(t)),e):e,nt=t=>({height:H(t.height,rt),width:H(t.width,et)}),ot=(t,e)=>{const r=t??e??A,n=Number.isFinite(r)?r:A;return Math.min(Math.max(1,Math.floor(n)),tt)},st=async(t,e)=>{let r;try{return await Promise.race([t(),new Promise((n,i)=>{r=setTimeout(()=>{i(new u("BROWSER_TIMEOUT",`@lunora/browser: navigation + operation exceeded the ${String(e)}ms timeout budget`,{status:504}))},e)})])}finally{r!==void 0&&clearTimeout(r)}},k=async t=>{try{await t.close()}catch{}},ht=t=>{if(!t.binding)throw new TypeError("@lunora/browser: `binding` is required (env.BROWSER)");const e=()=>{if(!t.launch)throw new u("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=(s,o)=>{if(!s)throw new u("INTERNAL",`@lunora/browser: \`${o}\` is not available — install the \`@cloudflare/playwright\` peer dependency. The generated worker wires it for you; outside codegen pass it via createBrowser({ binding, ${o} }).`);return s},n=async(s,o)=>{const a=o!==void 0&&Number.isFinite(o)&&o>0?o:void 0;if(a!==void 0&&(a<R||a>M))throw new u("BAD_REQUEST",`@lunora/browser: keepAlive must be between ${String(R)} and ${String(M)} seconds (Browser Rendering accepts keep_alive from 10s to 10min; got ${String(o)})`);const l=await e()(t.binding,a===void 0?void 0:{keep_alive:a*1e3});if(a!==void 0)return await s(l);try{return await s(l)}finally{await k(l)}},i=async(s,o,a,l)=>{const p=t.allowPrivateTargets??!1,_=B(s,p,t.allowedHosts),E=ot(o.timeoutMs,t.timeoutMs),I=t.resolveDns??(t.allowedHosts?.length??0)===0,P=Math.min(E,U);!p&&I&&await $(_,P);const W=async m=>{B(m,p,t.allowedHosts),!p&&I&&await $(m,P)},V=m=>{let g;try{g=new URL(m)}catch{return!1}if(g.protocol!=="http:"&&g.protocol!=="https:")return!1;if(t.allowedHosts&&t.allowedHosts.length>0){const d=h(g.hostname);if(!t.allowedHosts.some(w=>h(w)===d))return!0}return O(g.hostname)};return n(async m=>{const d=await(await m.newContext()).newPage();return d.route&&(!p||(t.allowedHosts?.length??0)>0)&&await d.route("**/*",async w=>{const b=w.request();if(!(b.isNavigationRequest?.()??!0)){if(V(b.url())){await w.abort("blockedbyclient");return}await w.continue();return}try{await W(b.url())}catch{await w.abort("blockedbyclient");return}await w.continue()}),l&&d.setViewportSize&&await d.setViewportSize(nt(l)),st(async()=>(await d.goto(_,{timeout:E,waitUntil:o.waitUntil??"load"}),a(d)),E)})};return{connect:async(s,o,a={})=>{const l=await r(t.connect,"connect")(t.binding,s);if(a.close!==!0)return await o(l);try{return await o(l)}finally{await k(l)}},content:async(s,o={})=>i(s,o,async a=>a.content()),launch:async(s,o={})=>n(s,o.keepAlive),pdf:async(s,o={})=>i(s,o,async a=>a.pdf({format:o.format,printBackground:o.printBackground??!1}),o.viewport),scrape:async(s,o,a={})=>i(s,a,async l=>l.evaluate(o)),screenshot:async(s,o={})=>i(s,o,async a=>a.screenshot({fullPage:o.fullPage??!1,type:o.type??"png"}),o.viewport),sessions:async()=>await r(t.sessions,"sessions")(t.binding)}};export{ht as createBrowser};
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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};
|