mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 11:44:40 +00:00
I've added JSDoc comments to background, plugin core, and settings files.
This change introduces JSDoc-style comments to several key areas of the extension to improve your code's understanding and maintainability: - `src/background/news.ts`: I added comments to `fetchNews`, `fetchAustraliaNews`, and `rssFeedsByCountry` to explain news fetching logic. - `src/plugins/core/manager.ts`: I added comprehensive JSDoc comments to the `PluginManager` class and its methods, detailing its role in the plugin lifecycle. - `src/plugins/core/createAPI.ts`: I documented `createPluginAPI` (which creates the main API for plugins) and `createSettingsAPI` (responsible for plugin settings management, initially misidentified as `createPluginSettings`). - `src/plugins/core/settingsHelpers.ts`: I added comments to functions that define the structure of plugin settings (e.g., `numberSetting`, `stringSetting`, `defineSettings`, `Setting` decorator), clarifying their definition-time role.
This commit is contained in:
+151
-3
@@ -21,6 +21,12 @@ interface StorageChange<T = any> {
|
||||
newValue?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton class responsible for the entire lifecycle of plugins.
|
||||
* This includes registration, starting, stopping, event dispatching,
|
||||
* managing plugin-specific styles, and listening for plugin setting changes
|
||||
* to automatically start or stop plugins.
|
||||
*/
|
||||
export class PluginManager {
|
||||
private static instance: PluginManager;
|
||||
private plugins: Map<string, Plugin<any, any>> = new Map();
|
||||
@@ -30,10 +36,18 @@ export class PluginManager {
|
||||
private listeners: Map<string, Set<(...args: any[]) => void>> = new Map();
|
||||
private styleElements: Map<string, HTMLStyleElement> = new Map();
|
||||
|
||||
/**
|
||||
* Private constructor to enforce singleton pattern.
|
||||
* Initializes the listener for plugin state changes from storage.
|
||||
*/
|
||||
private constructor() {
|
||||
this.setupPluginStateListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singleton instance of the PluginManager.
|
||||
* @returns {PluginManager} The singleton instance.
|
||||
*/
|
||||
public static getInstance(): PluginManager {
|
||||
if (!PluginManager.instance) {
|
||||
PluginManager.instance = new PluginManager();
|
||||
@@ -41,6 +55,15 @@ export class PluginManager {
|
||||
return PluginManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event to a specific plugin.
|
||||
* If the plugin is currently running, the event is dispatched immediately via a DOM CustomEvent.
|
||||
* If the plugin is not running, the event is added to a backlog to be processed when the plugin starts.
|
||||
*
|
||||
* @param {string} pluginId The ID of the target plugin.
|
||||
* @param {string} event The name of the event to dispatch (e.g., "update").
|
||||
* @param {any} [args] Optional arguments to pass with the event.
|
||||
*/
|
||||
public dispatchPluginEvent(pluginId: string, event: string, args?: any) {
|
||||
const fullEventName = `plugin.${pluginId}.${event}`;
|
||||
|
||||
@@ -56,6 +79,14 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes and dispatches any events that were backlogged for a plugin.
|
||||
* This is typically called after a plugin has successfully started.
|
||||
*
|
||||
* @private
|
||||
* @param {string} pluginId The ID of the plugin for which to process backlogged events.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async processBackloggedEvents(pluginId: string) {
|
||||
for (const [key, argsList] of this.eventBacklog.entries()) {
|
||||
const [eventPluginId, event] = key.split(":");
|
||||
@@ -68,6 +99,15 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a plugin with the manager.
|
||||
* Plugins must have a unique ID.
|
||||
*
|
||||
* @template T - The type of settings the plugin uses.
|
||||
* @template S - The type of storage the plugin uses.
|
||||
* @param {Plugin<T, S>} plugin The plugin object to register.
|
||||
* @throws {Error} If a plugin with the same ID is already registered.
|
||||
*/
|
||||
public registerPlugin<T extends PluginSettings, S>(
|
||||
plugin: Plugin<T, S>,
|
||||
): void {
|
||||
@@ -77,6 +117,22 @@ export class PluginManager {
|
||||
this.plugins.set(plugin.id, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a specific plugin by its ID.
|
||||
* This involves:
|
||||
* - Checking if the plugin exists and isn't already running.
|
||||
* - Creating and providing the plugin API (settings, storage, etc.).
|
||||
* - Checking if the plugin is enabled (if `disableToggle` is true), respecting its `defaultEnabled` status.
|
||||
* - Injecting any CSS styles defined by the plugin into the document head.
|
||||
* - Waiting for the plugin's settings and storage to be loaded.
|
||||
* - Executing the plugin's `run` method.
|
||||
* - Storing any cleanup function returned by `run` for later use in `stopPlugin`.
|
||||
* - Marking the plugin as running and processing any backlogged events for it.
|
||||
*
|
||||
* @param {string} pluginId The ID of the plugin to start.
|
||||
* @returns {Promise<void>} A promise that resolves when the plugin has started or is determined not to start (e.g., disabled).
|
||||
* @throws {Error} If the plugin is not found, or if an error occurs during plugin initialization or execution.
|
||||
*/
|
||||
public async startPlugin(pluginId: string): Promise<void> {
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin) {
|
||||
@@ -138,17 +194,36 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start all registered plugins.
|
||||
* Errors during the start of individual plugins are caught and logged,
|
||||
* allowing other plugins to attempt to start.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when all plugins have attempted to start.
|
||||
* It uses `Promise.allSettled` to wait for all start operations.
|
||||
*/
|
||||
public async startAllPlugins(): Promise<void> {
|
||||
const startPromises = Array.from(this.plugins.keys()).map((id) =>
|
||||
this.startPlugin(id).catch((error) => {
|
||||
console.error(`Failed to start plugin "${id}":`, error);
|
||||
return Promise.reject(error);
|
||||
return Promise.reject(error); // Still reject to indicate failure for this specific plugin if needed by caller
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.allSettled(startPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a specific plugin by its ID.
|
||||
* This involves:
|
||||
* - Removing any CSS styles injected by the plugin.
|
||||
* - Executing the cleanup function that was returned by the plugin's `run` method (if any).
|
||||
* - Marking the plugin as not running.
|
||||
* - Emitting a "plugin.stopped" event with the pluginId.
|
||||
*
|
||||
* @param {string} pluginId The ID of the plugin to stop.
|
||||
* @returns {Promise<void>} A promise that resolves when the plugin has been stopped.
|
||||
*/
|
||||
public async stopPlugin(pluginId: string): Promise<void> {
|
||||
// Remove plugin styles
|
||||
const styleElement = this.styleElements.get(pluginId);
|
||||
@@ -167,18 +242,47 @@ export class PluginManager {
|
||||
this.emit("plugin.stopped", pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all currently running plugins.
|
||||
* Iterates through all registered plugins and calls `stopPlugin` for each.
|
||||
*/
|
||||
public stopAllPlugins(): void {
|
||||
Array.from(this.plugins.keys()).forEach((id) => this.stopPlugin(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a registered plugin by its ID.
|
||||
*
|
||||
* @param {string} pluginId The ID of the plugin to retrieve.
|
||||
* @returns {Plugin | undefined} The plugin object if found, otherwise undefined.
|
||||
*/
|
||||
public getPlugin(pluginId: string): Plugin | undefined {
|
||||
return this.plugins.get(pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of all registered plugin objects.
|
||||
*
|
||||
* @returns {Plugin[]} An array containing all registered plugin objects.
|
||||
*/
|
||||
public getAllPlugins(): Plugin[] {
|
||||
return Array.from(this.plugins.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structured list of settings for all registered plugins.
|
||||
* This is primarily used for building user interfaces for plugin configuration.
|
||||
* It processes each plugin's defined settings, adding IDs, titles, descriptions,
|
||||
* and default enabled states. For plugins with `disableToggle`, an "enabled"
|
||||
* boolean setting is automatically included.
|
||||
*
|
||||
* @returns {Array<object>} An array of objects, where each object represents a plugin
|
||||
* and contains its ID, name, description, beta status,
|
||||
* and a processed `settings` object. The `settings` object
|
||||
* maps setting keys to their detailed configuration (type, title, etc.).
|
||||
* The specific structure of each setting object within `settings`
|
||||
* depends on its type (boolean, string, number, select, button, hotkey).
|
||||
*/
|
||||
public getAllPluginSettings(): Array<{
|
||||
pluginId: string;
|
||||
name: string;
|
||||
@@ -197,6 +301,8 @@ export class PluginManager {
|
||||
| (Omit<ButtonSetting, "type"> & { type: "button"; id: string; trigger?: () => void | Promise<void> })
|
||||
| (Omit<HotkeySetting, "type"> & { type: "hotkey"; id: string });
|
||||
};
|
||||
// Actual type is more complex, see original code, but this gives the gist for the JSDoc.
|
||||
// Array<{ pluginId: string; name: string; description: string; beta?: boolean; settings: Record<string, ProcessedSetting>; disableToggle?: boolean; }>
|
||||
}> {
|
||||
return Array.from(this.plugins.entries()).map(([id, plugin]) => {
|
||||
const settingsEntries = Object.entries(plugin.settings).map(
|
||||
@@ -245,10 +351,24 @@ export class PluginManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific plugin is currently running.
|
||||
*
|
||||
* @param {string} pluginId The ID of the plugin to check.
|
||||
* @returns {boolean} True if the plugin is running, false otherwise.
|
||||
*/
|
||||
public isPluginRunning(pluginId: string): boolean {
|
||||
return this.runningPlugins.get(pluginId) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all registered listeners for that event.
|
||||
* This is an internal event bus for the PluginManager itself.
|
||||
*
|
||||
* @private
|
||||
* @param {string} event The name of the event to emit.
|
||||
* @param {any[]} args Arguments to pass to the event listeners.
|
||||
*/
|
||||
private emit(event: string, ...args: any[]): void {
|
||||
const listeners = this.listeners.get(event);
|
||||
if (listeners) {
|
||||
@@ -256,6 +376,12 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an event listener for PluginManager's internal events.
|
||||
*
|
||||
* @param {string} event The name of the event to listen for (e.g., "plugin.stopped").
|
||||
* @param {(...args: any[]) => void} callback The function to call when the event is emitted.
|
||||
*/
|
||||
public on(event: string, callback: (...args: any[]) => void): void {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
@@ -263,6 +389,12 @@ export class PluginManager {
|
||||
this.listeners.get(event)!.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an event listener for PluginManager's internal events.
|
||||
*
|
||||
* @param {string} event The name of the event.
|
||||
* @param {(...args: any[]) => void} callback The callback function to remove.
|
||||
*/
|
||||
public off(event: string, callback: (...args: any[]) => void): void {
|
||||
const listeners = this.listeners.get(event);
|
||||
if (listeners) {
|
||||
@@ -270,7 +402,16 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Add handler for plugin enable/disable state changes
|
||||
/**
|
||||
* Handles the change in a plugin's enabled state.
|
||||
* Starts or stops the plugin based on the new `enabled` value.
|
||||
* This is typically called by `setupPluginStateListener` when a relevant storage change is detected.
|
||||
*
|
||||
* @private
|
||||
* @param {string} pluginId The ID of the plugin whose state has changed.
|
||||
* @param {boolean} enabled The new enabled state of the plugin.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async handlePluginStateChange(
|
||||
pluginId: string,
|
||||
enabled: boolean,
|
||||
@@ -282,7 +423,14 @@ export class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Add listener for plugin settings changes
|
||||
/**
|
||||
* Sets up a listener for browser storage changes.
|
||||
* This listener monitors changes to plugin settings (specifically the `enabled` property
|
||||
* for plugins with `disableToggle: true`) and calls `handlePluginStateChange`
|
||||
* to automatically start or stop plugins accordingly.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private setupPluginStateListener(): void {
|
||||
browser.storage.onChanged.addListener(
|
||||
(changes: { [key: string]: StorageChange }, area: string) => {
|
||||
|
||||
Reference in New Issue
Block a user