| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export class PollingManager { |
| constructor() { |
| this.polls = new Map(); |
| this.lastUpdates = new Map(); |
| this.isVisible = !document.hidden; |
| this.updateCallbacks = new Map(); |
| |
| |
| document.addEventListener('visibilitychange', () => { |
| this.isVisible = !document.hidden; |
| console.log(`[PollingManager] Page visibility changed: ${this.isVisible ? 'visible' : 'hidden'}`); |
| |
| if (this.isVisible) { |
| this.resumeAll(); |
| } else { |
| this.pauseAll(); |
| } |
| }); |
|
|
| |
| window.addEventListener('beforeunload', () => { |
| this.stopAll(); |
| }); |
|
|
| console.log('[PollingManager] Initialized'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| start(key, fetchFunction, callback, interval) { |
| |
| this.stop(key); |
|
|
| const poll = { |
| fetchFunction, |
| callback, |
| interval, |
| timerId: null, |
| isPaused: false, |
| errorCount: 0, |
| consecutiveErrors: 0, |
| maxConsecutiveErrors: 5, |
| }; |
|
|
| |
| this._executePoll(key, poll); |
|
|
| |
| poll.timerId = setInterval(() => { |
| if (!poll.isPaused && this.isVisible) { |
| this._executePoll(key, poll); |
| } |
| }, interval); |
|
|
| this.polls.set(key, poll); |
| console.log(`[PollingManager] Started polling: ${key} every ${interval}ms`); |
| } |
|
|
| |
| |
| |
| async _executePoll(key, poll) { |
| try { |
| console.log(`[PollingManager] Fetching: ${key}`); |
| const data = await poll.fetchFunction(); |
| |
| |
| poll.consecutiveErrors = 0; |
| |
| |
| this.lastUpdates.set(key, Date.now()); |
| |
| |
| poll.callback(data, null); |
| |
| |
| this._notifyUpdateCallbacks(key); |
|
|
| } catch (error) { |
| poll.consecutiveErrors++; |
| poll.errorCount++; |
| |
| console.error(`[PollingManager] Error in ${key} (${poll.consecutiveErrors}/${poll.maxConsecutiveErrors}):`, error); |
| |
| |
| poll.callback(null, error); |
| |
| |
| if (poll.consecutiveErrors >= poll.maxConsecutiveErrors) { |
| console.error(`[PollingManager] Too many consecutive errors, stopping ${key}`); |
| this.stop(key); |
| } |
| } |
| } |
|
|
| |
| |
| |
| stop(key) { |
| const poll = this.polls.get(key); |
| if (poll && poll.timerId) { |
| clearInterval(poll.timerId); |
| this.polls.delete(key); |
| this.lastUpdates.delete(key); |
| console.log(`[PollingManager] Stopped polling: ${key}`); |
| } |
| } |
|
|
| |
| |
| |
| pause(key) { |
| const poll = this.polls.get(key); |
| if (poll) { |
| poll.isPaused = true; |
| console.log(`[PollingManager] Paused: ${key}`); |
| } |
| } |
|
|
| |
| |
| |
| resume(key) { |
| const poll = this.polls.get(key); |
| if (poll) { |
| poll.isPaused = false; |
| |
| this._executePoll(key, poll); |
| console.log(`[PollingManager] Resumed: ${key}`); |
| } |
| } |
|
|
| |
| |
| |
| pauseAll() { |
| console.log('[PollingManager] Pausing all polls'); |
| for (const [key, poll] of this.polls) { |
| poll.isPaused = true; |
| } |
| } |
|
|
| |
| |
| |
| resumeAll() { |
| console.log('[PollingManager] Resuming all polls'); |
| for (const [key, poll] of this.polls) { |
| if (poll.isPaused) { |
| poll.isPaused = false; |
| |
| this._executePoll(key, poll); |
| } |
| } |
| } |
|
|
| |
| |
| |
| stopAll() { |
| console.log('[PollingManager] Stopping all polls'); |
| for (const key of this.polls.keys()) { |
| this.stop(key); |
| } |
| } |
|
|
| |
| |
| |
| getLastUpdate(key) { |
| return this.lastUpdates.get(key) || null; |
| } |
|
|
| |
| |
| |
| getLastUpdateText(key) { |
| const timestamp = this.getLastUpdate(key); |
| if (!timestamp) return 'Never'; |
| |
| const seconds = Math.floor((Date.now() - timestamp) / 1000); |
| |
| if (seconds < 5) return 'Just now'; |
| if (seconds < 60) return `${seconds}s ago`; |
| if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; |
| if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; |
| return `${Math.floor(seconds / 86400)}d ago`; |
| } |
|
|
| |
| |
| |
| isActive(key) { |
| return this.polls.has(key); |
| } |
|
|
| |
| |
| |
| isPaused(key) { |
| const poll = this.polls.get(key); |
| return poll ? poll.isPaused : false; |
| } |
|
|
| |
| |
| |
| getActivePolls() { |
| return Array.from(this.polls.keys()); |
| } |
|
|
| |
| |
| |
| getPollInfo(key) { |
| const poll = this.polls.get(key); |
| if (!poll) return null; |
|
|
| return { |
| key, |
| interval: poll.interval, |
| isPaused: poll.isPaused, |
| errorCount: poll.errorCount, |
| consecutiveErrors: poll.consecutiveErrors, |
| lastUpdate: this.getLastUpdateText(key), |
| isActive: true, |
| }; |
| } |
|
|
| |
| |
| |
| |
| onLastUpdate(callback) { |
| const id = Date.now() + Math.random(); |
| this.updateCallbacks.set(id, callback); |
| |
| |
| return () => this.updateCallbacks.delete(id); |
| } |
|
|
| |
| |
| |
| _notifyUpdateCallbacks(key) { |
| const text = this.getLastUpdateText(key); |
| for (const callback of this.updateCallbacks.values()) { |
| try { |
| callback(key, text); |
| } catch (error) { |
| console.error('[PollingManager] Error in update callback:', error); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| updateAllLastUpdateTexts() { |
| for (const key of this.polls.keys()) { |
| this._notifyUpdateCallbacks(key); |
| } |
| } |
| } |
|
|
| |
| |
| |
|
|
| export const pollingManager = new PollingManager(); |
|
|
| |
| setInterval(() => { |
| pollingManager.updateAllLastUpdateTexts(); |
| }, 1000); |
|
|
| export default pollingManager; |
|
|