mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
feat: add types to storage api
This commit is contained in:
@@ -9,7 +9,12 @@ interface NotificationCollectorSettings extends PluginSettings {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationCollectorPlugin: Plugin<NotificationCollectorSettings> = {
|
interface NotificationCollectorStorage {
|
||||||
|
lastNotificationCount: number;
|
||||||
|
lastCheckedTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationCollectorPlugin: Plugin<NotificationCollectorSettings, NotificationCollectorStorage> = {
|
||||||
id: 'notificationCollector',
|
id: 'notificationCollector',
|
||||||
name: 'Notification Collector',
|
name: 'Notification Collector',
|
||||||
description: 'Collects and displays SEQTA notifications',
|
description: 'Collects and displays SEQTA notifications',
|
||||||
@@ -26,8 +31,19 @@ const notificationCollectorPlugin: Plugin<NotificationCollectorSettings> = {
|
|||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
let pollInterval: number | null = null;
|
let pollInterval: number | null = null;
|
||||||
|
|
||||||
|
// Store last notification count in storage
|
||||||
|
if (!api.storage.lastNotificationCount) {
|
||||||
|
api.storage.lastNotificationCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const checkNotifications = async () => {
|
const checkNotifications = async () => {
|
||||||
try {
|
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?`, {
|
const response = await fetch(`${location.origin}/seqta/student/heartbeat?`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -40,10 +56,14 @@ const notificationCollectorPlugin: Plugin<NotificationCollectorSettings> = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
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) {
|
if (alertDiv) {
|
||||||
alertDiv.textContent = data.payload.notifications.length.toString();
|
alertDiv.textContent = notificationCount.toString();
|
||||||
} else {
|
} else {
|
||||||
console.info("[BetterSEQTA+] No notifications currently");
|
console.info("[BetterSEQTA+] No notifications currently");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStorageAPI(pluginId: string): StorageAPI {
|
function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in keyof T]: T[K] } {
|
||||||
const prefix = `plugin.${pluginId}.storage.`;
|
const prefix = `plugin.${pluginId}.storage.`;
|
||||||
const cache: Record<string, any> = {};
|
const cache: Record<string, any> = {};
|
||||||
const listeners = new Map<string, Set<(value: any) => void>>();
|
const listeners = new Map<string, Set<(value: any) => void>>();
|
||||||
@@ -152,28 +152,17 @@ function createStorageAPI(pluginId: string): StorageAPI {
|
|||||||
// Create the proxy for direct property access
|
// Create the proxy for direct property access
|
||||||
return new Proxy(cache, {
|
return new Proxy(cache, {
|
||||||
get(target, prop: string) {
|
get(target, prop: string) {
|
||||||
if (prop === 'get') {
|
|
||||||
return async <T>(key: string) => {
|
|
||||||
return target[key] as T || null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (prop === 'set') {
|
|
||||||
return async <T>(key: string, value: T) => {
|
|
||||||
target[key] = value;
|
|
||||||
await browser.storage.local.set({ [prefix + key]: value });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (prop === 'onChange') {
|
if (prop === 'onChange') {
|
||||||
return (key: string, callback: (value: any) => void) => {
|
return (key: keyof T, callback: (value: T[keyof T]) => void) => {
|
||||||
if (!listeners.has(key)) {
|
if (!listeners.has(key as string)) {
|
||||||
listeners.set(key, new Set());
|
listeners.set(key as string, new Set());
|
||||||
}
|
}
|
||||||
listeners.get(key)!.add(callback);
|
listeners.get(key as string)!.add(callback);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (prop === 'offChange') {
|
if (prop === 'offChange') {
|
||||||
return (key: string, callback: (value: any) => void) => {
|
return (key: keyof T, callback: (value: T[keyof T]) => void) => {
|
||||||
listeners.get(key)?.delete(callback);
|
listeners.get(key as string)?.delete(callback);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (prop === 'loaded') {
|
if (prop === 'loaded') {
|
||||||
@@ -184,7 +173,7 @@ function createStorageAPI(pluginId: string): StorageAPI {
|
|||||||
return target[prop];
|
return target[prop];
|
||||||
},
|
},
|
||||||
set(target, prop: string, value: any) {
|
set(target, prop: string, value: any) {
|
||||||
if (['get', 'set', 'onChange', 'offChange', 'loaded'].includes(prop)) {
|
if (['onChange', 'offChange', 'loaded'].includes(prop)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +186,7 @@ function createStorageAPI(pluginId: string): StorageAPI {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}) as StorageAPI;
|
}) as StorageAPI<T> & { [K in keyof T]: T[K] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEventsAPI(pluginId: string): EventsAPI {
|
function createEventsAPI(pluginId: string): EventsAPI {
|
||||||
@@ -219,11 +208,11 @@ function createEventsAPI(pluginId: string): EventsAPI {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPluginAPI<T extends PluginSettings>(plugin: Plugin<T>): PluginAPI<T> {
|
export function createPluginAPI<T extends PluginSettings, S = any>(plugin: Plugin<T, S>): PluginAPI<T, S> {
|
||||||
return {
|
return {
|
||||||
seqta: createSEQTAAPI(),
|
seqta: createSEQTAAPI(),
|
||||||
settings: createSettingsAPI(plugin),
|
settings: createSettingsAPI(plugin),
|
||||||
storage: createStorageAPI(plugin.id),
|
storage: createStorageAPI<S>(plugin.id),
|
||||||
events: createEventsAPI(plugin.id),
|
events: createEventsAPI(plugin.id),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ import { createPluginAPI } from './createAPI';
|
|||||||
|
|
||||||
export class PluginManager {
|
export class PluginManager {
|
||||||
private static instance: PluginManager;
|
private static instance: PluginManager;
|
||||||
private plugins: Map<string, Plugin<any>> = new Map();
|
private plugins: Map<string, Plugin<any, any>> = new Map();
|
||||||
private runningPlugins: Map<string, boolean> = new Map();
|
private runningPlugins: Map<string, boolean> = new Map();
|
||||||
private eventBacklog: Map<string, any[]> = new Map();
|
private eventBacklog: Map<string, any[]> = new Map();
|
||||||
private cleanupFunctions: Map<string, () => void> = new Map();
|
private cleanupFunctions: Map<string, () => void> = new Map();
|
||||||
@@ -45,7 +45,7 @@ export class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerPlugin<T extends PluginSettings>(plugin: Plugin<T>): void {
|
public registerPlugin<T extends PluginSettings, S>(plugin: Plugin<T, S>): void {
|
||||||
if (this.plugins.has(plugin.id)) {
|
if (this.plugins.has(plugin.id)) {
|
||||||
throw new Error(`Plugin with id "${plugin.id}" is already registered`);
|
throw new Error(`Plugin with id "${plugin.id}" is already registered`);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-10
@@ -57,13 +57,25 @@ export interface SEQTAAPI {
|
|||||||
onPageChange: (callback: (page: string) => void) => void;
|
onPageChange: (callback: (page: string) => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageAPI {
|
export interface StorageAPI<T = any> {
|
||||||
get: <T>(key: string) => Promise<T | null>;
|
/**
|
||||||
set: <T>(key: string, value: T) => Promise<void>;
|
* Register a callback to be called when a storage value changes
|
||||||
onChange: (key: string, callback: (value: any) => void) => void;
|
*/
|
||||||
offChange: (key: string, callback: (value: any) => void) => void;
|
onChange: <K extends keyof T>(key: K, callback: (value: T[K]) => void) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a previously registered callback
|
||||||
|
*/
|
||||||
|
offChange: <K extends keyof T>(key: K, callback: (value: T[K]) => void) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise that resolves when storage values are loaded
|
||||||
|
*/
|
||||||
loaded: Promise<void>;
|
loaded: Promise<void>;
|
||||||
[key: string]: any;
|
}
|
||||||
|
|
||||||
|
export type TypedStorageAPI<T> = StorageAPI<T> & {
|
||||||
|
[K in keyof T]: T[K];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventsAPI {
|
export interface EventsAPI {
|
||||||
@@ -71,18 +83,18 @@ export interface EventsAPI {
|
|||||||
emit: (event: string, ...args: any[]) => void;
|
emit: (event: string, ...args: any[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginAPI<T extends PluginSettings> {
|
export interface PluginAPI<T extends PluginSettings, S = any> {
|
||||||
seqta: SEQTAAPI;
|
seqta: SEQTAAPI;
|
||||||
settings: SettingsAPI<T>;
|
settings: SettingsAPI<T>;
|
||||||
storage: StorageAPI;
|
storage: TypedStorageAPI<S>;
|
||||||
events: EventsAPI;
|
events: EventsAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Plugin<T extends PluginSettings = PluginSettings> {
|
export interface Plugin<T extends PluginSettings = PluginSettings, S = any> {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
version: string;
|
version: string;
|
||||||
settings: T;
|
settings: T;
|
||||||
run: (api: PluginAPI<T>) => void | Promise<void> | (() => void) | Promise<() => void>;
|
run: (api: PluginAPI<T, S>) => void | Promise<void> | (() => void) | Promise<() => void>;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user