diff --git a/docs/api-reference.html b/docs/api-reference.html index a20c1f9..ffbc783 100644 --- a/docs/api-reference.html +++ b/docs/api-reference.html @@ -18,6 +18,7 @@ Getting Started API Reference WebSocket + Plugins Security diff --git a/docs/getting-started.html b/docs/getting-started.html index 476e0a2..98773f4 100644 --- a/docs/getting-started.html +++ b/docs/getting-started.html @@ -18,6 +18,7 @@ Getting Started API Reference WebSocket + Plugins Security diff --git a/docs/index.html b/docs/index.html index da440bc..947d9cc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -19,6 +19,7 @@ Getting Started API Reference WebSocket + Plugins Security @@ -56,6 +57,11 @@

🔒 Type-Safe

đŸ“Ļ Full Coverage

Guilds, Channels, Roles, Members, Bans, Timeouts, Messages, Reactions, Pins, and more.

+
+

🔌 Plugin System

+

Extend functionality with plugins. Add routes, listen to events, and communicate between plugins. +

+

Quick Links

@@ -72,9 +78,9 @@

📖 API Reference

⚡ WebSocket Events

Real-time event streaming documentation.

- -

đŸ’ģ Source Code

-

View the source code on GitHub.

+
+

🔌 Plugin System

+

Create plugins with event handling and REST endpoints.

diff --git a/docs/plugins.html b/docs/plugins.html new file mode 100644 index 0000000..f4daa59 --- /dev/null +++ b/docs/plugins.html @@ -0,0 +1,667 @@ + + + + + + + Plugin System - Holo Bridge + + + + + +
+
+ + +
+
+ +
+
+

Plugin System

+

Extend HoloBridge with custom functionality using the powerful plugin system. Plugins can add REST API + endpoints, listen to Discord events, and communicate with other plugins via a typed event bus.

+ +
+ â„šī¸ Note: Plugins are JavaScript/ESM modules placed in the plugins/ + directory. They are automatically loaded on startup. +
+ +

Quick Start

+

Create a file in the plugins/ directory with a .js or .mjs + extension:

+ +

plugins/my-plugin.js

+
export default {
+    metadata: {
+        name: 'my-plugin',
+        version: '1.0.0',
+        author: 'Your Name',
+        description: 'A sample HoloBridge plugin'
+    },
+
+    // Optional: Register REST endpoints
+    routes: (router, ctx) => {
+        router.get('/status', (req, res) => {
+            res.json({ status: 'ok', plugin: 'my-plugin' });
+        });
+    },
+
+    // Optional: Subscribe to events
+    events: (on, ctx) => [
+        on.onDiscord('messageCreate', (msg) => {
+            ctx.logger.info('New message:', msg.content);
+        }),
+    ],
+
+    // Optional: Called when plugin loads
+    onLoad: (ctx) => {
+        ctx.logger.info('Plugin loaded!');
+    },
+
+    // Optional: Called when plugin unloads
+    onUnload: () => {
+        console.log('Plugin unloaded!');
+    }
+};
+ +

Plugin Structure

+ +

Plugin Metadata

+

Every plugin must export an object with a metadata property:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeRequiredDescription
namestringYesUnique plugin identifier (used in routes and logs)
versionstringYesSemantic version (e.g., "1.0.0")
authorstringNoPlugin author name
descriptionstringNoShort description of the plugin
+ +

Lifecycle Hooks

+ + + + + + + + + + + + + + + + + + + + +
HookWhen CalledUse Case
onLoad(ctx)After plugin is loadedInitialize state, setup connections
onUnload()Before plugin is unloadedCleanup resources, close connections
+ +

Plugin Context

+

The ctx object provides access to core HoloBridge services:

