feat(desktop): ride an existing base app on :3000 instead of duplicating (S1)
When the always-on tunnel daemon already serves :3000, the app reuses it (close() is a no-op) rather than spawning a second server on another port, so the GUI and the tunnel share one base app.
This commit is contained in:
@@ -23,6 +23,8 @@ import { buildServerEnv } from './server-config.js'
|
|||||||
|
|
||||||
/** Default listen port when the user hasn't pinned one (free-port fallback still applies). */
|
/** Default listen port when the user hasn't pinned one (free-port fallback still applies). */
|
||||||
const DEFAULT_PORT = 3000
|
const DEFAULT_PORT = 3000
|
||||||
|
/** How long to wait for the probe of an already-running base app before giving up. */
|
||||||
|
const PROBE_TIMEOUT_MS = 1500
|
||||||
|
|
||||||
export interface EmbeddedServerDeps {
|
export interface EmbeddedServerDeps {
|
||||||
readonly prefs: DesktopPrefs
|
readonly prefs: DesktopPrefs
|
||||||
@@ -34,6 +36,26 @@ interface ServerHandle {
|
|||||||
close(): Promise<void>
|
close(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe whether a web-terminal base app is ALREADY serving on `port` (typically
|
||||||
|
* the always-on tunnel daemon that owns :3000 + frpc). `GET /config/ui` returns a
|
||||||
|
* small JSON object with `allowAutoMode` only from our backend, so it doubles as a
|
||||||
|
* cheap "is this one of ours?" fingerprint. Any failure (nothing listening, wrong
|
||||||
|
* shape, timeout) → false, and we spawn our own server as before.
|
||||||
|
*/
|
||||||
|
async function probeExistingBaseApp(port: number): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`http://127.0.0.1:${port}/config/ui`, {
|
||||||
|
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
||||||
|
})
|
||||||
|
if (!res.ok) return false
|
||||||
|
const body: unknown = await res.json()
|
||||||
|
return typeof body === 'object' && body !== null && 'allowAutoMode' in body
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the embedded backend and return a lifecycle handle. Throws (after
|
* Start the embedded backend and return a lifecycle handle. Throws (after
|
||||||
* logging) if the compiled server can't be imported or fails to start — the app
|
* logging) if the compiled server can't be imported or fails to start — the app
|
||||||
@@ -41,7 +63,23 @@ interface ServerHandle {
|
|||||||
*/
|
*/
|
||||||
export async function startEmbeddedServer(deps: EmbeddedServerDeps): Promise<EmbeddedServer> {
|
export async function startEmbeddedServer(deps: EmbeddedServerDeps): Promise<EmbeddedServer> {
|
||||||
const { prefs, logger } = deps
|
const { prefs, logger } = deps
|
||||||
const port = await pickFreePort(prefs.port ?? DEFAULT_PORT)
|
const preferred = prefs.port ?? DEFAULT_PORT
|
||||||
|
|
||||||
|
// If a base app is already up on the preferred port (the always-on tunnel
|
||||||
|
// daemon), RIDE it instead of spawning a duplicate on another port: one base app
|
||||||
|
// then serves the desktop UI, LAN, and the frpc tunnel alike. close() is a no-op
|
||||||
|
// because we don't own that process — quitting the window must not kill it.
|
||||||
|
if (await probeExistingBaseApp(preferred)) {
|
||||||
|
logger.info(`reusing base app already on http://127.0.0.1:${preferred} (tunnel daemon) — not spawning a duplicate`)
|
||||||
|
return {
|
||||||
|
port: preferred,
|
||||||
|
bindHost: '127.0.0.1',
|
||||||
|
allowedOrigins: [],
|
||||||
|
close: async () => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = await pickFreePort(preferred)
|
||||||
const env = buildServerEnv({ prefs, port, platform: process.platform, env: process.env })
|
const env = buildServerEnv({ prefs, port, platform: process.platform, env: process.env })
|
||||||
|
|
||||||
// dist/ is a sibling of the desktop dir in dev; under resourcesPath when packaged.
|
// dist/ is a sibling of the desktop dir in dev; under resourcesPath when packaged.
|
||||||
|
|||||||
Reference in New Issue
Block a user