147 lines
3.9 KiB
TypeScript
147 lines
3.9 KiB
TypeScript
|
|
import { getVersion } from "@tauri-apps/api/app";
|
|||
|
|
|
|||
|
|
// 可选导入:在未注册插件或非 Tauri 环境下,调用时会抛错,外层需做兜底
|
|||
|
|
// 我们按需加载并在运行时捕获错误,避免构建期类型问题
|
|||
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|||
|
|
import type { Update } from "@tauri-apps/plugin-updater";
|
|||
|
|
|
|||
|
|
export type UpdateChannel = "stable" | "beta";
|
|||
|
|
|
|||
|
|
export type UpdaterPhase =
|
|||
|
|
| "idle"
|
|||
|
|
| "checking"
|
|||
|
|
| "available"
|
|||
|
|
| "downloading"
|
|||
|
|
| "installing"
|
|||
|
|
| "restarting"
|
|||
|
|
| "upToDate"
|
|||
|
|
| "error";
|
|||
|
|
|
|||
|
|
export interface UpdateInfo {
|
|||
|
|
currentVersion: string;
|
|||
|
|
availableVersion: string;
|
|||
|
|
notes?: string;
|
|||
|
|
pubDate?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface UpdateProgressEvent {
|
|||
|
|
event: "Started" | "Progress" | "Finished";
|
|||
|
|
total?: number;
|
|||
|
|
downloaded?: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface UpdateHandle {
|
|||
|
|
version: string;
|
|||
|
|
notes?: string;
|
|||
|
|
date?: string;
|
|||
|
|
downloadAndInstall: (
|
|||
|
|
onProgress?: (e: UpdateProgressEvent) => void,
|
|||
|
|
) => Promise<void>;
|
|||
|
|
download?: () => Promise<void>;
|
|||
|
|
install?: () => Promise<void>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface CheckOptions {
|
|||
|
|
timeout?: number;
|
|||
|
|
channel?: UpdateChannel;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function mapUpdateHandle(raw: Update): UpdateHandle {
|
|||
|
|
return {
|
|||
|
|
version: (raw as any).version ?? "",
|
|||
|
|
notes: (raw as any).notes,
|
|||
|
|
date: (raw as any).date,
|
|||
|
|
async downloadAndInstall(onProgress?: (e: UpdateProgressEvent) => void) {
|
|||
|
|
await (raw as any).downloadAndInstall((evt: any) => {
|
|||
|
|
if (!onProgress) return;
|
|||
|
|
const mapped: UpdateProgressEvent = {
|
|||
|
|
event: evt?.event,
|
|||
|
|
};
|
|||
|
|
if (evt?.event === "Started") {
|
|||
|
|
mapped.total = evt?.data?.contentLength ?? 0;
|
|||
|
|
mapped.downloaded = 0;
|
|||
|
|
} else if (evt?.event === "Progress") {
|
|||
|
|
mapped.downloaded = evt?.data?.chunkLength ?? 0; // 累积由调用方完成
|
|||
|
|
}
|
|||
|
|
onProgress(mapped);
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
// 透传可选 API(若插件版本支持)
|
|||
|
|
download: (raw as any).download
|
|||
|
|
? async () => {
|
|||
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|||
|
|
await (raw as any).download();
|
|||
|
|
}
|
|||
|
|
: undefined,
|
|||
|
|
install: (raw as any).install
|
|||
|
|
? async () => {
|
|||
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|||
|
|
await (raw as any).install();
|
|||
|
|
}
|
|||
|
|
: undefined,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function getCurrentVersion(): Promise<string> {
|
|||
|
|
try {
|
|||
|
|
return await getVersion();
|
|||
|
|
} catch {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function checkForUpdate(
|
|||
|
|
opts: CheckOptions = {},
|
|||
|
|
): Promise<
|
|||
|
|
| { status: "up-to-date" }
|
|||
|
|
| { status: "available"; info: UpdateInfo; update: UpdateHandle }
|
|||
|
|
> {
|
|||
|
|
// 动态引入,避免在未安装插件时导致打包期问题
|
|||
|
|
const { check } = await import("@tauri-apps/plugin-updater");
|
|||
|
|
|
|||
|
|
const currentVersion = await getCurrentVersion();
|
|||
|
|
const update = await check({ timeout: opts.timeout ?? 30000 } as any);
|
|||
|
|
|
|||
|
|
if (!update) {
|
|||
|
|
return { status: "up-to-date" };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const mapped = mapUpdateHandle(update);
|
|||
|
|
const info: UpdateInfo = {
|
|||
|
|
currentVersion,
|
|||
|
|
availableVersion: mapped.version,
|
|||
|
|
notes: mapped.notes,
|
|||
|
|
pubDate: mapped.date,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return { status: "available", info, update: mapped };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function relaunchApp(): Promise<void> {
|
|||
|
|
const { relaunch } = await import("@tauri-apps/plugin-process");
|
|||
|
|
await relaunch();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function runUpdateFlow(
|
|||
|
|
opts: CheckOptions = {},
|
|||
|
|
): Promise<{ status: "up-to-date" | "done" }> {
|
|||
|
|
const result = await checkForUpdate(opts);
|
|||
|
|
if (result.status === "up-to-date") return result;
|
|||
|
|
|
|||
|
|
let downloaded = 0;
|
|||
|
|
let total = 0;
|
|||
|
|
await result.update.downloadAndInstall((e) => {
|
|||
|
|
if (e.event === "Started") {
|
|||
|
|
total = e.total ?? 0;
|
|||
|
|
downloaded = 0;
|
|||
|
|
} else if (e.event === "Progress") {
|
|||
|
|
downloaded += e.downloaded ?? 0;
|
|||
|
|
// 调用方可监听此处并更新 UI(目前设置页仅显示加载态)
|
|||
|
|
console.debug("update progress", { downloaded, total });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await relaunchApp();
|
|||
|
|
return { status: "done" };
|
|||
|
|
}
|