cmd/tsconnect: switch to TypeScript
Continues to use esbuild for development mode and building. Also includes a `yarn lint` script that uses tsc to do full type checking. Fixes #5138 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
This commit is contained in:
committed by
Mihai Parparita
parent
0a6aa75a2d
commit
389629258b
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Type definitions for types generated by the esbuild build
|
||||
* process.
|
||||
*/
|
||||
|
||||
declare module "*.wasm" {
|
||||
const path: string
|
||||
export default path
|
||||
}
|
||||
|
||||
declare const DEBUG: boolean
|
||||
@@ -7,7 +7,7 @@ import wasmUrl from "./main.wasm"
|
||||
import { notifyState, notifyNetMap, notifyBrowseToURL } from "./notifier"
|
||||
import { sessionStateStorage } from "./js-state-store"
|
||||
|
||||
const go = new window.Go()
|
||||
const go = new Go()
|
||||
WebAssembly.instantiateStreaming(
|
||||
fetch(`./dist/${wasmUrl}`),
|
||||
go.importObject
|
||||
@@ -2,11 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* @fileoverview Callbacks used by jsStateStore to persist IPN state.
|
||||
*/
|
||||
/** @fileoverview Callbacks used by jsStateStore to persist IPN state. */
|
||||
|
||||
export const sessionStateStorage = {
|
||||
export const sessionStateStorage: IPNStateStorage = {
|
||||
setState(id, value) {
|
||||
window.sessionStorage[`ipn-state-${id}`] = value
|
||||
},
|
||||
@@ -2,9 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import QRCode from "qrcode"
|
||||
import * as qrcode from "qrcode"
|
||||
|
||||
export async function showLoginURL(url) {
|
||||
export async function showLoginURL(url: string) {
|
||||
if (loginNode) {
|
||||
loginNode.remove()
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export async function showLoginURL(url) {
|
||||
loginNode.appendChild(linkNode)
|
||||
|
||||
try {
|
||||
const dataURL = await QRCode.toDataURL(url, { width: 512 })
|
||||
const dataURL = await qrcode.toDataURL(url, { width: 512 })
|
||||
const imageNode = document.createElement("img")
|
||||
imageNode.src = dataURL
|
||||
imageNode.width = 256
|
||||
@@ -41,9 +41,9 @@ export function hideLoginURL() {
|
||||
loginNode = undefined
|
||||
}
|
||||
|
||||
let loginNode
|
||||
let loginNode: HTMLDivElement | undefined
|
||||
|
||||
export function showLogoutButton(ipn) {
|
||||
export function showLogoutButton(ipn: IPN) {
|
||||
if (logoutButtonNode) {
|
||||
logoutButtonNode.remove()
|
||||
}
|
||||
@@ -57,7 +57,8 @@ export function showLogoutButton(ipn) {
|
||||
},
|
||||
{ once: true }
|
||||
)
|
||||
document.getElementById("header").appendChild(logoutButtonNode)
|
||||
const headerNode = document.getElementById("header") as HTMLDivElement
|
||||
headerNode.appendChild(logoutButtonNode)
|
||||
}
|
||||
|
||||
export function hideLogoutButton() {
|
||||
@@ -68,4 +69,4 @@ export function hideLogoutButton() {
|
||||
logoutButtonNode = undefined
|
||||
}
|
||||
|
||||
let logoutButtonNode
|
||||
let logoutButtonNode: HTMLButtonElement | undefined
|
||||
@@ -9,60 +9,50 @@ import {
|
||||
hideLogoutButton,
|
||||
} from "./login"
|
||||
import { showSSHPeers, hideSSHPeers } from "./ssh"
|
||||
import { IPNState } from "./wasm_js"
|
||||
|
||||
/**
|
||||
* @fileoverview Notification callback functions (bridged from ipn.Notify)
|
||||
*/
|
||||
|
||||
/** Mirrors values from ipn/backend.go */
|
||||
const State = {
|
||||
NoState: 0,
|
||||
InUseOtherUser: 1,
|
||||
NeedsLogin: 2,
|
||||
NeedsMachineAuth: 3,
|
||||
Stopped: 4,
|
||||
Starting: 5,
|
||||
Running: 6,
|
||||
}
|
||||
|
||||
export function notifyState(ipn, state) {
|
||||
export function notifyState(ipn: IPN, state: IPNState) {
|
||||
let stateLabel
|
||||
switch (state) {
|
||||
case State.NoState:
|
||||
case IPNState.NoState:
|
||||
stateLabel = "Initializing…"
|
||||
break
|
||||
case State.InUseOtherUser:
|
||||
case IPNState.InUseOtherUser:
|
||||
stateLabel = "In-use by another user"
|
||||
break
|
||||
case State.NeedsLogin:
|
||||
case IPNState.NeedsLogin:
|
||||
stateLabel = "Needs Login"
|
||||
hideLogoutButton()
|
||||
hideSSHPeers()
|
||||
ipn.login()
|
||||
break
|
||||
case State.NeedsMachineAuth:
|
||||
case IPNState.NeedsMachineAuth:
|
||||
stateLabel = "Needs authorization"
|
||||
break
|
||||
case State.Stopped:
|
||||
case IPNState.Stopped:
|
||||
stateLabel = "Stopped"
|
||||
hideLogoutButton()
|
||||
hideSSHPeers()
|
||||
break
|
||||
case State.Starting:
|
||||
case IPNState.Starting:
|
||||
stateLabel = "Starting…"
|
||||
break
|
||||
case State.Running:
|
||||
case IPNState.Running:
|
||||
stateLabel = "Running"
|
||||
hideLoginURL()
|
||||
showLogoutButton(ipn)
|
||||
break
|
||||
}
|
||||
const stateNode = document.getElementById("state")
|
||||
const stateNode = document.getElementById("state") as HTMLDivElement
|
||||
stateNode.textContent = stateLabel ?? ""
|
||||
}
|
||||
|
||||
export function notifyNetMap(ipn, netMapStr) {
|
||||
const netMap = JSON.parse(netMapStr)
|
||||
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))
|
||||
}
|
||||
@@ -70,6 +60,6 @@ export function notifyNetMap(ipn, netMapStr) {
|
||||
showSSHPeers(netMap.peers, ipn)
|
||||
}
|
||||
|
||||
export function notifyBrowseToURL(ipn, url) {
|
||||
export function notifyBrowseToURL(ipn: IPN, url: string) {
|
||||
showLoginURL(url)
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
import { Terminal } from "xterm"
|
||||
|
||||
export function showSSHPeers(peers, ipn) {
|
||||
const peersNode = document.getElementById("peers")
|
||||
export function showSSHPeers(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
||||
const peersNode = document.getElementById("peers") as HTMLDivElement
|
||||
peersNode.innerHTML = ""
|
||||
|
||||
const sshPeers = peers.filter((p) => p.tailscaleSSHEnabled)
|
||||
@@ -35,11 +35,11 @@ export function showSSHPeers(peers, ipn) {
|
||||
}
|
||||
|
||||
export function hideSSHPeers() {
|
||||
const peersNode = document.getElementById("peers")
|
||||
const peersNode = document.getElementById("peers") as HTMLDivElement
|
||||
peersNode.innerHTML = ""
|
||||
}
|
||||
|
||||
function ssh(hostname, ipn) {
|
||||
function ssh(hostname: string, ipn: IPN) {
|
||||
const termContainerNode = document.createElement("div")
|
||||
termContainerNode.className = "term-container"
|
||||
document.body.appendChild(termContainerNode)
|
||||
@@ -56,7 +56,7 @@ function ssh(hostname, ipn) {
|
||||
}
|
||||
})
|
||||
|
||||
let onDataHook
|
||||
let onDataHook: ((data: string) => void) | undefined
|
||||
term.onData((e) => {
|
||||
onDataHook?.(e)
|
||||
})
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Type definitions for types exported by the wasm_js.go Go
|
||||
* module. Not actually a .d.ts file so that we can use enums from it in
|
||||
* esbuild's simplified TypeScript compiler (see https://github.com/evanw/esbuild/issues/2298#issuecomment-1146378367)
|
||||
*/
|
||||
|
||||
declare global {
|
||||
function newIPN(config: IPNConfig): IPN
|
||||
|
||||
interface IPN {
|
||||
run(callbacks: IPNCallbacks): void
|
||||
login(): void
|
||||
logout(): void
|
||||
ssh(
|
||||
host: string,
|
||||
writeFn: (data: string) => void,
|
||||
setReadFn: (readFn: (data: string) => void) => void,
|
||||
rows: number,
|
||||
cols: number,
|
||||
onDone: () => void
|
||||
): void
|
||||
}
|
||||
|
||||
interface IPNStateStorage {
|
||||
setState(id: string, value: string): void
|
||||
getState(id: string): string
|
||||
}
|
||||
|
||||
type IPNConfig = {
|
||||
stateStorage?: IPNStateStorage
|
||||
}
|
||||
|
||||
type IPNCallbacks = {
|
||||
notifyState: (state: IPNState) => void
|
||||
notifyNetMap: (netMapStr: string) => void
|
||||
notifyBrowseToURL: (url: string) => void
|
||||
}
|
||||
|
||||
type IPNNetMap = {
|
||||
self: IPNNetMapSelfNode
|
||||
peers: IPNNetMapPeerNode[]
|
||||
}
|
||||
|
||||
type IPNNetMapNode = {
|
||||
name: string
|
||||
addresses: string[]
|
||||
machineKey: string
|
||||
nodeKey: string
|
||||
}
|
||||
|
||||
type IPNNetMapSelfNode = IPNNetMapNode & {
|
||||
machineStatus: IPNMachineStatus
|
||||
}
|
||||
|
||||
type IPNNetMapPeerNode = IPNNetMapNode & {
|
||||
online: boolean
|
||||
tailscaleSSHEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/** Mirrors values from ipn/backend.go */
|
||||
export const enum IPNState {
|
||||
NoState = 0,
|
||||
InUseOtherUser = 1,
|
||||
NeedsLogin = 2,
|
||||
NeedsMachineAuth = 3,
|
||||
Stopped = 4,
|
||||
Starting = 5,
|
||||
Running = 6,
|
||||
}
|
||||
|
||||
/** Mirrors values from MachineStatus in tailcfg.go */
|
||||
export const enum IPNMachineStatus {
|
||||
MachineUnknown = 0,
|
||||
MachineUnauthorized = 1,
|
||||
MachineAuthorized = 2,
|
||||
MachineInvalid = 3,
|
||||
}
|
||||
Reference in New Issue
Block a user