export interface UpdateLifecyclePorts { quiesce(): Promise restore(): void shutdown(): Promise exit(): void log(error: unknown): void prepareTimeoutMs?: number quitTimeoutMs?: number } export function createUpdateLifecycle(ports: UpdateLifecyclePorts): { beforeInstall(): Promise installFailed(): void quitForUpdate(): Promise } { let preparation: Promise | null = null let committed = false const restore = (): void => { if (!committed) ports.restore() } const installFailed = (): void => { // Stopping work is not cancellable. Do not resume underneath an old stop. if (preparation) void preparation.then(restore, restore) else restore() } return { async beforeInstall() { if (committed) throw new Error('安装器已启动,请退出应用完成更新') if (preparation) throw new Error('上次更新准备仍在结束中,请稍后重试') let timer: ReturnType | undefined const pending = ports.quiesce() preparation = pending void pending.finally(() => { if (preparation === pending) preparation = null }).catch(() => undefined) try { await Promise.race([ pending, new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('任务清理尚未结束,暂不能开始新任务;请稍后重试或重启应用')), ports.prepareTimeoutMs ?? 30_000) }) ]) return true } finally { clearTimeout(timer) } }, installFailed, async quitForUpdate() { committed = true let timer: ReturnType | undefined try { await Promise.race([ ports.shutdown(), new Promise(resolve => { timer = setTimeout(resolve, ports.quitTimeoutMs ?? 15_000) }) ]) } catch (error) { ports.log(error) } finally { clearTimeout(timer) ports.exit() } } } }