Event Bus
Inter-plugin communication via the event bus: events, request/response patterns, and built-in event map.
Overview#
The Event Bus is the primary mechanism for communication between plugins and the shell. It supports fire-and-forget events as well as request/response patterns.
Emitting Events#
TypeScript
| 1 | import { useEvents } from '@naap/plugin-sdk'; |
| 2 | |
| 3 | function MyComponent() { |
| 4 | const events = useEvents(); |
| 5 | |
| 6 | const handleSave = () => { |
| 7 | // Fire and forget |
| 8 | events.emit('my-plugin:item-saved', { |
| 9 | itemId: '123', |
| 10 | timestamp: Date.now(), |
| 11 | }); |
| 12 | }; |
| 13 | |
| 14 | return <button onClick={handleSave}>Save</button>; |
| 15 | } |
Listening for Events#
TypeScript
| 1 | import { useEvents } from '@naap/plugin-sdk'; |
| 2 | import { useEffect, useState } from 'react'; |
| 3 | |
| 4 | function StatusBadge() { |
| 5 | const events = useEvents(); |
| 6 | const [status, setStatus] = useState('unknown'); |
| 7 | |
| 8 | useEffect(() => { |
| 9 | const unsubscribe = events.on('gateway:status-changed', (data) => { |
| 10 | setStatus(data.status); |
| 11 | }); |
| 12 | |
| 13 | // Clean up on unmount |
| 14 | return unsubscribe; |
| 15 | }, [events]); |
| 16 | |
| 17 | return <span>{status}</span>; |
| 18 | } |
Request / Response Pattern#
For synchronous-style communication between plugins:
Making a Request#
TypeScript
| 1 | const events = useEvents(); |
| 2 | |
| 3 | // Request data from another plugin |
| 4 | const result = await events.request< |
| 5 | { query: string }, // Request type |
| 6 | { items: Item[] } // Response type |
| 7 | >('inventory-plugin:search', { |
| 8 | query: 'network adapter', |
| 9 | }, { |
| 10 | timeout: 5000, // Optional timeout in ms |
| 11 | }); |
| 12 | |
| 13 | console.log(result.items); |
Handling Requests#
TypeScript
| 1 | const events = useEvents(); |
| 2 | |
| 3 | useEffect(() => { |
| 4 | const unsubscribe = events.handleRequest< |
| 5 | { query: string }, |
| 6 | { items: Item[] } |
| 7 | >('my-plugin:search', async (data) => { |
| 8 | const items = await searchItems(data.query); |
| 9 | return { items }; |
| 10 | }); |
| 11 | |
| 12 | return unsubscribe; |
| 13 | }, [events]); |
Built-in Events#
These events are emitted by the shell and available to all plugins:
Shell Events#
| Event | Payload | When |
|---|---|---|
shell:ready | { version: string } | Shell initialization complete |
shell:error | { error: string } | Unrecoverable shell error |
Auth Events#
| Event | Payload | When |
|---|---|---|
auth:login | { userId: string, email?: string } | User logged in |
auth:logout | {} | User logged out |
auth:token-refreshed | { token: string } | Token was refreshed |
Theme Events#
| Event | Payload | When |
|---|---|---|
theme:change | { mode: 'light' | 'dark' } | Theme was toggled |
Team Events#
| Event | Payload | When |
|---|---|---|
team:change | { teamId: string | null } | Team context switched |
team:created | { teamId: string, name: string } | New team created |
Plugin Events#
| Event | Payload | When |
|---|---|---|
plugin:preferences:changed | { pluginName: string } | Plugin preferences updated |
plugin:installed | { pluginName: string } | Plugin was installed |
plugin:uninstalled | { pluginName: string } | Plugin was uninstalled |
Naming Conventions#
Follow these conventions for custom events:
{plugin-name}:{action}
{plugin-name}:{entity}-{action}Examples:
task-tracker:task-createdgateway-manager:gateway-status-changedanalytics:report-generated
Best Practices#
- Always clean up listeners -- Return the unsubscribe function in
useEffectcleanup - Use TypeScript generics -- Type your event payloads for safety
- Namespace your events -- Prefix with your plugin name to avoid collisions
- Set timeouts on requests -- Avoid hanging if the handler plugin is not loaded
- Keep payloads small -- Events are synchronous; pass IDs rather than full objects