@rimori/client 2.2.0 → 2.3.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import html2canvas from 'html2canvas';
11
10
  /**
12
11
  * Singleton Logger class for Rimori client plugins.
13
12
  * Handles all logging levels, production filtering, and log transmission to Rimori.
@@ -242,17 +241,30 @@ export class Logger {
242
241
  }
243
242
  /**
244
243
  * Capture a screenshot of the current page.
244
+ * Dynamically imports html2canvas only in browser environments.
245
245
  * @returns Promise resolving to base64 screenshot or null if failed
246
246
  */
247
247
  captureScreenshot() {
248
248
  return __awaiter(this, void 0, void 0, function* () {
249
- if (typeof window !== 'undefined' && typeof document !== 'undefined') {
249
+ // Only attempt to capture screenshot in browser environments
250
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
251
+ return null;
252
+ }
253
+ try {
254
+ // Dynamically import html2canvas only when window is available
255
+ // html2canvas is an optional peer dependency - provided by @rimori/react-client
256
+ // In worker builds, this import should be marked as external to prevent bundling
257
+ const html2canvas = (yield import('html2canvas')).default;
250
258
  const canvas = yield html2canvas(document.body);
251
259
  const screenshot = canvas.toDataURL('image/png');
252
260
  // this.originalConsole.log("screenshot captured", screenshot)
253
261
  return screenshot;
254
262
  }
255
- return null;
263
+ catch (error) {
264
+ // html2canvas may not be available (e.g., in workers or when not installed)
265
+ // Silently fail to avoid breaking logging functionality
266
+ return null;
267
+ }
256
268
  });
257
269
  }
258
270
  /**
@@ -22,6 +22,11 @@ export declare class RimoriClient {
22
22
  private constructor();
23
23
  get plugin(): {
24
24
  pluginId: string;
25
+ /**
26
+ * The release channel of this plugin installation.
27
+ * Determines which database schema is used for plugin tables.
28
+ */
29
+ releaseChannel: "alpha" | "beta" | "stable";
25
30
  /**
26
31
  * Set the settings for the plugin.
27
32
  * @param settings The settings to set.
@@ -67,6 +72,13 @@ export declare class RimoriClient {
67
72
  * The table prefix for of database tables of the plugin.
68
73
  */
69
74
  tablePrefix: string;
