refactor(tsconnect): move the fork's own TS onto the init callback

build-pkg runs tsc and dts-bundle-generator over src/, so the demo app
and the package entry point have to follow the runtime off the global
factory or the wasm build stops working.

Both now start the runtime through a shared startIPN helper, which
installs the callback, passes its name in through go.env, and races
readiness against the runtime exiting so a startup crash rejects instead
of hanging. The panic handler is wired to that exit rather than being
attached to a floating go.run() promise.

Co-Authored-By: claude-opus-5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 01:01:41 +00:00
co-authored by Claude
parent c98a03dfa5
commit 7ca658b028
4 changed files with 61 additions and 20 deletions
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
/**
* Starts a Go runtime and returns the single IPN it owns.
*
* The runtime does not publish its bridge on a global. It reads the name of a
* callback from its environment and invokes it once the bridge is ready, so
* this resolves on an explicit signal rather than on the Go scheduler having
* run far enough. The name is generated per runtime, so several runtimes can
* start in one page without racing each other.
*/
export async function startIPN(
go: Go,
instance: WebAssembly.Instance,
config: IPNConfig,
onExit: (reason: string) => void
): Promise<IPN> {
const name = `__tsconnectInit_${Math.random().toString(36).slice(2)}`
const globals = globalThis as Record<string, unknown>
const ready = new Promise<(config: IPNConfig) => Promise<IPN>>(
(resolve) => {
globals[name] = (newIPN: (config: IPNConfig) => Promise<IPN>) => {
delete globals[name]
resolve(newIPN)
}
}
)
go.env[INIT_CALLBACK_ENV] = name
const exited = go.run(instance).then(() => {
delete globals[name]
onExit("Unexpected shutdown")
throw new Error("Go runtime exited before the IPN was ready")
})
const newIPN = await Promise.race([ready, exited])
return newIPN(config)
}
/** Must match initCallbackEnv in wasm_js.go. */
const INIT_CALLBACK_ENV = "TSCONNECT_INIT_CALLBACK"