import { EventManager } from "#Source/event/index.ts" import type { BuildEvents, SubscriberEntry } from "#Source/event/index.ts" import type { ExpirationManager, ExpirationManagerEvents } from "./expiration-manager.ts" /** * @description 表示一个以秒为单位的剩余时长。 */ export type RemainingTime = number /** * @description 描述一组按名称索引的派生剩余时长字典。 */ export type RemainingDict = { [K in ExpirationName]?: RemainingTime | undefined } /** * @description 描述 `RemainingManager` 对外发出的事件表。 */ export type RemainingManagerEvents = BuildEvents<{ /** * @description 在剩余时间快照发生变化时发出最新结果。 */ remaining: (remaining: RemainingDict) => void }> /** * @description 描述创建 `RemainingManager` 时可用的配置项。 */ export interface RemainingManagerOptions { /** * @description 提供过期状态真相来源的过期管理器。 */ expirationManager: ExpirationManager /** * @description 控制是否在创建后立即启用周期性剩余时间检查。 */ enabled?: boolean | undefined } const isRemainingDictEqual = ( left: RemainingDict, right: RemainingDict, ): boolean => { const leftKeys = Object.keys(left) const rightKeys = Object.keys(right) if (leftKeys.length !== rightKeys.length) { return false } for (const key of leftKeys) { const expirationName = key as ExpirationName if (left[expirationName] !== right[expirationName]) { return false } } return true } /** * @description 管理基于 `ExpirationManager` 状态派生出的剩余时间查询与周期性通知。 * * `RemainingManager` 不拥有独立的过期真相来源,而是始终从 `ExpirationManager` * 读取当前过期状态,并在需要时把这些状态换算为以秒为单位的剩余值。 * * 这里的 `enabled` 只影响内部的周期性检查是否运行,不影响过期状态变化时 * 立即触发的派生结果更新。 */ export class RemainingManager { readonly expirationManager: ExpirationManager readonly event: EventManager> private enabled: boolean private lastEmittedRemainingSnapshot: RemainingDict private timer: ReturnType | null private terminated: boolean private readonly expirationStateSubscriberEntry: SubscriberEntry< ExpirationManagerEvents, "expirationState" > constructor(options: RemainingManagerOptions) { const { expirationManager, enabled } = options this.expirationManager = expirationManager this.event = new EventManager() this.enabled = enabled === true this.lastEmittedRemainingSnapshot = {} this.timer = null this.terminated = false this.expirationStateSubscriberEntry = this.expirationManager.event.subscribe( "expirationState", (): void => { this.emit() }, ) if (this.enabled === true) { this.resume() } } /** * @description 返回某个具名过期项当前的秒级剩余时长。 * * 当目标不存在,或其状态不是 `active` 时,返回 `undefined`。 */ getRemaining(name: ExpirationName): number | undefined { const expirationState = this.expirationManager.getExpirationState(name) if (expirationState === undefined) { return undefined } if (expirationState.state !== "active") { return undefined } return Math.max(0, Math.ceil((expirationState.endAt - this.expirationManager.getNow()) / 1_000)) } /** * @description 返回当前所有活跃过期项的秒级剩余时长快照。 */ getRemainingSnapshot(): RemainingDict { const expirationDict = this.expirationManager.getExpirationSnapshot() const result: RemainingDict = {} for (const name in expirationDict) { if (Object.hasOwn(expirationDict, name) === false) { continue } const expirationName = name as ExpirationName const remaining = this.getRemaining(expirationName) if (remaining === undefined) { continue } result[expirationName] = remaining } return result } /** * @description 在剩余时间快照发生变化时发出一次 `remaining` 事件。 * * 若当前实例已经终止,或新旧快照完全一致,则不会重复发出事件。 */ emit(): void { if (this.terminated === true) { return } const remainingSnapshot = this.getRemainingSnapshot() if (isRemainingDictEqual(this.lastEmittedRemainingSnapshot, remainingSnapshot) === true) { return } this.lastEmittedRemainingSnapshot = remainingSnapshot this.event.emit("remaining", remainingSnapshot) } /** * @description 启用周期性剩余时间检查。 * * 这不会跳过过期状态变更时本来就会触发的即时派生通知。 */ start(): void { if (this.terminated === true) { return } this.enabled = true this.resume() } /** * @description 停止周期性剩余时间检查,并将启用标记设为关闭。 */ stop(): void { this.enabled = false if (this.timer === null) { return } clearInterval(this.timer) this.timer = null } /** * @description 暂停当前周期性定时器,但保留启用标记。 * * 这通常用于由外层过期管理器在暂停期间临时停止派生检查, * 以便之后通过 `resume()` 继续运行。 */ pause(): void { if (this.timer !== null) { clearInterval(this.timer) this.timer = null } } /** * @description 在实例已启用且尚未终止时恢复周期性剩余时间检查。 */ resume(): void { if (this.terminated === true || this.enabled !== true || this.timer !== null) { return } this.timer = setInterval(() => { if (Object.keys(this.expirationManager.getExpirationSnapshot()).length === 0) { return } this.emit() }, 1_000) } /** * @description 终止实例并释放订阅与定时器资源。 */ terminate(): void { if (this.terminated === true) { return } this.terminated = true this.expirationStateSubscriberEntry.unsubscribe() this.stop() } }