60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
export interface UpdateLifecyclePorts {
|
|
quiesce(): Promise<void>
|
|
restore(): void
|
|
shutdown(): Promise<void>
|
|
exit(): void
|
|
log(error: unknown): void
|
|
prepareTimeoutMs?: number
|
|
quitTimeoutMs?: number
|
|
}
|
|
|
|
export function createUpdateLifecycle(ports: UpdateLifecyclePorts): {
|
|
beforeInstall(): Promise<boolean>
|
|
installFailed(): void
|
|
quitForUpdate(): Promise<void>
|
|
} {
|
|
let preparation: Promise<void> | 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<typeof setTimeout> | undefined
|
|
const pending = ports.quiesce()
|
|
preparation = pending
|
|
void pending.finally(() => { if (preparation === pending) preparation = null }).catch(() => undefined)
|
|
try {
|
|
await Promise.race([
|
|
pending,
|
|
new Promise<never>((_, reject) => {
|
|
timer = setTimeout(() => reject(new Error('任务清理尚未结束,暂不能开始新任务;请稍后重试或重启应用')), ports.prepareTimeoutMs ?? 30_000)
|
|
})
|
|
])
|
|
return true
|
|
} finally { clearTimeout(timer) }
|
|
},
|
|
installFailed,
|
|
async quitForUpdate() {
|
|
committed = true
|
|
let timer: ReturnType<typeof setTimeout> | undefined
|
|
try {
|
|
await Promise.race([
|
|
ports.shutdown(),
|
|
new Promise<void>(resolve => { timer = setTimeout(resolve, ports.quitTimeoutMs ?? 15_000) })
|
|
])
|
|
} catch (error) {
|
|
ports.log(error)
|
|
} finally {
|
|
clearTimeout(timer)
|
|
ports.exit()
|
|
}
|
|
}
|
|
}
|
|
}
|