From ab159f748bdb965797cf9d80dcbbdf876829b12b Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Thu, 4 Aug 2022 14:18:47 -0700 Subject: [PATCH] cmd/tsconnect: switch UI to Preact Reduces the amount of boilerplate to render the UI and makes it easier to respond to state changes (e.g. machine getting authorized, netmap changing, etc.) Preact adds ~13K to our bundle size (5K after Brotli) thus is a neglibible size contribution. We mitigate the delay in rendering the UI by having a static placeholder in the HTML. Required bumping the esbuild version to pick up evanw/esbuild#2349, which makes it easier to support Preact's JSX code generation. Fixes #5137 Fixes #5273 Signed-off-by: Mihai Parparita --- cmd/tsconnect/common.go | 1 + cmd/tsconnect/index.html | 30 +---- cmd/tsconnect/package.json | 1 + cmd/tsconnect/src/app.tsx | 124 ++++++++++++++++++++ cmd/tsconnect/src/go-panic-display.tsx | 21 ++++ cmd/tsconnect/src/header.tsx | 40 +++++++ cmd/tsconnect/src/index.css | 4 - cmd/tsconnect/src/index.ts | 55 +++------ cmd/tsconnect/src/login.ts | 74 ------------ cmd/tsconnect/src/notifier.ts | 65 ----------- cmd/tsconnect/src/ssh.ts | 98 ---------------- cmd/tsconnect/src/ssh.tsx | 156 +++++++++++++++++++++++++ cmd/tsconnect/src/url-display.tsx | 32 +++++ cmd/tsconnect/tailwind.config.js | 2 +- cmd/tsconnect/tsconfig.json | 4 +- cmd/tsconnect/wasm/wasm_js.go | 2 +- cmd/tsconnect/yarn.lock | 5 + go.mod | 4 +- go.sum | 9 +- 19 files changed, 407 insertions(+), 320 deletions(-) create mode 100644 cmd/tsconnect/src/app.tsx create mode 100644 cmd/tsconnect/src/go-panic-display.tsx create mode 100644 cmd/tsconnect/src/header.tsx delete mode 100644 cmd/tsconnect/src/login.ts delete mode 100644 cmd/tsconnect/src/notifier.ts delete mode 100644 cmd/tsconnect/src/ssh.ts create mode 100644 cmd/tsconnect/src/ssh.tsx create mode 100644 cmd/tsconnect/src/url-display.tsx diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go index c03e04a94..9c801c6ee 100644 --- a/cmd/tsconnect/common.go +++ b/cmd/tsconnect/common.go @@ -51,6 +51,7 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) { setupEsbuildTailwind(build, dev) }, }}, + JSXMode: esbuild.JSXModeAutomatic, }, nil } diff --git a/cmd/tsconnect/index.html b/cmd/tsconnect/index.html index 0d78a1cec..3db45fdef 100644 --- a/cmd/tsconnect/index.html +++ b/cmd/tsconnect/index.html @@ -8,37 +8,13 @@ +

Tailscale Connect