75
+ /**
76
+ * The database schema used for plugin tables.
77
+ * Determined by rimori-main based on release channel:
78
+ * - 'plugins_alpha' for alpha release channel
79
+ * - 'plugins' for beta and stable release channels
80
+ */
81
+ schema: "plugins" | "plugins_alpha";
70
82
  /**
71
83
  * Get the table name for a given plugin table.
72
84
  * Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
@@ -101,9 +101,9 @@ export class RimoriClient {
101
101
  // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
102
102
  this.event.emit('action.requestMain');
103
103
  this.event.on('action.requestMain', ({ data }) => {
104
- console.log('Received action ' + data.action);
105
- console.log('Listening to actions', listeningActions);
106
- if (actionsToListen.length === 0 || actionsToListen.includes(data.action)) {
104
+ // console.log('Received action for main panel ' + data.action_key);
105
+ // console.log('Listening to actions', listeningActions);
106
+ if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
107
107
  callback(data);
108
108
  }
109
109
  });
@@ -113,9 +113,10 @@ export class RimoriClient {
113
113
  // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
114
114
  this.event.emit('action.requestSidebar');
115
115
  this.event.on('action.requestSidebar', ({ data }) => {
116
- console.log('Received action ' + data.action);
117
- console.log('Listening to actions', listeningActions);
118
- if (actionsToListen.length === 0 || actionsToListen.includes(data.action)) {
116
+ // console.log("eventHandler .onSidePanelAction", data);
117
+ // console.log('Received action for sidebar ' + data.action);
118
+ // console.log('Listening to actions', listeningActions);
119
+ if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
119
120
  callback(data);
120
121
  }
121
122
  });
@@ -276,7 +277,8 @@ export class RimoriClient {
276
277
  this.accomplishmentHandler = new AccomplishmentController(info.pluginId);
277
278
  this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
278
279
  this.sharedContentController = new SharedContentController(supabase, this);
279
- this.translator = new Translator(info.interfaceLanguage);
280
+ const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
281
+ this.translator = new Translator(info.interfaceLanguage, (currentPlugin === null || currentPlugin === void 0 ? void 0 : currentPlugin.endpoint) || '');
280
282
  //only init logger in workers and on main plugin pages
281
283
  if (this.getQueryParam('applicationMode') !== 'sidebar') {
282
284
  Logger.getInstance(this);
@@ -285,6 +287,11 @@ export class RimoriClient {
285
287
  get plugin() {
286
288
  return {
287
289
  pluginId: this.rimoriInfo.pluginId,
290
+ /**
291
+ * The release channel of this plugin installation.
292
+ * Determines which database schema is used for plugin tables.
293
+ */
294
+ releaseChannel: this.rimoriInfo.releaseChannel,
288
295
  /**
289
296
  * Set the settings for the plugin.
290
297
  * @param settings The settings to set.
@@ -337,7 +344,16 @@ export class RimoriClient {
337
344
  // relation: ViewName,
338
345
  // ): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
339
346
  from: (relation) => {
340
- return this.superbase.from(this.db.getTableName(relation));
347
+ const tableName = this.db.getTableName(relation);
348
+ // Use the schema determined by rimori-main based on release channel
349
+ // Global tables (starting with 'global_') remain in public schema
350
+ // Plugin tables use the schema provided by rimori-main (plugins or plugins_alpha)
351
+ if (relation.startsWith('global_')) {
352
+ // Global tables stay in public schema
353
+ return this.superbase.from(tableName);
354
+ }
355
+ // Plugin tables go to the schema provided by rimori-main
356
+ return this.superbase.schema(this.rimoriInfo.dbSchema).from(tableName);
341
357
  },
342
358
  // storage: this.superbase.storage,
343
359
  // functions: this.superbase.functions,
@@ -345,6 +361,13 @@ export class RimoriClient {
345
361
  * The table prefix for of database tables of the plugin.
346
362
  */
347
363
  tablePrefix: this.rimoriInfo.tablePrefix,
364
+ /**
365
+ * The database schema used for plugin tables.
366
+ * Determined by rimori-main based on release channel:
367
+ * - 'plugins_alpha' for alpha release channel
368
+ * - 'plugins' for beta and stable release channels
369
+ */
370
+ schema: this.rimoriInfo.dbSchema,
348
371
  /**
349
372
  * Get the table name for a given plugin table.
350
373
  * Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
@@ -14,6 +14,9 @@ export default defineConfig({
14
14
  outDir: path.resolve(__dirname, '../public'),
15
15
  emptyOutDir: false,
16
16
  rollupOptions: {
17
+ // Exclude DOM-only libraries that can't run in workers
18
+ // html2canvas is provided by @rimori/react-client for browser contexts
19
+ external: ['html2canvas'],
17
20
  output: {
18
21
  inlineDynamicImports: true,
19
22
  entryFileNames: 'web-worker.js',
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@rimori/client",
3
- "version": "2.2.0",
3
+ "version": "2.3.0-next.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/rimori-org/rimori-client.git"
9
+ },
10
+ "license": "apache-2.0",
6
11
  "bin": {
7
12
  "rimori-release": "./dist/cli/scripts/release/release.js",
8
13
  "rimori-init": "./dist/cli/scripts/init/main.js"
@@ -32,6 +37,7 @@
32
37
  "eslint-plugin-prettier": "^5.5.4",
33
38
  "eslint-plugin-react-hooks": "^7.0.0",
34
39
  "eslint-plugin-react-refresh": "^0.4.23",
40
+ "html2canvas": "^1.4.1",
35
41
  "globals": "^16.4.0",
36
42
  "prettier": "^3.6.2",
37
43
  "typescript": "^5.7.2",
@@ -19,6 +19,8 @@ export interface Language {
19
19
  uppercase: string;
20
20
  }
21
21
 
22
+ export type UserRole = 'user' | 'plugin_moderator' | 'lang_moderator' | 'admin';
23
+
22
24
  export interface UserInfo {
23
25
  skill_level_reading: LanguageLevel;
24
26
  skill_level_writing: LanguageLevel;
@@ -32,8 +34,7 @@ export interface UserInfo {
32
34
  story_genre: string;
33
35
  study_duration: number;
34
36
  /**
35
- * The 2 letter language code of the language the user speaks natively.
36
- * With the function getLanguageName, the language name can be retrieved.
37
+ * The language the user speaks natively.
37
38
  */
