11import type { CommandRegistry } from './commands' ;
22import {
3+ type ArgFreeEventType ,
34 type EventRegistry ,
5+ FigmaEvent ,
46 type FigmaEventDefinition ,
57 type FigmaEventRegistry ,
68 isFigmaEvent ,
@@ -10,7 +12,8 @@ import type { CommandHandlers, DeregisterFn, EventListeners } from './types';
1012import { JsonReviver , serializeForMessageBus } from './utils' ;
1113
1214/**
13- * A simple message bus implementation which magically works in both the main thread and the plugin UI.
15+ * A simple message bus implementation facilitating communication between
16+ * the main thread and the plugin UI.
1417 * No need to worry about sending messages in the right direction.
1518 *
1619 * @remarks
@@ -24,13 +27,22 @@ import { JsonReviver, serializeForMessageBus } from './utils';
2427export class MessageBusSingleton < TCommands = unknown , TEvents = unknown > {
2528 private static instance ?: MessageBusSingleton < unknown , unknown > ;
2629
30+ /** Internal storage for registered command handlers. */
2731 protected $handlers : Partial < CommandHandlers < TCommands > > = { } ;
2832
33+ /** Internal storage for registered event listeners. */
2934 protected $listeners : Partial < EventListeners < TEvents > > = { } ;
3035
36+ // Prevent external instantiation
3137 // eslint-disable-next-line @typescript-eslint/no-empty-function
3238 private constructor ( ) { }
3339
40+ /**
41+ * Retrieves the singleton instance of the MessageBus.
42+ * @template T - The type definition for commands the bus will handle.
43+ * @template E - The type definition for events the bus will handle.
44+ * @returns The singleton instance of MessageBusSingleton.
45+ */
3446 public static getInstance < T = unknown , E = unknown > ( ) : MessageBusSingleton <
3547 T ,
3648 E
@@ -41,6 +53,13 @@ export class MessageBusSingleton<TCommands = unknown, TEvents = unknown> {
4153 return MessageBusSingleton . instance as MessageBusSingleton < T , E > ;
4254 }
4355
56+ /**
57+ * Registers a handler function for a specific command.
58+ * @template Id - The ID of the command to handle.
59+ * @param command The command ID.
60+ * @param handler The function to execute when the command is received.
61+ * @returns A function to deregister the handler.
62+ */
4463 public handleCommand < Id extends keyof CommandHandlers < TCommands > > (
4564 command : Id ,
4665 handler : CommandHandlers < TCommands > [ Id ] ,
@@ -57,6 +76,13 @@ export class MessageBusSingleton<TCommands = unknown, TEvents = unknown> {
5776 } ) ;
5877 }
5978
79+ /**
80+ * Sends a command to the appropriate context (main thread or UI).
81+ * @template Id - The ID of the command to send.
82+ * @param command The command ID.
83+ * @param data The payload for the command.
84+ * @returns The result of the command execution (currently always returns `undefined` as execution is asynchronous).
85+ */
6086 public sendCommand < Id extends keyof CommandRegistry < TCommands > > (
6187 command : Id ,
6288 data : CommandRegistry < TCommands > [ Id ] [ 'message' ] ,
@@ -68,77 +94,143 @@ export class MessageBusSingleton<TCommands = unknown, TEvents = unknown> {
6894 return undefined ;
6995 }
7096
97+ /**
98+ * Registers a listener function for a specific event.
99+ * This handles both custom events and standard Figma events.
100+ * @template Id - The ID of the event to listen to.
101+ * @param event The event ID.
102+ * @param listener The function to execute when the event is published.
103+ * @returns A function to deregister the listener.
104+ */
71105 public listenToEvent < Id extends keyof EventListeners < TEvents > > (
72106 event : Id ,
73107 listener : EventListeners < TEvents > [ Id ] ,
74108 ) : DeregisterFn {
75109 this . $listeners [ event ] = listener as Partial < EventListeners < TEvents > > [ Id ] ;
76110
77- if ( isFigmaEvent ( event as string ) ) {
78- // Only attempt to use Figma API if the global `figma` object exists
111+ const eventString = String ( event ) ;
112+
113+ if ( isFigmaEvent ( eventString ) ) {
79114 if ( typeof figma !== 'undefined' ) {
80- figma . on (
81- event as ArgFreeEventType ,
82- listener as ( ...args : unknown [ ] ) => void ,
83- ) ;
115+ const typedListener = listener as ( ...args : any [ ] ) => any ;
116+
117+ // Use the string value for the switch and figma.on/off calls
118+ switch ( eventString ) {
119+ case FigmaEvent . SelectionChanged :
120+ case FigmaEvent . CurrentPageChanged :
121+ case FigmaEvent . OnClose :
122+ case FigmaEvent . TimerStarted :
123+ case FigmaEvent . TimerPaused :
124+ case FigmaEvent . TimerStopped :
125+ case FigmaEvent . TimerDone :
126+ case FigmaEvent . TimerResume :
127+ case FigmaEvent . TimerAdjust :
128+ figma . on (
129+ eventString as ArgFreeEventType ,
130+ typedListener as ( ) => void ,
131+ ) ;
132+ break ;
133+ case FigmaEvent . DocumentChanged :
134+ figma . on (
135+ 'documentchange' ,
136+ typedListener as ( evt : DocumentChangeEvent ) => void ,
137+ ) ;
138+ break ;
139+ case FigmaEvent . OnDrop :
140+ figma . on ( 'drop' , typedListener as ( evt : DropEvent ) => boolean ) ;
141+ break ;
142+ case FigmaEvent . OnRun :
143+ figma . on ( 'run' , typedListener as ( evt : RunEvent ) => void ) ;
144+ break ;
145+ }
146+
84147 return ( ) : void => {
85- // Also check before using figma.off
86148 if ( typeof figma !== 'undefined' ) {
87- figma . off (
88- event as ArgFreeEventType ,
89- listener as ( ...args : unknown [ ] ) => void ,
90- ) ;
149+ switch ( eventString ) {
150+ case FigmaEvent . SelectionChanged :
151+ case FigmaEvent . CurrentPageChanged :
152+ case FigmaEvent . OnClose :
153+ case FigmaEvent . TimerStarted :
154+ case FigmaEvent . TimerPaused :
155+ case FigmaEvent . TimerStopped :
156+ case FigmaEvent . TimerDone :
157+ case FigmaEvent . TimerResume :
158+ case FigmaEvent . TimerAdjust :
159+ figma . off (
160+ eventString as ArgFreeEventType ,
161+ typedListener as ( ) => void ,
162+ ) ;
163+ break ;
164+ case FigmaEvent . DocumentChanged :
165+ figma . off (
166+ 'documentchange' ,
167+ typedListener as ( evt : DocumentChangeEvent ) => void ,
168+ ) ;
169+ break ;
170+ case FigmaEvent . OnDrop :
171+ figma . off ( 'drop' , typedListener as ( evt : DropEvent ) => boolean ) ;
172+ break ;
173+ case FigmaEvent . OnRun :
174+ figma . off ( 'run' , typedListener as ( evt : RunEvent ) => void ) ;
175+ break ;
176+ }
91177 }
92178 } ;
93179 }
94180
95- // If figma is not defined, warn and return a no-op deregister function
96- // This code is only reached if `typeof figma === 'undefined'`
181+ // Handle case where Figma API is not available but a Figma event is listened to
97182 console . warn (
98- `Attempted to listen to Figma event '${ String (
99- event ,
100- ) } ' in a non-Figma environment.`,
183+ `Attempted to listen to Figma event '${ eventString } ' in a non-Figma environment.` ,
101184 ) ;
102- return ( ) => { } ; // Return a no-op function
185+ // Return a no-op deregister function
186+ return ( ) => { } ;
103187 }
104188
105- return evtHandler . on ( String ( event ) , ( data : unknown ) => {
106- // Parse received data through JSON to reconstruct Map objects
189+ // Handle custom events using the internal event handler system
190+ return evtHandler . on ( eventString , ( data : unknown ) => {
107191 const parsedData =
108192 typeof data === 'string' ? JSON . parse ( data , JsonReviver ) : data ;
109-
110193 listener ( parsedData as EventRegistry < TEvents > [ Id ] [ 'message' ] ) ;
111194 } ) ;
112195 }
113196
197+ /**
198+ * Publishes an event to the appropriate context (main thread or UI).
199+ * This handles both custom events and standard Figma events.
200+ * @template Id - The ID of the event to publish.
201+ * @param event The event ID.
202+ * @param data The payload for the event.
203+ */
114204 public publishEvent <
115205 Id extends keyof EventRegistry < TEvents > | keyof FigmaEventRegistry ,
116206 > (
117207 event : Id ,
118208 data : Id extends keyof EventRegistry < TEvents >
119209 ? EventRegistry < TEvents > [ Id ] [ 'message' ]
120210 : Id extends keyof FigmaEventRegistry
121- ? FigmaEventRegistry [ Id ] extends FigmaEventDefinition < infer T , infer U >
211+ ? FigmaEventRegistry [ Id ] extends FigmaEventDefinition < infer _T , infer U >
122212 ? U
123213 : never
124214 : never ,
125215 ) : void {
126- // Serialize data to handle Maps and complex objects
127216 const serializedData = serializeForMessageBus ( data ) ;
128-
129217 evtHandler . emit ( String ( event ) , serializedData ) ;
130218 }
131219}
132220
221+ /** The singleton instance of the MessageBus. */
133222const singleton = MessageBusSingleton . getInstance ( ) ;
134223
135224// ensure the API is never changed
136- // -------------------------------
137225Object . freeze ( singleton ) ;
138226
139- // export the singleton instance only
140- // -----------------------------
141-
227+ /**
228+ * Retrieves the singleton instance of the MessageBus, typed for specific command and event registries.
229+ * This is the preferred way to access the MessageBus.
230+ * @template TCmdRegistry - The type definition for the command registry.
231+ * @template TEvtRegistry - The type definition for the event registry.
232+ * @returns The singleton `MessageBusSingleton` instance, properly typed.
233+ */
142234export function getMessageBus <
143235 TCmdRegistry = unknown ,
144236 TEvtRegistry = unknown ,
0 commit comments