-
Loading…
+
Loading…
-
- - -
diff --git a/cmd/tsconnect/package.json b/cmd/tsconnect/package.json index 7f09527ff..06bee1e13 100644 --- a/cmd/tsconnect/package.json +++ b/cmd/tsconnect/package.json @@ -5,6 +5,7 @@ "devDependencies": { "@types/golang-wasm-exec": "^1.15.0", "@types/qrcode": "^1.4.2", + "preact": "^10.10.0", "qrcode": "^1.5.0", "tailwindcss": "^3.1.6", "typescript": "^4.7.4", diff --git a/cmd/tsconnect/src/app.tsx b/cmd/tsconnect/src/app.tsx new file mode 100644 index 000000000..27c44d3c7 --- /dev/null +++ b/cmd/tsconnect/src/app.tsx @@ -0,0 +1,124 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { render, Component } from "preact" +import { IPNState } from "./wasm_js" +import { URLDisplay } from "./url-display" +import { Header } from "./header" +import { GoPanicDisplay } from "./go-panic-display" +import { SSH } from "./ssh" + +type AppState = { + ipn?: IPN + ipnState: IPNState + netMap?: IPNNetMap + browseToURL?: string + goPanicError?: string +} + +class App extends Component<{}, AppState> { + state: AppState = { ipnState: IPNState.NoState } + #goPanicTimeout?: number + + render() { + const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state + + let goPanicDisplay + if (goPanicError) { + goPanicDisplay = ( + + ) + } + + let urlDisplay + if (browseToURL) { + urlDisplay = + } + + let machineAuthInstructions + if (ipnState === IPNState.NeedsMachineAuth) { + machineAuthInstructions = ( +
+ An administrator needs to authorize this device. +
+ ) + } + + let ssh + if (ipn && ipnState === IPNState.Running && netMap) { + ssh = + } + + return ( + <> +
+ {goPanicDisplay} +
+ {urlDisplay} + {machineAuthInstructions} + {ssh} +
+ + ) + } + + runWithIPN(ipn: IPN) { + this.setState({ ipn }, () => { + ipn.run({ + notifyState: this.handleIPNState, + notifyNetMap: this.handleNetMap, + notifyBrowseToURL: this.handleBrowseToURL, + notifyPanicRecover: this.handleGoPanic, + }) + }) + } + + handleIPNState = (state: IPNState) => { + const { ipn } = this.state + this.setState({ ipnState: state }) + if (state == IPNState.NeedsLogin) { + ipn?.login() + } else if ([IPNState.Running, IPNState.NeedsMachineAuth].includes(state)) { + this.setState({ browseToURL: undefined }) + } + } + + handleNetMap = (netMapStr: string) => { + const netMap = JSON.parse(netMapStr) as IPNNetMap + if (DEBUG) { + console.log("Received net map: " + JSON.stringify(netMap, null, 2)) + } + this.setState({ netMap }) + } + + handleBrowseToURL = (url: string) => { + this.setState({ browseToURL: url }) + } + + handleGoPanic = (error: string) => { + if (DEBUG) { + console.error("Go panic", error) + } + this.setState({ goPanicError: error }) + if (this.#goPanicTimeout) { + window.clearTimeout(this.#goPanicTimeout) + } + this.#goPanicTimeout = window.setTimeout(this.clearGoPanic, 10000) + } + + clearGoPanic = () => { + window.clearTimeout(this.#goPanicTimeout) + this.#goPanicTimeout = undefined + this.setState({ goPanicError: undefined }) + } +} + +export function renderApp(): Promise { + return new Promise((resolve) => { + render( + (app ? resolve(app) : undefined)} />, + document.body + ) + }) +} diff --git a/cmd/tsconnect/src/go-panic-display.tsx b/cmd/tsconnect/src/go-panic-display.tsx new file mode 100644 index 000000000..d7c0b808f --- /dev/null +++ b/cmd/tsconnect/src/go-panic-display.tsx @@ -0,0 +1,21 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +export function GoPanicDisplay({ + error, + dismiss, +}: { + error: string + dismiss: () => void +}) { + return ( +
+ Tailscale has encountered an error. +
Click to reload
+
+ ) +} diff --git a/cmd/tsconnect/src/header.tsx b/cmd/tsconnect/src/header.tsx new file mode 100644 index 000000000..ff08adef9 --- /dev/null +++ b/cmd/tsconnect/src/header.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { IPNState } from "./wasm_js" + +export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) { + const stateText = STATE_LABELS[state] + + let logoutButton + if (state === IPNState.Running) { + logoutButton = ( + + ) + } + return ( +
+
+

Tailscale Connect

+
{stateText}
+ {logoutButton} +
+
+ ) +} + +const STATE_LABELS = { + [IPNState.NoState]: "Initializing…", + [IPNState.InUseOtherUser]: "In-use by another user", + [IPNState.NeedsLogin]: "Needs login", + [IPNState.NeedsMachineAuth]: "Needs authorization", + [IPNState.Stopped]: "Stopped", + [IPNState.Starting]: "Starting…", + [IPNState.Running]: "Running", +} as const diff --git a/cmd/tsconnect/src/index.css b/cmd/tsconnect/src/index.css index c0ad0b037..24dd0cb64 100644 --- a/cmd/tsconnect/src/index.css +++ b/cmd/tsconnect/src/index.css @@ -73,7 +73,3 @@ background-color: currentColor; clip-path: polygon(100% 0%, 0 0%, 50% 100%); } - -body.ssh-active #ssh-form { - @apply hidden; -} diff --git a/cmd/tsconnect/src/index.ts b/cmd/tsconnect/src/index.ts index 8716a69bb..198d4d5ac 100644 --- a/cmd/tsconnect/src/index.ts +++ b/cmd/tsconnect/src/index.ts @@ -4,55 +4,26 @@ import "./wasm_exec" import wasmUrl from "./main.wasm" -import { notifyState, notifyNetMap, notifyBrowseToURL } from "./notifier" import { sessionStateStorage } from "./js-state-store" +import { renderApp } from "./app" -const go = new Go() -WebAssembly.instantiateStreaming( - fetch(`./dist/${wasmUrl}`), - go.importObject -).then((result) => { +async function main() { + const app = await renderApp() + const go = new Go() + const wasmInstance = await WebAssembly.instantiateStreaming( + fetch(`./dist/${wasmUrl}`), + go.importObject + ) // The Go process should never exit, if it does then it's an unhandled panic. - go.run(result.instance).then(() => handleGoPanic()) + go.run(wasmInstance.instance).then(() => + app.handleGoPanic("Unexpected shutdown") + ) 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: DEBUG ? sessionStateStorage : undefined, }) - ipn.run({ - notifyState: notifyState.bind(null, ipn), - notifyNetMap: notifyNetMap.bind(null, ipn), - notifyBrowseToURL: notifyBrowseToURL.bind(null, ipn), - notifyPanicRecover: handleGoPanic, - }) -}) - -function handleGoPanic(err?: string) { - if (DEBUG && err) { - console.error("Go panic", err) - } - if (panicNode) { - panicNode.remove() - } - panicNode = document.createElement("div") - panicNode.className = - "rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer" - panicNode.textContent = "Tailscale has encountered an error." - const panicDetailNode = document.createElement("div") - panicDetailNode.className = "text-sm font-normal" - panicDetailNode.textContent = "Click to reload" - panicNode.appendChild(panicDetailNode) - panicNode.addEventListener("click", () => location.reload(), { - once: true, - }) - document.body.appendChild(panicNode) - setTimeout(() => { - panicNode!.remove() - }, 10000) + app.runWithIPN(ipn) } -let panicNode: HTMLDivElement | undefined - -export function getContentNode(): HTMLDivElement { - return document.querySelector("#content") as HTMLDivElement -} +main() diff --git a/cmd/tsconnect/src/login.ts b/cmd/tsconnect/src/login.ts deleted file mode 100644 index 5431cab44..000000000 --- a/cmd/tsconnect/src/login.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import * as qrcode from "qrcode" -import { getContentNode } from "./index" - -export async function showLoginURL(url: string) { - if (loginNode) { - loginNode.remove() - } - loginNode = document.createElement("div") - loginNode.className = "flex flex-col items-center justify-items-center" - const linkNode = document.createElement("a") - linkNode.className = "link" - linkNode.href = url - linkNode.target = "_blank" - loginNode.appendChild(linkNode) - - try { - const dataURL = await qrcode.toDataURL(url, { width: 512 }) - const imageNode = document.createElement("img") - imageNode.className = "mx-auto" - imageNode.src = dataURL - imageNode.width = 256 - imageNode.height = 256 - linkNode.appendChild(imageNode) - } catch (err) { - console.error("Could not generate QR code:", err) - } - - linkNode.appendChild(document.createTextNode(url)) - - getContentNode().appendChild(loginNode) -} - -export function hideLoginURL() { - if (!loginNode) { - return - } - loginNode.remove() - loginNode = undefined -} - -let loginNode: HTMLDivElement | undefined - -export function showLogoutButton(ipn: IPN) { - if (logoutButtonNode) { - logoutButtonNode.remove() - } - logoutButtonNode = document.createElement("button") - logoutButtonNode.className = - "button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold" - logoutButtonNode.textContent = "Logout" - logoutButtonNode.addEventListener( - "click", - () => { - ipn.logout() - }, - { once: true } - ) - const headerNode = document.getElementsByTagName("header")[0]! - headerNode.appendChild(logoutButtonNode) -} - -export function hideLogoutButton() { - if (!logoutButtonNode) { - return - } - logoutButtonNode.remove() - logoutButtonNode = undefined -} - -let logoutButtonNode: HTMLButtonElement | undefined diff --git a/cmd/tsconnect/src/notifier.ts b/cmd/tsconnect/src/notifier.ts deleted file mode 100644 index a5ce3ffca..000000000 --- a/cmd/tsconnect/src/notifier.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import { - showLoginURL, - hideLoginURL, - showLogoutButton, - hideLogoutButton, -} from "./login" -import { showSSHForm, hideSSHForm } from "./ssh" -import { IPNState } from "./wasm_js" - -/** - * @fileoverview Notification callback functions (bridged from ipn.Notify) - */ - -export function notifyState(ipn: IPN, state: IPNState) { - let stateLabel - switch (state) { - case IPNState.NoState: - stateLabel = "Initializing…" - break - case IPNState.InUseOtherUser: - stateLabel = "In-use by another user" - break - case IPNState.NeedsLogin: - stateLabel = "Needs Login" - hideLogoutButton() - hideSSHForm() - ipn.login() - break - case IPNState.NeedsMachineAuth: - stateLabel = "Needs authorization" - break - case IPNState.Stopped: - stateLabel = "Stopped" - hideLogoutButton() - hideSSHForm() - break - case IPNState.Starting: - stateLabel = "Starting…" - break - case IPNState.Running: - stateLabel = "Running" - hideLoginURL() - showLogoutButton(ipn) - break - } - const stateNode = document.querySelector("#state") as HTMLDivElement - stateNode.textContent = stateLabel ?? "" -} - -export function notifyNetMap(ipn: IPN, netMapStr: string) { - const netMap = JSON.parse(netMapStr) as IPNNetMap - if (DEBUG) { - console.log("Received net map: " + JSON.stringify(netMap, null, 2)) - } - - showSSHForm(netMap.peers, ipn) -} - -export function notifyBrowseToURL(ipn: IPN, url: string) { - showLoginURL(url) -} diff --git a/cmd/tsconnect/src/ssh.ts b/cmd/tsconnect/src/ssh.ts deleted file mode 100644 index 1bdaf9307..000000000 --- a/cmd/tsconnect/src/ssh.ts +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import { Terminal } from "xterm" -import { FitAddon } from "xterm-addon-fit" -import { getContentNode } from "./index" - -export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) { - const formNode = document.querySelector("#ssh-form") as HTMLDivElement - const noSSHNode = document.querySelector("#no-ssh") as HTMLDivElement - - const sshPeers = peers.filter( - (p) => p.tailscaleSSHEnabled && p.online !== false - ) - if (sshPeers.length == 0) { - formNode.classList.add("hidden") - noSSHNode.classList.remove("hidden") - return - } - sshPeers.sort((a, b) => a.name.localeCompare(b.name)) - - const selectNode = formNode.querySelector("select")! - selectNode.innerHTML = "" - for (const p of sshPeers) { - const option = document.createElement("option") - option.textContent = p.name.split(".")[0] - option.value = p.name - selectNode.appendChild(option) - } - - const usernameNode = formNode.querySelector(".username") as HTMLInputElement - formNode.onsubmit = (e) => { - e.preventDefault() - const hostname = selectNode.value - ssh(hostname, usernameNode.value, ipn) - } - - noSSHNode.classList.add("hidden") - formNode.classList.remove("hidden") -} - -export function hideSSHForm() { - const formNode = document.querySelector("#ssh-form") as HTMLDivElement - formNode.classList.add("hidden") -} - -function ssh(hostname: string, username: string, ipn: IPN) { - document.body.classList.add("ssh-active") - const termContainerNode = document.createElement("div") - termContainerNode.className = "flex-grow bg-black p-2 overflow-hidden" - getContentNode().appendChild(termContainerNode) - - const term = new Terminal({ - cursorBlink: true, - }) - const fitAddon = new FitAddon() - term.loadAddon(fitAddon) - term.open(termContainerNode) - fitAddon.fit() - - let onDataHook: ((data: string) => void) | undefined - term.onData((e) => { - onDataHook?.(e) - }) - - term.focus() - - const sshSession = ipn.ssh(hostname, username, { - writeFn: (input) => term.write(input), - setReadFn: (hook) => (onDataHook = hook), - rows: term.rows, - cols: term.cols, - onDone: () => { - resizeObserver.disconnect() - term.dispose() - termContainerNode.remove() - document.body.classList.remove("ssh-active") - window.removeEventListener("beforeunload", beforeUnloadListener) - }, - }) - - // Make terminal and SSH session track the size of the containing DOM node. - const resizeObserver = new ResizeObserver((entries) => { - fitAddon.fit() - }) - resizeObserver.observe(termContainerNode) - term.onResize(({ rows, cols }) => { - sshSession.resize(rows, cols) - }) - - // Close the session if the user closes the window without an explicit - // exit. - const beforeUnloadListener = () => { - sshSession.close() - } - window.addEventListener("beforeunload", beforeUnloadListener) -} diff --git a/cmd/tsconnect/src/ssh.tsx b/cmd/tsconnect/src/ssh.tsx new file mode 100644 index 000000000..91ebee04f --- /dev/null +++ b/cmd/tsconnect/src/ssh.tsx @@ -0,0 +1,156 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { useState, useCallback } from "preact/hooks" +import { Terminal } from "xterm" +import { FitAddon } from "xterm-addon-fit" + +type SSHSessionDef = { + username: string + hostname: string +} + +export function SSH({ netMap, ipn }: { netMap: IPNNetMap; ipn: IPN }) { + const [sshSessionDef, setSSHSessionDef] = useState(null) + const clearSSHSessionDef = useCallback(() => setSSHSessionDef(null), []) + if (sshSessionDef) { + return ( + + ) + } + const sshPeers = netMap.peers.filter( + (p) => p.tailscaleSSHEnabled && p.online !== false + ) + + if (sshPeers.length == 0) { + return + } + + return +} + +function SSHSession({ + def, + ipn, + onDone, +}: { + def: SSHSessionDef + ipn: IPN + onDone: () => void +}) { + return ( +
{ + if (node) { + // Run the SSH session aysnchronously, so that the React render + // loop is complete (otherwise the SSH form may still be visible, + // which affects the size of the terminal, leading to a spurious + // initial resize). + setTimeout(() => runSSHSession(node, def, ipn, onDone), 0) + } + }} + /> + ) +} + +function runSSHSession( + termContainerNode: HTMLDivElement, + def: SSHSessionDef, + ipn: IPN, + onDone: () => void +) { + const term = new Terminal({ + cursorBlink: true, + }) + const fitAddon = new FitAddon() + term.loadAddon(fitAddon) + term.open(termContainerNode) + fitAddon.fit() + + let onDataHook: ((data: string) => void) | undefined + term.onData((e) => { + onDataHook?.(e) + }) + + term.focus() + + const sshSession = ipn.ssh(def.hostname, def.username, { + writeFn: (input) => term.write(input), + setReadFn: (hook) => (onDataHook = hook), + rows: term.rows, + cols: term.cols, + onDone: () => { + resizeObserver.disconnect() + term.dispose() + window.removeEventListener("beforeunload", handleBeforeUnload) + onDone() + }, + }) + + // Make terminal and SSH session track the size of the containing DOM node. + const resizeObserver = new ResizeObserver(() => fitAddon.fit()) + resizeObserver.observe(termContainerNode) + term.onResize(({ rows, cols }) => sshSession.resize(rows, cols)) + + // Close the session if the user closes the window without an explicit + // exit. + const handleBeforeUnload = () => sshSession.close() + window.addEventListener("beforeunload", handleBeforeUnload) +} + +function NoSSHPeers() { + return ( +
+ None of your machines have + + Tailscale SSH + + enabled. Give it a try! +
+ ) +} + +function SSHForm({ + sshPeers, + onSubmit, +}: { + sshPeers: IPNNetMapPeerNode[] + onSubmit: (def: SSHSessionDef) => void +}) { + sshPeers = sshPeers.slice().sort((a, b) => a.name.localeCompare(b.name)) + const [username, setUsername] = useState("") + const [hostname, setHostname] = useState(sshPeers[0].name) + return ( +
{ + e.preventDefault() + onSubmit({ username, hostname }) + }} + > + setUsername(e.currentTarget.value)} + /> +
+ +
+ +
+ ) +} diff --git a/cmd/tsconnect/src/url-display.tsx b/cmd/tsconnect/src/url-display.tsx new file mode 100644 index 000000000..47ee1fe4c --- /dev/null +++ b/cmd/tsconnect/src/url-display.tsx @@ -0,0 +1,32 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { useState } from "preact/hooks" +import * as qrcode from "qrcode" + +export function URLDisplay({ url }: { url: string }) { + const [dataURL, setDataURL] = useState("") + qrcode.toDataURL(url, { width: 512 }, (err, dataURL) => { + if (err) { + console.error("Error generating QR code", err) + } else { + setDataURL(dataURL) + } + }) + + return ( + + ) +} diff --git a/cmd/tsconnect/tailwind.config.js b/cmd/tsconnect/tailwind.config.js index dcd5f5878..31823000b 100644 --- a/cmd/tsconnect/tailwind.config.js +++ b/cmd/tsconnect/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./index.html", "./src/**/*.ts"], + content: ["./index.html", "./src/**/*.ts", "./src/**/*.tsx"], theme: { extend: {}, }, diff --git a/cmd/tsconnect/tsconfig.json b/cmd/tsconnect/tsconfig.json index 9a3ccb290..52c25c727 100644 --- a/cmd/tsconnect/tsconfig.json +++ b/cmd/tsconnect/tsconfig.json @@ -6,7 +6,9 @@ "isolatedModules": true, "strict": true, "forceConsistentCasingInFileNames": true, - "sourceMap": true + "sourceMap": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index 121f77ae0..750270a46 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -203,7 +203,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) { if n.State != nil { notifyState(*n.State) } - if nm := n.NetMap; nm != nil && i.lb.State() == ipn.Running { + if nm := n.NetMap; nm != nil { jsNetMap := jsNetMap{ Self: jsNetMapSelfNode{ jsNetMapNode: jsNetMapNode{ diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock index 81c19436b..d9dad03fb 100644 --- a/cmd/tsconnect/yarn.lock +++ b/cmd/tsconnect/yarn.lock @@ -443,6 +443,11 @@ postcss@^8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" +preact@^10.10.0: + version "10.10.0" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.10.0.tgz#7434750a24b59dae1957d95dc0aa47a4a8e9a180" + integrity sha512-fszkg1iJJjq68I4lI8ZsmBiaoQiQHbxf1lNq+72EmC/mZOsFF5zn3k1yv9QGoFgIXzgsdSKtYymLJsrJPoamjQ== + qrcode@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" diff --git a/go.mod b/go.mod index 5257e85aa..706662ab5 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/creack/pty v1.1.17 github.com/dave/jennifer v1.4.1 - github.com/evanw/esbuild v0.14.39 + github.com/evanw/esbuild v0.14.53 github.com/frankban/quicktest v1.14.0 github.com/fxamacker/cbor/v2 v2.4.0 github.com/go-ole/go-ole v1.2.6 @@ -59,7 +59,7 @@ require ( golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/net v0.0.0-20220607020251-c690dde0001d golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f - golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/tools v0.1.11 diff --git a/go.sum b/go.sum index 5938ecbc0..7849f6702 100644 --- a/go.sum +++ b/go.sum @@ -278,8 +278,8 @@ github.com/esimonov/ifshort v1.0.3 h1:JD6x035opqGec5fZ0TLjXeROD2p5H7oLGn8MKfy9HT github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/evanw/esbuild v0.14.39 h1:1TMZtCXOY4ctAbGY4QT9sjT203I/cQ16vXt2F9rLT58= -github.com/evanw/esbuild v0.14.39/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY= +github.com/evanw/esbuild v0.14.53 h1:9uU73SZUmP1jRQhaC6hPm9aoqFGYlPwfk7OrhG6AhpQ= +github.com/evanw/esbuild v0.14.53/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -1484,7 +1484,6 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1495,8 +1494,8 @@ golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=