PluginManager Class
The PluginManager class handles the lifecycle of 3Lens plugins, including registration, activation, deactivation, and inter-plugin communication.
Overview
import { createProbe } from '@3lens/core';
const probe = createProbe({ /* config */ });
const pluginManager = probe.getPluginManager();
// Register a plugin
pluginManager.registerPlugin(myPlugin);
// Activate the plugin
await pluginManager.activatePlugin('com.example.my-plugin');
// Get all plugins
const plugins = pluginManager.getPlugins();
// Deactivate and unregister
await pluginManager.deactivatePlugin('com.example.my-plugin');
await pluginManager.unregisterPlugin('com.example.my-plugin');2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Constructor
new PluginManager(probe: DevtoolProbe)INFO
PluginManager is created internally by DevtoolProbe. Access it via probe.getPluginManager().
Plugin Lifecycle Methods
registerPlugin
Registers a plugin without activating it.
registerPlugin(plugin: DevtoolPlugin): voidParameters:
| Parameter | Type | Description |
|---|---|---|
plugin | DevtoolPlugin | The plugin to register |
Throws: Error if a plugin with the same ID is already registered.
Example:
import { LODCheckerPlugin } from '@3lens/core/plugins/builtin';
pluginManager.registerPlugin(LODCheckerPlugin);
pluginManager.registerPlugin(myCustomPlugin);2
3
4
Behavior:
- Validates the plugin structure
- Creates a
RegisteredPluginentry with state'registered' - Registers panels, toolbar actions, and context menu items
- Does NOT call
activate()- useactivatePlugin()for that
unregisterPlugin
Unregisters a plugin, removing all its panels, actions, and menu items.
async unregisterPlugin(pluginId: PluginId): Promise<void>Parameters:
| Parameter | Type | Description |
|---|---|---|
pluginId | PluginId | The plugin ID to unregister |
Example:
await pluginManager.unregisterPlugin('com.example.my-plugin');Behavior:
- If the plugin is active, deactivates it first
- Removes all registered panels
- Removes all toolbar actions
- Removes all context menu items
- Removes the plugin from the registry
activatePlugin
Activates a registered plugin.
async activatePlugin(pluginId: PluginId): Promise<void>Parameters:
| Parameter | Type | Description |
|---|---|---|
pluginId | PluginId | The plugin ID to activate |
Throws: Error if plugin is not found or activation fails.
Example:
try {
await pluginManager.activatePlugin('com.example.my-plugin');
console.log('Plugin activated successfully');
} catch (error) {
console.error('Activation failed:', error);
}2
3
4
5
6
Behavior:
- Creates a
DevtoolContextfor the plugin - Calls the plugin's
activate()method - Updates state to
'activated' - Records activation timestamp
- On error, sets state to
'error'and stores the error
deactivatePlugin
Deactivates an active plugin.
async deactivatePlugin(pluginId: PluginId): Promise<void>Parameters:
| Parameter | Type | Description |
|---|---|---|
pluginId | PluginId | The plugin ID to deactivate |
Example:
await pluginManager.deactivatePlugin('com.example.my-plugin');Behavior:
- Calls
onUnmount()for all mounted panels - Clears panel container references
- Calls the plugin's
deactivate()method (if defined) - Updates state to
'deactivated' - Clears message handlers
Query Methods
getPlugins
Gets all registered plugins with their state.
getPlugins(): Array<{
id: PluginId;
metadata: PluginMetadata;
state: PluginState;
}>2
3
4
5
Example:
const plugins = pluginManager.getPlugins();
plugins.forEach(plugin => {
console.log(`${plugin.metadata.name} (${plugin.id}): ${plugin.state}`);
});
// Filter active plugins
const activePlugins = plugins.filter(p => p.state === 'activated');2
3
4
5
6
7
8
getPlugin
Gets detailed information about a specific plugin.
getPlugin(pluginId: PluginId): RegisteredPlugin | undefinedReturns: RegisteredPlugin or undefined if not found.
Example:
const registered = pluginManager.getPlugin('com.example.my-plugin');
if (registered) {
console.log('State:', registered.state);
console.log('Registered at:', new Date(registered.registeredAt));
console.log('Activated at:', registered.activatedAt ? new Date(registered.activatedAt) : 'never');
console.log('Error:', registered.error?.message ?? 'none');
console.log('Settings:', registered.settings);
}2
3
4
5
6
7
8
9
getPanels
Gets all registered panels sorted by order.
getPanels(): Array<{
key: string;
pluginId: PluginId;
panel: PanelDefinition;
}>2
3
4
5
Example:
const panels = pluginManager.getPanels();
panels.forEach(({ key, pluginId, panel }) => {
console.log(`Panel: ${panel.name} (${key}) from ${pluginId}`);
});2
3
4
5
getToolbarActions
Gets all registered toolbar actions sorted by order.
getToolbarActions(): Array<{
key: string;
pluginId: PluginId;
action: ToolbarActionDefinition;
}>2
3
4
5
Example:
const actions = pluginManager.getToolbarActions();
actions.forEach(({ key, action }) => {
console.log(`Action: ${action.name} [${action.shortcut ?? 'no shortcut'}]`);
});2
3
4
5
getContextMenuItems
Gets context menu items for a specific target.
getContextMenuItems(
target: 'scene-tree' | 'inspector' | 'viewport'
): Array<{
key: string;
pluginId: PluginId;
item: ContextMenuItemDefinition;
}>2
3
4
5
6
7
Example:
const sceneTreeItems = pluginManager.getContextMenuItems('scene-tree');
const viewportItems = pluginManager.getContextMenuItems('viewport');2
Panel Methods
renderPanel
Renders a panel and returns its HTML content.
renderPanel(panelKey: string): stringParameters:
| Parameter | Type | Description |
|---|---|---|
panelKey | string | Panel key in format pluginId:panelId |
Returns: HTML string for the panel content.
Example:
const html = pluginManager.renderPanel('3lens.lod-checker:main');
container.innerHTML = html;2
mountPanel
Mounts a panel to a DOM container, calling the panel's onMount callback.
mountPanel(panelKey: string, container: HTMLElement): voidExample:
const container = document.getElementById('panel-container');
pluginManager.mountPanel('3lens.lod-checker:main', container);2
unmountPanel
Unmounts a panel, calling its onUnmount callback.
unmountPanel(panelKey: string): voidExample:
pluginManager.unmountPanel('3lens.lod-checker:main');Action Execution
executeToolbarAction
Executes a toolbar action.
async executeToolbarAction(actionKey: string): Promise<void>Example:
await pluginManager.executeToolbarAction('3lens.lod-checker:refresh');executeContextMenuItem
Executes a context menu item with the given context.
async executeContextMenuItem(
itemKey: string,
menuContext: {
targetElement: HTMLElement | null;
targetObject: THREE.Object3D | null;
targetNode: SceneNode | null;
position: { x: number; y: number };
}
): Promise<void>2
3
4
5
6
7
8
9
Example:
await pluginManager.executeContextMenuItem('my-plugin:copy-transform', {
targetElement: event.target as HTMLElement,
targetObject: selectedMesh,
targetNode: selectedNode,
position: { x: event.clientX, y: event.clientY },
});2
3
4
5
6
Inter-Plugin Communication
sendMessage
Sends a message between plugins.
sendMessage(
source: PluginId,
target: PluginId | '*',
type: string,
payload: unknown
): void2
3
4
5
6
Example:
// Typically called via DevtoolContext, but can be used directly
pluginManager.sendMessage(
'plugin-a',
'plugin-b',
'data-ready',
{ items: results }
);
// Broadcast to all plugins
pluginManager.sendMessage(
'plugin-a',
'*',
'analysis-complete',
{ duration: 500 }
);2
3
4
5
6
7
8
9
10
11
12
13
14
15
onMessage
Subscribes to all plugin messages (global handler).
onMessage(handler: PluginMessageHandler): () => voidReturns: Unsubscribe function.
Example:
const unsubscribe = pluginManager.onMessage((message) => {
console.log(`[${message.source} → ${message.target}] ${message.type}`);
console.log('Payload:', message.payload);
});
// Later: stop listening
unsubscribe();2
3
4
5
6
7
State Management
getPluginState
Gets a plugin's stored state.
getPluginState(pluginId: PluginId): Record<string, unknown>setPluginState
Sets a value in a plugin's state.
setPluginState(pluginId: PluginId, key: string, value: unknown): voidUI Integration
setToastCallback
Sets the callback for showing toast notifications (called by UI layer).
setToastCallback(
callback: (message: string, type: 'info' | 'success' | 'warning' | 'error') => void
): void2
3
setRenderRequestCallback
Sets the callback for panel re-render requests (called by UI layer).
setRenderRequestCallback(callback: (pluginId: PluginId) => void): voidRegisteredPlugin Interface
interface RegisteredPlugin {
/** Plugin definition */
plugin: DevtoolPlugin;
/** Current state */
state: PluginState; // 'registered' | 'activated' | 'deactivated' | 'error'
/** Plugin's stored data */
storage: Record<string, unknown>;
/** Plugin's settings values */
settings: Record<string, unknown>;
/** Message handlers */
messageHandlers: PluginMessageHandler[];
/** Panel containers */
panelContainers: Map<string, HTMLElement>;
/** When the plugin was registered */
registeredAt: number;
/** When the plugin was last activated */
activatedAt: number | null;
/** Error if in error state */
error: Error | null;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Complete Example
import { createProbe, LODCheckerPlugin, ShadowDebuggerPlugin } from '@3lens/core';
const probe = createProbe({
name: 'My App',
sampleInterval: 100,
});
const pluginManager = probe.getPluginManager();
// Set up UI callbacks
pluginManager.setToastCallback((message, type) => {
showToastNotification(message, type);
});
pluginManager.setRenderRequestCallback((pluginId) => {
const panels = pluginManager.getPanels().filter(p => p.pluginId === pluginId);
panels.forEach(({ key }) => {
const container = document.querySelector(`[data-panel="${key}"]`);
if (container) {
container.innerHTML = pluginManager.renderPanel(key);
}
});
});
// Register built-in plugins
pluginManager.registerPlugin(LODCheckerPlugin);
pluginManager.registerPlugin(ShadowDebuggerPlugin);
// Register custom plugin
pluginManager.registerPlugin(myCustomPlugin);
// Activate all plugins
async function activateAllPlugins() {
const plugins = pluginManager.getPlugins();
for (const plugin of plugins) {
try {
await pluginManager.activatePlugin(plugin.id);
console.log(`✓ Activated: ${plugin.metadata.name}`);
} catch (error) {
console.error(`✗ Failed to activate ${plugin.metadata.name}:`, error);
}
}
}
// Listen for plugin messages
pluginManager.onMessage((message) => {
if (message.type === 'analysis-complete') {
console.log(`Analysis from ${message.source}:`, message.payload);
}
});
// Start
await activateAllPlugins();
// Render panels
const panels = pluginManager.getPanels();
panels.forEach(({ key, panel }) => {
const tab = createTab(panel.name, panel.icon);
const container = createPanelContainer(key);
tab.onclick = () => {
container.innerHTML = pluginManager.renderPanel(key);
pluginManager.mountPanel(key, container);
};
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
See Also
- DevtoolPlugin - Plugin interface
- DevtoolContext - Context API
- PluginLoader - Loading plugins from npm/URL
- LOD Checker Plugin - Built-in plugin
- Shadow Debugger Plugin - Built-in plugin