218 lines
5.7 KiB
TypeScript
218 lines
5.7 KiB
TypeScript
/**
|
||
* 应用更新检测工具
|
||
*/
|
||
|
||
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();
|