mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
207 lines
5.5 KiB
HTML
207 lines
5.5 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Browser Dialer</title>
|
|
<link rel="icon" href="data:">
|
|
</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;
|
|
}
|
|
|
|
if (extra.cookies) {
|
|
requestInit.credentials = 'include';
|
|
}
|
|
|
|
return requestInit;
|
|
}
|
|
|
|
function setCookiesFromTask(task) {
|
|
if (!task.extra.cookies) {
|
|
return;
|
|
}
|
|
|
|
const url = new URL(task.url);
|
|
|
|
for (const [name, value] of Object.entries(task.extra.cookies)) {
|
|
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=' + url.pathname;
|
|
}
|
|
}
|
|
|
|
function clearCookiesFromTask(task) {
|
|
if (!task.extra.cookies) {
|
|
return;
|
|
}
|
|
|
|
const url = new URL(task.url);
|
|
|
|
for (const [name, value] of Object.entries(task.extra.cookies)) {
|
|
document.cookie = encodeURIComponent(name) + '=; path=' + url.pathname + '; Max-Age=0';
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (task.method == "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()
|
|
};
|
|
}
|
|
else if (task.method == "GET" && task.streamResponse) {
|
|
(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;
|
|
setCookiesFromTask(task);
|
|
const response = await fetch(task.url, requestInit);
|
|
clearCookiesFromTask(task);
|
|
|
|
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();
|
|
}
|
|
})();
|
|
}
|
|
else if (!task.streamResponse) {
|
|
upstreamPostCount += 1;
|
|
|
|
const requestInit = prepareRequestInit(task.extra);
|
|
requestInit.method = task.method;
|
|
|
|
console.log("Dial", task.method, task.url);
|
|
ws.send("ok");
|
|
ws.onmessage = async (event) => {
|
|
try {
|
|
if (event.data.byteLength > 0) {
|
|
requestInit.body = event.data;
|
|
}
|
|
setCookiesFromTask(task);
|
|
const response = await fetch(task.url, requestInit);
|
|
clearCookiesFromTask(task);
|
|
if (response.ok) {
|
|
ws.send("ok");
|
|
} else {
|
|
console.error("bad status code");
|
|
ws.send("fail");
|
|
}
|
|
} finally {
|
|
upstreamPostCount -= 1;
|
|
console.log("Dial", task.method, "packet DONE, remaining: ", upstreamPostCount);
|
|
ws.close();
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
console.error(`Incorrect task method=${task.method} streamResponse=${task.streamResponse}.`);
|
|
ws.close();
|
|
}
|
|
|
|
check();
|
|
};
|
|
ws.onerror = function (event) {
|
|
ws.close();
|
|
};
|
|
};
|
|
let checkTask = setInterval(check, 1000);
|
|
</script>
|
|
</body>
|
|
</html>
|