<!DOCTYPE html> <html> <head> <title>Browser Dialer</title> </head> <body> <script> "use strict"; // Enable a much more aggressive JIT for performance gains // Copyright (c) 2021 XRAY. Mozilla Public License 2.0. let url = "ws://" + window.location.host + "/websocket?token=csrfToken"; let clientIdleCount = 0; let upstreamGetCount = 0; let upstreamWsCount = 0; let upstreamPostCount = 0; function prepareRequestInit(extra) { const requestInit = {}; if (extra.referrer) { // note: we have to strip the protocol and host part. // Browsers disallow that, and will reset the value to current page if attempted. const referrer = URL.parse(extra.referrer); requestInit.referrer = referrer.pathname + referrer.search + referrer.hash; requestInit.referrerPolicy = "unsafe-url"; } if (extra.headers) { requestInit.headers = extra.headers; } return requestInit; } let check = function () { if (clientIdleCount > 0) { return; } clientIdleCount += 1; console.log("Prepare", url); let ws = new WebSocket(url); // arraybuffer is significantly faster in chrome than default // blob, tested with chrome 123 ws.binaryType = "arraybuffer"; // note: this event listener is later overwritten after the // handshake has completed. do not attempt to modernize it without // double-checking that this continues to work ws.onmessage = function (event) { clientIdleCount -= 1; let task = JSON.parse(event.data); switch (task.method) { case "WS": { upstreamWsCount += 1; console.log("Dial WS", task.url, task.extra.protocol); const wss = new WebSocket(task.url, task.extra.protocol); wss.binaryType = "arraybuffer"; let opened = false; ws.onmessage = function (event) { wss.send(event.data) }; wss.onopen = function (event) { opened = true; ws.send("ok") }; wss.onmessage = function (event) { ws.send(event.data) }; wss.onclose = function (event) { upstreamWsCount -= 1; console.log("Dial WS DONE, remaining: ", upstreamWsCount); ws.close() }; wss.onerror = function (event) { !opened && ws.send("fail") wss.close() }; ws.onclose = function (event) { wss.close() }; break; } case "GET": { (async () => { const requestInit = prepareRequestInit(task.extra); console.log("Dial GET", task.url); ws.send("ok"); const controller = new AbortController(); /* Aborting a streaming response in JavaScript requires two levers to be pulled: First, the streaming read itself has to be cancelled using reader.cancel(), only then controller.abort() will actually work. If controller.abort() alone is called while a reader.read() is ongoing, it will block until the server closes the response, the page is refreshed or the network connection is lost. */ let reader = null; ws.onclose = (event) => { try { reader && reader.cancel(); } catch(e) {} try { controller.abort(); } catch(e) {} }; try { upstreamGetCount += 1; requestInit.signal = controller.signal; const response = await fetch(task.url, requestInit); const body = await response.body; reader = body.getReader(); while (true) { const { done, value } = await reader.read(); if (value) ws.send(value); // don't send back "undefined" string when received nothing if (done) break; } } finally { upstreamGetCount -= 1; console.log("Dial GET DONE, remaining: ", upstreamGetCount); ws.close(); } })(); break; } case "POST": { upstreamPostCount += 1; const requestInit = prepareRequestInit(task.extra); requestInit.method = "POST"; console.log("Dial POST", task.url); ws.send("ok"); ws.onmessage = async (event) => { try { requestInit.body = event.data; const response = await fetch(task.url, requestInit); if (response.ok) { ws.send("ok"); } else { console.error("bad status code"); ws.send("fail"); } } finally { upstreamPostCount -= 1; console.log("Dial POST DONE, remaining: ", upstreamPostCount); ws.close(); } }; break; } } check(); }; ws.onerror = function (event) { ws.close(); }; }; let checkTask = setInterval(check, 1000); </script> </body> </html>