perf: reduce timetable resource consuption and update notification collector

This commit is contained in:
SethBurkart123
2025-06-12 16:24:28 +10:00
parent d377329bf9
commit 8bd9b1dae7
2 changed files with 104 additions and 25 deletions
@@ -3,6 +3,7 @@ import type { Plugin } from "../../core/types";
interface NotificationCollectorStorage { interface NotificationCollectorStorage {
lastNotificationCount: number; lastNotificationCount: number;
lastCheckedTime: string; lastCheckedTime: string;
consecutiveErrors: number;
} }
const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = { const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
@@ -15,13 +16,24 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
run: async (api) => { run: async (api) => {
let pollInterval: number | null = null; let pollInterval: number | null = null;
let isVisible = !document.hidden;
let baseInterval = 30000; // 30 seconds
const maxInterval = 300000; // 5 minutes max
// Store last notification count in storage // Store last notification count in storage
if (!api.storage.lastNotificationCount) { if (!api.storage.lastNotificationCount) {
api.storage.lastNotificationCount = 0; api.storage.lastNotificationCount = 0;
} }
if (!api.storage.consecutiveErrors) {
api.storage.consecutiveErrors = 0;
}
const checkNotifications = async () => { const checkNotifications = async () => {
// Skip if tab is not visible to save battery
if (!isVisible) {
return;
}
try { try {
const alertDiv = document.querySelector( const alertDiv = document.querySelector(
"[class*='notifications__bubble___']", "[class*='notifications__bubble___']",
@@ -52,6 +64,9 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
api.storage.lastNotificationCount = notificationCount; api.storage.lastNotificationCount = notificationCount;
api.storage.lastCheckedTime = new Date().toISOString(); api.storage.lastCheckedTime = new Date().toISOString();
// Reset error count on success
api.storage.consecutiveErrors = 0;
if (alertDiv) { if (alertDiv) {
alertDiv.textContent = notificationCount.toString(); alertDiv.textContent = notificationCount.toString();
} else { } else {
@@ -59,18 +74,37 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
} }
} catch (error) { } catch (error) {
console.error("[BetterSEQTA+] Error fetching notifications:", error); console.error("[BetterSEQTA+] Error fetching notifications:", error);
api.storage.consecutiveErrors = (api.storage.consecutiveErrors || 0) + 1;
} }
}; };
const getNextInterval = () => {
// Exponential backoff on errors, max 5 minutes
const errorMultiplier = Math.min(Math.pow(2, api.storage.consecutiveErrors || 0), 10);
return Math.min(baseInterval * errorMultiplier, maxInterval);
};
const startPolling = () => { const startPolling = () => {
if (pollInterval) return; // Already polling if (pollInterval) return; // Already polling
checkNotifications(); checkNotifications();
pollInterval = window.setInterval(checkNotifications, 30000);
const scheduleNext = () => {
const interval = getNextInterval();
pollInterval = window.setTimeout(() => {
checkNotifications().then(() => {
if (pollInterval) { // Only continue if not stopped
scheduleNext();
}
});
}, interval);
};
scheduleNext();
}; };
const stopPolling = () => { const stopPolling = () => {
if (pollInterval) { if (pollInterval) {
window.clearInterval(pollInterval); window.clearTimeout(pollInterval);
pollInterval = null; pollInterval = null;
const alertDiv = document.querySelector( const alertDiv = document.querySelector(
"[class*='notifications__bubble___']", "[class*='notifications__bubble___']",
@@ -85,12 +119,27 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
} }
}; };
// Listen for visibility changes to pause/resume polling
const handleVisibilityChange = () => {
isVisible = !document.hidden;
if (isVisible && !pollInterval) {
// Resume polling when tab becomes visible
const alertDiv = document.querySelector("[class*='notifications__bubble___']");
if (alertDiv) {
startPolling();
}
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
api.seqta.onMount("[class*='notifications__bubble___']", (_) => { api.seqta.onMount("[class*='notifications__bubble___']", (_) => {
startPolling(); startPolling();
}); });
return () => { return () => {
stopPolling(); stopPolling();
document.removeEventListener('visibilitychange', handleVisibilityChange);
}; };
}, },
}; };
+52 -22
View File
@@ -4,7 +4,8 @@ import { waitForElm } from "./waitForElm";
let timetableObserver: MutationObserver | null = null; let timetableObserver: MutationObserver | null = null;
let isOnTimetablePage = false; let isOnTimetablePage = false;
let urlCheckInterval: number | null = null; let isInitialized = false;
let abortController: AbortController | null = null;
function updateTimeElements(): void { function updateTimeElements(): void {
if (!settingsState.timeFormat || settingsState.timeFormat !== "12") return; if (!settingsState.timeFormat || settingsState.timeFormat !== "12") return;
@@ -90,30 +91,54 @@ function stopTimetableMonitoring(): void {
} }
} }
function startUrlMonitoring(): void { function handleUrlChange(): void {
if (urlCheckInterval) return; const currentlyOnTimetable = checkIfOnTimetablePage();
// Check URL every 100ms when on timetable page if (currentlyOnTimetable !== isOnTimetablePage) {
urlCheckInterval = window.setInterval(() => { isOnTimetablePage = currentlyOnTimetable;
const currentlyOnTimetable = checkIfOnTimetablePage();
if (currentlyOnTimetable !== isOnTimetablePage) { if (isOnTimetablePage) {
isOnTimetablePage = currentlyOnTimetable; // Wait a bit for the page to load, then start monitoring
setTimeout(() => {
if (isOnTimetablePage) { updateTimeElements();
// Wait a bit for the page to load, then start monitoring startTimetableMonitoring();
setTimeout(() => { }, 100);
updateTimeElements(); } else {
startTimetableMonitoring(); stopTimetableMonitoring();
}, 100);
} else {
stopTimetableMonitoring();
}
} else if (isOnTimetablePage) {
// Even if we're still on timetable page, update times in case navigation happened
updateTimeElements();
} }
}, 100); } else if (isOnTimetablePage) {
// Even if we're still on timetable page, update times in case navigation happened
updateTimeElements();
}
}
function startUrlMonitoring(): void {
if (isInitialized) return;
isInitialized = true;
// Create abort controller for cleanup
abortController = new AbortController();
const signal = abortController.signal;
// Listen for hash changes (more efficient than polling)
window.addEventListener('hashchange', handleUrlChange, { signal });
window.addEventListener('popstate', handleUrlChange, { signal });
// Initial check
handleUrlChange();
}
function stopUrlMonitoring(): void {
if (!isInitialized) return;
isInitialized = false;
// Abort all event listeners at once
if (abortController) {
abortController.abort();
abortController = null;
}
stopTimetableMonitoring();
} }
export async function updateTimetableTimes(): Promise<void> { export async function updateTimetableTimes(): Promise<void> {
@@ -144,3 +169,8 @@ if (typeof window !== "undefined") {
// Start URL monitoring immediately // Start URL monitoring immediately
startUrlMonitoring(); startUrlMonitoring();
} }
// Cleanup function for when the module is unloaded
export function cleanup(): void {
stopUrlMonitoring();
}