Prechádzať zdrojové kódy

lazy load go wasm module only when logging in

To avoid waiting too much for the go module download and initialization,
and being considered an unwanted popup

Issue: the login window is blocked as a popup
Yuri Iozzelli 2 rokov pred
rodič
commit
d0e3852b59
6 zmenil súbory, kde vykonal 90 pridanie a 263 odobranie
  1. 22 3
      index.html
  2. 12 0
      login.html
  3. BIN
      tun/tailscale.wasm
  4. 53 48
      tun/tailscale_tun.js
  5. 3 2
      tun/tailscale_tun_auto.js
  6. 0 210
      tun/tailscale_tun_ui.js

+ 22 - 3
index.html

@@ -43,9 +43,17 @@
         import { State } from "/tun/tailscale_tun.js";
         import { autoConf } from "/tun/tailscale_tun_auto.js";
 
+        let resolveLogin = null;
+        let loginPromise = new Promise((f,r) => {
+            resolveLogin = f;
+        });
         const loginUrlCb = (url) => {
             const a = document.getElementById("loginLink");
             a.href = url;
+            a.target = "_blank";
+            const status = document.getElementById("networkStatus");
+            status.innerHTML = "Tailscale Login";
+            resolveLogin(url);
         };
         const stateUpdateCb = (state) => {
             switch(state)
@@ -79,7 +87,7 @@
             const status = document.getElementById("networkStatus");
             status.innerHTML = "Ip: "+ip;
         };
