mirror of
https://github.com/tale/headplane.git
synced 2026-08-30 08:49:30 +00:00
feat(ssh): bump restty and follow upstream tailscale ssh
This commit is contained in:
@@ -1,56 +1,21 @@
|
||||
const WASM_MODULE_URL = `${__PREFIX__}/hp_ssh.wasm`;
|
||||
const WASM_HELPER_URL = `${__PREFIX__}/wasm_exec.js`;
|
||||
|
||||
declare global {
|
||||
type HeadplaneSSHFactory = (config: HeadplaneSSHConfig) => HeadplaneSSH;
|
||||
var __hp_ssh_resolve: ((factory: HeadplaneSSHFactory) => void) | undefined;
|
||||
|
||||
var Go: {
|
||||
new (): {
|
||||
importObject: WebAssembly.Imports;
|
||||
run(instance: WebAssembly.Instance): Promise<void>;
|
||||
argv?: string[];
|
||||
env?: Record<string, string>;
|
||||
exit?: (code: number) => void;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface HeadplaneSSHConfig {
|
||||
export interface TailnetConfig {
|
||||
controlURL: string;
|
||||
preAuthKey: string;
|
||||
authKey: string;
|
||||
hostname: string;
|
||||
onReady: () => void;
|
||||
onError?: (message: string) => void;
|
||||
onPanic: (error: string) => void;
|
||||
}
|
||||
|
||||
export interface HeadplaneSSH {
|
||||
openTunnel(config: TunnelConfig): TunnelSession;
|
||||
}
|
||||
|
||||
interface TunnelConfig {
|
||||
ipAddress: string;
|
||||
username: string;
|
||||
timeout?: number;
|
||||
onData: (data: string) => void;
|
||||
onConnect: () => void;
|
||||
onDisconnect: () => void;
|
||||
}
|
||||
|
||||
export interface TunnelSession {
|
||||
writeInput(data: string): void;
|
||||
resize(cols: number, rows: number): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
let resolvedFactory: Promise<HeadplaneSSHFactory> | null = null;
|
||||
let goHelper: Promise<void> | null = null;
|
||||
|
||||
function loadGoHelper(): Promise<void> {
|
||||
if (typeof globalThis.Go !== "undefined") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
goHelper ??= new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = WASM_HELPER_URL;
|
||||
script.crossOrigin = "anonymous";
|
||||
@@ -58,25 +23,47 @@ function loadGoHelper(): Promise<void> {
|
||||
script.onerror = () => reject(new Error("Failed to load Go WASM helper"));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
return goHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-shot function that loads the Go WASM binary and returns the SSH factory.
|
||||
* Automatically loads the Go JS helper if it hasn't been loaded yet.
|
||||
* Boots the Tailscale WASM node and resolves once it has joined the Tailnet.
|
||||
* Rejects if the pre-auth key is refused or the Go runtime panics.
|
||||
*/
|
||||
export async function loadHeadplaneWASM(): Promise<HeadplaneSSHFactory> {
|
||||
if (!resolvedFactory) {
|
||||
await loadGoHelper();
|
||||
export async function connectTailnet(config: TailnetConfig): Promise<IPN> {
|
||||
await loadGoHelper();
|
||||
|
||||
const go = new Go();
|
||||
const result = await WebAssembly.instantiateStreaming(fetch(WASM_MODULE_URL), go.importObject);
|
||||
const go = new Go();
|
||||
const module = await WebAssembly.instantiateStreaming(fetch(WASM_MODULE_URL), go.importObject);
|
||||
|
||||
resolvedFactory = new Promise<HeadplaneSSHFactory>((resolve) => {
|
||||
globalThis.__hp_ssh_resolve = resolve;
|
||||
// The Go process parks on a channel forever, so returning means it died.
|
||||
go.run(module.instance).then(() => config.onPanic("Unexpected shutdown"));
|
||||
|
||||
const ipn = newIPN({
|
||||
controlURL: config.controlURL,
|
||||
authKey: config.authKey,
|
||||
hostname: config.hostname,
|
||||
});
|
||||
|
||||
let loginStarted = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipn.run({
|
||||
notifyState: (state) => {
|
||||
if (state === "Running") resolve(ipn);
|
||||
|
||||
// The backend parks at NeedsLogin until login starts. With an auth key
|
||||
// set this consumes it rather than opening an interactive flow.
|
||||
if (state === "NeedsLogin" && !loginStarted) {
|
||||
loginStarted = true;
|
||||
ipn.login();
|
||||
}
|
||||
},
|
||||
notifyNetMap: () => {},
|
||||
// Only reached when the auth key was refused and the node wants a human.
|
||||
notifyBrowseToURL: () => reject(new Error("Headscale rejected the pre-auth key")),
|
||||
notifyPanicRecover: (error) => reject(new Error(error)),
|
||||
});
|
||||
|
||||
go.run(result.instance);
|
||||
}
|
||||
|
||||
return resolvedFactory;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user