@agimon-ai/po-core 0.0.0-alpha.81 → 0.0.0-alpha.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -11,9 +11,9 @@ let node_child_process = require("node:child_process");
|
|
|
11
11
|
//#region src/services/tunnelProcess/index.ts
|
|
12
12
|
/** Explicit binary path, ahead of a PATH scan. */
|
|
13
13
|
const CLOUDFLARED_ENV = "PO_CLOUDFLARED";
|
|
14
|
-
const START_TIMEOUT_MS =
|
|
14
|
+
const START_TIMEOUT_MS = 12e4;
|
|
15
15
|
const TERMINATE_GRACE_MS = 2e3;
|
|
16
|
-
const SELF_TEST_ATTEMPTS =
|
|
16
|
+
const SELF_TEST_ATTEMPTS = 240;
|
|
17
17
|
const SELF_TEST_RETRY_MS = 500;
|
|
18
18
|
const NAMED_RESTART_ATTEMPTS = 3;
|
|
19
19
|
const NAMED_RESTART_BASE_DELAY_MS = 500;
|
|
@@ -103,7 +103,9 @@ function reapStaleTunnel(stateDir, onNotice) {
|
|
|
103
103
|
try {
|
|
104
104
|
process.kill(pid, "SIGTERM");
|
|
105
105
|
onNotice(`stopped a tunnel left running by a previous hub (pid ${String(pid)})`);
|
|
106
|
-
} catch {
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code !== "ESRCH") onNotice(`Could not stop stale tunnel: ${String(error)}`);
|
|
108
|
+
}
|
|
107
109
|
}
|
|
108
110
|
function tailOf(lines) {
|
|
109
111
|
return lines.slice(-20).join("").trim();
|
|
@@ -128,7 +130,9 @@ async function writeTokenFile(config, stateDir) {
|
|
|
128
130
|
let existingTarget;
|
|
129
131
|
try {
|
|
130
132
|
existingTarget = await node_fs_promises.default.realpath(tokenPath);
|
|
131
|
-
} catch {
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error.code !== "ENOENT") throw error;
|
|
135
|
+
}
|
|
132
136
|
if (sourcePath === node_path.default.resolve(tokenPath) || sourcePath === existingTarget) throw new Error("The configured tunnel token file cannot also be Po runtime token storage.");
|
|
133
137
|
await node_fs_promises.default.rm(tokenPath, { force: true });
|
|
134
138
|
await node_fs_promises.default.writeFile(tokenPath, token, { mode: FILE_MODE });
|
|
@@ -309,13 +313,17 @@ function createSingleTunnelLauncher(options) {
|
|
|
309
313
|
const killTree = () => {
|
|
310
314
|
try {
|
|
311
315
|
child.kill("SIGTERM");
|
|
312
|
-
} catch {
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (error.code !== "ESRCH") notice(`Could not stop tunnel: ${String(error)}`);
|
|
318
|
+
}
|
|
313
319
|
};
|
|
314
320
|
const onProcessExit = () => {
|
|
315
321
|
(0, node_fs.rmSync)(runtimeTokenPath, { force: true });
|
|
316
322
|
try {
|
|
317
323
|
if (child.pid !== void 0) process.kill(child.pid, "SIGKILL");
|
|
318
|
-
} catch {
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (error.code !== "ESRCH") notice(`Could not kill tunnel at exit: ${String(error)}`);
|
|
326
|
+
}
|
|
319
327
|
};
|
|
320
328
|
process.once("exit", onProcessExit);
|
|
321
329
|
const stop = async () => {
|
|
@@ -332,7 +340,9 @@ function createSingleTunnelLauncher(options) {
|
|
|
332
340
|
const escalate = setTimeout(() => {
|
|
333
341
|
try {
|
|
334
342
|
child.kill("SIGKILL");
|
|
335
|
-
} catch {
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error.code !== "ESRCH") notice(`Could not kill tunnel: ${String(error)}`);
|
|
345
|
+
}
|
|
336
346
|
}, TERMINATE_GRACE_MS);
|
|
337
347
|
child.once("exit", () => {
|
|
338
348
|
clearTimeout(escalate);
|
|
@@ -348,16 +358,14 @@ function createSingleTunnelLauncher(options) {
|
|
|
348
358
|
const pidWrite = node_fs_promises.default.mkdir(options.stateDir, {
|
|
349
359
|
recursive: true,
|
|
350
360
|
mode: 448
|
|
351
|
-
}).then(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
} catch {}
|
|
360
|
-
}).catch(() => void 0);
|
|
361
|
+
}).then(() => node_fs_promises.default.writeFile(pidPath, JSON.stringify({
|
|
362
|
+
pid: child.pid,
|
|
363
|
+
ownerPid: process.pid,
|
|
364
|
+
startedAt: Date.now(),
|
|
365
|
+
command: binary
|
|
366
|
+
}), { mode: FILE_MODE })).catch((error) => {
|
|
367
|
+
notice(`Could not write tunnel process record: ${String(error)}`);
|
|
368
|
+
});
|
|
361
369
|
let seenUrl = input.config.kind === "named" ? `https://${input.config.hostname}` : void 0;
|
|
362
370
|
let seenConnection = false;
|
|
363
371
|
const waitToRetry = async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["path","fs","readFileSync","spawn","describeTunnelFailure","tunnelArgs","PAIRING_PAGE_ROUTE","extractTunnelUrl","mentionsRegisteredConnection"],"sources":["../../../../src/services/tunnelProcess/index.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { readFileSync, rmSync } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PAIRING_PAGE_ROUTE } from '../../constants/remote';\nimport {\n type TunnelConfig,\n type TunnelLauncher,\n type TunnelStartInput,\n type TunnelStartResult,\n} from '../../types/remote';\nimport { PAIRING_PAGE_MARKER } from '../pairingPage';\nimport { describeTunnelFailure, extractTunnelUrl, mentionsRegisteredConnection, tunnelArgs } from '../tunnelOutput';\n\n/** Explicit binary path, ahead of a PATH scan. */\nexport const CLOUDFLARED_ENV = 'PO_CLOUDFLARED';\nconst START_TIMEOUT_MS = 45_000;\nconst TERMINATE_GRACE_MS = 2000;\nconst SELF_TEST_ATTEMPTS = 10;\nconst SELF_TEST_RETRY_MS = 500;\nconst NAMED_RESTART_ATTEMPTS = 3;\nconst NAMED_RESTART_BASE_DELAY_MS = 500;\nconst NAMED_RESTART_MAX_DELAY_MS = 4000;\nconst NAMED_RESTART_STABLE_MS = 60_000;\n/** Lines of process output kept for a failure report. */\nconst LOG_TAIL = 20;\nconst UNAUTHORIZED = 401;\n/** Cloudflare has the DNS route but no connector has registered yet. */\nconst EDGE_TUNNEL_UNAVAILABLE = 530;\nconst OK = 200;\nconst PID_FILE = 'tunnel.pid';\nconst TOKEN_FILE = 'tunnel.token';\nconst FILE_MODE = 0o600;\n/** A pid file older than this is stale beyond usefulness and the pid has likely been recycled. */\nconst PID_FILE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport interface ProbeResult {\n status: number;\n body: string;\n}\n\nexport interface TunnelProcessOptions {\n /** Explicit binary, ahead of the env var and the PATH scan. */\n cloudflaredPath?: string;\n stateDir: string;\n onNotice?: (message: string) => void;\n /** Test seam: stands in for node:child_process.spawn. */\n spawnProcess?: typeof spawn;\n /** Test seam: reaches the tunnel from outside, so the self-test needs no network. */\n probe?: (url: string, signal: AbortSignal) => Promise<ProbeResult>;\n /** Test seam: production waits briefly for a new public hostname to propagate. */\n selfTestRetryMs?: number;\n /** Test seam: production bounds the complete startup, including its self-test. */\n startTimeoutMs?: number;\n /** Test seam: production backs named-tunnel restarts off from 500 ms. */\n restartBaseDelayMs?: number;\n /** Test seam: production resets the rapid-crash budget after one stable minute. */\n restartStableMs?: number;\n /** Called after a quick tunnel exits, or after named-tunnel recovery is exhausted. */\n onExit?: (message: string) => void;\n}\n\n/**\n * Finds cloudflared without a shell.\n *\n * A manual scan rather than `which`: spawning a shell to locate a binary is a\n * quoting hazard for no benefit, and this also works the same on every platform.\n */\nexport async function findCloudflared(env: NodeJS.ProcessEnv = process.env): Promise<string | undefined> {\n const names = process.platform === 'win32' ? ['cloudflared.exe', 'cloudflared'] : ['cloudflared'];\n for (const directory of (env.PATH ?? '').split(path.delimiter)) {\n if (directory === '') continue;\n for (const name of names) {\n const candidate = path.join(directory, name);\n try {\n await fs.access(candidate, fs.constants.X_OK);\n return candidate;\n } catch {\n // Not here, or not executable; keep looking.\n }\n }\n }\n return undefined;\n}\n\nasync function defaultProbe(url: string, signal: AbortSignal): Promise<ProbeResult> {\n const response = await fetch(url, { redirect: 'manual', signal });\n return { status: response.status, body: await response.text() };\n}\n\n/**\n * Kills a tunnel left behind by a hub that could not clean up after itself.\n *\n * A SIGKILLed hub never runs its exit handler, and the child is not detached so\n * it is reparented rather than killed. It then holds the tunnel open against a\n * dead port. This is the sweep on the next start.\n *\n * The residual risk is pid reuse after a reboot. The age window narrows it and\n * on Linux the command line is checked; macOS has no equally cheap check, so\n * there the window stands.\n */\ninterface TunnelPidRecord {\n pid?: unknown;\n ownerPid?: unknown;\n startedAt?: unknown;\n}\n\nfunction validPid(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n}\n\n/** Only ESRCH proves the owner is gone; permission and platform errors fail safe. */\nfunction ownerMayBeAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== 'ESRCH';\n }\n}\n\n// sync: exported createRemoteRuntime must reap stale public tunnels before its synchronous startup returns.\nexport function reapStaleTunnel(stateDir: string, onNotice: (message: string) => void): void {\n const pidPath = path.join(stateDir, PID_FILE);\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let raw: string;\n try {\n raw = readFileSync(pidPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') rmSync(tokenPath, { force: true });\n return; // No pid file is the normal case; other read failures are unsafe to guess through.\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return; // A malformed record cannot prove that its owner is gone.\n }\n if (typeof parsed !== 'object' || parsed === null) return;\n const record = parsed as TunnelPidRecord;\n if (Object.hasOwn(record, 'ownerPid')) {\n if (!validPid(record.ownerPid) || ownerMayBeAlive(record.ownerPid)) return;\n }\n\n rmSync(tokenPath, { force: true });\n rmSync(pidPath, { force: true });\n const pid = validPid(record.pid) ? record.pid : undefined;\n const startedAt = typeof record.startedAt === 'number' ? record.startedAt : 0;\n if (pid === undefined || Date.now() - startedAt > PID_FILE_MAX_AGE_MS) return;\n if (process.platform === 'linux') {\n try {\n if (!readFileSync(`/proc/${String(pid)}/cmdline`, 'utf8').includes('cloudflared')) return;\n } catch {\n return; // The process is gone.\n }\n }\n try {\n process.kill(pid, 'SIGTERM');\n onNotice(`stopped a tunnel left running by a previous hub (pid ${String(pid)})`);\n } catch {\n // Already gone, which is the outcome we wanted.\n }\n}\n\nfunction tailOf(lines: readonly string[]): string {\n return lines.slice(-LOG_TAIL).join('').trim();\n}\n\n/** Copies a named tunnel token to a private runtime file understood by cloudflared. */\nasync function writeTokenFile(config: TunnelConfig, stateDir: string): Promise<string | undefined> {\n if (config.kind !== 'named' || config.tokenFile === undefined) return undefined;\n let token: string;\n let sourcePath: string;\n try {\n sourcePath = await fs.realpath(config.tokenFile);\n token = (await fs.readFile(sourcePath, 'utf8')).trim();\n } catch {\n return undefined;\n }\n if (token === '') return undefined;\n await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let existingTarget: string | undefined;\n try {\n existingTarget = await fs.realpath(tokenPath);\n } catch {\n // Missing is the normal state before launch.\n }\n if (sourcePath === path.resolve(tokenPath) || sourcePath === existingTarget) {\n throw new Error('The configured tunnel token file cannot also be Po runtime token storage.');\n }\n await fs.rm(tokenPath, { force: true });\n await fs.writeFile(tokenPath, token, { mode: FILE_MODE });\n await fs.chmod(tokenPath, FILE_MODE);\n return tokenPath;\n}\n\nexport function createTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const restartBaseDelayMs = options.restartBaseDelayMs ?? NAMED_RESTART_BASE_DELAY_MS;\n const restartStableMs = options.restartStableMs ?? NAMED_RESTART_STABLE_MS;\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n let stopped = false;\n let activeStop: (() => Promise<void>) | undefined;\n let stopPromise: Promise<void> | undefined;\n let recoveryPromise: Promise<void> | undefined;\n let pendingExit: string | undefined;\n let restartCount = 0;\n let lastReadyAt = 0;\n const recoveryAbort = new AbortController();\n const signal =\n input.signal === undefined ? recoveryAbort.signal : AbortSignal.any([input.signal, recoveryAbort.signal]);\n const supervisedInput: TunnelStartInput = { ...input, signal };\n\n function queueUnexpectedExit(message: string): void {\n if (stopped) return;\n if (input.config.kind === 'quick') {\n options.onExit?.(message);\n return;\n }\n if (recoveryPromise !== undefined) {\n pendingExit = message;\n return;\n }\n beginRecovery(message);\n }\n\n const single = createSingleTunnelLauncher({ ...options, onExit: queueUnexpectedExit });\n\n async function waitForRestart(attempt: number): Promise<void> {\n if (recoveryAbort.signal.aborted) return;\n const delayMs = Math.min(restartBaseDelayMs * 2 ** (attempt - 1), NAMED_RESTART_MAX_DELAY_MS);\n await new Promise<void>((resolve) => {\n let timer: ReturnType<typeof setTimeout>;\n const done = (): void => {\n clearTimeout(timer);\n recoveryAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n timer = setTimeout(done, delayMs);\n recoveryAbort.signal.addEventListener('abort', done, { once: true });\n if (recoveryAbort.signal.aborted) done();\n });\n }\n\n async function recover(initialMessage: string): Promise<void> {\n if (Date.now() - lastReadyAt >= restartStableMs) restartCount = 0;\n let lastFailure = initialMessage;\n while (!stopped && restartCount < NAMED_RESTART_ATTEMPTS) {\n restartCount += 1;\n notice(\n `cloudflared stopped; restarting named tunnel (attempt ${String(restartCount)} of ${String(NAMED_RESTART_ATTEMPTS)})`,\n );\n await waitForRestart(restartCount);\n if (stopped) return;\n\n const outcome = await single(supervisedInput);\n if (stopped) {\n if (outcome.ok) await outcome.stop().catch(() => undefined);\n return;\n }\n if (!outcome.ok) {\n lastFailure = outcome.message;\n continue;\n }\n activeStop = outcome.stop;\n lastReadyAt = Date.now();\n notice(`named tunnel recovered at ${outcome.publicOrigin}`);\n return;\n }\n\n if (!stopped) {\n options.onExit?.(\n `${initialMessage}\\nNamed tunnel recovery failed after ${String(NAMED_RESTART_ATTEMPTS)} attempts. Last failure: ${lastFailure}`,\n );\n }\n }\n\n function beginRecovery(message: string): void {\n recoveryPromise = recover(message)\n .catch((error: unknown) => {\n if (!stopped) {\n const cause = error instanceof Error ? error.message : String(error);\n options.onExit?.(`${message}\\nNamed tunnel recovery failed: ${cause}`);\n }\n })\n .finally(() => {\n recoveryPromise = undefined;\n const queued = pendingExit;\n pendingExit = undefined;\n if (queued !== undefined && !stopped) beginRecovery(queued);\n });\n }\n\n const initial = await single(supervisedInput);\n if (!initial.ok) {\n stopped = true;\n recoveryAbort.abort();\n return initial;\n }\n activeStop = initial.stop;\n lastReadyAt = Date.now();\n\n const stop = async (): Promise<void> => {\n if (stopPromise !== undefined) return await stopPromise;\n stopped = true;\n recoveryAbort.abort();\n const recovering = recoveryPromise;\n stopPromise = (async () => {\n await activeStop?.().catch(() => undefined);\n await recovering?.catch(() => undefined);\n await fs.rm(path.join(options.stateDir, PID_FILE), { force: true });\n await fs.rm(path.join(options.stateDir, TOKEN_FILE), { force: true });\n })();\n return await stopPromise;\n };\n\n return { ok: true, publicOrigin: initial.publicOrigin, stop };\n };\n}\n\n/** Starts one cloudflared child; the exported launcher supervises successive named-tunnel children. */\nfunction createSingleTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const spawnProcess = options.spawnProcess ?? spawn;\n const probe = options.probe ?? defaultProbe;\n const selfTestRetryMs = options.selfTestRetryMs ?? SELF_TEST_RETRY_MS;\n const startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;\n const pidPath = path.join(options.stateDir, PID_FILE);\n const runtimeTokenPath = path.join(options.stateDir, TOKEN_FILE);\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n if (input.signal?.aborted === true) {\n return { ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' };\n }\n const binary = options.cloudflaredPath ?? process.env[CLOUDFLARED_ENV] ?? (await findCloudflared());\n if (binary === undefined || binary === '') {\n return { ok: false, failure: 'not_installed', message: describeTunnelFailure('not_installed') };\n }\n\n let tokenPath: string | undefined;\n try {\n tokenPath = await writeTokenFile(input.config, options.stateDir);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n const args = tunnelArgs(input.config, input.port, tokenPath);\n let child: ChildProcess;\n try {\n // Never detached and never unref'd. A detached child becomes a process\n // group leader that survives the terminal's SIGINT, which is exactly the\n // orphaned public tunnel this must avoid. Contrast serverSpawner.ts,\n // which detaches on purpose because sessions outlive the hub.\n child = spawnProcess(binary, args, { detached: false, stdio: ['ignore', 'pipe', 'pipe'] });\n } catch (error) {\n await fs.rm(runtimeTokenPath, { force: true });\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n\n const lines: string[] = [];\n let stopped = false;\n let settle: ((result: TunnelStartResult) => void) | undefined;\n let selfTestStarted = false;\n const selfTestAbort = new AbortController();\n\n const killTree = (): void => {\n try {\n child.kill('SIGTERM');\n } catch {\n // Already gone.\n }\n };\n const onProcessExit = (): void => {\n // sync: Node does not await filesystem cleanup in the process exit handler.\n // This is the net under process.exit(), an uncaught exception, and an\n // unhandled rejection, none of which reach the graceful close path.\n rmSync(runtimeTokenPath, { force: true });\n try {\n if (child.pid !== undefined) process.kill(child.pid, 'SIGKILL');\n } catch {\n // Already gone.\n }\n };\n process.once('exit', onProcessExit);\n\n const stop = async (): Promise<void> => {\n if (stopped) return;\n stopped = true;\n if (child.exitCode !== null || child.signalCode !== null) {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n return;\n }\n await new Promise<void>((resolve) => {\n const escalate = setTimeout(() => {\n try {\n child.kill('SIGKILL');\n } catch {\n // Already gone.\n }\n }, TERMINATE_GRACE_MS);\n child.once('exit', () => {\n clearTimeout(escalate);\n resolve();\n });\n killTree();\n });\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n };\n\n // Register child listeners below without yielding, so early exits cannot be missed.\n // Cleanup waits for this ordered write before removing the record.\n const pidWrite = fs\n .mkdir(options.stateDir, { recursive: true, mode: 0o700 })\n .then(async () => {\n try {\n await fs.writeFile(\n pidPath,\n JSON.stringify({ pid: child.pid, ownerPid: process.pid, startedAt: Date.now(), command: binary }),\n { mode: FILE_MODE },\n );\n } catch {\n // The reaper is a safety net; a missing record must not stop startup.\n }\n })\n .catch(() => undefined);\n\n const configuredOrigin = input.config.kind === 'named' ? `https://${input.config.hostname}` : undefined;\n let seenUrl = configuredOrigin;\n let seenConnection = false;\n\n const waitToRetry = async (): Promise<void> => {\n if (selfTestRetryMs <= 0) return;\n await new Promise<void>((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n selfTestAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, selfTestRetryMs);\n selfTestAbort.signal.addEventListener('abort', done, { once: true });\n });\n };\n\n /** Proves end to end that pairing is reachable and that everything else is not. */\n const runSelfTest = async (origin: string): Promise<TunnelStartResult> => {\n let pairResponse: ProbeResult | undefined;\n let healthResponse: ProbeResult | undefined;\n let cause = 'fetch failed';\n for (let attempt = 0; attempt < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted; attempt += 1) {\n try {\n pairResponse = await probe(`${origin}${PAIRING_PAGE_ROUTE}`, selfTestAbort.signal);\n healthResponse = await probe(`${origin}/api/health`, selfTestAbort.signal);\n if (pairResponse.status !== EDGE_TUNNEL_UNAVAILABLE && healthResponse.status !== EDGE_TUNNEL_UNAVAILABLE)\n break;\n cause = `Cloudflare edge answered ${String(healthResponse.status)} before the connector registered`;\n } catch (error) {\n cause = error instanceof Error ? error.message : String(error);\n }\n if (attempt + 1 < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted) await waitToRetry();\n }\n if (pairResponse === undefined || healthResponse === undefined) {\n return { ok: false, failure: 'self_test_failed', message: `The tunnel did not answer: ${cause}` };\n }\n if (healthResponse.status !== UNAUTHORIZED) {\n // The agent would be on the public internet unauthenticated. This is\n // the one failure worth an automatic abort rather than a warning.\n const message = `${describeTunnelFailure('self_test_failed')} /api/health answered ${String(healthResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n if (pairResponse.status !== OK || !pairResponse.body.includes(PAIRING_PAGE_MARKER)) {\n const message = `The pairing page answered ${String(pairResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n notice(`tunnel ready at ${origin}`);\n return { ok: true, publicOrigin: origin, stop };\n };\n\n const consider = (chunk: string): void => {\n lines.push(chunk);\n if (settle === undefined || selfTestStarted) return;\n seenUrl ??= extractTunnelUrl(chunk);\n seenConnection ||= mentionsRegisteredConnection(chunk);\n if (seenUrl === undefined || !seenConnection) return;\n selfTestStarted = true;\n input.acceptOrigin?.(seenUrl);\n void runSelfTest(seenUrl).then((result) => settle?.(result));\n };\n\n child.stdout?.on('data', (data: Buffer) => consider(data.toString()));\n child.stderr?.on('data', (data: Buffer) => consider(data.toString()));\n\n return await new Promise<TunnelStartResult>((resolve) => {\n let deadline: ReturnType<typeof setTimeout>;\n const finish = (result: TunnelStartResult, stopProcess = !result.ok): void => {\n if (settle === undefined) return;\n settle = undefined;\n clearTimeout(deadline);\n input.signal?.removeEventListener('abort', cancel);\n selfTestAbort.abort();\n if (stopProcess) {\n void stop().then(() => resolve(result));\n } else {\n resolve(result);\n }\n };\n const cancel = (): void => {\n finish({ ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' });\n };\n settle = (result) => finish(result);\n deadline = setTimeout(() => {\n finish({\n ok: false,\n failure: 'timeout',\n message: `${describeTunnelFailure('timeout')}\\n${tailOf(lines)}`,\n });\n }, startTimeoutMs);\n input.signal?.addEventListener('abort', cancel, { once: true });\n\n child.once('error', (error) => {\n finish({\n ok: false,\n failure: 'spawn_failed',\n message: `${describeTunnelFailure('spawn_failed')} ${error.message}`,\n });\n });\n\n child.once('exit', async (code) => {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(runtimeTokenPath, { force: true }).catch((error: unknown) => {\n notice(`Tunnel token cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n });\n const message = `${describeTunnelFailure('exited')} (code ${String(code)})\\n${tailOf(lines)}`;\n if (settle !== undefined) {\n finish({ ok: false, failure: 'exited', message }, false);\n return;\n }\n // It came up and then died: network loss, an edge rejection, or a\n // self-update. The supervisor either replaces a named connector or tells\n // the host to fail closed rather than leave an unreachable cockpit enabled.\n if (!stopped) options.onExit?.(message);\n });\n\n // A named tunnel already knows its hostname, but it still waits for\n // cloudflared to register an edge connection before probing the public URL.\n if (input.signal?.aborted === true) cancel();\n });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["path","fs","readFileSync","spawn","describeTunnelFailure","tunnelArgs","PAIRING_PAGE_ROUTE","extractTunnelUrl","mentionsRegisteredConnection"],"sources":["../../../../src/services/tunnelProcess/index.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { readFileSync, rmSync } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PAIRING_PAGE_ROUTE } from '../../constants/remote';\nimport {\n type TunnelConfig,\n type TunnelLauncher,\n type TunnelStartInput,\n type TunnelStartResult,\n} from '../../types/remote';\nimport { PAIRING_PAGE_MARKER } from '../pairingPage';\nimport { describeTunnelFailure, extractTunnelUrl, mentionsRegisteredConnection, tunnelArgs } from '../tunnelOutput';\n\n/** Explicit binary path, ahead of a PATH scan. */\nexport const CLOUDFLARED_ENV = 'PO_CLOUDFLARED';\n// Give new quick-host DNS and negative caches time to settle, within a bounded startup.\nconst START_TIMEOUT_MS = 120_000;\nconst TERMINATE_GRACE_MS = 2000;\nconst SELF_TEST_ATTEMPTS = 240;\nconst SELF_TEST_RETRY_MS = 500;\nconst NAMED_RESTART_ATTEMPTS = 3;\nconst NAMED_RESTART_BASE_DELAY_MS = 500;\nconst NAMED_RESTART_MAX_DELAY_MS = 4000;\nconst NAMED_RESTART_STABLE_MS = 60_000;\n/** Lines of process output kept for a failure report. */\nconst LOG_TAIL = 20;\nconst UNAUTHORIZED = 401;\n/** Cloudflare has the DNS route but no connector has registered yet. */\nconst EDGE_TUNNEL_UNAVAILABLE = 530;\nconst OK = 200;\nconst PID_FILE = 'tunnel.pid';\nconst TOKEN_FILE = 'tunnel.token';\nconst FILE_MODE = 0o600;\n/** A pid file older than this is stale beyond usefulness and the pid has likely been recycled. */\nconst PID_FILE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport interface ProbeResult {\n status: number;\n body: string;\n}\n\nexport interface TunnelProcessOptions {\n /** Explicit binary, ahead of the env var and the PATH scan. */\n cloudflaredPath?: string;\n stateDir: string;\n onNotice?: (message: string) => void;\n /** Test seam: stands in for node:child_process.spawn. */\n spawnProcess?: typeof spawn;\n /** Test seam: reaches the tunnel from outside, so the self-test needs no network. */\n probe?: (url: string, signal: AbortSignal) => Promise<ProbeResult>;\n /** Test seam: production waits briefly for a new public hostname to propagate. */\n selfTestRetryMs?: number;\n /** Test seam: production bounds the complete startup, including its self-test. */\n startTimeoutMs?: number;\n /** Test seam: production backs named-tunnel restarts off from 500 ms. */\n restartBaseDelayMs?: number;\n /** Test seam: production resets the rapid-crash budget after one stable minute. */\n restartStableMs?: number;\n /** Called after a quick tunnel exits, or after named-tunnel recovery is exhausted. */\n onExit?: (message: string) => void;\n}\n\n/**\n * Finds cloudflared without a shell.\n *\n * A manual scan rather than `which`: spawning a shell to locate a binary is a\n * quoting hazard for no benefit, and this also works the same on every platform.\n */\nexport async function findCloudflared(env: NodeJS.ProcessEnv = process.env): Promise<string | undefined> {\n const names = process.platform === 'win32' ? ['cloudflared.exe', 'cloudflared'] : ['cloudflared'];\n for (const directory of (env.PATH ?? '').split(path.delimiter)) {\n if (directory === '') continue;\n for (const name of names) {\n const candidate = path.join(directory, name);\n try {\n await fs.access(candidate, fs.constants.X_OK);\n return candidate;\n } catch {\n // Not here, or not executable; keep looking.\n }\n }\n }\n return undefined;\n}\n\nasync function defaultProbe(url: string, signal: AbortSignal): Promise<ProbeResult> {\n const response = await fetch(url, { redirect: 'manual', signal });\n return { status: response.status, body: await response.text() };\n}\n\n/**\n * Kills a tunnel left behind by a hub that could not clean up after itself.\n *\n * A SIGKILLed hub never runs its exit handler, and the child is not detached so\n * it is reparented rather than killed. It then holds the tunnel open against a\n * dead port. This is the sweep on the next start.\n *\n * The residual risk is pid reuse after a reboot. The age window narrows it and\n * on Linux the command line is checked; macOS has no equally cheap check, so\n * there the window stands.\n */\ninterface TunnelPidRecord {\n pid?: unknown;\n ownerPid?: unknown;\n startedAt?: unknown;\n}\n\nfunction validPid(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n}\n\n/** Only ESRCH proves the owner is gone; permission and platform errors fail safe. */\nfunction ownerMayBeAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== 'ESRCH';\n }\n}\n\n// sync: exported createRemoteRuntime must reap stale public tunnels before its synchronous startup returns.\nexport function reapStaleTunnel(stateDir: string, onNotice: (message: string) => void): void {\n const pidPath = path.join(stateDir, PID_FILE);\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let raw: string;\n try {\n raw = readFileSync(pidPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') rmSync(tokenPath, { force: true });\n return; // No pid file is the normal case; other read failures are unsafe to guess through.\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return; // A malformed record cannot prove that its owner is gone.\n }\n if (typeof parsed !== 'object' || parsed === null) return;\n const record = parsed as TunnelPidRecord;\n if (Object.hasOwn(record, 'ownerPid')) {\n if (!validPid(record.ownerPid) || ownerMayBeAlive(record.ownerPid)) return;\n }\n\n rmSync(tokenPath, { force: true });\n rmSync(pidPath, { force: true });\n const pid = validPid(record.pid) ? record.pid : undefined;\n const startedAt = typeof record.startedAt === 'number' ? record.startedAt : 0;\n if (pid === undefined || Date.now() - startedAt > PID_FILE_MAX_AGE_MS) return;\n if (process.platform === 'linux') {\n try {\n if (!readFileSync(`/proc/${String(pid)}/cmdline`, 'utf8').includes('cloudflared')) return;\n } catch {\n return; // The process is gone.\n }\n }\n try {\n process.kill(pid, 'SIGTERM');\n onNotice(`stopped a tunnel left running by a previous hub (pid ${String(pid)})`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH') onNotice(`Could not stop stale tunnel: ${String(error)}`);\n }\n}\n\nfunction tailOf(lines: readonly string[]): string {\n return lines.slice(-LOG_TAIL).join('').trim();\n}\n\n/** Copies a named tunnel token to a private runtime file understood by cloudflared. */\nasync function writeTokenFile(config: TunnelConfig, stateDir: string): Promise<string | undefined> {\n if (config.kind !== 'named' || config.tokenFile === undefined) return undefined;\n let token: string;\n let sourcePath: string;\n try {\n sourcePath = await fs.realpath(config.tokenFile);\n token = (await fs.readFile(sourcePath, 'utf8')).trim();\n } catch {\n return undefined;\n }\n if (token === '') return undefined;\n await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let existingTarget: string | undefined;\n try {\n existingTarget = await fs.realpath(tokenPath);\n } catch (error) {\n // Missing is the normal state before launch; other failures must not hide an alias.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n if (sourcePath === path.resolve(tokenPath) || sourcePath === existingTarget) {\n throw new Error('The configured tunnel token file cannot also be Po runtime token storage.');\n }\n await fs.rm(tokenPath, { force: true });\n await fs.writeFile(tokenPath, token, { mode: FILE_MODE });\n await fs.chmod(tokenPath, FILE_MODE);\n return tokenPath;\n}\n\nexport function createTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const restartBaseDelayMs = options.restartBaseDelayMs ?? NAMED_RESTART_BASE_DELAY_MS;\n const restartStableMs = options.restartStableMs ?? NAMED_RESTART_STABLE_MS;\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n let stopped = false;\n let activeStop: (() => Promise<void>) | undefined;\n let stopPromise: Promise<void> | undefined;\n let recoveryPromise: Promise<void> | undefined;\n let pendingExit: string | undefined;\n let restartCount = 0;\n let lastReadyAt = 0;\n const recoveryAbort = new AbortController();\n const signal =\n input.signal === undefined ? recoveryAbort.signal : AbortSignal.any([input.signal, recoveryAbort.signal]);\n const supervisedInput: TunnelStartInput = { ...input, signal };\n\n function queueUnexpectedExit(message: string): void {\n if (stopped) return;\n if (input.config.kind === 'quick') {\n options.onExit?.(message);\n return;\n }\n if (recoveryPromise !== undefined) {\n pendingExit = message;\n return;\n }\n beginRecovery(message);\n }\n\n const single = createSingleTunnelLauncher({ ...options, onExit: queueUnexpectedExit });\n\n async function waitForRestart(attempt: number): Promise<void> {\n if (recoveryAbort.signal.aborted) return;\n const delayMs = Math.min(restartBaseDelayMs * 2 ** (attempt - 1), NAMED_RESTART_MAX_DELAY_MS);\n await new Promise<void>((resolve) => {\n let timer: ReturnType<typeof setTimeout>;\n const done = (): void => {\n clearTimeout(timer);\n recoveryAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n timer = setTimeout(done, delayMs);\n recoveryAbort.signal.addEventListener('abort', done, { once: true });\n if (recoveryAbort.signal.aborted) done();\n });\n }\n\n async function recover(initialMessage: string): Promise<void> {\n if (Date.now() - lastReadyAt >= restartStableMs) restartCount = 0;\n let lastFailure = initialMessage;\n while (!stopped && restartCount < NAMED_RESTART_ATTEMPTS) {\n restartCount += 1;\n notice(\n `cloudflared stopped; restarting named tunnel (attempt ${String(restartCount)} of ${String(NAMED_RESTART_ATTEMPTS)})`,\n );\n await waitForRestart(restartCount);\n if (stopped) return;\n\n const outcome = await single(supervisedInput);\n if (stopped) {\n if (outcome.ok) await outcome.stop().catch(() => undefined);\n return;\n }\n if (!outcome.ok) {\n lastFailure = outcome.message;\n continue;\n }\n activeStop = outcome.stop;\n lastReadyAt = Date.now();\n notice(`named tunnel recovered at ${outcome.publicOrigin}`);\n return;\n }\n\n if (!stopped) {\n options.onExit?.(\n `${initialMessage}\\nNamed tunnel recovery failed after ${String(NAMED_RESTART_ATTEMPTS)} attempts. Last failure: ${lastFailure}`,\n );\n }\n }\n\n function beginRecovery(message: string): void {\n recoveryPromise = recover(message)\n .catch((error: unknown) => {\n if (!stopped) {\n const cause = error instanceof Error ? error.message : String(error);\n options.onExit?.(`${message}\\nNamed tunnel recovery failed: ${cause}`);\n }\n })\n .finally(() => {\n recoveryPromise = undefined;\n const queued = pendingExit;\n pendingExit = undefined;\n if (queued !== undefined && !stopped) beginRecovery(queued);\n });\n }\n\n const initial = await single(supervisedInput);\n if (!initial.ok) {\n stopped = true;\n recoveryAbort.abort();\n return initial;\n }\n activeStop = initial.stop;\n lastReadyAt = Date.now();\n\n const stop = async (): Promise<void> => {\n if (stopPromise !== undefined) return await stopPromise;\n stopped = true;\n recoveryAbort.abort();\n const recovering = recoveryPromise;\n stopPromise = (async () => {\n await activeStop?.().catch(() => undefined);\n await recovering?.catch(() => undefined);\n await fs.rm(path.join(options.stateDir, PID_FILE), { force: true });\n await fs.rm(path.join(options.stateDir, TOKEN_FILE), { force: true });\n })();\n return await stopPromise;\n };\n\n return { ok: true, publicOrigin: initial.publicOrigin, stop };\n };\n}\n\n/** Starts one cloudflared child; the exported launcher supervises successive named-tunnel children. */\nfunction createSingleTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const spawnProcess = options.spawnProcess ?? spawn;\n const probe = options.probe ?? defaultProbe;\n const selfTestRetryMs = options.selfTestRetryMs ?? SELF_TEST_RETRY_MS;\n const startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;\n const pidPath = path.join(options.stateDir, PID_FILE);\n const runtimeTokenPath = path.join(options.stateDir, TOKEN_FILE);\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n if (input.signal?.aborted === true) {\n return { ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' };\n }\n const binary = options.cloudflaredPath ?? process.env[CLOUDFLARED_ENV] ?? (await findCloudflared());\n if (binary === undefined || binary === '') {\n return { ok: false, failure: 'not_installed', message: describeTunnelFailure('not_installed') };\n }\n\n let tokenPath: string | undefined;\n try {\n tokenPath = await writeTokenFile(input.config, options.stateDir);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n const args = tunnelArgs(input.config, input.port, tokenPath);\n let child: ChildProcess;\n try {\n // Never detached and never unref'd. A detached child becomes a process\n // group leader that survives the terminal's SIGINT, which is exactly the\n // orphaned public tunnel this must avoid. Contrast serverSpawner.ts,\n // which detaches on purpose because sessions outlive the hub.\n child = spawnProcess(binary, args, { detached: false, stdio: ['ignore', 'pipe', 'pipe'] });\n } catch (error) {\n await fs.rm(runtimeTokenPath, { force: true });\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n\n const lines: string[] = [];\n let stopped = false;\n let settle: ((result: TunnelStartResult) => void) | undefined;\n let selfTestStarted = false;\n const selfTestAbort = new AbortController();\n\n const killTree = (): void => {\n try {\n child.kill('SIGTERM');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH') notice(`Could not stop tunnel: ${String(error)}`);\n }\n };\n const onProcessExit = (): void => {\n // sync: Node does not await filesystem cleanup in the process exit handler.\n // This is the net under process.exit(), an uncaught exception, and an\n // unhandled rejection, none of which reach the graceful close path.\n rmSync(runtimeTokenPath, { force: true });\n try {\n if (child.pid !== undefined) process.kill(child.pid, 'SIGKILL');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH')\n notice(`Could not kill tunnel at exit: ${String(error)}`);\n }\n };\n process.once('exit', onProcessExit);\n\n const stop = async (): Promise<void> => {\n if (stopped) return;\n stopped = true;\n if (child.exitCode !== null || child.signalCode !== null) {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n return;\n }\n await new Promise<void>((resolve) => {\n const escalate = setTimeout(() => {\n try {\n child.kill('SIGKILL');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH') notice(`Could not kill tunnel: ${String(error)}`);\n }\n }, TERMINATE_GRACE_MS);\n child.once('exit', () => {\n clearTimeout(escalate);\n resolve();\n });\n killTree();\n });\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n };\n\n // Register child listeners below without yielding, so early exits cannot be missed.\n // Cleanup waits for this ordered write before removing the record.\n const pidWrite = fs\n .mkdir(options.stateDir, { recursive: true, mode: 0o700 })\n .then(() =>\n fs.writeFile(\n pidPath,\n JSON.stringify({ pid: child.pid, ownerPid: process.pid, startedAt: Date.now(), command: binary }),\n { mode: FILE_MODE },\n ),\n )\n .catch((error: unknown) => {\n // The reaper is a safety net; report unavailable recovery without blocking startup.\n notice(`Could not write tunnel process record: ${String(error)}`);\n });\n\n const configuredOrigin = input.config.kind === 'named' ? `https://${input.config.hostname}` : undefined;\n let seenUrl = configuredOrigin;\n let seenConnection = false;\n\n const waitToRetry = async (): Promise<void> => {\n if (selfTestRetryMs <= 0) return;\n await new Promise<void>((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n selfTestAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, selfTestRetryMs);\n selfTestAbort.signal.addEventListener('abort', done, { once: true });\n });\n };\n\n /** Proves end to end that pairing is reachable and that everything else is not. */\n const runSelfTest = async (origin: string): Promise<TunnelStartResult> => {\n let pairResponse: ProbeResult | undefined;\n let healthResponse: ProbeResult | undefined;\n let cause = 'fetch failed';\n for (let attempt = 0; attempt < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted; attempt += 1) {\n try {\n pairResponse = await probe(`${origin}${PAIRING_PAGE_ROUTE}`, selfTestAbort.signal);\n healthResponse = await probe(`${origin}/api/health`, selfTestAbort.signal);\n if (pairResponse.status !== EDGE_TUNNEL_UNAVAILABLE && healthResponse.status !== EDGE_TUNNEL_UNAVAILABLE)\n break;\n cause = `Cloudflare edge answered ${String(healthResponse.status)} before the connector registered`;\n } catch (error) {\n cause = error instanceof Error ? error.message : String(error);\n }\n if (attempt + 1 < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted) await waitToRetry();\n }\n if (pairResponse === undefined || healthResponse === undefined) {\n return { ok: false, failure: 'self_test_failed', message: `The tunnel did not answer: ${cause}` };\n }\n if (healthResponse.status !== UNAUTHORIZED) {\n // The agent would be on the public internet unauthenticated. This is\n // the one failure worth an automatic abort rather than a warning.\n const message = `${describeTunnelFailure('self_test_failed')} /api/health answered ${String(healthResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n if (pairResponse.status !== OK || !pairResponse.body.includes(PAIRING_PAGE_MARKER)) {\n const message = `The pairing page answered ${String(pairResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n notice(`tunnel ready at ${origin}`);\n return { ok: true, publicOrigin: origin, stop };\n };\n\n const consider = (chunk: string): void => {\n lines.push(chunk);\n if (settle === undefined || selfTestStarted) return;\n seenUrl ??= extractTunnelUrl(chunk);\n seenConnection ||= mentionsRegisteredConnection(chunk);\n if (seenUrl === undefined || !seenConnection) return;\n selfTestStarted = true;\n input.acceptOrigin?.(seenUrl);\n void runSelfTest(seenUrl).then((result) => settle?.(result));\n };\n\n child.stdout?.on('data', (data: Buffer) => consider(data.toString()));\n child.stderr?.on('data', (data: Buffer) => consider(data.toString()));\n\n return await new Promise<TunnelStartResult>((resolve) => {\n let deadline: ReturnType<typeof setTimeout>;\n const finish = (result: TunnelStartResult, stopProcess = !result.ok): void => {\n if (settle === undefined) return;\n settle = undefined;\n clearTimeout(deadline);\n input.signal?.removeEventListener('abort', cancel);\n selfTestAbort.abort();\n if (stopProcess) {\n void stop().then(() => resolve(result));\n } else {\n resolve(result);\n }\n };\n const cancel = (): void => {\n finish({ ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' });\n };\n settle = (result) => finish(result);\n deadline = setTimeout(() => {\n finish({\n ok: false,\n failure: 'timeout',\n message: `${describeTunnelFailure('timeout')}\\n${tailOf(lines)}`,\n });\n }, startTimeoutMs);\n input.signal?.addEventListener('abort', cancel, { once: true });\n\n child.once('error', (error) => {\n finish({\n ok: false,\n failure: 'spawn_failed',\n message: `${describeTunnelFailure('spawn_failed')} ${error.message}`,\n });\n });\n\n child.once('exit', async (code) => {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(runtimeTokenPath, { force: true }).catch((error: unknown) => {\n notice(`Tunnel token cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n });\n const message = `${describeTunnelFailure('exited')} (code ${String(code)})\\n${tailOf(lines)}`;\n if (settle !== undefined) {\n finish({ ok: false, failure: 'exited', message }, false);\n return;\n }\n // It came up and then died: network loss, an edge rejection, or a\n // self-update. The supervisor either replaces a named connector or tells\n // the host to fail closed rather than leave an unreachable cockpit enabled.\n if (!stopped) options.onExit?.(message);\n });\n\n // A named tunnel already knows its hostname, but it still waits for\n // cloudflared to register an edge connection before probing the public URL.\n if (input.signal?.aborted === true) cancel();\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;AAgBA,MAAa,kBAAkB;AAE/B,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,yBAAyB;AAC/B,MAAM,8BAA8B;AACpC,MAAM,6BAA6B;AACnC,MAAM,0BAA0B;AAGhC,MAAM,eAAe;;AAErB,MAAM,0BAA0B;AAChC,MAAM,KAAK;AACX,MAAM,WAAW;AACjB,MAAM,aAAa;AACnB,MAAM,YAAY;;AAElB,MAAM,sBAAsB;;;;;;;AAkC5B,eAAsB,gBAAgB,MAAyB,QAAQ,KAAkC;CACvG,MAAM,QAAQ,QAAQ,aAAa,UAAU,CAAC,mBAAmB,aAAa,IAAI,CAAC,aAAa;CAChG,KAAK,MAAM,cAAc,IAAI,QAAQ,GAAA,CAAI,MAAMA,UAAAA,QAAK,SAAS,GAAG;EAC9D,IAAI,cAAc,IAAI;EACtB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,YAAYA,UAAAA,QAAK,KAAK,WAAW,IAAI;GAC3C,IAAI;IACF,MAAMC,iBAAAA,QAAG,OAAO,WAAWA,iBAAAA,QAAG,UAAU,IAAI;IAC5C,OAAO;GACT,QAAQ,CAER;EACF;CACF;AAEF;AAEA,eAAe,aAAa,KAAa,QAA2C;CAClF,MAAM,WAAW,MAAM,MAAM,KAAK;EAAE,UAAU;EAAU;CAAO,CAAC;CAChE,OAAO;EAAE,QAAQ,SAAS;EAAQ,MAAM,MAAM,SAAS,KAAK;CAAE;AAChE;AAmBA,SAAS,SAAS,OAAiC;CACjD,OAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,QAAQ;AAC7E;;AAGA,SAAS,gBAAgB,KAAsB;CAC7C,IAAI;EACF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,SAAS,OAAO;EACd,OAAQ,MAAgC,SAAS;CACnD;AACF;AAGA,SAAgB,gBAAgB,UAAkB,UAA2C;CAC3F,MAAM,UAAUD,UAAAA,QAAK,KAAK,UAAU,QAAQ;CAC5C,MAAM,YAAYA,UAAAA,QAAK,KAAK,UAAU,UAAU;CAChD,IAAI;CACJ,IAAI;EACF,OAAA,GAAME,QAAAA,aAAAA,CAAa,SAAS,MAAM;CACpC,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,CAAA,GAAA,QAAA,OAAA,CAAO,WAAW,EAAE,OAAO,KAAK,CAAC;EACzF;CACF;CACA,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN;CACF;CACA,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;CACnD,MAAM,SAAS;CACf,IAAI,OAAO,OAAO,QAAQ,UAAU,GAC9B;MAAA,CAAC,SAAS,OAAO,QAAQ,KAAK,gBAAgB,OAAO,QAAQ,GAAG;CAAA;CAGtE,CAAA,GAAA,QAAA,OAAA,CAAO,WAAW,EAAE,OAAO,KAAK,CAAC;CACjC,CAAA,GAAA,QAAA,OAAA,CAAO,SAAS,EAAE,OAAO,KAAK,CAAC;CAC/B,MAAM,MAAM,SAAS,OAAO,GAAG,IAAI,OAAO,MAAM,KAAA;CAChD,MAAM,YAAY,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;CAC5E,IAAI,QAAQ,KAAA,KAAa,KAAK,IAAI,IAAI,YAAY,qBAAqB;CACvE,IAAI,QAAQ,aAAa,SACvB,IAAI;EACF,IAAI,EAAA,GAACA,QAAAA,aAAAA,CAAa,SAAS,OAAO,GAAG,EAAE,WAAW,MAAM,CAAC,CAAC,SAAS,aAAa,GAAG;CACrF,QAAQ;EACN;CACF;CAEF,IAAI;EACF,QAAQ,KAAK,KAAK,SAAS;EAC3B,SAAS,wDAAwD,OAAO,GAAG,EAAE,EAAE;CACjF,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,SAAS,SAAS,gCAAgC,OAAO,KAAK,GAAG;CACjH;AACF;AAEA,SAAS,OAAO,OAAkC;CAChD,OAAO,MAAM,MAAM,GAAS,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK;AAC9C;;AAGA,eAAe,eAAe,QAAsB,UAA+C;CACjG,IAAI,OAAO,SAAS,WAAW,OAAO,cAAc,KAAA,GAAW,OAAO,KAAA;CACtE,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,aAAa,MAAMD,iBAAAA,QAAG,SAAS,OAAO,SAAS;EAC/C,SAAS,MAAMA,iBAAAA,QAAG,SAAS,YAAY,MAAM,EAAA,CAAG,KAAK;CACvD,QAAQ;EACN;CACF;CACA,IAAI,UAAU,IAAI,OAAO,KAAA;CACzB,MAAMA,iBAAAA,QAAG,MAAM,UAAU;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACzD,MAAM,YAAYD,UAAAA,QAAK,KAAK,UAAU,UAAU;CAChD,IAAI;CACJ,IAAI;EACF,iBAAiB,MAAMC,iBAAAA,QAAG,SAAS,SAAS;CAC9C,SAAS,OAAO;EAEd,IAAK,MAAgC,SAAS,UAAU,MAAM;CAChE;CACA,IAAI,eAAeD,UAAAA,QAAK,QAAQ,SAAS,KAAK,eAAe,gBAC3D,MAAM,IAAI,MAAM,2EAA2E;CAE7F,MAAMC,iBAAAA,QAAG,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;CACtC,MAAMA,iBAAAA,QAAG,UAAU,WAAW,OAAO,EAAE,MAAM,UAAU,CAAC;CACxD,MAAMA,iBAAAA,QAAG,MAAM,WAAW,SAAS;CACnC,OAAO;AACT;AAEA,SAAgB,qBAAqB,SAA+C;CAClF,MAAM,SAAS,QAAQ,mBAAyB,CAAC;CACjD,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,MAAM,kBAAkB,QAAQ,mBAAmB;CAEnD,OAAO,eAAe,OAAO,OAAqD;EAChF,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI,eAAe;EACnB,IAAI,cAAc;EAClB,MAAM,gBAAgB,IAAI,gBAAgB;EAC1C,MAAM,SACJ,MAAM,WAAW,KAAA,IAAY,cAAc,SAAS,YAAY,IAAI,CAAC,MAAM,QAAQ,cAAc,MAAM,CAAC;EAC1G,MAAM,kBAAoC;GAAE,GAAG;GAAO;EAAO;EAE7D,SAAS,oBAAoB,SAAuB;GAClD,IAAI,SAAS;GACb,IAAI,MAAM,OAAO,SAAS,SAAS;IACjC,QAAQ,SAAS,OAAO;IACxB;GACF;GACA,IAAI,oBAAoB,KAAA,GAAW;IACjC,cAAc;IACd;GACF;GACA,cAAc,OAAO;EACvB;EAEA,MAAM,SAAS,2BAA2B;GAAE,GAAG;GAAS,QAAQ;EAAoB,CAAC;EAErF,eAAe,eAAe,SAAgC;GAC5D,IAAI,cAAc,OAAO,SAAS;GAClC,MAAM,UAAU,KAAK,IAAI,qBAAqB,MAAM,UAAU,IAAI,0BAA0B;GAC5F,MAAM,IAAI,SAAe,YAAY;IACnC,IAAI;IACJ,MAAM,aAAmB;KACvB,aAAa,KAAK;KAClB,cAAc,OAAO,oBAAoB,SAAS,IAAI;KACtD,QAAQ;IACV;IACA,QAAQ,WAAW,MAAM,OAAO;IAChC,cAAc,OAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;IACnE,IAAI,cAAc,OAAO,SAAS,KAAK;GACzC,CAAC;EACH;EAEA,eAAe,QAAQ,gBAAuC;GAC5D,IAAI,KAAK,IAAI,IAAI,eAAe,iBAAiB,eAAe;GAChE,IAAI,cAAc;GAClB,OAAO,CAAC,WAAW,eAAe,wBAAwB;IACxD,gBAAgB;IAChB,OACE,yDAAyD,OAAO,YAAY,EAAE,MAAM,OAAO,sBAAsB,EAAE,EACrH;IACA,MAAM,eAAe,YAAY;IACjC,IAAI,SAAS;IAEb,MAAM,UAAU,MAAM,OAAO,eAAe;IAC5C,IAAI,SAAS;KACX,IAAI,QAAQ,IAAI,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;KAC1D;IACF;IACA,IAAI,CAAC,QAAQ,IAAI;KACf,cAAc,QAAQ;KACtB;IACF;IACA,aAAa,QAAQ;IACrB,cAAc,KAAK,IAAI;IACvB,OAAO,6BAA6B,QAAQ,cAAc;IAC1D;GACF;GAEA,IAAI,CAAC,SACH,QAAQ,SACN,GAAG,eAAe,uCAAuC,OAAO,sBAAsB,EAAE,2BAA2B,aACrH;EAEJ;EAEA,SAAS,cAAc,SAAuB;GAC5C,kBAAkB,QAAQ,OAAO,CAAC,CAC/B,OAAO,UAAmB;IACzB,IAAI,CAAC,SAAS;KACZ,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACnE,QAAQ,SAAS,GAAG,QAAQ,kCAAkC,OAAO;IACvE;GACF,CAAC,CAAC,CACD,cAAc;IACb,kBAAkB,KAAA;IAClB,MAAM,SAAS;IACf,cAAc,KAAA;IACd,IAAI,WAAW,KAAA,KAAa,CAAC,SAAS,cAAc,MAAM;GAC5D,CAAC;EACL;EAEA,MAAM,UAAU,MAAM,OAAO,eAAe;EAC5C,IAAI,CAAC,QAAQ,IAAI;GACf,UAAU;GACV,cAAc,MAAM;GACpB,OAAO;EACT;EACA,aAAa,QAAQ;EACrB,cAAc,KAAK,IAAI;EAEvB,MAAM,OAAO,YAA2B;GACtC,IAAI,gBAAgB,KAAA,GAAW,OAAO,MAAM;GAC5C,UAAU;GACV,cAAc,MAAM;GACpB,MAAM,aAAa;GACnB,eAAe,YAAY;IACzB,MAAM,aAAa,CAAC,CAAC,YAAY,KAAA,CAAS;IAC1C,MAAM,YAAY,YAAY,KAAA,CAAS;IACvC,MAAMA,iBAAAA,QAAG,GAAGD,UAAAA,QAAK,KAAK,QAAQ,UAAU,QAAQ,GAAG,EAAE,OAAO,KAAK,CAAC;IAClE,MAAMC,iBAAAA,QAAG,GAAGD,UAAAA,QAAK,KAAK,QAAQ,UAAU,UAAU,GAAG,EAAE,OAAO,KAAK,CAAC;GACtE,EAAA,CAAG;GACH,OAAO,MAAM;EACf;EAEA,OAAO;GAAE,IAAI;GAAM,cAAc,QAAQ;GAAc;EAAK;CAC9D;AACF;;AAGA,SAAS,2BAA2B,SAA+C;CACjF,MAAM,SAAS,QAAQ,mBAAyB,CAAC;CACjD,MAAM,eAAe,QAAQ,gBAAgBG,mBAAAA;CAC7C,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,UAAUH,UAAAA,QAAK,KAAK,QAAQ,UAAU,QAAQ;CACpD,MAAM,mBAAmBA,UAAAA,QAAK,KAAK,QAAQ,UAAU,UAAU;CAE/D,OAAO,eAAe,OAAO,OAAqD;EAChF,IAAI,MAAM,QAAQ,YAAY,MAC5B,OAAO;GAAE,IAAI;GAAO,SAAS;GAAU,SAAS;EAAgC;EAElF,MAAM,SAAS,QAAQ,mBAAmB,QAAQ,IAAA,qBAAyB,MAAM,gBAAgB;EACjG,IAAI,WAAW,KAAA,KAAa,WAAW,IACrC,OAAO;GAAE,IAAI;GAAO,SAAS;GAAiB,SAASI,gBAAAA,sBAAsB,eAAe;EAAE;EAGhG,IAAI;EACJ,IAAI;GACF,YAAY,MAAM,eAAe,MAAM,QAAQ,QAAQ,QAAQ;EACjE,SAAS,OAAO;GACd,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnE,OAAO;IAAE,IAAI;IAAO,SAAS;IAAgB,SAAS,GAAGA,gBAAAA,sBAAsB,cAAc,EAAE,GAAG;GAAQ;EAC5G;EACA,MAAM,OAAOC,gBAAAA,WAAW,MAAM,QAAQ,MAAM,MAAM,SAAS;EAC3D,IAAI;EACJ,IAAI;GAKF,QAAQ,aAAa,QAAQ,MAAM;IAAE,UAAU;IAAO,OAAO;KAAC;KAAU;KAAQ;IAAM;GAAE,CAAC;EAC3F,SAAS,OAAO;GACd,MAAMJ,iBAAAA,QAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC;GAC7C,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnE,OAAO;IAAE,IAAI;IAAO,SAAS;IAAgB,SAAS,GAAGG,gBAAAA,sBAAsB,cAAc,EAAE,GAAG;GAAQ;EAC5G;EAEA,MAAM,QAAkB,CAAC;EACzB,IAAI,UAAU;EACd,IAAI;EACJ,IAAI,kBAAkB;EACtB,MAAM,gBAAgB,IAAI,gBAAgB;EAE1C,MAAM,iBAAuB;GAC3B,IAAI;IACF,MAAM,KAAK,SAAS;GACtB,SAAS,OAAO;IACd,IAAK,MAAgC,SAAS,SAAS,OAAO,0BAA0B,OAAO,KAAK,GAAG;GACzG;EACF;EACA,MAAM,sBAA4B;GAIhC,CAAA,GAAA,QAAA,OAAA,CAAO,kBAAkB,EAAE,OAAO,KAAK,CAAC;GACxC,IAAI;IACF,IAAI,MAAM,QAAQ,KAAA,GAAW,QAAQ,KAAK,MAAM,KAAK,SAAS;GAChE,SAAS,OAAO;IACd,IAAK,MAAgC,SAAS,SAC5C,OAAO,kCAAkC,OAAO,KAAK,GAAG;GAC5D;EACF;EACA,QAAQ,KAAK,QAAQ,aAAa;EAElC,MAAM,OAAO,YAA2B;GACtC,IAAI,SAAS;GACb,UAAU;GACV,IAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM;IACxD,QAAQ,IAAI,QAAQ,aAAa;IACjC,MAAM;IACN,MAAMH,iBAAAA,QAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC;IACpC,MAAMA,iBAAAA,QAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC;IAC7C;GACF;GACA,MAAM,IAAI,SAAe,YAAY;IACnC,MAAM,WAAW,iBAAiB;KAChC,IAAI;MACF,MAAM,KAAK,SAAS;KACtB,SAAS,OAAO;MACd,IAAK,MAAgC,SAAS,SAAS,OAAO,0BAA0B,OAAO,KAAK,GAAG;KACzG;IACF,GAAG,kBAAkB;IACrB,MAAM,KAAK,cAAc;KACvB,aAAa,QAAQ;KACrB,QAAQ;IACV,CAAC;IACD,SAAS;GACX,CAAC;GACD,QAAQ,IAAI,QAAQ,aAAa;GACjC,MAAM;GACN,MAAMA,iBAAAA,QAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC;GACpC,MAAMA,iBAAAA,QAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC;EAC/C;EAIA,MAAM,WAAWA,iBAAAA,QACd,MAAM,QAAQ,UAAU;GAAE,WAAW;GAAM,MAAM;EAAM,CAAC,CAAC,CACzD,WACCA,iBAAAA,QAAG,UACD,SACA,KAAK,UAAU;GAAE,KAAK,MAAM;GAAK,UAAU,QAAQ;GAAK,WAAW,KAAK,IAAI;GAAG,SAAS;EAAO,CAAC,GAChG,EAAE,MAAM,UAAU,CACpB,CACF,CAAC,CACA,OAAO,UAAmB;GAEzB,OAAO,0CAA0C,OAAO,KAAK,GAAG;EAClE,CAAC;EAGH,IAAI,UADqB,MAAM,OAAO,SAAS,UAAU,WAAW,MAAM,OAAO,aAAa,KAAA;EAE9F,IAAI,iBAAiB;EAErB,MAAM,cAAc,YAA2B;GAC7C,IAAI,mBAAmB,GAAG;GAC1B,MAAM,IAAI,SAAe,YAAY;IACnC,MAAM,aAAmB;KACvB,aAAa,KAAK;KAClB,cAAc,OAAO,oBAAoB,SAAS,IAAI;KACtD,QAAQ;IACV;IACA,MAAM,QAAQ,WAAW,MAAM,eAAe;IAC9C,cAAc,OAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;GACrE,CAAC;EACH;;EAGA,MAAM,cAAc,OAAO,WAA+C;GACxE,IAAI;GACJ,IAAI;GACJ,IAAI,QAAQ;GACZ,KAAK,IAAI,UAAU,GAAG,UAAU,sBAAsB,CAAC,cAAc,OAAO,SAAS,WAAW,GAAG;IACjG,IAAI;KACF,eAAe,MAAM,MAAM,GAAG,SAASK,eAAAA,sBAAsB,cAAc,MAAM;KACjF,iBAAiB,MAAM,MAAM,GAAG,OAAO,cAAc,cAAc,MAAM;KACzE,IAAI,aAAa,WAAW,2BAA2B,eAAe,WAAW,yBAC/E;KACF,QAAQ,4BAA4B,OAAO,eAAe,MAAM,EAAE;IACpE,SAAS,OAAO;KACd,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC/D;IACA,IAAI,UAAU,IAAI,sBAAsB,CAAC,cAAc,OAAO,SAAS,MAAM,YAAY;GAC3F;GACA,IAAI,iBAAiB,KAAA,KAAa,mBAAmB,KAAA,GACnD,OAAO;IAAE,IAAI;IAAO,SAAS;IAAoB,SAAS,8BAA8B;GAAQ;GAElG,IAAI,eAAe,WAAW,cAAc;IAG1C,MAAM,UAAU,GAAGF,gBAAAA,sBAAsB,kBAAkB,EAAE,wBAAwB,OAAO,eAAe,MAAM,EAAE;IACnH,OAAO,OAAO;IACd,OAAO;KAAE,IAAI;KAAO,SAAS;KAAoB;IAAQ;GAC3D;GACA,IAAI,aAAa,WAAW,MAAM,CAAC,aAAa,KAAK,SAAA,iBAA4B,GAAG;IAClF,MAAM,UAAU,6BAA6B,OAAO,aAAa,MAAM,EAAE;IACzE,OAAO,OAAO;IACd,OAAO;KAAE,IAAI;KAAO,SAAS;KAAoB;IAAQ;GAC3D;GACA,OAAO,mBAAmB,QAAQ;GAClC,OAAO;IAAE,IAAI;IAAM,cAAc;IAAQ;GAAK;EAChD;EAEA,MAAM,YAAY,UAAwB;GACxC,MAAM,KAAK,KAAK;GAChB,IAAI,WAAW,KAAA,KAAa,iBAAiB;GAC7C,YAAYG,gBAAAA,iBAAiB,KAAK;GAClC,mBAAmBC,gBAAAA,6BAA6B,KAAK;GACrD,IAAI,YAAY,KAAA,KAAa,CAAC,gBAAgB;GAC9C,kBAAkB;GAClB,MAAM,eAAe,OAAO;GAC5B,YAAiB,OAAO,CAAC,CAAC,MAAM,WAAW,SAAS,MAAM,CAAC;EAC7D;EAEA,MAAM,QAAQ,GAAG,SAAS,SAAiB,SAAS,KAAK,SAAS,CAAC,CAAC;EACpE,MAAM,QAAQ,GAAG,SAAS,SAAiB,SAAS,KAAK,SAAS,CAAC,CAAC;EAEpE,OAAO,MAAM,IAAI,SAA4B,YAAY;GACvD,IAAI;GACJ,MAAM,UAAU,QAA2B,cAAc,CAAC,OAAO,OAAa;IAC5E,IAAI,WAAW,KAAA,GAAW;IAC1B,SAAS,KAAA;IACT,aAAa,QAAQ;IACrB,MAAM,QAAQ,oBAAoB,SAAS,MAAM;IACjD,cAAc,MAAM;IACpB,IAAI,aACF,KAAU,CAAC,CAAC,WAAW,QAAQ,MAAM,CAAC;SAEtC,QAAQ,MAAM;GAElB;GACA,MAAM,eAAqB;IACzB,OAAO;KAAE,IAAI;KAAO,SAAS;KAAU,SAAS;IAAgC,CAAC;GACnF;GACA,UAAU,WAAW,OAAO,MAAM;GAClC,WAAW,iBAAiB;IAC1B,OAAO;KACL,IAAI;KACJ,SAAS;KACT,SAAS,GAAGJ,gBAAAA,sBAAsB,SAAS,EAAE,IAAI,OAAO,KAAK;IAC/D,CAAC;GACH,GAAG,cAAc;GACjB,MAAM,QAAQ,iBAAiB,SAAS,QAAQ,EAAE,MAAM,KAAK,CAAC;GAE9D,MAAM,KAAK,UAAU,UAAU;IAC7B,OAAO;KACL,IAAI;KACJ,SAAS;KACT,SAAS,GAAGA,gBAAAA,sBAAsB,cAAc,EAAE,GAAG,MAAM;IAC7D,CAAC;GACH,CAAC;GAED,MAAM,KAAK,QAAQ,OAAO,SAAS;IACjC,QAAQ,IAAI,QAAQ,aAAa;IACjC,MAAM;IACN,MAAMH,iBAAAA,QAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;KACvE,OAAO,gCAAgC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;IACjG,CAAC;IACD,MAAM,UAAU,GAAGG,gBAAAA,sBAAsB,QAAQ,EAAE,SAAS,OAAO,IAAI,EAAE,KAAK,OAAO,KAAK;IAC1F,IAAI,WAAW,KAAA,GAAW;KACxB,OAAO;MAAE,IAAI;MAAO,SAAS;MAAU;KAAQ,GAAG,KAAK;KACvD;IACF;IAIA,IAAI,CAAC,SAAS,QAAQ,SAAS,OAAO;GACxC,CAAC;GAID,IAAI,MAAM,QAAQ,YAAY,MAAM,OAAO;EAC7C,CAAC;CACH;AACF"}
|
|
@@ -8,9 +8,9 @@ import { spawn } from "node:child_process";
|
|
|
8
8
|
//#region src/services/tunnelProcess/index.ts
|
|
9
9
|
/** Explicit binary path, ahead of a PATH scan. */
|
|
10
10
|
const CLOUDFLARED_ENV = "PO_CLOUDFLARED";
|
|
11
|
-
const START_TIMEOUT_MS =
|
|
11
|
+
const START_TIMEOUT_MS = 12e4;
|
|
12
12
|
const TERMINATE_GRACE_MS = 2e3;
|
|
13
|
-
const SELF_TEST_ATTEMPTS =
|
|
13
|
+
const SELF_TEST_ATTEMPTS = 240;
|
|
14
14
|
const SELF_TEST_RETRY_MS = 500;
|
|
15
15
|
const NAMED_RESTART_ATTEMPTS = 3;
|
|
16
16
|
const NAMED_RESTART_BASE_DELAY_MS = 500;
|
|
@@ -100,7 +100,9 @@ function reapStaleTunnel(stateDir, onNotice) {
|
|
|
100
100
|
try {
|
|
101
101
|
process.kill(pid, "SIGTERM");
|
|
102
102
|
onNotice(`stopped a tunnel left running by a previous hub (pid ${String(pid)})`);
|
|
103
|
-
} catch {
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.code !== "ESRCH") onNotice(`Could not stop stale tunnel: ${String(error)}`);
|
|
105
|
+
}
|
|
104
106
|
}
|
|
105
107
|
function tailOf(lines) {
|
|
106
108
|
return lines.slice(-20).join("").trim();
|
|
@@ -125,7 +127,9 @@ async function writeTokenFile(config, stateDir) {
|
|
|
125
127
|
let existingTarget;
|
|
126
128
|
try {
|
|
127
129
|
existingTarget = await fs.realpath(tokenPath);
|
|
128
|
-
} catch {
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error.code !== "ENOENT") throw error;
|
|
132
|
+
}
|
|
129
133
|
if (sourcePath === path.resolve(tokenPath) || sourcePath === existingTarget) throw new Error("The configured tunnel token file cannot also be Po runtime token storage.");
|
|
130
134
|
await fs.rm(tokenPath, { force: true });
|
|
131
135
|
await fs.writeFile(tokenPath, token, { mode: FILE_MODE });
|
|
@@ -306,13 +310,17 @@ function createSingleTunnelLauncher(options) {
|
|
|
306
310
|
const killTree = () => {
|
|
307
311
|
try {
|
|
308
312
|
child.kill("SIGTERM");
|
|
309
|
-
} catch {
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error.code !== "ESRCH") notice(`Could not stop tunnel: ${String(error)}`);
|
|
315
|
+
}
|
|
310
316
|
};
|
|
311
317
|
const onProcessExit = () => {
|
|
312
318
|
rmSync(runtimeTokenPath, { force: true });
|
|
313
319
|
try {
|
|
314
320
|
if (child.pid !== void 0) process.kill(child.pid, "SIGKILL");
|
|
315
|
-
} catch {
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (error.code !== "ESRCH") notice(`Could not kill tunnel at exit: ${String(error)}`);
|
|
323
|
+
}
|
|
316
324
|
};
|
|
317
325
|
process.once("exit", onProcessExit);
|
|
318
326
|
const stop = async () => {
|
|
@@ -329,7 +337,9 @@ function createSingleTunnelLauncher(options) {
|
|
|
329
337
|
const escalate = setTimeout(() => {
|
|
330
338
|
try {
|
|
331
339
|
child.kill("SIGKILL");
|
|
332
|
-
} catch {
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (error.code !== "ESRCH") notice(`Could not kill tunnel: ${String(error)}`);
|
|
342
|
+
}
|
|
333
343
|
}, TERMINATE_GRACE_MS);
|
|
334
344
|
child.once("exit", () => {
|
|
335
345
|
clearTimeout(escalate);
|
|
@@ -345,16 +355,14 @@ function createSingleTunnelLauncher(options) {
|
|
|
345
355
|
const pidWrite = fs.mkdir(options.stateDir, {
|
|
346
356
|
recursive: true,
|
|
347
357
|
mode: 448
|
|
348
|
-
}).then(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
} catch {}
|
|
357
|
-
}).catch(() => void 0);
|
|
358
|
+
}).then(() => fs.writeFile(pidPath, JSON.stringify({
|
|
359
|
+
pid: child.pid,
|
|
360
|
+
ownerPid: process.pid,
|
|
361
|
+
startedAt: Date.now(),
|
|
362
|
+
command: binary
|
|
363
|
+
}), { mode: FILE_MODE })).catch((error) => {
|
|
364
|
+
notice(`Could not write tunnel process record: ${String(error)}`);
|
|
365
|
+
});
|
|
358
366
|
let seenUrl = input.config.kind === "named" ? `https://${input.config.hostname}` : void 0;
|
|
359
367
|
let seenConnection = false;
|
|
360
368
|
const waitToRetry = async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/tunnelProcess/index.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { readFileSync, rmSync } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PAIRING_PAGE_ROUTE } from '../../constants/remote';\nimport {\n type TunnelConfig,\n type TunnelLauncher,\n type TunnelStartInput,\n type TunnelStartResult,\n} from '../../types/remote';\nimport { PAIRING_PAGE_MARKER } from '../pairingPage';\nimport { describeTunnelFailure, extractTunnelUrl, mentionsRegisteredConnection, tunnelArgs } from '../tunnelOutput';\n\n/** Explicit binary path, ahead of a PATH scan. */\nexport const CLOUDFLARED_ENV = 'PO_CLOUDFLARED';\nconst START_TIMEOUT_MS = 45_000;\nconst TERMINATE_GRACE_MS = 2000;\nconst SELF_TEST_ATTEMPTS = 10;\nconst SELF_TEST_RETRY_MS = 500;\nconst NAMED_RESTART_ATTEMPTS = 3;\nconst NAMED_RESTART_BASE_DELAY_MS = 500;\nconst NAMED_RESTART_MAX_DELAY_MS = 4000;\nconst NAMED_RESTART_STABLE_MS = 60_000;\n/** Lines of process output kept for a failure report. */\nconst LOG_TAIL = 20;\nconst UNAUTHORIZED = 401;\n/** Cloudflare has the DNS route but no connector has registered yet. */\nconst EDGE_TUNNEL_UNAVAILABLE = 530;\nconst OK = 200;\nconst PID_FILE = 'tunnel.pid';\nconst TOKEN_FILE = 'tunnel.token';\nconst FILE_MODE = 0o600;\n/** A pid file older than this is stale beyond usefulness and the pid has likely been recycled. */\nconst PID_FILE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport interface ProbeResult {\n status: number;\n body: string;\n}\n\nexport interface TunnelProcessOptions {\n /** Explicit binary, ahead of the env var and the PATH scan. */\n cloudflaredPath?: string;\n stateDir: string;\n onNotice?: (message: string) => void;\n /** Test seam: stands in for node:child_process.spawn. */\n spawnProcess?: typeof spawn;\n /** Test seam: reaches the tunnel from outside, so the self-test needs no network. */\n probe?: (url: string, signal: AbortSignal) => Promise<ProbeResult>;\n /** Test seam: production waits briefly for a new public hostname to propagate. */\n selfTestRetryMs?: number;\n /** Test seam: production bounds the complete startup, including its self-test. */\n startTimeoutMs?: number;\n /** Test seam: production backs named-tunnel restarts off from 500 ms. */\n restartBaseDelayMs?: number;\n /** Test seam: production resets the rapid-crash budget after one stable minute. */\n restartStableMs?: number;\n /** Called after a quick tunnel exits, or after named-tunnel recovery is exhausted. */\n onExit?: (message: string) => void;\n}\n\n/**\n * Finds cloudflared without a shell.\n *\n * A manual scan rather than `which`: spawning a shell to locate a binary is a\n * quoting hazard for no benefit, and this also works the same on every platform.\n */\nexport async function findCloudflared(env: NodeJS.ProcessEnv = process.env): Promise<string | undefined> {\n const names = process.platform === 'win32' ? ['cloudflared.exe', 'cloudflared'] : ['cloudflared'];\n for (const directory of (env.PATH ?? '').split(path.delimiter)) {\n if (directory === '') continue;\n for (const name of names) {\n const candidate = path.join(directory, name);\n try {\n await fs.access(candidate, fs.constants.X_OK);\n return candidate;\n } catch {\n // Not here, or not executable; keep looking.\n }\n }\n }\n return undefined;\n}\n\nasync function defaultProbe(url: string, signal: AbortSignal): Promise<ProbeResult> {\n const response = await fetch(url, { redirect: 'manual', signal });\n return { status: response.status, body: await response.text() };\n}\n\n/**\n * Kills a tunnel left behind by a hub that could not clean up after itself.\n *\n * A SIGKILLed hub never runs its exit handler, and the child is not detached so\n * it is reparented rather than killed. It then holds the tunnel open against a\n * dead port. This is the sweep on the next start.\n *\n * The residual risk is pid reuse after a reboot. The age window narrows it and\n * on Linux the command line is checked; macOS has no equally cheap check, so\n * there the window stands.\n */\ninterface TunnelPidRecord {\n pid?: unknown;\n ownerPid?: unknown;\n startedAt?: unknown;\n}\n\nfunction validPid(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n}\n\n/** Only ESRCH proves the owner is gone; permission and platform errors fail safe. */\nfunction ownerMayBeAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== 'ESRCH';\n }\n}\n\n// sync: exported createRemoteRuntime must reap stale public tunnels before its synchronous startup returns.\nexport function reapStaleTunnel(stateDir: string, onNotice: (message: string) => void): void {\n const pidPath = path.join(stateDir, PID_FILE);\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let raw: string;\n try {\n raw = readFileSync(pidPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') rmSync(tokenPath, { force: true });\n return; // No pid file is the normal case; other read failures are unsafe to guess through.\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return; // A malformed record cannot prove that its owner is gone.\n }\n if (typeof parsed !== 'object' || parsed === null) return;\n const record = parsed as TunnelPidRecord;\n if (Object.hasOwn(record, 'ownerPid')) {\n if (!validPid(record.ownerPid) || ownerMayBeAlive(record.ownerPid)) return;\n }\n\n rmSync(tokenPath, { force: true });\n rmSync(pidPath, { force: true });\n const pid = validPid(record.pid) ? record.pid : undefined;\n const startedAt = typeof record.startedAt === 'number' ? record.startedAt : 0;\n if (pid === undefined || Date.now() - startedAt > PID_FILE_MAX_AGE_MS) return;\n if (process.platform === 'linux') {\n try {\n if (!readFileSync(`/proc/${String(pid)}/cmdline`, 'utf8').includes('cloudflared')) return;\n } catch {\n return; // The process is gone.\n }\n }\n try {\n process.kill(pid, 'SIGTERM');\n onNotice(`stopped a tunnel left running by a previous hub (pid ${String(pid)})`);\n } catch {\n // Already gone, which is the outcome we wanted.\n }\n}\n\nfunction tailOf(lines: readonly string[]): string {\n return lines.slice(-LOG_TAIL).join('').trim();\n}\n\n/** Copies a named tunnel token to a private runtime file understood by cloudflared. */\nasync function writeTokenFile(config: TunnelConfig, stateDir: string): Promise<string | undefined> {\n if (config.kind !== 'named' || config.tokenFile === undefined) return undefined;\n let token: string;\n let sourcePath: string;\n try {\n sourcePath = await fs.realpath(config.tokenFile);\n token = (await fs.readFile(sourcePath, 'utf8')).trim();\n } catch {\n return undefined;\n }\n if (token === '') return undefined;\n await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let existingTarget: string | undefined;\n try {\n existingTarget = await fs.realpath(tokenPath);\n } catch {\n // Missing is the normal state before launch.\n }\n if (sourcePath === path.resolve(tokenPath) || sourcePath === existingTarget) {\n throw new Error('The configured tunnel token file cannot also be Po runtime token storage.');\n }\n await fs.rm(tokenPath, { force: true });\n await fs.writeFile(tokenPath, token, { mode: FILE_MODE });\n await fs.chmod(tokenPath, FILE_MODE);\n return tokenPath;\n}\n\nexport function createTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const restartBaseDelayMs = options.restartBaseDelayMs ?? NAMED_RESTART_BASE_DELAY_MS;\n const restartStableMs = options.restartStableMs ?? NAMED_RESTART_STABLE_MS;\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n let stopped = false;\n let activeStop: (() => Promise<void>) | undefined;\n let stopPromise: Promise<void> | undefined;\n let recoveryPromise: Promise<void> | undefined;\n let pendingExit: string | undefined;\n let restartCount = 0;\n let lastReadyAt = 0;\n const recoveryAbort = new AbortController();\n const signal =\n input.signal === undefined ? recoveryAbort.signal : AbortSignal.any([input.signal, recoveryAbort.signal]);\n const supervisedInput: TunnelStartInput = { ...input, signal };\n\n function queueUnexpectedExit(message: string): void {\n if (stopped) return;\n if (input.config.kind === 'quick') {\n options.onExit?.(message);\n return;\n }\n if (recoveryPromise !== undefined) {\n pendingExit = message;\n return;\n }\n beginRecovery(message);\n }\n\n const single = createSingleTunnelLauncher({ ...options, onExit: queueUnexpectedExit });\n\n async function waitForRestart(attempt: number): Promise<void> {\n if (recoveryAbort.signal.aborted) return;\n const delayMs = Math.min(restartBaseDelayMs * 2 ** (attempt - 1), NAMED_RESTART_MAX_DELAY_MS);\n await new Promise<void>((resolve) => {\n let timer: ReturnType<typeof setTimeout>;\n const done = (): void => {\n clearTimeout(timer);\n recoveryAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n timer = setTimeout(done, delayMs);\n recoveryAbort.signal.addEventListener('abort', done, { once: true });\n if (recoveryAbort.signal.aborted) done();\n });\n }\n\n async function recover(initialMessage: string): Promise<void> {\n if (Date.now() - lastReadyAt >= restartStableMs) restartCount = 0;\n let lastFailure = initialMessage;\n while (!stopped && restartCount < NAMED_RESTART_ATTEMPTS) {\n restartCount += 1;\n notice(\n `cloudflared stopped; restarting named tunnel (attempt ${String(restartCount)} of ${String(NAMED_RESTART_ATTEMPTS)})`,\n );\n await waitForRestart(restartCount);\n if (stopped) return;\n\n const outcome = await single(supervisedInput);\n if (stopped) {\n if (outcome.ok) await outcome.stop().catch(() => undefined);\n return;\n }\n if (!outcome.ok) {\n lastFailure = outcome.message;\n continue;\n }\n activeStop = outcome.stop;\n lastReadyAt = Date.now();\n notice(`named tunnel recovered at ${outcome.publicOrigin}`);\n return;\n }\n\n if (!stopped) {\n options.onExit?.(\n `${initialMessage}\\nNamed tunnel recovery failed after ${String(NAMED_RESTART_ATTEMPTS)} attempts. Last failure: ${lastFailure}`,\n );\n }\n }\n\n function beginRecovery(message: string): void {\n recoveryPromise = recover(message)\n .catch((error: unknown) => {\n if (!stopped) {\n const cause = error instanceof Error ? error.message : String(error);\n options.onExit?.(`${message}\\nNamed tunnel recovery failed: ${cause}`);\n }\n })\n .finally(() => {\n recoveryPromise = undefined;\n const queued = pendingExit;\n pendingExit = undefined;\n if (queued !== undefined && !stopped) beginRecovery(queued);\n });\n }\n\n const initial = await single(supervisedInput);\n if (!initial.ok) {\n stopped = true;\n recoveryAbort.abort();\n return initial;\n }\n activeStop = initial.stop;\n lastReadyAt = Date.now();\n\n const stop = async (): Promise<void> => {\n if (stopPromise !== undefined) return await stopPromise;\n stopped = true;\n recoveryAbort.abort();\n const recovering = recoveryPromise;\n stopPromise = (async () => {\n await activeStop?.().catch(() => undefined);\n await recovering?.catch(() => undefined);\n await fs.rm(path.join(options.stateDir, PID_FILE), { force: true });\n await fs.rm(path.join(options.stateDir, TOKEN_FILE), { force: true });\n })();\n return await stopPromise;\n };\n\n return { ok: true, publicOrigin: initial.publicOrigin, stop };\n };\n}\n\n/** Starts one cloudflared child; the exported launcher supervises successive named-tunnel children. */\nfunction createSingleTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const spawnProcess = options.spawnProcess ?? spawn;\n const probe = options.probe ?? defaultProbe;\n const selfTestRetryMs = options.selfTestRetryMs ?? SELF_TEST_RETRY_MS;\n const startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;\n const pidPath = path.join(options.stateDir, PID_FILE);\n const runtimeTokenPath = path.join(options.stateDir, TOKEN_FILE);\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n if (input.signal?.aborted === true) {\n return { ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' };\n }\n const binary = options.cloudflaredPath ?? process.env[CLOUDFLARED_ENV] ?? (await findCloudflared());\n if (binary === undefined || binary === '') {\n return { ok: false, failure: 'not_installed', message: describeTunnelFailure('not_installed') };\n }\n\n let tokenPath: string | undefined;\n try {\n tokenPath = await writeTokenFile(input.config, options.stateDir);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n const args = tunnelArgs(input.config, input.port, tokenPath);\n let child: ChildProcess;\n try {\n // Never detached and never unref'd. A detached child becomes a process\n // group leader that survives the terminal's SIGINT, which is exactly the\n // orphaned public tunnel this must avoid. Contrast serverSpawner.ts,\n // which detaches on purpose because sessions outlive the hub.\n child = spawnProcess(binary, args, { detached: false, stdio: ['ignore', 'pipe', 'pipe'] });\n } catch (error) {\n await fs.rm(runtimeTokenPath, { force: true });\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n\n const lines: string[] = [];\n let stopped = false;\n let settle: ((result: TunnelStartResult) => void) | undefined;\n let selfTestStarted = false;\n const selfTestAbort = new AbortController();\n\n const killTree = (): void => {\n try {\n child.kill('SIGTERM');\n } catch {\n // Already gone.\n }\n };\n const onProcessExit = (): void => {\n // sync: Node does not await filesystem cleanup in the process exit handler.\n // This is the net under process.exit(), an uncaught exception, and an\n // unhandled rejection, none of which reach the graceful close path.\n rmSync(runtimeTokenPath, { force: true });\n try {\n if (child.pid !== undefined) process.kill(child.pid, 'SIGKILL');\n } catch {\n // Already gone.\n }\n };\n process.once('exit', onProcessExit);\n\n const stop = async (): Promise<void> => {\n if (stopped) return;\n stopped = true;\n if (child.exitCode !== null || child.signalCode !== null) {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n return;\n }\n await new Promise<void>((resolve) => {\n const escalate = setTimeout(() => {\n try {\n child.kill('SIGKILL');\n } catch {\n // Already gone.\n }\n }, TERMINATE_GRACE_MS);\n child.once('exit', () => {\n clearTimeout(escalate);\n resolve();\n });\n killTree();\n });\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n };\n\n // Register child listeners below without yielding, so early exits cannot be missed.\n // Cleanup waits for this ordered write before removing the record.\n const pidWrite = fs\n .mkdir(options.stateDir, { recursive: true, mode: 0o700 })\n .then(async () => {\n try {\n await fs.writeFile(\n pidPath,\n JSON.stringify({ pid: child.pid, ownerPid: process.pid, startedAt: Date.now(), command: binary }),\n { mode: FILE_MODE },\n );\n } catch {\n // The reaper is a safety net; a missing record must not stop startup.\n }\n })\n .catch(() => undefined);\n\n const configuredOrigin = input.config.kind === 'named' ? `https://${input.config.hostname}` : undefined;\n let seenUrl = configuredOrigin;\n let seenConnection = false;\n\n const waitToRetry = async (): Promise<void> => {\n if (selfTestRetryMs <= 0) return;\n await new Promise<void>((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n selfTestAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, selfTestRetryMs);\n selfTestAbort.signal.addEventListener('abort', done, { once: true });\n });\n };\n\n /** Proves end to end that pairing is reachable and that everything else is not. */\n const runSelfTest = async (origin: string): Promise<TunnelStartResult> => {\n let pairResponse: ProbeResult | undefined;\n let healthResponse: ProbeResult | undefined;\n let cause = 'fetch failed';\n for (let attempt = 0; attempt < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted; attempt += 1) {\n try {\n pairResponse = await probe(`${origin}${PAIRING_PAGE_ROUTE}`, selfTestAbort.signal);\n healthResponse = await probe(`${origin}/api/health`, selfTestAbort.signal);\n if (pairResponse.status !== EDGE_TUNNEL_UNAVAILABLE && healthResponse.status !== EDGE_TUNNEL_UNAVAILABLE)\n break;\n cause = `Cloudflare edge answered ${String(healthResponse.status)} before the connector registered`;\n } catch (error) {\n cause = error instanceof Error ? error.message : String(error);\n }\n if (attempt + 1 < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted) await waitToRetry();\n }\n if (pairResponse === undefined || healthResponse === undefined) {\n return { ok: false, failure: 'self_test_failed', message: `The tunnel did not answer: ${cause}` };\n }\n if (healthResponse.status !== UNAUTHORIZED) {\n // The agent would be on the public internet unauthenticated. This is\n // the one failure worth an automatic abort rather than a warning.\n const message = `${describeTunnelFailure('self_test_failed')} /api/health answered ${String(healthResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n if (pairResponse.status !== OK || !pairResponse.body.includes(PAIRING_PAGE_MARKER)) {\n const message = `The pairing page answered ${String(pairResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n notice(`tunnel ready at ${origin}`);\n return { ok: true, publicOrigin: origin, stop };\n };\n\n const consider = (chunk: string): void => {\n lines.push(chunk);\n if (settle === undefined || selfTestStarted) return;\n seenUrl ??= extractTunnelUrl(chunk);\n seenConnection ||= mentionsRegisteredConnection(chunk);\n if (seenUrl === undefined || !seenConnection) return;\n selfTestStarted = true;\n input.acceptOrigin?.(seenUrl);\n void runSelfTest(seenUrl).then((result) => settle?.(result));\n };\n\n child.stdout?.on('data', (data: Buffer) => consider(data.toString()));\n child.stderr?.on('data', (data: Buffer) => consider(data.toString()));\n\n return await new Promise<TunnelStartResult>((resolve) => {\n let deadline: ReturnType<typeof setTimeout>;\n const finish = (result: TunnelStartResult, stopProcess = !result.ok): void => {\n if (settle === undefined) return;\n settle = undefined;\n clearTimeout(deadline);\n input.signal?.removeEventListener('abort', cancel);\n selfTestAbort.abort();\n if (stopProcess) {\n void stop().then(() => resolve(result));\n } else {\n resolve(result);\n }\n };\n const cancel = (): void => {\n finish({ ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' });\n };\n settle = (result) => finish(result);\n deadline = setTimeout(() => {\n finish({\n ok: false,\n failure: 'timeout',\n message: `${describeTunnelFailure('timeout')}\\n${tailOf(lines)}`,\n });\n }, startTimeoutMs);\n input.signal?.addEventListener('abort', cancel, { once: true });\n\n child.once('error', (error) => {\n finish({\n ok: false,\n failure: 'spawn_failed',\n message: `${describeTunnelFailure('spawn_failed')} ${error.message}`,\n });\n });\n\n child.once('exit', async (code) => {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(runtimeTokenPath, { force: true }).catch((error: unknown) => {\n notice(`Tunnel token cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n });\n const message = `${describeTunnelFailure('exited')} (code ${String(code)})\\n${tailOf(lines)}`;\n if (settle !== undefined) {\n finish({ ok: false, failure: 'exited', message }, false);\n return;\n }\n // It came up and then died: network loss, an edge rejection, or a\n // self-update. The supervisor either replaces a named connector or tells\n // the host to fail closed rather than leave an unreachable cockpit enabled.\n if (!stopped) options.onExit?.(message);\n });\n\n // A named tunnel already knows its hostname, but it still waits for\n // cloudflared to register an edge connection before probing the public URL.\n if (input.signal?.aborted === true) cancel();\n });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/tunnelProcess/index.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { readFileSync, rmSync } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PAIRING_PAGE_ROUTE } from '../../constants/remote';\nimport {\n type TunnelConfig,\n type TunnelLauncher,\n type TunnelStartInput,\n type TunnelStartResult,\n} from '../../types/remote';\nimport { PAIRING_PAGE_MARKER } from '../pairingPage';\nimport { describeTunnelFailure, extractTunnelUrl, mentionsRegisteredConnection, tunnelArgs } from '../tunnelOutput';\n\n/** Explicit binary path, ahead of a PATH scan. */\nexport const CLOUDFLARED_ENV = 'PO_CLOUDFLARED';\n// Give new quick-host DNS and negative caches time to settle, within a bounded startup.\nconst START_TIMEOUT_MS = 120_000;\nconst TERMINATE_GRACE_MS = 2000;\nconst SELF_TEST_ATTEMPTS = 240;\nconst SELF_TEST_RETRY_MS = 500;\nconst NAMED_RESTART_ATTEMPTS = 3;\nconst NAMED_RESTART_BASE_DELAY_MS = 500;\nconst NAMED_RESTART_MAX_DELAY_MS = 4000;\nconst NAMED_RESTART_STABLE_MS = 60_000;\n/** Lines of process output kept for a failure report. */\nconst LOG_TAIL = 20;\nconst UNAUTHORIZED = 401;\n/** Cloudflare has the DNS route but no connector has registered yet. */\nconst EDGE_TUNNEL_UNAVAILABLE = 530;\nconst OK = 200;\nconst PID_FILE = 'tunnel.pid';\nconst TOKEN_FILE = 'tunnel.token';\nconst FILE_MODE = 0o600;\n/** A pid file older than this is stale beyond usefulness and the pid has likely been recycled. */\nconst PID_FILE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport interface ProbeResult {\n status: number;\n body: string;\n}\n\nexport interface TunnelProcessOptions {\n /** Explicit binary, ahead of the env var and the PATH scan. */\n cloudflaredPath?: string;\n stateDir: string;\n onNotice?: (message: string) => void;\n /** Test seam: stands in for node:child_process.spawn. */\n spawnProcess?: typeof spawn;\n /** Test seam: reaches the tunnel from outside, so the self-test needs no network. */\n probe?: (url: string, signal: AbortSignal) => Promise<ProbeResult>;\n /** Test seam: production waits briefly for a new public hostname to propagate. */\n selfTestRetryMs?: number;\n /** Test seam: production bounds the complete startup, including its self-test. */\n startTimeoutMs?: number;\n /** Test seam: production backs named-tunnel restarts off from 500 ms. */\n restartBaseDelayMs?: number;\n /** Test seam: production resets the rapid-crash budget after one stable minute. */\n restartStableMs?: number;\n /** Called after a quick tunnel exits, or after named-tunnel recovery is exhausted. */\n onExit?: (message: string) => void;\n}\n\n/**\n * Finds cloudflared without a shell.\n *\n * A manual scan rather than `which`: spawning a shell to locate a binary is a\n * quoting hazard for no benefit, and this also works the same on every platform.\n */\nexport async function findCloudflared(env: NodeJS.ProcessEnv = process.env): Promise<string | undefined> {\n const names = process.platform === 'win32' ? ['cloudflared.exe', 'cloudflared'] : ['cloudflared'];\n for (const directory of (env.PATH ?? '').split(path.delimiter)) {\n if (directory === '') continue;\n for (const name of names) {\n const candidate = path.join(directory, name);\n try {\n await fs.access(candidate, fs.constants.X_OK);\n return candidate;\n } catch {\n // Not here, or not executable; keep looking.\n }\n }\n }\n return undefined;\n}\n\nasync function defaultProbe(url: string, signal: AbortSignal): Promise<ProbeResult> {\n const response = await fetch(url, { redirect: 'manual', signal });\n return { status: response.status, body: await response.text() };\n}\n\n/**\n * Kills a tunnel left behind by a hub that could not clean up after itself.\n *\n * A SIGKILLed hub never runs its exit handler, and the child is not detached so\n * it is reparented rather than killed. It then holds the tunnel open against a\n * dead port. This is the sweep on the next start.\n *\n * The residual risk is pid reuse after a reboot. The age window narrows it and\n * on Linux the command line is checked; macOS has no equally cheap check, so\n * there the window stands.\n */\ninterface TunnelPidRecord {\n pid?: unknown;\n ownerPid?: unknown;\n startedAt?: unknown;\n}\n\nfunction validPid(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;\n}\n\n/** Only ESRCH proves the owner is gone; permission and platform errors fail safe. */\nfunction ownerMayBeAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code !== 'ESRCH';\n }\n}\n\n// sync: exported createRemoteRuntime must reap stale public tunnels before its synchronous startup returns.\nexport function reapStaleTunnel(stateDir: string, onNotice: (message: string) => void): void {\n const pidPath = path.join(stateDir, PID_FILE);\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let raw: string;\n try {\n raw = readFileSync(pidPath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') rmSync(tokenPath, { force: true });\n return; // No pid file is the normal case; other read failures are unsafe to guess through.\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return; // A malformed record cannot prove that its owner is gone.\n }\n if (typeof parsed !== 'object' || parsed === null) return;\n const record = parsed as TunnelPidRecord;\n if (Object.hasOwn(record, 'ownerPid')) {\n if (!validPid(record.ownerPid) || ownerMayBeAlive(record.ownerPid)) return;\n }\n\n rmSync(tokenPath, { force: true });\n rmSync(pidPath, { force: true });\n const pid = validPid(record.pid) ? record.pid : undefined;\n const startedAt = typeof record.startedAt === 'number' ? record.startedAt : 0;\n if (pid === undefined || Date.now() - startedAt > PID_FILE_MAX_AGE_MS) return;\n if (process.platform === 'linux') {\n try {\n if (!readFileSync(`/proc/${String(pid)}/cmdline`, 'utf8').includes('cloudflared')) return;\n } catch {\n return; // The process is gone.\n }\n }\n try {\n process.kill(pid, 'SIGTERM');\n onNotice(`stopped a tunnel left running by a previous hub (pid ${String(pid)})`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH') onNotice(`Could not stop stale tunnel: ${String(error)}`);\n }\n}\n\nfunction tailOf(lines: readonly string[]): string {\n return lines.slice(-LOG_TAIL).join('').trim();\n}\n\n/** Copies a named tunnel token to a private runtime file understood by cloudflared. */\nasync function writeTokenFile(config: TunnelConfig, stateDir: string): Promise<string | undefined> {\n if (config.kind !== 'named' || config.tokenFile === undefined) return undefined;\n let token: string;\n let sourcePath: string;\n try {\n sourcePath = await fs.realpath(config.tokenFile);\n token = (await fs.readFile(sourcePath, 'utf8')).trim();\n } catch {\n return undefined;\n }\n if (token === '') return undefined;\n await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });\n const tokenPath = path.join(stateDir, TOKEN_FILE);\n let existingTarget: string | undefined;\n try {\n existingTarget = await fs.realpath(tokenPath);\n } catch (error) {\n // Missing is the normal state before launch; other failures must not hide an alias.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n if (sourcePath === path.resolve(tokenPath) || sourcePath === existingTarget) {\n throw new Error('The configured tunnel token file cannot also be Po runtime token storage.');\n }\n await fs.rm(tokenPath, { force: true });\n await fs.writeFile(tokenPath, token, { mode: FILE_MODE });\n await fs.chmod(tokenPath, FILE_MODE);\n return tokenPath;\n}\n\nexport function createTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const restartBaseDelayMs = options.restartBaseDelayMs ?? NAMED_RESTART_BASE_DELAY_MS;\n const restartStableMs = options.restartStableMs ?? NAMED_RESTART_STABLE_MS;\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n let stopped = false;\n let activeStop: (() => Promise<void>) | undefined;\n let stopPromise: Promise<void> | undefined;\n let recoveryPromise: Promise<void> | undefined;\n let pendingExit: string | undefined;\n let restartCount = 0;\n let lastReadyAt = 0;\n const recoveryAbort = new AbortController();\n const signal =\n input.signal === undefined ? recoveryAbort.signal : AbortSignal.any([input.signal, recoveryAbort.signal]);\n const supervisedInput: TunnelStartInput = { ...input, signal };\n\n function queueUnexpectedExit(message: string): void {\n if (stopped) return;\n if (input.config.kind === 'quick') {\n options.onExit?.(message);\n return;\n }\n if (recoveryPromise !== undefined) {\n pendingExit = message;\n return;\n }\n beginRecovery(message);\n }\n\n const single = createSingleTunnelLauncher({ ...options, onExit: queueUnexpectedExit });\n\n async function waitForRestart(attempt: number): Promise<void> {\n if (recoveryAbort.signal.aborted) return;\n const delayMs = Math.min(restartBaseDelayMs * 2 ** (attempt - 1), NAMED_RESTART_MAX_DELAY_MS);\n await new Promise<void>((resolve) => {\n let timer: ReturnType<typeof setTimeout>;\n const done = (): void => {\n clearTimeout(timer);\n recoveryAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n timer = setTimeout(done, delayMs);\n recoveryAbort.signal.addEventListener('abort', done, { once: true });\n if (recoveryAbort.signal.aborted) done();\n });\n }\n\n async function recover(initialMessage: string): Promise<void> {\n if (Date.now() - lastReadyAt >= restartStableMs) restartCount = 0;\n let lastFailure = initialMessage;\n while (!stopped && restartCount < NAMED_RESTART_ATTEMPTS) {\n restartCount += 1;\n notice(\n `cloudflared stopped; restarting named tunnel (attempt ${String(restartCount)} of ${String(NAMED_RESTART_ATTEMPTS)})`,\n );\n await waitForRestart(restartCount);\n if (stopped) return;\n\n const outcome = await single(supervisedInput);\n if (stopped) {\n if (outcome.ok) await outcome.stop().catch(() => undefined);\n return;\n }\n if (!outcome.ok) {\n lastFailure = outcome.message;\n continue;\n }\n activeStop = outcome.stop;\n lastReadyAt = Date.now();\n notice(`named tunnel recovered at ${outcome.publicOrigin}`);\n return;\n }\n\n if (!stopped) {\n options.onExit?.(\n `${initialMessage}\\nNamed tunnel recovery failed after ${String(NAMED_RESTART_ATTEMPTS)} attempts. Last failure: ${lastFailure}`,\n );\n }\n }\n\n function beginRecovery(message: string): void {\n recoveryPromise = recover(message)\n .catch((error: unknown) => {\n if (!stopped) {\n const cause = error instanceof Error ? error.message : String(error);\n options.onExit?.(`${message}\\nNamed tunnel recovery failed: ${cause}`);\n }\n })\n .finally(() => {\n recoveryPromise = undefined;\n const queued = pendingExit;\n pendingExit = undefined;\n if (queued !== undefined && !stopped) beginRecovery(queued);\n });\n }\n\n const initial = await single(supervisedInput);\n if (!initial.ok) {\n stopped = true;\n recoveryAbort.abort();\n return initial;\n }\n activeStop = initial.stop;\n lastReadyAt = Date.now();\n\n const stop = async (): Promise<void> => {\n if (stopPromise !== undefined) return await stopPromise;\n stopped = true;\n recoveryAbort.abort();\n const recovering = recoveryPromise;\n stopPromise = (async () => {\n await activeStop?.().catch(() => undefined);\n await recovering?.catch(() => undefined);\n await fs.rm(path.join(options.stateDir, PID_FILE), { force: true });\n await fs.rm(path.join(options.stateDir, TOKEN_FILE), { force: true });\n })();\n return await stopPromise;\n };\n\n return { ok: true, publicOrigin: initial.publicOrigin, stop };\n };\n}\n\n/** Starts one cloudflared child; the exported launcher supervises successive named-tunnel children. */\nfunction createSingleTunnelLauncher(options: TunnelProcessOptions): TunnelLauncher {\n const notice = options.onNotice ?? ((): void => {});\n const spawnProcess = options.spawnProcess ?? spawn;\n const probe = options.probe ?? defaultProbe;\n const selfTestRetryMs = options.selfTestRetryMs ?? SELF_TEST_RETRY_MS;\n const startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;\n const pidPath = path.join(options.stateDir, PID_FILE);\n const runtimeTokenPath = path.join(options.stateDir, TOKEN_FILE);\n\n return async function launch(input: TunnelStartInput): Promise<TunnelStartResult> {\n if (input.signal?.aborted === true) {\n return { ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' };\n }\n const binary = options.cloudflaredPath ?? process.env[CLOUDFLARED_ENV] ?? (await findCloudflared());\n if (binary === undefined || binary === '') {\n return { ok: false, failure: 'not_installed', message: describeTunnelFailure('not_installed') };\n }\n\n let tokenPath: string | undefined;\n try {\n tokenPath = await writeTokenFile(input.config, options.stateDir);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n const args = tunnelArgs(input.config, input.port, tokenPath);\n let child: ChildProcess;\n try {\n // Never detached and never unref'd. A detached child becomes a process\n // group leader that survives the terminal's SIGINT, which is exactly the\n // orphaned public tunnel this must avoid. Contrast serverSpawner.ts,\n // which detaches on purpose because sessions outlive the hub.\n child = spawnProcess(binary, args, { detached: false, stdio: ['ignore', 'pipe', 'pipe'] });\n } catch (error) {\n await fs.rm(runtimeTokenPath, { force: true });\n const cause = error instanceof Error ? error.message : String(error);\n return { ok: false, failure: 'spawn_failed', message: `${describeTunnelFailure('spawn_failed')} ${cause}` };\n }\n\n const lines: string[] = [];\n let stopped = false;\n let settle: ((result: TunnelStartResult) => void) | undefined;\n let selfTestStarted = false;\n const selfTestAbort = new AbortController();\n\n const killTree = (): void => {\n try {\n child.kill('SIGTERM');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH') notice(`Could not stop tunnel: ${String(error)}`);\n }\n };\n const onProcessExit = (): void => {\n // sync: Node does not await filesystem cleanup in the process exit handler.\n // This is the net under process.exit(), an uncaught exception, and an\n // unhandled rejection, none of which reach the graceful close path.\n rmSync(runtimeTokenPath, { force: true });\n try {\n if (child.pid !== undefined) process.kill(child.pid, 'SIGKILL');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH')\n notice(`Could not kill tunnel at exit: ${String(error)}`);\n }\n };\n process.once('exit', onProcessExit);\n\n const stop = async (): Promise<void> => {\n if (stopped) return;\n stopped = true;\n if (child.exitCode !== null || child.signalCode !== null) {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n return;\n }\n await new Promise<void>((resolve) => {\n const escalate = setTimeout(() => {\n try {\n child.kill('SIGKILL');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ESRCH') notice(`Could not kill tunnel: ${String(error)}`);\n }\n }, TERMINATE_GRACE_MS);\n child.once('exit', () => {\n clearTimeout(escalate);\n resolve();\n });\n killTree();\n });\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(pidPath, { force: true });\n await fs.rm(runtimeTokenPath, { force: true });\n };\n\n // Register child listeners below without yielding, so early exits cannot be missed.\n // Cleanup waits for this ordered write before removing the record.\n const pidWrite = fs\n .mkdir(options.stateDir, { recursive: true, mode: 0o700 })\n .then(() =>\n fs.writeFile(\n pidPath,\n JSON.stringify({ pid: child.pid, ownerPid: process.pid, startedAt: Date.now(), command: binary }),\n { mode: FILE_MODE },\n ),\n )\n .catch((error: unknown) => {\n // The reaper is a safety net; report unavailable recovery without blocking startup.\n notice(`Could not write tunnel process record: ${String(error)}`);\n });\n\n const configuredOrigin = input.config.kind === 'named' ? `https://${input.config.hostname}` : undefined;\n let seenUrl = configuredOrigin;\n let seenConnection = false;\n\n const waitToRetry = async (): Promise<void> => {\n if (selfTestRetryMs <= 0) return;\n await new Promise<void>((resolve) => {\n const done = (): void => {\n clearTimeout(timer);\n selfTestAbort.signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, selfTestRetryMs);\n selfTestAbort.signal.addEventListener('abort', done, { once: true });\n });\n };\n\n /** Proves end to end that pairing is reachable and that everything else is not. */\n const runSelfTest = async (origin: string): Promise<TunnelStartResult> => {\n let pairResponse: ProbeResult | undefined;\n let healthResponse: ProbeResult | undefined;\n let cause = 'fetch failed';\n for (let attempt = 0; attempt < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted; attempt += 1) {\n try {\n pairResponse = await probe(`${origin}${PAIRING_PAGE_ROUTE}`, selfTestAbort.signal);\n healthResponse = await probe(`${origin}/api/health`, selfTestAbort.signal);\n if (pairResponse.status !== EDGE_TUNNEL_UNAVAILABLE && healthResponse.status !== EDGE_TUNNEL_UNAVAILABLE)\n break;\n cause = `Cloudflare edge answered ${String(healthResponse.status)} before the connector registered`;\n } catch (error) {\n cause = error instanceof Error ? error.message : String(error);\n }\n if (attempt + 1 < SELF_TEST_ATTEMPTS && !selfTestAbort.signal.aborted) await waitToRetry();\n }\n if (pairResponse === undefined || healthResponse === undefined) {\n return { ok: false, failure: 'self_test_failed', message: `The tunnel did not answer: ${cause}` };\n }\n if (healthResponse.status !== UNAUTHORIZED) {\n // The agent would be on the public internet unauthenticated. This is\n // the one failure worth an automatic abort rather than a warning.\n const message = `${describeTunnelFailure('self_test_failed')} /api/health answered ${String(healthResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n if (pairResponse.status !== OK || !pairResponse.body.includes(PAIRING_PAGE_MARKER)) {\n const message = `The pairing page answered ${String(pairResponse.status)} through the tunnel.`;\n notice(message);\n return { ok: false, failure: 'self_test_failed', message };\n }\n notice(`tunnel ready at ${origin}`);\n return { ok: true, publicOrigin: origin, stop };\n };\n\n const consider = (chunk: string): void => {\n lines.push(chunk);\n if (settle === undefined || selfTestStarted) return;\n seenUrl ??= extractTunnelUrl(chunk);\n seenConnection ||= mentionsRegisteredConnection(chunk);\n if (seenUrl === undefined || !seenConnection) return;\n selfTestStarted = true;\n input.acceptOrigin?.(seenUrl);\n void runSelfTest(seenUrl).then((result) => settle?.(result));\n };\n\n child.stdout?.on('data', (data: Buffer) => consider(data.toString()));\n child.stderr?.on('data', (data: Buffer) => consider(data.toString()));\n\n return await new Promise<TunnelStartResult>((resolve) => {\n let deadline: ReturnType<typeof setTimeout>;\n const finish = (result: TunnelStartResult, stopProcess = !result.ok): void => {\n if (settle === undefined) return;\n settle = undefined;\n clearTimeout(deadline);\n input.signal?.removeEventListener('abort', cancel);\n selfTestAbort.abort();\n if (stopProcess) {\n void stop().then(() => resolve(result));\n } else {\n resolve(result);\n }\n };\n const cancel = (): void => {\n finish({ ok: false, failure: 'exited', message: 'Tunnel startup was cancelled.' });\n };\n settle = (result) => finish(result);\n deadline = setTimeout(() => {\n finish({\n ok: false,\n failure: 'timeout',\n message: `${describeTunnelFailure('timeout')}\\n${tailOf(lines)}`,\n });\n }, startTimeoutMs);\n input.signal?.addEventListener('abort', cancel, { once: true });\n\n child.once('error', (error) => {\n finish({\n ok: false,\n failure: 'spawn_failed',\n message: `${describeTunnelFailure('spawn_failed')} ${error.message}`,\n });\n });\n\n child.once('exit', async (code) => {\n process.off('exit', onProcessExit);\n await pidWrite;\n await fs.rm(runtimeTokenPath, { force: true }).catch((error: unknown) => {\n notice(`Tunnel token cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n });\n const message = `${describeTunnelFailure('exited')} (code ${String(code)})\\n${tailOf(lines)}`;\n if (settle !== undefined) {\n finish({ ok: false, failure: 'exited', message }, false);\n return;\n }\n // It came up and then died: network loss, an edge rejection, or a\n // self-update. The supervisor either replaces a named connector or tells\n // the host to fail closed rather than leave an unreachable cockpit enabled.\n if (!stopped) options.onExit?.(message);\n });\n\n // A named tunnel already knows its hostname, but it still waits for\n // cloudflared to register an edge connection before probing the public URL.\n if (input.signal?.aborted === true) cancel();\n });\n };\n}\n"],"mappings":";;;;;;;;;AAgBA,MAAa,kBAAkB;AAE/B,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,yBAAyB;AAC/B,MAAM,8BAA8B;AACpC,MAAM,6BAA6B;AACnC,MAAM,0BAA0B;AAGhC,MAAM,eAAe;;AAErB,MAAM,0BAA0B;AAChC,MAAM,KAAK;AACX,MAAM,WAAW;AACjB,MAAM,aAAa;AACnB,MAAM,YAAY;;AAElB,MAAM,sBAAsB;;;;;;;AAkC5B,eAAsB,gBAAgB,MAAyB,QAAQ,KAAkC;CACvG,MAAM,QAAQ,QAAQ,aAAa,UAAU,CAAC,mBAAmB,aAAa,IAAI,CAAC,aAAa;CAChG,KAAK,MAAM,cAAc,IAAI,QAAQ,GAAA,CAAI,MAAM,KAAK,SAAS,GAAG;EAC9D,IAAI,cAAc,IAAI;EACtB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,YAAY,KAAK,KAAK,WAAW,IAAI;GAC3C,IAAI;IACF,MAAM,GAAG,OAAO,WAAW,GAAG,UAAU,IAAI;IAC5C,OAAO;GACT,QAAQ,CAER;EACF;CACF;AAEF;AAEA,eAAe,aAAa,KAAa,QAA2C;CAClF,MAAM,WAAW,MAAM,MAAM,KAAK;EAAE,UAAU;EAAU;CAAO,CAAC;CAChE,OAAO;EAAE,QAAQ,SAAS;EAAQ,MAAM,MAAM,SAAS,KAAK;CAAE;AAChE;AAmBA,SAAS,SAAS,OAAiC;CACjD,OAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,QAAQ;AAC7E;;AAGA,SAAS,gBAAgB,KAAsB;CAC7C,IAAI;EACF,QAAQ,KAAK,KAAK,CAAC;EACnB,OAAO;CACT,SAAS,OAAO;EACd,OAAQ,MAAgC,SAAS;CACnD;AACF;AAGA,SAAgB,gBAAgB,UAAkB,UAA2C;CAC3F,MAAM,UAAU,KAAK,KAAK,UAAU,QAAQ;CAC5C,MAAM,YAAY,KAAK,KAAK,UAAU,UAAU;CAChD,IAAI;CACJ,IAAI;EACF,MAAM,aAAa,SAAS,MAAM;CACpC,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;EACzF;CACF;CACA,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN;CACF;CACA,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;CACnD,MAAM,SAAS;CACf,IAAI,OAAO,OAAO,QAAQ,UAAU,GAC9B;MAAA,CAAC,SAAS,OAAO,QAAQ,KAAK,gBAAgB,OAAO,QAAQ,GAAG;CAAA;CAGtE,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;CACjC,OAAO,SAAS,EAAE,OAAO,KAAK,CAAC;CAC/B,MAAM,MAAM,SAAS,OAAO,GAAG,IAAI,OAAO,MAAM,KAAA;CAChD,MAAM,YAAY,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;CAC5E,IAAI,QAAQ,KAAA,KAAa,KAAK,IAAI,IAAI,YAAY,qBAAqB;CACvE,IAAI,QAAQ,aAAa,SACvB,IAAI;EACF,IAAI,CAAC,aAAa,SAAS,OAAO,GAAG,EAAE,WAAW,MAAM,CAAC,CAAC,SAAS,aAAa,GAAG;CACrF,QAAQ;EACN;CACF;CAEF,IAAI;EACF,QAAQ,KAAK,KAAK,SAAS;EAC3B,SAAS,wDAAwD,OAAO,GAAG,EAAE,EAAE;CACjF,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,SAAS,SAAS,gCAAgC,OAAO,KAAK,GAAG;CACjH;AACF;AAEA,SAAS,OAAO,OAAkC;CAChD,OAAO,MAAM,MAAM,GAAS,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK;AAC9C;;AAGA,eAAe,eAAe,QAAsB,UAA+C;CACjG,IAAI,OAAO,SAAS,WAAW,OAAO,cAAc,KAAA,GAAW,OAAO,KAAA;CACtE,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,aAAa,MAAM,GAAG,SAAS,OAAO,SAAS;EAC/C,SAAS,MAAM,GAAG,SAAS,YAAY,MAAM,EAAA,CAAG,KAAK;CACvD,QAAQ;EACN;CACF;CACA,IAAI,UAAU,IAAI,OAAO,KAAA;CACzB,MAAM,GAAG,MAAM,UAAU;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACzD,MAAM,YAAY,KAAK,KAAK,UAAU,UAAU;CAChD,IAAI;CACJ,IAAI;EACF,iBAAiB,MAAM,GAAG,SAAS,SAAS;CAC9C,SAAS,OAAO;EAEd,IAAK,MAAgC,SAAS,UAAU,MAAM;CAChE;CACA,IAAI,eAAe,KAAK,QAAQ,SAAS,KAAK,eAAe,gBAC3D,MAAM,IAAI,MAAM,2EAA2E;CAE7F,MAAM,GAAG,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;CACtC,MAAM,GAAG,UAAU,WAAW,OAAO,EAAE,MAAM,UAAU,CAAC;CACxD,MAAM,GAAG,MAAM,WAAW,SAAS;CACnC,OAAO;AACT;AAEA,SAAgB,qBAAqB,SAA+C;CAClF,MAAM,SAAS,QAAQ,mBAAyB,CAAC;CACjD,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,MAAM,kBAAkB,QAAQ,mBAAmB;CAEnD,OAAO,eAAe,OAAO,OAAqD;EAChF,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI,eAAe;EACnB,IAAI,cAAc;EAClB,MAAM,gBAAgB,IAAI,gBAAgB;EAC1C,MAAM,SACJ,MAAM,WAAW,KAAA,IAAY,cAAc,SAAS,YAAY,IAAI,CAAC,MAAM,QAAQ,cAAc,MAAM,CAAC;EAC1G,MAAM,kBAAoC;GAAE,GAAG;GAAO;EAAO;EAE7D,SAAS,oBAAoB,SAAuB;GAClD,IAAI,SAAS;GACb,IAAI,MAAM,OAAO,SAAS,SAAS;IACjC,QAAQ,SAAS,OAAO;IACxB;GACF;GACA,IAAI,oBAAoB,KAAA,GAAW;IACjC,cAAc;IACd;GACF;GACA,cAAc,OAAO;EACvB;EAEA,MAAM,SAAS,2BAA2B;GAAE,GAAG;GAAS,QAAQ;EAAoB,CAAC;EAErF,eAAe,eAAe,SAAgC;GAC5D,IAAI,cAAc,OAAO,SAAS;GAClC,MAAM,UAAU,KAAK,IAAI,qBAAqB,MAAM,UAAU,IAAI,0BAA0B;GAC5F,MAAM,IAAI,SAAe,YAAY;IACnC,IAAI;IACJ,MAAM,aAAmB;KACvB,aAAa,KAAK;KAClB,cAAc,OAAO,oBAAoB,SAAS,IAAI;KACtD,QAAQ;IACV;IACA,QAAQ,WAAW,MAAM,OAAO;IAChC,cAAc,OAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;IACnE,IAAI,cAAc,OAAO,SAAS,KAAK;GACzC,CAAC;EACH;EAEA,eAAe,QAAQ,gBAAuC;GAC5D,IAAI,KAAK,IAAI,IAAI,eAAe,iBAAiB,eAAe;GAChE,IAAI,cAAc;GAClB,OAAO,CAAC,WAAW,eAAe,wBAAwB;IACxD,gBAAgB;IAChB,OACE,yDAAyD,OAAO,YAAY,EAAE,MAAM,OAAO,sBAAsB,EAAE,EACrH;IACA,MAAM,eAAe,YAAY;IACjC,IAAI,SAAS;IAEb,MAAM,UAAU,MAAM,OAAO,eAAe;IAC5C,IAAI,SAAS;KACX,IAAI,QAAQ,IAAI,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS;KAC1D;IACF;IACA,IAAI,CAAC,QAAQ,IAAI;KACf,cAAc,QAAQ;KACtB;IACF;IACA,aAAa,QAAQ;IACrB,cAAc,KAAK,IAAI;IACvB,OAAO,6BAA6B,QAAQ,cAAc;IAC1D;GACF;GAEA,IAAI,CAAC,SACH,QAAQ,SACN,GAAG,eAAe,uCAAuC,OAAO,sBAAsB,EAAE,2BAA2B,aACrH;EAEJ;EAEA,SAAS,cAAc,SAAuB;GAC5C,kBAAkB,QAAQ,OAAO,CAAC,CAC/B,OAAO,UAAmB;IACzB,IAAI,CAAC,SAAS;KACZ,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACnE,QAAQ,SAAS,GAAG,QAAQ,kCAAkC,OAAO;IACvE;GACF,CAAC,CAAC,CACD,cAAc;IACb,kBAAkB,KAAA;IAClB,MAAM,SAAS;IACf,cAAc,KAAA;IACd,IAAI,WAAW,KAAA,KAAa,CAAC,SAAS,cAAc,MAAM;GAC5D,CAAC;EACL;EAEA,MAAM,UAAU,MAAM,OAAO,eAAe;EAC5C,IAAI,CAAC,QAAQ,IAAI;GACf,UAAU;GACV,cAAc,MAAM;GACpB,OAAO;EACT;EACA,aAAa,QAAQ;EACrB,cAAc,KAAK,IAAI;EAEvB,MAAM,OAAO,YAA2B;GACtC,IAAI,gBAAgB,KAAA,GAAW,OAAO,MAAM;GAC5C,UAAU;GACV,cAAc,MAAM;GACpB,MAAM,aAAa;GACnB,eAAe,YAAY;IACzB,MAAM,aAAa,CAAC,CAAC,YAAY,KAAA,CAAS;IAC1C,MAAM,YAAY,YAAY,KAAA,CAAS;IACvC,MAAM,GAAG,GAAG,KAAK,KAAK,QAAQ,UAAU,QAAQ,GAAG,EAAE,OAAO,KAAK,CAAC;IAClE,MAAM,GAAG,GAAG,KAAK,KAAK,QAAQ,UAAU,UAAU,GAAG,EAAE,OAAO,KAAK,CAAC;GACtE,EAAA,CAAG;GACH,OAAO,MAAM;EACf;EAEA,OAAO;GAAE,IAAI;GAAM,cAAc,QAAQ;GAAc;EAAK;CAC9D;AACF;;AAGA,SAAS,2BAA2B,SAA+C;CACjF,MAAM,SAAS,QAAQ,mBAAyB,CAAC;CACjD,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,UAAU,KAAK,KAAK,QAAQ,UAAU,QAAQ;CACpD,MAAM,mBAAmB,KAAK,KAAK,QAAQ,UAAU,UAAU;CAE/D,OAAO,eAAe,OAAO,OAAqD;EAChF,IAAI,MAAM,QAAQ,YAAY,MAC5B,OAAO;GAAE,IAAI;GAAO,SAAS;GAAU,SAAS;EAAgC;EAElF,MAAM,SAAS,QAAQ,mBAAmB,QAAQ,IAAA,qBAAyB,MAAM,gBAAgB;EACjG,IAAI,WAAW,KAAA,KAAa,WAAW,IACrC,OAAO;GAAE,IAAI;GAAO,SAAS;GAAiB,SAAS,sBAAsB,eAAe;EAAE;EAGhG,IAAI;EACJ,IAAI;GACF,YAAY,MAAM,eAAe,MAAM,QAAQ,QAAQ,QAAQ;EACjE,SAAS,OAAO;GACd,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnE,OAAO;IAAE,IAAI;IAAO,SAAS;IAAgB,SAAS,GAAG,sBAAsB,cAAc,EAAE,GAAG;GAAQ;EAC5G;EACA,MAAM,OAAO,WAAW,MAAM,QAAQ,MAAM,MAAM,SAAS;EAC3D,IAAI;EACJ,IAAI;GAKF,QAAQ,aAAa,QAAQ,MAAM;IAAE,UAAU;IAAO,OAAO;KAAC;KAAU;KAAQ;IAAM;GAAE,CAAC;EAC3F,SAAS,OAAO;GACd,MAAM,GAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC;GAC7C,MAAM,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnE,OAAO;IAAE,IAAI;IAAO,SAAS;IAAgB,SAAS,GAAG,sBAAsB,cAAc,EAAE,GAAG;GAAQ;EAC5G;EAEA,MAAM,QAAkB,CAAC;EACzB,IAAI,UAAU;EACd,IAAI;EACJ,IAAI,kBAAkB;EACtB,MAAM,gBAAgB,IAAI,gBAAgB;EAE1C,MAAM,iBAAuB;GAC3B,IAAI;IACF,MAAM,KAAK,SAAS;GACtB,SAAS,OAAO;IACd,IAAK,MAAgC,SAAS,SAAS,OAAO,0BAA0B,OAAO,KAAK,GAAG;GACzG;EACF;EACA,MAAM,sBAA4B;GAIhC,OAAO,kBAAkB,EAAE,OAAO,KAAK,CAAC;GACxC,IAAI;IACF,IAAI,MAAM,QAAQ,KAAA,GAAW,QAAQ,KAAK,MAAM,KAAK,SAAS;GAChE,SAAS,OAAO;IACd,IAAK,MAAgC,SAAS,SAC5C,OAAO,kCAAkC,OAAO,KAAK,GAAG;GAC5D;EACF;EACA,QAAQ,KAAK,QAAQ,aAAa;EAElC,MAAM,OAAO,YAA2B;GACtC,IAAI,SAAS;GACb,UAAU;GACV,IAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,MAAM;IACxD,QAAQ,IAAI,QAAQ,aAAa;IACjC,MAAM;IACN,MAAM,GAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC;IACpC,MAAM,GAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC;IAC7C;GACF;GACA,MAAM,IAAI,SAAe,YAAY;IACnC,MAAM,WAAW,iBAAiB;KAChC,IAAI;MACF,MAAM,KAAK,SAAS;KACtB,SAAS,OAAO;MACd,IAAK,MAAgC,SAAS,SAAS,OAAO,0BAA0B,OAAO,KAAK,GAAG;KACzG;IACF,GAAG,kBAAkB;IACrB,MAAM,KAAK,cAAc;KACvB,aAAa,QAAQ;KACrB,QAAQ;IACV,CAAC;IACD,SAAS;GACX,CAAC;GACD,QAAQ,IAAI,QAAQ,aAAa;GACjC,MAAM;GACN,MAAM,GAAG,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC;GACpC,MAAM,GAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC;EAC/C;EAIA,MAAM,WAAW,GACd,MAAM,QAAQ,UAAU;GAAE,WAAW;GAAM,MAAM;EAAM,CAAC,CAAC,CACzD,WACC,GAAG,UACD,SACA,KAAK,UAAU;GAAE,KAAK,MAAM;GAAK,UAAU,QAAQ;GAAK,WAAW,KAAK,IAAI;GAAG,SAAS;EAAO,CAAC,GAChG,EAAE,MAAM,UAAU,CACpB,CACF,CAAC,CACA,OAAO,UAAmB;GAEzB,OAAO,0CAA0C,OAAO,KAAK,GAAG;EAClE,CAAC;EAGH,IAAI,UADqB,MAAM,OAAO,SAAS,UAAU,WAAW,MAAM,OAAO,aAAa,KAAA;EAE9F,IAAI,iBAAiB;EAErB,MAAM,cAAc,YAA2B;GAC7C,IAAI,mBAAmB,GAAG;GAC1B,MAAM,IAAI,SAAe,YAAY;IACnC,MAAM,aAAmB;KACvB,aAAa,KAAK;KAClB,cAAc,OAAO,oBAAoB,SAAS,IAAI;KACtD,QAAQ;IACV;IACA,MAAM,QAAQ,WAAW,MAAM,eAAe;IAC9C,cAAc,OAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;GACrE,CAAC;EACH;;EAGA,MAAM,cAAc,OAAO,WAA+C;GACxE,IAAI;GACJ,IAAI;GACJ,IAAI,QAAQ;GACZ,KAAK,IAAI,UAAU,GAAG,UAAU,sBAAsB,CAAC,cAAc,OAAO,SAAS,WAAW,GAAG;IACjG,IAAI;KACF,eAAe,MAAM,MAAM,GAAG,SAAS,sBAAsB,cAAc,MAAM;KACjF,iBAAiB,MAAM,MAAM,GAAG,OAAO,cAAc,cAAc,MAAM;KACzE,IAAI,aAAa,WAAW,2BAA2B,eAAe,WAAW,yBAC/E;KACF,QAAQ,4BAA4B,OAAO,eAAe,MAAM,EAAE;IACpE,SAAS,OAAO;KACd,QAAQ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC/D;IACA,IAAI,UAAU,IAAI,sBAAsB,CAAC,cAAc,OAAO,SAAS,MAAM,YAAY;GAC3F;GACA,IAAI,iBAAiB,KAAA,KAAa,mBAAmB,KAAA,GACnD,OAAO;IAAE,IAAI;IAAO,SAAS;IAAoB,SAAS,8BAA8B;GAAQ;GAElG,IAAI,eAAe,WAAW,cAAc;IAG1C,MAAM,UAAU,GAAG,sBAAsB,kBAAkB,EAAE,wBAAwB,OAAO,eAAe,MAAM,EAAE;IACnH,OAAO,OAAO;IACd,OAAO;KAAE,IAAI;KAAO,SAAS;KAAoB;IAAQ;GAC3D;GACA,IAAI,aAAa,WAAW,MAAM,CAAC,aAAa,KAAK,SAAA,iBAA4B,GAAG;IAClF,MAAM,UAAU,6BAA6B,OAAO,aAAa,MAAM,EAAE;IACzE,OAAO,OAAO;IACd,OAAO;KAAE,IAAI;KAAO,SAAS;KAAoB;IAAQ;GAC3D;GACA,OAAO,mBAAmB,QAAQ;GAClC,OAAO;IAAE,IAAI;IAAM,cAAc;IAAQ;GAAK;EAChD;EAEA,MAAM,YAAY,UAAwB;GACxC,MAAM,KAAK,KAAK;GAChB,IAAI,WAAW,KAAA,KAAa,iBAAiB;GAC7C,YAAY,iBAAiB,KAAK;GAClC,mBAAmB,6BAA6B,KAAK;GACrD,IAAI,YAAY,KAAA,KAAa,CAAC,gBAAgB;GAC9C,kBAAkB;GAClB,MAAM,eAAe,OAAO;GAC5B,YAAiB,OAAO,CAAC,CAAC,MAAM,WAAW,SAAS,MAAM,CAAC;EAC7D;EAEA,MAAM,QAAQ,GAAG,SAAS,SAAiB,SAAS,KAAK,SAAS,CAAC,CAAC;EACpE,MAAM,QAAQ,GAAG,SAAS,SAAiB,SAAS,KAAK,SAAS,CAAC,CAAC;EAEpE,OAAO,MAAM,IAAI,SAA4B,YAAY;GACvD,IAAI;GACJ,MAAM,UAAU,QAA2B,cAAc,CAAC,OAAO,OAAa;IAC5E,IAAI,WAAW,KAAA,GAAW;IAC1B,SAAS,KAAA;IACT,aAAa,QAAQ;IACrB,MAAM,QAAQ,oBAAoB,SAAS,MAAM;IACjD,cAAc,MAAM;IACpB,IAAI,aACF,KAAU,CAAC,CAAC,WAAW,QAAQ,MAAM,CAAC;SAEtC,QAAQ,MAAM;GAElB;GACA,MAAM,eAAqB;IACzB,OAAO;KAAE,IAAI;KAAO,SAAS;KAAU,SAAS;IAAgC,CAAC;GACnF;GACA,UAAU,WAAW,OAAO,MAAM;GAClC,WAAW,iBAAiB;IAC1B,OAAO;KACL,IAAI;KACJ,SAAS;KACT,SAAS,GAAG,sBAAsB,SAAS,EAAE,IAAI,OAAO,KAAK;IAC/D,CAAC;GACH,GAAG,cAAc;GACjB,MAAM,QAAQ,iBAAiB,SAAS,QAAQ,EAAE,MAAM,KAAK,CAAC;GAE9D,MAAM,KAAK,UAAU,UAAU;IAC7B,OAAO;KACL,IAAI;KACJ,SAAS;KACT,SAAS,GAAG,sBAAsB,cAAc,EAAE,GAAG,MAAM;IAC7D,CAAC;GACH,CAAC;GAED,MAAM,KAAK,QAAQ,OAAO,SAAS;IACjC,QAAQ,IAAI,QAAQ,aAAa;IACjC,MAAM;IACN,MAAM,GAAG,GAAG,kBAAkB,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;KACvE,OAAO,gCAAgC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;IACjG,CAAC;IACD,MAAM,UAAU,GAAG,sBAAsB,QAAQ,EAAE,SAAS,OAAO,IAAI,EAAE,KAAK,OAAO,KAAK;IAC1F,IAAI,WAAW,KAAA,GAAW;KACxB,OAAO;MAAE,IAAI;MAAO,SAAS;MAAU;KAAQ,GAAG,KAAK;KACvD;IACF;IAIA,IAAI,CAAC,SAAS,QAAQ,SAAS,OAAO;GACxC,CAAC;GAID,IAAI,MAAM,QAAQ,YAAY,MAAM,OAAO;EAC7C,CAAC;CACH;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/po-core",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.82",
|
|
4
4
|
"description": "Po runtime systems and reusable core services.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -569,8 +569,8 @@
|
|
|
569
569
|
"registry": "https://registry.npmjs.org/"
|
|
570
570
|
},
|
|
571
571
|
"dependencies": {
|
|
572
|
-
"@agimon-ai/po-logsink": "0.0.1-alpha.
|
|
573
|
-
"@agimon-ai/po-web-security": "0.0.0-alpha.
|
|
572
|
+
"@agimon-ai/po-logsink": "0.0.1-alpha.64",
|
|
573
|
+
"@agimon-ai/po-web-security": "0.0.0-alpha.82",
|
|
574
574
|
"@deepseek-ai/cordis": "4.0.5",
|
|
575
575
|
"@earendil-works/chord": "1.4.0",
|
|
576
576
|
"@earendil-works/pi-agent-core": "1.4.0",
|
|
@@ -594,7 +594,7 @@
|
|
|
594
594
|
"yaml": "2.9.1"
|
|
595
595
|
},
|
|
596
596
|
"devDependencies": {
|
|
597
|
-
"@agimon-ai/vibe-lint-plugin-po-core": "0.0.0-alpha.
|
|
597
|
+
"@agimon-ai/vibe-lint-plugin-po-core": "0.0.0-alpha.82",
|
|
598
598
|
"@apidevtools/swagger-parser": "^13.1.0",
|
|
599
599
|
"@asyncapi/parser": "^3.6.4",
|
|
600
600
|
"@earendil-works/pi-ai": "1.4.0",
|