mirror of https://github.com/XTLS/Xray-core
				
				
				
			
		
			
				
	
	
		
			173 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			HTML
		
	
	
			
		
		
	
	
			173 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			HTML
		
	
	
<!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>
 |