优化更新失败时的提示语
修复检查更新按钮点击后有重影
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
|||||||
closeAppUpdate,
|
closeAppUpdate,
|
||||||
getCurrentAppVersion,
|
getCurrentAppVersion,
|
||||||
installAppUpdate,
|
installAppUpdate,
|
||||||
|
normalizeAppUpdateError,
|
||||||
relaunchApp,
|
relaunchApp,
|
||||||
} from "@/lib/updater"
|
} from "@/lib/updater"
|
||||||
|
|
||||||
@@ -45,6 +46,8 @@ function isAppLocale(value: string): value is AppLocale {
|
|||||||
return APP_LANGUAGE_VALUES.includes(value as AppLocale)
|
return APP_LANGUAGE_VALUES.includes(value as AppLocale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateAction = "check" | "install"
|
||||||
|
|
||||||
export function SystemNetworkSettings() {
|
export function SystemNetworkSettings() {
|
||||||
const t = useTranslations("SystemSettings")
|
const t = useTranslations("SystemSettings")
|
||||||
const tLanguage = useTranslations("Language")
|
const tLanguage = useTranslations("Language")
|
||||||
@@ -172,6 +175,31 @@ export function SystemNetworkSettings() {
|
|||||||
}
|
}
|
||||||
}, [appLanguage, languageSettings.language, setLanguageSettings, t])
|
}, [appLanguage, languageSettings.language, setLanguageSettings, t])
|
||||||
|
|
||||||
|
const formatUpdateError = useCallback(
|
||||||
|
(error: unknown, action: UpdateAction): string => {
|
||||||
|
const { kind, rawMessage } = normalizeAppUpdateError(error)
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
case "source_unreachable":
|
||||||
|
return t("updateErrors.sourceUnavailable")
|
||||||
|
case "network":
|
||||||
|
return t("updateErrors.network")
|
||||||
|
case "download_failed":
|
||||||
|
return t("updateErrors.downloadFailed")
|
||||||
|
case "install_failed":
|
||||||
|
return t("updateErrors.installFailed")
|
||||||
|
case "unknown":
|
||||||
|
default:
|
||||||
|
if (action === "install") {
|
||||||
|
return t("updateErrors.installFailed")
|
||||||
|
}
|
||||||
|
console.error("[Settings] updater unknown error:", rawMessage)
|
||||||
|
return t("updateErrors.unknown")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
|
||||||
const checkForUpdates = useCallback(async () => {
|
const checkForUpdates = useCallback(async () => {
|
||||||
setCheckingUpdate(true)
|
setCheckingUpdate(true)
|
||||||
setUpdateError(null)
|
setUpdateError(null)
|
||||||
@@ -194,13 +222,14 @@ export function SystemNetworkSettings() {
|
|||||||
await closeAppUpdate(previousUpdate)
|
await closeAppUpdate(previousUpdate)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = formatUpdateError(err, "check")
|
||||||
setUpdateError(message)
|
setUpdateError(message)
|
||||||
toast.error(t("checkUpdateFailed", { message }))
|
toast.error(t("checkUpdateFailed", { message }))
|
||||||
|
console.error("[Settings] check app update failed:", err)
|
||||||
} finally {
|
} finally {
|
||||||
setCheckingUpdate(false)
|
setCheckingUpdate(false)
|
||||||
}
|
}
|
||||||
}, [availableUpdate, t])
|
}, [availableUpdate, formatUpdateError, t])
|
||||||
|
|
||||||
const installUpdate = useCallback(async () => {
|
const installUpdate = useCallback(async () => {
|
||||||
if (!availableUpdate) return
|
if (!availableUpdate) return
|
||||||
@@ -213,13 +242,14 @@ export function SystemNetworkSettings() {
|
|||||||
toast.success(t("installSuccess"))
|
toast.success(t("installSuccess"))
|
||||||
await relaunchApp()
|
await relaunchApp()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = formatUpdateError(err, "install")
|
||||||
setUpdateError(message)
|
setUpdateError(message)
|
||||||
toast.error(t("installFailed", { message }))
|
toast.error(t("installFailed", { message }))
|
||||||
|
console.error("[Settings] install app update failed:", err)
|
||||||
} finally {
|
} finally {
|
||||||
setInstallingUpdate(false)
|
setInstallingUpdate(false)
|
||||||
}
|
}
|
||||||
}, [availableUpdate, t])
|
}, [availableUpdate, formatUpdateError, t])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -393,24 +423,31 @@ export function SystemNetworkSettings() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2 justify-end">
|
<div className="flex flex-wrap items-center gap-2 justify-end">
|
||||||
|
{checkingUpdate ? (
|
||||||
<Button
|
<Button
|
||||||
|
key="checking-update"
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
disabled
|
||||||
|
aria-busy="true"
|
||||||
|
className="w-[9.5rem] justify-center transition-none"
|
||||||
|
>
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
{t("checking")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
key="check-update"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={checkForUpdates}
|
onClick={checkForUpdates}
|
||||||
disabled={checkingUpdate || installingUpdate}
|
disabled={installingUpdate}
|
||||||
|
className="w-[9.5rem] justify-center transition-none"
|
||||||
>
|
>
|
||||||
{checkingUpdate ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
||||||
{t("checking")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="h-3.5 w-3.5" />
|
<RefreshCw className="h-3.5 w-3.5" />
|
||||||
{t("checkUpdate")}
|
{t("checkUpdate")}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{availableUpdate && (
|
{availableUpdate && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -112,7 +112,14 @@
|
|||||||
"alreadyLatest": "You're on the latest version",
|
"alreadyLatest": "You're on the latest version",
|
||||||
"checkUpdateFailed": "Failed to check for updates: {message}",
|
"checkUpdateFailed": "Failed to check for updates: {message}",
|
||||||
"installSuccess": "Update installed. Relaunching app.",
|
"installSuccess": "Update installed. Relaunching app.",
|
||||||
"installFailed": "Update failed: {message}"
|
"installFailed": "Update failed: {message}",
|
||||||
|
"updateErrors": {
|
||||||
|
"sourceUnavailable": "Cannot reach the update source. Check your network or proxy and try again.",
|
||||||
|
"network": "Network connection failed. Check your network or proxy and try again.",
|
||||||
|
"downloadFailed": "Failed to download update package. Please try again later.",
|
||||||
|
"installFailed": "Failed to install update. Please close the app and try again.",
|
||||||
|
"unknown": "Update failed. Please try again later."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "Shortcuts",
|
"sectionTitle": "Shortcuts",
|
||||||
|
|||||||
@@ -112,7 +112,14 @@
|
|||||||
"alreadyLatest": "当前已经是最新版本",
|
"alreadyLatest": "当前已经是最新版本",
|
||||||
"checkUpdateFailed": "检查更新失败:{message}",
|
"checkUpdateFailed": "检查更新失败:{message}",
|
||||||
"installSuccess": "升级包已安装,正在重启应用",
|
"installSuccess": "升级包已安装,正在重启应用",
|
||||||
"installFailed": "升级失败:{message}"
|
"installFailed": "升级失败:{message}",
|
||||||
|
"updateErrors": {
|
||||||
|
"sourceUnavailable": "无法连接更新源,请检查网络或代理设置后重试。",
|
||||||
|
"network": "网络连接异常,请检查网络或代理设置后重试。",
|
||||||
|
"downloadFailed": "下载更新包失败,请稍后重试。",
|
||||||
|
"installFailed": "安装更新失败,请关闭应用后重试。",
|
||||||
|
"unknown": "更新失败,请稍后重试。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "快捷键",
|
"sectionTitle": "快捷键",
|
||||||
|
|||||||
@@ -112,7 +112,14 @@
|
|||||||
"alreadyLatest": "目前已是最新版本",
|
"alreadyLatest": "目前已是最新版本",
|
||||||
"checkUpdateFailed": "檢查更新失敗:{message}",
|
"checkUpdateFailed": "檢查更新失敗:{message}",
|
||||||
"installSuccess": "升級包已安裝,正在重新啟動應用",
|
"installSuccess": "升級包已安裝,正在重新啟動應用",
|
||||||
"installFailed": "升級失敗:{message}"
|
"installFailed": "升級失敗:{message}",
|
||||||
|
"updateErrors": {
|
||||||
|
"sourceUnavailable": "無法連線更新來源,請檢查網路或代理設定後重試。",
|
||||||
|
"network": "網路連線異常,請檢查網路或代理設定後重試。",
|
||||||
|
"downloadFailed": "下載更新包失敗,請稍後再試。",
|
||||||
|
"installFailed": "安裝更新失敗,請關閉應用後重試。",
|
||||||
|
"unknown": "更新失敗,請稍後再試。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "快捷鍵",
|
"sectionTitle": "快捷鍵",
|
||||||
|
|||||||
@@ -7,6 +7,18 @@ export interface AppUpdateCheckResult {
|
|||||||
update: Update | null
|
update: Update | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AppUpdateErrorKind =
|
||||||
|
| "source_unreachable"
|
||||||
|
| "network"
|
||||||
|
| "download_failed"
|
||||||
|
| "install_failed"
|
||||||
|
| "unknown"
|
||||||
|
|
||||||
|
export interface AppUpdateErrorInfo {
|
||||||
|
kind: AppUpdateErrorKind
|
||||||
|
rawMessage: string
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCurrentAppVersion(): Promise<string> {
|
export async function getCurrentAppVersion(): Promise<string> {
|
||||||
return getVersion()
|
return getVersion()
|
||||||
}
|
}
|
||||||
@@ -27,3 +39,49 @@ export async function relaunchApp(): Promise<void> {
|
|||||||
export async function closeAppUpdate(update: Update): Promise<void> {
|
export async function closeAppUpdate(update: Update): Promise<void> {
|
||||||
await update.close()
|
await update.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toErrorMessage(error: unknown): string {
|
||||||
|
if (error instanceof Error) return error.message
|
||||||
|
return String(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeAppUpdateError(error: unknown): AppUpdateErrorInfo {
|
||||||
|
const rawMessage = toErrorMessage(error)
|
||||||
|
const normalized = rawMessage.toLowerCase()
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalized.includes("latest.json") ||
|
||||||
|
normalized.includes("/releases/latest/download/")
|
||||||
|
) {
|
||||||
|
return { kind: "source_unreachable", rawMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalized.includes("error sending request for url") ||
|
||||||
|
normalized.includes("failed to send request") ||
|
||||||
|
normalized.includes("network") ||
|
||||||
|
normalized.includes("timed out") ||
|
||||||
|
normalized.includes("dns") ||
|
||||||
|
normalized.includes("connection refused")
|
||||||
|
) {
|
||||||
|
return { kind: "network", rawMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalized.includes("download") ||
|
||||||
|
normalized.includes("checksum") ||
|
||||||
|
normalized.includes("content-length")
|
||||||
|
) {
|
||||||
|
return { kind: "download_failed", rawMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
normalized.includes("install") ||
|
||||||
|
normalized.includes("installer") ||
|
||||||
|
normalized.includes("permission denied")
|
||||||
|
) {
|
||||||
|
return { kind: "install_failed", rawMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { kind: "unknown", rawMessage }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user