diff --git a/src/plugins/built-in/notificationCollector/index.ts b/src/plugins/built-in/notificationCollector/index.ts index 7c26b41c..fe9644af 100644 --- a/src/plugins/built-in/notificationCollector/index.ts +++ b/src/plugins/built-in/notificationCollector/index.ts @@ -9,7 +9,12 @@ interface NotificationCollectorSettings extends PluginSettings { }; } -const notificationCollectorPlugin: Plugin = { +interface NotificationCollectorStorage { + lastNotificationCount: number; + lastCheckedTime: string; +} + +const notificationCollectorPlugin: Plugin = { id: 'notificationCollector', name: 'Notification Collector', description: 'Collects and displays SEQTA notifications', @@ -26,8 +31,19 @@ const notificationCollectorPlugin: Plugin = { run: async (api) => { let pollInterval: number | null = null; + // Store last notification count in storage + if (!api.storage.lastNotificationCount) { + api.storage.lastNotificationCount = 0; + } + const checkNotifications = async () => { try { + const alertDiv = document.querySelector(".notifications__bubble___1EkSQ") as HTMLElement; + + if (api.storage.lastNotificationCount !== 0) { + alertDiv.textContent = api.storage.lastNotificationCount.toString(); + } + const response = await fetch(`${location.origin}/seqta/student/heartbeat?`, { method: 'POST', headers: { @@ -40,10 +56,14 @@ const notificationCollectorPlugin: Plugin = { }); const data = await response.json(); - const alertDiv = document.querySelector(".notifications__bubble___1EkSQ") as HTMLElement; + + // Store notification count for history + const notificationCount = data.payload.notifications.length; + api.storage.lastNotificationCount = notificationCount; + api.storage.lastCheckedTime = new Date().toISOString(); if (alertDiv) { - alertDiv.textContent = data.payload.notifications.length.toString(); + alertDiv.textContent = notificationCount.toString(); } else { console.info("[BetterSEQTA+] No notifications currently"); } diff --git a/src/plugins/core/createAPI.ts b/src/plugins/core/createAPI.ts index 9c406c80..b1e1cf7d 100644 --- a/src/plugins/core/createAPI.ts +++ b/src/plugins/core/createAPI.ts @@ -112,7 +112,7 @@ function createSettingsAPI(plugin: Plugin): Setting return proxy; } -function createStorageAPI(pluginId: string): StorageAPI { +function createStorageAPI(pluginId: string): StorageAPI & { [K in keyof T]: T[K] } { const prefix = `plugin.${pluginId}.storage.`; const cache: Record = {}; const listeners = new Map void>>(); @@ -152,28 +152,17 @@ function createStorageAPI(pluginId: string): StorageAPI { // Create the proxy for direct property access return new Proxy(cache, { get(target, prop: string) { - if (prop === 'get') { - return async (key: string) => { - return target[key] as T || null; - }; - } - if (prop === 'set') { - return async (key: string, value: T) => { - target[key] = value; - await browser.storage.local.set({ [prefix + key]: value }); - }; - } if (prop === 'onChange') { - return (key: string, callback: (value: any) => void) => { - if (!listeners.has(key)) { - listeners.set(key, new Set()); + return (key: keyof T, callback: (value: T[keyof T]) => void) => { + if (!listeners.has(key as string)) { + listeners.set(key as string, new Set()); } - listeners.get(key)!.add(callback); + listeners.get(key as string)!.add(callback); }; } if (prop === 'offChange') { - return (key: string, callback: (value: any) => void) => { - listeners.get(key)?.delete(callback); + return (key: keyof T, callback: (value: T[keyof T]) => void) => { + listeners.get(key as string)?.delete(callback); }; } if (prop === 'loaded') { @@ -184,7 +173,7 @@ function createStorageAPI(pluginId: string): StorageAPI { return target[prop]; }, set(target, prop: string, value: any) { - if (['get', 'set', 'onChange', 'offChange', 'loaded'].includes(prop)) { + if (['onChange', 'offChange', 'loaded'].includes(prop)) { return false; } @@ -197,7 +186,7 @@ function createStorageAPI(pluginId: string): StorageAPI { return true; } - }) as StorageAPI; + }) as StorageAPI & { [K in keyof T]: T[K] }; } function createEventsAPI(pluginId: string): EventsAPI { @@ -219,11 +208,11 @@ function createEventsAPI(pluginId: string): EventsAPI { }; } -export function createPluginAPI(plugin: Plugin): PluginAPI { +export function createPluginAPI(plugin: Plugin): PluginAPI { return { seqta: createSEQTAAPI(), settings: createSettingsAPI(plugin), - storage: createStorageAPI(plugin.id), + storage: createStorageAPI(plugin.id), events: createEventsAPI(plugin.id), }; } \ No newline at end of file diff --git a/src/plugins/core/manager.ts b/src/plugins/core/manager.ts index f7caafb3..78017c90 100644 --- a/src/plugins/core/manager.ts +++ b/src/plugins/core/manager.ts @@ -3,7 +3,7 @@ import { createPluginAPI } from './createAPI'; export class PluginManager { private static instance: PluginManager; - private plugins: Map> = new Map(); + private plugins: Map> = new Map(); private runningPlugins: Map = new Map(); private eventBacklog: Map = new Map(); private cleanupFunctions: Map void> = new Map(); @@ -45,7 +45,7 @@ export class PluginManager { } } - public registerPlugin(plugin: Plugin): void { + public registerPlugin(plugin: Plugin): void { if (this.plugins.has(plugin.id)) { throw new Error(`Plugin with id "${plugin.id}" is already registered`); } diff --git a/src/plugins/core/types.ts b/src/plugins/core/types.ts index 657238e3..fc3c9145 100644 --- a/src/plugins/core/types.ts +++ b/src/plugins/core/types.ts @@ -57,13 +57,25 @@ export interface SEQTAAPI { onPageChange: (callback: (page: string) => void) => void; } -export interface StorageAPI { - get: (key: string) => Promise; - set: (key: string, value: T) => Promise; - onChange: (key: string, callback: (value: any) => void) => void; - offChange: (key: string, callback: (value: any) => void) => void; +export interface StorageAPI { + /** + * Register a callback to be called when a storage value changes + */ + onChange: (key: K, callback: (value: T[K]) => void) => void; + + /** + * Remove a previously registered callback + */ + offChange: (key: K, callback: (value: T[K]) => void) => void; + + /** + * Promise that resolves when storage values are loaded + */ loaded: Promise; - [key: string]: any; +} + +export type TypedStorageAPI = StorageAPI & { + [K in keyof T]: T[K]; } export interface EventsAPI { @@ -71,18 +83,18 @@ export interface EventsAPI { emit: (event: string, ...args: any[]) => void; } -export interface PluginAPI { +export interface PluginAPI { seqta: SEQTAAPI; settings: SettingsAPI; - storage: StorageAPI; + storage: TypedStorageAPI; events: EventsAPI; } -export interface Plugin { +export interface Plugin { id: string; name: string; description: string; version: string; settings: T; - run: (api: PluginAPI) => void | Promise | (() => void) | Promise<() => void>; + run: (api: PluginAPI) => void | Promise | (() => void) | Promise<() => void>; } \ No newline at end of file