156 lines
4.5 KiB
TypeScript
156 lines
4.5 KiB
TypeScript
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;
|
||
|
||
// 操作方法
|
||
checkUpdate: () => Promise<boolean>;
|
||
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 () => {
|
||
if (isCheckingRef.current) return false;
|
||
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);
|
||
return true; // 有更新
|
||
} else {
|
||
setHasUpdate(false);
|
||
setUpdateInfo(null);
|
||
setUpdateHandle(null);
|
||
setIsDismissed(false);
|
||
return false; // 已是最新
|
||
}
|
||
} catch (err) {
|
||
console.error("检查更新失败:", err);
|
||
setError(err instanceof Error ? err.message : "检查更新失败");
|
||
setHasUpdate(false);
|
||
throw err; // 抛出错误让调用方处理
|
||
} 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;
|
||
}
|