+
interface PluginContext {
+    client: Client;              // Discord.js client
+    io: SocketIOServer;          // Socket.IO server
+    config: Config;              // Application configuration
+    app: Application;            // Express application
+    eventBus: PluginEventBus;    // Event bus for inter-plugin communication
+    logger: PluginLogger;        // Logging utility
+    log: (message: string) => void;  // Legacy logger
+    getPlugin: (name: string) => PluginMetadata | undefined;
+    listPlugins: () => string[];
+}
+ +

Logger

+

Use the built-in logger for consistent output:

+
ctx.logger.info('Information message');
+ctx.logger.warn('Warning message');
+ctx.logger.error('Error message');
+ctx.logger.debug('Debug message (only in debug mode)');
+ +
+ +

REST API Routes

+

Plugins can register REST API endpoints that are automatically mounted at + /api/plugins/{plugin-name}/: +

+ +
routes: (router, ctx) => {
+    // GET /api/plugins/my-plugin/status
+    router.get('/status', (req, res) => {
+        res.json({ status: 'ok' });
+    });
+
+    // POST /api/plugins/my-plugin/action
+    router.post('/action', (req, res) => {
+        const { userId, action } = req.body;
+        // Handle the action
+        res.json({ success: true });
+    });
+
+    // Routes support all HTTP methods
+    router.put('/update', handler);
+    router.patch('/modify', handler);
+    router.delete('/remove', handler);
+
+    // Add middleware
+    router.use(myMiddleware);
+}
+ +
+ â„šī¸ Automatic Error Handling: Plugin routes are automatically wrapped with error + handling. Errors are caught and returned as JSON responses. +
+ +
+ +

Event Bus

+

HoloBridge provides a typed event bus for inter-plugin communication with three event categories:

+ +
+
+

🎮 Discord Events

+

Events forwarded from the Discord gateway (prefixed with discord:)

+
+
+

🔌 Plugin Events

+

Lifecycle events like plugin load/unload (prefixed with plugin:)

+
+
+

✨ Custom Events

+

Events emitted by plugins for inter-plugin communication (prefixed with custom:)

+
+
+ +

Subscribing to Events

+

Use the events hook to subscribe to events. Return an array of subscriptions for automatic + cleanup:

+ +
events: (on, ctx) => [
+    // Subscribe to Discord events
+    on.onDiscord('messageCreate', (message) => {
+        ctx.logger.info('New message:', message.content);
+    }),
+
+    on.onDiscord('guildMemberAdd', (member) => {
+        ctx.logger.info('New member joined:', member.user.username);
+    }),
+
+    // Subscribe to custom events from other plugins
+    on.onCustom('moderation:user-warned', (data) => {
+        ctx.logger.info(`User ${data.userId} was warned`);
+    }),
+
+    // Subscribe to plugin lifecycle events
+    on.onPluginLoaded((data) => {
+        ctx.logger.info(`Plugin loaded: ${data.name} v${data.version}`);
+    }),
+
+    on.onPluginUnloaded((data) => {
+        ctx.logger.info(`Plugin unloaded: ${data.name}`);
+    }),
+]
+ +

Emitting Custom Events

+

Plugins can emit custom events for other plugins to consume:

+
events: (on, ctx) => {
+    // Emit a custom event
+    on.emit('my-plugin:action-performed', {
+        userId: '123456789',
+        action: 'ban',
+        reason: 'Spam'
+    });
+
+    return [];
+}
+ +

Available Discord Events

+

All standard Discord.js events are available. Common events include:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
messageCreateSerialized MessageNew message sent
messageUpdateSerialized MessageMessage edited
messageDeleteMessage infoMessage deleted
guildMemberAddSerialized MemberMember joined
guildMemberRemoveSerialized MemberMember left/kicked
guildMemberUpdateSerialized MemberMember updated
channelCreateSerialized ChannelChannel created
channelUpdateSerialized ChannelChannel updated
channelDeleteChannel infoChannel deleted
roleCreateSerialized RoleRole created
roleUpdateSerialized RoleRole updated
roleDeleteRole infoRole deleted
voiceStateUpdateVoice state dataVoice state changed
guildBanAddBan infoMember banned
guildBanRemoveBan infoMember unbanned
threadCreateSerialized ThreadThread created
threadUpdateSerialized ThreadThread updated
threadDeleteThread infoThread deleted
+ +

