2025-09-10 19:46:38 +08:00
|
|
|
|
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from "react";
|
|
|
|
|
|
import type { UpdateInfo, UpdateHandle } from "../lib/updater";
|
|
|
|
|
|
import { checkForUpdate } from "../lib/updater";
|
|
|
|
|
|
|
|
|
|
|
|
interface UpdateContextValue {
|
|
|
|
|
|
// 更新状态
|
|
|
|
|
|
hasUpdate: boolean;
|
|
|
|
|
|
updateInfo: UpdateInfo | null;
|
|
|
|
|
|
updateHandle: UpdateHandle | null;
|
|
|
|
|
|
isChecking: boolean;
|
|
|
|
|
|
error: string | null;
|
|
|
|
|
|
|
|
|
|
|
|
// 提示状态
|
|
|
|
|
|
isDismissed: boolean;
|
|
|
|
|
|
dismissUpdate: () => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 操作方法
|
2025-09-11 15:13:33 +08:00
|
|
|
|
checkUpdate: () => Promise<boolean>;
|
2025-09-10 19:46:38 +08:00
|
|
|
|
resetDismiss: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const UpdateContext = createContext<UpdateContextValue | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
|
|
export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
|
|
|
|
|
const DISMISSED_VERSION_KEY = "ccswitch:update:dismissedVersion";
|
|
|
|
|
|
const LEGACY_DISMISSED_KEY = "dismissedUpdateVersion"; // 兼容旧键
|
|
|
|
|
|
|
|
|
|
|
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
|
|
|
|
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
|
|
|
|
|
const [updateHandle, setUpdateHandle] = useState<UpdateHandle | null>(null);
|
|
|
|
|
|
const [isChecking, setIsChecking] = useState(false);
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
|
const [isDismissed, setIsDismissed] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 从 localStorage 读取已关闭的版本
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const current = updateInfo?.availableVersion;
|
|
|
|
|
|
if (!current) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 读取新键;若不存在,尝试迁移旧键
|
|
|
|
|
|
let dismissedVersion = localStorage.getItem(DISMISSED_VERSION_KEY);
|
|
|
|
|
|
if (!dismissedVersion) {
|
|
|
|
|
|
const legacy = localStorage.getItem(LEGACY_DISMISSED_KEY);
|
|
|
|
|
|
if (legacy) {
|
|
|
|
|
|
localStorage.setItem(DISMISSED_VERSION_KEY, legacy);
|
|
|
|
|
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
|
|
|
|
|
dismissedVersion = legacy;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setIsDismissed(dismissedVersion === current);
|
|
|
|
|
|
}, [updateInfo?.availableVersion]);
|
|
|
|
|
|
|
|
|
|
|
|
const isCheckingRef = useRef(false);
|
|
|
|
|
|
|
|
|
|
|
|
const checkUpdate = useCallback(async () => {
|
2025-09-11 15:13:33 +08:00
|
|
|
|
if (isCheckingRef.current) return false;
|
2025-09-10 19:46:38 +08:00
|
|
|
|
isCheckingRef.current = true;
|
|
|
|
|
|
setIsChecking(true);
|
|
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await checkForUpdate({ timeout: 30000 });
|
|
|
|
|
|
|
|
|
|
|
|
if (result.status === "available") {
|
|
|
|
|
|
setHasUpdate(true);
|
|
|
|
|
|
setUpdateInfo(result.info);
|
|
|
|
|
|
setUpdateHandle(result.update);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已经关闭过这个版本的提醒
|
|
|
|
|
|
let dismissedVersion = localStorage.getItem(DISMISSED_VERSION_KEY);
|
|
|
|
|
|
if (!dismissedVersion) {
|
|
|
|
|
|
const legacy = localStorage.getItem(LEGACY_DISMISSED_KEY);
|
|
|
|
|
|
if (legacy) {
|
|
|
|
|
|
localStorage.setItem(DISMISSED_VERSION_KEY, legacy);
|
|
|
|
|
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
|
|
|
|
|
dismissedVersion = legacy;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setIsDismissed(dismissedVersion === result.info.availableVersion);
|
2025-09-11 15:13:33 +08:00
|
|
|
|
return true; // 有更新
|
2025-09-10 19:46:38 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
setHasUpdate(false);
|
|
|
|
|
|
setUpdateInfo(null);
|
|
|
|
|
|
setUpdateHandle(null);
|
|
|
|
|
|
setIsDismissed(false);
|
2025-09-11 15:13:33 +08:00
|
|
|
|
return false; // 已是最新
|
2025-09-10 19:46:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error("检查更新失败:", err);
|
|
|
|
|
|
setError(err instanceof Error ? err.message : "检查更新失败");
|
|
|
|
|
|
setHasUpdate(false);
|
2025-09-11 15:13:33 +08:00
|
|
|
|
throw err; // 抛出错误让调用方处理
|
2025-09-10 19:46:38 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
setIsChecking(false);
|
|
|
|
|
|
isCheckingRef.current = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const dismissUpdate = useCallback(() => {
|
|
|
|
|
|
setIsDismissed(true);
|
|
|
|
|
|
if (updateInfo?.availableVersion) {
|
|
|
|
|
|
localStorage.setItem(DISMISSED_VERSION_KEY, updateInfo.availableVersion);
|
|
|
|
|
|
// 清理旧键
|
|
|
|
|
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [updateInfo?.availableVersion]);
|
|
|
|
|
|
|
|
|
|
|
|
const resetDismiss = useCallback(() => {
|
|
|
|
|
|
setIsDismissed(false);
|
|
|
|
|
|
localStorage.removeItem(DISMISSED_VERSION_KEY);
|
|
|
|
|
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
// 应用启动时自动检查更新
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// 延迟1秒后检查,避免影响启动体验
|
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
|
checkUpdate().catch(console.error);
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
|
|
}, [checkUpdate]);
|
|
|
|
|
|
|
|
|
|
|
|
const value: UpdateContextValue = {
|
|
|
|
|
|
hasUpdate,
|
|
|
|
|
|
updateInfo,
|
|
|
|
|
|
updateHandle,
|
|
|
|
|
|
isChecking,
|
|
|
|
|
|
error,
|
|
|
|
|
|
isDismissed,
|
|
|
|
|
|
dismissUpdate,
|
|
|
|
|
|
checkUpdate,
|
|
|
|
|
|
resetDismiss,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<UpdateContext.Provider value={value}>
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</UpdateContext.Provider>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function useUpdate() {
|
|
|
|
|
|
const context = useContext(UpdateContext);
|
|
|
|
|
|
if (!context) {
|
|
|
|
|
|
throw new Error("useUpdate must be used within UpdateProvider");
|
|
|
|
|
|
}
|
|
|
|
|
|
return context;
|
|
|
|
|
|
}
|