mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
fix: address review findings in updater flow (#114)
- Add console.error logging to startup update check catch block - Add state guard to downloadUpdate() to prevent calls outside 'available' state - Track and dismiss 'available' toast when download starts or completes - Include version number in downloading status text Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6650289dcb
commit
149acf26bb
+21
-3
@@ -9,6 +9,7 @@ let currentStatus: UpdateStatus = { state: 'idle' }
|
||||
let userInitiatedCheck = false
|
||||
let onBeforeQuitCleanup: (() => void) | null = null
|
||||
let autoUpdaterInitialized = false
|
||||
let availableVersion: string | null = null
|
||||
|
||||
function sendStatus(status: UpdateStatus): void {
|
||||
currentStatus = status
|
||||
@@ -79,7 +80,7 @@ export function setupAutoUpdater(
|
||||
return
|
||||
}
|
||||
|
||||
autoUpdater.autoDownload = true
|
||||
autoUpdater.autoDownload = false
|
||||
autoUpdater.autoInstallOnAppQuit = true
|
||||
// Use allowPrerelease to bypass broken /releases/latest endpoint (returns 406)
|
||||
// and instead parse the version directly from the atom feed which works reliably.
|
||||
@@ -105,6 +106,7 @@ export function setupAutoUpdater(
|
||||
sendStatus({ state: 'not-available', userInitiated: wasUserInitiated || undefined })
|
||||
return
|
||||
}
|
||||
availableVersion = info.version
|
||||
sendStatus({ state: 'available', version: info.version })
|
||||
})
|
||||
|
||||
@@ -115,7 +117,11 @@ export function setupAutoUpdater(
|
||||
})
|
||||
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
sendStatus({ state: 'downloading', percent: Math.round(progress.percent) })
|
||||
sendStatus({
|
||||
state: 'downloading',
|
||||
percent: Math.round(progress.percent),
|
||||
version: availableVersion ?? ''
|
||||
})
|
||||
})
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
@@ -137,5 +143,17 @@ export function setupAutoUpdater(
|
||||
})
|
||||
})
|
||||
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
autoUpdater.checkForUpdates().catch((err) => {
|
||||
// Startup check — don't bother the user, but log for diagnostics
|
||||
console.error('[updater] startup check failed:', err?.message ?? err)
|
||||
})
|
||||
}
|
||||
|
||||
export function downloadUpdate(): void {
|
||||
if (currentStatus.state !== 'available') {
|
||||
return
|
||||
}
|
||||
autoUpdater.downloadUpdate().catch((err) => {
|
||||
sendStatus({ state: 'error', message: String(err?.message ?? err) })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@ import type { Store } from '../persistence'
|
||||
import { registerRepoHandlers } from '../ipc/repos'
|
||||
import { registerWorktreeHandlers } from '../ipc/worktrees'
|
||||
import { registerPtyHandlers } from '../ipc/pty'
|
||||
import { checkForUpdates, getUpdateStatus, quitAndInstall, setupAutoUpdater } from '../updater'
|
||||
import {
|
||||
checkForUpdates,
|
||||
downloadUpdate,
|
||||
getUpdateStatus,
|
||||
quitAndInstall,
|
||||
setupAutoUpdater
|
||||
} from '../updater'
|
||||
|
||||
export function attachMainWindowServices(mainWindow: BrowserWindow, store: Store): void {
|
||||
registerRepoHandlers(mainWindow, store)
|
||||
@@ -46,10 +52,12 @@ export function registerUpdaterHandlers(): void {
|
||||
ipcMain.removeHandler('updater:getStatus')
|
||||
ipcMain.removeHandler('updater:getVersion')
|
||||
ipcMain.removeHandler('updater:check')
|
||||
ipcMain.removeHandler('updater:download')
|
||||
ipcMain.removeHandler('updater:quitAndInstall')
|
||||
|
||||
ipcMain.handle('updater:getStatus', () => getUpdateStatus())
|
||||
ipcMain.handle('updater:getVersion', () => app.getVersion())
|
||||
ipcMain.handle('updater:check', () => checkForUpdates())
|
||||
ipcMain.handle('updater:download', () => downloadUpdate())
|
||||
ipcMain.handle('updater:quitAndInstall', () => quitAndInstall())
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -99,6 +99,7 @@ type UpdaterApi = {
|
||||
getVersion: () => Promise<string>
|
||||
getStatus: () => Promise<UpdateStatus>
|
||||
check: () => Promise<void>
|
||||
download: () => Promise<void>
|
||||
quitAndInstall: () => Promise<void>
|
||||
onStatus: (callback: (status: UpdateStatus) => void) => () => void
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ const api = {
|
||||
getStatus: (): Promise<unknown> => ipcRenderer.invoke('updater:getStatus'),
|
||||
getVersion: (): Promise<string> => ipcRenderer.invoke('updater:getVersion'),
|
||||
check: (): Promise<void> => ipcRenderer.invoke('updater:check'),
|
||||
download: (): Promise<void> => ipcRenderer.invoke('updater:download'),
|
||||
quitAndInstall: (): Promise<void> => ipcRenderer.invoke('updater:quitAndInstall'),
|
||||
onStatus: (callback: (status: unknown) => void): (() => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, status: unknown) => callback(status)
|
||||
|
||||
@@ -161,7 +161,17 @@ export function GeneralPane({
|
||||
Check for Updates
|
||||
</Button>
|
||||
|
||||
{updateStatus.state === 'downloaded' ? (
|
||||
{updateStatus.state === 'available' ? (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={() => window.api.updater.download()}
|
||||
className="gap-2"
|
||||
>
|
||||
<Download className="size-3.5" />
|
||||
Install Update ({updateStatus.version})
|
||||
</Button>
|
||||
) : updateStatus.state === 'downloaded' ? (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
@@ -178,9 +188,10 @@ export function GeneralPane({
|
||||
{updateStatus.state === 'idle' && 'Updates are checked automatically on launch.'}
|
||||
{updateStatus.state === 'checking' && 'Checking for updates...'}
|
||||
{updateStatus.state === 'available' &&
|
||||
`Version ${updateStatus.version} is available. Downloading...`}
|
||||
`Version ${updateStatus.version} is available. Click "Install Update" to download.`}
|
||||
{updateStatus.state === 'not-available' && 'You\u2019re on the latest version.'}
|
||||
{updateStatus.state === 'downloading' && `Downloading update... ${updateStatus.percent}%`}
|
||||
{updateStatus.state === 'downloading' &&
|
||||
`Downloading v${updateStatus.version}... ${updateStatus.percent}%`}
|
||||
{updateStatus.state === 'downloaded' &&
|
||||
`Version ${updateStatus.version} is ready to install.`}
|
||||
{updateStatus.state === 'error' && `Update error: ${updateStatus.message}`}
|
||||
|
||||
@@ -13,7 +13,9 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
|
||||
const updateVersion = 'version' in updateStatus ? updateStatus.version : null
|
||||
const showUpdateBanner =
|
||||
(updateStatus.state === 'downloaded' || updateStatus.state === 'available') &&
|
||||
(updateStatus.state === 'downloaded' ||
|
||||
updateStatus.state === 'available' ||
|
||||
updateStatus.state === 'downloading') &&
|
||||
updateVersion !== dismissedUpdateVersion
|
||||
|
||||
return (
|
||||
@@ -21,17 +23,28 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
{showUpdateBanner && (
|
||||
<div className="flex items-center border-t border-sidebar-border bg-primary/10">
|
||||
<button
|
||||
onClick={() =>
|
||||
updateStatus.state === 'downloaded' ? window.api.updater.quitAndInstall() : undefined
|
||||
}
|
||||
className="flex items-center gap-1.5 flex-1 min-w-0 px-2 py-1.5 text-[11px] font-medium text-primary hover:bg-primary/15 transition-colors cursor-pointer"
|
||||
onClick={() => {
|
||||
if (updateStatus.state === 'downloaded') {
|
||||
window.api.updater.quitAndInstall()
|
||||
} else if (updateStatus.state === 'available') {
|
||||
window.api.updater.download()
|
||||
}
|
||||
}}
|
||||
disabled={updateStatus.state === 'downloading'}
|
||||
className="flex items-center gap-1.5 flex-1 min-w-0 px-2 py-1.5 text-[11px] font-medium text-primary hover:bg-primary/15 transition-colors cursor-pointer disabled:opacity-60 disabled:cursor-default"
|
||||
>
|
||||
<Download className="size-3.5 shrink-0" />
|
||||
{updateStatus.state === 'downloaded' ? (
|
||||
<span>Restart now (update)</span>
|
||||
) : updateStatus.state === 'downloading' ? (
|
||||
<span>
|
||||
Downloading <span className="font-semibold">v{updateVersion}</span>…{' '}
|
||||
{updateStatus.percent}%
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
Downloading <span className="font-semibold">v{updateStatus.version}</span>…
|
||||
Update <span className="font-semibold">v{updateStatus.version}</span> available —
|
||||
Install
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -34,6 +34,7 @@ export function useIpcEvents(): void {
|
||||
})
|
||||
|
||||
let checkingToastId: string | number | undefined
|
||||
let availableToastId: string | number | undefined
|
||||
unsubs.push(
|
||||
window.api.updater.onStatus((raw) => {
|
||||
const status = raw as UpdateStatus
|
||||
@@ -52,7 +53,23 @@ export function useIpcEvents(): void {
|
||||
toast.dismiss(checkingToastId)
|
||||
}
|
||||
checkingToastId = undefined
|
||||
availableToastId = toast.info(`Version ${status.version} is available.`, {
|
||||
duration: Infinity,
|
||||
action: {
|
||||
label: 'Install',
|
||||
onClick: () => window.api.updater.download()
|
||||
}
|
||||
})
|
||||
} else if (status.state === 'downloading') {
|
||||
if (availableToastId) {
|
||||
toast.dismiss(availableToastId)
|
||||
availableToastId = undefined
|
||||
}
|
||||
} else if (status.state === 'downloaded') {
|
||||
if (availableToastId) {
|
||||
toast.dismiss(availableToastId)
|
||||
availableToastId = undefined
|
||||
}
|
||||
toast.success(`Version ${status.version} is ready to install.`, {
|
||||
duration: Infinity,
|
||||
action: {
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ export type UpdateStatus =
|
||||
| { state: 'checking'; userInitiated?: boolean }
|
||||
| { state: 'available'; version: string }
|
||||
| { state: 'not-available'; userInitiated?: boolean }
|
||||
| { state: 'downloading'; percent: number }
|
||||
| { state: 'downloading'; percent: number; version: string }
|
||||
| { state: 'downloaded'; version: string }
|
||||
| { state: 'error'; message: string; userInitiated?: boolean }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user