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 = `
-
-
-
-
-
Network Configuration
-
-
Network Status
-
Disconnected
-
-
-
-
-
-
-`;
- 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,
- }
-}
-