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:
Mihai Parparita
2022-07-26 15:45:52 -07:00
committed by Mihai Parparita
parent 0a6aa75a2d
commit 389629258b
13 changed files with 173 additions and 47 deletions
+15
View File
@@ -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)
})
+82
View File
@@ -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,
}