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
This commit is contained in:
parent
d6df3009d8
commit
d0e3852b59
25
index.html
25
index.html
@ -43,9 +43,17 @@
|
|||||||
import { State } from "/tun/tailscale_tun.js";
|
import { State } from "/tun/tailscale_tun.js";
|
||||||
import { autoConf } from "/tun/tailscale_tun_auto.js";
|
import { autoConf } from "/tun/tailscale_tun_auto.js";
|
||||||
|
|
||||||
|
let resolveLogin = null;
|
||||||
|
let loginPromise = new Promise((f,r) => {
|
||||||
|
resolveLogin = f;
|
||||||
|
});
|
||||||
const loginUrlCb = (url) => {
|
const loginUrlCb = (url) => {
|
||||||
const a = document.getElementById("loginLink");
|
const a = document.getElementById("loginLink");
|
||||||
a.href = url;
|
a.href = url;
|
||||||
|
a.target = "_blank";
|
||||||
|
const status = document.getElementById("networkStatus");
|
||||||
|
status.innerHTML = "Tailscale Login";
|
||||||
|
resolveLogin(url);
|
||||||
};
|
};
|
||||||
const stateUpdateCb = (state) => {
|
const stateUpdateCb = (state) => {
|
||||||
switch(state)
|
switch(state)
|
||||||
@ -79,7 +87,7 @@
|
|||||||
const status = document.getElementById("networkStatus");
|
const status = document.getElementById("networkStatus");
|
||||||
status.innerHTML = "Ip: "+ip;
|
status.innerHTML = "Ip: "+ip;
|
||||||
};
|
};
|
||||||
const { listen, connect, bind, parseIP } = await autoConf({
|
const { listen, connect, bind, up } = await autoConf({
|
||||||
loginUrlCb,
|
loginUrlCb,
|
||||||
stateUpdateCb,
|
stateUpdateCb,
|
||||||
netmapUpdateCb,
|
netmapUpdateCb,
|
||||||
@ -87,7 +95,18 @@
|
|||||||
window.networkInterface.bind = bind;
|
window.networkInterface.bind = bind;
|
||||||
window.networkInterface.connect = connect;
|
window.networkInterface.connect = connect;
|
||||||
window.networkInterface.listen = listen;
|
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>
|
||||||
<script src="./xterm/xterm.js"></script>
|
<script src="./xterm/xterm.js"></script>
|
||||||
<script src="./xterm/xterm-addon-fit.js"></script>
|
<script src="./xterm/xterm-addon-fit.js"></script>
|
||||||
@ -126,7 +145,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li style=" margin-right: 50px; height: 100%; display: flex; align-items: center;">
|
<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>
|
<div id="networkStatus" style="color: white; font-family: montserrat; font-weight: 700; font-size: large;">Tailscale Login</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
12
login.html
Normal file
12
login.html
Normal file
@ -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>
|
Binary file not shown.
@ -14,11 +14,7 @@ export const State = {
|
|||||||
|
|
||||||
export async function init() {
|
export async function init() {
|
||||||
const {IpStack} = await ipStackAwait();
|
const {IpStack} = await ipStackAwait();
|
||||||
|
IpStack.init();
|
||||||
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 listeners = {
|
const listeners = {
|
||||||
onstateupdate: () => {},
|
onstateupdate: () => {},
|
||||||
@ -26,6 +22,16 @@ export async function init() {
|
|||||||
onloginurl: () => {},
|
onloginurl: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ipn = null;
|
||||||
|
let localIp = null;
|
||||||
|
let dnsIp = null;
|
||||||
|
|
||||||
|
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 = {
|
const sessionStateStorage = {
|
||||||
setState(id, value) {
|
setState(id, value) {
|
||||||
window.sessionStorage[`ipn-state-${id}`] = value
|
window.sessionStorage[`ipn-state-${id}`] = value
|
||||||
@ -34,8 +40,7 @@ export async function init() {
|
|||||||
return window.sessionStorage[`ipn-state-${id}`] || ""
|
return window.sessionStorage[`ipn-state-${id}`] || ""
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
ipn = newIPN({
|
||||||
const ipn = newIPN({
|
|
||||||
// Persist IPN state in sessionStorage in development, so that we don't need
|
// Persist IPN state in sessionStorage in development, so that we don't need
|
||||||
// to re-authorize every time we reload the page.
|
// to re-authorize every time we reload the page.
|
||||||
stateStorage: sessionStateStorage,
|
stateStorage: sessionStateStorage,
|
||||||
@ -43,20 +48,14 @@ export async function init() {
|
|||||||
|
|
||||||
const setupIpStack = () => {
|
const setupIpStack = () => {
|
||||||
ipn.tun.onmessage = function(ev) {
|
ipn.tun.onmessage = function(ev) {
|
||||||
console.log("received on tun:", ev.data)
|
|
||||||
IpStack.input(ev.data)
|
IpStack.input(ev.data)
|
||||||
};
|
};
|
||||||
IpStack.output(function(p){
|
IpStack.output(function(p){
|
||||||
console.log("sending from tun:", p)
|
|
||||||
ipn.tun.postMessage(p);
|
ipn.tun.postMessage(p);
|
||||||
});
|
});
|
||||||
IpStack.init();
|
|
||||||
};
|
};
|
||||||
setupIpStack();
|
setupIpStack();
|
||||||
|
|
||||||
let localIp = null;
|
|
||||||
let dnsIp = null;
|
|
||||||
|
|
||||||
ipn.run({
|
ipn.run({
|
||||||
notifyState: (s) => listeners.onstateupdate(s),
|
notifyState: (s) => listeners.onstateupdate(s),
|
||||||
notifyNetMap: (s) => {
|
notifyNetMap: (s) => {
|
||||||
@ -75,12 +74,18 @@ export async function init() {
|
|||||||
notifyBrowseToURL: (l) => listeners.onloginurl(l),
|
notifyBrowseToURL: (l) => listeners.onloginurl(l),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connect: IpStack.connect,
|
connect: IpStack.connect,
|
||||||
listen: IpStack.listen,
|
listen: IpStack.listen,
|
||||||
bind: IpStack.bind,
|
bind: IpStack.bind,
|
||||||
parseIP: IpStack.parseIP,
|
parseIP: IpStack.parseIP,
|
||||||
up: (conf) => {
|
up: async (conf) => {
|
||||||
|
if (ipn == null) {
|
||||||
|
await lazyRunIpn();
|
||||||
|
}
|
||||||
ipn.up(conf);
|
ipn.up(conf);
|
||||||
localIp = null;
|
localIp = null;
|
||||||
dnsIp = conf.dnsIp || "127.0.0.53";
|
dnsIp = conf.dnsIp || "127.0.0.53";
|
||||||
|
@ -70,13 +70,14 @@ export async function autoConf({loginUrlCb, stateUpdateCb, netmapUpdateCb}) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
up(settings);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bind,
|
bind,
|
||||||
connect,
|
connect,
|
||||||
listen,
|
listen,
|
||||||
parseIP,
|
parseIP,
|
||||||
|
up: async () => {
|
||||||
|
await up(settings);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user