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>
pull/5172/head
Mihai Parparita 2 years ago committed by Mihai Parparita
parent 0a6aa75a2d
commit 389629258b

@ -36,7 +36,7 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
}
return &esbuild.BuildOptions{
EntryPoints: []string{"src/index.js", "src/index.css"},
EntryPoints: []string{"src/index.ts", "src/index.css"},
Loader: map[string]esbuild.Loader{".wasm": esbuild.LoaderFile},
Outdir: *distDir,
Bundle: true,

@ -2,9 +2,15 @@
"name": "@tailscale/ssh",
"version": "0.0.1",
"devDependencies": {
"@types/golang-wasm-exec": "^1.15.0",
"@types/qrcode": "^1.4.2",
"qrcode": "^1.5.0",
"typescript": "^4.7.4",
"xterm": "^4.18.0"
},
"scripts": {
"lint": "tsc --noEmit"
},
"prettier": {
"semi": false,
"printWidth": 80

@ -102,7 +102,7 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
var entryPointsToDefaultDistPaths = map[string]string{
"src/index.css": "dist/index.css",
"src/index.js": "dist/index.js",
"src/index.ts": "dist/index.js",
}
func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) {

@ -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,
}

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "ES2020",
"moduleResolution": "node",
"isolatedModules": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

@ -339,11 +339,10 @@ type jsNetMap struct {
}
type jsNetMapNode struct {
Name string `json:"name"`
Addresses []string `json:"addresses"`
MachineStatus int `json:"machineStatus"`
MachineKey string `json:"machineKey"`
NodeKey string `json:"nodeKey"`
Name string `json:"name"`
Addresses []string `json:"addresses"`
MachineKey string `json:"machineKey"`
NodeKey string `json:"nodeKey"`
}
type jsNetMapSelfNode struct {

@ -2,6 +2,23 @@
# yarn lockfile v1
"@types/golang-wasm-exec@^1.15.0":
version "1.15.0"
resolved "https://registry.yarnpkg.com/@types/golang-wasm-exec/-/golang-wasm-exec-1.15.0.tgz#d0aafbb2b0dc07eaf45dfb83bfb6cdd5b2b3c55c"
integrity sha512-FrL97mp7WW8LqNinVkzTVKOIQKuYjQqgucnh41+1vRQ+bf1LT8uh++KRf9otZPXsa6H1p8ruIGz1BmCGttOL6Q==
"@types/node@*":
version "18.6.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.1.tgz#828e4785ccca13f44e2fb6852ae0ef11e3e20ba5"
integrity sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==
"@types/qrcode@^1.4.2":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74"
integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ==
dependencies:
"@types/node" "*"
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@ -155,6 +172,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"
typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"

Loading…
Cancel
Save