5f40b8a0bc
Enforcing inclusion of our OSS license at the top of .ts and .tsx files. Also updates any relevant files in the repo that were previously missing the license comment. An additional `@license` comment is added to client/web/src/index.tsx to preserve the license in generated Javascript. Updates #10261 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
92 lines
2.4 KiB
TypeScript
92 lines
2.4 KiB
TypeScript
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
import { Terminal, ITerminalOptions } from "xterm"
|
|
import { FitAddon } from "xterm-addon-fit"
|
|
import { WebLinksAddon } from "xterm-addon-web-links"
|
|
|
|
export type SSHSessionDef = {
|
|
username: string
|
|
hostname: string
|
|
/** Defaults to 5 seconds */
|
|
timeoutSeconds?: number
|
|
}
|
|
|
|
export type SSHSessionCallbacks = {
|
|
onConnectionProgress: (messsage: string) => void
|
|
onConnected: () => void
|
|
onDone: () => void
|
|
onError?: (err: string) => void
|
|
}
|
|
|
|
export function runSSHSession(
|
|
termContainerNode: HTMLDivElement,
|
|
def: SSHSessionDef,
|
|
ipn: IPN,
|
|
callbacks: SSHSessionCallbacks,
|
|
terminalOptions?: ITerminalOptions
|
|
) {
|
|
const parentWindow = termContainerNode.ownerDocument.defaultView ?? window
|
|
const term = new Terminal({
|
|
cursorBlink: true,
|
|
allowProposedApi: true,
|
|
...terminalOptions,
|
|
})
|
|
|
|
const fitAddon = new FitAddon()
|
|
term.loadAddon(fitAddon)
|
|
term.open(termContainerNode)
|
|
fitAddon.fit()
|
|
|
|
const webLinksAddon = new WebLinksAddon((event, uri) =>
|
|
event.view?.open(uri, "_blank", "noopener")
|
|
)
|
|
term.loadAddon(webLinksAddon)
|
|
|
|
let onDataHook: ((data: string) => void) | undefined
|
|
term.onData((e) => {
|
|
onDataHook?.(e)
|
|
})
|
|
|
|
term.focus()
|
|
|
|
let resizeObserver: ResizeObserver | undefined
|
|
let handleUnload: ((e: Event) => void) | undefined
|
|
|
|
const sshSession = ipn.ssh(def.hostname, def.username, {
|
|
writeFn(input) {
|
|
term.write(input)
|
|
},
|
|
writeErrorFn(err) {
|
|
callbacks.onError?.(err)
|
|
term.write(err)
|
|
},
|
|
setReadFn(hook) {
|
|
onDataHook = hook
|
|
},
|
|
rows: term.rows,
|
|
cols: term.cols,
|
|
onConnectionProgress: callbacks.onConnectionProgress,
|
|
onConnected: callbacks.onConnected,
|
|
onDone() {
|
|
resizeObserver?.disconnect()
|
|
term.dispose()
|
|
if (handleUnload) {
|
|
parentWindow.removeEventListener("unload", handleUnload)
|
|
}
|
|
callbacks.onDone()
|
|
},
|
|
timeoutSeconds: def.timeoutSeconds,
|
|
})
|
|
|
|
// Make terminal and SSH session track the size of the containing DOM node.
|
|
resizeObserver = new parentWindow.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.
|
|
handleUnload = () => sshSession.close()
|
|
parentWindow.addEventListener("unload", handleUnload)
|
|
}
|