const MINUTE = 60 * 1000; const MAX_RELOAD_COUNT = 1; const MIN_COOL_DOWN_INTERVAL = 5 * MINUTE; export interface ReloadCounter { count: number; expiresAt: number; } export interface ReloadHandlerOptions { id: string; coolDownInterval?: number; handler?: () => void; storage?: 'local' | 'session'; } export class ReloadHandler { #reloadCounter?: Partial; readonly #options: ReloadHandlerOptions; constructor(options: ReloadHandlerOptions) { this.#options = options; this.#reloadCounter = this.getCounter(); } reload() { if (this.incrementCounter() <= MAX_RELOAD_COUNT) { if (this.#options.handler) { this.#options.handler(); } else { window.location.reload(); } } } reset() { this.storage.removeItem(this.counterKey); this.#reloadCounter = undefined; } private incrementCounter() { const { count = 0, expiresAt = 0 } = this.#reloadCounter ?? {}; const counter = expiresAt < Date.now() ? { count: 1, expiresAt: Date.now() + this.coolDownInterval } : { count: count + 1, expiresAt }; this.setCounter(counter); return counter.count; } private getCounter(): Partial | undefined { try { const counter = JSON.parse(this.storage.getItem(this.counterKey) ?? '{}'); if (counter && typeof counter === 'object') { return counter; } } catch { // ignore JSON.parse error } } private setCounter(counter: ReloadCounter) { this.storage.setItem(this.counterKey, JSON.stringify(counter)); this.#reloadCounter = counter; } private get counterKey() { return `reload-counter-${this.#options.id}`; } private get coolDownInterval() { return Math.max( MIN_COOL_DOWN_INTERVAL, this.#options.coolDownInterval ?? MIN_COOL_DOWN_INTERVAL ); } private get storage() { return this.#options.storage === 'local' ? localStorage : sessionStorage; } }