a5fd25f10a
- 客户端接入 tauri-plugin-updater:原生侧注册插件,删除自研更新下载命令、下载进度事件与安装器启动逻辑 - 客户端更新服务与更新提示改走官方插件接口,删除自研清单解析、版本比较与下载实现 - 更新能力只授予客户端主窗口,移除只为自研清单放行的 OSS 白名单与 CSP 连接项 - 新增构建期更新检查开关:开发态默认关闭,agc 启动不请求更新清单、不显示更新入口 - 发布脚本按渠道生成官方更新插件清单与签名,universal macOS 产物同时挂两个平台键,缺签名失败关闭 - 发布脚本按渠道上传安装包、签名与渠道清单,并为 dev-win 生成旧协议 sha256 迁移清单 - 构建期按渠道注入更新端点配置,渠道与目标平台不匹配时发布失败关闭 - Jenkins 流水线新增渠道参数与签名凭据注入,归档补充签名与迁移清单 - 新增发布上传 dry-run 开关,只打印 ossutil 命令且不回显凭据 - 更新技术方案与开发运维文档,登记 macOS 渠道落地待办
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
import {
|
|
check,
|
|
type DownloadEvent,
|
|
type Update,
|
|
} from '@tauri-apps/plugin-updater';
|
|
|
|
import { appUpdateCheckEnabled } from '../app/featureFlags';
|
|
import { resolveTauriInvoke } from '../app/tauri';
|
|
|
|
/** 更新提示所需的元数据;清单请求、版本比较、下载、校验与安装都由官方更新插件在原生侧完成。 */
|
|
export type AppUpdateInfo = {
|
|
version: string;
|
|
currentVersion: string;
|
|
releaseNotes?: string;
|
|
};
|
|
|
|
export type AppUpdateProgress = {
|
|
downloadedBytes: number;
|
|
totalBytes?: number;
|
|
};
|
|
|
|
let pendingUpdate: Update | null = null;
|
|
let updateCheckPromise: Promise<AppUpdateInfo | null> | null = null;
|
|
const updateListeners = new Set<(update: AppUpdateInfo | null) => void>();
|
|
|
|
function toAppUpdateInfo(update: Update): AppUpdateInfo {
|
|
return {
|
|
version: update.version,
|
|
currentVersion: update.currentVersion,
|
|
...(update.body ? { releaseNotes: update.body } : {}),
|
|
};
|
|
}
|
|
|
|
async function runAppUpdateCheck(): Promise<AppUpdateInfo | null> {
|
|
try {
|
|
const update = await check();
|
|
pendingUpdate = update;
|
|
const info = update ? toAppUpdateInfo(update) : null;
|
|
updateListeners.forEach((listener) => listener(info));
|
|
return info;
|
|
} catch {
|
|
// 清单 404、渠道缺少当前平台条目、网络或签名错误都按“无更新”收口,不阻塞启动。
|
|
pendingUpdate = null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** 同一客户端生命周期内只请求一次清单;`force` 供「关于」页手动检查使用。 */
|
|
export function checkForAppUpdate(
|
|
options: { force?: boolean } = {},
|
|
): Promise<AppUpdateInfo | null> {
|
|
// 开发态(`agc` 启动)默认关闭更新检查:不请求清单,也不显示更新入口。
|
|
if (!appUpdateCheckEnabled) return Promise.resolve(null);
|
|
if (options.force) updateCheckPromise = null;
|
|
updateCheckPromise ??= runAppUpdateCheck();
|
|
return updateCheckPromise;
|
|
}
|
|
|
|
export function subscribeToAppUpdate(
|
|
listener: (update: AppUpdateInfo | null) => void,
|
|
) {
|
|
updateListeners.add(listener);
|
|
return () => updateListeners.delete(listener);
|
|
}
|
|
|
|
/**
|
|
* 下载并安装最近一次检测到的更新。
|
|
*
|
|
* Windows 上安装程序接管后客户端退出并由安装程序重启;macOS / Linux 在安装完成后由本函数重启进程。
|
|
*/
|
|
export async function installAppUpdate(
|
|
onProgress: (progress: AppUpdateProgress) => void = () => undefined,
|
|
) {
|
|
const update = pendingUpdate;
|
|
if (!update) throw new Error('没有可安装的更新');
|
|
let downloadedBytes = 0;
|
|
let totalBytes: number | undefined;
|
|
const report = () =>
|
|
onProgress({
|
|
downloadedBytes,
|
|
...(totalBytes ? { totalBytes } : {}),
|
|
});
|
|
await update.downloadAndInstall((event: DownloadEvent) => {
|
|
if (event.event === 'Started') {
|
|
downloadedBytes = 0;
|
|
totalBytes = event.data.contentLength;
|
|
} else if (event.event === 'Progress') {
|
|
downloadedBytes += event.data.chunkLength;
|
|
}
|
|
report();
|
|
});
|
|
// 失败时保留待装更新,让「重试」仍能走同一条安装链路。
|
|
pendingUpdate = null;
|
|
restartAppAfterUpdate();
|
|
}
|
|
|
|
function restartAppAfterUpdate() {
|
|
const invoke = resolveTauriInvoke();
|
|
if (!invoke) return;
|
|
// Windows 的 install 已在启动安装程序后退出进程,这里只覆盖 macOS / Linux 的重启收敛。
|
|
void invoke('restart_agc_app').catch(() => undefined);
|
|
}
|
|
|
|
export function resetAppUpdateCheckForTests() {
|
|
pendingUpdate = null;
|
|
updateCheckPromise = null;
|
|
}
|