Devtools
Source: web/sdk/src/devtools/EventMonitor.ts
EventMonitor is a static class — a process-wide event recorder useful for programmatic logging, test assertions, or building custom dev panels. Unlike <UnrealDevPanel>, it has no UI and no React dependency; you call it from anywhere in your app (or browser console).
TIP
For an out-of-the-box visual debugger, use <UnrealDevPanel>. Reach for EventMonitor when you want raw history access — e.g. a Playwright test asserting 'skill:cast' was sent.
Why static?
Every method on EventMonitor is static. There is a single global recorder for the whole page; you don't construct instances. This makes the recorder cheap to enable from anywhere (a test, a console session, a debug build) without threading an object through your component tree.
import { EventMonitor } from 'unreal-react-bridge';
EventMonitor.enable();
// ... interact with the app ...
console.table(EventMonitor.getEventHistory());
EventMonitor.disable();API
EventMonitor.enable()
static enable(): void;Turns recording on. Until enabled, recordSentEvent / recordReceivedEvent are no-ops. Idempotent.
EventMonitor.disable()
static disable(): void;Turns recording off. Existing history is kept until you call clearHistory().
EventMonitor.isMonitoring()
static isMonitoring(): boolean;Returns the current enabled state.
EventMonitor.recordSentEvent(eventName, data)
static recordSentEvent(eventName: string, data: unknown): void;Push an outgoing event into history. Used by integrations that wrap bridge.send. No-op when isMonitoring() is false.
EventMonitor.recordReceivedEvent(eventName, data)
static recordReceivedEvent(eventName: string, data: unknown): void;Push an incoming event into history. Used by integrations that wrap bridge.on (or window.addEventListener). No-op when isMonitoring() is false.
EventMonitor.getEventHistory()
static getEventHistory(): Array<MonitoredEvent>;Returns a copy of the recorded history (newest entries last). Safe to mutate.
EventMonitor.clearHistory()
static clearHistory(): void;Empties the history buffer. Does not disable monitoring.
EventMonitor.getEventsByName(eventName)
static getEventsByName(eventName: string): Array<MonitoredEvent>;Returns only events whose eventName matches exactly. Useful for assertions like "did 'skill:cast' fire exactly twice?".
EventMonitor.getStats()
static getStats(): {
totalEvents: number;
sentEvents: number;
receivedEvents: number;
uniqueEventNames: string[];
};One-shot summary of recorded history.
EventMonitor.setMaxHistorySize(size)
static setMaxHistorySize(size: number): void;Caps how many entries the ring buffer keeps. Default is 100. Values < 1 are clamped to 1. Shrinking below the current length truncates the oldest entries.
MonitoredEvent shape
Every entry in history has this exact shape (inlined in the source):
interface MonitoredEvent {
type: 'sent' | 'received';
eventName: string;
data: unknown;
timestamp: number; // Date.now() at record time, ms since epoch
}Wiring it up
EventMonitor does not automatically tap the bridge — you decide what to feed it. The simplest pattern is to wrap bridge.send from a small adapter you mount at app startup:
import { EventMonitor } from 'unreal-react-bridge';
EventMonitor.enable();
// In a setup module: tap outbound sends.
function tapBridge(bridge: { send: (n: string, d: unknown) => void }) {
const original = bridge.send.bind(bridge);
bridge.send = (name, data) => {
EventMonitor.recordSentEvent(name, data);
original(name, data);
};
}
// Tap inbound CustomEvents (mirror what UnrealDevPanel does).
const originalDispatch = window.dispatchEvent.bind(window);
window.dispatchEvent = (event: Event): boolean => {
if (event instanceof CustomEvent && event.type.includes(':')) {
EventMonitor.recordReceivedEvent(event.type, event.detail);
}
return originalDispatch(event);
};If you only need a UI, prefer <UnrealDevPanel>, which already implements both taps.
Example: console inspection
import { EventMonitor } from 'unreal-react-bridge';
EventMonitor.enable();
EventMonitor.setMaxHistorySize(500);
// later, in the browser devtools:
console.table(EventMonitor.getEventHistory());
console.log(EventMonitor.getStats());
console.log(EventMonitor.getEventsByName('skill:cast'));
EventMonitor.clearHistory();
EventMonitor.disable();Example: Playwright assertion
const sent = await page.evaluate(() => {
// unreal-react-bridge is loaded by the app
return (window as any).EventMonitor?.getEventsByName('skill:cast') ?? [];
});
expect(sent).toHaveLength(1);(Expose EventMonitor on window from your app's dev build if you want to inspect it from Playwright.)
See also
<UnrealDevPanel>— visual companion that already taps both directions/guide/debugging— debugging workflow and recipes