Files
Genarrative/apps/ai-game-creator-shell/src/services/appUpdate.ts
T
kdletters a5fd25f10a 客户端更新切换到官方更新插件并按渠道分发
- 客户端接入 tauri-plugin-updater:原生侧注册插件,删除自研更新下载命令、下载进度事件与安装器启动逻辑
- 客户端更新服务与更新提示改走官方插件接口,删除自研清单解析、版本比较与下载实现
- 更新能力只授予客户端主窗口,移除只为自研清单放行的 OSS 白名单与 CSP 连接项
- 新增构建期更新检查开关:开发态默认关闭,agc 启动不请求更新清单、不显示更新入口
- 发布脚本按渠道生成官方更新插件清单与签名,universal macOS 产物同时挂两个平台键,缺签名失败关闭
- 发布脚本按渠道上传安装包、签名与渠道清单,并为 dev-win 生成旧协议 sha256 迁移清单
- 构建期按渠道注入更新端点配置,渠道与目标平台不匹配时发布失败关闭
- Jenkins 流水线新增渠道参数与签名凭据注入,归档补充签名与迁移清单
- 新增发布上传 dry-run 开关,只打印 ossutil 命令且不回显凭据
- 更新技术方案与开发运维文档,登记 macOS 渠道落地待办
2026-09-17 17:56:53 +08:00

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;
}