-        const { listen, connect, bind, parseIP } = await autoConf({
+        const { listen, connect, bind, up } = await autoConf({
             loginUrlCb,
             stateUpdateCb,
             netmapUpdateCb,
@@ -87,7 +95,18 @@
         window.networkInterface.bind = bind;
         window.networkInterface.connect = connect;
         window.networkInterface.listen = listen;
-        window.parseIP = parseIP;
+        window.startTailscaleAndGetLogin = async () => {
+            const a = document.getElementById("loginLink");
+            a.onclick = null;
+            const status = document.getElementById("networkStatus");
+            status.innerHTML = "Downloading network code...";
+            const w = window.open("login.html", "_blank");
+            await up();
+            w.document.body.innerHTML = "Starting login...";
+            status.innerHTML = "Starting login...";
+            const url = await loginPromise;
+            w.location.href = url;
+        };
     </script>
     <script src="./xterm/xterm.js"></script>
     <script src="./xterm/xterm-addon-fit.js"></script>
@@ -126,7 +145,7 @@
             </a>
           </li>
           <li style=" margin-right: 50px; height: 100%; display: flex; align-items: center;">
-            <a id="loginLink" href="#" target="_blank">
+            <a id="loginLink" href="#" onclick="startTailscaleAndGetLogin()">
               <div id="networkStatus" style="color: white; font-family: montserrat; font-weight: 700; font-size: large;">Tailscale Login</div>
             </a>
           </li>

+ 12 - 0
login.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>Tailscale login</title>
+</head>
+<body>
+    Loading network code...
+</body>
+</html>

BIN
tun/tailscale.wasm


+ 53 - 48
tun/tailscale_tun.js

@@ -14,11 +14,7 @@ export const State = {
 
 export async function init() {
 	const {IpStack} = await ipStackAwait();
-
-	const wasmUrl = new URL("tailscale.wasm", import.meta.url);
-	const go = new window.Go();
-	let {instance} = await WebAssembly.instantiateStreaming(fetch(wasmUrl),go.importObject);
-	go.run(instance);
+	IpStack.init();
 
 	const listeners = {
 		onstateupdate: () => {},
@@ -26,61 +22,70 @@ export async function init() {
 		onloginurl: () => {},
 	}
 
-	const sessionStateStorage = {
-		setState(id, value) {
-		window.sessionStorage[`ipn-state-${id}`] = value
-		},
-		getState(id) {
-		return window.sessionStorage[`ipn-state-${id}`] || ""
-		},
-	}
+	let ipn = null;
+	let localIp = null;
+	let dnsIp = null;
 
-	const ipn = newIPN({
-		// Persist IPN state in sessionStorage in development, so that we don't need
-		// to re-authorize every time we reload the page.
-		stateStorage: sessionStateStorage,
-	});
+	const lazyRunIpn = async () => {
+		const wasmUrl = new URL("tailscale.wasm", import.meta.url);
+		const go = new window.Go();
+		let {instance} = await WebAssembly.instantiateStreaming(fetch(wasmUrl),go.importObject);
+		go.run(instance);
+
+		const sessionStateStorage = {
+			setState(id, value) {
+			window.sessionStorage[`ipn-state-${id}`] = value
+			},
+			getState(id) {
+			return window.sessionStorage[`ipn-state-${id}`] || ""
+			},
+		}
+		ipn = newIPN({
+			// Persist IPN state in sessionStorage in development, so that we don't need
+			// to re-authorize every time we reload the page.
+			stateStorage: sessionStateStorage,
+		});
 
-	const setupIpStack = () => {
-		ipn.tun.onmessage = function(ev) {
-		console.log("received on tun:", ev.data)
-		IpStack.input(ev.data)
+		const setupIpStack = () => {
+			ipn.tun.onmessage = function(ev) {
+				IpStack.input(ev.data)
+			};
+			IpStack.output(function(p){
+				ipn.tun.postMessage(p);
+			});
 		};
-		IpStack.output(function(p){
-		console.log("sending from tun:", p)
-		ipn.tun.postMessage(p);
+		setupIpStack();
+
+		ipn.run({
+			notifyState: (s) => listeners.onstateupdate(s),
+			notifyNetMap: (s) => {
+				const netMap = JSON.parse(s);
+				listeners.onnetmap(netMap);
+				const newLocalIp = netMap.self.addresses[0];
+				if (localIp != newLocalIp)
+				{
+					localIp = newLocalIp;
+					IpStack.up({localIp, ipMap: {
+						["127.0.0.53"]: dnsIp,
+						[dnsIp]: "127.0.0.53",
+					}});
+				}
+			},
+			notifyBrowseToURL: (l) => listeners.onloginurl(l),
 		});
-		IpStack.init();
-	};
-	setupIpStack();
 
-	let localIp = null;
-	let dnsIp = null;
+	};
 
-	ipn.run({
-		notifyState: (s) => listeners.onstateupdate(s),
-		notifyNetMap: (s) => {
-			const netMap = JSON.parse(s);
-			listeners.onnetmap(netMap);
-			const newLocalIp = netMap.self.addresses[0];
-			if (localIp != newLocalIp)
-			{
-				localIp = newLocalIp;
-				IpStack.up({localIp, ipMap: {
-					["127.0.0.53"]: dnsIp,
-					[dnsIp]: "127.0.0.53",
-				}});
-			}
-		},
-		notifyBrowseToURL: (l) => listeners.onloginurl(l),
-	});
 
 	return {
 		connect: IpStack.connect,
 		listen: IpStack.listen,
 		bind: IpStack.bind,
 		parseIP: IpStack.parseIP,
-		up: (conf) => {
+		up: async (conf) => {
+			if (ipn == null) {
+				await lazyRunIpn();
+			}
 			ipn.up(conf);
 			localIp = null;
 			dnsIp = conf.dnsIp || "127.0.0.53";

+ 3 - 2
tun/tailscale_tun_auto.js

@@ -70,13 +70,14 @@ export async function autoConf({loginUrlCb, stateUpdateCb, netmapUpdateCb}) {
 		}
 	};
 
-	up(settings);
-
 	return {
 		bind,
 		connect,
 		listen,
 		parseIP,
+		up: async () => {
+			await up(settings);
+		},
 	}
 }
 

+ 0 - 210
tun/tailscale_tun_ui.js

@@ -1,210 +0,0 @@
-const State = {
-	NoState: 0,
-	InUseOtherUser: 1,
-	NeedsLogin: 2,
-	NeedsMachineAuth: 3,
-	Stopped: 4,
-	Starting: 5,
-	Running: 6,
-};
-export const createUi = (parent, {upCb,downCb,loginCb,logoutCb}) => {
-	const html = `
-<div id="networkModalOverlay" style="width:100%;height:100vh;position:absolute;display:none ;align-items:center;justify-content:center;background:rgba(0,0,0,0.7);color:black;z-index:100">
-	<div id="networkModal" style="max-width:650px;width:100%;background:white;height:400px;display:flex;flex-direction:row;padding:10px;justify-content:space-around">
-
-        <div class="networkModalLeft">
-			<h2>Network Configuration</h2>
-			<form id="networkModalForm">
-				<label for="controlUrl">Control URL: </label>
-				<input type="text" id="controlUrl" name="controlUrl"><br><br>
-				<label for="exitNode">Exit Node: </label>
-				<input type="text" id="exitNode" name="exitNode"><br><br>
-				<label for="dns">DNS Server: </label>
-				<input type="text" id="dns" name="dns"><br><br>
-				<button type="submit">Save</button>
-			</form>
-			<h2>Network Status</h2>
-			<div id="networkModalState">Disconnected</div>
-			<div id="networkModalAction"></div>
-		</div>
-
-		<div class="networkModalRight">
-			<h2>Peers</h2>
-			<div id="networkModalPeers"></div>
-		</div>
-	</div>
-</div>
-`;
-	const templ = document.createElement("template");
-	templ.innerHTML = html;
-	parent.prepend(templ.content);
-
-	const overlay = parent.querySelector("#networkModalOverlay");
-	const form = parent.querySelector("#networkModalForm");
-	const stateDiv = parent.querySelector("#networkModalState");
-	const actionDiv = parent.querySelector("#networkModalAction");
-	const peersDiv = parent.querySelector("#networkModalPeers");
-
-	const getSettings = () => {
-		const str = window.localStorage["networkSettings"] || "{}";
-		const v = JSON.parse(str);
-		return v;
-	};
-	const setSetting = (settings) => {
-		for (const k of Object.keys(settings))
-		{
-			if (settings[k] === "")
-				settings[k] = undefined;
-		}
-		window.localStorage["networkSettings"] = JSON.stringify(settings);
-	}
-	const populate = () => {
-		const settings = getSettings();
-		form.querySelector("#controlUrl").value = settings.controlUrl || "";
-		form.querySelector("#exitNode").value = settings.exitNodeIp || "";
-		form.querySelector("#dns").value = settings.dnsIp || "";
-	};
-	populate();
-
-	const showModal = () => {
-		overlay.style.display = "flex";
-	};
-	const hideModal = () => {
-		overlay.style.display = "none";
-	};
-	overlay.onclick = (e) => {
-		if (e.target === e.currentTarget)
-			hideModal();
-	};
-
-	form.onsubmit = (e) => {
-		e.preventDefault();
-		const settings = {
-			controlUrl: form.elements["controlUrl"].value,
-			exitNodeIp: form.elements["exitNode"].value,
-			dnsIp: form.elements["dns"].value,
-		};
-		setSetting(settings);
-	};
-
-	const updateState = (state) => {
-		switch(state)
-		{
-			case State.NeedsLogin:
-			{
-				loginCb();
-				break;
-			}
-			case State.Running:
-			{
-				const settings = getSettings();
-				settings.wantsRunning = true;
-				setSetting(settings);
-
-				stateDiv.innerHTML = "Running";
-				const action = document.createElement("button");
-				action.textContent = "Stop";
-				action.onclick = () => {
-					downCb();
-					action.disabled = true;
-				}
-				actionDiv.innerHTML = "";
-				actionDiv.appendChild(action);
-				break;
-			}
-			case State.Starting:
-			{
-				stateDiv.innerHTML = "Starting";
-				actionDiv.innerHTML = "";
-				break;
-			}
-			case State.Stopped:
-			{
-				const settings = getSettings();
-				settings.wantsRunning = false;
-				setSetting(settings);
-
-				stateDiv.innerHTML = "Stopped";
-				const actionLogout = document.createElement("button");
-				const actionStart = document.createElement("button");
-				actionStart.textContent = "Start";
-				actionStart.onclick = () => {
-					const settings = getSettings();
-					upCb(settings);
-					actionStart.disabled = true;
-					actionLogout.disabled = true;
-				}
-				actionLogout.textContent = "Logout";
-				actionLogout.onclick = () => {
-					logoutCb();
-					actionStart.disabled = true;
-					actionLogout.disabled = true;
-				}
-				actionDiv.innerHTML = "";
-				actionDiv.appendChild(actionStart);
-				actionDiv.appendChild(actionLogout);
-				break;
-			}
-			case State.NoState:
-			{
-				stateDiv.innerHTML = "Not Started";
-				const action = document.createElement("button");
-				action.textContent = "Start";
-				action.onclick = () => {
-					const settings = getSettings();
-					upCb(settings);
-					action.disabled = true;
-				}
-				actionDiv.innerHTML = "";
-				actionDiv.appendChild(action);
-				setTimeout(()=> {
-					const settings = getSettings();
-					if (settings.wantsRunning)
-					{
-						upCb(settings);
-						action.disabled = true;
-						return;
-					}
-				},0);
-				break;
-			}
-			default:
-			{
-				console.log(state);
-				stateDiv.innerHTML = "Loading";
-				actionDiv.innerHTML = "";
-				break;
-			}
-		}
-	};
-
-	const setLoginUrl = (login) => {
-		console.log("login url:",login);
-		stateDiv.innerHTML = "Need Login";
-		const action = document.createElement("button");
-		action.textContent = "Login";
-		action.onclick = () => {
-			window.open(login, "_blank");
-		}
-		actionDiv.innerHTML = "";
-		actionDiv.appendChild(action);
-	};
-
-	const updatePeers = (map) => {
-		const myIP = map.self.addresses[0];
-		let peers = `self -> ${myIP}<br/>`;
-		for (let p of map.peers) {
-			peers = `${peers}${p.name.split(".")[0]} -> ${p.addresses[0]}<br/>`;
-		}
-		peersDiv.innerHTML = peers;
-	};
-
-	return {
-		showModal,
-		updateState,
-		updatePeers,
-		setLoginUrl,
-		getSettings,
-	}
-}
-