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
JavaScript
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;
}
}