diff --git a/index.html b/index.html index 8ae6b04..d69cd30 100644 --- a/index.html +++ b/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; + }; @@ -126,7 +145,7 @@
  • - +
    Tailscale Login
  • diff --git a/login.html b/login.html new file mode 100644 index 0000000..775fa45 --- /dev/null +++ b/login.html @@ -0,0 +1,12 @@ + + + + + + + Tailscale login + + + Loading network code... + + diff --git a/tun/tailscale.wasm b/tun/tailscale.wasm index f1f2ed9..5c06dbb 100755 Binary files a/tun/tailscale.wasm and b/tun/tailscale.wasm differ diff --git a/tun/tailscale_tun.js b/tun/tailscale_tun.js index 94dc365..d7184a6 100644 --- a/tun/tailscale_tun.js +++ b/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}`] || "" - }, - } - - 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 setupIpStack = () => { - ipn.tun.onmessage = function(ev) { - console.log("received on tun:", ev.data) - IpStack.input(ev.data) - }; - IpStack.output(function(p){ - console.log("sending from tun:", p) - ipn.tun.postMessage(p); - }); - IpStack.init(); - }; - setupIpStack(); - + let ipn = null; 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), - }); + 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) { + IpStack.input(ev.data) + }; + IpStack.output(function(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), + }); + + }; + 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"; diff --git a/tun/tailscale_tun_auto.js b/tun/tailscale_tun_auto.js index 37f5b80..c102e66 100644 --- a/tun/tailscale_tun_auto.js +++ b/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); + }, } } diff --git a/tun/tailscale_tun_ui.js b/tun/tailscale_tun_ui.js deleted file mode 100644 index 4583753..0000000 --- a/tun/tailscale_tun_ui.js +++ /dev/null @@ -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 = ` - -`; - 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}
    `; - for (let p of map.peers) { - peers = `${peers}${p.name.split(".")[0]} -> ${p.addresses[0]}
    `; - } - peersDiv.innerHTML = peers; - }; - - return { - showModal, - updateState, - updatePeers, - setLoginUrl, - getSettings, - } -} -