Files
orca/src/main/window/main-window-updater.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

136 lines
5.5 KiB
TypeScript

import { app, ipcMain } from 'electron'
import type { BrowserWindow, IpcMainInvokeEvent } from 'electron'
import type { ReleaseBuildListResult, UpdateCheckOptions } from '../../shared/update-status-types'
import { RELEASE_CHANNELS, type ReleaseChannel } from '../../shared/release-channel'
import { isTrustedUIRenderer } from '../ipc/ui'
import type { Store } from '../persistence'
import { logStartupMilestone } from '../startup/startup-diagnostics'
import {
checkForUpdatesFromMenu,
dismissAvailableUpdate,
dismissNudge,
downloadUpdate,
getLinuxPackageInstallInstructions,
getUpdateStatus,
listAvailableReleaseBuilds,
quitAndInstall,
setupAutoUpdater,
showLinuxPackage,
type UpdateInstallMode
} from '../updater'
const UPDATER_SETUP_FALLBACK_MS = 15_000
// Why: a manual check can arrive before deferred setup runs, so entry points force this pending setup to configure the updater first.
let pendingAutoUpdaterSetup: (() => void) | null = null
export function ensureAutoUpdaterConfigured(): void {
pendingAutoUpdaterSetup?.()
}
export function scheduleMainWindowAutoUpdaterSetup(
mainWindow: BrowserWindow,
store: Store,
options?: {
onBeforeUpdateQuit?: () => void | Promise<void>
updateInstallMode?: UpdateInstallMode
}
): void {
// Why: setupAutoUpdater sync-require()s electron-updater (slow on cold Windows w/ Defender, #7225), so defer past first paint; timer fallback covers crash-looping renderers.
let updaterSetupDone = false
const setupAutoUpdaterDeferred = (): void => {
if (updaterSetupDone || mainWindow.isDestroyed()) {
return
}
updaterSetupDone = true
setupAutoUpdater(mainWindow, {
getLastUpdateCheckAt: () => store.getUI().lastUpdateCheckAt,
onBeforeQuit: async () => {
try {
await options?.onBeforeUpdateQuit?.()
} finally {
await store.flushPendingAsync()
}
},
setLastUpdateCheckAt: (timestamp) => {
store.updateUI({ lastUpdateCheckAt: timestamp })
},
getPendingUpdateNudgeId: () => store.getUI().pendingUpdateNudgeId ?? null,
getDismissedUpdateNudgeId: () => store.getUI().dismissedUpdateNudgeId ?? null,
setPendingUpdateNudgeId: (id) => {
// Why: only the apply branch also nulls dismissedUpdateVersion so relaunch can't resurrect the old hidden card; clearing must not, or it un-dismisses.
if (id) {
store.updateUI({ pendingUpdateNudgeId: id, dismissedUpdateVersion: null })
} else {
store.updateUI({ pendingUpdateNudgeId: null })
}
},
setDismissedUpdateNudgeId: (id) => {
store.updateUI({ dismissedUpdateNudgeId: id })
},
getReleaseChannelOverride: () => store.getUI().releaseChannelOverride ?? null,
installMode: options?.updateInstallMode
})
logStartupMilestone('updater-setup-done')
}
pendingAutoUpdaterSetup = setupAutoUpdaterDeferred
mainWindow.once('ready-to-show', () => setImmediate(setupAutoUpdaterDeferred))
const updaterSetupFallback = setTimeout(setupAutoUpdaterDeferred, UPDATER_SETUP_FALLBACK_MS)
updaterSetupFallback.unref?.()
}
export function registerUpdaterHandlers(_store: Store): void {
ipcMain.removeHandler('updater:getStatus')
ipcMain.removeHandler('updater:getVersion')
ipcMain.removeHandler('updater:check')
ipcMain.removeHandler('updater:download')
ipcMain.removeHandler('updater:quitAndInstall')
ipcMain.removeHandler('updater:dismissNudge')
ipcMain.removeHandler('updater:dismissAvailableUpdate')
ipcMain.removeHandler('updater:getLinuxPackageInstallInstructions')
ipcMain.removeHandler('updater:showLinuxPackage')
ipcMain.removeHandler('updater:listBuilds')
ipcMain.handle('updater:getStatus', () => getUpdateStatus())
ipcMain.handle('updater:getVersion', () => app.getVersion())
ipcMain.handle('updater:check', (_event, options?: UpdateCheckOptions) => {
ensureAutoUpdaterConfigured()
return checkForUpdatesFromMenu(options)
})
ipcMain.handle('updater:download', () => downloadUpdate())
ipcMain.handle('updater:quitAndInstall', () => quitAndInstall())
ipcMain.handle('updater:dismissNudge', () => dismissNudge())
ipcMain.handle('updater:dismissAvailableUpdate', () => dismissAvailableUpdate())
// Why: the response carries a local package path and the reveal touches the native desktop, so
// neither may be reached from a guest, dashboard popout, stale window, or utility renderer.
ipcMain.handle('updater:getLinuxPackageInstallInstructions', (event) => {
assertTrustedUpdaterRecoverySender(event)
return getLinuxPackageInstallInstructions()
})
ipcMain.handle('updater:showLinuxPackage', (event) => {
assertTrustedUpdaterRecoverySender(event)
return showLinuxPackage()
})
ipcMain.handle(
'updater:listBuilds',
async (_event, channel: ReleaseChannel): Promise<ReleaseBuildListResult> => {
if (!RELEASE_CHANNELS.includes(channel)) {
return { ok: false, channel, message: `Unknown release channel "${channel}".` }
}
try {
return { ok: true, channel, builds: await listAvailableReleaseBuilds(channel) }
} catch (error) {
// Why: a network/rate-limit failure is expected here; return it as data so
// the picker can render the reason instead of rejecting the invoke.
return { ok: false, channel, message: String((error as Error)?.message ?? error) }
}
}
)
}
function assertTrustedUpdaterRecoverySender(event: IpcMainInvokeEvent): void {
if (!isTrustedUIRenderer(event.sender)) {
throw new Error('Unauthorized updater package recovery sender')
}
}