feat(desktop): add client release updater
Desktop Client Build / Resolve Build Metadata (push) Successful in 19s
Frontend CI / Frontend (push) Successful in 3m20s
Backend CI / Backend (push) Successful in 16m48s
Desktop Client Build / Build Desktop Client (push) Successful in 24m46s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 37s

This commit is contained in:
2026-05-25 19:23:49 +08:00
parent 41b4a765ac
commit 5f6e9f11da
46 changed files with 5508 additions and 160 deletions
+104 -9
View File
@@ -3,6 +3,7 @@ import { join } from 'node:path'
import type {
DesktopAccountInfo,
DesktopClientReleaseCheckResponse,
DesktopClientRotateResponse,
DesktopRuntimeSessionSyncRequest,
} from '@geo/shared-types'
@@ -31,23 +32,29 @@ import {
setDesktopAppSetting,
type DesktopAppSettingKey,
} from './app-settings'
import { resolveDesktopClientID, resolveDesktopDeviceInfo } from './device-id'
import { getDesktopAppVersion, getDesktopReleaseChannel } from './app-version'
import {
installObservedGlobalFetch,
registerObservedRequestRendererTarget,
} from './network-observer'
import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from './playwright-cdp'
defaultDesktopUpdateChannel,
startDesktopClientUpdate,
type DesktopClientUpdateProgressEvent,
} from './client-updater'
import { resolveDesktopClientID, resolveDesktopDeviceInfo } from './device-id'
import {
clearSavedLoginCredentials,
getSavedLoginCredentials,
saveLoginCredentials,
} from './login-credentials'
import {
installObservedGlobalFetch,
registerObservedRequestRendererTarget,
} from './network-observer'
import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from './playwright-cdp'
import { initProcessMetricsSampler } from './process-metrics'
import { registerRendererDevtoolsProxyTarget } from './renderer-devtools-proxy'
import {
getRuntimeControllerSnapshot,
noteRuntimeAccountBound,
refreshRuntimeAccounts,
getRuntimeControllerSnapshot,
releaseRuntimeSession,
requestRuntimeAccountProbe,
syncRuntimeSession,
@@ -59,8 +66,10 @@ import { initScheduler } from './scheduler'
import { initSessionRegistry } from './session-registry'
import { initSingleInstance } from './single-instance'
import {
checkDesktopClientRelease,
initTransport,
listDesktopPublishTasks,
resolveDesktopClientReleaseDownloadURL,
retryDesktopPublishTask,
rotateDesktopClient,
} from './transport/api-client'
@@ -772,15 +781,73 @@ function isSafeExternalUrl(url: string): boolean {
}
}
function desktopReleasePlatform(): string {
switch (process.platform) {
case 'darwin':
return 'darwin'
case 'win32':
return 'win32'
case 'linux':
return 'linux'
default:
return process.platform
}
}
function desktopReleaseArch(): string {
switch (process.arch) {
case 'x64':
case 'arm64':
case 'ia32':
return process.arch
default:
return 'universal'
}
}
async function checkCurrentDesktopClientRelease(): Promise<DesktopClientReleaseCheckResponse> {
const snapshot = getRuntimeControllerSnapshot()
if (!snapshot.session?.client_token) {
throw new Error('desktop_client_not_registered')
}
return checkDesktopClientRelease({
platform: desktopReleasePlatform(),
arch: desktopReleaseArch(),
version: getDesktopAppVersion(),
channel: snapshot.session.desktop_client?.channel || getDesktopReleaseChannel(),
})
}
async function resolveCurrentDesktopClientReleaseDownloadURL(): Promise<{ download_url: string }> {
const snapshot = getRuntimeControllerSnapshot()
if (!snapshot.session?.client_token) {
throw new Error('desktop_client_not_registered')
}
return resolveDesktopClientReleaseDownloadURL({
platform: desktopReleasePlatform(),
arch: desktopReleaseArch(),
version: getDesktopAppVersion(),
channel: snapshot.session.desktop_client?.channel || getDesktopReleaseChannel(),
})
}
function emitClientUpdateProgress(event: DesktopClientUpdateProgressEvent): void {
for (const window of BrowserWindow.getAllWindows()) {
if (!window.isDestroyed()) {
window.webContents.send('desktop:client-update-progress', event)
}
}
}
function registerDesktopProtocolClient(): void {
if (app.isDefaultProtocolClient(desktopDeepLinkScheme)) {
return
}
if (process.defaultApp && process.execPath) {
app.setAsDefaultProtocolClient(desktopDeepLinkScheme, process.execPath, [
process.argv[1] ?? '',
])
app.setAsDefaultProtocolClient(desktopDeepLinkScheme, process.execPath, [process.argv[1] ?? ''])
return
}
@@ -847,6 +914,34 @@ function handleDesktopDeepLink(rawUrl: string): void {
function registerBridgeHandlers(): void {
ipcMain.handle('desktop:ping', () => 'pong')
ipcMain.handle('desktop:version-info', () => ({
version: getDesktopAppVersion(),
channel: getDesktopReleaseChannel(),
}))
safeHandle('desktop:check-client-release', () => checkCurrentDesktopClientRelease())
safeHandle('desktop:client-release-download-url', () =>
resolveCurrentDesktopClientReleaseDownloadURL(),
)
safeHandle('desktop:quit-app', async () => {
app.quit()
return null
})
safeHandle('desktop:start-client-update', async () => {
const snapshot = getRuntimeControllerSnapshot()
if (!snapshot.session?.client_token) {
throw new Error('desktop_client_not_registered')
}
return startDesktopClientUpdate({
platform: desktopReleasePlatform(),
arch: desktopReleaseArch(),
channel: defaultDesktopUpdateChannel(snapshot.session.desktop_client?.channel),
notify: emitClientUpdateProgress,
beforeInstall: () => {
quitReleaseInFlight = true
},
})
})
ipcMain.handle('desktop:device-info', () => resolveDesktopDeviceInfo())
safeHandle(
'desktop:client-id',