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:
@@ -5,6 +5,7 @@ import "../wasm_exec"
|
|||||||
import wasmUrl from "./main.wasm"
|
import wasmUrl from "./main.wasm"
|
||||||
import { sessionStateStorage } from "../lib/js-state-store"
|
import { sessionStateStorage } from "../lib/js-state-store"
|
||||||
import { renderApp } from "./app"
|
import { renderApp } from "./app"
|
||||||
|
import { startIPN } from "../lib/start-ipn"
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const app = await renderApp()
|
const app = await renderApp()
|
||||||
@@ -13,23 +14,25 @@ async function main() {
|
|||||||
fetch(`./dist/${wasmUrl}`),
|
fetch(`./dist/${wasmUrl}`),
|
||||||
go.importObject
|
go.importObject
|
||||||
)
|
)
|
||||||
// The Go process should never exit, if it does then it's an unhandled panic.
|
|
||||||
go.run(wasmInstance.instance).then(() =>
|
|
||||||
app.handleGoPanic("Unexpected shutdown")
|
|
||||||
)
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
const authKey = params.get("authkey") ?? undefined
|
const authKey = params.get("authkey") ?? undefined
|
||||||
|
|
||||||
const ipn = newIPN({
|
// The Go process should never exit, if it does then it's an unhandled panic.
|
||||||
// Persist IPN state in sessionStorage in development, so that we don't need
|
const ipn = await startIPN(
|
||||||
// to re-authorize every time we reload the page.
|
go,
|
||||||
stateStorage: DEBUG ? sessionStateStorage : undefined,
|
wasmInstance.instance,
|
||||||
// authKey allows for an auth key to be
|
{
|
||||||
// specified as a url param which automatically
|
// Persist IPN state in sessionStorage in development, so that we don't
|
||||||
// authorizes the client for use.
|
// need to re-authorize every time we reload the page.
|
||||||
authKey: DEBUG ? authKey : undefined,
|
stateStorage: DEBUG ? sessionStateStorage : undefined,
|
||||||
})
|
// authKey allows for an auth key to be
|
||||||
|
// specified as a url param which automatically
|
||||||
|
// authorizes the client for use.
|
||||||
|
authKey: DEBUG ? authKey : undefined,
|
||||||
|
},
|
||||||
|
(reason) => app.handleGoPanic(reason)
|
||||||
|
)
|
||||||
app.runWithIPN(ipn)
|
app.runWithIPN(ipn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
/// <reference path="../types/wasm_js.d.ts" />
|
/// <reference path="../types/wasm_js.d.ts" />
|
||||||
|
|
||||||
import "../wasm_exec"
|
import "../wasm_exec"
|
||||||
|
import { startIPN } from "../lib/start-ipn"
|
||||||
import wasmURL from "./main.wasm"
|
import wasmURL from "./main.wasm"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,11 +31,7 @@ export async function createIPN(config: IPNPackageConfig): Promise<IPN> {
|
|||||||
go.importObject
|
go.importObject
|
||||||
)
|
)
|
||||||
// The Go process should never exit, if it does then it's an unhandled panic.
|
// The Go process should never exit, if it does then it's an unhandled panic.
|
||||||
go.run(wasmInstance.instance).then(() =>
|
return startIPN(go, wasmInstance.instance, config, config.panicHandler)
|
||||||
config.panicHandler("Unexpected shutdown")
|
|
||||||
)
|
|
||||||
|
|
||||||
return newIPN(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { runSSHSession } from "../lib/ssh"
|
export { runSSHSession } from "../lib/ssh"
|
||||||
|
|||||||
Vendored
-2
@@ -7,8 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
function newIPN(config: IPNConfig): IPN
|
|
||||||
|
|
||||||
interface IPN {
|
interface IPN {
|
||||||
run(callbacks: IPNCallbacks): void
|
run(callbacks: IPNCallbacks): void
|
||||||
login(): void
|
login(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user