import BareClient from "/vendor/bare/index.mjs"; import { requestBodyForHttp1 } from "/body.js"; import { classifyTransportError } from "/transport-diagnostics.js"; // The upstream transport omits credentials. This same-origin adapter preserves // the relay's HttpOnly unlock cookie, without exposing it to Discord. class SessionBareClient extends BareClient { async request(remote, method, body, headers, signal) { // Native fetch cannot send streaming request bodies over localhost HTTP/1. // Buffer browser-side streams with an explicit cap; responses stay streamed. body = await requestBodyForHttp1(body,signal); const endpoint=new URL(this.http);endpoint.searchParams.set("target",remote.href); const response = await fetch(endpoint, { method, body, headers:this.createBareHeaders(remote,headers), credentials:"same-origin",signal,duplex:"half", }); if (!response.ok) {const text=await response.text();let message;try{message=JSON.parse(text).message;}catch{}throw new Error(message||("Relay HTTP "+response.status+": "+text.slice(0,120)));} // A destination's 4xx status is not a transport failure. Pass its response // through unchanged so the proxied page can handle it normally. const backend=response.headers.get("x-relay-backend")||"unknown"; const upstreamStatus=Number(response.headers.get("x-bare-status")); if(upstreamStatus===429){ reportRelayProblem("The destination rate-limited a request (429) via "+backend+". Try again later."); }else if(upstreamStatus===400){ reportRelayProblem("The destination rejected a request (400) via "+backend+". Site compatibility may be limited."); } let metadata = response.headers.get("x-bare-headers"); if (!metadata) { const parts = []; for (let i=0; response.headers.has("x-bare-headers-"+i); i++) { const value=response.headers.get("x-bare-headers-"+i); if (!value.startsWith(";")) throw new Error("Invalid response metadata."); parts.push(value.slice(1)); } metadata=parts.join(""); } return { body:response.body, headers:Object.entries(JSON.parse(metadata)), status:Number(response.headers.get("x-bare-status")), statusText:response.headers.get("x-bare-status-text") || "", }; } } const get = id => document.getElementById(id); let enginePromise; let frame; let authenticated = false; let pendingTarget=null,launchTimer,workerKeepalive,opening=false; function transportFailure(error){ const diagnosis=classifyTransportError(error); return diagnosis.category+": "+String(error?.message||error).slice(0,160)+". "+diagnosis.suggestion; } function pingWorker(){ if(get("workspace").hidden)return; try{navigator.serviceWorker.controller?.postMessage("keepalive");} catch(error){console.warn("Service worker keepalive unavailable:",classifyTransportError(error).category);} } function startWorkerKeepalive(){ if(workerKeepalive)return; // Scramjet controller 0.0.14 can forget route prefixes after idle SW termination. // Only ping while the proxy workspace is open; the launchpad itself stays idle. workerKeepalive=setInterval(pingWorker,15000); } function stopWorkerKeepalive(){clearInterval(workerKeepalive);workerKeepalive=null;} document.addEventListener("visibilitychange",()=>{if(!document.hidden)pingWorker();}); addEventListener("pagehide",stopWorkerKeepalive); const query=new URLSearchParams(location.search),embedded=location.pathname==="/embed"; const localDevelopment=["localhost","127.0.0.1","[::1]"].includes(location.hostname); const hostedWisp=query.get("transport")!=="bare"&&!localDevelopment; const wispLabUrl=hostedWisp?`wss://${location.host}/wisp/`:"ws://127.0.0.1:3100/wisp/"; const wispLabAssets="/vendor/libcurl/index.mjs"; const transportOption=get("wisp-lab-option"); const transportSelect=get("transport-mode"); if(localDevelopment){ transportOption.hidden=false; transportSelect.value=localStorage.getItem("zs.localTransport")==="libcurl"?"libcurl":"bare"; transportSelect.addEventListener("change",()=>{ localStorage.setItem("zs.localTransport",transportSelect.value); status("Transport choice saved. Reload the page to use it for a new browser engine."); }); } function useLocalWisp(){return hostedWisp||(localDevelopment&&transportSelect.value==="libcurl");} async function makeTransport(){ if(!useLocalWisp()){ const client=new SessionBareClient(new URL("/bare/",location.href)); await client.init(); return client; } const {default: LibcurlClient}=await import(wispLabAssets); // Below the server limits (8 total/4 per host) to avoid stream throttling. const client=new LibcurlClient({wisp:wispLabUrl,connections:[6,4,3]}); if(typeof client.init==="function")await client.init(); return client; } function testLocalWispConnection(){ return new Promise((resolve,reject)=>{ const socket=new WebSocket(wispLabUrl); const timer=setTimeout(()=>finish(new Error("Local Wisp connection timed out.")),5000); let done=false; function finish(error){if(done)return;done=true;clearTimeout(timer);socket.close();error?reject(error):resolve();} socket.onopen=()=>finish(); socket.onerror=()=>finish(new Error("Start the local Wisp lab server on port 3100.")); socket.onclose=()=>{if(!done)finish(new Error("Local Wisp socket closed."));}; }); } function validTarget(raw){const u=new URL(raw);if(u.protocol!=="https:"||u.username||u.password||u.port)throw new Error("Use an HTTPS website address.");return u.href;} function relayState(text,state="loading"){ get("relay-page-status").textContent=text;get("relay-toolbar").dataset.state=state; if(state!=="loading")clearTimeout(launchTimer); } let lastRelayProblem="",lastRelayProblemAt=0; function reportRelayProblem(message){ const now=Date.now(); if(message===lastRelayProblem&&now-lastRelayProblemAt<30_000)return; lastRelayProblem=message;lastRelayProblemAt=now; console.warn("Relay diagnostic:",message); relayState(message,"warning"); // Keep the original upstream response available to the website. } function requestRelay(url){if(opening)return;try{pendingTarget=validTarget(url);}catch(error){status(error.message,true);return;} const tab=new URL("/embed",location.href);tab.searchParams.set("target",pendingTarget);if(hostedWisp)tab.searchParams.set("transport","wisp");get("relay-tab").href=tab.href;get("relay-direct").href=pendingTarget; if(!authenticated){get("settings-dialog").showModal();document.querySelector(".connection-settings").open=true;get("password").focus();status("Enter your relay passphrase to open this website.");return;} get("open").click();} addEventListener("zs:relay",event=>requestRelay(event.detail.url)); addEventListener("message", event => { if (event.source === get("browser").contentWindow && event.data?.relayDiagnostic) { console.warn("Proxied application:", event.data.relayDiagnostic);relayState("Website reported a script error. If a feature fails, try Open direct.","warning"); } }); function status(message, error = false) { get("status").textContent = message; get("status").dataset.state = error ? "fail" : ""; } function mark(id, text, state = "") { get(id).textContent = text; get(id).dataset.state = state; } async function checkSession() { const response = await fetch("/api/session", {cache:"no-store"}); if (!response.ok) throw new Error("The relay did not respond."); authenticated = (await response.json()).authenticated; get("unlock").hidden = authenticated; get("open").disabled = get("check").disabled = !authenticated; status(authenticated ? "Relay unlocked." : "Unlock your proxy to continue."); } get("mode").textContent = ["localhost","127.0.0.1","[::1]"].includes(location.hostname) ? "LOCAL DEVELOPMENT" : "PRIVATE PROXY"; get("unlock").addEventListener("submit", async event => { event.preventDefault(); const button = get("unlock").querySelector("button"); button.disabled = true; try { const response = await fetch("/api/session"+(embedded?"?embedded=1":""), {method:"POST", body:new URLSearchParams({password:get("password").value,remember:get("remember-device").checked?"1":"0"})}); if (!response.ok) throw new Error((await response.json()).error || "Could not unlock."); get("password").value = ""; await checkSession(); if(!authenticated)throw new Error("Your browser blocked the embedded session cookie. Use Open in a tab to unlock there."); if(pendingTarget){get("settings-dialog").close();get("open").click();} } catch(error) { status(error.message, true); } finally { button.disabled = false; } }); let wakeCancelled=false,wakeAbort; let scriptsPromise; function loadEngineScripts(){ if(scriptsPromise)return scriptsPromise; scriptsPromise=(async()=>{ for(const [src,loaded] of [["/vendor/scramjet/scramjet.js",()=>typeof $scramjet!=="undefined"],["/vendor/controller/controller.api.js",()=>typeof $scramjetController!=="undefined"]]){ if(loaded())continue; await new Promise((resolve,reject)=>{ const script=document.createElement("script");script.src=src; const fail=()=>{clearTimeout(timer);script.remove();reject(new Error("Could not load the browser engine. Check your connection and retry."));}; const timer=setTimeout(fail,15000); script.onload=()=>{clearTimeout(timer);resolve();};script.onerror=fail;document.head.append(script); }); } })(); scriptsPromise.catch(()=>{scriptsPromise=null;});return scriptsPromise; } async function initialize() { if (enginePromise) return enginePromise; enginePromise = (async () => { if (!("serviceWorker" in navigator) || !isSecureContext) throw new Error("Open this page on HTTPS or localhost so the browser engine can start."); await navigator.serviceWorker.register("/sw.js", {scope:"/",updateViaCache:"none"}); await navigator.serviceWorker.ready; if (!navigator.serviceWorker.controller) { await new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error("The service worker has not taken control. Reload the page.")), 15_000); navigator.serviceWorker.addEventListener("controllerchange", () => { clearTimeout(timer); resolve(); }, {once:true}); }); } relayState("Loading browser engine…"); await loadEngineScripts(); const transport = await makeTransport(); const controller = new $scramjetController.Controller({ serviceworker:navigator.serviceWorker.controller, transport, config:{ prefix:"/proxy/", scramjetPath:"/vendor/scramjet/scramjet.js", wasmPath:"/vendor/scramjet/scramjet.wasm", injectPath:"/inject.js", }, scramjetConfig:{ flags:{allowFailedIntercepts:true,captureErrors:false}, siteFlags:{"^https://discord\\.com/":{disableComputedWrap:true}}, }, }); let timer; try { await Promise.race([controller.wait(), new Promise((_, reject) => {timer=setTimeout(() => reject(new Error("Browser engine startup timed out. Reload to retry.")), 20_000);})]); } finally {clearTimeout(timer);} frame = controller.createFrame(get("browser")); navigator.serviceWorker.addEventListener("controllerchange",()=>{ if(!get("workspace").hidden)relayState("Service worker changed. If navigation fails, reload the page to reconnect."); }); get("browser").addEventListener("load", () => { if(!get("workspace").hidden&&get("browser").getAttribute("src")?.includes("/proxy/")){ if(get("relay-toolbar").dataset.state!=="warning")relayState("Page opened. Website features may still need to load.","ready"); else clearTimeout(launchTimer); } try { const page = get("browser").contentWindow; page.addEventListener("error", event => { console.warn("Proxied page error:", event.message || "A script failed to load."); }); page.addEventListener("unhandledrejection", event => { console.warn("Proxied page rejected a promise:", String(event.reason)); }); } catch { /* Isolated pages may not expose their error events. */ } }); return frame; })(); enginePromise.catch(()=>{enginePromise=null;}); return enginePromise; } get("relay-dismiss").addEventListener("click",()=>{get("relay-toolbar").hidden=true;}); get("relay-status-toggle").addEventListener("click",()=>{get("relay-toolbar").hidden=!get("relay-toolbar").hidden;}); get("relay-retry").addEventListener("click",()=>{if(pendingTarget&&!opening)requestRelay(pendingTarget);}); get("open").addEventListener("click", async () => { if (!authenticated||opening) return; get("relay-toolbar").hidden=false;get("relay-status-toggle").hidden=false;wakeCancelled=false;wakeAbort=new AbortController();opening=true;get("relay-retry").disabled=true; get("open").disabled = true; const target=pendingTarget||"https://discord.com/login"; status("Starting relay…");relayState("Checking destination…"); try { const validation=await fetch("/api/target?url="+encodeURIComponent(target)); if(!validation.ok){const result=await validation.json();throw new Error(result.message||"Destination is not enabled on this relay.");} get("launcher").hidden=true;get("workspace").hidden=false;get("home").hidden=false; if(get("settings-dialog").open)get("settings-dialog").close(); if(!localDevelopment){ const started=Date.now();let ready=false; while(Date.now()-started<180000){ if(wakeCancelled)return; relayState(Date.now()-started<5000?"Connecting to relay…":"Waking relay… this can take about a minute."); const slowMessage=setTimeout(()=>relayState("Waking relay… this can take about a minute."),2500); let response; try{response=await fetch("/api/relay/wake",{method:"POST",signal:AbortSignal.any([wakeAbort.signal,AbortSignal.timeout(70000)])});} catch(error){if(wakeCancelled)return;if(Date.now()-started>=180000)throw error;await new Promise(resolve=>setTimeout(resolve,2000));continue;} finally{clearTimeout(slowMessage);} if(response.status===401)throw new Error("Unlock the relay again, then retry."); if(!response.ok){if(response.status>=500){await new Promise(resolve=>setTimeout(resolve,2000));continue;}throw new Error("Relay is unavailable. Try again in a moment.");} if(wakeCancelled)return; if((await response.json()).ready){ready=true;break;} await new Promise(resolve=>setTimeout(resolve,2000)); } if(!ready)throw new Error("The relay is taking too long to wake up. Press Retry in a moment."); } relayState("Starting browser engine…"); const browser = await initialize(); if(wakeCancelled)return; get("launcher").hidden = true; get("workspace").hidden = false; get("home").hidden = false; if(get("settings-dialog").open)get("settings-dialog").close(); startWorkerKeepalive();pingWorker(); relayState("Loading "+new URL(target).hostname+"…");browser.go(target); clearTimeout(launchTimer);launchTimer=setTimeout(()=>relayState("Still loading. Retry, return to your stuff, or open in a tab.","warning"),20000); } catch(error) { status(error.message, true);relayState(error.message,"error");if(get("workspace").hidden){get("settings-dialog").showModal();document.querySelector(".connection-settings").open=true;}} finally {opening=false;get("open").disabled = false;get("relay-retry").disabled=false;} }); get("home").addEventListener("click", () => { get("relay-status-toggle").hidden=true;wakeCancelled=true;wakeAbort?.abort(); get("launcher").hidden = false; get("workspace").hidden = true; get("home").hidden = true; clearTimeout(launchTimer);stopWorkerKeepalive();status("Relay remains open in the background."); }); function testGateway() { return new Promise((resolve, reject) => { const url = new URL("/bare/v3/",location.href); url.protocol = location.protocol === "https:" ? "wss:" : "ws:"; const socket = new WebSocket(url); const timer = setTimeout(() => done(new Error("Gateway test timed out.")), 15_000); let finished = false; function done(error) { if (finished) return; finished = true; clearTimeout(timer); socket.close(); error ? reject(error) : resolve(); } socket.onopen = () => socket.send(JSON.stringify({ type:"connect",remote:"wss://gateway.discord.gg/?v=10&encoding=json", protocols:[],headers:{Origin:"https://discord.com"},forwardHeaders:[], })); socket.onmessage = event => { try { const data = JSON.parse(event.data); if (data.type === "open") return; if (data.op === 10 && data.d?.heartbeat_interval) done(); else done(new Error("Unexpected Gateway response.")); } catch {done(new Error("Invalid Gateway response."));} }; socket.onerror = () => done(new Error("The WebSocket connection failed.")); socket.onclose = () => {if (!finished) done(new Error("The gateway closed before sending its hello."));}; }); } get("check").addEventListener("click", async () => { get("check").disabled = true; mark("http-status","Testing…"); mark("ws-status","Waiting…"); if(useLocalWisp()){ if(hostedWisp){ try{await testLocalWispConnection();mark("ws-status","Hosted Wisp connected","pass");mark("http-status","Not tested");status("Hosted Wisp handshake passed. Website requests and sign-in still need testing.");} catch(error){mark("ws-status","Failed","fail");status(transportFailure(error),true);} get("check").disabled=false;return; } try { const response=await fetch("http://127.0.0.1:3100/healthz",{cache:"no-store"}); if(!response.ok||(await response.json()).status!=="ok")throw new Error("Local Wisp server health check failed."); mark("http-status","Local server ready","pass"); } catch(error){mark("http-status","Failed","fail");status(transportFailure(error),true);} mark("ws-status","Testing…"); try{await testLocalWispConnection();mark("ws-status","Wisp connected","pass");} catch(error){mark("ws-status","Failed","fail");status(transportFailure(error),true);} if(get("http-status").dataset.state==="pass"&&get("ws-status").dataset.state==="pass") status("Local Wisp lab reachable. Try the public example.com page to test rendering; these checks do not test Discord sign-in."); get("check").disabled=false; return; } try { const response = await fetch("/bare/v3/", { headers:{"x-bare-url":"https://discord.com/api/v10/gateway","x-bare-headers":JSON.stringify({Accept:"application/json"})}, signal:AbortSignal.timeout(20_000), }); if (!response.ok || response.headers.get("x-bare-status") !== "200") throw new Error("Discord’s HTTP endpoint did not return 200."); const data = await response.json(); if (data.url !== "wss://gateway.discord.gg") throw new Error("Discord returned an unexpected gateway."); mark("http-status","Passed","pass"); } catch(error) {mark("http-status","Failed","fail"); status(error.message,true);} mark("ws-status","Testing…"); try { await testGateway(); mark("ws-status","Passed","pass"); } catch(error) {mark("ws-status","Failed","fail"); status(error.message,true);} if (get("http-status").dataset.state==="pass" && get("ws-status").dataset.state==="pass") status("Both transports reached Discord. Sign-in, messages, and calls need separate compatibility testing."); get("check").disabled = false; }); try { await checkSession(); const target=query.get("target")||(query.get("open")==="discord"?"https://discord.com/login":null); if(embedded){document.documentElement.dataset.embedded="true";get("launcher").hidden=true;get("workspace").hidden=false;get("home").hidden=true;} if(target)requestRelay(target); } catch(error) {status(error.message,true);}