Files
paseo/packages/app/e2e/helpers/daemon-update.ts
T
Mohamed BoudraandGitHub d2de309fc3 Make relay opt-in when pairing devices (#2706)
* feat: make relay opt-in for device pairing

* fix(relay): keep pairing state authoritative

* fix(relay): refresh pairing state across transports

* fix(onboard): honor explicit relay opt-out

* fix(relay): surface transport startup failures

* fix(pairing): gate offers for older daemons

* test(cli): rely on real pairing transport coverage

* fix(cli): report live relay without a pid file

* fix(relay): synchronize config during daemon startup

* fix(relay): preserve mutability for ignored overrides

* fix(cli): use reachable daemon for pairing

* fix(app): redirect invalid pair device routes

* fix(pairing): support remote host device pairing

Keep Pair Device host-relative through the existing settings and daemon RPC flow. Route CLI output through an injected adapter so workflow tests do not mutate process globals.

* fix(pairing): validate pairing destinations

Expose the host-relative Pair Device flow on every supported app platform. Reject a reachable daemon whose identity does not match the requested Paseo home before reading or mutating its pairing state.

* fix(pairing): render QR codes across platforms

Generate SVG text without DOM or canvas and render it through the existing cross-platform SVG stack.

* fix(cli): pair from persisted relay state

Allow explicit relay pairing while the daemon is stopped when relay is already enabled in persisted configuration.
2026-08-01 04:35:06 +08:00

98 lines
2.8 KiB
TypeScript

import { fork, type ChildProcess } from "node:child_process";
import { once } from "node:events";
import path from "node:path";
export interface OutdatedDaemon {
endpoint: string;
label: string;
serverId: string;
close(): Promise<void>;
}
interface OutdatedDaemonReadyMessage {
type: "ready";
endpoint: string;
serverId: string;
}
interface OutdatedDaemonErrorMessage {
type: "error";
error: string;
}
type OutdatedDaemonMessage = OutdatedDaemonReadyMessage | OutdatedDaemonErrorMessage;
export async function startOutdatedDaemon(options?: {
desktopManaged?: boolean;
daemonStatusRpcCapability?: boolean;
relayConfigCapability?: boolean;
}): Promise<OutdatedDaemon> {
const metroPort = process.env.E2E_METRO_PORT;
if (!metroPort) {
throw new Error("E2E_METRO_PORT is not set - globalSetup must run first");
}
const child = fork(
path.resolve(__dirname, "../../../server/src/server/test-utils/outdated-daemon-process.ts"),
{
env: {
...process.env,
E2E_METRO_PORT: metroPort,
E2E_DESKTOP_MANAGED: options?.desktopManaged === true ? "1" : "0",
E2E_DAEMON_STATUS_RPC_CAPABILITY: options?.daemonStatusRpcCapability === false ? "0" : "1",
E2E_RELAY_CONFIG_CAPABILITY: options?.relayConfigCapability === false ? "0" : "1",
},
execArgv: ["--import", "tsx"],
stdio: ["ignore", "pipe", "pipe", "ipc"],
},
);
const stderr: string[] = [];
child.stderr?.on("data", (data: Buffer) => stderr.push(data.toString("utf8")));
try {
const ready = await waitForDaemon(child, stderr);
return {
endpoint: ready.endpoint,
label: options?.desktopManaged === true ? "outdated Desktop host" : "outdated host",
serverId: ready.serverId,
async close() {
if (child.exitCode !== null || child.signalCode !== null) return;
child.kill("SIGTERM");
await once(child, "exit");
},
};
} catch (error) {
child.kill("SIGTERM");
throw error;
}
}
async function waitForDaemon(
child: ChildProcess,
stderr: string[],
): Promise<OutdatedDaemonReadyMessage> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Timed out starting outdated daemon. ${stderr.join("")}`));
}, 20_000);
child.once("exit", (code, signal) => {
clearTimeout(timeout);
reject(
new Error(
`Outdated daemon exited before startup (code ${String(code)}, signal ${String(signal)}). ${stderr.join("")}`,
),
);
});
child.once("message", (message: OutdatedDaemonMessage) => {
if (message.type === "error") {
clearTimeout(timeout);
reject(new Error(message.error));
return;
}
clearTimeout(timeout);
resolve(message);
});
});
}