// 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 ) }) }