feat: verify updater package and harden client reliability in 0.1.25

This commit is contained in:
2026-02-20 22:15:28 +08:00
parent fe544efc91
commit c8f63d6fc9
7 changed files with 393 additions and 18 deletions

View File

@@ -153,12 +153,14 @@ const syncState = reactive({
nextRunAt: "",
});
const updateState = reactive({
currentVersion: "0.1.24",
currentVersion: "0.1.25",
latestVersion: "",
available: false,
mandatory: false,
checking: false,
downloadUrl: "",
packageSha256: "",
packageSize: 0,
releaseNotes: "",
lastCheckedAt: "",
message: "",
@@ -884,6 +886,17 @@ function normalizeReleaseNotesText(raw: string | undefined) {
.trim();
}
function normalizeSha256(raw: string | undefined) {
const digest = String(raw || "").trim().toLowerCase();
return /^[a-f0-9]{64}$/.test(digest) ? digest : "";
}
function normalizePackageSize(raw: unknown) {
const value = Number(raw);
if (!Number.isFinite(value) || value <= 0) return 0;
return Math.floor(value);
}
async function checkClientUpdate(showResultToast = true): Promise<boolean> {
if (updateState.checking) {
return false;
@@ -904,6 +917,8 @@ async function checkClientUpdate(showResultToast = true): Promise<boolean> {
updateState.latestVersion = String(response.data.latestVersion || updateState.currentVersion);
updateState.available = Boolean(response.data.updateAvailable);
updateState.downloadUrl = String(response.data.downloadUrl || "");
updateState.packageSha256 = normalizeSha256(String(response.data.sha256 || ""));
updateState.packageSize = normalizePackageSize(response.data.packageSize);
updateState.releaseNotes = normalizeReleaseNotesText(String(response.data.releaseNotes || ""));
updateState.mandatory = Boolean(response.data.mandatory);
updateState.message = String(response.data.message || "");
@@ -919,6 +934,8 @@ async function checkClientUpdate(showResultToast = true): Promise<boolean> {
updateState.available = false;
updateState.downloadUrl = "";
updateState.packageSha256 = "";
updateState.packageSize = 0;
updateState.message = String(response.data?.message || "检查更新失败");
if (showResultToast) {
showToast(updateState.message, "error");
@@ -984,7 +1001,7 @@ async function installLatestUpdate(): Promise<boolean> {
resetUpdateRuntime();
updateRuntime.downloading = true;
const taskId = `UPD-${Date.now()}`;
const installerName = `玩玩云_v${updateState.latestVersion || updateState.currentVersion}.exe`;
const installerName = `wanwan-cloud-desktop_v${updateState.latestVersion || updateState.currentVersion}.exe`;
updateRuntime.taskId = taskId;
updateRuntime.progress = 1;
updateRuntime.speed = "准备下载";
@@ -998,12 +1015,40 @@ async function installLatestUpdate(): Promise<boolean> {
try {
if (response.ok && response.data?.success) {
updateRuntime.downloading = false;
updateRuntime.installing = true;
updateRuntime.progress = 100;
updateRuntime.speed = "-";
updateRuntime.speed = "校验中";
const savePath = String(response.data?.savePath || "").trim();
updateRuntime.installerPath = savePath;
if (savePath) {
const expectedSha = normalizeSha256(updateState.packageSha256);
const expectedSize = normalizePackageSize(updateState.packageSize);
if (expectedSha || expectedSize > 0) {
const verifyResponse = await invokeBridge("api_compute_file_sha256", {
filePath: savePath,
});
if (!(verifyResponse.ok && verifyResponse.data?.success)) {
const message = String(verifyResponse.data?.message || "校验更新包失败");
resetUpdateRuntime();
showToast(message, "error");
return false;
}
const actualSha = normalizeSha256(String(verifyResponse.data?.sha256 || ""));
const actualSize = normalizePackageSize(verifyResponse.data?.fileSize);
if (expectedSize > 0 && actualSize > 0 && expectedSize !== actualSize) {
resetUpdateRuntime();
showToast(`更新包大小校验失败(期望 ${formatBytes(expectedSize)},实际 ${formatBytes(actualSize)}`, "error");
return false;
}
if (expectedSha && actualSha !== expectedSha) {
resetUpdateRuntime();
showToast("更新包完整性校验失败,请重试下载", "error");
return false;
}
}
updateRuntime.installing = true;
updateRuntime.progress = 100;
updateRuntime.speed = "-";
const launchResponse = await invokeBridge("api_silent_install_and_restart", {
installerPath: savePath,
});