UNPKG

svelte-firebase-upload

Version:

Enterprise-grade file upload manager for Svelte with Firebase Storage integration, featuring concurrent uploads, resumable transfers, validation, health monitoring, and plugin system

193 lines (192 loc) 7.83 kB
export class PluginSystem { _plugins = new Map(); _manager; _PLUGIN_TIMEOUT = 30000; // 30 seconds default timeout constructor(manager) { this._manager = manager; } // Register a plugin async registerPlugin(plugin, config = {}) { const pluginConfig = { enabled: true, priority: 0, ...config }; // Check if plugin is already registered if (this._plugins.has(plugin.name)) { throw new Error(`Plugin '${plugin.name}' is already registered`); } // Register plugin this._plugins.set(plugin.name, { plugin, config: pluginConfig }); // Initialize plugin if enabled if (pluginConfig.enabled && plugin.onInitialize) { try { await this.callPluginMethod(plugin, 'onInitialize', [this._manager]); } catch (error) { console.error(`Failed to initialize plugin '${plugin.name}':`, error); } } } // Unregister a plugin async unregisterPlugin(pluginName) { const entry = this._plugins.get(pluginName); if (!entry) { throw new Error(`Plugin '${pluginName}' is not registered`); } const { plugin } = entry; // Call destroy hook if (plugin.onDestroy) { try { await this.callPluginMethod(plugin, 'onDestroy', []); } catch (error) { console.error(`Failed to destroy plugin '${pluginName}':`, error); } } // Remove from registry this._plugins.delete(pluginName); } // Enable/disable a plugin async setPluginEnabled(pluginName, enabled) { const entry = this._plugins.get(pluginName); if (!entry) { throw new Error(`Plugin '${pluginName}' is not registered`); } entry.config.enabled = enabled; if (enabled && entry.plugin.onInitialize) { try { await this.callPluginMethod(entry.plugin, 'onInitialize', [this._manager]); } catch (error) { console.error(`Failed to initialize plugin '${pluginName}':`, error); } } } // Get plugin by name getPlugin(pluginName) { const entry = this._plugins.get(pluginName); return entry ? entry.plugin : null; } // Get all registered plugins getAllPlugins() { const result = Array.from(this._plugins.entries()).map(([name, entry]) => ({ name, plugin: entry.plugin, config: entry.config })); return result; } // Get enabled plugins getEnabledPlugins() { const result = this.getAllPlugins().filter(({ config }) => config.enabled); return result; } /** * Execute a plugin method with timeout protection. * * @param operation - The async operation to execute * @param timeoutMs - Timeout in milliseconds * @param context - Context for error messages * @returns Promise that resolves with the operation result or rejects on timeout */ async _withTimeout(operation, timeoutMs, context) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`Plugin operation '${context}' timed out after ${timeoutMs}ms`)); }, timeoutMs); operation() .then(result => { clearTimeout(timer); resolve(result); }) .catch(error => { clearTimeout(timer); reject(error); }); }); } // Call a plugin method with timeout protection async callPluginMethod(plugin, methodName, args) { const method = plugin[methodName]; if (typeof method === 'function') { try { const result = await this._withTimeout(() => Promise.resolve(method.apply(plugin, args)), this._PLUGIN_TIMEOUT, `${plugin.name}.${methodName}`); return result; } catch (error) { console.error('[PluginSystem] callPluginMethod error:', plugin.name, methodName, args, error); if (plugin.onError && methodName !== 'onError') { try { await this._withTimeout(() => Promise.resolve(plugin.onError(error, { methodName, args })), 5000, // Shorter timeout for error handlers `${plugin.name}.onError`); } catch (errorHandlerError) { console.error(`Error in plugin '${plugin.name}' error handler:`, errorHandlerError); } } throw error; } } return undefined; } // Emit an event to all plugins async emitEvent(eventType, ...args) { const enabledPlugins = this.getEnabledPlugins(); // Sort by priority (higher priority first) const sortedPlugins = enabledPlugins.sort((a, b) => b.config.priority - a.config.priority); for (const { plugin } of sortedPlugins) { const method = plugin[eventType]; if (typeof method === 'function') { try { await this._withTimeout(() => Promise.resolve(method.apply(plugin, args)), this._PLUGIN_TIMEOUT, `${plugin.name}.${eventType}`); } catch (error) { console.error(`[PluginSystem] Error in plugin '${plugin.name}' event handler for '${eventType}':`, error); // Call error handler if available if (plugin.onError) { try { await this._withTimeout(() => Promise.resolve(plugin.onError(error, { eventType, args })), 5000, `${plugin.name}.onError`); } catch (errorHandlerError) { console.error(`Error in plugin '${plugin.name}' error handler:`, errorHandlerError); } } } } } } // Execute a pipeline of plugins (for hooks that can modify data) async executePipeline(eventType, initialValue, ...args) { const enabledPlugins = this.getEnabledPlugins(); // Sort by priority (higher priority first) const sortedPlugins = enabledPlugins.sort((a, b) => b.config.priority - a.config.priority); let result = initialValue; for (const { plugin } of sortedPlugins) { const method = plugin[eventType]; if (typeof method === 'function') { try { const pluginResult = await this._withTimeout(() => method.apply(plugin, [result, ...args]), this._PLUGIN_TIMEOUT, `${plugin.name}.${eventType}`); if (pluginResult !== undefined) { result = pluginResult; } } catch (error) { console.error(`[PluginSystem] Error in plugin '${plugin.name}' pipeline for '${eventType}':`, error); if (plugin.onError) { try { await this._withTimeout(() => Promise.resolve(plugin.onError(error, { eventType, initialValue, args })), 5000, `${plugin.name}.onError`); } catch (errorHandlerError) { console.error(`Error in plugin '${plugin.name}' error handler:`, errorHandlerError); } } } } } return result; } }