| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- <!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>
|