export default class SessionTicker { constructor({ duration, onTick, onExpire, tickInterval = 1 }) { this.duration = duration; this.onTick = onTick; this.onExpire = onExpire; this.tickInterval = tickInterval; this.expiresAt = null; this._interval = null; this._onVisibility = this._onVisibility.bind(this); this._onPageShow = this._onPageShow.bind(this); } start() { this.expiresAt = Date.now() + this.duration * 1000; this._attachListeners(); this._tick(); this._interval = setInterval(() => this._tick(), this.tickInterval * 1000); return this; } stop() { clearInterval(this._interval); this._interval = null; this._detachListeners(); return this; } reset(newDuration) { if (newDuration != null) this.duration = newDuration; this.stop(); return this.start(); } getRemaining() { if (!this.expiresAt) return 0; return Math.max(0, Math.ceil((this.expiresAt - Date.now()) / 1000)); } _tick() { const remaining = this.getRemaining(); this.onTick?.(remaining); if (remaining <= 0) { this.stop(); this.onExpire?.(); } } _onVisibility() { if (document.visibilityState === 'visible') this._tick(); } _onPageShow(e) { if (e.persisted) this._tick(); } _attachListeners() { document.addEventListener('visibilitychange', this._onVisibility); window.addEventListener('pageshow', this._onPageShow); } _detachListeners() { document.removeEventListener('visibilitychange', this._onVisibility); window.removeEventListener('pageshow', this._onPageShow); } }