Plugin Runtime System
The plugin runtime is the core engine that manages plugin discovery, loading, activation, and disposal within the Electron-based host application.
Repository Structureโ
The plugin runtime is organized as a cohesive module within the desktop library:
plugin-system/
โโโ data-access/ # Core plugin management logic
โ โโโ strategies/ # Download strategy implementations
โ โโโ dialog/ # UI dialogs for plugin operations
โ โโโ plugin-manager.ts
โ โโโ download-context.factory.ts
โโโ database/ # Persistence layer
โ โโโ plugin-metadata.service.ts
โโโ events/ # Event management
โ โโโ plugin-event.manager.ts
โ โโโ plugin.event.ts
โโโ shared/ # Shared utilities and interfaces
โโโ concretes/ # Concrete implementations
โโโ enumerations/ # Enums and constants
โโโ interfaces/ # TypeScript interfaces
โโโ utils/ # Utility functions
The PluginManagerโ
The PluginManager constitutes the central orchestrator of the plugin runtime. Implemented as a singleton, it is responsible for the complete lifecycle of every installed plugin: installation from multiple source types, state management (activation and deactivation), persistence of plugin metadata, and coordination of the event notification subsystem.
Upon application startup, the PluginManager invokes loadPlugins(), which queries the persistent metadata store for all registered plugins and dynamically loads their entry modules using a lazy-loading utility backed by Node.js require().
class PluginManager {
static getInstance(): IPluginManager;
downloadPlugin<U>(config: U): Promise<IPluginMetadata>;
installPlugin(metadata: IPluginMetadata, dir: string): Promise<void>;
updatePlugin(metadata: IPluginMetadata): Promise<void>;
activatePlugin(name: string): Promise<void>;
deactivatePlugin(name: string): Promise<void>;
uninstallPlugin(input: IPluginMetadataFindOne): Promise<ID>;
loadPlugins(): Promise<void>;
getAllPlugins(): Promise<IPluginMetadata[]>;
getOnePlugin(name: string): Promise<IPluginMetadata>;
checkInstallation(marketplaceId: ID): Promise<IPluginMetadata>;
initializePlugins(): Promise<void>;
disposePlugins(): Promise<void>;
getMenuPlugins(): MenuItemConstructorOptions[];
}
Application Startup Integrationโ
const pluginManager = PluginManager.getInstance();
// Load all installed plugins from persistent storage
await pluginManager.loadPlugins();
// Invoke initialize() on all previously active plugins
await pluginManager.initializePlugins();
// On shutdown: invoke dispose() on all active plugins
await pluginManager.disposePlugins();
Menu Integrationโ
Plugins that contribute native menu items may do so through the getMenuPlugins() method:
import { Menu } from "electron";
const pluginMenus = pluginManager.getMenuPlugins();
const template = [
// ... other menu items
{
label: "Plugins",
submenu: pluginMenus,
},
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
Plugin Storage and Metadataโ
Installed plugins are stored within the application's user data directory under a plugins/ subdirectory. Each plugin occupies a uniquely named directory constructed from a Unix millisecond timestamp and the plugin's name (e.g., 1234567890123-plugin-name/).
The PluginMetadataService exposes the following operations:
| Method | Description |
|---|---|
create() | Store new plugin metadata upon installation |
update() | Persist modifications to an existing plugin record |
delete() | Remove a plugin record upon uninstallation |
findAll() | Retrieve the complete set of registered plugins |
findOne() | Locate a specific plugin by identifier |
findActivated() | Retrieve only those plugins currently in the ACTIVE state |
Download Strategiesโ
The plugin runtime supports installation from three distinct source types, each encapsulated within a dedicated strategy class following the Strategy design pattern:
enum PluginDownloadContextType {
CDN = "cdn",
LOCAL = "local",
NPM = "npm",
}
CDN Download Strategyโ
Plugins distributed via CDNs are downloaded as ZIP archives over HTTPS. The strategy implements streaming-based extraction with robust retry logic (exponential backoff, up to three attempts) and a five-minute timeout.
Local Download Strategyโ
For development and enterprise scenarios, plugins may be installed directly from the local filesystem. A native file picker dialog is presented to the user.
npm Download Strategyโ
Plugins published to npm registries may be installed by package name, supporting configurable registry URLs, authentication tokens, automatic dependency resolution, and native module handling:
{
pkg: { name: string, version: string },
pluginPath: string,
registry: {
privateURL?: string,
authToken?: string
}
}
Security Measures During Installationโ
The CDN download strategy incorporates security validations:
- Protocol validation: Only
https://andhttp://URLs are accepted - File extension validation: Only
.ziparchives are processed - Path traversal protection: All paths validated against the target directory to prevent "zip slip" attacks
- Size limits: Individual files capped at 500 MB; aggregate extraction capped at 1 GB
The Lazy Loaderโ
The LazyLoader utility dynamically resolves and loads plugin entry modules at runtime. Built upon Node.js require(), it accommodates both CommonJS default exports and named exports, with structured error handling to isolate loading failures.
The Event Systemโ
The PluginEventManager provides an application-wide event bus implemented atop Node.js EventEmitter:
const eventManager = PluginEventManager.getInstance();
// Emit a notification
eventManager.notify("Plugin installation completed");
// Register a subscriber
eventManager.listen((message) => {
console.log("Plugin event:", message);
});