Files
cc-switch/src/contexts/UpdateContext.tsx

156 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}