Plugin Lifecycle Events

+ + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
plugin:loaded{ name, version }A plugin was loaded
plugin:unloaded{ name }A plugin was unloaded
plugin:error{ name, error }A plugin encountered an error
+ +
+ +

Direct Event Bus Access

+

For advanced use cases, access the event bus directly via ctx.eventBus:

+ +
onLoad: (ctx) => {
+    const { eventBus } = ctx;
+
+    // Subscribe to any event
+    const subscription = eventBus.subscribe('custom:my-event', (data) => {
+        console.log('Received:', data);
+    });
+
+    // Subscribe once
+    eventBus.subscribeOnce('discord:ready', () => {
+        console.log('Bot is ready!');
+    });
+
+    // Emit Discord events (typically done by core)
+    eventBus.emitDiscord('messageCreate', messageData);
+
+    // Emit custom events
+    eventBus.emitCustom('my-plugin:action', { key: 'value' });
+
+    // Emit plugin lifecycle events
+    eventBus.emitPlugin('plugin:error', { name: 'my-plugin', error: new Error('Oops') });
+
+    // Get listener counts (for debugging)
+    console.log(eventBus.getListenerCounts());
+}
+ +

Event Bus Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
onDiscord(event, handler)Subscribe to a Discord event
onCustom(event, handler)Subscribe to a custom event
onPlugin(event, handler)Subscribe to a plugin lifecycle event
emitDiscord(event, data)Emit a Discord event
emitCustom(event, data)Emit a custom event
emitPlugin(event, data)Emit a plugin lifecycle event
subscribe(event, handler)Subscribe to any event (returns subscription object)
subscribeOnce(event, handler)Subscribe once and automatically unsubscribe
unsubscribeAll(subscriptions)Unsubscribe from multiple events at once
+ +
+ +

Inter-Plugin Communication

+

Plugins can discover and interact with other loaded plugins:

+ +
onLoad: (ctx) => {
+    // List all loaded plugins
+    const plugins = ctx.listPlugins();
+    ctx.logger.info('Loaded plugins:', plugins);
+
+    // Get another plugin's metadata
+    const modPlugin = ctx.getPlugin('moderation');
+    if (modPlugin) {
+        ctx.logger.info(`Moderation plugin v${modPlugin.version} is loaded`);
+    }
+}
+ +

Example: Plugin Communication Pattern

+
// Plugin A: moderation.js
+export default {
+    metadata: { name: 'moderation', version: '1.0.0' },
+    
+    routes: (router, ctx) => {
+        router.post('/warn', (req, res) => {
+            const { userId, reason } = req.body;
+            
+            // Emit event for other plugins
+            ctx.eventBus.emitCustom('moderation:user-warned', {
+                userId,
+                reason,
+                timestamp: Date.now()
+            });
+            
+            res.json({ success: true });
+        });
+    }
+};
+
+// Plugin B: logging.js
+export default {
+    metadata: { name: 'logging', version: '1.0.0' },
+    
+    events: (on, ctx) => [
+        // Listen for moderation events
+        on.onCustom('moderation:user-warned', (data) => {
+            ctx.logger.info(`[AUDIT] User ${data.userId} warned: ${data.reason}`);
+            // Log to database, send to webhook, etc.
+        }),
+    ]
+};
+ +
+ +

Complete Plugin Example

+

Here's a complete example of a plugin that demonstrates all features:

