|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|