@stacksjs/rpx 0.11.16 → 0.11.17
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/bin/cli.js +1 -1
- package/dist/{chunk-1sg87m7v.js → chunk-fndafyac.js} +1 -1
- package/dist/{chunk-n3etse5z.js → chunk-zx2ghrc1.js} +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -6
- package/dist/origin-guard.d.ts +12 -0
- package/package.json +1 -1
- package/src/https.ts +2 -2
- package/src/index.ts +3 -0
- package/src/origin-guard.ts +105 -0
package/dist/bin/cli.js
CHANGED
|
@@ -200,7 +200,7 @@ $store.Open("ReadWrite")
|
|
|
200
200
|
$store.Add($cert)
|
|
201
201
|
$store.Close()
|
|
202
202
|
Write-Host "Root CA trusted successfully!"
|
|
203
|
-
`,j=k$(WJ.tmpdir(),"rpx-trust.ps1");if(await q$.writeFile(j,E),I1(`powershell -ExecutionPolicy Bypass -File "${j}"`),$.verbose)q.success("Successfully added certificate to Windows trust store");B=!0}catch(E){if($.verbose)q.warn(`Could not trust certificate: ${E}`)}else try{await EU(W,X.certificate,A),B=!0}catch(E){if($.verbose)q.warn(`Could not add certificate to trust store: ${E}`)}if(Ew={key:W.privateKey,cert:W.certificate,ca:X.certificate},$.verbose)q.success(`Certificate generated successfully for ${w.length} domain${w.length>1?"s":""}`);if(!B&&$.verbose)q.warn('If you see certificate warnings in Chrome/Arc, type "thisisunsafe" on the warning page'),q.warn("This will bypass the warning and you should only need to do it once")}function H7(){Ew=null}async function p0($){if(!$)return null;if(Ew)return Ew;let w=wA($);try{let[A,Y,f]=await Promise.all([q$.access(w.keyPath).then(()=>!0).catch(()=>!1),q$.access(w.certPath).then(()=>!0).catch(()=>!1),w.caCertPath?q$.access(w.caCertPath).then(()=>!0).catch(()=>!1):Promise.resolve(!1)]);if(!A||!Y)return z("ssl",`Certificate files don't exist: key=${A}, cert=${Y}, paths: ${w.keyPath}, ${w.certPath}`,$.verbose),null;let X="regenerateUntrustedCerts"in $,U=$.regenerateUntrustedCerts,W=X?U!==!1:!0;z("ssl",`Trust check: hasFlag=${X}, flagValue=${U}, shouldCheckTrust=${W}`,$.verbose);let J=w.basePath||k$(xf(),".stacks","ssl"),B=dT(J);if(!(W?await tT(B.caCertPath,$):!0))return z("ssl","Root CA exists but is not trusted, will regenerate",$.verbose),null;let[j,T,F]=await Promise.all([q$.readFile(w.keyPath,"utf8"),q$.readFile(w.certPath,"utf8"),f&&w.caCertPath?q$.readFile(w.caCertPath,"utf8"):Promise.resolve(void 0)]);if(F&&!F.includes("-----BEGIN CERTIFICATE-----"))return z("ssl","Invalid root CA certificate content, will regenerate",$.verbose),null;if(Iw($))try{let{X509Certificate:H}=await import("node:crypto"),K=new H(T).subjectAltName||"",h=$.proxies.map((N)=>N.to).filter((N)=>!K.includes(`DNS:${N}`));if(h.length>0)return z("ssl",`Certificate missing SANs for: ${h.join(", ")}, will regenerate`,$.verbose),null}catch(H){z("ssl",`Could not verify cert SANs: ${H}`,$.verbose)}return z("ssl","Successfully loaded existing certificates",$.verbose),Ew={key:j,cert:T,ca:F},Ew}catch(A){return z("ssl",`Error checking existing certificates: ${A}`,$.verbose),null}}function wA($,w){let A=_f($);z("ssl",`Primary domain: ${A}`,w);let Y=lT($),f=k$(xf(),".stacks","ssl");if(typeof $.https==="object"){let X=$.https.basePath&&$.https.basePath.trim()!==""?$.https.basePath:f,U={domain:A,hostCertCN:A,basePath:X,caCertPath:$.https.caCertPath||Y.caCertPath,certPath:$.https.certPath||Y.certPath,keyPath:$.https.keyPath||Y.keyPath,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:$.https.commonName||A,organizationName:$.https.organizationName||"Local Development",countryName:$.https.countryName||"US",stateName:$.https.stateName||"California",localityName:$.https.localityName||"Playa Vista",validityDays:$.https.validityDays||825,verbose:w||!1,subjectAltNames:Array.from(mT($)).map((W)=>({type:2,value:W}))};if($J($.https.rootCA))U.rootCA=$.https.rootCA;return U}return{domain:A,hostCertCN:A,basePath:f,...Y,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:A,organizationName:"Local Development",countryName:"US",stateName:"California",localityName:"Playa Vista",validityDays:825,verbose:w||!1,subjectAltNames:Array.from(mT($)).map((X)=>({type:2,value:X}))}}async function gT($,w){let A=lT({to:$,verbose:w}),Y=[A.caCertPath,A.certPath,A.keyPath];z("certificates","Attempting to clean up relating certificates",w),await Promise.all(Y.map((f)=>AJ(f,w)))}async function tT($,w){try{if(z("ssl",`Checking if certificate is trusted: ${$}`,w?.verbose),Bw.platform==="darwin"){if(w?.serverName)return UJ($,w.serverName,{verbose:w.verbose});return Zf($,{verbose:w.verbose})}else if(Bw.platform==="win32")try{let Y=I1(`openssl x509 -noout -subject -in "${$}"`).toString().trim().split("=").slice(1).join("=").trim()||"";if(!Y)return z("ssl","Could not extract certificate subject",w?.verbose),!1;let f=`powershell -Command "Get-ChildItem -Path Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -like '*${Y}*' } | Select-Object Subject"`;if(I1(f).toString().includes(Y))return z("ssl","Certificate found in trusted root store",w?.verbose),!0;return z("ssl","Certificate not found in trusted root store",w?.verbose),!1}catch(A){return z("ssl",`Error checking certificate trust on Windows: ${A}`,w?.verbose),!1}else if(Bw.platform==="linux")try{let Y=I1(`openssl x509 -noout -fingerprint -sha256 -in "${$}"`).toString().trim().split("=")[1]?.trim()||"",f=["/etc/ssl/certs","/etc/pki/tls/certs"];for(let X of f)try{if(I1(`find ${X} -type f -exec openssl x509 -noout -fingerprint -sha256 -in {} \\; 2>/dev/null | grep "${Y}"`).toString().includes(Y))return z("ssl",`Certificate fingerprint found in ${X}`,w?.verbose),!0}catch{}return z("ssl","Certificate not found in system trust stores",w?.verbose),!1}catch(A){return z("ssl",`Error checking certificate trust on Linux: ${A}`,w?.verbose),!1}return z("ssl",`Platform ${Bw.platform} not supported for certificate trust check`,w?.verbose),!1}catch(A){return z("ssl",`Error checking if certificate is trusted: ${A}`,w?.verbose),!1}}var Ew=null,F7="rpx-root-ca.crt",z7="rpx-root-ca.key";var Pf=E$(()=>{jU();Aw();GU();JJ();U$();JJ();HU()});import*as B0 from"node:path";function AA($,w){if(typeof $==="string")return{dir:$,spa:!1,pathRewriteStyle:"directory",maxAge:0,cleanUrls:w};return{dir:$.dir,spa:$.spa??!1,pathRewriteStyle:$.pathRewriteStyle??"directory",maxAge:$.maxAge??0,cleanUrls:w}}function R7($){let w=B0.extname($).toLowerCase();return K7[w]??"application/octet-stream"}function h7($){let w;try{w=decodeURIComponent($)}catch{return null}if(w.includes("\x00")||w.includes("\\"))return null;let A=B0.posix.normalize(`/${w}`);if(A.includes(".."))return null;return A.replace(/^\/+/,"")}function q7($,w){let A=h7($);if(A===null)return null;let Y=B0.posix.extname(A);if(w.cleanUrls&&Y===".html"){let f=$.replace(/\/index\.html$/i,"/").replace(/\.html$/i,"");return{filePath:B0.join(w.dir,A),redirectTo:f||"/"}}if(A===""||$.endsWith("/"))return{filePath:B0.join(w.dir,A,"index.html")};if(Y!=="")return{filePath:B0.join(w.dir,A)};if(w.pathRewriteStyle==="flat")return{filePath:B0.join(w.dir,`${A}.html`)};return{filePath:B0.join(w.dir,A,"index.html")}}async function pT($,w){let A=q7($,w);if(!A)return new Response("Forbidden",{status:403});if(A.redirectTo)return new Response(null,{status:301,headers:{Location:A.redirectTo}});let Y=w.maxAge>0?`public, max-age=${w.maxAge}`:"no-cache",f=Bun.file(A.filePath);if(await f.exists())return new Response(f,{status:200,headers:{"Content-Type":R7(A.filePath),"Cache-Control":Y}});if(w.spa){let X=B0.join(w.dir,"index.html"),U=Bun.file(X);if(await U.exists())return new Response(U,{status:200,headers:{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache"}})}return new Response("Not Found",{status:404})}var K7;var YA=E$(()=>{K7={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".eot":"application/vnd.ms-fontobject",".txt":"text/plain; charset=utf-8",".xml":"application/xml; charset=utf-8",".pdf":"application/pdf",".wasm":"application/wasm",".mp4":"video/mp4",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav"}});function y7($){return($.headers.get("host")||"").split(":")[0]}function aT($,w){if(!w||w==="/")return $;if($===w)return"/";if($.startsWith(`${w}/`)){let A=$.slice(w.length);return A===""?"/":A}return $}function rT($,w,A){let Y=new URL($.url),f=w.sourceHost??"",U=w.stripBasePathPrefix??!1?aT(Y.pathname,w.basePath):Y.pathname,W=wJ(U,w.pathRewrites);if(W)f=W.targetHost,U=W.targetPath,z("request",`Path rewrite: ${Y.pathname} → ${f}${U}`,A);return{targetHost:f,targetPath:U,search:Y.search}}function fA($,w){return async(A,Y)=>{let f=new URL(A.url),X=y7(A),U=$(X,f.pathname);if(!U)return z("request",`No route found for host: ${X}`,w),new Response(`No proxy configured for ${X}`,{status:404});if(U.static){let T=U.stripBasePathPrefix??!0?aT(f.pathname,U.basePath):f.pathname;return pT(T,U.static)}if(A.headers.get("upgrade")?.toLowerCase()==="websocket"){if(!Y||!U.sourceHost)return new Response("WebSocket upgrade not supported here",{status:400});let{targetHost:j,targetPath:T,search:F}=rT(A,U,w),H=`ws://${j}${T}${F}`,G={};for(let[h,N]of A.headers)if(!N7.has(h.toLowerCase())&&h.toLowerCase()!=="host")G[h]=N;G.host=j,G["x-forwarded-for"]="127.0.0.1",G["x-forwarded-proto"]="https",G["x-forwarded-host"]=X;let K={targetUrl:H,forwardHeaders:G};if(Y.upgrade(A,{data:K})){z("ws",`upgraded ${X}${T} → ${H}`,w);return}return new Response("WebSocket upgrade failed",{status:400})}if(!U.sourceHost)return new Response(`No upstream configured for ${X}`,{status:502});let{targetHost:W,targetPath:J,search:B}=rT(A,U,w),E=`http://${W}${J}${B}`;try{let j=new Headers(A.headers);if(j.set("host",W),U.changeOrigin)j.set("origin",`http://${U.sourceHost}`);j.set("x-forwarded-for","127.0.0.1"),j.set("x-forwarded-proto","https"),j.set("x-forwarded-host",X);let T=await fetch(E,{method:A.method,headers:j,body:A.body,redirect:"manual"});if(U.cleanUrls&&f.pathname.endsWith(".html")){let H=f.pathname.replace(/\.html$/,"");return new Response(null,{status:301,headers:{Location:H}})}let F=new Headers(T.headers);return new Response(T.body,{status:T.status,statusText:T.statusText,headers:F})}catch(j){return z("request",`Proxy error for ${X}: ${j}`,w),new Response(`Proxy Error: ${j}`,{status:502})}}}function XA($){let w=new WeakMap;return{open(A){let{targetUrl:Y,forwardHeaders:f}=A.data,X;try{X=new WebSocket(Y,{headers:f})}catch(W){z("ws",`failed to open upstream ${Y}: ${W}`,$),A.close(1011,"upstream connect failed");return}X.binaryType="arraybuffer";let U={upstream:X,upstreamOpen:!1,pending:[]};w.set(A,U),X.addEventListener("open",()=>{U.upstreamOpen=!0;for(let W of U.pending)X.send(W);U.pending=[]}),X.addEventListener("message",(W)=>{A.send(W.data)}),X.addEventListener("close",(W)=>{try{A.close(W.code||1000,W.reason||"")}catch{}}),X.addEventListener("error",()=>{z("ws",`upstream error for ${Y}`,$);try{A.close(1011,"upstream error")}catch{}})},message(A,Y){let f=w.get(A);if(!f)return;let X=typeof Y==="string"?Y:new Uint8Array(Y);if(f.upstreamOpen)f.upstream.send(X);else f.pending.push(X)},close(A,Y,f){let X=w.get(A);if(!X)return;w.delete(A);try{X.upstream.close(Y||1000,f||"")}catch{}}}}var N7;var bf=E$(()=>{YA();U$();N7=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade","sec-websocket-key","sec-websocket-version","sec-websocket-extensions"])});function UA($){return $.startsWith("*.")}function sT($,w){if(!UA(w))return!1;let A=w.slice(1);return $.length>A.length&&$.endsWith(A)}function S1($){if(!$||$==="/")return"/";let w=$.trim();if(!w.startsWith("/"))w=`/${w}`;return w=w.replace(/\/+$/,""),w===""?"/":w}function I7($,w){if(w==="/")return!0;if($===w)return!0;return $.startsWith(`${w}/`)}function JA($){let w=new Map;for(let Y of $){let f=S1(Y.path),X=w.get(Y.host);if(!X)X=new Map,w.set(Y.host,X);X.set(f,Y.route)}let A=new Map;for(let[Y,f]of w){let X=[];for(let[U,W]of f)X.push({path:U,route:W});X.sort((U,W)=>W.path.length-U.path.length),A.set(Y,X)}return A}function C7($,w){let A=$.get(w);if(A!==void 0)return A;let Y,f=-1;for(let[X,U]of $){if(!UA(X))continue;if(sT(w,X)){let W=X.length-1;if(W>f)f=W,Y=U}}return Y}function WA($,w,A){let Y=C7($,w);if(!Y)return;for(let f of Y)if(I7(A,f.path))return f.route;return}var cf=()=>{};import*as BA from"node:fs/promises";import*as BJ from"node:path";function S7($){if(!$.endsWith(".crt"))return null;let w=$.slice(0,-4);if(w.length===0)return null;if(w.startsWith("_wildcard."))return`*.${w.slice(10)}`;return w}async function L7($,w,A,Y){try{let[f,X]=await Promise.all([BA.readFile(w,"utf8"),BA.readFile(A,"utf8")]);return{serverName:$,cert:f,key:X}}catch(f){return z("sni",`skipping ${$}: ${f.message}`,Y),null}}async function EJ($,w){let A=new Map;if($.certsDir){let f=[];try{f=await BA.readdir($.certsDir)}catch(X){z("sni",`certsDir read failed (${$.certsDir}): ${X.message}`,w)}for(let X of f){let U=S7(X);if(!U)continue;let W=X.slice(0,-4);A.set(U,{certPath:BJ.join($.certsDir,X),keyPath:BJ.join($.certsDir,`${W}.key`)})}}if($.domains)for(let[f,X]of Object.entries($.domains))A.set(f,X);let Y=[];for(let[f,X]of A){let U=await L7(f,X.certPath,X.keyPath,w);if(U)Y.push(U)}return Y}var jJ=E$(()=>{U$()});import*as jw from"node:fs/promises";import*as TJ from"node:path";function V7($,w){if(!w||w.length===0)return!1;return w.some((A)=>{let Y=A.startsWith(".")?A.slice(1):A;return $===Y||$.endsWith(`.${Y}`)})}function Q7($){if(!$||$.length>253)return!1;if($.includes("/")||$.includes(":")||$.includes(" "))return!1;if($.startsWith("*"))return!1;return/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test($)}class uf{config;certsDir;onCertAdded;http01Store;issuer;verbose;negativeCacheMs;certs=new Map;inFlight=new Map;negativeCache=new Map;constructor($){this.config=$.config,this.certsDir=$.certsDir,this.onCertAdded=$.onCertAdded,this.http01Store=$.http01Store??pY,this.issuer=$.issuer??p8,this.verbose=$.verbose??!1,this.negativeCacheMs=$.negativeCacheMs??O7;for(let w of $.initial??[])this.certs.set(w.serverName,w)}get challengeStore(){return this.http01Store}sniEntries(){return Array.from(this.certs.values())}hasCert($){return this.certs.has($)}async isApproved($){if(!Q7($))return!1;if(V7($,this.config.allowedSuffixes))return!0;if(this.config.ask)try{return await this.config.ask($)}catch(w){return z("on-demand",`ask(${$}) threw: ${w.message}`,this.verbose),!1}return!1}async ensureCert($){if(!this.config.enabled)return!1;if(this.certs.has($))return!0;let w=this.inFlight.get($);if(w)return w;let A=this.negativeCache.get($);if(A!==void 0&&Date.now()<A)return z("on-demand",`${$} negatively cached for ${A-Date.now()}ms`,this.verbose),!1;let Y=this.issue($).finally(()=>{this.inFlight.delete($)});return this.inFlight.set($,Y),Y}async issue($){if(this.certs.has($))return!0;if(await this.loadFromDisk($))return!0;if(!await this.isApproved($))return z("on-demand",`refused issuance for ${$} (not approved)`,this.verbose),!1;try{z("on-demand",`issuing cert for ${$} (staging=${this.config.staging??!1})`,this.verbose);let w=await this.issuer({domains:[$],method:"http-01",http01Store:this.http01Store,email:this.config.email,staging:this.config.staging});await this.persist($,w.fullChainPem,w.keyPem);let A={serverName:$,cert:w.fullChainPem,key:w.keyPem};return this.certs.set($,A),this.negativeCache.delete($),z("on-demand",`issued + installed cert for ${$}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch(w){return this.negativeCache.set($,Date.now()+this.negativeCacheMs),z("on-demand",`issuance for ${$} failed: ${w.message}`,this.verbose),!1}}async loadFromDisk($){let{certPath:w,keyPath:A}=this.pathsFor($);try{let[Y,f]=await Promise.all([jw.readFile(w,"utf8"),jw.readFile(A,"utf8")]),X={serverName:$,cert:Y,key:f};return this.certs.set($,X),z("on-demand",`adopted existing on-disk cert for ${$}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch{return!1}}pathsFor($){return{certPath:TJ.join(this.certsDir,`${$}.crt`),keyPath:TJ.join(this.certsDir,`${$}.key`)}}async persist($,w,A){await jw.mkdir(this.certsDir,{recursive:!0}).catch(()=>{});let{certPath:Y,keyPath:f}=this.pathsFor($);await Promise.all([jw.writeFile(Y,w,"utf8"),jw.writeFile(f,A,{encoding:"utf8",mode:384})])}}var O7=60000;var FJ=E$(()=>{jU();U$()});import*as nf from"node:fs";import*as n$ from"node:fs/promises";import{homedir as _7}from"node:os";import*as zJ from"node:path";import*as vf from"node:process";function Tw(){return zJ.join(_7(),".stacks","rpx","registry.d")}function EA($){return typeof $==="string"&&$.length>0&&$.length<=128&&D7.test($)}function GJ($,w){if(!EA(w))throw Error(`invalid registry id: ${JSON.stringify(w)}`);return zJ.join($,`${w}.json`)}function l$($){if(!Number.isInteger($)||$<=0)return!1;try{return vf.kill($,0),!0}catch(w){return w.code==="EPERM"}}function oT($){if(!$||typeof $!=="object")return!1;let w=$,A=w.pid===void 0||typeof w.pid==="number"&&Number.isInteger(w.pid)&&w.pid>0,Y=typeof w.from==="string"&&w.from.length>0,f=typeof w.static==="string"||!!w.static&&typeof w.static==="object"&&typeof w.static.dir==="string",X=w.path===void 0||typeof w.path==="string";return typeof w.id==="string"&&EA(w.id)&&(Y||f)&&typeof w.to==="string"&&w.to.length>0&&X&&A&&typeof w.createdAt==="string"}async function k7($){await n$.mkdir($,{recursive:!0})}async function eT($,w=Tw(),A){if(!oT($))throw Error(`invalid registry entry: ${JSON.stringify($)}`);await k7(w);let Y=GJ(w,$.id),f=`${Y}.tmp.${vf.pid}.${Date.now()}`,X=JSON.stringify($,null,2);try{await n$.writeFile(f,X,{encoding:"utf8",mode:420}),await n$.rename(f,Y),z("registry",`wrote entry ${$.id} → ${Y}`,A)}catch(U){throw await n$.unlink(f).catch(()=>{}),U}}async function HJ($,w=Tw(),A){let Y=GJ(w,$);try{await n$.unlink(Y),z("registry",`removed entry ${$}`,A)}catch(f){if(f.code!=="ENOENT")throw f}}async function Z7($,w=Tw(),A){let Y=GJ(w,$);try{let f=await n$.readFile(Y,"utf8"),X=JSON.parse(f);if(!oT(X))return z("registry",`entry ${$} failed validation, removing`,A),await n$.unlink(Y).catch(()=>{}),null;return X}catch(f){if(f.code==="ENOENT")return null;if(f instanceof SyntaxError)return z("registry",`entry ${$} has invalid JSON, removing`,A),await n$.unlink(Y).catch(()=>{}),null;throw f}}async function Cw($=Tw(),w){let A;try{A=await n$.readdir($)}catch(f){if(f.code==="ENOENT")return[];throw f}let Y=[];for(let f of A){if(!f.endsWith(".json"))continue;let X=f.slice(0,-5);if(!EA(X))continue;let U=await Z7(X,$,w);if(U)Y.push(U)}return Y}async function mf($=Tw(),w){let A=await Cw($,w),Y=0;for(let f of A){if(f.pid===void 0)continue;if(!l$(f.pid))z("registry",`GC: pid ${f.pid} for ${f.id} is dead, removing`,w),await HJ(f.id,$,w).catch(()=>{}),Y++}return Y}function KJ($,w={}){let A=w.dir??Tw(),Y=w.debounceMs??100,f=w.pollMs??Math.max(Y*2,250),X=w.verbose;nf.mkdirSync(A,{recursive:!0});let U=null,W=!1,J=null,B=!1,E=(K)=>{return JSON.stringify(K.map((R)=>({id:R.id,from:R.from,to:R.to,path:R.path,pid:R.pid,pathRewrites:R.pathRewrites,cleanUrls:R.cleanUrls,changeOrigin:R.changeOrigin,static:R.static})).sort((R,h)=>R.id.localeCompare(h.id)))},j=()=>{if(U=null,W)return;Cw(A,X).then((K)=>{return J=E(K),$(K)}).catch((K)=>{z("registry",`watcher onChange failed: ${K}`,X)})},T=()=>{if(W)return;if(U)clearTimeout(U);U=setTimeout(j,Y)},H=setInterval(()=>{if(W||B)return;B=!0,Cw(A,X).then((K)=>{if(E(K)!==J)T()}).catch((K)=>{z("registry",`watcher poll failed: ${K}`,X)}).finally(()=>{B=!1})},f),G=nf.watch(A,{persistent:!0},(K,R)=>{if(R&&/\.tmp\.\d+\.\d+$/.test(R))return;T()});return G.on("error",(K)=>{z("registry",`watcher error: ${K}`,X)}),T(),{close:()=>{if(W=!0,U)clearTimeout(U);clearInterval(H),G.close()}}}var D7;var jA=E$(()=>{U$();D7=/^[a-zA-Z0-9._-]+$/});import{spawn as x7}from"node:child_process";import*as Sw from"node:fs/promises";import{homedir as P7}from"node:os";import*as TA from"node:path";import*as v$ from"node:process";function r0(){return TA.join(P7(),".stacks","rpx")}function wF($=r0()){return TA.join($,"daemon.pid")}async function $F($=r0()){try{let w=await Sw.readFile(wF($),"utf8"),A=Number.parseInt(w.trim(),10);if(!Number.isFinite(A)||A<=0)return null;return A}catch(w){if(w.code==="ENOENT")return null;throw w}}async function i7($=r0()){await Sw.unlink(wF($)).catch(()=>{})}function b7(){let $=v$.execPath,w=TA.basename($).toLowerCase();if((w==="bun"||w==="node"||w.startsWith("bun-"))&&v$.argv[1])return[$,v$.argv[1],"daemon:start"];return[$,"daemon:start"]}async function AF($={}){let w=$.rpxDir??r0(),A=$.verbose??!1;await Fw({rpxDir:w,verbose:A}).catch((E)=>{z("daemon",`DNS reconcile before ensureDaemonRunning: ${E}`,A)});let Y=await $F(w);if(Y!==null&&l$(Y))return z("daemon",`ensureDaemonRunning: already running pid=${Y}`,A),{pid:Y,spawned:!1};if(Y!==null)z("daemon",`ensureDaemonRunning: clearing stale pid=${Y}`,A),await i7(w);await Sw.mkdir(w,{recursive:!0});let f=$.spawnCommand??b7();if(f.length===0)throw Error("ensureDaemonRunning: spawnCommand is empty");z("daemon",`spawning daemon: ${f.join(" ")}`,A);let X=x7(f[0],f.slice(1),{detached:!0,stdio:"ignore",cwd:$.spawnCwd??v$.cwd(),env:$.spawnEnv?{...v$.env,...$.spawnEnv}:v$.env});X.unref();let U=null;X.once("error",(E)=>{U=E});let W=$.startupTimeoutMs??5000,J=$.pollIntervalMs??50,B=Date.now()+W;while(Date.now()<B){if(U)throw U;let E=await $F(w);if(E!==null&&l$(E))return z("daemon",`daemon registered with pid=${E}`,A),{pid:E,spawned:!0};await new Promise((j)=>setTimeout(j,J))}if(U)throw U;throw Error(`rpx daemon failed to start within ${W}ms (rpxDir=${w})`)}var RJ=E$(()=>{Aw();Pf();bf();cf();jJ();FJ();YA();jA();L1();U$()});import*as zw from"node:fs/promises";import{homedir as c7}from"node:os";import*as hJ from"node:path";function fF(){return hJ.join(c7(),".stacks","rpx")}function qJ($=fF()){return hJ.join($,u7)}async function lf($=fF()){try{let w=await zw.readFile(qJ($),"utf8"),A=JSON.parse(w);if(A.version!==df||!Array.isArray(A.resolvers))return null;return{version:df,resolvers:A.resolvers.filter((Y)=>typeof Y==="string"),domains:Array.isArray(A.domains)?A.domains.filter((Y)=>typeof Y==="string"):[],ownerPid:typeof A.ownerPid==="number"?A.ownerPid:null,updatedAt:typeof A.updatedAt==="string"?A.updatedAt:""}}catch(w){if(w.code==="ENOENT")return null;throw w}}async function XF($,w){await zw.mkdir($,{recursive:!0}),await zw.writeFile(qJ($),`${JSON.stringify(w,null,2)}
|
|
203
|
+
`,j=k$(WJ.tmpdir(),"rpx-trust.ps1");if(await q$.writeFile(j,E),I1(`powershell -ExecutionPolicy Bypass -File "${j}"`),$.verbose)q.success("Successfully added certificate to Windows trust store");B=!0}catch(E){if($.verbose)q.warn(`Could not trust certificate: ${E}`)}else try{await EU(W,X.certificate,A),B=!0}catch(E){if($.verbose)q.warn(`Could not add certificate to trust store: ${E}`)}if(Ew={key:W.privateKey,cert:W.certificate,ca:X.certificate},$.verbose)q.success(`Certificate generated successfully for ${w.length} domain${w.length>1?"s":""}`);if(!B&&$.verbose)q.warn('If you see certificate warnings in Chrome/Arc, type "thisisunsafe" on the warning page'),q.warn("This will bypass the warning and you should only need to do it once")}function H7(){Ew=null}async function p0($){if(!$)return null;if(Ew)return Ew;let w=wA($);try{let[A,Y,f]=await Promise.all([q$.access(w.keyPath).then(()=>!0).catch(()=>!1),q$.access(w.certPath).then(()=>!0).catch(()=>!1),w.caCertPath?q$.access(w.caCertPath).then(()=>!0).catch(()=>!1):Promise.resolve(!1)]);if(!A||!Y)return z("ssl",`Certificate files don't exist: key=${A}, cert=${Y}, paths: ${w.keyPath}, ${w.certPath}`,$.verbose),null;let X="regenerateUntrustedCerts"in $,U=$.regenerateUntrustedCerts,W=X?U!==!1:!0;z("ssl",`Trust check: hasFlag=${X}, flagValue=${U}, shouldCheckTrust=${W}`,$.verbose);let J=w.basePath||k$(xf(),".stacks","ssl"),B=dT(J);if(!(W?await tT(B.caCertPath,$):!0))return z("ssl","Root CA exists but is not trusted, will regenerate",$.verbose),null;let[j,T,F]=await Promise.all([q$.readFile(w.keyPath,"utf8"),q$.readFile(w.certPath,"utf8"),f&&w.caCertPath?q$.readFile(w.caCertPath,"utf8"):Promise.resolve(void 0)]);if(F&&!F.includes("-----BEGIN CERTIFICATE-----"))return z("ssl","Invalid root CA certificate content, will regenerate",$.verbose),null;if(Iw($))try{let{X509Certificate:H}=await import("node:crypto"),K=new H(T).subjectAltName||"",h=$.proxies.map((N)=>N.to).filter((N)=>!K.includes(`DNS:${N}`));if(h.length>0)return z("ssl",`Certificate missing SANs for: ${h.join(", ")}, will regenerate`,$.verbose),null}catch(H){z("ssl",`Could not verify cert SANs: ${H}`,$.verbose)}return z("ssl","Successfully loaded existing certificates",$.verbose),Ew={key:j,cert:T,ca:F},Ew}catch(A){return z("ssl",`Error checking existing certificates: ${A}`,$.verbose),null}}function wA($,w){let A=_f($);z("ssl",`Primary domain: ${A}`,w);let Y=lT($),f=k$(xf(),".stacks","ssl");if(typeof $.https==="object"){let X=$.https.basePath&&$.https.basePath.trim()!==""?$.https.basePath:f,U={domain:A,hostCertCN:A,basePath:X,caCertPath:$.https.caCertPath||Y.caCertPath,certPath:$.https.certPath||Y.certPath,keyPath:$.https.keyPath||Y.keyPath,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:$.https.commonName||A,organizationName:$.https.organizationName||"Local Development",countryName:$.https.countryName||"US",stateName:$.https.stateName||"California",localityName:$.https.localityName||"Playa Vista",validityDays:$.https.validityDays||825,verbose:w||!1,subjectAltNames:Array.from(mT($)).map((W)=>({type:2,value:W}))};if($J($.https.rootCA))U.rootCA=$.https.rootCA;return U}return{domain:A,hostCertCN:A,basePath:f,...Y,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:A,organizationName:"Local Development",countryName:"US",stateName:"California",localityName:"Playa Vista",validityDays:825,verbose:w||!1,subjectAltNames:Array.from(mT($)).map((X)=>({type:2,value:X}))}}async function gT($,w){let A=lT({to:$,verbose:w}),Y=[A.caCertPath,A.certPath,A.keyPath];z("certificates","Attempting to clean up relating certificates",w),await Promise.all(Y.map((f)=>AJ(f,w)))}async function tT($,w){try{if(z("ssl",`Checking if certificate is trusted: ${$}`,w?.verbose),Bw.platform==="darwin"){if(w?.serverName)return UJ($,w.serverName,{verbose:w?.verbose});return Zf($,{verbose:w?.verbose})}else if(Bw.platform==="win32")try{let Y=I1(`openssl x509 -noout -subject -in "${$}"`).toString().trim().split("=").slice(1).join("=").trim()||"";if(!Y)return z("ssl","Could not extract certificate subject",w?.verbose),!1;let f=`powershell -Command "Get-ChildItem -Path Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -like '*${Y}*' } | Select-Object Subject"`;if(I1(f).toString().includes(Y))return z("ssl","Certificate found in trusted root store",w?.verbose),!0;return z("ssl","Certificate not found in trusted root store",w?.verbose),!1}catch(A){return z("ssl",`Error checking certificate trust on Windows: ${A}`,w?.verbose),!1}else if(Bw.platform==="linux")try{let Y=I1(`openssl x509 -noout -fingerprint -sha256 -in "${$}"`).toString().trim().split("=")[1]?.trim()||"",f=["/etc/ssl/certs","/etc/pki/tls/certs"];for(let X of f)try{if(I1(`find ${X} -type f -exec openssl x509 -noout -fingerprint -sha256 -in {} \\; 2>/dev/null | grep "${Y}"`).toString().includes(Y))return z("ssl",`Certificate fingerprint found in ${X}`,w?.verbose),!0}catch{}return z("ssl","Certificate not found in system trust stores",w?.verbose),!1}catch(A){return z("ssl",`Error checking certificate trust on Linux: ${A}`,w?.verbose),!1}return z("ssl",`Platform ${Bw.platform} not supported for certificate trust check`,w?.verbose),!1}catch(A){return z("ssl",`Error checking if certificate is trusted: ${A}`,w?.verbose),!1}}var Ew=null,F7="rpx-root-ca.crt",z7="rpx-root-ca.key";var Pf=E$(()=>{jU();Aw();GU();JJ();U$();JJ();HU()});import*as B0 from"node:path";function AA($,w){if(typeof $==="string")return{dir:$,spa:!1,pathRewriteStyle:"directory",maxAge:0,cleanUrls:w};return{dir:$.dir,spa:$.spa??!1,pathRewriteStyle:$.pathRewriteStyle??"directory",maxAge:$.maxAge??0,cleanUrls:w}}function R7($){let w=B0.extname($).toLowerCase();return K7[w]??"application/octet-stream"}function h7($){let w;try{w=decodeURIComponent($)}catch{return null}if(w.includes("\x00")||w.includes("\\"))return null;let A=B0.posix.normalize(`/${w}`);if(A.includes(".."))return null;return A.replace(/^\/+/,"")}function q7($,w){let A=h7($);if(A===null)return null;let Y=B0.posix.extname(A);if(w.cleanUrls&&Y===".html"){let f=$.replace(/\/index\.html$/i,"/").replace(/\.html$/i,"");return{filePath:B0.join(w.dir,A),redirectTo:f||"/"}}if(A===""||$.endsWith("/"))return{filePath:B0.join(w.dir,A,"index.html")};if(Y!=="")return{filePath:B0.join(w.dir,A)};if(w.pathRewriteStyle==="flat")return{filePath:B0.join(w.dir,`${A}.html`)};return{filePath:B0.join(w.dir,A,"index.html")}}async function pT($,w){let A=q7($,w);if(!A)return new Response("Forbidden",{status:403});if(A.redirectTo)return new Response(null,{status:301,headers:{Location:A.redirectTo}});let Y=w.maxAge>0?`public, max-age=${w.maxAge}`:"no-cache",f=Bun.file(A.filePath);if(await f.exists())return new Response(f,{status:200,headers:{"Content-Type":R7(A.filePath),"Cache-Control":Y}});if(w.spa){let X=B0.join(w.dir,"index.html"),U=Bun.file(X);if(await U.exists())return new Response(U,{status:200,headers:{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache"}})}return new Response("Not Found",{status:404})}var K7;var YA=E$(()=>{K7={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".eot":"application/vnd.ms-fontobject",".txt":"text/plain; charset=utf-8",".xml":"application/xml; charset=utf-8",".pdf":"application/pdf",".wasm":"application/wasm",".mp4":"video/mp4",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav"}});function y7($){return($.headers.get("host")||"").split(":")[0]}function aT($,w){if(!w||w==="/")return $;if($===w)return"/";if($.startsWith(`${w}/`)){let A=$.slice(w.length);return A===""?"/":A}return $}function rT($,w,A){let Y=new URL($.url),f=w.sourceHost??"",U=w.stripBasePathPrefix??!1?aT(Y.pathname,w.basePath):Y.pathname,W=wJ(U,w.pathRewrites);if(W)f=W.targetHost,U=W.targetPath,z("request",`Path rewrite: ${Y.pathname} → ${f}${U}`,A);return{targetHost:f,targetPath:U,search:Y.search}}function fA($,w){return async(A,Y)=>{let f=new URL(A.url),X=y7(A),U=$(X,f.pathname);if(!U)return z("request",`No route found for host: ${X}`,w),new Response(`No proxy configured for ${X}`,{status:404});if(U.static){let T=U.stripBasePathPrefix??!0?aT(f.pathname,U.basePath):f.pathname;return pT(T,U.static)}if(A.headers.get("upgrade")?.toLowerCase()==="websocket"){if(!Y||!U.sourceHost)return new Response("WebSocket upgrade not supported here",{status:400});let{targetHost:j,targetPath:T,search:F}=rT(A,U,w),H=`ws://${j}${T}${F}`,G={};for(let[h,N]of A.headers)if(!N7.has(h.toLowerCase())&&h.toLowerCase()!=="host")G[h]=N;G.host=j,G["x-forwarded-for"]="127.0.0.1",G["x-forwarded-proto"]="https",G["x-forwarded-host"]=X;let K={targetUrl:H,forwardHeaders:G};if(Y.upgrade(A,{data:K})){z("ws",`upgraded ${X}${T} → ${H}`,w);return}return new Response("WebSocket upgrade failed",{status:400})}if(!U.sourceHost)return new Response(`No upstream configured for ${X}`,{status:502});let{targetHost:W,targetPath:J,search:B}=rT(A,U,w),E=`http://${W}${J}${B}`;try{let j=new Headers(A.headers);if(j.set("host",W),U.changeOrigin)j.set("origin",`http://${U.sourceHost}`);j.set("x-forwarded-for","127.0.0.1"),j.set("x-forwarded-proto","https"),j.set("x-forwarded-host",X);let T=await fetch(E,{method:A.method,headers:j,body:A.body,redirect:"manual"});if(U.cleanUrls&&f.pathname.endsWith(".html")){let H=f.pathname.replace(/\.html$/,"");return new Response(null,{status:301,headers:{Location:H}})}let F=new Headers(T.headers);return new Response(T.body,{status:T.status,statusText:T.statusText,headers:F})}catch(j){return z("request",`Proxy error for ${X}: ${j}`,w),new Response(`Proxy Error: ${j}`,{status:502})}}}function XA($){let w=new WeakMap;return{open(A){let{targetUrl:Y,forwardHeaders:f}=A.data,X;try{X=new WebSocket(Y,{headers:f})}catch(W){z("ws",`failed to open upstream ${Y}: ${W}`,$),A.close(1011,"upstream connect failed");return}X.binaryType="arraybuffer";let U={upstream:X,upstreamOpen:!1,pending:[]};w.set(A,U),X.addEventListener("open",()=>{U.upstreamOpen=!0;for(let W of U.pending)X.send(W);U.pending=[]}),X.addEventListener("message",(W)=>{A.send(W.data)}),X.addEventListener("close",(W)=>{try{A.close(W.code||1000,W.reason||"")}catch{}}),X.addEventListener("error",()=>{z("ws",`upstream error for ${Y}`,$);try{A.close(1011,"upstream error")}catch{}})},message(A,Y){let f=w.get(A);if(!f)return;let X=typeof Y==="string"?Y:new Uint8Array(Y);if(f.upstreamOpen)f.upstream.send(X);else f.pending.push(X)},close(A,Y,f){let X=w.get(A);if(!X)return;w.delete(A);try{X.upstream.close(Y||1000,f||"")}catch{}}}}var N7;var bf=E$(()=>{YA();U$();N7=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade","sec-websocket-key","sec-websocket-version","sec-websocket-extensions"])});function UA($){return $.startsWith("*.")}function sT($,w){if(!UA(w))return!1;let A=w.slice(1);return $.length>A.length&&$.endsWith(A)}function S1($){if(!$||$==="/")return"/";let w=$.trim();if(!w.startsWith("/"))w=`/${w}`;return w=w.replace(/\/+$/,""),w===""?"/":w}function I7($,w){if(w==="/")return!0;if($===w)return!0;return $.startsWith(`${w}/`)}function JA($){let w=new Map;for(let Y of $){let f=S1(Y.path),X=w.get(Y.host);if(!X)X=new Map,w.set(Y.host,X);X.set(f,Y.route)}let A=new Map;for(let[Y,f]of w){let X=[];for(let[U,W]of f)X.push({path:U,route:W});X.sort((U,W)=>W.path.length-U.path.length),A.set(Y,X)}return A}function C7($,w){let A=$.get(w);if(A!==void 0)return A;let Y,f=-1;for(let[X,U]of $){if(!UA(X))continue;if(sT(w,X)){let W=X.length-1;if(W>f)f=W,Y=U}}return Y}function WA($,w,A){let Y=C7($,w);if(!Y)return;for(let f of Y)if(I7(A,f.path))return f.route;return}var cf=()=>{};import*as BA from"node:fs/promises";import*as BJ from"node:path";function S7($){if(!$.endsWith(".crt"))return null;let w=$.slice(0,-4);if(w.length===0)return null;if(w.startsWith("_wildcard."))return`*.${w.slice(10)}`;return w}async function L7($,w,A,Y){try{let[f,X]=await Promise.all([BA.readFile(w,"utf8"),BA.readFile(A,"utf8")]);return{serverName:$,cert:f,key:X}}catch(f){return z("sni",`skipping ${$}: ${f.message}`,Y),null}}async function EJ($,w){let A=new Map;if($.certsDir){let f=[];try{f=await BA.readdir($.certsDir)}catch(X){z("sni",`certsDir read failed (${$.certsDir}): ${X.message}`,w)}for(let X of f){let U=S7(X);if(!U)continue;let W=X.slice(0,-4);A.set(U,{certPath:BJ.join($.certsDir,X),keyPath:BJ.join($.certsDir,`${W}.key`)})}}if($.domains)for(let[f,X]of Object.entries($.domains))A.set(f,X);let Y=[];for(let[f,X]of A){let U=await L7(f,X.certPath,X.keyPath,w);if(U)Y.push(U)}return Y}var jJ=E$(()=>{U$()});import*as jw from"node:fs/promises";import*as TJ from"node:path";function V7($,w){if(!w||w.length===0)return!1;return w.some((A)=>{let Y=A.startsWith(".")?A.slice(1):A;return $===Y||$.endsWith(`.${Y}`)})}function Q7($){if(!$||$.length>253)return!1;if($.includes("/")||$.includes(":")||$.includes(" "))return!1;if($.startsWith("*"))return!1;return/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test($)}class uf{config;certsDir;onCertAdded;http01Store;issuer;verbose;negativeCacheMs;certs=new Map;inFlight=new Map;negativeCache=new Map;constructor($){this.config=$.config,this.certsDir=$.certsDir,this.onCertAdded=$.onCertAdded,this.http01Store=$.http01Store??pY,this.issuer=$.issuer??p8,this.verbose=$.verbose??!1,this.negativeCacheMs=$.negativeCacheMs??O7;for(let w of $.initial??[])this.certs.set(w.serverName,w)}get challengeStore(){return this.http01Store}sniEntries(){return Array.from(this.certs.values())}hasCert($){return this.certs.has($)}async isApproved($){if(!Q7($))return!1;if(V7($,this.config.allowedSuffixes))return!0;if(this.config.ask)try{return await this.config.ask($)}catch(w){return z("on-demand",`ask(${$}) threw: ${w.message}`,this.verbose),!1}return!1}async ensureCert($){if(!this.config.enabled)return!1;if(this.certs.has($))return!0;let w=this.inFlight.get($);if(w)return w;let A=this.negativeCache.get($);if(A!==void 0&&Date.now()<A)return z("on-demand",`${$} negatively cached for ${A-Date.now()}ms`,this.verbose),!1;let Y=this.issue($).finally(()=>{this.inFlight.delete($)});return this.inFlight.set($,Y),Y}async issue($){if(this.certs.has($))return!0;if(await this.loadFromDisk($))return!0;if(!await this.isApproved($))return z("on-demand",`refused issuance for ${$} (not approved)`,this.verbose),!1;try{z("on-demand",`issuing cert for ${$} (staging=${this.config.staging??!1})`,this.verbose);let w=await this.issuer({domains:[$],method:"http-01",http01Store:this.http01Store,email:this.config.email,staging:this.config.staging});await this.persist($,w.fullChainPem,w.keyPem);let A={serverName:$,cert:w.fullChainPem,key:w.keyPem};return this.certs.set($,A),this.negativeCache.delete($),z("on-demand",`issued + installed cert for ${$}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch(w){return this.negativeCache.set($,Date.now()+this.negativeCacheMs),z("on-demand",`issuance for ${$} failed: ${w.message}`,this.verbose),!1}}async loadFromDisk($){let{certPath:w,keyPath:A}=this.pathsFor($);try{let[Y,f]=await Promise.all([jw.readFile(w,"utf8"),jw.readFile(A,"utf8")]),X={serverName:$,cert:Y,key:f};return this.certs.set($,X),z("on-demand",`adopted existing on-disk cert for ${$}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch{return!1}}pathsFor($){return{certPath:TJ.join(this.certsDir,`${$}.crt`),keyPath:TJ.join(this.certsDir,`${$}.key`)}}async persist($,w,A){await jw.mkdir(this.certsDir,{recursive:!0}).catch(()=>{});let{certPath:Y,keyPath:f}=this.pathsFor($);await Promise.all([jw.writeFile(Y,w,"utf8"),jw.writeFile(f,A,{encoding:"utf8",mode:384})])}}var O7=60000;var FJ=E$(()=>{jU();U$()});import*as nf from"node:fs";import*as n$ from"node:fs/promises";import{homedir as _7}from"node:os";import*as zJ from"node:path";import*as vf from"node:process";function Tw(){return zJ.join(_7(),".stacks","rpx","registry.d")}function EA($){return typeof $==="string"&&$.length>0&&$.length<=128&&D7.test($)}function GJ($,w){if(!EA(w))throw Error(`invalid registry id: ${JSON.stringify(w)}`);return zJ.join($,`${w}.json`)}function l$($){if(!Number.isInteger($)||$<=0)return!1;try{return vf.kill($,0),!0}catch(w){return w.code==="EPERM"}}function oT($){if(!$||typeof $!=="object")return!1;let w=$,A=w.pid===void 0||typeof w.pid==="number"&&Number.isInteger(w.pid)&&w.pid>0,Y=typeof w.from==="string"&&w.from.length>0,f=typeof w.static==="string"||!!w.static&&typeof w.static==="object"&&typeof w.static.dir==="string",X=w.path===void 0||typeof w.path==="string";return typeof w.id==="string"&&EA(w.id)&&(Y||f)&&typeof w.to==="string"&&w.to.length>0&&X&&A&&typeof w.createdAt==="string"}async function k7($){await n$.mkdir($,{recursive:!0})}async function eT($,w=Tw(),A){if(!oT($))throw Error(`invalid registry entry: ${JSON.stringify($)}`);await k7(w);let Y=GJ(w,$.id),f=`${Y}.tmp.${vf.pid}.${Date.now()}`,X=JSON.stringify($,null,2);try{await n$.writeFile(f,X,{encoding:"utf8",mode:420}),await n$.rename(f,Y),z("registry",`wrote entry ${$.id} → ${Y}`,A)}catch(U){throw await n$.unlink(f).catch(()=>{}),U}}async function HJ($,w=Tw(),A){let Y=GJ(w,$);try{await n$.unlink(Y),z("registry",`removed entry ${$}`,A)}catch(f){if(f.code!=="ENOENT")throw f}}async function Z7($,w=Tw(),A){let Y=GJ(w,$);try{let f=await n$.readFile(Y,"utf8"),X=JSON.parse(f);if(!oT(X))return z("registry",`entry ${$} failed validation, removing`,A),await n$.unlink(Y).catch(()=>{}),null;return X}catch(f){if(f.code==="ENOENT")return null;if(f instanceof SyntaxError)return z("registry",`entry ${$} has invalid JSON, removing`,A),await n$.unlink(Y).catch(()=>{}),null;throw f}}async function Cw($=Tw(),w){let A;try{A=await n$.readdir($)}catch(f){if(f.code==="ENOENT")return[];throw f}let Y=[];for(let f of A){if(!f.endsWith(".json"))continue;let X=f.slice(0,-5);if(!EA(X))continue;let U=await Z7(X,$,w);if(U)Y.push(U)}return Y}async function mf($=Tw(),w){let A=await Cw($,w),Y=0;for(let f of A){if(f.pid===void 0)continue;if(!l$(f.pid))z("registry",`GC: pid ${f.pid} for ${f.id} is dead, removing`,w),await HJ(f.id,$,w).catch(()=>{}),Y++}return Y}function KJ($,w={}){let A=w.dir??Tw(),Y=w.debounceMs??100,f=w.pollMs??Math.max(Y*2,250),X=w.verbose;nf.mkdirSync(A,{recursive:!0});let U=null,W=!1,J=null,B=!1,E=(K)=>{return JSON.stringify(K.map((R)=>({id:R.id,from:R.from,to:R.to,path:R.path,pid:R.pid,pathRewrites:R.pathRewrites,cleanUrls:R.cleanUrls,changeOrigin:R.changeOrigin,static:R.static})).sort((R,h)=>R.id.localeCompare(h.id)))},j=()=>{if(U=null,W)return;Cw(A,X).then((K)=>{return J=E(K),$(K)}).catch((K)=>{z("registry",`watcher onChange failed: ${K}`,X)})},T=()=>{if(W)return;if(U)clearTimeout(U);U=setTimeout(j,Y)},H=setInterval(()=>{if(W||B)return;B=!0,Cw(A,X).then((K)=>{if(E(K)!==J)T()}).catch((K)=>{z("registry",`watcher poll failed: ${K}`,X)}).finally(()=>{B=!1})},f),G=nf.watch(A,{persistent:!0},(K,R)=>{if(R&&/\.tmp\.\d+\.\d+$/.test(R))return;T()});return G.on("error",(K)=>{z("registry",`watcher error: ${K}`,X)}),T(),{close:()=>{if(W=!0,U)clearTimeout(U);clearInterval(H),G.close()}}}var D7;var jA=E$(()=>{U$();D7=/^[a-zA-Z0-9._-]+$/});import{spawn as x7}from"node:child_process";import*as Sw from"node:fs/promises";import{homedir as P7}from"node:os";import*as TA from"node:path";import*as v$ from"node:process";function r0(){return TA.join(P7(),".stacks","rpx")}function wF($=r0()){return TA.join($,"daemon.pid")}async function $F($=r0()){try{let w=await Sw.readFile(wF($),"utf8"),A=Number.parseInt(w.trim(),10);if(!Number.isFinite(A)||A<=0)return null;return A}catch(w){if(w.code==="ENOENT")return null;throw w}}async function i7($=r0()){await Sw.unlink(wF($)).catch(()=>{})}function b7(){let $=v$.execPath,w=TA.basename($).toLowerCase();if((w==="bun"||w==="node"||w.startsWith("bun-"))&&v$.argv[1])return[$,v$.argv[1],"daemon:start"];return[$,"daemon:start"]}async function AF($={}){let w=$.rpxDir??r0(),A=$.verbose??!1;await Fw({rpxDir:w,verbose:A}).catch((E)=>{z("daemon",`DNS reconcile before ensureDaemonRunning: ${E}`,A)});let Y=await $F(w);if(Y!==null&&l$(Y))return z("daemon",`ensureDaemonRunning: already running pid=${Y}`,A),{pid:Y,spawned:!1};if(Y!==null)z("daemon",`ensureDaemonRunning: clearing stale pid=${Y}`,A),await i7(w);await Sw.mkdir(w,{recursive:!0});let f=$.spawnCommand??b7();if(f.length===0)throw Error("ensureDaemonRunning: spawnCommand is empty");z("daemon",`spawning daemon: ${f.join(" ")}`,A);let X=x7(f[0],f.slice(1),{detached:!0,stdio:"ignore",cwd:$.spawnCwd??v$.cwd(),env:$.spawnEnv?{...v$.env,...$.spawnEnv}:v$.env});X.unref();let U=null;X.once("error",(E)=>{U=E});let W=$.startupTimeoutMs??5000,J=$.pollIntervalMs??50,B=Date.now()+W;while(Date.now()<B){if(U)throw U;let E=await $F(w);if(E!==null&&l$(E))return z("daemon",`daemon registered with pid=${E}`,A),{pid:E,spawned:!0};await new Promise((j)=>setTimeout(j,J))}if(U)throw U;throw Error(`rpx daemon failed to start within ${W}ms (rpxDir=${w})`)}var RJ=E$(()=>{Aw();Pf();bf();cf();jJ();FJ();YA();jA();L1();U$()});import*as zw from"node:fs/promises";import{homedir as c7}from"node:os";import*as hJ from"node:path";function fF(){return hJ.join(c7(),".stacks","rpx")}function qJ($=fF()){return hJ.join($,u7)}async function lf($=fF()){try{let w=await zw.readFile(qJ($),"utf8"),A=JSON.parse(w);if(A.version!==df||!Array.isArray(A.resolvers))return null;return{version:df,resolvers:A.resolvers.filter((Y)=>typeof Y==="string"),domains:Array.isArray(A.domains)?A.domains.filter((Y)=>typeof Y==="string"):[],ownerPid:typeof A.ownerPid==="number"?A.ownerPid:null,updatedAt:typeof A.updatedAt==="string"?A.updatedAt:""}}catch(w){if(w.code==="ENOENT")return null;throw w}}async function XF($,w){await zw.mkdir($,{recursive:!0}),await zw.writeFile(qJ($),`${JSON.stringify(w,null,2)}
|
|
204
204
|
`,"utf8")}async function NJ($){await zw.rm(qJ($),{force:!0})}function UF($){let w=$.trim().toLowerCase().replace(/\.$/,"");if(!w||w.includes("127.0.0.1"))return null;if(w==="localhost"||w.endsWith(".localhost"))return null;if(/^\d{1,3}(\.\d{1,3}){3}$/.test(w))return null;return w}function n7($){let w=UF($);if(!w)return null;let A=w.split(".");if(A.length<2)return null;return A.slice(-2).join(".")}function yJ($){let w=new Set;for(let A of $){let Y=n7(A);if(Y)w.add(Y)}return Array.from(w).sort()}function MJ($){let w=new Set;for(let A of $){let Y=UF(A);if(Y)w.add(Y)}return Array.from(w).sort()}var df=1,u7="dns-state.json",YF;var JF=E$(()=>{YF=["com","test","dev","app","page","local","localhost","example","invalid"]});var rf={};dJ(rf,{tearDownDevelopmentDns:()=>Lw,syncDevelopmentDnsFromRegistry:()=>FA,stopDnsServer:()=>IJ,startDnsServer:()=>FF,setupResolver:()=>o7,setupDevelopmentDns:()=>OJ,resolverFilePath:()=>O1,removeResolver:()=>$R,removeLegacyTldResolvers:()=>LJ,reconcileStaleDevelopmentDns:()=>Fw,isDnsServerRunning:()=>t7,contentLooksLikeRpxResolver:()=>zF,RPX_RESOLVER_MARKER:()=>EF,DNS_PORT:()=>pf});import v7 from"node:dgram";import*as WF from"node:fs/promises";import*as BF from"node:path";import*as a0 from"node:process";function m7($){return{id:$.readUInt16BE(0),flags:$.readUInt16BE(2),qdcount:$.readUInt16BE(4),ancount:$.readUInt16BE(6),nscount:$.readUInt16BE(8),arcount:$.readUInt16BE(10)}}function TF($,w){let A=[],Y=w;while(!0){let f=$[Y];if(f===0){Y++;break}if((f&192)===192){let X=$.readUInt16BE(Y)&16383,{name:U}=TF($,X);A.push(U),Y+=2;break}Y++,A.push($.subarray(Y,Y+f).toString("ascii")),Y+=f}return{name:A.join("."),newOffset:Y}}function d7($,w){let{name:A,newOffset:Y}=TF($,w),f=$.readUInt16BE(Y),X=$.readUInt16BE(Y+2);return{question:{name:A,type:f,class:X},newOffset:Y+4}}function tf($){let w=$.split("."),A=[];for(let Y of w)A.push(Buffer.from([Y.length])),A.push(Buffer.from(Y,"ascii"));return A.push(Buffer.from([0])),Buffer.concat(A)}function l7($,w,A){let Y=[],f=Buffer.alloc(12);f.writeUInt16BE($,0),f.writeUInt16BE(33152,2),f.writeUInt16BE(1,4),f.writeUInt16BE(1,6),f.writeUInt16BE(0,8),f.writeUInt16BE(0,10),Y.push(f),Y.push(tf(w.name));let X=Buffer.alloc(4);X.writeUInt16BE(w.type,0),X.writeUInt16BE(w.class,2),Y.push(X),Y.push(tf(w.name));let U=Buffer.alloc(10);if(U.writeUInt16BE(w.type,0),U.writeUInt16BE(1,2),U.writeUInt32BE(300,4),w.type===1){U.writeUInt16BE(4,8),Y.push(U);let W=A.split(".").map((J)=>Number.parseInt(J,10));Y.push(Buffer.from(W))}else if(w.type===28)U.writeUInt16BE(16,8),Y.push(U),Y.push(Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]));else return f.writeUInt16BE(33155,2),f.writeUInt16BE(0,6),Buffer.concat([f,tf(w.name),X]);return Buffer.concat(Y)}function g7($,w){let A=[],Y=Buffer.alloc(12);Y.writeUInt16BE($,0),Y.writeUInt16BE(33155,2),Y.writeUInt16BE(1,4),Y.writeUInt16BE(0,6),Y.writeUInt16BE(0,8),Y.writeUInt16BE(0,10),A.push(Y),A.push(tf(w.name));let f=Buffer.alloc(4);return f.writeUInt16BE(w.type,0),f.writeUInt16BE(w.class,2),A.push(f),Buffer.concat(A)}async function FF($,w){if(a0.platform!=="darwin")return!1;let A=MJ($);if(A.length===0)return!1;if(g$){for(let Y of A)gf.add(Y);return z("dns","DNS server already running — merged domains",w),!0}return gf=new Set(A),new Promise((Y)=>{g$=v7.createSocket("udp4"),g$.on("error",(f)=>{z("dns",`DNS server error: ${f.message}`,w),g$?.close(),g$=null,Y(!1)}),g$.on("message",(f,X)=>{try{let U=m7(f),{question:W}=d7(f,12);z("dns",`Query for ${W.name} type ${W.type} from ${X.address}`,w);let J=W.name.toLowerCase(),B=!1;for(let j of gf)if(J===j||J.endsWith(`.${j}`)){B=!0;break}let E;if(B&&(W.type===1||W.type===28))E=l7(U.id,W,"127.0.0.1"),z("dns",`Responding with localhost for ${W.name}`,w);else E=g7(U.id,W),z("dns",`NXDOMAIN for ${W.name}`,w);g$?.send(E,X.port,X.address)}catch(U){z("dns",`Error processing DNS query: ${U}`,w)}}),g$.on("listening",()=>{let f=g$?.address();z("dns",`DNS server listening on ${f?.address}:${f?.port}`,w),Y(!0)});try{g$.bind(pf,"127.0.0.1")}catch(f){z("dns",`Failed to bind DNS server: ${f}`,w),Y(!1)}})}function IJ($){if(g$)z("dns","Stopping DNS server",$),g$.close(),g$=null,gf=new Set}function t7(){return g$!==null}function p7(){return`${EF}
|
|
205
205
|
nameserver 127.0.0.1
|
|
206
206
|
port ${pf}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ka as a,la as b,ma as c,na as d,oa as e,pa as f,qa as g,ra as h,sa as i,ta as j,ua as k,va as l,wa as m,xa as n}from"./chunk-
|
|
1
|
+
import{ka as a,la as b,ma as c,na as d,oa as e,pa as f,qa as g,ra as h,sa as i,ta as j,ua as k,va as l,wa as m,xa as n}from"./chunk-zx2ghrc1.js";import"./chunk-rbgb5fyg.js";export{l as tearDownDevelopmentDns,k as syncDevelopmentDnsFromRegistry,d as stopDnsServer,c as startDnsServer,h as setupResolver,j as setupDevelopmentDns,f as resolverFilePath,m as removeResolver,i as removeLegacyTldResolvers,n as reconcileStaleDevelopmentDns,e as isDnsServerRunning,g as contentLooksLikeRpxResolver,b as RPX_RESOLVER_MARKER,a as DNS_PORT};
|
|
@@ -151,7 +151,7 @@ $store.Open("ReadWrite")
|
|
|
151
151
|
$store.Add($cert)
|
|
152
152
|
$store.Close()
|
|
153
153
|
Write-Host "Root CA trusted successfully!"
|
|
154
|
-
`,u=tt(us.tmpdir(),"rpx-trust.ps1");if(await b.writeFile(u,a),Vi(`powershell -ExecutionPolicy Bypass -File "${u}"`),t.verbose)H.success("Successfully added certificate to Windows trust store");h=!0}catch(a){if(t.verbose)H.warn(`Could not trust certificate: ${a}`)}else try{await Af(c,f.certificate,r),h=!0}catch(a){if(t.verbose)H.warn(`Could not add certificate to trust store: ${a}`)}if(yi={key:c.privateKey,cert:c.certificate,ca:f.certificate},t.verbose)H.success(`Certificate generated successfully for ${i.length} domain${i.length>1?"s":""}`);if(!h&&t.verbose)H.warn('If you see certificate warnings in Chrome/Arc, type "thisisunsafe" on the warning page'),H.warn("This will bypass the warning and you should only need to do it once")}function u0(){yi=null}async function ws(t){if(!t)return null;if(yi)return yi;let i=$s(t);try{let[r,n,e]=await Promise.all([b.access(i.keyPath).then(()=>!0).catch(()=>!1),b.access(i.certPath).then(()=>!0).catch(()=>!1),i.caCertPath?b.access(i.caCertPath).then(()=>!0).catch(()=>!1):Promise.resolve(!1)]);if(!r||!n)return $("ssl",`Certificate files don't exist: key=${r}, cert=${n}, paths: ${i.keyPath}, ${i.certPath}`,t.verbose),null;let f="regenerateUntrustedCerts"in t,s=t.regenerateUntrustedCerts,c=f?s!==!1:!0;$("ssl",`Trust check: hasFlag=${f}, flagValue=${s}, shouldCheckTrust=${c}`,t.verbose);let l=i.basePath||tt(ne(),".stacks","ssl"),h=ys(l);if(!(c?await Aa(h.caCertPath,t):!0))return $("ssl","Root CA exists but is not trusted, will regenerate",t.verbose),null;let[u,y,o]=await Promise.all([b.readFile(i.keyPath,"utf8"),b.readFile(i.certPath,"utf8"),e&&i.caCertPath?b.readFile(i.caCertPath,"utf8"):Promise.resolve(void 0)]);if(o&&!o.includes("-----BEGIN CERTIFICATE-----"))return $("ssl","Invalid root CA certificate content, will regenerate",t.verbose),null;if(re(t))try{let{X509Certificate:m}=await import("node:crypto"),d=new m(y).subjectAltName||"",T=t.proxies.map((Y)=>Y.to).filter((Y)=>!d.includes(`DNS:${Y}`));if(T.length>0)return $("ssl",`Certificate missing SANs for: ${T.join(", ")}, will regenerate`,t.verbose),null}catch(m){$("ssl",`Could not verify cert SANs: ${m}`,t.verbose)}return $("ssl","Successfully loaded existing certificates",t.verbose),yi={key:u,cert:y,ca:o},yi}catch(r){return $("ssl",`Error checking existing certificates: ${r}`,t.verbose),null}}function $s(t,i){let r=os(t);$("ssl",`Primary domain: ${r}`,i);let n=ma(t),e=tt(ne(),".stacks","ssl");if(typeof t.https==="object"){let f=t.https.basePath&&t.https.basePath.trim()!==""?t.https.basePath:e,s={domain:r,hostCertCN:r,basePath:f,caCertPath:t.https.caCertPath||n.caCertPath,certPath:t.https.certPath||n.certPath,keyPath:t.https.keyPath||n.keyPath,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:t.https.commonName||r,organizationName:t.https.organizationName||"Local Development",countryName:t.https.countryName||"US",stateName:t.https.stateName||"California",localityName:t.https.localityName||"Playa Vista",validityDays:t.https.validityDays||825,verbose:i||!1,subjectAltNames:Array.from(oa(t)).map((c)=>({type:2,value:c}))};if(ya(t.https.rootCA))s.rootCA=t.https.rootCA;return s}return{domain:r,hostCertCN:r,basePath:e,...n,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:r,organizationName:"Local Development",countryName:"US",stateName:"California",localityName:"Playa Vista",validityDays:825,verbose:i||!1,subjectAltNames:Array.from(oa(t)).map((f)=>({type:2,value:f}))}}async function QE(t,i){let r=ma({to:t,verbose:i}),n=[r.caCertPath,r.certPath,r.keyPath];$("certificates","Attempting to clean up relating certificates",i),await Promise.all(n.map((e)=>$a(e,i)))}async function Aa(t,i){try{if($("ssl",`Checking if certificate is trusted: ${t}`,i?.verbose),Mt.platform==="darwin"){if(i?.serverName)return mr(t,i.serverName,{verbose:i.verbose});return dr(t,{verbose:i.verbose})}else if(Mt.platform==="win32")try{let n=Vi(`openssl x509 -noout -subject -in "${t}"`).toString().trim().split("=").slice(1).join("=").trim()||"";if(!n)return $("ssl","Could not extract certificate subject",i?.verbose),!1;let e=`powershell -Command "Get-ChildItem -Path Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -like '*${n}*' } | Select-Object Subject"`;if(Vi(e).toString().includes(n))return $("ssl","Certificate found in trusted root store",i?.verbose),!0;return $("ssl","Certificate not found in trusted root store",i?.verbose),!1}catch(r){return $("ssl",`Error checking certificate trust on Windows: ${r}`,i?.verbose),!1}else if(Mt.platform==="linux")try{let n=Vi(`openssl x509 -noout -fingerprint -sha256 -in "${t}"`).toString().trim().split("=")[1]?.trim()||"",e=["/etc/ssl/certs","/etc/pki/tls/certs"];for(let f of e)try{if(Vi(`find ${f} -type f -exec openssl x509 -noout -fingerprint -sha256 -in {} \\; 2>/dev/null | grep "${n}"`).toString().includes(n))return $("ssl",`Certificate fingerprint found in ${f}`,i?.verbose),!0}catch{}return $("ssl","Certificate not found in system trust stores",i?.verbose),!1}catch(r){return $("ssl",`Error checking certificate trust on Linux: ${r}`,i?.verbose),!1}return $("ssl",`Platform ${Mt.platform} not supported for certificate trust check`,i?.verbose),!1}catch(r){return $("ssl",`Error checking if certificate is trusted: ${r}`,i?.verbose),!1}}import*as Nt from"node:path";function Ea(t,i){if(typeof t==="string")return{dir:t,spa:!1,pathRewriteStyle:"directory",maxAge:0,cleanUrls:i};return{dir:t.dir,spa:t.spa??!1,pathRewriteStyle:t.pathRewriteStyle??"directory",maxAge:t.maxAge??0,cleanUrls:i}}var o0={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".eot":"application/vnd.ms-fontobject",".txt":"text/plain; charset=utf-8",".xml":"application/xml; charset=utf-8",".pdf":"application/pdf",".wasm":"application/wasm",".mp4":"video/mp4",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav"};function y0(t){let i=Nt.extname(t).toLowerCase();return o0[i]??"application/octet-stream"}function w0(t){let i;try{i=decodeURIComponent(t)}catch{return null}if(i.includes("\x00")||i.includes("\\"))return null;let r=Nt.posix.normalize(`/${i}`);if(r.includes(".."))return null;return r.replace(/^\/+/,"")}function $0(t,i){let r=w0(t);if(r===null)return null;let n=Nt.posix.extname(r);if(i.cleanUrls&&n===".html"){let e=t.replace(/\/index\.html$/i,"/").replace(/\.html$/i,"");return{filePath:Nt.join(i.dir,r),redirectTo:e||"/"}}if(r===""||t.endsWith("/"))return{filePath:Nt.join(i.dir,r,"index.html")};if(n!=="")return{filePath:Nt.join(i.dir,r)};if(i.pathRewriteStyle==="flat")return{filePath:Nt.join(i.dir,`${r}.html`)};return{filePath:Nt.join(i.dir,r,"index.html")}}async function Ta(t,i){let r=$0(t,i);if(!r)return new Response("Forbidden",{status:403});if(r.redirectTo)return new Response(null,{status:301,headers:{Location:r.redirectTo}});let n=i.maxAge>0?`public, max-age=${i.maxAge}`:"no-cache",e=Bun.file(r.filePath);if(await e.exists())return new Response(e,{status:200,headers:{"Content-Type":y0(r.filePath),"Cache-Control":n}});if(i.spa){let f=Nt.join(i.dir,"index.html"),s=Bun.file(f);if(await s.exists())return new Response(s,{status:200,headers:{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache"}})}return new Response("Not Found",{status:404})}var m0=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade","sec-websocket-key","sec-websocket-version","sec-websocket-extensions"]);function d0(t){return(t.headers.get("host")||"").split(":")[0]}function ga(t,i){if(!i||i==="/")return t;if(t===i)return"/";if(t.startsWith(`${i}/`)){let r=t.slice(i.length);return r===""?"/":r}return t}function Ca(t,i,r){let n=new URL(t.url),e=i.sourceHost??"",s=i.stripBasePathPrefix??!1?ga(n.pathname,i.basePath):n.pathname,c=Sa(s,i.pathRewrites);if(c)e=c.targetHost,s=c.targetPath,$("request",`Path rewrite: ${n.pathname} → ${e}${s}`,r);return{targetHost:e,targetPath:s,search:n.search}}function Ba(t,i){return async(r,n)=>{let e=new URL(r.url),f=d0(r),s=t(f,e.pathname);if(!s)return $("request",`No route found for host: ${f}`,i),new Response(`No proxy configured for ${f}`,{status:404});if(s.static){let y=s.stripBasePathPrefix??!0?ga(e.pathname,s.basePath):e.pathname;return Ta(y,s.static)}if(r.headers.get("upgrade")?.toLowerCase()==="websocket"){if(!n||!s.sourceHost)return new Response("WebSocket upgrade not supported here",{status:400});let{targetHost:u,targetPath:y,search:o}=Ca(r,s,i),m=`ws://${u}${y}${o}`,w={};for(let[T,Y]of r.headers)if(!m0.has(T.toLowerCase())&&T.toLowerCase()!=="host")w[T]=Y;w.host=u,w["x-forwarded-for"]="127.0.0.1",w["x-forwarded-proto"]="https",w["x-forwarded-host"]=f;let d={targetUrl:m,forwardHeaders:w};if(n.upgrade(r,{data:d})){$("ws",`upgraded ${f}${y} → ${m}`,i);return}return new Response("WebSocket upgrade failed",{status:400})}if(!s.sourceHost)return new Response(`No upstream configured for ${f}`,{status:502});let{targetHost:c,targetPath:l,search:h}=Ca(r,s,i),a=`http://${c}${l}${h}`;try{let u=new Headers(r.headers);if(u.set("host",c),s.changeOrigin)u.set("origin",`http://${s.sourceHost}`);u.set("x-forwarded-for","127.0.0.1"),u.set("x-forwarded-proto","https"),u.set("x-forwarded-host",f);let y=await fetch(a,{method:r.method,headers:u,body:r.body,redirect:"manual"});if(s.cleanUrls&&e.pathname.endsWith(".html")){let m=e.pathname.replace(/\.html$/,"");return new Response(null,{status:301,headers:{Location:m}})}let o=new Headers(y.headers);return new Response(y.body,{status:y.status,statusText:y.statusText,headers:o})}catch(u){return $("request",`Proxy error for ${f}: ${u}`,i),new Response(`Proxy Error: ${u}`,{status:502})}}}function Ra(t){let i=new WeakMap;return{open(r){let{targetUrl:n,forwardHeaders:e}=r.data,f;try{f=new WebSocket(n,{headers:e})}catch(c){$("ws",`failed to open upstream ${n}: ${c}`,t),r.close(1011,"upstream connect failed");return}f.binaryType="arraybuffer";let s={upstream:f,upstreamOpen:!1,pending:[]};i.set(r,s),f.addEventListener("open",()=>{s.upstreamOpen=!0;for(let c of s.pending)f.send(c);s.pending=[]}),f.addEventListener("message",(c)=>{r.send(c.data)}),f.addEventListener("close",(c)=>{try{r.close(c.code||1000,c.reason||"")}catch{}}),f.addEventListener("error",()=>{$("ws",`upstream error for ${n}`,t);try{r.close(1011,"upstream error")}catch{}})},message(r,n){let e=i.get(r);if(!e)return;let f=typeof n==="string"?n:new Uint8Array(n);if(e.upstreamOpen)e.upstream.send(f);else e.pending.push(f)},close(r,n,e){let f=i.get(r);if(!f)return;i.delete(r);try{f.upstream.close(n||1000,e||"")}catch{}}}}function ee(t){return t.startsWith("*.")}function ms(t,i){if(!ee(i))return!1;let r=i.slice(1);return t.length>r.length&&t.endsWith(r)}function sT(t,i){let r=t.get(i);if(r!==void 0)return r;let n,e=-1;for(let[f,s]of t){if(!ee(f))continue;if(ms(i,f)){let c=f.length-1;if(c>e)e=c,n=s}}return n}function ds(t){if(!t||t==="/")return"/";let i=t.trim();if(!i.startsWith("/"))i=`/${i}`;return i=i.replace(/\/+$/,""),i===""?"/":i}function A0(t,i){if(i==="/")return!0;if(t===i)return!0;return t.startsWith(`${i}/`)}function Ya(t){let i=new Map;for(let n of t){let e=ds(n.path),f=i.get(n.host);if(!f)f=new Map,i.set(n.host,f);f.set(e,n.route)}let r=new Map;for(let[n,e]of i){let f=[];for(let[s,c]of e)f.push({path:s,route:c});f.sort((s,c)=>c.path.length-s.path.length),r.set(n,f)}return r}function E0(t,i){let r=t.get(i);if(r!==void 0)return r;let n,e=-1;for(let[f,s]of t){if(!ee(f))continue;if(ms(i,f)){let c=f.length-1;if(c>e)e=c,n=s}}return n}function Ua(t,i,r){let n=E0(t,i);if(!n)return;for(let e of n)if(A0(r,e.path))return e.route;return}import*as Ar from"node:fs/promises";import*as As from"node:path";function T0(t){if(!t.endsWith(".crt"))return null;let i=t.slice(0,-4);if(i.length===0)return null;if(i.startsWith("_wildcard."))return`*.${i.slice(10)}`;return i}async function C0(t,i,r,n){try{let[e,f]=await Promise.all([Ar.readFile(i,"utf8"),Ar.readFile(r,"utf8")]);return{serverName:t,cert:e,key:f}}catch(e){return $("sni",`skipping ${t}: ${e.message}`,n),null}}async function Fa(t,i){let r=new Map;if(t.certsDir){let e=[];try{e=await Ar.readdir(t.certsDir)}catch(f){$("sni",`certsDir read failed (${t.certsDir}): ${f.message}`,i)}for(let f of e){let s=T0(f);if(!s)continue;let c=f.slice(0,-4);r.set(s,{certPath:As.join(t.certsDir,f),keyPath:As.join(t.certsDir,`${c}.key`)})}}if(t.domains)for(let[e,f]of Object.entries(t.domains))r.set(e,f);let n=[];for(let[e,f]of r){let s=await C0(e,f.certPath,f.keyPath,i);if(s)n.push(s)}return n}import*as wi from"node:fs/promises";import*as Es from"node:path";var S0=60000;function g0(t,i){if(!i||i.length===0)return!1;return i.some((r)=>{let n=r.startsWith(".")?r.slice(1):r;return t===n||t.endsWith(`.${n}`)})}function B0(t){if(!t||t.length>253)return!1;if(t.includes("/")||t.includes(":")||t.includes(" "))return!1;if(t.startsWith("*"))return!1;return/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test(t)}class Ts{config;certsDir;onCertAdded;http01Store;issuer;verbose;negativeCacheMs;certs=new Map;inFlight=new Map;negativeCache=new Map;constructor(t){this.config=t.config,this.certsDir=t.certsDir,this.onCertAdded=t.onCertAdded,this.http01Store=t.http01Store??an,this.issuer=t.issuer??rf,this.verbose=t.verbose??!1,this.negativeCacheMs=t.negativeCacheMs??S0;for(let i of t.initial??[])this.certs.set(i.serverName,i)}get challengeStore(){return this.http01Store}sniEntries(){return Array.from(this.certs.values())}hasCert(t){return this.certs.has(t)}async isApproved(t){if(!B0(t))return!1;if(g0(t,this.config.allowedSuffixes))return!0;if(this.config.ask)try{return await this.config.ask(t)}catch(i){return $("on-demand",`ask(${t}) threw: ${i.message}`,this.verbose),!1}return!1}async ensureCert(t){if(!this.config.enabled)return!1;if(this.certs.has(t))return!0;let i=this.inFlight.get(t);if(i)return i;let r=this.negativeCache.get(t);if(r!==void 0&&Date.now()<r)return $("on-demand",`${t} negatively cached for ${r-Date.now()}ms`,this.verbose),!1;let n=this.issue(t).finally(()=>{this.inFlight.delete(t)});return this.inFlight.set(t,n),n}async issue(t){if(this.certs.has(t))return!0;if(await this.loadFromDisk(t))return!0;if(!await this.isApproved(t))return $("on-demand",`refused issuance for ${t} (not approved)`,this.verbose),!1;try{$("on-demand",`issuing cert for ${t} (staging=${this.config.staging??!1})`,this.verbose);let i=await this.issuer({domains:[t],method:"http-01",http01Store:this.http01Store,email:this.config.email,staging:this.config.staging});await this.persist(t,i.fullChainPem,i.keyPem);let r={serverName:t,cert:i.fullChainPem,key:i.keyPem};return this.certs.set(t,r),this.negativeCache.delete(t),$("on-demand",`issued + installed cert for ${t}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch(i){return this.negativeCache.set(t,Date.now()+this.negativeCacheMs),$("on-demand",`issuance for ${t} failed: ${i.message}`,this.verbose),!1}}async loadFromDisk(t){let{certPath:i,keyPath:r}=this.pathsFor(t);try{let[n,e]=await Promise.all([wi.readFile(i,"utf8"),wi.readFile(r,"utf8")]),f={serverName:t,cert:n,key:e};return this.certs.set(t,f),$("on-demand",`adopted existing on-disk cert for ${t}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch{return!1}}pathsFor(t){return{certPath:Es.join(this.certsDir,`${t}.crt`),keyPath:Es.join(this.certsDir,`${t}.key`)}}async persist(t,i,r){await wi.mkdir(this.certsDir,{recursive:!0}).catch(()=>{});let{certPath:n,keyPath:e}=this.pathsFor(t);await Promise.all([wi.writeFile(n,i,"utf8"),wi.writeFile(e,r,{encoding:"utf8",mode:384})])}}import*as fe from"node:fs";import*as wt from"node:fs/promises";import{homedir as R0}from"node:os";import*as Cs from"node:path";import*as se from"node:process";var Y0=/^[a-zA-Z0-9._-]+$/;function Pi(){return Cs.join(R0(),".stacks","rpx","registry.d")}function Ss(t){return typeof t==="string"&&t.length>0&&t.length<=128&&Y0.test(t)}function gs(t,i){if(!Ss(i))throw Error(`invalid registry id: ${JSON.stringify(i)}`);return Cs.join(t,`${i}.json`)}function zt(t){if(!Number.isInteger(t)||t<=0)return!1;try{return se.kill(t,0),!0}catch(i){return i.code==="EPERM"}}function Na(t){if(!t||typeof t!=="object")return!1;let i=t,r=i.pid===void 0||typeof i.pid==="number"&&Number.isInteger(i.pid)&&i.pid>0,n=typeof i.from==="string"&&i.from.length>0,e=typeof i.static==="string"||!!i.static&&typeof i.static==="object"&&typeof i.static.dir==="string",f=i.path===void 0||typeof i.path==="string";return typeof i.id==="string"&&Ss(i.id)&&(n||e)&&typeof i.to==="string"&&i.to.length>0&&f&&r&&typeof i.createdAt==="string"}async function U0(t){await wt.mkdir(t,{recursive:!0})}async function dT(t,i=Pi(),r){if(!Na(t))throw Error(`invalid registry entry: ${JSON.stringify(t)}`);await U0(i);let n=gs(i,t.id),e=`${n}.tmp.${se.pid}.${Date.now()}`,f=JSON.stringify(t,null,2);try{await wt.writeFile(e,f,{encoding:"utf8",mode:420}),await wt.rename(e,n),$("registry",`wrote entry ${t.id} → ${n}`,r)}catch(s){throw await wt.unlink(e).catch(()=>{}),s}}async function F0(t,i=Pi(),r){let n=gs(i,t);try{await wt.unlink(n),$("registry",`removed entry ${t}`,r)}catch(e){if(e.code!=="ENOENT")throw e}}async function N0(t,i=Pi(),r){let n=gs(i,t);try{let e=await wt.readFile(n,"utf8"),f=JSON.parse(e);if(!Na(f))return $("registry",`entry ${t} failed validation, removing`,r),await wt.unlink(n).catch(()=>{}),null;return f}catch(e){if(e.code==="ENOENT")return null;if(e instanceof SyntaxError)return $("registry",`entry ${t} has invalid JSON, removing`,r),await wt.unlink(n).catch(()=>{}),null;throw e}}async function pi(t=Pi(),i){let r;try{r=await wt.readdir(t)}catch(e){if(e.code==="ENOENT")return[];throw e}let n=[];for(let e of r){if(!e.endsWith(".json"))continue;let f=e.slice(0,-5);if(!Ss(f))continue;let s=await N0(f,t,i);if(s)n.push(s)}return n}async function Bs(t=Pi(),i){let r=await pi(t,i),n=0;for(let e of r){if(e.pid===void 0)continue;if(!zt(e.pid))$("registry",`GC: pid ${e.pid} for ${e.id} is dead, removing`,i),await F0(e.id,t,i).catch(()=>{}),n++}return n}function ja(t,i={}){let r=i.dir??Pi(),n=i.debounceMs??100,e=i.pollMs??Math.max(n*2,250),f=i.verbose;fe.mkdirSync(r,{recursive:!0});let s=null,c=!1,l=null,h=!1,a=(d)=>{return JSON.stringify(d.map((A)=>({id:A.id,from:A.from,to:A.to,path:A.path,pid:A.pid,pathRewrites:A.pathRewrites,cleanUrls:A.cleanUrls,changeOrigin:A.changeOrigin,static:A.static})).sort((A,T)=>A.id.localeCompare(T.id)))},u=()=>{if(s=null,c)return;pi(r,f).then((d)=>{return l=a(d),t(d)}).catch((d)=>{$("registry",`watcher onChange failed: ${d}`,f)})},y=()=>{if(c)return;if(s)clearTimeout(s);s=setTimeout(u,n)},m=setInterval(()=>{if(c||h)return;h=!0,pi(r,f).then((d)=>{if(a(d)!==l)y()}).catch((d)=>{$("registry",`watcher poll failed: ${d}`,f)}).finally(()=>{h=!1})},e),w=fe.watch(r,{persistent:!0},(d,A)=>{if(A&&/\.tmp\.\d+\.\d+$/.test(A))return;y()});return w.on("error",(d)=>{$("registry",`watcher error: ${d}`,f)}),y(),{close:()=>{if(c=!0,s)clearTimeout(s);clearInterval(m),w.close()}}}var j0=5000;function $t(){return jt.join(Ys(),".stacks","rpx")}function le(t=$t()){return jt.join(t,"daemon.pid")}async function vi(t=$t()){try{let i=await Gt.readFile(le(t),"utf8"),r=Number.parseInt(i.trim(),10);if(!Number.isFinite(r)||r<=0)return null;return r}catch(i){if(i.code==="ENOENT")return null;throw i}}async function W0(t=$t()){let i=await vi(t);return i!==null&&zt(i)}async function X0(t=$t()){await Gt.mkdir(t,{recursive:!0});let i=le(t);while(!0){try{let n=await Gt.open(i,"wx");try{await n.write(`${N.pid}
|
|
154
|
+
`,u=tt(us.tmpdir(),"rpx-trust.ps1");if(await b.writeFile(u,a),Vi(`powershell -ExecutionPolicy Bypass -File "${u}"`),t.verbose)H.success("Successfully added certificate to Windows trust store");h=!0}catch(a){if(t.verbose)H.warn(`Could not trust certificate: ${a}`)}else try{await Af(c,f.certificate,r),h=!0}catch(a){if(t.verbose)H.warn(`Could not add certificate to trust store: ${a}`)}if(yi={key:c.privateKey,cert:c.certificate,ca:f.certificate},t.verbose)H.success(`Certificate generated successfully for ${i.length} domain${i.length>1?"s":""}`);if(!h&&t.verbose)H.warn('If you see certificate warnings in Chrome/Arc, type "thisisunsafe" on the warning page'),H.warn("This will bypass the warning and you should only need to do it once")}function u0(){yi=null}async function ws(t){if(!t)return null;if(yi)return yi;let i=$s(t);try{let[r,n,e]=await Promise.all([b.access(i.keyPath).then(()=>!0).catch(()=>!1),b.access(i.certPath).then(()=>!0).catch(()=>!1),i.caCertPath?b.access(i.caCertPath).then(()=>!0).catch(()=>!1):Promise.resolve(!1)]);if(!r||!n)return $("ssl",`Certificate files don't exist: key=${r}, cert=${n}, paths: ${i.keyPath}, ${i.certPath}`,t.verbose),null;let f="regenerateUntrustedCerts"in t,s=t.regenerateUntrustedCerts,c=f?s!==!1:!0;$("ssl",`Trust check: hasFlag=${f}, flagValue=${s}, shouldCheckTrust=${c}`,t.verbose);let l=i.basePath||tt(ne(),".stacks","ssl"),h=ys(l);if(!(c?await Aa(h.caCertPath,t):!0))return $("ssl","Root CA exists but is not trusted, will regenerate",t.verbose),null;let[u,y,o]=await Promise.all([b.readFile(i.keyPath,"utf8"),b.readFile(i.certPath,"utf8"),e&&i.caCertPath?b.readFile(i.caCertPath,"utf8"):Promise.resolve(void 0)]);if(o&&!o.includes("-----BEGIN CERTIFICATE-----"))return $("ssl","Invalid root CA certificate content, will regenerate",t.verbose),null;if(re(t))try{let{X509Certificate:m}=await import("node:crypto"),d=new m(y).subjectAltName||"",T=t.proxies.map((Y)=>Y.to).filter((Y)=>!d.includes(`DNS:${Y}`));if(T.length>0)return $("ssl",`Certificate missing SANs for: ${T.join(", ")}, will regenerate`,t.verbose),null}catch(m){$("ssl",`Could not verify cert SANs: ${m}`,t.verbose)}return $("ssl","Successfully loaded existing certificates",t.verbose),yi={key:u,cert:y,ca:o},yi}catch(r){return $("ssl",`Error checking existing certificates: ${r}`,t.verbose),null}}function $s(t,i){let r=os(t);$("ssl",`Primary domain: ${r}`,i);let n=ma(t),e=tt(ne(),".stacks","ssl");if(typeof t.https==="object"){let f=t.https.basePath&&t.https.basePath.trim()!==""?t.https.basePath:e,s={domain:r,hostCertCN:r,basePath:f,caCertPath:t.https.caCertPath||n.caCertPath,certPath:t.https.certPath||n.certPath,keyPath:t.https.keyPath||n.keyPath,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:t.https.commonName||r,organizationName:t.https.organizationName||"Local Development",countryName:t.https.countryName||"US",stateName:t.https.stateName||"California",localityName:t.https.localityName||"Playa Vista",validityDays:t.https.validityDays||825,verbose:i||!1,subjectAltNames:Array.from(oa(t)).map((c)=>({type:2,value:c}))};if(ya(t.https.rootCA))s.rootCA=t.https.rootCA;return s}return{domain:r,hostCertCN:r,basePath:e,...n,altNameIPs:["127.0.0.1","::1"],altNameURIs:[],commonName:r,organizationName:"Local Development",countryName:"US",stateName:"California",localityName:"Playa Vista",validityDays:825,verbose:i||!1,subjectAltNames:Array.from(oa(t)).map((f)=>({type:2,value:f}))}}async function QE(t,i){let r=ma({to:t,verbose:i}),n=[r.caCertPath,r.certPath,r.keyPath];$("certificates","Attempting to clean up relating certificates",i),await Promise.all(n.map((e)=>$a(e,i)))}async function Aa(t,i){try{if($("ssl",`Checking if certificate is trusted: ${t}`,i?.verbose),Mt.platform==="darwin"){if(i?.serverName)return mr(t,i.serverName,{verbose:i?.verbose});return dr(t,{verbose:i?.verbose})}else if(Mt.platform==="win32")try{let n=Vi(`openssl x509 -noout -subject -in "${t}"`).toString().trim().split("=").slice(1).join("=").trim()||"";if(!n)return $("ssl","Could not extract certificate subject",i?.verbose),!1;let e=`powershell -Command "Get-ChildItem -Path Cert:\\LocalMachine\\Root | Where-Object { $_.Subject -like '*${n}*' } | Select-Object Subject"`;if(Vi(e).toString().includes(n))return $("ssl","Certificate found in trusted root store",i?.verbose),!0;return $("ssl","Certificate not found in trusted root store",i?.verbose),!1}catch(r){return $("ssl",`Error checking certificate trust on Windows: ${r}`,i?.verbose),!1}else if(Mt.platform==="linux")try{let n=Vi(`openssl x509 -noout -fingerprint -sha256 -in "${t}"`).toString().trim().split("=")[1]?.trim()||"",e=["/etc/ssl/certs","/etc/pki/tls/certs"];for(let f of e)try{if(Vi(`find ${f} -type f -exec openssl x509 -noout -fingerprint -sha256 -in {} \\; 2>/dev/null | grep "${n}"`).toString().includes(n))return $("ssl",`Certificate fingerprint found in ${f}`,i?.verbose),!0}catch{}return $("ssl","Certificate not found in system trust stores",i?.verbose),!1}catch(r){return $("ssl",`Error checking certificate trust on Linux: ${r}`,i?.verbose),!1}return $("ssl",`Platform ${Mt.platform} not supported for certificate trust check`,i?.verbose),!1}catch(r){return $("ssl",`Error checking if certificate is trusted: ${r}`,i?.verbose),!1}}import*as Nt from"node:path";function Ea(t,i){if(typeof t==="string")return{dir:t,spa:!1,pathRewriteStyle:"directory",maxAge:0,cleanUrls:i};return{dir:t.dir,spa:t.spa??!1,pathRewriteStyle:t.pathRewriteStyle??"directory",maxAge:t.maxAge??0,cleanUrls:i}}var o0={".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".json":"application/json; charset=utf-8",".map":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf",".eot":"application/vnd.ms-fontobject",".txt":"text/plain; charset=utf-8",".xml":"application/xml; charset=utf-8",".pdf":"application/pdf",".wasm":"application/wasm",".mp4":"video/mp4",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav"};function y0(t){let i=Nt.extname(t).toLowerCase();return o0[i]??"application/octet-stream"}function w0(t){let i;try{i=decodeURIComponent(t)}catch{return null}if(i.includes("\x00")||i.includes("\\"))return null;let r=Nt.posix.normalize(`/${i}`);if(r.includes(".."))return null;return r.replace(/^\/+/,"")}function $0(t,i){let r=w0(t);if(r===null)return null;let n=Nt.posix.extname(r);if(i.cleanUrls&&n===".html"){let e=t.replace(/\/index\.html$/i,"/").replace(/\.html$/i,"");return{filePath:Nt.join(i.dir,r),redirectTo:e||"/"}}if(r===""||t.endsWith("/"))return{filePath:Nt.join(i.dir,r,"index.html")};if(n!=="")return{filePath:Nt.join(i.dir,r)};if(i.pathRewriteStyle==="flat")return{filePath:Nt.join(i.dir,`${r}.html`)};return{filePath:Nt.join(i.dir,r,"index.html")}}async function Ta(t,i){let r=$0(t,i);if(!r)return new Response("Forbidden",{status:403});if(r.redirectTo)return new Response(null,{status:301,headers:{Location:r.redirectTo}});let n=i.maxAge>0?`public, max-age=${i.maxAge}`:"no-cache",e=Bun.file(r.filePath);if(await e.exists())return new Response(e,{status:200,headers:{"Content-Type":y0(r.filePath),"Cache-Control":n}});if(i.spa){let f=Nt.join(i.dir,"index.html"),s=Bun.file(f);if(await s.exists())return new Response(s,{status:200,headers:{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache"}})}return new Response("Not Found",{status:404})}var m0=new Set(["connection","keep-alive","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade","sec-websocket-key","sec-websocket-version","sec-websocket-extensions"]);function d0(t){return(t.headers.get("host")||"").split(":")[0]}function ga(t,i){if(!i||i==="/")return t;if(t===i)return"/";if(t.startsWith(`${i}/`)){let r=t.slice(i.length);return r===""?"/":r}return t}function Ca(t,i,r){let n=new URL(t.url),e=i.sourceHost??"",s=i.stripBasePathPrefix??!1?ga(n.pathname,i.basePath):n.pathname,c=Sa(s,i.pathRewrites);if(c)e=c.targetHost,s=c.targetPath,$("request",`Path rewrite: ${n.pathname} → ${e}${s}`,r);return{targetHost:e,targetPath:s,search:n.search}}function Ba(t,i){return async(r,n)=>{let e=new URL(r.url),f=d0(r),s=t(f,e.pathname);if(!s)return $("request",`No route found for host: ${f}`,i),new Response(`No proxy configured for ${f}`,{status:404});if(s.static){let y=s.stripBasePathPrefix??!0?ga(e.pathname,s.basePath):e.pathname;return Ta(y,s.static)}if(r.headers.get("upgrade")?.toLowerCase()==="websocket"){if(!n||!s.sourceHost)return new Response("WebSocket upgrade not supported here",{status:400});let{targetHost:u,targetPath:y,search:o}=Ca(r,s,i),m=`ws://${u}${y}${o}`,w={};for(let[T,Y]of r.headers)if(!m0.has(T.toLowerCase())&&T.toLowerCase()!=="host")w[T]=Y;w.host=u,w["x-forwarded-for"]="127.0.0.1",w["x-forwarded-proto"]="https",w["x-forwarded-host"]=f;let d={targetUrl:m,forwardHeaders:w};if(n.upgrade(r,{data:d})){$("ws",`upgraded ${f}${y} → ${m}`,i);return}return new Response("WebSocket upgrade failed",{status:400})}if(!s.sourceHost)return new Response(`No upstream configured for ${f}`,{status:502});let{targetHost:c,targetPath:l,search:h}=Ca(r,s,i),a=`http://${c}${l}${h}`;try{let u=new Headers(r.headers);if(u.set("host",c),s.changeOrigin)u.set("origin",`http://${s.sourceHost}`);u.set("x-forwarded-for","127.0.0.1"),u.set("x-forwarded-proto","https"),u.set("x-forwarded-host",f);let y=await fetch(a,{method:r.method,headers:u,body:r.body,redirect:"manual"});if(s.cleanUrls&&e.pathname.endsWith(".html")){let m=e.pathname.replace(/\.html$/,"");return new Response(null,{status:301,headers:{Location:m}})}let o=new Headers(y.headers);return new Response(y.body,{status:y.status,statusText:y.statusText,headers:o})}catch(u){return $("request",`Proxy error for ${f}: ${u}`,i),new Response(`Proxy Error: ${u}`,{status:502})}}}function Ra(t){let i=new WeakMap;return{open(r){let{targetUrl:n,forwardHeaders:e}=r.data,f;try{f=new WebSocket(n,{headers:e})}catch(c){$("ws",`failed to open upstream ${n}: ${c}`,t),r.close(1011,"upstream connect failed");return}f.binaryType="arraybuffer";let s={upstream:f,upstreamOpen:!1,pending:[]};i.set(r,s),f.addEventListener("open",()=>{s.upstreamOpen=!0;for(let c of s.pending)f.send(c);s.pending=[]}),f.addEventListener("message",(c)=>{r.send(c.data)}),f.addEventListener("close",(c)=>{try{r.close(c.code||1000,c.reason||"")}catch{}}),f.addEventListener("error",()=>{$("ws",`upstream error for ${n}`,t);try{r.close(1011,"upstream error")}catch{}})},message(r,n){let e=i.get(r);if(!e)return;let f=typeof n==="string"?n:new Uint8Array(n);if(e.upstreamOpen)e.upstream.send(f);else e.pending.push(f)},close(r,n,e){let f=i.get(r);if(!f)return;i.delete(r);try{f.upstream.close(n||1000,e||"")}catch{}}}}function ee(t){return t.startsWith("*.")}function ms(t,i){if(!ee(i))return!1;let r=i.slice(1);return t.length>r.length&&t.endsWith(r)}function sT(t,i){let r=t.get(i);if(r!==void 0)return r;let n,e=-1;for(let[f,s]of t){if(!ee(f))continue;if(ms(i,f)){let c=f.length-1;if(c>e)e=c,n=s}}return n}function ds(t){if(!t||t==="/")return"/";let i=t.trim();if(!i.startsWith("/"))i=`/${i}`;return i=i.replace(/\/+$/,""),i===""?"/":i}function A0(t,i){if(i==="/")return!0;if(t===i)return!0;return t.startsWith(`${i}/`)}function Ya(t){let i=new Map;for(let n of t){let e=ds(n.path),f=i.get(n.host);if(!f)f=new Map,i.set(n.host,f);f.set(e,n.route)}let r=new Map;for(let[n,e]of i){let f=[];for(let[s,c]of e)f.push({path:s,route:c});f.sort((s,c)=>c.path.length-s.path.length),r.set(n,f)}return r}function E0(t,i){let r=t.get(i);if(r!==void 0)return r;let n,e=-1;for(let[f,s]of t){if(!ee(f))continue;if(ms(i,f)){let c=f.length-1;if(c>e)e=c,n=s}}return n}function Ua(t,i,r){let n=E0(t,i);if(!n)return;for(let e of n)if(A0(r,e.path))return e.route;return}import*as Ar from"node:fs/promises";import*as As from"node:path";function T0(t){if(!t.endsWith(".crt"))return null;let i=t.slice(0,-4);if(i.length===0)return null;if(i.startsWith("_wildcard."))return`*.${i.slice(10)}`;return i}async function C0(t,i,r,n){try{let[e,f]=await Promise.all([Ar.readFile(i,"utf8"),Ar.readFile(r,"utf8")]);return{serverName:t,cert:e,key:f}}catch(e){return $("sni",`skipping ${t}: ${e.message}`,n),null}}async function Fa(t,i){let r=new Map;if(t.certsDir){let e=[];try{e=await Ar.readdir(t.certsDir)}catch(f){$("sni",`certsDir read failed (${t.certsDir}): ${f.message}`,i)}for(let f of e){let s=T0(f);if(!s)continue;let c=f.slice(0,-4);r.set(s,{certPath:As.join(t.certsDir,f),keyPath:As.join(t.certsDir,`${c}.key`)})}}if(t.domains)for(let[e,f]of Object.entries(t.domains))r.set(e,f);let n=[];for(let[e,f]of r){let s=await C0(e,f.certPath,f.keyPath,i);if(s)n.push(s)}return n}import*as wi from"node:fs/promises";import*as Es from"node:path";var S0=60000;function g0(t,i){if(!i||i.length===0)return!1;return i.some((r)=>{let n=r.startsWith(".")?r.slice(1):r;return t===n||t.endsWith(`.${n}`)})}function B0(t){if(!t||t.length>253)return!1;if(t.includes("/")||t.includes(":")||t.includes(" "))return!1;if(t.startsWith("*"))return!1;return/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i.test(t)}class Ts{config;certsDir;onCertAdded;http01Store;issuer;verbose;negativeCacheMs;certs=new Map;inFlight=new Map;negativeCache=new Map;constructor(t){this.config=t.config,this.certsDir=t.certsDir,this.onCertAdded=t.onCertAdded,this.http01Store=t.http01Store??an,this.issuer=t.issuer??rf,this.verbose=t.verbose??!1,this.negativeCacheMs=t.negativeCacheMs??S0;for(let i of t.initial??[])this.certs.set(i.serverName,i)}get challengeStore(){return this.http01Store}sniEntries(){return Array.from(this.certs.values())}hasCert(t){return this.certs.has(t)}async isApproved(t){if(!B0(t))return!1;if(g0(t,this.config.allowedSuffixes))return!0;if(this.config.ask)try{return await this.config.ask(t)}catch(i){return $("on-demand",`ask(${t}) threw: ${i.message}`,this.verbose),!1}return!1}async ensureCert(t){if(!this.config.enabled)return!1;if(this.certs.has(t))return!0;let i=this.inFlight.get(t);if(i)return i;let r=this.negativeCache.get(t);if(r!==void 0&&Date.now()<r)return $("on-demand",`${t} negatively cached for ${r-Date.now()}ms`,this.verbose),!1;let n=this.issue(t).finally(()=>{this.inFlight.delete(t)});return this.inFlight.set(t,n),n}async issue(t){if(this.certs.has(t))return!0;if(await this.loadFromDisk(t))return!0;if(!await this.isApproved(t))return $("on-demand",`refused issuance for ${t} (not approved)`,this.verbose),!1;try{$("on-demand",`issuing cert for ${t} (staging=${this.config.staging??!1})`,this.verbose);let i=await this.issuer({domains:[t],method:"http-01",http01Store:this.http01Store,email:this.config.email,staging:this.config.staging});await this.persist(t,i.fullChainPem,i.keyPem);let r={serverName:t,cert:i.fullChainPem,key:i.keyPem};return this.certs.set(t,r),this.negativeCache.delete(t),$("on-demand",`issued + installed cert for ${t}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch(i){return this.negativeCache.set(t,Date.now()+this.negativeCacheMs),$("on-demand",`issuance for ${t} failed: ${i.message}`,this.verbose),!1}}async loadFromDisk(t){let{certPath:i,keyPath:r}=this.pathsFor(t);try{let[n,e]=await Promise.all([wi.readFile(i,"utf8"),wi.readFile(r,"utf8")]),f={serverName:t,cert:n,key:e};return this.certs.set(t,f),$("on-demand",`adopted existing on-disk cert for ${t}`,this.verbose),await this.onCertAdded?.(this.sniEntries()),!0}catch{return!1}}pathsFor(t){return{certPath:Es.join(this.certsDir,`${t}.crt`),keyPath:Es.join(this.certsDir,`${t}.key`)}}async persist(t,i,r){await wi.mkdir(this.certsDir,{recursive:!0}).catch(()=>{});let{certPath:n,keyPath:e}=this.pathsFor(t);await Promise.all([wi.writeFile(n,i,"utf8"),wi.writeFile(e,r,{encoding:"utf8",mode:384})])}}import*as fe from"node:fs";import*as wt from"node:fs/promises";import{homedir as R0}from"node:os";import*as Cs from"node:path";import*as se from"node:process";var Y0=/^[a-zA-Z0-9._-]+$/;function Pi(){return Cs.join(R0(),".stacks","rpx","registry.d")}function Ss(t){return typeof t==="string"&&t.length>0&&t.length<=128&&Y0.test(t)}function gs(t,i){if(!Ss(i))throw Error(`invalid registry id: ${JSON.stringify(i)}`);return Cs.join(t,`${i}.json`)}function zt(t){if(!Number.isInteger(t)||t<=0)return!1;try{return se.kill(t,0),!0}catch(i){return i.code==="EPERM"}}function Na(t){if(!t||typeof t!=="object")return!1;let i=t,r=i.pid===void 0||typeof i.pid==="number"&&Number.isInteger(i.pid)&&i.pid>0,n=typeof i.from==="string"&&i.from.length>0,e=typeof i.static==="string"||!!i.static&&typeof i.static==="object"&&typeof i.static.dir==="string",f=i.path===void 0||typeof i.path==="string";return typeof i.id==="string"&&Ss(i.id)&&(n||e)&&typeof i.to==="string"&&i.to.length>0&&f&&r&&typeof i.createdAt==="string"}async function U0(t){await wt.mkdir(t,{recursive:!0})}async function dT(t,i=Pi(),r){if(!Na(t))throw Error(`invalid registry entry: ${JSON.stringify(t)}`);await U0(i);let n=gs(i,t.id),e=`${n}.tmp.${se.pid}.${Date.now()}`,f=JSON.stringify(t,null,2);try{await wt.writeFile(e,f,{encoding:"utf8",mode:420}),await wt.rename(e,n),$("registry",`wrote entry ${t.id} → ${n}`,r)}catch(s){throw await wt.unlink(e).catch(()=>{}),s}}async function F0(t,i=Pi(),r){let n=gs(i,t);try{await wt.unlink(n),$("registry",`removed entry ${t}`,r)}catch(e){if(e.code!=="ENOENT")throw e}}async function N0(t,i=Pi(),r){let n=gs(i,t);try{let e=await wt.readFile(n,"utf8"),f=JSON.parse(e);if(!Na(f))return $("registry",`entry ${t} failed validation, removing`,r),await wt.unlink(n).catch(()=>{}),null;return f}catch(e){if(e.code==="ENOENT")return null;if(e instanceof SyntaxError)return $("registry",`entry ${t} has invalid JSON, removing`,r),await wt.unlink(n).catch(()=>{}),null;throw e}}async function pi(t=Pi(),i){let r;try{r=await wt.readdir(t)}catch(e){if(e.code==="ENOENT")return[];throw e}let n=[];for(let e of r){if(!e.endsWith(".json"))continue;let f=e.slice(0,-5);if(!Ss(f))continue;let s=await N0(f,t,i);if(s)n.push(s)}return n}async function Bs(t=Pi(),i){let r=await pi(t,i),n=0;for(let e of r){if(e.pid===void 0)continue;if(!zt(e.pid))$("registry",`GC: pid ${e.pid} for ${e.id} is dead, removing`,i),await F0(e.id,t,i).catch(()=>{}),n++}return n}function ja(t,i={}){let r=i.dir??Pi(),n=i.debounceMs??100,e=i.pollMs??Math.max(n*2,250),f=i.verbose;fe.mkdirSync(r,{recursive:!0});let s=null,c=!1,l=null,h=!1,a=(d)=>{return JSON.stringify(d.map((A)=>({id:A.id,from:A.from,to:A.to,path:A.path,pid:A.pid,pathRewrites:A.pathRewrites,cleanUrls:A.cleanUrls,changeOrigin:A.changeOrigin,static:A.static})).sort((A,T)=>A.id.localeCompare(T.id)))},u=()=>{if(s=null,c)return;pi(r,f).then((d)=>{return l=a(d),t(d)}).catch((d)=>{$("registry",`watcher onChange failed: ${d}`,f)})},y=()=>{if(c)return;if(s)clearTimeout(s);s=setTimeout(u,n)},m=setInterval(()=>{if(c||h)return;h=!0,pi(r,f).then((d)=>{if(a(d)!==l)y()}).catch((d)=>{$("registry",`watcher poll failed: ${d}`,f)}).finally(()=>{h=!1})},e),w=fe.watch(r,{persistent:!0},(d,A)=>{if(A&&/\.tmp\.\d+\.\d+$/.test(A))return;y()});return w.on("error",(d)=>{$("registry",`watcher error: ${d}`,f)}),y(),{close:()=>{if(c=!0,s)clearTimeout(s);clearInterval(m),w.close()}}}var j0=5000;function $t(){return jt.join(Ys(),".stacks","rpx")}function le(t=$t()){return jt.join(t,"daemon.pid")}async function vi(t=$t()){try{let i=await Gt.readFile(le(t),"utf8"),r=Number.parseInt(i.trim(),10);if(!Number.isFinite(r)||r<=0)return null;return r}catch(i){if(i.code==="ENOENT")return null;throw i}}async function W0(t=$t()){let i=await vi(t);return i!==null&&zt(i)}async function X0(t=$t()){await Gt.mkdir(t,{recursive:!0});let i=le(t);while(!0){try{let n=await Gt.open(i,"wx");try{await n.write(`${N.pid}
|
|
155
155
|
`)}finally{await n.close()}return i}catch(n){if(n.code!=="EEXIST")throw n}let r=await vi(t);if(r!==null&&zt(r))throw Error(`rpx daemon already running (pid=${r})`);await Gt.unlink(i).catch(()=>{})}}async function Er(t=$t()){await Gt.unlink(le(t)).catch(()=>{})}function _0(t){let i=t.cleanUrls??!1,r=ds(t.path);if(t.static)return{static:Ea(t.static,i),cleanUrls:i,basePath:r};let n=t.from??"localhost:1";return{sourceHost:new URL(n.startsWith("http")?n:`http://${n}`).host,cleanUrls:i,changeOrigin:t.changeOrigin??!1,pathRewrites:t.pathRewrites,basePath:r}}function I0(t){return t.find((r)=>!/^api\./.test(r)&&!/^docs\./.test(r)&&!/^dashboard\./.test(r))??t[0]??"rpx.localhost"}async function J0(t,i){let r=await pi(i,t.verbose),n=[...new Set(r.map((a)=>a.to))],e=I0(n),f=[...new Set([e,...n,"rpx.localhost"])],s=jt.join(Ys(),".stacks","ssl"),c=jt.join(s,"rpx.localhost.crt"),l={https:typeof t.https==="object"?{...t.https,certPath:c,keyPath:jt.join(s,"rpx.localhost.key"),commonName:e}:{certPath:c,keyPath:jt.join(s,"rpx.localhost.key"),caCertPath:jt.join(s,"rpx.localhost.ca.crt"),commonName:e},verbose:t.verbose,regenerateUntrustedCerts:!0,...f.length>1?{proxies:f.map((a)=>({from:"localhost:1",to:a}))}:{to:e,from:"localhost:1"}},h=await ws(l);if(!h)$("daemon","no usable cert on disk, generating one",t.verbose),await da(l),h=await ws(l);if(!h)throw Error("failed to bootstrap TLS for rpx daemon");return h}async function O0(t,i,r,n){let e=N.env.SUDO_PASSWORD,f=N.env.HOME??Ys(),s=[N.execPath,...N.argv.slice(1)],c=[`HOME=${f}`,`PATH=${N.env.PATH??""}`];if(n)c.push("RPX_VERBOSE=1");let l=e?["-S","-p","","env",...c,...s]:["-n","env",...c,...s];$("daemon",`elevating daemon via sudo for privileged ports ${i}/${r}`,n);let h=Wa("sudo",l,{detached:!0,stdio:["pipe","ignore","ignore"]}),a=null,u=null;if(h.once("error",(m)=>{a=m}),h.once("exit",(m)=>{u=m??0}),e&&h.stdin)h.stdin.write(`${e}
|
|
156
156
|
`),h.stdin.end();h.unref();let y=le(t),o=Date.now()+15000;while(Date.now()<o){if(a)throw a;let m=await vi(t);if(m!==null&&zt(m)){if(n)H.success(`rpx daemon elevated to root (pid=${m}, https on :${i})`);return{httpsPort:i,httpPort:r,pidPath:y,done:Promise.resolve(),stop:async()=>{try{N.kill(m,"SIGTERM")}catch{}},ensureCert:()=>Promise.resolve(!1)}}if(u!==null&&u!==0)throw Error(`rpx daemon could not elevate to bind :${i} (sudo exited ${u}). Set SUDO_PASSWORD in .env or run \`sudo -v\` first.`);await new Promise((w)=>setTimeout(w,50))}throw Error(`rpx daemon failed to elevate within 15000ms (rpxDir=${t})`)}async function XT(t={}){let i=t.verbose??!1,r=t.rpxDir??$t(),n=t.registryDir??jt.join(r,"registry.d"),e=t.httpsPort??443,f=t.httpPort??80,s=t.hostname??"0.0.0.0",c=t.gcIntervalMs??j0,l=e>0&&e<1024||f>0&&f<1024,h=typeof N.getuid==="function"&&N.getuid()===0;if(N.platform!=="win32"&&l&&!h)return O0(r,e,f,i);let a=await X0(r),u=new Map,y=(S,L)=>Ua(u,S,L);function o(S){u=Ya(S.map((Ct)=>({host:Ct.to,path:Ct.path,route:_0(Ct)})));let L=Array.from(u.keys());$("daemon",`routing table now covers ${L.length} host(s): ${L.join(", ")||"<empty>"}`,i)}await Bs(n,i).catch((S)=>{$("daemon",`initial gc failed: ${S}`,i)});let m=await pi(n,i);o(m),await Cr({rpxDir:r,verbose:i}).catch((S)=>{$("daemon",`DNS reconcile on start failed: ${S}`,i)}),await Rs(m,{rpxDir:r,verbose:i,ownerPid:N.pid}).catch((S)=>{$("daemon",`DNS setup on start failed: ${S}`,i)});let w=[];if(t.productionCerts){if(w=await Fa(t.productionCerts,i),i&&w.length>0)H.info(`SNI: serving ${w.length} real cert(s): ${w.map((S)=>S.serverName).join(", ")}`)}let d=Ba(y,i),A=Ra(i),T=null;if(w.length===0)T=await J0(t,n);let Y=t.onDemandTls,g=Y?.enabled?new Ts({config:Y,certsDir:Y.certsDir??t.productionCerts?.certsDir??jt.join(r,"on-demand-certs"),initial:w,verbose:i,onCertAdded:(S)=>{Qi(S)}}):null;function X(S){if(S.length>0)return S.map((L)=>({serverName:L.serverName,cert:L.cert,key:L.key}));return{key:T.key,cert:T.cert,ca:T.ca,requestCert:!1,rejectUnauthorized:!1}}function xt(S){return Bun.serve({port:e,hostname:s,tls:X(S),fetch(L,Ct){return d(L,Ct)},websocket:A,error(L){return $("daemon",`https server error: ${L}`,i),new Response(`Server Error: ${L.message}`,{status:500})}})}let Wt=xt(g?g.sniEntries():w);async function Qi(S){if(gr)return;$("daemon",`rebuilding :443 with ${S.length} SNI cert(s)`,i),Wt.stop(!1);let L;for(let Ct=0;Ct<20&&!gr;Ct++)try{Wt=xt(S);return}catch(Br){L=Br,await new Promise((qa)=>setTimeout(qa,25))}H.error(`rpx: failed to rebuild :443 after issuing cert: ${L?.message}`)}let Lt=null;if(f>0)Lt=Bun.serve({port:f,hostname:s,fetch(S){let L=new URL(S.url),Ct=(S.headers.get("host")??L.hostname).split(":")[0];if(g&&L.pathname.startsWith("/.well-known/acme-challenge/")){let Br=g.challengeStore.handlePath(L.pathname);if(Br!==void 0)return new Response(Br,{status:200,headers:{"content-type":"text/plain"}});return new Response("challenge not found",{status:404})}if(g&&!g.hasCert(Ct))g.ensureCert(Ct).catch(()=>{});return new Response(null,{status:301,headers:{Location:`https://${Ct}${L.pathname}${L.search}`}})}});if(i)H.success(`rpx daemon listening on https://${s}:${e}${Lt?` (http→https on :${f})`:""}`),H.info(`pid file: ${a}`),H.info(`registry: ${n}`);let xa=ja((S)=>{o(S),Rs(S,{rpxDir:r,verbose:i,ownerPid:N.pid}).catch((L)=>{$("daemon",`DNS sync on registry change failed: ${L}`,i)})},{dir:n,verbose:i}),oe=setInterval(()=>{Bs(n,i).then((S)=>{if(S>0)$("daemon",`gc reaped ${S} stale entries`,i)}).catch((S)=>{$("daemon",`periodic gc failed: ${S}`,i)})},c);if(typeof oe.unref==="function")oe.unref();let gr=!1,Js,ye=new Promise((S)=>{Js=S});async function Os(){if(gr)return ye;if(gr=!0,clearInterval(oe),xa.close(),Wt.stop(!1),Lt?.stop(!1),await Tr({rpxDir:r,verbose:i}).catch((S)=>{$("daemon",`DNS teardown failed: ${S}`,i)}),await Er(r),i)H.info("rpx daemon stopped");return Js(),ye}let Ks=(S)=>{$("daemon",`received ${S}, shutting down`,i),Os().catch(()=>{})};return N.once("SIGINT",Ks),N.once("SIGTERM",Ks),{stop:Os,done:ye,httpsPort:typeof Wt.port==="number"?Wt.port:e,httpPort:Lt&&typeof Lt.port==="number"?Lt.port:f,pidPath:a,ensureCert:(S)=>g?g.ensureCert(S):Promise.resolve(!1)}}function K0(){let t=N.execPath,i=jt.basename(t).toLowerCase();if((i==="bun"||i==="node"||i.startsWith("bun-"))&&N.argv[1])return[t,N.argv[1],"daemon:start"];return[t,"daemon:start"]}async function _T(t={}){let i=t.rpxDir??$t(),r=t.verbose??!1;await Cr({rpxDir:i,verbose:r}).catch((a)=>{$("daemon",`DNS reconcile before ensureDaemonRunning: ${a}`,r)});let n=await vi(i);if(n!==null&&zt(n))return $("daemon",`ensureDaemonRunning: already running pid=${n}`,r),{pid:n,spawned:!1};if(n!==null)$("daemon",`ensureDaemonRunning: clearing stale pid=${n}`,r),await Er(i);await Gt.mkdir(i,{recursive:!0});let e=t.spawnCommand??K0();if(e.length===0)throw Error("ensureDaemonRunning: spawnCommand is empty");$("daemon",`spawning daemon: ${e.join(" ")}`,r);let f=Wa(e[0],e.slice(1),{detached:!0,stdio:"ignore",cwd:t.spawnCwd??N.cwd(),env:t.spawnEnv?{...N.env,...t.spawnEnv}:N.env});f.unref();let s=null;f.once("error",(a)=>{s=a});let c=t.startupTimeoutMs??5000,l=t.pollIntervalMs??50,h=Date.now()+c;while(Date.now()<h){if(s)throw s;let a=await vi(i);if(a!==null&&zt(a))return $("daemon",`daemon registered with pid=${a}`,r),{pid:a,spawned:!0};await new Promise((u)=>setTimeout(u,l))}if(s)throw s;throw Error(`rpx daemon failed to start within ${c}ms (rpxDir=${i})`)}async function IT(t={}){let i=t.rpxDir??$t(),r=t.verbose??!1,n=t.timeoutMs??5000,e=t.pollIntervalMs??50,f=t.forceAfterTimeout??!0,s=await vi(i);if(s===null||!zt(s)){if(s!==null)await Er(i);return await Cr({rpxDir:i,verbose:r}).catch(()=>{}),{stopped:!1,pid:s,forced:!1}}try{N.kill(s,"SIGTERM")}catch(l){if(l.code==="ESRCH")return await Er(i),{stopped:!1,pid:s,forced:!1};throw l}let c=Date.now()+n;while(Date.now()<c){if(!zt(s))return $("daemon",`daemon pid=${s} stopped cleanly`,r),{stopped:!0,pid:s,forced:!1};await new Promise((l)=>setTimeout(l,e))}if(!f)throw Error(`rpx daemon (pid=${s}) did not exit within ${n}ms`);$("daemon",`daemon pid=${s} did not exit, escalating to SIGKILL`,r);try{N.kill(s,"SIGKILL")}catch(l){if(l.code!=="ESRCH")throw l}return await Er(i),await Tr({rpxDir:i,verbose:r}).catch((l)=>{$("daemon",`DNS teardown after SIGKILL: ${l}`,r)}),{stopped:!0,pid:s,forced:!0}}async function JT(t={}){let i=t.rpxDir??$t();if(await W0(i))return;await Cr({rpxDir:i,verbose:t.verbose})}import*as $i from"node:fs/promises";import{homedir as H0}from"node:os";import*as Us from"node:path";var ce=1,M0="dns-state.json",Xa=["com","test","dev","app","page","local","localhost","example","invalid"];function _a(){return Us.join(H0(),".stacks","rpx")}function Fs(t=_a()){return Us.join(t,M0)}async function he(t=_a()){try{let i=await $i.readFile(Fs(t),"utf8"),r=JSON.parse(i);if(r.version!==ce||!Array.isArray(r.resolvers))return null;return{version:ce,resolvers:r.resolvers.filter((n)=>typeof n==="string"),domains:Array.isArray(r.domains)?r.domains.filter((n)=>typeof n==="string"):[],ownerPid:typeof r.ownerPid==="number"?r.ownerPid:null,updatedAt:typeof r.updatedAt==="string"?r.updatedAt:""}}catch(i){if(i.code==="ENOENT")return null;throw i}}async function Ia(t,i){await $i.mkdir(t,{recursive:!0}),await $i.writeFile(Fs(t),`${JSON.stringify(i,null,2)}
|
|
157
157
|
`,"utf8")}async function Ns(t){await $i.rm(Fs(t),{force:!0})}function Ja(t){let i=t.trim().toLowerCase().replace(/\.$/,"");if(!i||i.includes("127.0.0.1"))return null;if(i==="localhost"||i.endsWith(".localhost"))return null;if(/^\d{1,3}(\.\d{1,3}){3}$/.test(i))return null;return i}function z0(t){let i=Ja(t);if(!i)return null;let r=i.split(".");if(r.length<2)return null;return r.slice(-2).join(".")}function js(t){let i=new Set;for(let r of t){let n=z0(r);if(n)i.add(n)}return Array.from(i).sort()}function Ws(t){let i=new Set;for(let r of t){let n=Ja(r);if(n)i.add(n)}return Array.from(i).sort()}var Xs=15353,G0="# managed-by: rpx",Ha="/etc/resolver",mt=null,ae=new Set;function x0(t){return{id:t.readUInt16BE(0),flags:t.readUInt16BE(2),qdcount:t.readUInt16BE(4),ancount:t.readUInt16BE(6),nscount:t.readUInt16BE(8),arcount:t.readUInt16BE(10)}}function Ma(t,i){let r=[],n=i;while(!0){let e=t[n];if(e===0){n++;break}if((e&192)===192){let f=t.readUInt16BE(n)&16383,{name:s}=Ma(t,f);r.push(s),n+=2;break}n++,r.push(t.subarray(n,n+e).toString("ascii")),n+=e}return{name:r.join("."),newOffset:n}}function q0(t,i){let{name:r,newOffset:n}=Ma(t,i),e=t.readUInt16BE(n),f=t.readUInt16BE(n+2);return{question:{name:r,type:e,class:f},newOffset:n+4}}function ue(t){let i=t.split("."),r=[];for(let n of i)r.push(Buffer.from([n.length])),r.push(Buffer.from(n,"ascii"));return r.push(Buffer.from([0])),Buffer.concat(r)}function k0(t,i,r){let n=[],e=Buffer.alloc(12);e.writeUInt16BE(t,0),e.writeUInt16BE(33152,2),e.writeUInt16BE(1,4),e.writeUInt16BE(1,6),e.writeUInt16BE(0,8),e.writeUInt16BE(0,10),n.push(e),n.push(ue(i.name));let f=Buffer.alloc(4);f.writeUInt16BE(i.type,0),f.writeUInt16BE(i.class,2),n.push(f),n.push(ue(i.name));let s=Buffer.alloc(10);if(s.writeUInt16BE(i.type,0),s.writeUInt16BE(1,2),s.writeUInt32BE(300,4),i.type===1){s.writeUInt16BE(4,8),n.push(s);let c=r.split(".").map((l)=>Number.parseInt(l,10));n.push(Buffer.from(c))}else if(i.type===28)s.writeUInt16BE(16,8),n.push(s),n.push(Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]));else return e.writeUInt16BE(33155,2),e.writeUInt16BE(0,6),Buffer.concat([e,ue(i.name),f]);return Buffer.concat(n)}function D0(t,i){let r=[],n=Buffer.alloc(12);n.writeUInt16BE(t,0),n.writeUInt16BE(33155,2),n.writeUInt16BE(1,4),n.writeUInt16BE(0,6),n.writeUInt16BE(0,8),n.writeUInt16BE(0,10),r.push(n),r.push(ue(i.name));let e=Buffer.alloc(4);return e.writeUInt16BE(i.type,0),e.writeUInt16BE(i.class,2),r.push(e),Buffer.concat(r)}async function b0(t,i){if(fi.platform!=="darwin")return!1;let r=Ws(t);if(r.length===0)return!1;if(mt){for(let n of r)ae.add(n);return $("dns","DNS server already running — merged domains",i),!0}return ae=new Set(r),new Promise((n)=>{mt=L0.createSocket("udp4"),mt.on("error",(e)=>{$("dns",`DNS server error: ${e.message}`,i),mt?.close(),mt=null,n(!1)}),mt.on("message",(e,f)=>{try{let s=x0(e),{question:c}=q0(e,12);$("dns",`Query for ${c.name} type ${c.type} from ${f.address}`,i);let l=c.name.toLowerCase(),h=!1;for(let u of ae)if(l===u||l.endsWith(`.${u}`)){h=!0;break}let a;if(h&&(c.type===1||c.type===28))a=k0(s.id,c,"127.0.0.1"),$("dns",`Responding with localhost for ${c.name}`,i);else a=D0(s.id,c),$("dns",`NXDOMAIN for ${c.name}`,i);mt?.send(a,f.port,f.address)}catch(s){$("dns",`Error processing DNS query: ${s}`,i)}}),mt.on("listening",()=>{let e=mt?.address();$("dns",`DNS server listening on ${e?.address}:${e?.port}`,i),n(!0)});try{mt.bind(Xs,"127.0.0.1")}catch(e){$("dns",`Failed to bind DNS server: ${e}`,i),n(!1)}})}function za(t){if(mt)$("dns","Stopping DNS server",t),mt.close(),mt=null,ae=new Set}function qT(){return mt!==null}function V0(){return`${G0}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type {
|
|
|
10
10
|
StopDaemonResult,
|
|
11
11
|
} from './daemon';
|
|
12
12
|
export type { GetRoute, ProxyFetchHandler, ProxyRoute, ProxyServer } from './proxy-handler';
|
|
13
|
+
export type { OriginGuard, OriginGuardOptions } from './origin-guard';
|
|
13
14
|
export type { HostRoutes, PathRoute } from './host-routes';
|
|
14
15
|
export type { ResolvedStaticRoute, StaticResolution } from './static-files';
|
|
15
16
|
export type { SniTlsEntry } from './sni';
|
|
@@ -105,6 +106,7 @@ export {
|
|
|
105
106
|
} from './daemon';
|
|
106
107
|
export { createProxyFetchHandler, createProxyWebSocketHandler, stripBasePath } from './proxy-handler';
|
|
107
108
|
export { isWildcardPattern, matchesWildcard, matchHost } from './host-match';
|
|
109
|
+
export { createOriginGuard } from './origin-guard';
|
|
108
110
|
export {
|
|
109
111
|
buildHostRoutes,
|
|
110
112
|
matchHostList,
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import{$ as
|
|
1
|
+
import{$ as X_,A as M_,Aa as WD,B as H2,Ba as ZD,C as F_,Ca as SD,D as k2,Da as VD,E as C2,Ea as AD,F as x2,Fa as zD,G as f2,Ga as J_,H as q2,Ha as MD,I as T_,Ia as FD,J as I_,K as j_,L as q_,M as y2,N as w_,O as U2,P as E_,Q as O2,R as H_,S as P2,T as L2,U as c2,V as h2,W as u2,X as R_,Y as Y_,Z as m2,_ as $_,a as J,aa as v2,b as N_,ba as b2,c as G2,ca as l2,d as K2,da as a2,e as R2,ea as d2,f as Y2,fa as g2,g as $2,ga as p2,h as X2,ha as i2,i as J2,ia as n2,j as Q2,ja as t2,k as W2,ka as r2,l as Z2,la as s2,m as S2,ma as o2,n as V2,na as e2,o as A2,oa as _D,p as z2,pa as DD,q as M2,qa as BD,r as F2,ra as ND,s as T2,sa as GD,t as I2,ta as KD,u as j2,ua as RD,v as w2,va as YD,w as G_,wa as $D,x as E2,xa as XD,y as b,ya as JD,z as K_,za as QD}from"./chunk-zx2ghrc1.js";import{Ja as z_,Ka as l,La as UB,Ma as N,Na as OB,Oa as y,Pa as PB,Qa as LB,Ra as cB,Sa as hB,Ta as uB,Ua as mB,Va as vB,Wa as bB,Xa as lB}from"./chunk-rbgb5fyg.js";import{execSync as n_}from"node:child_process";import*as c from"node:http";import*as v_ from"node:http2";import*as b_ from"node:net";import*as I from"node:process";var t=(D,_)=>(B)=>`\x1B[${D}m${B}\x1B[${_}m`,f={bold:t(1,22),dim:t(2,22),green:t(32,39),cyan:t(36,39)};import*as U_ from"node:fs";import*as O_ from"node:path";import*as q from"node:process";function P_(D,_){let G=(_&&_!=="/"?`${D}${_}`:D).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return G.length>0?G:"rpx"}async function r(D){if(D.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let _=D.verbose??!1,B=D.registryDir,G=new Set,K=D.proxies.map((z)=>{let j=z.id??P_(z.to,z.path);if(!Y_(j))throw Error(`invalid registry id "${j}" derived from to="${z.to}"`);if(G.has(j))throw Error(`duplicate registry id "${j}" — set an explicit \`id\` on one of the proxies`);return G.add(j),{...z,id:j}}),X=new Date().toISOString();for(let z of K)await $_({id:z.id,from:z.from,to:z.to,path:z.path,pid:D.persistent?void 0:q.pid,cwd:q.cwd(),createdAt:X,cleanUrls:z.cleanUrls,changeOrigin:z.changeOrigin,pathRewrites:z.pathRewrites,static:z.static},B,_);let $=await J_({rpxDir:D.rpxDir,verbose:_,spawnCommand:D.spawnCommand,startupTimeoutMs:D.startupTimeoutMs,spawnEnv:D.spawnEnv});for(let z of K){let j=z.static?`static ${typeof z.static==="string"?z.static:z.static.dir}`:z.from;J.success(`https://${z.to} → ${j}`)}if(J.info(`(via rpx daemon pid=${$.pid}; \`rpx daemon:status\` to inspect)`),D.detached)return;let V=!1,R=B??R_(),Q=K.map((z)=>z.id),T=async()=>{if(V)return;V=!0;for(let z of Q)await X_(z,B,_).catch((j)=>{N("runner",`removeEntry(${z}) failed: ${j}`,_)})},w=(z)=>{N("runner",`received ${z}, unregistering ${Q.length} entries`,_),T().finally(()=>q.exit(0))};q.once("SIGINT",w),q.once("SIGTERM",w),q.once("exit",()=>{if(V)return;for(let z of Q)try{U_.unlinkSync(O_.join(R,`${z}.json`))}catch{}}),await new Promise(()=>{})}import{exec as d_}from"node:child_process";import P from"node:fs";import c_ from"node:os";import W_ from"node:path";import*as a from"node:process";import{promisify as g_}from"node:util";var s=g_(d_);function L_(D){let _=D.trim().toLowerCase();return _==="localhost"||_.endsWith(".localhost")||_.endsWith(".localhost.")}var C=a.platform==="win32"?W_.join(a.env.windir||"C:\\Windows","System32","drivers","etc","hosts"):"/etc/hosts",Q_=!1;async function o(D){if(a.platform==="win32")throw Error("Administrator privileges required on Windows");let _=l(),B=D.replace(/'/g,"'\\''");try{if(_){let{stdout:G}=await s(`echo '${_}' | sudo -S sh -c '${B}' 2>/dev/null`);return Q_=!0,G}if(Q_)try{let{stdout:G}=await s(`sudo -n sh -c '${B}'`);return G}catch(G){N("hosts","Cached sudo privileges expired, requesting again",!0)}try{let{stdout:G}=await s(`sudo -n sh -c '${B}'`);return Q_=!0,G}catch{throw Error("sudo required but no cached credentials (set SUDO_PASSWORD in .env or run sudo -v)")}}catch(G){throw Error(`Failed to execute sudo command: ${G.message}`)}}async function m(D,_){let B=D.filter((K)=>!L_(K)),G=D.filter((K)=>L_(K));if(G.length>0)N("hosts",`Skipping /etc/hosts for loopback dev names: ${G.join(", ")}`,_);if(B.length===0)return;N("hosts",`Adding hosts: ${B.join(", ")}`,_),N("hosts",`Using hosts file at: ${C}`,_);try{let K;try{K=await P.promises.readFile(C,"utf-8")}catch{N("hosts","Reading hosts file requires elevated permissions, using sudo",_);try{K=await o(`cat "${C}"`)}catch(R){throw console.log(" Could not read hosts file — skipping hosts setup"),N("hosts",`sudo read also failed: ${R}`,_),Error(`Cannot read hosts file: ${R}`)}}let X=B.filter((R)=>{let Q=`127.0.0.1 ${R}`,T=`::1 ${R}`;return!K.includes(Q)&&!K.includes(T)});if(X.length===0){N("hosts","All hosts already exist in hosts file",_);return}let $=X.map((R)=>`
|
|
2
2
|
# Added by rpx
|
|
3
|
-
127.0.0.1 ${
|
|
4
|
-
::1 ${
|
|
5
|
-
`),
|
|
6
|
-
`),
|
|
3
|
+
127.0.0.1 ${R}
|
|
4
|
+
::1 ${R}`).join(`
|
|
5
|
+
`),V=W_.join(c_.tmpdir(),`rpx-hosts-${Date.now()}.tmp`);try{await P.promises.writeFile(V,K+$,"utf8"),await o(`cat "${V}" | tee "${C}" > /dev/null`),console.log(` Hosts updated: ${X.join(", ")}`)}catch(R){console.log(" Could not update hosts file automatically"),console.log(" Add these entries to /etc/hosts:"),X.forEach((Q)=>{console.log(` 127.0.0.1 ${Q}`),console.log(` ::1 ${Q}`)}),console.log(` Or run: sudo nano ${C}`)}finally{try{await P.promises.unlink(V)}catch{}}}catch(K){N("hosts",`Failed to manage hosts file: ${K.message}`,_)}}async function Z_(D,_){N("hosts",`Removing hosts: ${D.join(", ")}`,_);try{let B;try{B=await P.promises.readFile(C,"utf-8")}catch{N("hosts","Reading hosts file requires elevated permissions, using sudo",_);try{B=await o(`cat "${C}"`)}catch(R){throw N("hosts",`sudo read also failed: ${R}`,_),Error(`Cannot read hosts file: ${R}`)}}let G=B.split(`
|
|
6
|
+
`),K=!1,X=G.filter((R)=>{if(D.some((T)=>R.includes(` ${T}`)&&(R.includes("127.0.0.1")||R.includes("::1"))))return K=!0,!1;if(R.trim()==="# Added by rpx")return K=!0,!1;return!0});if(!K){N("hosts","No matching hosts found to remove",_);return}while(X[X.length-1]?.trim()==="")X.pop();let $=`${X.join(`
|
|
7
7
|
`)}
|
|
8
|
-
`,M=Q_.join(L_.tmpdir(),`rpx-hosts-${Date.now()}.tmp`);try{await P.promises.writeFile(M,$,"utf8"),await o(`cat "${M}" | tee "${C}" > /dev/null`),N("hosts","Hosts removed successfully",_)}catch(W){N("hosts","Could not clean up hosts file automatically",_)}finally{try{await P.promises.unlink(M)}catch(W){N("hosts",`Failed to remove temporary file: ${W}`,_)}}}catch(B){N("hosts",`Failed to clean up hosts file: ${B.message}`,_)}}async function v(D,_){N("hosts",`Checking hosts: ${D}`,_);let B;try{B=await P.promises.readFile(C,"utf-8")}catch(K){N("hosts",`Error reading hosts file: ${K}`,_);try{let R=l(),G;if(R)G=`echo '${R}' | sudo -S cat "${C}" 2>/dev/null`;else G=`sudo -n cat "${C}" 2>/dev/null || cat "${C}" 2>/dev/null || echo ""`;let{stdout:$}=await s(G);B=$}catch(R){return N("hosts",`Cannot read hosts file, assuming entries don't exist: ${R}`,_),D.map(()=>!1)}}return D.map((K)=>{let R=`127.0.0.1 ${K}`,G=`::1 ${K}`;return B.includes(R)||B.includes(G)})}import*as e from"node:net";function U(D,_,B){return N("port",`Checking if port ${D} is in use on ${_}`,B),new Promise((K)=>{let R=e.createServer(),G=setTimeout(()=>{N("port",`Checking port ${D} timed out, assuming it's in use`,B),R.close(),K(!0)},3000);R.once("error",($)=>{if(clearTimeout(G),$.code==="EADDRINUSE")N("port",`Port ${D} is in use`,B),K(!0);else N("port",`Error checking port ${D}: ${$.message}`,B),K(!0)}),R.once("listening",()=>{clearTimeout(G),N("port",`Port ${D} is available`,B),R.close(),K(!1)});try{R.listen(D,_)}catch($){clearTimeout(G),N("port",`Exception checking port ${D}: ${$}`,B),K(!0)}})}async function h_(D,_,B,K=50){N("port",`Finding available port starting from ${D} (max attempts: ${K})`,B);let R=D,G=0;while(G<K){if(G++,!await U(R,_,B))return N("port",`Found available port: ${R} after ${G} attempts`,B),R;N("port",`Port ${R} is in use, trying ${R+1} (attempt ${G}/${K})`,B),R++}throw Error(`Unable to find available port after ${K} attempts starting from ${D}`)}function c_(D,_,B=5000,K){return N("port",`Testing connection to ${_}:${D}`,K),new Promise((R)=>{let G=e.connect({host:_,port:D,timeout:B});G.once("connect",()=>{N("port",`Successfully connected to ${_}:${D}`,K),G.end(),R(!0)}),G.once("timeout",()=>{N("port",`Connection to ${_}:${D} timed out`,K),G.destroy(),R(!1)}),G.once("error",($)=>{N("port",`Failed to connect to ${_}:${D}: ${$.message}`,K),G.destroy(),R(!1)})})}class d{usedPorts=new Set;hostname;verbose;maxRetries;constructor(D="0.0.0.0",_,B=50){this.hostname=D,this.verbose=_,this.maxRetries=B}async getNextAvailablePort(D,_=!1){if(this.usedPorts.has(D))return this.findNextAvailablePort(D+1,_);if(await U(D,this.hostname,this.verbose))return this.findNextAvailablePort(D+1,_);if(_){if(!await c_(D,this.hostname,3000,this.verbose))return N("port",`Port ${D} is available but not connectable, trying next port`,this.verbose),this.findNextAvailablePort(D+1,_)}return this.usedPorts.add(D),D}async findNextAvailablePort(D,_=!1){let B=await h_(D,this.hostname,this.verbose,this.maxRetries);if(_){if(!await c_(B,this.hostname,3000,this.verbose))if(B<D+this.maxRetries)return this.findNextAvailablePort(B+1,_);else throw Error(`Unable to find a connectable port after ${this.maxRetries} attempts`)}return this.usedPorts.add(B),B}releasePort(D){N("port",`Releasing port ${D}`,this.verbose),this.usedPorts.delete(D)}}var p_=new d;import{spawn as g_}from"node:child_process";import*as L from"node:process";class __{processes=new Map;isShuttingDown=!1;async startProcess(D,_,B){if(this.processes.has(D)){N("start",`Process ${D} is already running`,B);return}let[K,...R]=_.command.split(" "),G=_.cwd||L.cwd();N("start",`Starting process ${D}:`,B),N("start",` Command: ${K} ${R.join(" ")}`,B),N("start",` Working directory: ${G}`,B),N("start",` Environment variables: ${y(_.env)}`,B);let $=g_(K,R,{cwd:G,env:{...L.env,..._.env},shell:!0,stdio:"inherit"});return this.processes.set(D,{command:_.command,cwd:G,process:$,env:_.env}),new Promise((M,W)=>{if($.on("error",(Q)=>{if(!this.isShuttingDown)N("start",`Process ${D} failed to start: ${Q}`,B),this.processes.delete(D),W(Q),L.emit("SIGINT")}),$.on("exit",(Q)=>{if(!this.isShuttingDown&&Q!==null&&Q!==0)N("start",`Process ${D} exited with code ${Q}`,B),this.processes.delete(D),W(Error(`Process ${D} exited with code ${Q}`)),L.emit("SIGINT")}),B)$.stdout?.on("data",(Q)=>{N("process",`[${D}] ${Q.toString().trim()}`,!0)}),$.stderr?.on("data",(Q)=>{N("process",`[${D}] ERR: ${Q.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&$.killed)this.processes.delete(D),W(Error(`Process ${D} was killed during startup`));else N("start",`Process ${D} started successfully`,B),M()},1000)})}async stopProcess(D,_){let B=this.processes.get(D);if(!B?.process){N("start",`No process found for ${D}`,_);return}return N("start",`Stopping process ${D}`,_),new Promise((K)=>{if(!B.process){K();return}B.process.once("exit",()=>{this.processes.delete(D),N("start",`Process ${D} stopped`,_),K()});try{B.process.kill("SIGTERM"),setTimeout(()=>{if(B.process){N("start",`Force killing process ${D}`,_);try{B.process.kill("SIGKILL")}catch(R){}}},3000)}catch(R){N("start",`Error stopping process ${D}: ${R}`,_),this.processes.delete(D),K()}})}async stopAll(D){if(this.isShuttingDown){N("start","Already shutting down, skipping duplicate stopAll call",D);return}this.isShuttingDown=!0,N("start","Stopping all processes",D);let _=Array.from(this.processes.keys()).map((B)=>this.stopProcess(B,D).catch((K)=>{X.error(`Failed to stop process ${B}:`,K)}));await Promise.allSettled(_),this.processes.clear(),this.isShuttingDown=!1}isRunning(D){let _=this.processes.get(D);return!!_?.process&&!_.process.killed}}var LD=new __;var B_=new __,n_="0.12.0",t_=new d("0.0.0.0"),p=new Set,A_=!1,D_=null,S_=null;async function i(D){if(A_)return N("cleanup","Cleanup already in progress, skipping",D?.verbose),S_||Promise.resolve();A_=!0,N("cleanup","Starting cleanup process",D?.verbose),S_=new Promise((_)=>{D_=_});try{await B_.stopAll(D?.verbose),X.info("Shutting down proxy servers...");let _=[],B=Array.from(p).map((K)=>new Promise((R)=>{K.close(()=>{N("cleanup","Server closed successfully",D?.verbose),R()})}));if(_.push(...B),D?.hosts&&D.domains?.length){N("cleanup","Cleaning up hosts file entries",D?.verbose),N("cleanup",`Original domains for cleanup: ${JSON.stringify(D.domains)}`,D?.verbose);let K=D.domains.filter((R)=>{if(R==="test.local")return!0;return R!=="localhost"&&!R.startsWith("localhost.")&&R!=="127.0.0.1"});if(N("cleanup",`Filtered domains for cleanup: ${JSON.stringify(K)}`,D?.verbose),K.length>0)X.info("Cleaning up hosts file entries..."),_.push(Z_(K,D?.verbose).then(()=>{N("cleanup",`Removed hosts entries for ${K.join(", ")}`,D?.verbose)}).catch((R)=>{N("cleanup",`Failed to remove hosts entries: ${R}`,D?.verbose),X.warn(`Failed to clean up hosts file entries for ${K.join(", ")}:`,R)}))}if(D?.certs&&D.domains?.length){N("cleanup","Cleaning up SSL certificates",D?.verbose),X.info("Cleaning up SSL certificates...");let K=D.domains.map(async(R)=>{try{await z_(R,D?.verbose),N("cleanup",`Removed certificates for ${R}`,D?.verbose)}catch(G){N("cleanup",`Failed to remove certificates for ${R}: ${G}`,D?.verbose),X.warn(`Failed to clean up certificates for ${R}:`,G)}});_.push(...K)}await Promise.allSettled(_),N("cleanup","All cleanup tasks completed successfully",D?.verbose),X.success("All cleanup tasks completed successfully")}catch(_){N("cleanup",`Error during cleanup: ${_}`,D?.verbose),X.error("Error during cleanup:",_)}finally{if(D_)D_();D_=null,A_=!1;let _=D&&"vitePluginUsage"in D&&D.vitePluginUsage===!0;if(T.env.NODE_ENV!=="test"&&T.env.BUN_ENV!=="test"&&!_)T.exit(0)}return S_}var V_=!1;function k_(D){if(V_){N("signal",`Received second ${D} signal, forcing exit`,!0),T.exit(1);return}V_=!0,N("signal",`Received ${D} signal, initiating cleanup`,!0),i().catch((_)=>{N("signal",`Cleanup failed after ${D}: ${_}`,!0),T.exit(1)}).finally(()=>{V_=!1})}T.once("SIGINT",()=>k_("SIGINT"));T.once("SIGTERM",()=>k_("SIGTERM"));T.on("uncaughtException",(D)=>{N("process",`Uncaught exception: ${D}`,!0),X.error("Uncaught exception:",D),k_("uncaughtException")});async function g(D,_,B,K=5){N("connection",`Testing connection to ${D}:${_} (retries left: ${K})`,B);let R=15000,G=Date.now();if(T.env.RPX_BYPASS_CONNECTION_TEST==="true"){N("connection",`Bypassing connection test for ${D}:${_} due to RPX_BYPASS_CONNECTION_TEST flag`,B);return}let $=()=>new Promise((M,W)=>{let Q=v_.connect({host:D,port:_,timeout:3000});Q.once("connect",()=>{N("connection",`Successfully connected to ${D}:${_}`,B),Q.end(),M()}),Q.once("timeout",()=>{N("connection",`Connection to ${D}:${_} timed out`,B),Q.destroy(),W(Error("Connection timed out"))}),Q.once("error",(w)=>{N("connection",`Failed to connect to ${D}:${_}: ${w}`,B),Q.destroy(),W(w)})});try{await $()}catch(M){if(Date.now()-G>R){N("connection",`Connection test timed out after ${R}ms, but continuing anyway`,B),X.warn(`Connection test to ${D}:${_} timed out, but RPX will try to proceed anyway.`);return}if(M.code==="ECONNREFUSED"&&K>0)return N("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${K} retries left)`,B),await new Promise((Q)=>setTimeout(Q,2000)),g(D,_,B,K-1);if(K>0)try{N("connection",`Trying HTTP request to ${D}:${_}`,B),await new Promise((Q,w)=>{let E=c.request({hostname:D,port:_,path:"/",method:"HEAD",timeout:5000},(V)=>{N("connection",`Received HTTP response with status: ${V.statusCode}`,B),Q()});E.on("error",(V)=>w(V)),E.on("timeout",()=>{E.destroy(),w(Error("HTTP request timed out"))}),E.end()}),N("connection",`HTTP request to ${D}:${_} succeeded`,B);return}catch(Q){return N("connection",`HTTP request to ${D}:${_} failed: ${Q}`,B),N("connection",`Retrying socket connection in 2 seconds... (${K} retries left)`,B),await new Promise((w)=>setTimeout(w,2000)),g(D,_,B,K-1)}let W=`Failed to connect to ${D}:${_} after ${5-K} attempts: ${M.message}`;N("connection",`${W}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,B),X.warn(W),X.warn("RPX will try to continue anyway. If you're sure this is correct, you can set RPX_BYPASS_CONNECTION_TEST=true to skip this check.")}}async function C_(D){N("server",`Starting server with options: ${y(D)}`,D.verbose);let _=new URL((D.from?.startsWith("http")?D.from:`http://${D.from}`)||"localhost:5173"),B=new URL((D.to?.startsWith("http")?D.to:`http://${D.to}`)||"rpx.localhost"),K=Number.parseInt(_.port)||(_.protocol.includes("https:")?443:80),R=[B.hostname];if(x_(D)&&!B.hostname.includes("localhost")&&!B.hostname.includes("127.0.0.1")){N("hosts",`Checking if hosts file entry exists for: ${B.hostname}`,D?.verbose);try{if(!(await v(R,D.verbose))[0]){X.info(`Adding ${B.hostname} to hosts file...`),X.info("This may require sudo/administrator privileges");try{await m(R,D.verbose)}catch(M){if(X.error("Failed to add hosts entry:",M.message),X.warn("You can manually add this entry to your hosts file:"),X.warn(`127.0.0.1 ${B.hostname}`),X.warn(`::1 ${B.hostname}`),T.platform==="win32")X.warn("On Windows:"),X.warn("1. Run notepad as administrator"),X.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else X.warn("On Unix systems:"),X.warn("sudo nano /etc/hosts")}}else N("hosts",`Host entry already exists for ${B.hostname}`,D.verbose)}catch($){X.error("Failed to check hosts file:",$.message)}}try{await g(_.hostname,K,D.verbose)}catch($){N("server",`Connection test failed: ${$}`,D.verbose),X.error($.message),X.warn("Continuing with proxy setup despite connection test failure..."),X.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let G=D._cachedSSLConfig||null;if(D.https)try{if(D.https===!0)D.https=R_({...D,to:B.hostname});if(G=await b({...D,to:B.hostname,https:D.https}),!G){if(N("ssl",`Generating new certificates for ${B.hostname}`,D.verbose),await K_({...D,from:_.toString(),to:B.hostname,https:D.https}),G=await b({...D,to:B.hostname,https:D.https}),!G)throw Error(`Failed to load SSL configuration after generating certificates for ${B.hostname}`)}}catch($){throw N("server",`SSL setup failed: ${$}`,D.verbose),$}N("server",`Setting up reverse proxy with SSL config for ${B.hostname}`,D.verbose),await s_({...D,from:D.from||"localhost:5173",to:B.hostname,fromPort:K,sourceUrl:{hostname:_.hostname,host:_.host},ssl:G})}async function r_(D,_,B,K,R,G,$,M,W,Q,w){N("proxy",`Creating proxy server ${D} -> ${_} with cleanUrls: ${Q}`,W);function E(S){let J={};for(let[A,z]of Object.entries(S))if(!A.startsWith(":"))J[A]=z;return J}let V=(S,J)=>{N("request",`Incoming request: ${S.method} ${S.url}`,W);let A=S.url||"/",z=S.method||"GET";if(S instanceof m_.Http2ServerRequest){let Z=S.headers;z=Z[":method"]||z,A=Z[":path"]||A}if(Q){if(!A.match(/\.[a-z0-9]+$/i))if(A.endsWith("/"))A=`${A}index.html`;else A=`${A}.html`}let j=E(S.headers);if(w)j.host=`${G.hostname}:${B}`,N("request",`Changed origin: setting host header to ${j.host}`,W);let k={hostname:G.hostname,port:B,path:A,method:z,headers:j};N("request",`Proxy request options: ${y(k)}`,W);let h=c.request(k,(Z)=>{if(N("response",`Proxy response received with status ${Z.statusCode}`,W),Q&&Z.statusCode===404){let H=[];if(A.endsWith(".html"))H.push(A.slice(0,-5));else if(!A.match(/\.[a-z0-9]+$/i))H.push(`${A}.html`);if(!A.endsWith("/"))H.push(`${A}/index.html`);if(H.length>0){N("cleanUrls",`Trying alternative paths: ${H.join(", ")}`,W);let x=(O)=>{if(O.length===0){J.writeHead(Z.statusCode||404,Z.headers),Z.pipe(J);return}let u=O[0],l_={...k,path:u},f_=c.request(l_,(n)=>{if(n.statusCode===200)N("cleanUrls",`Found matching path: ${u}`,W),J.writeHead(n.statusCode,n.headers),n.pipe(J);else x(O.slice(1))});f_.on("error",()=>x(O.slice(1))),f_.end()};x(H);return}}let F={...Z.headers,"Strict-Transport-Security":"max-age=31536000; includeSubDomains; preload","X-Content-Type-Options":"nosniff"};J.writeHead(Z.statusCode||500,F),Z.pipe(J)});h.on("error",(Z)=>{N("request",`Proxy request failed: ${Z}`,W),X.error("Proxy request failed:",Z),J.writeHead(502),J.end(`Proxy Error: ${Z.message}`)}),S.pipe(h)};if(N("server",`Creating server with SSL config: ${!!$}`,W),$)return new Promise((S,J)=>{try{let A=Bun.serve({port:K,hostname:R,tls:{key:$.key,cert:$.cert,ca:$.ca,requestCert:!1,rejectUnauthorized:!1},async fetch(z){let j=new URL(z.url);N("request",`Bun.serve received: ${z.method} ${j.pathname}`,W);let k=`http://${G.host}`,h=new URL(j.pathname+j.search,k);try{let Z=new Headers(z.headers);if(Z.set("host",G.host),w)Z.set("origin",k);Z.set("x-forwarded-for","127.0.0.1"),Z.set("x-forwarded-proto","https"),Z.set("x-forwarded-host",_);let F=await fetch(h.toString(),{method:z.method,headers:Z,body:z.body,redirect:"manual"}),H=new Headers(F.headers);if(Q&&j.pathname.endsWith(".html")){let x=j.pathname.replace(/\.html$/,"");return new Response(null,{status:301,headers:{Location:x}})}return new Response(F.body,{status:F.status,statusText:F.statusText,headers:H})}catch(Z){return N("request",`Proxy error: ${Z}`,W),new Response(`Proxy Error: ${Z}`,{status:502})}},error(z){return N("server",`Bun.serve error: ${z}`,W),new Response(`Server Error: ${z.message}`,{status:500})}});p.add(A),u_({from:D,to:_,vitePluginUsage:M,listenPort:K,ssl:!0,cleanUrls:Q,verbose:W}),S()}catch(A){J(A)}});let I=c.createServer(V);function Y(S){return p.add(S),new Promise((J,A)=>{S.listen(K,R,()=>{N("server",`Server listening on port ${K}`,W),u_({from:D,to:_,vitePluginUsage:M,listenPort:K,ssl:!!$,cleanUrls:Q,verbose:W}),J()}),S.on("error",(z)=>{N("server",`Server error: ${z}`,W),A(z)})})}return Y(I)}async function s_(D){N("setup",`Setting up reverse proxy: ${y(D)}`,D.verbose);let{from:_,to:B,fromPort:K,sourceUrl:R,ssl:G,verbose:$,cleanup:M,vitePluginUsage:W,changeOrigin:Q,cleanUrls:w}=D,E=80,V=443,I="0.0.0.0",Y=D.portManager||t_,S=x_(D);try{if(S&&B&&!B.includes("localhost")&&!B.includes("127.0.0.1")){if(!(await v([B],$))[0]){X.warn(`The hostname ${B} isn't in your hosts file. Adding it now...`);try{await m([B],$),X.success(`Added ${B} to your hosts file.`)}catch(k){X.error(`Failed to add ${B} to your hosts file: ${k}`),X.info(`You may need to manually add '127.0.0.1 ${B}' to your /etc/hosts file.`)}}}else if(S&&T.platform!=="darwin"&&B&&B.includes("localhost")&&!B.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await v([B],$))[0]){N("hosts",`${B} not found in hosts file, adding...`,$);try{await m([B],$)}catch(k){N("hosts",`Failed to add ${B} to hosts file: ${k}`,$)}}}if(G&&!Y.usedPorts.has(E)){if(!await U(E,I,$))N("setup","Starting HTTP redirect server",$),b_($),Y.usedPorts.add(E);else if(N("setup","Port 80 is in use, skipping HTTP redirect",$),$)X.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let J=G?V:E,A=await U(J,I,$),z;if(A){if(N("setup",`Port ${J} is already in use`,$),$)X.warn(`Port ${J} is already in use. This may be another instance of rpx or another service.`);if(J===443){if(z=await Y.getNextAvailablePort(3443,!0),N("setup",`Using port ${z} instead of ${J}`,$),$)X.info(`Using port ${z} instead. Access your site at https://${B}:${z}`)}else if(z=await Y.getNextAvailablePort(J+1000,!0),N("setup",`Using port ${z} instead of ${J}`,$),$)X.info(`Using port ${z} instead. Access your site at http://${B}:${z}`)}else z=J,Y.usedPorts.add(z),N("setup",`Using standard ${J===443?"HTTPS":"HTTP"} port ${J} for ${B}`,$);await r_(_,B,K,z,I,R,G,W,$,w,Q)}catch(J){N("setup",`Setup failed: ${J}`,$),X.error(`Failed to setup reverse proxy: ${J.message}`),i({domains:[B],hosts:typeof M==="boolean"?M:M?.hosts,certs:typeof M==="boolean"?M:M?.certs,verbose:$,vitePluginUsage:W})}}function b_(D){N("redirect","Starting HTTP redirect server",D);let _=c.createServer((B,K)=>{let R=B.headers.host||"";N("redirect",`Redirecting request from ${R}${B.url} to HTTPS`,D),K.writeHead(301,{Location:`https://${R}${B.url}`}),K.end()}).listen(80);p.add(_),N("redirect","HTTP redirect server started",D)}function o_(D){let _={...N_,...D};if(N("proxy",`Starting proxy with options: ${y(_)}`,_?.verbose),_.viaDaemon){if(!_.from||!_.to){X.error("viaDaemon mode requires both `from` and `to`");return}r({proxies:[{id:_.id,from:_.from,to:_.to,path:_.path,cleanUrls:_.cleanUrls,changeOrigin:_.changeOrigin,pathRewrites:_.pathRewrites}],verbose:_.verbose}).catch((W)=>{X.error(`Failed to register with rpx daemon: ${W.message}`),T.exit(1)});return}let B=_.to||"",K=B.split(".").pop()?.toLowerCase()||"",R=T.platform==="darwin"&&B&&!B.includes("localhost")&&!B.includes("127.0.0.1"),G=["dev","app","page","new","day","foo"],$=["test","localhost","local","example","invalid"];if(R&&G.includes(K)&&_?.verbose)X.warn(`The .${K} TLD may not work reliably for local development`),X.info(` Google owns .${K} with HSTS preloading, which can bypass local DNS`),X.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(R)import("./chunk-1sg87m7v.js").then(({setupDevelopmentDns:W})=>{W({domains:[B],verbose:_.verbose}).then((Q)=>{if(Q)Promise.resolve().then(()=>{if(_.verbose)if($.includes(K))X.success(`DNS server started for .${K} domains`);else X.success(`DNS server started for .${K} domains (hosts file entry also added)`)});else N("dns",`Could not start DNS server - ${B} may not resolve in browser`,_.verbose)})}).catch((W)=>{N("dns",`Failed to start DNS server: ${W}`,_.verbose)});let M={from:_.from,to:_.to,cleanUrls:_.cleanUrls,https:R_(_),cleanup:_.cleanup,vitePluginUsage:_.vitePluginUsage,changeOrigin:_.changeOrigin,verbose:_.verbose,regenerateUntrustedCerts:_.regenerateUntrustedCerts};N("proxy",`Server options: ${y(M)}`,_.verbose),C_(M).catch((W)=>{N("proxy",`Failed to start proxy: ${W}`,_.verbose),X.error(`Failed to start proxy: ${W.message}`),i({domains:[_.to],hosts:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.hosts,certs:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.certs,verbose:_.verbose})})}function e_(D){return D?.verbose||!1}function x_(D){if(D?.hostsManagement===!1)return!1;let _=D?.cleanup;if(_===!1)return!1;if(_&&typeof _==="object"&&_.hosts===!1)return!1;return!0}async function q_(D){let _={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(D)_={..._,...D};let B=e_(_),K=x_(_);if(N("config",`Starting with config: ${y(_,2)}`,B),N("config",`Is multi-proxy? ${"proxies"in _}`,B),N("config",`Hosts management enabled? ${K}`,B),_.viaDaemon){let S="proxies"in _&&Array.isArray(_.proxies)?_.proxies.map((J)=>({id:J.id,from:J.from,to:J.to,path:J.path,cleanUrls:J.cleanUrls??_.cleanUrls,changeOrigin:J.changeOrigin??_.changeOrigin,pathRewrites:J.pathRewrites})):[{id:_.id,from:_.from,to:_.to,path:_.path,cleanUrls:_.cleanUrls,changeOrigin:_.changeOrigin,pathRewrites:_.pathRewrites}];await r({proxies:S,verbose:B});return}if("proxies"in _&&Array.isArray(_.proxies)){N("servers",`Found ${_.proxies.length} proxies in config`,B);for(let Y of _.proxies)if(Y.start){let S=`${Y.from}-${Y.to}`;try{N("watch",`Starting command for ${S} with command: ${Y.start.command}`,B),X.info(`Starting command for ${S}...`),await B_.startProcess(S,Y.start,B);let J=new URL(Y.from.startsWith("http")?Y.from:`http://${Y.from}`),A=J.hostname||"localhost",z=Number(J.port)||80;try{await g(A,z,B),N("watch",`Dev server is ready at ${A}:${z}`,B)}catch(j){N("watch",`Connection check failed, but continuing with proxy setup: ${j}`,B),X.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(J){throw N("watch",`Failed to start command for ${S}: ${J}`,B),Error(`Failed to start command for ${S}: ${J}`)}}else N("watch",`No start command for proxy ${Y.from} -> ${Y.to}`,B)}else if("start"in _&&_.start){N("watch","Found start command in single proxy config",B);let Y=`${_.from}-${_.to}`;try{if(_.start)N("watch",`Starting command: ${_.start.command}`,B),await B_.startProcess(Y,_.start,B);let S=new URL(_.from?.startsWith("http")?_.from:`http://${_.from}`),J=S.hostname||"localhost",A=Number(S.port)||80;try{await g(J,A,B),N("watch",`Dev server is ready at ${J}:${A}`,B)}catch(z){N("watch",`Connection check failed, but continuing with proxy setup: ${z}`,B),X.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(S){throw N("watch",`Failed to run start command: ${S}`,B),Error(`Failed to run start command: ${S}`)}}else N("watch","No start command found in config",B);let R="proxies"in _&&Array.isArray(_.proxies)?_.proxies[0]?.to:("to"in _)?_.to:"rpx.localhost";if(T.platform!=="win32"&&(_.https||K)){if(!l())try{N("sudo","Pre-acquiring sudo credentials for privileged operations",B),i_("sudo -v",{stdio:"inherit"})}catch{N("sudo","Could not pre-acquire sudo credentials",B)}}if(_.https){let Y=await b(_);if(!Y){if(N("ssl",`No valid or trusted certificates found for ${R}, generating new ones`,_.verbose),await K_(_),Y=await b(_),!Y)throw Error(`Failed to load SSL certificates after generation for ${R}`)}else N("ssl",`Using existing and trusted certificates for ${R}`,_.verbose);_._cachedSSLConfig=Y}let G="proxies"in _&&Array.isArray(_.proxies)?_.proxies.map((Y)=>({...Y,https:_.https,cleanup:_.cleanup,cleanUrls:Y.cleanUrls??("cleanUrls"in _?_.cleanUrls:!1),vitePluginUsage:_.vitePluginUsage,changeOrigin:Y.changeOrigin??_.changeOrigin,verbose:B,_cachedSSLConfig:_._cachedSSLConfig})):[{from:"from"in _?_.from:"localhost:5173",to:"to"in _?_.to:"rpx.localhost",cleanUrls:"cleanUrls"in _?_.cleanUrls:!1,https:_.https,cleanup:_.cleanup,vitePluginUsage:_.vitePluginUsage,start:"start"in _?_.start:void 0,changeOrigin:_.changeOrigin,verbose:B,_cachedSSLConfig:_._cachedSSLConfig}],$=G.map((Y)=>Y.to||"rpx.localhost"),M=_._cachedSSLConfig,W=$.filter((Y)=>Y&&!Y.includes("localhost")&&!Y.includes("127.0.0.1")),Q=["dev","app","page","new","day","foo"],w=["test","localhost","local","example","invalid"],E=[...new Set(W.map((Y)=>Y.split(".").pop()?.toLowerCase()))],V=E.filter((Y)=>!!Y&&Q.includes(Y));if(V.length>0&&B)X.warn(`The following TLDs may not work reliably for local development: ${V.map((Y)=>`.${Y}`).join(", ")}`),X.info(" These TLDs have HSTS preloading which can bypass local DNS"),X.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(K&&T.platform==="darwin"&&W.length>0){let{setupDevelopmentDns:Y}=await import("./chunk-1sg87m7v.js");if(await Y({domains:W,verbose:B})){if(B)if(E.every((A)=>!!A&&w.includes(A)))X.success(`DNS server started for ${E.map((A)=>`.${A}`).join(", ")} domains`);else X.success(`DNS server started for ${E.map((A)=>`.${A}`).join(", ")} domains (hosts file entries also added)`)}else N("dns","Could not start DNS server - custom domains may not resolve",B)}let I=async()=>{N("cleanup","Starting cleanup handler",_.verbose);try{let{tearDownDevelopmentDns:Y}=await import("./chunk-1sg87m7v.js");await Y({verbose:_.verbose})}catch(Y){N("cleanup",`Error stopping DNS server: ${Y}`,_.verbose)}try{await B_.stopAll(_.verbose)}catch(Y){N("cleanup",`Error stopping processes: ${Y}`,_.verbose)}await i({domains:$,hosts:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.hosts,certs:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.certs,verbose:_.verbose||!1})};if(T.on("SIGINT",I),T.on("SIGTERM",I),T.on("uncaughtException",(Y)=>{N("process",`Uncaught exception: ${Y}`,!0),console.error("Uncaught exception:",Y),I()}),M&&G.length>1){N("proxies",`Creating shared HTTPS server for ${G.length} domains`,B);let Y=[],S=new Set;for(let Z of G){let F=Z.to||"rpx.localhost",H=Z.cleanUrls||!1,x=Z.path,O=E_(x);if(Z.static)Y.push({host:F,path:x,route:{static:F_(Z.static,H),cleanUrls:H,basePath:O}}),N("proxies",`Route: ${F}${x??""} → static ${typeof Z.static==="string"?Z.static:Z.static.dir}`,B);else{let u=new URL(Z.from?.startsWith("http")?Z.from:`http://${Z.from}`);Y.push({host:F,path:x,route:{sourceHost:u.host,cleanUrls:H,changeOrigin:Z.changeOrigin||!1,pathRewrites:Z.pathRewrites,basePath:O}}),N("proxies",`Route: ${F}${x??""} → ${u.host}`,B)}if(S.has(F))continue;if(S.add(F),K&&!w_(F)&&!F.includes("localhost")&&!F.includes("127.0.0.1"))try{if(!(await v([F],B))[0])await m([F],B)}catch{N("hosts",`Could not add hosts entry for ${F}`,B)}}if(!await U(80,"0.0.0.0",B))b_(B);let A=443;if(await U(A,"0.0.0.0",B)){if(N("proxies",`Port ${A} is already in use, cannot start shared proxy`,B),B)X.warn(`Port ${A} is in use. Shared HTTPS proxy cannot start.`);return}let j=j_(Y),k=T_((Z,F)=>H_(j,Z,F),B),h=I_(B);try{let Z=Bun.serve({port:A,hostname:"0.0.0.0",tls:{key:M.key,cert:M.cert,ca:M.ca,requestCert:!1,rejectUnauthorized:!1},fetch(F,H){return k(F,H)},websocket:h,error(F){return N("server",`Shared proxy server error: ${F}`,B),new Response(`Server Error: ${F.message}`,{status:500})}});p.add(Z),N("proxies",`Shared HTTPS proxy listening on port ${A} for ${j.size} domains`,B)}catch(Z){N("proxies",`Failed to start shared proxy: ${Z}`,B),console.error("Failed to start shared HTTPS proxy:",Z),I()}}else for(let Y of G)try{let S=Y.to||"rpx.localhost";N("proxy",`Starting proxy for ${S} with SSL config: ${!!M}`,Y.verbose),await C_({from:Y.from||"localhost:5173",to:S,cleanUrls:Y.cleanUrls||!1,https:Y.https||!1,cleanup:Y.cleanup||!1,vitePluginUsage:Y.vitePluginUsage||!1,verbose:Y.verbose||!1,_cachedSSLConfig:M,changeOrigin:Y.changeOrigin||!1})}catch(S){N("proxies",`Failed to start proxy for ${Y.to}: ${S}`,Y.verbose),console.error(`Failed to start proxy for ${Y.to}:`,S),I()}}function u_(D){if(D?.vitePluginUsage||!D?.verbose)return;if(console.log(""),console.log(` ${q.green(q.bold("rpx"))} ${q.green(`v${n_}`)}`),console.log(` ${q.green("➜")} ${q.dim(D?.from??"")} ${q.dim("➜")} ${q.cyan(D?.ssl?`https://${D?.to}`:`http://${D?.to}`)}`),D?.listenPort!==(D?.ssl?443:80))console.log(` ${q.green("➜")} Listening on port ${D?.listenPort}`);if(D?.cleanUrls)console.log(` ${q.green("➜")} Clean URLs enabled`)}var HB=q_;export{G_ as writeEntry,v2 as watchRegistry,K2 as verifyHttpsChain,S2 as trustRootCaForBrowsers,KD as tearDownDevelopmentDns,ND as syncDevelopmentDnsFromRegistry,k2 as stripBasePath,r2 as stopDnsServer,SD as stopDaemon,C_ as startServer,o_ as startProxy,q_ as startProxies,t2 as startDnsServer,_D as setupResolver,BD as setupDevelopmentDns,y2 as serverNameFromCertFilename,H2 as serveStaticFile,y as safeStringify,E2 as safeRelativePath,cB as safeDeleteFile,r as runViaDaemon,ZD as runDaemon,o2 as resolverFilePath,p2 as resolverBasenamesForDomains,d2 as resolverBasenameForDomain,F_ as resolveStaticRoute,j2 as resolveStaticFile,LB as resolvePathRewrite,RD as removeResolver,DD as removeLegacyTldResolvers,Z_ as removeHosts,W_ as removeEntry,QD as releaseDaemonLock,CB as redactSensitive,YD as reconcileStaleDevelopmentDns,VD as reconcileDevelopmentDnsOnIdle,h2 as readEntry,WD as readDaemonPid,D2 as readCertSha256Fingerprint,B2 as readCertCommonName,u2 as readAll,Q2 as pruneStaleRootCas,p_ as portManager,q2 as pathPrefixMatches,R2 as parseSha256HashesFromSecurityListing,_2 as normalizeSha256Fingerprint,E_ as normalizePathPrefix,a2 as normalizeDevDomain,C2 as matchesWildcard,O2 as matchesAllowedSuffix,H_ as matchHostRoute,f2 as matchHostList,x2 as matchHost,z2 as loadSSLConfig,J2 as listCertSha256HashesByCommonName,w_ as isWildcardPattern,qB as isValidRootCA,$_ as isValidId,OB as isSingleProxyOptions,PB as isSingleProxyConfig,Z2 as isRootCaTrustedForSsl,A2 as isRootCaFingerprintInKeychains,U as isPortInUse,c2 as isPidAlive,UB as isMultiProxyOptions,yB as isMultiProxyConfig,P2 as isLikelyHostname,s2 as isDnsServerRunning,XD as isDaemonRunning,I2 as isCertTrusted,R_ as httpsConfig,l as getSudoPassword,M2 as getSharedDaemonCertPaths,V2 as getRootCAPaths,Y_ as getRegistryDir,fB as getPrimaryDomain,X2 as getMacosTrustKeychains,W2 as getMacosLoginKeychainPath,$D as getDaemonRpxDir,GD as getDaemonPidPath,K_ as generateCertificate,m2 as gcStaleEntries,F2 as forceTrustCertificate,h_ as findAvailablePort,xB as extractHostname,kB as execSudoSync,X_ as ensureDaemonRunning,g2 as devDomainsFromHosts,O_ as deriveIdFromTarget,AD as defaultDaemonSpawnCommand,N_ as defaultConfig,HB as default,N as debugLog,I_ as createProxyWebSocketHandler,T_ as createProxyFetchHandler,w2 as contentTypeFor,e2 as contentLooksLikeRpxResolver,N_ as config,q as colors,T2 as clearSslConfigCache,z_ as cleanupCertificates,i as cleanup,v as checkHosts,b as checkExistingCertificates,N2 as certIncludesSanHostnames,U2 as buildSniTlsConfig,j_ as buildHostRoutes,m as addHosts,JD as acquireDaemonLock,G2 as RPX_ROOT_CA_COMMON_NAME,n2 as RPX_RESOLVER_MARKER,L2 as OnDemandCertManager,$2 as MACOS_SYSTEM_KEYCHAIN,Y2 as MACOS_CA_TRUST_FLAGS,l2 as LEGACY_TLD_RESOLVER_LABELS,d as DefaultPortManager,b2 as DNS_STATE_VERSION,i2 as DNS_PORT};
|
|
8
|
+
`,V=W_.join(c_.tmpdir(),`rpx-hosts-${Date.now()}.tmp`);try{await P.promises.writeFile(V,$,"utf8"),await o(`cat "${V}" | tee "${C}" > /dev/null`),N("hosts","Hosts removed successfully",_)}catch(R){N("hosts","Could not clean up hosts file automatically",_)}finally{try{await P.promises.unlink(V)}catch(R){N("hosts",`Failed to remove temporary file: ${R}`,_)}}}catch(B){N("hosts",`Failed to clean up hosts file: ${B.message}`,_)}}async function v(D,_){N("hosts",`Checking hosts: ${D}`,_);let B;try{B=await P.promises.readFile(C,"utf-8")}catch(G){N("hosts",`Error reading hosts file: ${G}`,_);try{let K=l(),X;if(K)X=`echo '${K}' | sudo -S cat "${C}" 2>/dev/null`;else X=`sudo -n cat "${C}" 2>/dev/null || cat "${C}" 2>/dev/null || echo ""`;let{stdout:$}=await s(X);B=$}catch(K){return N("hosts",`Cannot read hosts file, assuming entries don't exist: ${K}`,_),D.map(()=>!1)}}return D.map((G)=>{let K=`127.0.0.1 ${G}`,X=`::1 ${G}`;return B.includes(K)||B.includes(X)})}import*as e from"node:net";function U(D,_,B){return N("port",`Checking if port ${D} is in use on ${_}`,B),new Promise((G)=>{let K=e.createServer(),X=setTimeout(()=>{N("port",`Checking port ${D} timed out, assuming it's in use`,B),K.close(),G(!0)},3000);K.once("error",($)=>{if(clearTimeout(X),$.code==="EADDRINUSE")N("port",`Port ${D} is in use`,B),G(!0);else N("port",`Error checking port ${D}: ${$.message}`,B),G(!0)}),K.once("listening",()=>{clearTimeout(X),N("port",`Port ${D} is available`,B),K.close(),G(!1)});try{K.listen(D,_)}catch($){clearTimeout(X),N("port",`Exception checking port ${D}: ${$}`,B),G(!0)}})}async function u_(D,_,B,G=50){N("port",`Finding available port starting from ${D} (max attempts: ${G})`,B);let K=D,X=0;while(X<G){if(X++,!await U(K,_,B))return N("port",`Found available port: ${K} after ${X} attempts`,B),K;N("port",`Port ${K} is in use, trying ${K+1} (attempt ${X}/${G})`,B),K++}throw Error(`Unable to find available port after ${G} attempts starting from ${D}`)}function h_(D,_,B=5000,G){return N("port",`Testing connection to ${_}:${D}`,G),new Promise((K)=>{let X=e.connect({host:_,port:D,timeout:B});X.once("connect",()=>{N("port",`Successfully connected to ${_}:${D}`,G),X.end(),K(!0)}),X.once("timeout",()=>{N("port",`Connection to ${_}:${D} timed out`,G),X.destroy(),K(!1)}),X.once("error",($)=>{N("port",`Failed to connect to ${_}:${D}: ${$.message}`,G),X.destroy(),K(!1)})})}class d{usedPorts=new Set;hostname;verbose;maxRetries;constructor(D="0.0.0.0",_,B=50){this.hostname=D,this.verbose=_,this.maxRetries=B}async getNextAvailablePort(D,_=!1){if(this.usedPorts.has(D))return this.findNextAvailablePort(D+1,_);if(await U(D,this.hostname,this.verbose))return this.findNextAvailablePort(D+1,_);if(_){if(!await h_(D,this.hostname,3000,this.verbose))return N("port",`Port ${D} is available but not connectable, trying next port`,this.verbose),this.findNextAvailablePort(D+1,_)}return this.usedPorts.add(D),D}async findNextAvailablePort(D,_=!1){let B=await u_(D,this.hostname,this.verbose,this.maxRetries);if(_){if(!await h_(B,this.hostname,3000,this.verbose))if(B<D+this.maxRetries)return this.findNextAvailablePort(B+1,_);else throw Error(`Unable to find a connectable port after ${this.maxRetries} attempts`)}return this.usedPorts.add(B),B}releasePort(D){N("port",`Releasing port ${D}`,this.verbose),this.usedPorts.delete(D)}}var p_=new d;import{spawn as i_}from"node:child_process";import*as L from"node:process";class __{processes=new Map;isShuttingDown=!1;async startProcess(D,_,B){if(this.processes.has(D)){N("start",`Process ${D} is already running`,B);return}let[G,...K]=_.command.split(" "),X=_.cwd||L.cwd();N("start",`Starting process ${D}:`,B),N("start",` Command: ${G} ${K.join(" ")}`,B),N("start",` Working directory: ${X}`,B),N("start",` Environment variables: ${y(_.env)}`,B);let $=i_(G,K,{cwd:X,env:{...L.env,..._.env},shell:!0,stdio:"inherit"});return this.processes.set(D,{command:_.command,cwd:X,process:$,env:_.env}),new Promise((V,R)=>{if($.on("error",(Q)=>{if(!this.isShuttingDown)N("start",`Process ${D} failed to start: ${Q}`,B),this.processes.delete(D),R(Q),L.emit("SIGINT")}),$.on("exit",(Q)=>{if(!this.isShuttingDown&&Q!==null&&Q!==0)N("start",`Process ${D} exited with code ${Q}`,B),this.processes.delete(D),R(Error(`Process ${D} exited with code ${Q}`)),L.emit("SIGINT")}),B)$.stdout?.on("data",(Q)=>{N("process",`[${D}] ${Q.toString().trim()}`,!0)}),$.stderr?.on("data",(Q)=>{N("process",`[${D}] ERR: ${Q.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&$.killed)this.processes.delete(D),R(Error(`Process ${D} was killed during startup`));else N("start",`Process ${D} started successfully`,B),V()},1000)})}async stopProcess(D,_){let B=this.processes.get(D);if(!B?.process){N("start",`No process found for ${D}`,_);return}return N("start",`Stopping process ${D}`,_),new Promise((G)=>{if(!B.process){G();return}B.process.once("exit",()=>{this.processes.delete(D),N("start",`Process ${D} stopped`,_),G()});try{B.process.kill("SIGTERM"),setTimeout(()=>{if(B.process){N("start",`Force killing process ${D}`,_);try{B.process.kill("SIGKILL")}catch(K){}}},3000)}catch(K){N("start",`Error stopping process ${D}: ${K}`,_),this.processes.delete(D),G()}})}async stopAll(D){if(this.isShuttingDown){N("start","Already shutting down, skipping duplicate stopAll call",D);return}this.isShuttingDown=!0,N("start","Stopping all processes",D);let _=Array.from(this.processes.keys()).map((B)=>this.stopProcess(B,D).catch((G)=>{J.error(`Failed to stop process ${B}:`,G)}));await Promise.allSettled(_),this.processes.clear(),this.isShuttingDown=!1}isRunning(D){let _=this.processes.get(D);return!!_?.process&&!_.process.killed}}var uD=new __;var B_=new __,t_="0.12.0",r_=new d("0.0.0.0"),g=new Set,S_=!1,D_=null,V_=null;async function i(D){if(S_)return N("cleanup","Cleanup already in progress, skipping",D?.verbose),V_||Promise.resolve();S_=!0,N("cleanup","Starting cleanup process",D?.verbose),V_=new Promise((_)=>{D_=_});try{await B_.stopAll(D?.verbose),J.info("Shutting down proxy servers...");let _=[],B=Array.from(g).map((G)=>new Promise((K)=>{G.close(()=>{N("cleanup","Server closed successfully",D?.verbose),K()})}));if(_.push(...B),D?.hosts&&D.domains?.length){N("cleanup","Cleaning up hosts file entries",D?.verbose),N("cleanup",`Original domains for cleanup: ${JSON.stringify(D.domains)}`,D?.verbose);let G=D.domains.filter((K)=>{if(K==="test.local")return!0;return K!=="localhost"&&!K.startsWith("localhost.")&&K!=="127.0.0.1"});if(N("cleanup",`Filtered domains for cleanup: ${JSON.stringify(G)}`,D?.verbose),G.length>0)J.info("Cleaning up hosts file entries..."),_.push(Z_(G,D?.verbose).then(()=>{N("cleanup",`Removed hosts entries for ${G.join(", ")}`,D?.verbose)}).catch((K)=>{N("cleanup",`Failed to remove hosts entries: ${K}`,D?.verbose),J.warn(`Failed to clean up hosts file entries for ${G.join(", ")}:`,K)}))}if(D?.certs&&D.domains?.length){N("cleanup","Cleaning up SSL certificates",D?.verbose),J.info("Cleaning up SSL certificates...");let G=D.domains.map(async(K)=>{try{await M_(K,D?.verbose),N("cleanup",`Removed certificates for ${K}`,D?.verbose)}catch(X){N("cleanup",`Failed to remove certificates for ${K}: ${X}`,D?.verbose),J.warn(`Failed to clean up certificates for ${K}:`,X)}});_.push(...G)}await Promise.allSettled(_),N("cleanup","All cleanup tasks completed successfully",D?.verbose),J.success("All cleanup tasks completed successfully")}catch(_){N("cleanup",`Error during cleanup: ${_}`,D?.verbose),J.error("Error during cleanup:",_)}finally{if(D_)D_();D_=null,S_=!1;let _=D&&"vitePluginUsage"in D&&D.vitePluginUsage===!0;if(I.env.NODE_ENV!=="test"&&I.env.BUN_ENV!=="test"&&!_)I.exit(0)}return V_}var A_=!1;function k_(D){if(A_){N("signal",`Received second ${D} signal, forcing exit`,!0),I.exit(1);return}A_=!0,N("signal",`Received ${D} signal, initiating cleanup`,!0),i().catch((_)=>{N("signal",`Cleanup failed after ${D}: ${_}`,!0),I.exit(1)}).finally(()=>{A_=!1})}I.once("SIGINT",()=>k_("SIGINT"));I.once("SIGTERM",()=>k_("SIGTERM"));I.on("uncaughtException",(D)=>{N("process",`Uncaught exception: ${D}`,!0),J.error("Uncaught exception:",D),k_("uncaughtException")});async function p(D,_,B,G=5){N("connection",`Testing connection to ${D}:${_} (retries left: ${G})`,B);let K=15000,X=Date.now();if(I.env.RPX_BYPASS_CONNECTION_TEST==="true"){N("connection",`Bypassing connection test for ${D}:${_} due to RPX_BYPASS_CONNECTION_TEST flag`,B);return}let $=()=>new Promise((V,R)=>{let Q=b_.connect({host:D,port:_,timeout:3000});Q.once("connect",()=>{N("connection",`Successfully connected to ${D}:${_}`,B),Q.end(),V()}),Q.once("timeout",()=>{N("connection",`Connection to ${D}:${_} timed out`,B),Q.destroy(),R(Error("Connection timed out"))}),Q.once("error",(T)=>{N("connection",`Failed to connect to ${D}:${_}: ${T}`,B),Q.destroy(),R(T)})});try{await $()}catch(V){if(Date.now()-X>K){N("connection",`Connection test timed out after ${K}ms, but continuing anyway`,B),J.warn(`Connection test to ${D}:${_} timed out, but RPX will try to proceed anyway.`);return}if(V.code==="ECONNREFUSED"&&G>0)return N("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${G} retries left)`,B),await new Promise((Q)=>setTimeout(Q,2000)),p(D,_,B,G-1);if(G>0)try{N("connection",`Trying HTTP request to ${D}:${_}`,B),await new Promise((Q,T)=>{let w=c.request({hostname:D,port:_,path:"/",method:"HEAD",timeout:5000},(z)=>{N("connection",`Received HTTP response with status: ${z.statusCode}`,B),Q()});w.on("error",(z)=>T(z)),w.on("timeout",()=>{w.destroy(),T(Error("HTTP request timed out"))}),w.end()}),N("connection",`HTTP request to ${D}:${_} succeeded`,B);return}catch(Q){return N("connection",`HTTP request to ${D}:${_} failed: ${Q}`,B),N("connection",`Retrying socket connection in 2 seconds... (${G} retries left)`,B),await new Promise((T)=>setTimeout(T,2000)),p(D,_,B,G-1)}let R=`Failed to connect to ${D}:${_} after ${5-G} attempts: ${V.message}`;N("connection",`${R}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,B),J.warn(R),J.warn("RPX will try to continue anyway. If you're sure this is correct, you can set RPX_BYPASS_CONNECTION_TEST=true to skip this check.")}}async function C_(D){N("server",`Starting server with options: ${y(D)}`,D.verbose);let _=new URL((D.from?.startsWith("http")?D.from:`http://${D.from}`)||"localhost:5173"),B=new URL((D.to?.startsWith("http")?D.to:`http://${D.to}`)||"rpx.localhost"),G=Number.parseInt(_.port)||(_.protocol.includes("https:")?443:80),K=[B.hostname];if(x_(D)&&!B.hostname.includes("localhost")&&!B.hostname.includes("127.0.0.1")){N("hosts",`Checking if hosts file entry exists for: ${B.hostname}`,D?.verbose);try{if(!(await v(K,D.verbose))[0]){J.info(`Adding ${B.hostname} to hosts file...`),J.info("This may require sudo/administrator privileges");try{await m(K,D.verbose)}catch(V){if(J.error("Failed to add hosts entry:",V.message),J.warn("You can manually add this entry to your hosts file:"),J.warn(`127.0.0.1 ${B.hostname}`),J.warn(`::1 ${B.hostname}`),I.platform==="win32")J.warn("On Windows:"),J.warn("1. Run notepad as administrator"),J.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else J.warn("On Unix systems:"),J.warn("sudo nano /etc/hosts")}}else N("hosts",`Host entry already exists for ${B.hostname}`,D.verbose)}catch($){J.error("Failed to check hosts file:",$.message)}}try{await p(_.hostname,G,D.verbose)}catch($){N("server",`Connection test failed: ${$}`,D.verbose),J.error($.message),J.warn("Continuing with proxy setup despite connection test failure..."),J.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let X=D._cachedSSLConfig||null;if(D.https)try{if(D.https===!0)D.https=K_({...D,to:B.hostname});if(X=await b({...D,to:B.hostname,https:D.https}),!X){if(N("ssl",`Generating new certificates for ${B.hostname}`,D.verbose),await G_({...D,from:_.toString(),to:B.hostname,https:D.https}),X=await b({...D,to:B.hostname,https:D.https}),!X)throw Error(`Failed to load SSL configuration after generating certificates for ${B.hostname}`)}}catch($){throw N("server",`SSL setup failed: ${$}`,D.verbose),$}N("server",`Setting up reverse proxy with SSL config for ${B.hostname}`,D.verbose),await o_({...D,from:D.from||"localhost:5173",to:B.hostname,fromPort:G,sourceUrl:{hostname:_.hostname,host:_.host},ssl:X})}async function s_(D,_,B,G,K,X,$,V,R,Q,T){N("proxy",`Creating proxy server ${D} -> ${_} with cleanUrls: ${Q}`,R);function w(A){let W={};for(let[S,M]of Object.entries(A))if(!S.startsWith(":"))W[S]=M;return W}let z=(A,W)=>{N("request",`Incoming request: ${A.method} ${A.url}`,R);let S=A.url||"/",M=A.method||"GET";if(A instanceof v_.Http2ServerRequest){let Z=A.headers;M=Z[":method"]||M,S=Z[":path"]||S}if(Q){if(!S.match(/\.[a-z0-9]+$/i))if(S.endsWith("/"))S=`${S}index.html`;else S=`${S}.html`}let E=w(A.headers);if(T)E.host=`${X.hostname}:${B}`,N("request",`Changed origin: setting host header to ${E.host}`,R);let k={hostname:X.hostname,port:B,path:S,method:M,headers:E};N("request",`Proxy request options: ${y(k)}`,R);let h=c.request(k,(Z)=>{if(N("response",`Proxy response received with status ${Z.statusCode}`,R),Q&&Z.statusCode===404){let H=[];if(S.endsWith(".html"))H.push(S.slice(0,-5));else if(!S.match(/\.[a-z0-9]+$/i))H.push(`${S}.html`);if(!S.endsWith("/"))H.push(`${S}/index.html`);if(H.length>0){N("cleanUrls",`Trying alternative paths: ${H.join(", ")}`,R);let x=(O)=>{if(O.length===0){W.writeHead(Z.statusCode||404,Z.headers),Z.pipe(W);return}let u=O[0],a_={...k,path:u},y_=c.request(a_,(n)=>{if(n.statusCode===200)N("cleanUrls",`Found matching path: ${u}`,R),W.writeHead(n.statusCode,n.headers),n.pipe(W);else x(O.slice(1))});y_.on("error",()=>x(O.slice(1))),y_.end()};x(H);return}}let F={...Z.headers,"Strict-Transport-Security":"max-age=31536000; includeSubDomains; preload","X-Content-Type-Options":"nosniff"};W.writeHead(Z.statusCode||500,F),Z.pipe(W)});h.on("error",(Z)=>{N("request",`Proxy request failed: ${Z}`,R),J.error("Proxy request failed:",Z),W.writeHead(502),W.end(`Proxy Error: ${Z.message}`)}),A.pipe(h)};if(N("server",`Creating server with SSL config: ${!!$}`,R),$)return new Promise((A,W)=>{try{let S=Bun.serve({port:G,hostname:K,tls:{key:$.key,cert:$.cert,ca:$.ca,requestCert:!1,rejectUnauthorized:!1},async fetch(M){let E=new URL(M.url);N("request",`Bun.serve received: ${M.method} ${E.pathname}`,R);let k=`http://${X.host}`,h=new URL(E.pathname+E.search,k);try{let Z=new Headers(M.headers);if(Z.set("host",X.host),T)Z.set("origin",k);Z.set("x-forwarded-for","127.0.0.1"),Z.set("x-forwarded-proto","https"),Z.set("x-forwarded-host",_);let F=await fetch(h.toString(),{method:M.method,headers:Z,body:M.body,redirect:"manual"}),H=new Headers(F.headers);if(Q&&E.pathname.endsWith(".html")){let x=E.pathname.replace(/\.html$/,"");return new Response(null,{status:301,headers:{Location:x}})}return new Response(F.body,{status:F.status,statusText:F.statusText,headers:H})}catch(Z){return N("request",`Proxy error: ${Z}`,R),new Response(`Proxy Error: ${Z}`,{status:502})}},error(M){return N("server",`Bun.serve error: ${M}`,R),new Response(`Server Error: ${M.message}`,{status:500})}});g.add(S),m_({from:D,to:_,vitePluginUsage:V,listenPort:G,ssl:!0,cleanUrls:Q,verbose:R}),A()}catch(S){W(S)}});let j=c.createServer(z);function Y(A){return g.add(A),new Promise((W,S)=>{A.listen(G,K,()=>{N("server",`Server listening on port ${G}`,R),m_({from:D,to:_,vitePluginUsage:V,listenPort:G,ssl:!!$,cleanUrls:Q,verbose:R}),W()}),A.on("error",(M)=>{N("server",`Server error: ${M}`,R),S(M)})})}return Y(j)}async function o_(D){N("setup",`Setting up reverse proxy: ${y(D)}`,D.verbose);let{from:_,to:B,fromPort:G,sourceUrl:K,ssl:X,verbose:$,cleanup:V,vitePluginUsage:R,changeOrigin:Q,cleanUrls:T}=D,w=80,z=443,j="0.0.0.0",Y=D.portManager||r_,A=x_(D);try{if(A&&B&&!B.includes("localhost")&&!B.includes("127.0.0.1")){if(!(await v([B],$))[0]){J.warn(`The hostname ${B} isn't in your hosts file. Adding it now...`);try{await m([B],$),J.success(`Added ${B} to your hosts file.`)}catch(k){J.error(`Failed to add ${B} to your hosts file: ${k}`),J.info(`You may need to manually add '127.0.0.1 ${B}' to your /etc/hosts file.`)}}}else if(A&&I.platform!=="darwin"&&B&&B.includes("localhost")&&!B.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await v([B],$))[0]){N("hosts",`${B} not found in hosts file, adding...`,$);try{await m([B],$)}catch(k){N("hosts",`Failed to add ${B} to hosts file: ${k}`,$)}}}if(X&&!Y.usedPorts.has(w)){if(!await U(w,j,$))N("setup","Starting HTTP redirect server",$),l_($),Y.usedPorts.add(w);else if(N("setup","Port 80 is in use, skipping HTTP redirect",$),$)J.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let W=X?z:w,S=await U(W,j,$),M;if(S){if(N("setup",`Port ${W} is already in use`,$),$)J.warn(`Port ${W} is already in use. This may be another instance of rpx or another service.`);if(W===443){if(M=await Y.getNextAvailablePort(3443,!0),N("setup",`Using port ${M} instead of ${W}`,$),$)J.info(`Using port ${M} instead. Access your site at https://${B}:${M}`)}else if(M=await Y.getNextAvailablePort(W+1000,!0),N("setup",`Using port ${M} instead of ${W}`,$),$)J.info(`Using port ${M} instead. Access your site at http://${B}:${M}`)}else M=W,Y.usedPorts.add(M),N("setup",`Using standard ${W===443?"HTTPS":"HTTP"} port ${W} for ${B}`,$);await s_(_,B,G,M,j,K,X,R,$,T,Q)}catch(W){N("setup",`Setup failed: ${W}`,$),J.error(`Failed to setup reverse proxy: ${W.message}`),i({domains:[B],hosts:typeof V==="boolean"?V:V?.hosts,certs:typeof V==="boolean"?V:V?.certs,verbose:$,vitePluginUsage:R})}}function l_(D){N("redirect","Starting HTTP redirect server",D);let _=c.createServer((B,G)=>{let K=B.headers.host||"";N("redirect",`Redirecting request from ${K}${B.url} to HTTPS`,D),G.writeHead(301,{Location:`https://${K}${B.url}`}),G.end()}).listen(80);g.add(_),N("redirect","HTTP redirect server started",D)}function e_(D){let _={...N_,...D};if(N("proxy",`Starting proxy with options: ${y(_)}`,_?.verbose),_.viaDaemon){if(!_.from||!_.to){J.error("viaDaemon mode requires both `from` and `to`");return}r({proxies:[{id:_.id,from:_.from,to:_.to,path:_.path,cleanUrls:_.cleanUrls,changeOrigin:_.changeOrigin,pathRewrites:_.pathRewrites}],verbose:_.verbose}).catch((R)=>{J.error(`Failed to register with rpx daemon: ${R.message}`),I.exit(1)});return}let B=_.to||"",G=B.split(".").pop()?.toLowerCase()||"",K=I.platform==="darwin"&&B&&!B.includes("localhost")&&!B.includes("127.0.0.1"),X=["dev","app","page","new","day","foo"],$=["test","localhost","local","example","invalid"];if(K&&X.includes(G)&&_?.verbose)J.warn(`The .${G} TLD may not work reliably for local development`),J.info(` Google owns .${G} with HSTS preloading, which can bypass local DNS`),J.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(K)import("./chunk-fndafyac.js").then(({setupDevelopmentDns:R})=>{R({domains:[B],verbose:_.verbose}).then((Q)=>{if(Q)Promise.resolve().then(()=>{if(_.verbose)if($.includes(G))J.success(`DNS server started for .${G} domains`);else J.success(`DNS server started for .${G} domains (hosts file entry also added)`)});else N("dns",`Could not start DNS server - ${B} may not resolve in browser`,_.verbose)})}).catch((R)=>{N("dns",`Failed to start DNS server: ${R}`,_.verbose)});let V={from:_.from,to:_.to,cleanUrls:_.cleanUrls,https:K_(_),cleanup:_.cleanup,vitePluginUsage:_.vitePluginUsage,changeOrigin:_.changeOrigin,verbose:_.verbose,regenerateUntrustedCerts:_.regenerateUntrustedCerts};N("proxy",`Server options: ${y(V)}`,_.verbose),C_(V).catch((R)=>{N("proxy",`Failed to start proxy: ${R}`,_.verbose),J.error(`Failed to start proxy: ${R.message}`),i({domains:[_.to],hosts:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.hosts,certs:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.certs,verbose:_.verbose})})}function _2(D){return D?.verbose||!1}function x_(D){if(D?.hostsManagement===!1)return!1;let _=D?.cleanup;if(_===!1)return!1;if(_&&typeof _==="object"&&_.hosts===!1)return!1;return!0}async function f_(D){let _={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(D)_={..._,...D};let B=_2(_),G=x_(_);if(N("config",`Starting with config: ${y(_,2)}`,B),N("config",`Is multi-proxy? ${"proxies"in _}`,B),N("config",`Hosts management enabled? ${G}`,B),_.viaDaemon){let A="proxies"in _&&Array.isArray(_.proxies)?_.proxies.map((W)=>({id:W.id,from:W.from,to:W.to,path:W.path,cleanUrls:W.cleanUrls??_.cleanUrls,changeOrigin:W.changeOrigin??_.changeOrigin,pathRewrites:W.pathRewrites})):[{id:_.id,from:_.from,to:_.to,path:_.path,cleanUrls:_.cleanUrls,changeOrigin:_.changeOrigin,pathRewrites:_.pathRewrites}];await r({proxies:A,verbose:B});return}if("proxies"in _&&Array.isArray(_.proxies)){N("servers",`Found ${_.proxies.length} proxies in config`,B);for(let Y of _.proxies)if(Y.start){let A=`${Y.from}-${Y.to}`;try{N("watch",`Starting command for ${A} with command: ${Y.start.command}`,B),J.info(`Starting command for ${A}...`),await B_.startProcess(A,Y.start,B);let W=new URL(Y.from.startsWith("http")?Y.from:`http://${Y.from}`),S=W.hostname||"localhost",M=Number(W.port)||80;try{await p(S,M,B),N("watch",`Dev server is ready at ${S}:${M}`,B)}catch(E){N("watch",`Connection check failed, but continuing with proxy setup: ${E}`,B),J.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(W){throw N("watch",`Failed to start command for ${A}: ${W}`,B),Error(`Failed to start command for ${A}: ${W}`)}}else N("watch",`No start command for proxy ${Y.from} -> ${Y.to}`,B)}else if("start"in _&&_.start){N("watch","Found start command in single proxy config",B);let Y=`${_.from}-${_.to}`;try{if(_.start)N("watch",`Starting command: ${_.start.command}`,B),await B_.startProcess(Y,_.start,B);let A=new URL(_.from?.startsWith("http")?_.from:`http://${_.from}`),W=A.hostname||"localhost",S=Number(A.port)||80;try{await p(W,S,B),N("watch",`Dev server is ready at ${W}:${S}`,B)}catch(M){N("watch",`Connection check failed, but continuing with proxy setup: ${M}`,B),J.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(A){throw N("watch",`Failed to run start command: ${A}`,B),Error(`Failed to run start command: ${A}`)}}else N("watch","No start command found in config",B);let K="proxies"in _&&Array.isArray(_.proxies)?_.proxies[0]?.to:("to"in _)?_.to:"rpx.localhost";if(I.platform!=="win32"&&(_.https||G)){if(!l())try{N("sudo","Pre-acquiring sudo credentials for privileged operations",B),n_("sudo -v",{stdio:"inherit"})}catch{N("sudo","Could not pre-acquire sudo credentials",B)}}if(_.https){let Y=await b(_);if(!Y){if(N("ssl",`No valid or trusted certificates found for ${K}, generating new ones`,_.verbose),await G_(_),Y=await b(_),!Y)throw Error(`Failed to load SSL certificates after generation for ${K}`)}else N("ssl",`Using existing and trusted certificates for ${K}`,_.verbose);_._cachedSSLConfig=Y}let X="proxies"in _&&Array.isArray(_.proxies)?_.proxies.map((Y)=>({...Y,https:_.https,cleanup:_.cleanup,cleanUrls:Y.cleanUrls??("cleanUrls"in _?_.cleanUrls:!1),vitePluginUsage:_.vitePluginUsage,changeOrigin:Y.changeOrigin??_.changeOrigin,verbose:B,_cachedSSLConfig:_._cachedSSLConfig})):[{from:"from"in _?_.from:"localhost:5173",to:"to"in _?_.to:"rpx.localhost",cleanUrls:"cleanUrls"in _?_.cleanUrls:!1,https:_.https,cleanup:_.cleanup,vitePluginUsage:_.vitePluginUsage,start:"start"in _?_.start:void 0,changeOrigin:_.changeOrigin,verbose:B,_cachedSSLConfig:_._cachedSSLConfig}],$=X.map((Y)=>Y.to||"rpx.localhost"),V=_._cachedSSLConfig,R=$.filter((Y)=>Y&&!Y.includes("localhost")&&!Y.includes("127.0.0.1")),Q=["dev","app","page","new","day","foo"],T=["test","localhost","local","example","invalid"],w=[...new Set(R.map((Y)=>Y.split(".").pop()?.toLowerCase()))],z=w.filter((Y)=>!!Y&&Q.includes(Y));if(z.length>0&&B)J.warn(`The following TLDs may not work reliably for local development: ${z.map((Y)=>`.${Y}`).join(", ")}`),J.info(" These TLDs have HSTS preloading which can bypass local DNS"),J.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(G&&I.platform==="darwin"&&R.length>0){let{setupDevelopmentDns:Y}=await import("./chunk-fndafyac.js");if(await Y({domains:R,verbose:B})){if(B)if(w.every((S)=>!!S&&T.includes(S)))J.success(`DNS server started for ${w.map((S)=>`.${S}`).join(", ")} domains`);else J.success(`DNS server started for ${w.map((S)=>`.${S}`).join(", ")} domains (hosts file entries also added)`)}else N("dns","Could not start DNS server - custom domains may not resolve",B)}let j=async()=>{N("cleanup","Starting cleanup handler",_.verbose);try{let{tearDownDevelopmentDns:Y}=await import("./chunk-fndafyac.js");await Y({verbose:_.verbose})}catch(Y){N("cleanup",`Error stopping DNS server: ${Y}`,_.verbose)}try{await B_.stopAll(_.verbose)}catch(Y){N("cleanup",`Error stopping processes: ${Y}`,_.verbose)}await i({domains:$,hosts:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.hosts,certs:typeof _.cleanup==="boolean"?_.cleanup:_.cleanup?.certs,verbose:_.verbose||!1})};if(I.on("SIGINT",j),I.on("SIGTERM",j),I.on("uncaughtException",(Y)=>{N("process",`Uncaught exception: ${Y}`,!0),console.error("Uncaught exception:",Y),j()}),V&&X.length>1){N("proxies",`Creating shared HTTPS server for ${X.length} domains`,B);let Y=[],A=new Set;for(let Z of X){let F=Z.to||"rpx.localhost",H=Z.cleanUrls||!1,x=Z.path,O=w_(x);if(Z.static)Y.push({host:F,path:x,route:{static:F_(Z.static,H),cleanUrls:H,basePath:O}}),N("proxies",`Route: ${F}${x??""} → static ${typeof Z.static==="string"?Z.static:Z.static.dir}`,B);else{let u=new URL(Z.from?.startsWith("http")?Z.from:`http://${Z.from}`);Y.push({host:F,path:x,route:{sourceHost:u.host,cleanUrls:H,changeOrigin:Z.changeOrigin||!1,pathRewrites:Z.pathRewrites,basePath:O}}),N("proxies",`Route: ${F}${x??""} → ${u.host}`,B)}if(A.has(F))continue;if(A.add(F),G&&!j_(F)&&!F.includes("localhost")&&!F.includes("127.0.0.1"))try{if(!(await v([F],B))[0])await m([F],B)}catch{N("hosts",`Could not add hosts entry for ${F}`,B)}}if(!await U(80,"0.0.0.0",B))l_(B);let S=443;if(await U(S,"0.0.0.0",B)){if(N("proxies",`Port ${S} is already in use, cannot start shared proxy`,B),B)J.warn(`Port ${S} is in use. Shared HTTPS proxy cannot start.`);return}let E=E_(Y),k=T_((Z,F)=>H_(E,Z,F),B),h=I_(B);try{let Z=Bun.serve({port:S,hostname:"0.0.0.0",tls:{key:V.key,cert:V.cert,ca:V.ca,requestCert:!1,rejectUnauthorized:!1},fetch(F,H){return k(F,H)},websocket:h,error(F){return N("server",`Shared proxy server error: ${F}`,B),new Response(`Server Error: ${F.message}`,{status:500})}});g.add(Z),N("proxies",`Shared HTTPS proxy listening on port ${S} for ${E.size} domains`,B)}catch(Z){N("proxies",`Failed to start shared proxy: ${Z}`,B),console.error("Failed to start shared HTTPS proxy:",Z),j()}}else for(let Y of X)try{let A=Y.to||"rpx.localhost";N("proxy",`Starting proxy for ${A} with SSL config: ${!!V}`,Y.verbose),await C_({from:Y.from||"localhost:5173",to:A,cleanUrls:Y.cleanUrls||!1,https:Y.https||!1,cleanup:Y.cleanup||!1,vitePluginUsage:Y.vitePluginUsage||!1,verbose:Y.verbose||!1,_cachedSSLConfig:V,changeOrigin:Y.changeOrigin||!1})}catch(A){N("proxies",`Failed to start proxy for ${Y.to}: ${A}`,Y.verbose),console.error(`Failed to start proxy for ${Y.to}:`,A),j()}}function m_(D){if(D?.vitePluginUsage||!D?.verbose)return;if(console.log(""),console.log(` ${f.green(f.bold("rpx"))} ${f.green(`v${t_}`)}`),console.log(` ${f.green("➜")} ${f.dim(D?.from??"")} ${f.dim("➜")} ${f.cyan(D?.ssl?`https://${D?.to}`:`http://${D?.to}`)}`),D?.listenPort!==(D?.ssl?443:80))console.log(` ${f.green("➜")} Listening on port ${D?.listenPort}`);if(D?.cleanUrls)console.log(` ${f.green("➜")} Clean URLs enabled`)}var D2=["/.well-known/acme-challenge/"];function B2(D){let _=D.headers.get("host");if(_)return _.split(":")[0].toLowerCase();try{return new URL(D.url).hostname.toLowerCase()}catch{return""}}function N2(D){let _=D.header.toLowerCase(),B=new Set,G=[];for(let R of D.hosts){let Q=R.toLowerCase();if(Q.startsWith("*."))G.push(Q);else B.add(Q)}let K=D.exemptPaths??D2,X=D.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN.
|
|
9
|
+
`,$=(R)=>{let Q=R.toLowerCase();return B.has(Q)||G.some((T)=>q_(Q,T))},V=(R)=>{let Q=B2(R);if(!$(Q))return;let T="/";try{T=new URL(R.url).pathname}catch{}if(K.some((w)=>T.startsWith(w)))return;if(R.headers.get(_)===D.value)return;return new Response(X,{status:403,headers:{"content-type":"text/plain"}})};return V.protects=$,V}var yB=f_;export{$_ as writeEntry,a2 as watchRegistry,$2 as verifyHttpsChain,F2 as trustRootCaForBrowsers,YD as tearDownDevelopmentDns,RD as syncDevelopmentDnsFromRegistry,q2 as stripBasePath,e2 as stopDnsServer,MD as stopDaemon,C_ as startServer,e_ as startProxy,f_ as startProxies,o2 as startDnsServer,ND as setupResolver,KD as setupDevelopmentDns,P2 as serverNameFromCertFilename,f2 as serveStaticFile,y as safeStringify,C2 as safeRelativePath,lB as safeDeleteFile,r as runViaDaemon,AD as runDaemon,DD as resolverFilePath,n2 as resolverBasenamesForDomains,i2 as resolverBasenameForDomain,F_ as resolveStaticRoute,x2 as resolveStaticFile,bB as resolvePathRewrite,$D as removeResolver,GD as removeLegacyTldResolvers,Z_ as removeHosts,X_ as removeEntry,VD as releaseDaemonLock,OB as redactSensitive,XD as reconcileStaleDevelopmentDns,FD as reconcileDevelopmentDnsOnIdle,v2 as readEntry,WD as readDaemonPid,K2 as readCertSha256Fingerprint,R2 as readCertCommonName,b2 as readAll,A2 as pruneStaleRootCas,p_ as portManager,U2 as pathPrefixMatches,X2 as parseSha256HashesFromSecurityListing,G2 as normalizeSha256Fingerprint,w_ as normalizePathPrefix,p2 as normalizeDevDomain,q_ as matchesWildcard,c2 as matchesAllowedSuffix,H_ as matchHostRoute,O2 as matchHostList,y2 as matchHost,j2 as loadSSLConfig,V2 as listCertSha256HashesByCommonName,j_ as isWildcardPattern,LB as isValidRootCA,Y_ as isValidId,mB as isSingleProxyOptions,vB as isSingleProxyConfig,z2 as isRootCaTrustedForSsl,M2 as isRootCaFingerprintInKeychains,U as isPortInUse,m2 as isPidAlive,uB as isMultiProxyOptions,hB as isMultiProxyConfig,h2 as isLikelyHostname,_D as isDnsServerRunning,ZD as isDaemonRunning,H2 as isCertTrusted,K_ as httpsConfig,l as getSudoPassword,I2 as getSharedDaemonCertPaths,T2 as getRootCAPaths,R_ as getRegistryDir,cB as getPrimaryDomain,S2 as getMacosTrustKeychains,Z2 as getMacosLoginKeychainPath,JD as getDaemonRpxDir,QD as getDaemonPidPath,G_ as generateCertificate,l2 as gcStaleEntries,w2 as forceTrustCertificate,u_ as findAvailablePort,PB as extractHostname,UB as execSudoSync,J_ as ensureDaemonRunning,t2 as devDomainsFromHosts,P_ as deriveIdFromTarget,zD as defaultDaemonSpawnCommand,N_ as defaultConfig,yB as default,N as debugLog,I_ as createProxyWebSocketHandler,T_ as createProxyFetchHandler,N2 as createOriginGuard,k2 as contentTypeFor,BD as contentLooksLikeRpxResolver,N_ as config,f as colors,E2 as clearSslConfigCache,M_ as cleanupCertificates,i as cleanup,v as checkHosts,b as checkExistingCertificates,Y2 as certIncludesSanHostnames,L2 as buildSniTlsConfig,E_ as buildHostRoutes,m as addHosts,SD as acquireDaemonLock,W2 as RPX_ROOT_CA_COMMON_NAME,s2 as RPX_RESOLVER_MARKER,u2 as OnDemandCertManager,Q2 as MACOS_SYSTEM_KEYCHAIN,J2 as MACOS_CA_TRUST_FLAGS,g2 as LEGACY_TLD_RESOLVER_LABELS,d as DefaultPortManager,d2 as DNS_STATE_VERSION,r2 as DNS_PORT};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function createOriginGuard(options: OriginGuardOptions): OriginGuard;
|
|
2
|
+
export declare interface OriginGuardOptions {
|
|
3
|
+
header: string
|
|
4
|
+
value: string
|
|
5
|
+
hosts: string[]
|
|
6
|
+
exemptPaths?: string[]
|
|
7
|
+
forbiddenMessage?: string
|
|
8
|
+
}
|
|
9
|
+
export declare interface OriginGuard {
|
|
10
|
+
(req: Request): Response | undefined
|
|
11
|
+
protects: (hostname: string) => boolean
|
|
12
|
+
}
|
package/package.json
CHANGED
package/src/https.ts
CHANGED
|
@@ -783,8 +783,8 @@ export async function isCertTrusted(
|
|
|
783
783
|
// Different check methods per platform
|
|
784
784
|
if (process.platform === 'darwin') {
|
|
785
785
|
if (options?.serverName)
|
|
786
|
-
return isRootCaTrustedForSsl(certPath, options.serverName, { verbose: options
|
|
787
|
-
return isRootCaFingerprintInKeychains(certPath, { verbose: options
|
|
786
|
+
return isRootCaTrustedForSsl(certPath, options.serverName, { verbose: options?.verbose })
|
|
787
|
+
return isRootCaFingerprintInKeychains(certPath, { verbose: options?.verbose })
|
|
788
788
|
}
|
|
789
789
|
else if (process.platform === 'win32') {
|
|
790
790
|
// On Windows, use PowerShell to check the certificate store
|
package/src/index.ts
CHANGED
|
@@ -117,6 +117,9 @@ export type { GetRoute, ProxyFetchHandler, ProxyRoute, ProxyServer } from './pro
|
|
|
117
117
|
|
|
118
118
|
export { isWildcardPattern, matchesWildcard, matchHost } from './host-match'
|
|
119
119
|
|
|
120
|
+
export { createOriginGuard } from './origin-guard'
|
|
121
|
+
export type { OriginGuard, OriginGuardOptions } from './origin-guard'
|
|
122
|
+
|
|
120
123
|
export {
|
|
121
124
|
buildHostRoutes,
|
|
122
125
|
matchHostList,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin verification guard for "CDN in front of rpx" topologies.
|
|
3
|
+
*
|
|
4
|
+
* When rpx is the origin behind a CDN (e.g. CloudFront → rpx), the origin host
|
|
5
|
+
* is publicly resolvable, so a client could resolve it and hit rpx directly,
|
|
6
|
+
* bypassing the CDN's caching/WAF. The standard mitigation is a shared secret:
|
|
7
|
+
* the CDN injects a secret request header on the origin fetch, and the origin
|
|
8
|
+
* rejects any request to the protected hosts that lacks it.
|
|
9
|
+
*
|
|
10
|
+
* `createOriginGuard` returns a tiny pre-router gate you place in front of your
|
|
11
|
+
* fetch handler. It only guards the listed hosts (exact or `*.wildcard`) — every
|
|
12
|
+
* other host (e.g. apps served directly, not via the CDN) passes through
|
|
13
|
+
* untouched. ACME HTTP-01 challenge paths are exempt by default so cert renewal
|
|
14
|
+
* keeps working on the open `:80` listener.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const guard = createOriginGuard({
|
|
18
|
+
* header: 'x-origin-verify',
|
|
19
|
+
* value: process.env.ORIGIN_SECRET!,
|
|
20
|
+
* hosts: ['stacksjs.com', 'www.stacksjs.com', 'origin.stacksjs.com'],
|
|
21
|
+
* })
|
|
22
|
+
* Bun.serve({ fetch: req => guard(req) ?? handler(req, server) })
|
|
23
|
+
*/
|
|
24
|
+
import { matchesWildcard } from './host-match'
|
|
25
|
+
|
|
26
|
+
export interface OriginGuardOptions {
|
|
27
|
+
/** Header the CDN injects on the origin hop (case-insensitive), e.g. `x-origin-verify`. */
|
|
28
|
+
header: string
|
|
29
|
+
/** Expected secret value. Requests to protected hosts must carry `header: value`. */
|
|
30
|
+
value: string
|
|
31
|
+
/** Hosts to protect — exact (`stacksjs.com`) or wildcard (`*.stacksjs.com`). Others pass through. */
|
|
32
|
+
hosts: string[]
|
|
33
|
+
/**
|
|
34
|
+
* Request paths exempt from the check (prefix match). Defaults to the ACME
|
|
35
|
+
* HTTP-01 challenge prefix so cert issuance/renewal is never blocked.
|
|
36
|
+
*/
|
|
37
|
+
exemptPaths?: string[]
|
|
38
|
+
/** Body returned on rejection. Defaults to a short plain-text message. */
|
|
39
|
+
forbiddenMessage?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OriginGuard {
|
|
43
|
+
/** Returns a 403 Response to short-circuit, or `undefined` to let the request proceed. */
|
|
44
|
+
(req: Request): Response | undefined
|
|
45
|
+
/** Whether a given hostname is in the protected set. */
|
|
46
|
+
protects: (hostname: string) => boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DEFAULT_EXEMPT = ['/.well-known/acme-challenge/']
|
|
50
|
+
|
|
51
|
+
function hostnameOf(req: Request): string {
|
|
52
|
+
const hostHeader = req.headers.get('host')
|
|
53
|
+
if (hostHeader)
|
|
54
|
+
return hostHeader.split(':')[0].toLowerCase()
|
|
55
|
+
try {
|
|
56
|
+
return new URL(req.url).hostname.toLowerCase()
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return ''
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createOriginGuard(options: OriginGuardOptions): OriginGuard {
|
|
64
|
+
const header = options.header.toLowerCase()
|
|
65
|
+
const exact = new Set<string>()
|
|
66
|
+
const wildcards: string[] = []
|
|
67
|
+
for (const h of options.hosts) {
|
|
68
|
+
const host = h.toLowerCase()
|
|
69
|
+
if (host.startsWith('*.'))
|
|
70
|
+
wildcards.push(host)
|
|
71
|
+
else
|
|
72
|
+
exact.add(host)
|
|
73
|
+
}
|
|
74
|
+
const exemptPaths = options.exemptPaths ?? DEFAULT_EXEMPT
|
|
75
|
+
const forbidden = options.forbiddenMessage
|
|
76
|
+
?? 'Forbidden: direct origin access is not allowed; requests must arrive via the CDN.\n'
|
|
77
|
+
|
|
78
|
+
const protects = (hostname: string): boolean => {
|
|
79
|
+
const h = hostname.toLowerCase()
|
|
80
|
+
return exact.has(h) || wildcards.some(w => matchesWildcard(h, w))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const guard: OriginGuard = ((req: Request): Response | undefined => {
|
|
84
|
+
const host = hostnameOf(req)
|
|
85
|
+
if (!protects(host))
|
|
86
|
+
return undefined
|
|
87
|
+
|
|
88
|
+
let pathname = '/'
|
|
89
|
+
try {
|
|
90
|
+
pathname = new URL(req.url).pathname
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// fall through with '/'
|
|
94
|
+
}
|
|
95
|
+
if (exemptPaths.some(p => pathname.startsWith(p)))
|
|
96
|
+
return undefined
|
|
97
|
+
|
|
98
|
+
if (req.headers.get(header) === options.value)
|
|
99
|
+
return undefined
|
|
100
|
+
|
|
101
|
+
return new Response(forbidden, { status: 403, headers: { 'content-type': 'text/plain' } })
|
|
102
|
+
}) as OriginGuard
|
|
103
|
+
guard.protects = protects
|
|
104
|
+
return guard
|
|
105
|
+
}
|