dialer.html 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Browser Dialer</title>
  5. <link rel="icon" href="data:">
  6. </head>
  7. <body>
  8. <script>
  9. "use strict";
  10. // Enable a much more aggressive JIT for performance gains
  11. // Copyright (c) 2021 XRAY. Mozilla Public License 2.0.
  12. let url = "ws://" + window.location.host + "/websocket?token=csrfToken";
  13. let clientIdleCount = 0;
  14. let upstreamGetCount = 0;
  15. let upstreamWsCount = 0;
  16. let upstreamPostCount = 0;
  17. function prepareRequestInit(extra) {
  18. const requestInit = {};
  19. if (extra.referrer) {
  20. // note: we have to strip the protocol and host part.
  21. // Browsers disallow that, and will reset the value to current page if attempted.
  22. const referrer = URL.parse(extra.referrer);
  23. requestInit.referrer = referrer.pathname + referrer.search + referrer.hash;
  24. requestInit.referrerPolicy = "unsafe-url";
  25. }
  26. if (extra.headers) {
  27. requestInit.headers = extra.headers;
  28. }
  29. if (extra.cookies) {
  30. requestInit.credentials = 'include';
  31. }
  32. return requestInit;
  33. }
  34. function setCookiesFromTask(task) {
  35. if (!task.extra.cookies) {
  36. return;
  37. }
  38. const url = new URL(task.url);
  39. for (const [name, value] of Object.entries(task.extra.cookies)) {
  40. document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=' + url.pathname;
  41. }
  42. }
  43. function clearCookiesFromTask(task) {
  44. if (!task.extra.cookies) {
  45. return;
  46. }
  47. const url = new URL(task.url);
  48. for (const [name, value] of Object.entries(task.extra.cookies)) {
  49. document.cookie = encodeURIComponent(name) + '=; path=' + url.pathname + '; Max-Age=0';
  50. }
  51. }
  52. let check = function () {
  53. if (clientIdleCount > 0) {
  54. return;
  55. }
  56. clientIdleCount += 1;
  57. console.log("Prepare", url);
  58. let ws = new WebSocket(url);
  59. // arraybuffer is significantly faster in chrome than default
  60. // blob, tested with chrome 123
  61. ws.binaryType = "arraybuffer";
  62. // note: this event listener is later overwritten after the
  63. // handshake has completed. do not attempt to modernize it without
  64. // double-checking that this continues to work
  65. ws.onmessage = function (event) {
  66. clientIdleCount -= 1;
  67. let task = JSON.parse(event.data);
  68. if (task.method == "WS") {
  69. upstreamWsCount += 1;
  70. console.log("Dial WS", task.url, task.extra.protocol);
  71. const wss = new WebSocket(task.url, task.extra.protocol);
  72. wss.binaryType = "arraybuffer";
  73. let opened = false;
  74. ws.onmessage = function (event) {
  75. wss.send(event.data)
  76. };
  77. wss.onopen = function (event) {
  78. opened = true;
  79. ws.send("ok")
  80. };
  81. wss.onmessage = function (event) {
  82. ws.send(event.data)
  83. };
  84. wss.onclose = function (event) {
  85. upstreamWsCount -= 1;
  86. console.log("Dial WS DONE, remaining: ", upstreamWsCount);
  87. ws.close()
  88. };
  89. wss.onerror = function (event) {
  90. !opened && ws.send("fail")
  91. wss.close()
  92. };
  93. ws.onclose = function (event) {
  94. wss.close()
  95. };
  96. }
  97. else if (task.method == "GET" && task.streamResponse) {
  98. (async () => {
  99. const requestInit = prepareRequestInit(task.extra);
  100. console.log("Dial GET", task.url);
  101. ws.send("ok");
  102. const controller = new AbortController();
  103. /*
  104. Aborting a streaming response in JavaScript
  105. requires two levers to be pulled:
  106. First, the streaming read itself has to be cancelled using
  107. reader.cancel(), only then controller.abort() will actually work.
  108. If controller.abort() alone is called while a
  109. reader.read() is ongoing, it will block until the server closes the
  110. response, the page is refreshed or the network connection is lost.
  111. */
  112. let reader = null;
  113. ws.onclose = (event) => {
  114. try {
  115. reader && reader.cancel();
  116. } catch(e) {}
  117. try {
  118. controller.abort();
  119. } catch(e) {}
  120. };
  121. try {
  122. upstreamGetCount += 1;
  123. requestInit.signal = controller.signal;
  124. setCookiesFromTask(task);
  125. const response = await fetch(task.url, requestInit);
  126. clearCookiesFromTask(task);
  127. const body = await response.body;
  128. reader = body.getReader();
  129. while (true) {
  130. const { done, value } = await reader.read();
  131. if (value) ws.send(value); // don't send back "undefined" string when received nothing
  132. if (done) break;
  133. }
  134. } finally {
  135. upstreamGetCount -= 1;
  136. console.log("Dial GET DONE, remaining: ", upstreamGetCount);
  137. ws.close();
  138. }
  139. })();
  140. }
  141. else if (!task.streamResponse) {
  142. upstreamPostCount += 1;
  143. const requestInit = prepareRequestInit(task.extra);
  144. requestInit.method = task.method;
  145. console.log("Dial", task.method, task.url);
  146. ws.send("ok");
  147. ws.onmessage = async (event) => {
  148. try {
  149. if (event.data.byteLength > 0) {
  150. requestInit.body = event.data;
  151. }
  152. setCookiesFromTask(task);
  153. const response = await fetch(task.url, requestInit);
  154. clearCookiesFromTask(task);
  155. if (response.ok) {
  156. ws.send("ok");
  157. } else {
  158. console.error("bad status code");
  159. ws.send("fail");
  160. }
  161. } finally {
  162. upstreamPostCount -= 1;
  163. console.log("Dial", task.method, "packet DONE, remaining: ", upstreamPostCount);
  164. ws.close();
  165. }
  166. };
  167. }
  168. else {
  169. console.error(`Incorrect task method=${task.method} streamResponse=${task.streamResponse}.`);
  170. ws.close();
  171. }
  172. check();
  173. };
  174. ws.onerror = function (event) {
  175. ws.close();
  176. };
  177. };
  178. let checkTask = setInterval(check, 1000);
  179. </script>
  180. </body>
  181. </html>