38
39
  mother_tongue: Language;
39
40
  /**
@@ -52,6 +53,10 @@ export interface UserInfo {
52
53
  * Optional: nearest big city (>100,000) near user's location
53
54
  */
54
55
  target_city?: string;
56
+ /**
57
+ * The user's role: 'user', 'plugin_moderator', 'lang_moderator', or 'admin'
58
+ */
59
+ user_role: UserRole;
55
60
  }
56
61
 
57
62
  export class SettingsController {
@@ -1,16 +1,22 @@
1
1
  import { createInstance, ThirdPartyModule, TOptions, i18n as i18nType } from 'i18next';
2
2
 
3
+ type InitializationState = 'not-inited' | 'initing' | 'finished';
4
+
3
5
  /**
4
6
  * Translator class for handling internationalization
5
7
  */
6
8
  export class Translator {
7
9
  private currentLanguage: string;
8
- private isInitialized: boolean;
10
+ private initializationState: InitializationState;
11
+ private initializationPromise: Promise<void> | null;
9
12
  private i18n: i18nType | undefined;
13
+ private translationUrl: string;
10
14
 
11
- constructor(initialLanguage: string) {
12
- this.isInitialized = false;
15
+ constructor(initialLanguage: string, translationUrl: string) {
13
16
  this.currentLanguage = initialLanguage;
17
+ this.initializationState = 'not-inited';
18
+ this.initializationPromise = null;
19
+ this.translationUrl = translationUrl;
14
20
  }
15
21
 
16
22
  /**
@@ -18,23 +24,46 @@ export class Translator {
18
24
  * @param userLanguage - Language code from user info
19
25
  */
20
26
  async initialize(): Promise<void> {
21
- if (this.isInitialized) return;
22
-
23
- const translations = await this.fetchTranslations(this.currentLanguage);
24
-
25
- const instance = createInstance({
26
- lng: this.currentLanguage,
27
- resources: {
28
- [this.currentLanguage]: {
29
- translation: translations,
30
- },
31
- },
32
- debug: window.location.hostname === 'localhost',
33
- });
34
-
35
- await instance.init();
36
- this.i18n = instance;
37
- this.isInitialized = true;
27
+ // If already finished, return immediately
28
+ if (this.initializationState === 'finished') {
29
+ return;
30
+ }
31
+
32
+ // If currently initializing, wait for the existing initialization to complete
33
+ if (this.initializationState === 'initing' && this.initializationPromise) {
34
+ return this.initializationPromise;
35
+ }
36
+
37
+ // Start initialization
38
+ this.initializationState = 'initing';
39
+
40
+ // Create a promise that will be resolved when initialization completes
41
+ this.initializationPromise = (async (): Promise<void> => {
42
+ try {
43
+ const translations = await this.fetchTranslations(this.currentLanguage);
44
+
45
+ const instance = createInstance({
46
+ lng: this.currentLanguage,
47
+ resources: {
48
+ [this.currentLanguage]: {
49
+ translation: translations,
50
+ },
51
+ },
52
+ debug: window.location.hostname === 'localhost',
53
+ });
54
+
55
+ await instance.init();
56
+ this.i18n = instance;
57
+ this.initializationState = 'finished';
58
+ } catch (error) {
59
+ // Reset state on error so it can be retried
60
+ this.initializationState = 'not-inited';
61
+ this.initializationPromise = null;
62
+ throw error;
63
+ }
64
+ })();
65
+
66
+ return this.initializationPromise;
38
67
  }
39
68
 
40
69
  private getTranslationUrl(language: string): string {
@@ -45,7 +74,7 @@ export class Translator {
45
74
  return `${window.location.origin}/locales/${filename}.json`;
46
75
  }
47
76
 
48
- return `./locales/${language}.json`;
77
+ return `${this.translationUrl}/locales/${language}.json`;
49
78
  }
50
79
 
51
80
  public usePlugin(plugin: ThirdPartyModule): void {
@@ -102,6 +131,6 @@ export class Translator {
102
131
  * Check if translator is initialized
103
132
  */
104
133
  isReady(): boolean {
105
- return this.isInitialized;
134
+ return this.initializationState === 'finished';
106
135
  }
107
136
  }
@@ -3,9 +3,9 @@ export type EventPayload = Record<string, any>;
3
3
 
4
4
  /**
5
5
  * Interface representing a message sent through the EventBus
6
- *
6
+ *
7
7
  * Debug capabilities:
8
- * - System-wide debugging: Send an event to "global.system.requestDebug"
8
+ * - System-wide debugging: Send an event to "global.system.requestDebug"
9
9
  * Example: `EventBus.emit("yourPluginId", "global.system.requestDebug");`
10
10
  */
11
11
  export interface EventBusMessage<T = EventPayload> {
@@ -39,8 +39,8 @@ export class EventBusHandler {
39
39
  private listeners: Map<string, Set<Listeners<EventPayload>>> = new Map();
40
40
  private responseResolvers: Map<number, (value: EventBusMessage<unknown>) => void> = new Map();
41
41
  private static instance: EventBusHandler | null = null;
42
- private debugEnabled: boolean = false;
43
- private evName: string = "";
42
+ private debugEnabled = false;
43
+ private evName = '';
44
44
 
45
45
  private constructor() {
46
46
  //private constructor
@@ -50,12 +50,16 @@ export class EventBusHandler {
50
50
  if (!EventBusHandler.instance) {
51
51
  EventBusHandler.instance = new EventBusHandler();
52
52
 
53
- EventBusHandler.instance.on("global.system.requestDebug", () => {
53
+ EventBusHandler.instance.on('global.system.requestDebug', () => {
54
54
  EventBusHandler.instance!.debugEnabled = true;
55
- console.log(`[${EventBusHandler.instance!.evName}] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`);
55
+ console.log(
56
+ `[${
57
+ EventBusHandler.instance!.evName
58
+ }] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`,
59
+ );
56
60
  });
57
61
  }
58
- if (name && EventBusHandler.instance.evName === "") {
62
+ if (name && EventBusHandler.instance.evName === '') {
59
63
  EventBusHandler.instance.evName = name;
60
64
  }
61
65
  return EventBusHandler.instance;
@@ -80,9 +84,9 @@ export class EventBusHandler {
80
84
  * @param topic - The topic of the event.
81
85
  * @param data - The data of the event.
82
86
  * @param eventId - The event id of the event.
83
- *
87
+ *
84
88
  * The topic format is: **pluginId.area.action**
85
- *
89
+ *
86
90
  * Example topics:
87
91
  * - pl1234.card.requestHard
88
92
  * - pl1234.card.requestNew
@@ -96,7 +100,13 @@ export class EventBusHandler {
96
100
  this.emitInternal(sender, topic, data || {}, eventId);
97
101
  }
98
102
 
99
- private emitInternal(sender: string, topic: string, data: EventPayload, eventId?: number, skipResponseTrigger = false): void {
103
+ private emitInternal(
104
+ sender: string,
105
+ topic: string,
106
+ data: EventPayload,
107
+ eventId?: number,
108
+ skipResponseTrigger = false,
109
+ ): void {
100
110
  if (!this.validateTopic(topic)) {
101
111
  this.logAndThrowError(false, `Invalid topic: ` + topic);
102
112
  return;
@@ -105,7 +115,7 @@ export class EventBusHandler {
105
115
  const event = this.createEvent(sender, topic, data, eventId);
106
116
 
107
117
  const handlers = this.getMatchingHandlers(event.topic);
108
- handlers.forEach(handler => {
118
+ handlers.forEach((handler) => {
109
119
  if (handler.ignoreSender && handler.ignoreSender.includes(sender)) {
110
120
  // console.log("ignore event as its in the ignoreSender list", { event, ignoreList: handler.ignoreSender });
111
121
  return;
@@ -132,8 +142,12 @@ export class EventBusHandler {
132
142
  * @param ignoreSender - The senders to ignore.
133
143
  * @returns An EventListener object containing an off() method to unsubscribe the listeners.
134
144
  */
135
- public on<T = EventPayload>(topics: string | string[], handler: EventHandler<T>, ignoreSender: string[] = []): EventListener {
136
- const ids = this.toArray(topics).map(topic => {
145
+ public on<T = EventPayload>(
146
+ topics: string | string[],
147
+ handler: EventHandler<T>,
148
+ ignoreSender: string[] = [],
149
+ ): EventListener {
150
+ const ids = this.toArray(topics).map((topic) => {
137
151
  this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
138
152
  if (!this.validateTopic(topic)) {
139
153
  this.logAndThrowError(true, `Invalid topic: ` + topic);
@@ -144,17 +158,35 @@ export class EventBusHandler {
144
158
  }
145
159
  const id = Math.floor(Math.random() * 10000000000);
146
160
 
147
- // Type assertion to handle the generic type mismatch
148
- const eventHandler = handler as unknown as EventHandler<EventPayload>;
161
+ // To prevent infinite loops and processing the same eventId multiple times
162
+ const blackListedEventIds: { eventId: number; sender: string }[] = [];
163
+ const eventHandler = (data: EventBusMessage) => {
164
+ if (blackListedEventIds.some((item) => item.eventId === data.eventId && item.sender === data.sender)) {
165
+ console.log('BLACKLISTED EVENT ID', data.eventId, data);
166
+ return;
167
+ }
168
+ blackListedEventIds.push({
169
+ eventId: data.eventId,
170
+ sender: data.sender,
171
+ });
172
+ if (blackListedEventIds.length > 100) {
173
+ blackListedEventIds.shift();
174
+ }
175
+ return (handler as unknown as EventHandler<EventPayload>)(data);
176
+ };
177
+
149
178
  this.listeners.get(topic)!.add({ id, handler: eventHandler, ignoreSender });
150
179
 
151
- this.logIfDebug(`Subscribed to ` + topic, { listenerId: id, ignoreSender });
180
+ this.logIfDebug(`Subscribed to ` + topic, {
181
+ listenerId: id,
182
+ ignoreSender,
183
+ });
152
184
 
153
185
  return btoa(JSON.stringify({ topic, id }));
154
186
  });
155
187
 
156
188
  return {
157
- off: () => this.off(ids)
189
+ off: () => this.off(ids),
158
190
  };
159
191
  }
160
192
 
@@ -165,33 +197,41 @@ export class EventBusHandler {
165
197
  * @param handler - The handler to be called when the event is received. The handler returns the data to be emitted. Can be a static object or a function.
166
198
  * @returns An EventListener object containing an off() method to unsubscribe the listeners.
167
199
  */
168
- public respond(sender: string, topic: string | string[], handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): EventListener {
200
+ public respond(
201
+ sender: string,
202
+ topic: string | string[],
203
+ handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>),
204
+ ): EventListener {
169
205
  const topics = Array.isArray(topic) ? topic : [topic];
170
- const listeners = topics.map(topic => {
206
+ const listeners = topics.map((topic) => {
171
207
  const blackListedEventIds: number[] = [];
172
208
  //To allow event communication inside the same plugin the sender needs to be ignored but the events still need to be checked for the same event just reaching the subscriber to prevent infinite loops
173
- const finalIgnoreSender = !topic.startsWith("self.") ? [sender] : [];
174
-
175
- const listener = this.on(topic, async (data: EventBusMessage) => {
176
- if (blackListedEventIds.includes(data.eventId)) {
177
- // console.log("BLACKLISTED EVENT ID", data.eventId);
178
- return;
179
- }
180
- blackListedEventIds.push(data.eventId);
181
- if (blackListedEventIds.length > 20) {
182
- blackListedEventIds.shift();
183
- }
184
- const response = typeof handler === "function" ? await handler(data) : handler;
185
- this.emit(sender, topic, response, data.eventId);
186
- }, finalIgnoreSender);
187
-
188
- this.logIfDebug(`Added respond listener ` + sender + " to topic " + topic, { listener, sender });
209
+ const finalIgnoreSender = !topic.startsWith('self.') ? [sender] : [];
210
+
211
+ const listener = this.on(
212
+ topic,
213
+ async (data: EventBusMessage) => {
214
+ if (blackListedEventIds.includes(data.eventId)) {
215
+ // console.log("BLACKLISTED EVENT ID", data.eventId);
216
+ return;
217
+ }
218
+ blackListedEventIds.push(data.eventId);
219
+ if (blackListedEventIds.length > 100) {
220
+ blackListedEventIds.shift();
221
+ }
222
+ const response = typeof handler === 'function' ? await handler(data) : handler;
223
+ this.emit(sender, topic, response, data.eventId);
224
+ },
225
+ finalIgnoreSender,
226
+ );
227
+
228
+ this.logIfDebug(`Added respond listener ` + sender + ' to topic ' + topic, { listener, sender });
189
229
  return {
190
- off: () => listener.off()
230
+ off: () => listener.off(),
191
231
  };
192
232
  });
193
233
  return {
194
- off: () => listeners.forEach(listener => listener.off())
234
+ off: () => listeners.forEach((listener) => listener.off()),
195
235
  };
196
236
  }
197
237
 
@@ -206,7 +246,7 @@ export class EventBusHandler {
206
246
  return;
207
247
  }
208
248
 
209
- let listener: EventListener | undefined;
249
+ let listener: EventListener | undefined = undefined;
210
250
  const wrapper = (event: EventBusMessage<T>) => {
211
251
  handler(event);
212
252
  listener?.off();
@@ -221,15 +261,18 @@ export class EventBusHandler {
221
261
  * @param listenerIds - The ids of the listeners to unsubscribe from.
222
262
  */
223
263
  private off(listenerIds: string | string[]): void {
224
- this.toArray(listenerIds).forEach(fullId => {
264
+ this.toArray(listenerIds).forEach((fullId) => {
225
265
  const { topic, id } = JSON.parse(atob(fullId));
226
266
 
227
267
  const listeners = this.listeners.get(topic) || new Set();
228
268
 
229
- listeners.forEach(listener => {
269
+ listeners.forEach((listener) => {
230
270
  if (listener.id === Number(id)) {
231
271
  listeners.delete(listener);
232
- this.logIfDebug(`Removed listener ` + fullId, { topic, listenerId: id });
272
+ this.logIfDebug(`Removed listener ` + fullId, {
273
+ topic,
274
+ listenerId: id,
275
+ });
233
276
  }
234
277
  });
235
278
  });
@@ -246,7 +289,11 @@ export class EventBusHandler {
246
289
  * @param data - The data of the event.
247
290
  * @returns A promise that resolves to the event.
248
291
  */
249
- public async request<T = EventPayload>(sender: string, topic: string, data?: EventPayload): Promise<EventBusMessage<T>> {
292
+ public async request<T = EventPayload>(
293
+ sender: string,
294
+ topic: string,
295
+ data?: EventPayload,
296
+ ): Promise<EventBusMessage<T>> {
250
297
  if (!this.validateTopic(topic)) {
251
298
  this.logAndThrowError(true, `Invalid topic: ` + topic);
252
299
  }
@@ -255,8 +302,10 @@ export class EventBusHandler {
255
302
 
256
303
  this.logIfDebug(`Requesting data from ` + topic, { event });
257
304
 
258
- return new Promise<EventBusMessage<T>>(resolve => {
259
- this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) => resolve(value as EventBusMessage<T>));
305
+ return new Promise<EventBusMessage<T>>((resolve) => {
306
+ this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) =>
307
+ resolve(value as EventBusMessage<T>),
308
+ );
260
309
  this.emitInternal(sender, topic, data || {}, event.eventId, true);
261
310
  });
262
311
  }
@@ -271,7 +320,7 @@ export class EventBusHandler {
271
320
 
272
321
  // Find wildcard matches
273
322
  const wildcard = [...this.listeners.entries()]
274
- .filter(([key]) => key.endsWith("*") && topic.startsWith(key.slice(0, -1)))
323
+ .filter(([key]) => key.endsWith('*') && topic.startsWith(key.slice(0, -1)))
275
324
  .flatMap(([_, handlers]) => [...handlers]);
276
325
  return new Set([...exact, ...wildcard]);
277
326
  }
@@ -283,32 +332,35 @@ export class EventBusHandler {
283
332
  */
284
333
  private validateTopic(topic: string): boolean {
285
334
  // Split event type into parts
286
- const parts = topic.split(".");
335
+ const parts = topic.split('.');
287
336
  const [plugin, area, action] = parts;
288
337
 
289
338
  if (parts.length !== 3) {
290
- if (parts.length === 1 && plugin === "*") {
339
+ if (parts.length === 1 && plugin === '*') {
291
340
  return true;
292
341
  }
293
- if (parts.length === 2 && plugin !== "*" && area === "*") {
342
+ if (parts.length === 2 && plugin !== '*' && area === '*') {
294
343
  return true;
295
344
  }
296
345
  this.logAndThrowError(false, `Event type must have 3 parts separated by dots. Received: ` + topic);
297
346
  return false;
298
347
  }
299
348
 
300
- if (action === "*") {
349
+ if (action === '*') {
301
350
  return true;
302
351
  }
303
352
 
304
353
  // Validate action part
305
- const validActions = ["request", "create", "update", "delete", "trigger"];
354
+ const validActions = ['request', 'create', 'update', 'delete', 'trigger'];
306
355
 
307
- if (validActions.some(a => action.startsWith(a))) {
356
+ if (validActions.some((a) => action.startsWith(a))) {
308
357
  return true;
309
358
  }
310
359
 
311
- this.logAndThrowError(false, `Invalid event topic name. The action: ` + action + ". Must be or start with one of: " + validActions.join(", "));
360
+ this.logAndThrowError(
361
+ false,
362
+ `Invalid event topic name. The action: ` + action + '. Must be or start with one of: ' + validActions.join(', '),
363
+ );
312
364
  return false;
313
365
  }
314
366
 
@@ -327,4 +379,4 @@ export class EventBusHandler {
327
379
  }
328
380
  }
329
381
 
330
- export const EventBus = EventBusHandler.getInstance();
382
+ export const EventBus = EventBusHandler.getInstance();