diff --git a/apps/desktop-client/src/main/bootstrap.ts b/apps/desktop-client/src/main/bootstrap.ts index 5cbf27e..9a4c85f 100644 --- a/apps/desktop-client/src/main/bootstrap.ts +++ b/apps/desktop-client/src/main/bootstrap.ts @@ -1,3 +1,4 @@ +import { hostname } from "node:os"; import { join } from "node:path"; import { BrowserWindow, WebContentsView, app, ipcMain, nativeTheme } from "electron/main"; @@ -125,6 +126,11 @@ function safeHandle( 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:runtime-snapshot", () => createRuntimeSnapshot()); safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => { const account = (await bindPublishAccount(platformId)) as DesktopAccountInfo; diff --git a/apps/desktop-client/src/main/runtime-controller.ts b/apps/desktop-client/src/main/runtime-controller.ts index 7eab01f..85faaf5 100644 --- a/apps/desktop-client/src/main/runtime-controller.ts +++ b/apps/desktop-client/src/main/runtime-controller.ts @@ -1,3 +1,5 @@ +import { hostname } from "node:os"; + import { ApiClientError } from "@geo/http-client"; import type { DesktopArticleContent, @@ -489,9 +491,9 @@ async function sendHeartbeat(source: "startup" | "timer"): Promise { try { const response = await heartbeatDesktopClient({ - device_name: state.client?.device_name ?? `Desktop ${process.platform}`, - os: state.client?.os ?? process.platform, - cpu_arch: state.client?.cpu_arch ?? process.arch, + device_name: hostname() || state.client?.device_name || `Desktop ${process.platform}`, + os: process.platform, + cpu_arch: process.arch, client_version: state.client?.client_version ?? "0.1.0-dev", channel: state.client?.channel ?? "dev", account_ids: state.accounts.filter((account) => account.health === "live").map((account) => account.id), diff --git a/apps/desktop-client/src/preload/bridge.ts b/apps/desktop-client/src/preload/bridge.ts index eb8f9bc..2fec5d1 100644 --- a/apps/desktop-client/src/preload/bridge.ts +++ b/apps/desktop-client/src/preload/bridge.ts @@ -8,9 +8,16 @@ import type { } from "@geo/shared-types"; import type { DesktopRuntimeSnapshot } from "../renderer/types"; +interface DesktopDeviceInfo { + device_name: string; + os: string; + cpu_arch: string; +} + const desktopBridge = { app: { ping: () => ipcRenderer.invoke("desktop:ping") as Promise, + deviceInfo: () => ipcRenderer.invoke("desktop:device-info") as Promise, runtimeSnapshot: () => ipcRenderer.invoke("desktop:runtime-snapshot") as Promise, bindPublishAccount: (platformId: string) => diff --git a/apps/desktop-client/src/renderer/composables/useDesktopSession.ts b/apps/desktop-client/src/renderer/composables/useDesktopSession.ts index a0c2b5f..0eef764 100644 --- a/apps/desktop-client/src/renderer/composables/useDesktopSession.ts +++ b/apps/desktop-client/src/renderer/composables/useDesktopSession.ts @@ -167,12 +167,32 @@ function getOrCreateClientRegistrationID(user: UserInfo): string { return generated; } -function registerPayload(user: UserInfo): DesktopClientRegisterRequest { - return { - client_id: getOrCreateClientRegistrationID(user), +async function resolveDeviceInfo(): Promise<{ device_name: string; os: string; cpu_arch: string }> { + const fallback = { device_name: `Desktop ${navigator.platform || "unknown"}`, os: navigator.platform || "unknown", - cpu_arch: "renderer-preview", + cpu_arch: "unknown", + }; + + try { + const info = await window.desktopBridge?.app?.deviceInfo?.(); + if (info?.device_name && info.os && info.cpu_arch) { + return info; + } + } catch (err) { + console.warn("[desktop-session] deviceInfo bridge failed", err); + } + + return fallback; +} + +async function registerPayload(user: UserInfo): Promise { + const device = await resolveDeviceInfo(); + return { + client_id: getOrCreateClientRegistrationID(user), + device_name: device.device_name, + os: device.os, + cpu_arch: device.cpu_arch, client_version: "0.1.0-dev", channel: "dev", }; @@ -193,16 +213,17 @@ function createPreviewUser(): UserInfo { }; } -function createPreviewClient(): DesktopClientInfo { +async function createPreviewClient(): Promise { const now = new Date().toISOString(); + const device = await resolveDeviceInfo(); return { id: "preview-client", tenant_id: 0, workspace_id: 0, user_id: 0, - device_name: `Preview ${navigator.platform || "Desktop"}`, - os: navigator.platform || "browser", - cpu_arch: "renderer-preview", + device_name: `Preview ${device.device_name}`, + os: device.os, + cpu_arch: device.cpu_arch, client_version: "0.1.0-preview", channel: "dev", created_at: now, @@ -260,7 +281,7 @@ async function login(payload: LoginRequest): Promise { const registered = await createAuthenticatedClient().post< DesktopClientRegisterResponse, DesktopClientRegisterRequest - >("/api/desktop/clients/register", registerPayload(authData.user)); + >("/api/desktop/clients/register", await registerPayload(authData.user)); persistClientRegistrationID(authData.user, registered.client.id); @@ -282,7 +303,7 @@ async function login(payload: LoginRequest): Promise { } } -function enterPreview(): void { +async function enterPreview(): Promise { error.value = null; persistSession({ mode: "preview", @@ -291,7 +312,7 @@ function enterPreview(): void { user: createPreviewUser(), clientToken: "preview-client-token", clientTokenExpiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, - desktopClient: createPreviewClient(), + desktopClient: await createPreviewClient(), }); } diff --git a/apps/desktop-client/src/renderer/env.d.ts b/apps/desktop-client/src/renderer/env.d.ts index a76b3a9..3aa4c20 100644 --- a/apps/desktop-client/src/renderer/env.d.ts +++ b/apps/desktop-client/src/renderer/env.d.ts @@ -14,6 +14,7 @@ declare global { desktopBridge: { app: { ping(): Promise; + deviceInfo(): Promise<{ device_name: string; os: string; cpu_arch: string }>; runtimeSnapshot(): Promise; bindPublishAccount(platformId: string): Promise; refreshRuntimeAccounts(): Promise;