+ +
// plugins/welcome.js
+export default {
+    metadata: {
+        name: 'welcome',
+        version: '1.0.0',
+        author: 'HoloBridge',
+        description: 'Welcome new members with customizable messages'
+    },
+
+    // Configuration stored in memory (use a database in production)
+    _config: {
+        enabled: true,
+        channelId: null,
+        message: 'Welcome to the server, {user}!'
+    },
+
+    routes: (router, ctx) => {
+        // GET /api/plugins/welcome/config
+        router.get('/config', (req, res) => {
+            res.json({
+                success: true,
+                data: this._config
+            });
+        });
+
+        // PATCH /api/plugins/welcome/config
+        router.patch('/config', (req, res) => {
+            const { enabled, channelId, message } = req.body;
+            
+            if (enabled !== undefined) this._config.enabled = enabled;
+            if (channelId !== undefined) this._config.channelId = channelId;
+            if (message !== undefined) this._config.message = message;
+
+            res.json({ success: true, data: this._config });
+        });
+    },
+
+    events: (on, ctx) => [
+        on.onDiscord('guildMemberAdd', async (member) => {
+            if (!this._config.enabled || !this._config.channelId) return;
+
+            try {
+                const channel = await ctx.client.channels.fetch(this._config.channelId);
+                if (channel?.isTextBased()) {
+                    const message = this._config.message
+                        .replace('{user}', `<@${member.user.id}>`)
+                        .replace('{username}', member.user.username)
+                        .replace('{server}', member.guild.name);
+                    
+                    await channel.send(message);
+                    ctx.logger.info(`Welcomed ${member.user.username}`);
+                }
+            } catch (error) {
+                ctx.logger.error('Failed to send welcome message:', error);
+            }
+        }),
+    ],
+
+    onLoad: (ctx) => {
+        ctx.logger.info('Welcome plugin loaded!');
+        ctx.logger.info(`Routes available at /api/plugins/welcome/`);
+    },
+
+    onUnload: () => {
+        console.log('[welcome] Plugin unloaded');
+    }
+};
+ +
+ +

Best Practices

+ +
+

✅ Do:

+
    +
  • Return event subscriptions from the events hook for automatic cleanup
  • +
  • Use ctx.logger for consistent, prefixed logging
  • +
  • Handle errors gracefully in event handlers
  • +
  • Use semantic versioning for your plugin version
  • +
  • Namespace custom events with your plugin name (e.g., my-plugin:event-name)
  • +
+
+ +
+

âš ī¸ Avoid:

+
    +
  • Blocking the event loop with synchronous operations
  • +
  • Storing sensitive data in plugin state without encryption
  • +
  • Using the deprecated onEvent hook (use events instead)
  • +
  • Creating memory leaks by not cleaning up resources in onUnload
  • +
+
+ +

Next Steps

+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/security.html b/docs/security.html index dfc2683..6ce8909 100644 --- a/docs/security.html +++ b/docs/security.html @@ -18,6 +18,7 @@ Getting Started API Reference WebSocket + Plugins Security @@ -27,7 +28,8 @@

Security & API Scopes

-

HoloBridge provides granular access control through API scopes, allowing you to create API keys with limited permissions.

+

HoloBridge provides granular access control through API scopes, allowing you to create API keys with + limited permissions.

API Key Configuration

@@ -37,7 +39,8 @@

Single Key (Simple)

This key has admin scope (full access).

Multiple Keys with Scopes

-

For production, define multiple keys with specific permissions using the API_KEYS environment variable:

+

For production, define multiple keys with specific permissions using the API_KEYS + environment variable:

API_KEYS=[
   {"id":"dashboard","name":"Web Dashboard","key":"dash_xxx","scopes":["read:guilds","read:members"]},
   {"id":"bot","name":"Chat Bot","key":"bot_xxx","scopes":["read:messages","write:messages"]},
@@ -195,4 +198,4 @@ 

🚀 Getting Started

- + \ No newline at end of file diff --git a/docs/websocket.html b/docs/websocket.html index 0d57ce5..05b0a16 100644 --- a/docs/websocket.html +++ b/docs/websocket.html @@ -18,6 +18,7 @@ Getting Started API Reference WebSocket + Plugins Security