feat(desktop-client): derive stable client_id from machine identity

Generate the desktop client_id deterministically from OS machine identifiers
(IOPlatformUUID / MachineGuid / /etc/machine-id) scoped per
tenant+workspace+user, with a persisted UUID fallback when the OS lookup
fails. The renderer now requests the id over a new IPC bridge instead of
keeping a localStorage UUID, so reinstalls and storage clears no longer
fork into duplicate clients. Server enforces the id as required and
rejects empty/malformed UUIDs. Also harden bootstrap reveal flow and
single-instance exit so a second launch focuses the existing window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 20:06:37 +08:00
parent fee16d3ea9
commit 543fe0635f
7 changed files with 358 additions and 131 deletions
+74 -58
View File
@@ -1,4 +1,3 @@
import { hostname } from "node:os";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
@@ -31,6 +30,7 @@ import {
unbindRuntimeAccount,
} from "./runtime-controller";
import { createRuntimeSnapshot } from "./runtime-snapshot";
import { resolveDesktopClientID, resolveDesktopDeviceInfo } from "./device-id";
import { initScheduler } from "./scheduler";
import { initSessionRegistry } from "./session-registry";
import { initSingleInstance } from "./single-instance";
@@ -166,7 +166,15 @@ async function ensureMainWindow(): Promise<ElectronBrowserWindow> {
async function revealMainWindow(): Promise<void> {
const window = await ensureMainWindow();
if (window.isMinimized()) {
window.restore();
}
applyWindowMode(window, currentWindowMode);
if (!window.isVisible()) {
window.show();
}
window.focus();
app.focus({ steal: true });
}
function revealMainWindowSafely(source: string): void {
@@ -295,11 +303,16 @@ function isSafeExternalUrl(url: string): boolean {
function registerBridgeHandlers(): void {
ipcMain.handle("desktop:ping", () => "pong");
ipcMain.handle("desktop:device-info", () => ({
device_name: hostname() || `Desktop ${process.platform}`,
os: process.platform,
cpu_arch: process.arch,
}));
ipcMain.handle("desktop:device-info", () => resolveDesktopDeviceInfo());
safeHandle(
"desktop:client-id",
(_event, scope: { tenant_id?: unknown; workspace_id?: unknown; user_id?: unknown }) =>
resolveDesktopClientID({
tenant_id: typeof scope?.tenant_id === "number" ? scope.tenant_id : 0,
workspace_id: typeof scope?.workspace_id === "number" ? scope.workspace_id : 0,
user_id: typeof scope?.user_id === "number" ? scope.user_id : 0,
}),
);
ipcMain.handle("desktop:runtime-snapshot", () => captureRuntimeSnapshot());
safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => {
const account = (await bindPublishAccount(platformId)) as DesktopAccountInfo;
@@ -365,60 +378,63 @@ function registerBridgeHandlers(): void {
});
}
if (!initSingleInstance(() => {
const hasSingleInstanceLock = initSingleInstance(() => {
revealMainWindowSafely("single-instance");
})) {
app.quit();
}
});
app.whenReady().then(async () => {
currentWindowMode = readPersistedWindowMode();
registerBridgeHandlers();
await initSessionRegistry();
initAccountHealth();
initTransport();
initScheduler();
initProcessMetricsSampler();
startHotViewReaper();
startHiddenPlaywrightReaper();
onRuntimeInvalidated((event) => {
syncDesktopHealthIndicator(currentMainWindow());
if (!mainRendererContents || mainRendererContents.isDestroyed()) {
if (!hasSingleInstanceLock) {
console.info("[desktop-main] another instance is already running; exiting");
app.exit(0);
} else {
app.whenReady().then(async () => {
currentWindowMode = readPersistedWindowMode();
registerBridgeHandlers();
await initSessionRegistry();
initAccountHealth();
initTransport();
initScheduler();
initProcessMetricsSampler();
startHotViewReaper();
startHiddenPlaywrightReaper();
onRuntimeInvalidated((event) => {
syncDesktopHealthIndicator(currentMainWindow());
if (!mainRendererContents || mainRendererContents.isDestroyed()) {
return;
}
mainRendererContents.send("desktop:runtime-invalidated", event);
});
await ensureMainWindow();
initTray(() => {
revealMainWindowSafely("tray");
});
startDesktopHealthIndicator(() => currentMainWindow());
}).catch((error) => {
console.error("[desktop-main] app.whenReady failed", error);
});
app.on("activate", () => {
revealMainWindowSafely("activate");
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("before-quit", (event) => {
if (quitReleaseInFlight) {
return;
}
mainRendererContents.send("desktop:runtime-invalidated", event);
quitReleaseInFlight = true;
event.preventDefault();
void Promise.race([
releaseRuntimeSession(),
new Promise<void>((resolve) => setTimeout(resolve, 1500)),
]).finally(() => {
app.quit();
});
});
await ensureMainWindow();
initTray(() => {
revealMainWindowSafely("tray");
});
startDesktopHealthIndicator(() => currentMainWindow());
}).catch((error) => {
console.error("[desktop-main] app.whenReady failed", error);
});
app.on("activate", () => {
revealMainWindowSafely("activate");
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("before-quit", (event) => {
if (quitReleaseInFlight) {
return;
}
quitReleaseInFlight = true;
event.preventDefault();
void Promise.race([
releaseRuntimeSession(),
new Promise<void>((resolve) => setTimeout(resolve, 1500)),
]).finally(() => {
app.quit();
});
});
}