Add plugin system with sample plugin and settings UI

Introduces a plugin architecture, including a PluginManager, plugin loading in main and renderer processes, and a sample plugin demonstrating menu, IPC, and context menu contributions. Adds a Plugins tab to the settings UI for managing plugins (enable/disable, reload), and updates preload.js to load renderer preloads from plugins. Documentation for plugin development is included in README-PLUGINS.md.
This commit is contained in:
2025-09-08 19:10:05 +12:00
parent 62810fcb89
commit e228ca6317
11 changed files with 576 additions and 2 deletions
+92
View File
@@ -0,0 +1,92 @@
# Nebula Plugins (Early Preview)
This document explains how to build simple plugins for Nebula. The initial API is intentionally small and will grow with feedback.
## Overview
- Plugins live under either of these folders:
- App folder: `<app>/plugins/<plugin-id>/`
- User folder: `%APPDATA%/Nebula/plugins/<plugin-id>/` (Windows) preferred for user-installed plugins.
- Each plugin has a `plugin.json` manifest. Optional `main.js` runs in the main process. Optional `renderer-preload.js` runs in the renderer preload context and can expose safe APIs via `contextBridge`.
- Plugins are loaded on app start. Toggle a plugin by setting `"enabled": false` in its manifest.
## Manifest (plugin.json)
Example:
{
"id": "my-plugin",
"name": "My Plugin",
"version": "0.1.0",
"description": "What it does",
"main": "main.js",
"rendererPreload": "renderer-preload.js",
"enabled": true
}
Fields:
- id: Unique id. Defaults to folder name if omitted.
- main: Optional entry for main process integration.
- rendererPreload: Optional file injected into the preload. Use it to expose limited APIs.
- enabled: Defaults to true.
## Main process API (activate)
If `main` is present, export an `activate(ctx)` function. The `ctx` contains:
- Electron: `app`, `BrowserWindow`, `ipcMain`, `session`, `Menu`, `dialog`, `shell`
- paths: `{ appPath, userData, pluginDir }`
- log/warn/error: prefix logs with your plugin id
- on(event, cb): subscribe to lifecycle events (experimental)
- registerIPC(channel, handler): quickly expose an `ipcMain.handle`
- registerWebRequest(filter, listener): attach `session.webRequest.onBeforeRequest`
Example:
module.exports.activate = (ctx) => {
ctx.log('hello');
ctx.registerIPC('my-plugin:do', async (_evt, payload) => ({ ok: true }));
ctx.registerWebRequest({ urls: ['*://*/*'] }, (details) => ({ cancel: false }));
};
## Renderer preload API
If `rendererPreload` is present, it will be `require()`-d from the app preload. You can use `contextBridge` to expose a safe surface to the page:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('myPlugin', {
hello: () => ipcRenderer.invoke('my-plugin:do'),
});
Your exposed API will be available on `window.myPlugin` in `renderer/` code (e.g., `script.js`).
## Sample plugin
A working sample is included at `plugins/sample-hello/`:
- Adds menu item "Say Hello (Sample Plugin)" under Help.
- Exposes `window.sampleHello.ping()` and `window.sampleHello.onHello(cb)`.
Try it from the DevTools console:
await window.sampleHello.ping();
window.sampleHello.onHello((m) => console.log('got hello', m));
Click Help -> Say Hello (Sample Plugin) to see the message delivered to the page.
## Loading order and safety
- Plugins load after the app is ready. Renderer preloads run after Nebula's own preload has exposed its APIs.
- Context isolation stays enabled. Only data explicitly exposed via `contextBridge` is available to pages.
- Avoid long blocking work in plugin activation.
## Debugging
- See logs with a `[Plugin:<id>]` prefix in the app console.
- Temporarily disable a plugin by setting `enabled: false` in `plugin.json`.
## Roadmap
This is a first pass. Planned next:
- Enable plugin settings UI
- Hot reload/reload button
- More lifecycle hooks (tab events, context menu contributions)
- Theming hooks