Files
cunkebao_v3/nkebao/src/utils/updateChecker.ts

218 lines
5.7 KiB
TypeScript
Raw Normal View History

/**
*
*/
interface UpdateInfo {
hasUpdate: boolean;
version?: string;
timestamp?: number;
}
class UpdateChecker {
private currentVersion: string;
private checkInterval: number = 1000; // 1秒检查一次用于测试
private intervalId: NodeJS.Timeout | null = null;
private updateCallbacks: ((info: UpdateInfo) => void)[] = [];
private currentHashes: string[] = [];
constructor() {
// 从package.json获取版本号
this.currentVersion = import.meta.env.VITE_APP_VERSION || "1.0.0";
// 初始化当前哈希值
this.initCurrentHashes();
}
/**
*
*/
private initCurrentHashes() {
// 从当前页面的资源中提取哈希值
const scripts = document.querySelectorAll("script[src]");
const links = document.querySelectorAll("link[href]");
const scriptHashes = Array.from(scripts)
.map(script => script.getAttribute("src"))
.filter(
src => src && (src.includes("assets/") || src.includes("/assets/")),
)
.map(src => {
// 修改正则表达式,匹配包含字母、数字和下划线的哈希值
const match = src?.match(/[a-zA-Z0-9_-]{8,}/);
return match ? match[0] : "";
})
.filter(hash => hash);
const linkHashes = Array.from(links)
.map(link => link.getAttribute("href"))
.filter(
href => href && (href.includes("assets/") || href.includes("/assets/")),
)
.map(href => {
// 修改正则表达式,匹配包含字母、数字和下划线的哈希值
const match = href?.match(/[a-zA-Z0-9_-]{8,}/);
return match ? match[0] : "";
})
.filter(hash => hash);
this.currentHashes = [...new Set([...scriptHashes, ...linkHashes])];
}
/**
*
*/
start() {
if (this.intervalId) {
return;
}
// 立即检查一次
this.checkForUpdate();
// 设置定时检查
this.intervalId = setInterval(() => {
this.checkForUpdate();
}, this.checkInterval);
}
/**
*
*/
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
/**
*
*/
async checkForUpdate(): Promise<UpdateInfo> {
try {
// 获取新的manifest文件
let manifestResponse;
let manifestPath = "/.vite/manifest.json";
try {
manifestResponse = await fetch(manifestPath, {
cache: "no-cache",
headers: {
"Cache-Control": "no-cache",
Pragma: "no-cache",
},
});
} catch (error) {
// 如果.vite路径失败尝试根路径
manifestPath = "/manifest.json";
manifestResponse = await fetch(manifestPath, {
cache: "no-cache",
headers: {
"Cache-Control": "no-cache",
Pragma: "no-cache",
},
});
}
if (!manifestResponse.ok) {
return { hasUpdate: false };
}
const manifest = await manifestResponse.json();
// 从Vite manifest中提取文件哈希
const newHashes: string[] = [];
Object.values(manifest).forEach((entry: any) => {
if (entry.file && entry.file.includes("assets/")) {
// console.log("处理manifest entry file:", entry.file);
// 修改正则表达式,匹配包含字母、数字和下划线的哈希值
const match = entry.file.match(/[a-zA-Z0-9_-]{8,}/);
if (match) {
const hash = match[0];
newHashes.push(hash);
}
}
// 也检查CSS文件
if (entry.css) {
entry.css.forEach((cssFile: string) => {
if (cssFile.includes("assets/")) {
// console.log("处理manifest entry css:", cssFile);
// 修改正则表达式,匹配包含字母、数字和下划线的哈希值
const match = cssFile.match(/[a-zA-Z0-9_-]{8,}/);
if (match) {
const hash = match[0];
// console.log("提取的manifest css哈希:", hash);
newHashes.push(hash);
}
}
});
}
});
// 去重新哈希值数组
const uniqueNewHashes = [...new Set(newHashes)];
// 比较哈希值
const hasUpdate = this.compareHashes(this.currentHashes, uniqueNewHashes);
const updateInfo: UpdateInfo = {
hasUpdate,
version: manifest.version || this.currentVersion,
timestamp: Date.now(),
};
// 通知所有回调
this.updateCallbacks.forEach(callback => callback(updateInfo));
return updateInfo;
} catch (error) {
return { hasUpdate: false };
}
}
/**
*
*/
private compareHashes(current: string[], newHashes: string[]): boolean {
if (current.length !== newHashes.length) {
return true;
}
// 对两个数组进行排序后比较,忽略顺序
const sortedCurrent = [...current].sort();
const sortedNewHashes = [...newHashes].sort();
const hasUpdate = sortedCurrent.some((hash, index) => {
return hash !== sortedNewHashes[index];
});
return hasUpdate;
}
/**
*
*/
onUpdate(callback: (info: UpdateInfo) => void) {
this.updateCallbacks.push(callback);
}
/**
*
*/
offUpdate(callback: (info: UpdateInfo) => void) {
const index = this.updateCallbacks.indexOf(callback);
if (index > -1) {
this.updateCallbacks.splice(index, 1);
}
}
/**
*
*/
forceReload() {
window.location.reload();
}
}
export const updateChecker = new UpdateChecker();