feat(desktop): add app and local daemon update controls in Settings
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import {
|
||||
checkDesktopAppUpdate,
|
||||
installDesktopAppUpdate,
|
||||
shouldShowDesktopUpdateSection,
|
||||
type DesktopAppUpdateCheckResult,
|
||||
type DesktopAppUpdateInstallResult,
|
||||
} from '@/utils/desktop-updates'
|
||||
|
||||
export type DesktopAppUpdateStatus =
|
||||
| 'idle'
|
||||
| 'checking'
|
||||
| 'up-to-date'
|
||||
| 'available'
|
||||
| 'installing'
|
||||
| 'installed'
|
||||
| 'error'
|
||||
|
||||
export interface UseDesktopAppUpdaterReturn {
|
||||
isDesktop: boolean
|
||||
status: DesktopAppUpdateStatus
|
||||
statusText: string
|
||||
availableUpdate: DesktopAppUpdateCheckResult | null
|
||||
errorMessage: string | null
|
||||
lastCheckedAt: number | null
|
||||
isChecking: boolean
|
||||
isInstalling: boolean
|
||||
checkForUpdates: (options?: { silent?: boolean }) => Promise<DesktopAppUpdateCheckResult | null>
|
||||
installUpdate: () => Promise<DesktopAppUpdateInstallResult | null>
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error && typeof error.message === 'string') {
|
||||
return error.message
|
||||
}
|
||||
return String(error)
|
||||
}
|
||||
|
||||
function formatStatusText(input: {
|
||||
status: DesktopAppUpdateStatus
|
||||
availableUpdate: DesktopAppUpdateCheckResult | null
|
||||
installMessage: string | null
|
||||
}): string {
|
||||
const { status, availableUpdate, installMessage } = input
|
||||
|
||||
if (status === 'checking') {
|
||||
return 'Checking for app updates...'
|
||||
}
|
||||
|
||||
if (status === 'installing') {
|
||||
return 'Installing app update...'
|
||||
}
|
||||
|
||||
if (status === 'up-to-date') {
|
||||
return 'App is up to date.'
|
||||
}
|
||||
|
||||
if (status === 'available') {
|
||||
if (availableUpdate?.latestVersion) {
|
||||
return `Update available: ${availableUpdate.latestVersion}`
|
||||
}
|
||||
return 'An app update is available.'
|
||||
}
|
||||
|
||||
if (status === 'installed') {
|
||||
return installMessage ?? 'App update installed. Restart required.'
|
||||
}
|
||||
|
||||
if (status === 'error') {
|
||||
return 'Failed to update app.'
|
||||
}
|
||||
|
||||
return 'Update status has not been checked yet.'
|
||||
}
|
||||
|
||||
export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
||||
const isDesktop = shouldShowDesktopUpdateSection()
|
||||
const requestVersionRef = useRef(0)
|
||||
const [status, setStatus] = useState<DesktopAppUpdateStatus>('idle')
|
||||
const [availableUpdate, setAvailableUpdate] = useState<DesktopAppUpdateCheckResult | null>(null)
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||
const [installMessage, setInstallMessage] = useState<string | null>(null)
|
||||
const [lastCheckedAt, setLastCheckedAt] = useState<number | null>(null)
|
||||
|
||||
const checkForUpdates = useCallback(
|
||||
async (options: { silent?: boolean } = {}) => {
|
||||
if (!isDesktop) {
|
||||
return null
|
||||
}
|
||||
|
||||
const requestVersion = requestVersionRef.current + 1
|
||||
requestVersionRef.current = requestVersion
|
||||
|
||||
if (!options.silent) {
|
||||
setStatus('checking')
|
||||
}
|
||||
setErrorMessage(null)
|
||||
|
||||
try {
|
||||
const result = await checkDesktopAppUpdate()
|
||||
if (requestVersion !== requestVersionRef.current) {
|
||||
return result
|
||||
}
|
||||
|
||||
setInstallMessage(null)
|
||||
setLastCheckedAt(Date.now())
|
||||
|
||||
if (result.hasUpdate) {
|
||||
setAvailableUpdate(result)
|
||||
setStatus('available')
|
||||
} else {
|
||||
setAvailableUpdate(null)
|
||||
setStatus('up-to-date')
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
if (requestVersion !== requestVersionRef.current) {
|
||||
return null
|
||||
}
|
||||
|
||||
const message = getErrorMessage(error)
|
||||
if (options.silent) {
|
||||
console.warn('[DesktopUpdater] Silent update check failed', message)
|
||||
} else {
|
||||
setStatus('error')
|
||||
setErrorMessage(message)
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
[isDesktop]
|
||||
)
|
||||
|
||||
const installUpdate = useCallback(async () => {
|
||||
if (!isDesktop) {
|
||||
return null
|
||||
}
|
||||
|
||||
setStatus('installing')
|
||||
setErrorMessage(null)
|
||||
|
||||
try {
|
||||
const result = await installDesktopAppUpdate()
|
||||
setLastCheckedAt(Date.now())
|
||||
|
||||
if (result.installed) {
|
||||
setAvailableUpdate(null)
|
||||
setInstallMessage(result.message)
|
||||
setStatus('installed')
|
||||
} else {
|
||||
setAvailableUpdate(null)
|
||||
setInstallMessage(result.message)
|
||||
setStatus('up-to-date')
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error)
|
||||
setStatus('error')
|
||||
setErrorMessage(message)
|
||||
return null
|
||||
}
|
||||
}, [isDesktop])
|
||||
|
||||
return {
|
||||
isDesktop,
|
||||
status,
|
||||
statusText: formatStatusText({
|
||||
status,
|
||||
availableUpdate,
|
||||
installMessage,
|
||||
}),
|
||||
availableUpdate,
|
||||
errorMessage,
|
||||
lastCheckedAt,
|
||||
isChecking: status === 'checking',
|
||||
isInstalling: status === 'installing',
|
||||
checkForUpdates,
|
||||
installUpdate,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user