@rimori/client 2.1.8 → 2.2.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.
@@ -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> {
@@ -23,7 +23,9 @@ export interface EventBusMessage<T = EventPayload> {
23
23
  debug: boolean;
24
24
  }
25
25
 
26
- export type EventHandler<T = EventPayload> = (event: EventBusMessage<T>) => void | Promise<void>;
26
+ export type EventHandler<T = EventPayload> = (
27
+ event: EventBusMessage<T>
28
+ ) => void | Promise<void>;
27
29
 
28
30
  interface Listeners<T = EventPayload> {
29
31
  id: number;
@@ -37,7 +39,10 @@ export interface EventListener {
37
39
 
38
40
  export class EventBusHandler {
39
41
  private listeners: Map<string, Set<Listeners<EventPayload>>> = new Map();
40
- private responseResolvers: Map<number, (value: EventBusMessage<unknown>) => void> = new Map();
42
+ private responseResolvers: Map<
43
+ number,
44
+ (value: EventBusMessage<unknown>) => void
45
+ > = new Map();
41
46
  private static instance: EventBusHandler | null = null;
42
47
  private debugEnabled: boolean = false;
43
48
  private evName: string = "";
@@ -52,7 +57,11 @@ export class EventBusHandler {
52
57
 
53
58
  EventBusHandler.instance.on("global.system.requestDebug", () => {
54
59
  EventBusHandler.instance!.debugEnabled = true;
55
- console.log(`[${EventBusHandler.instance!.evName}] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`);
60
+ console.log(
61
+ `[${
62
+ EventBusHandler.instance!.evName
63
+ }] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`
64
+ );
56
65
  });
57
66
  }
58
67
  if (name && EventBusHandler.instance.evName === "") {
@@ -61,7 +70,12 @@ export class EventBusHandler {
61
70
  return EventBusHandler.instance;
62
71
  }
63
72
 
64
- private createEvent(sender: string, topic: string, data: EventPayload, eventId?: number): EventBusMessage {
73
+ private createEvent(
74
+ sender: string,
75
+ topic: string,
76
+ data: EventPayload,
77
+ eventId?: number
78
+ ): EventBusMessage {
65
79
  const generatedEventId = eventId || Math.floor(Math.random() * 10000000000);
66
80
 
67
81
  return {
@@ -80,9 +94,9 @@ export class EventBusHandler {
80
94
  * @param topic - The topic of the event.
81
95
  * @param data - The data of the event.
82
96
  * @param eventId - The event id of the event.
83
- *
97
+ *
84
98
  * The topic format is: **pluginId.area.action**
85
- *
99
+ *
86
100
  * Example topics:
87
101
  * - pl1234.card.requestHard
88
102
  * - pl1234.card.requestNew
@@ -92,11 +106,22 @@ export class EventBusHandler {
92
106
  * - pl1234.card.delete
93
107
  * - pl1234.card.triggerBackup
94
108
  */
95
- public emit<T = EventPayload>(sender: string, topic: string, data?: T, eventId?: number): void {
109
+ public emit<T = EventPayload>(
110
+ sender: string,
111
+ topic: string,
112
+ data?: T,
113
+ eventId?: number
114
+ ): void {
96
115
  this.emitInternal(sender, topic, data || {}, eventId);
97
116
  }
98
117
 
99
- private emitInternal(sender: string, topic: string, data: EventPayload, eventId?: number, skipResponseTrigger = false): void {
118
+ private emitInternal(
119
+ sender: string,
120
+ topic: string,
121
+ data: EventPayload,
122
+ eventId?: number,
123
+ skipResponseTrigger = false
124
+ ): void {
100
125
  if (!this.validateTopic(topic)) {
101
126
  this.logAndThrowError(false, `Invalid topic: ` + topic);
102
127
  return;
@@ -105,7 +130,7 @@ export class EventBusHandler {
105
130
  const event = this.createEvent(sender, topic, data, eventId);
106
131
 
107
132
  const handlers = this.getMatchingHandlers(event.topic);
108
- handlers.forEach(handler => {
133
+ handlers.forEach((handler) => {
109
134
  if (handler.ignoreSender && handler.ignoreSender.includes(sender)) {
110
135
  // console.log("ignore event as its in the ignoreSender list", { event, ignoreList: handler.ignoreSender });
111
136
  return;
@@ -118,7 +143,11 @@ export class EventBusHandler {
118
143
  }
119
144
 
120
145
  // If it's a response to a request
121
- if (eventId && this.responseResolvers.has(eventId) && !skipResponseTrigger) {
146
+ if (
147
+ eventId &&
148
+ this.responseResolvers.has(eventId) &&
149
+ !skipResponseTrigger
150
+ ) {
122
151
  // console.log("[Rimori] Resolving response to request: " + eventId, event.data);
123
152
  this.responseResolvers.get(eventId)!(event);
124
153
  this.responseResolvers.delete(eventId);
@@ -132,8 +161,12 @@ export class EventBusHandler {
132
161
  * @param ignoreSender - The senders to ignore.
133
162
  * @returns An EventListener object containing an off() method to unsubscribe the listeners.
134
163
  */
135
- public on<T = EventPayload>(topics: string | string[], handler: EventHandler<T>, ignoreSender: string[] = []): EventListener {
136
- const ids = this.toArray(topics).map(topic => {
164
+ public on<T = EventPayload>(
165
+ topics: string | string[],
166
+ handler: EventHandler<T>,
167
+ ignoreSender: string[] = []
168
+ ): EventListener {
169
+ const ids = this.toArray(topics).map((topic) => {
137
170
  this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
138
171
  if (!this.validateTopic(topic)) {
139
172
  this.logAndThrowError(true, `Invalid topic: ` + topic);
@@ -144,17 +177,42 @@ export class EventBusHandler {
144
177
  }
145
178
  const id = Math.floor(Math.random() * 10000000000);
146
179
 
147
- // Type assertion to handle the generic type mismatch
148
- const eventHandler = handler as unknown as EventHandler<EventPayload>;
149
- this.listeners.get(topic)!.add({ id, handler: eventHandler, ignoreSender });
180
+ // To prevent infinite loops and processing the same eventId multiple times
181
+ const blackListedEventIds: { eventId: number; sender: string }[] = [];
182
+ const eventHandler = (data: EventBusMessage) => {
183
+ if (
184
+ blackListedEventIds.some(
185
+ (item) =>
186
+ item.eventId === data.eventId && item.sender === data.sender
187
+ )
188
+ ) {
189
+ console.log("BLACKLISTED EVENT ID", data.eventId, data);
190
+ return;
191
+ }
192
+ blackListedEventIds.push({
193
+ eventId: data.eventId,
194
+ sender: data.sender,
195
+ });
196
+ if (blackListedEventIds.length > 100) {
197
+ blackListedEventIds.shift();
198
+ }
199
+ return (handler as unknown as EventHandler<EventPayload>)(data);
200
+ };
201
+
202
+ this.listeners
203
+ .get(topic)!
204
+ .add({ id, handler: eventHandler, ignoreSender });
150
205
 
151
- this.logIfDebug(`Subscribed to ` + topic, { listenerId: id, ignoreSender });
206
+ this.logIfDebug(`Subscribed to ` + topic, {
207
+ listenerId: id,
208
+ ignoreSender,
209
+ });
152
210
 
153
211
  return btoa(JSON.stringify({ topic, id }));
154
212
  });
155
213
 
156
214
  return {
157
- off: () => this.off(ids)
215
+ off: () => this.off(ids),
158
216
  };
159
217
  }
160
218
 
@@ -165,33 +223,47 @@ export class EventBusHandler {
165
223
  * @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
224
  * @returns An EventListener object containing an off() method to unsubscribe the listeners.
167
225
  */
168
- public respond(sender: string, topic: string | string[], handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): EventListener {
226
+ public respond(
227
+ sender: string,
228
+ topic: string | string[],
229
+ handler:
230
+ | EventPayload
231
+ | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)
232
+ ): EventListener {
169
233
  const topics = Array.isArray(topic) ? topic : [topic];
170
- const listeners = topics.map(topic => {
234
+ const listeners = topics.map((topic) => {
171
235
  const blackListedEventIds: number[] = [];
172
236
  //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
237
  const finalIgnoreSender = !topic.startsWith("self.") ? [sender] : [];
174
238
 
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 });
239
+ const listener = this.on(
240
+ topic,
241
+ async (data: EventBusMessage) => {
242
+ if (blackListedEventIds.includes(data.eventId)) {
243
+ // console.log("BLACKLISTED EVENT ID", data.eventId);
244
+ return;
245
+ }
246
+ blackListedEventIds.push(data.eventId);
247
+ if (blackListedEventIds.length > 100) {
248
+ blackListedEventIds.shift();
249
+ }
250
+ const response =
251
+ typeof handler === "function" ? await handler(data) : handler;
252
+ this.emit(sender, topic, response, data.eventId);
253
+ },
254
+ finalIgnoreSender
255
+ );
256
+
257
+ this.logIfDebug(
258
+ `Added respond listener ` + sender + " to topic " + topic,
259
+ { listener, sender }
260
+ );
189
261
  return {
190
- off: () => listener.off()
262
+ off: () => listener.off(),
191
263
  };
192
264
  });
193
265
  return {
194
- off: () => listeners.forEach(listener => listener.off())
266
+ off: () => listeners.forEach((listener) => listener.off()),
195
267
  };
196
268
  }
197
269
 
@@ -221,15 +293,18 @@ export class EventBusHandler {
221
293
  * @param listenerIds - The ids of the listeners to unsubscribe from.
222
294
  */
223
295
  private off(listenerIds: string | string[]): void {
224
- this.toArray(listenerIds).forEach(fullId => {
296
+ this.toArray(listenerIds).forEach((fullId) => {
225
297
  const { topic, id } = JSON.parse(atob(fullId));
226
298
 
227
299
  const listeners = this.listeners.get(topic) || new Set();
228
300
 
229
- listeners.forEach(listener => {
301
+ listeners.forEach((listener) => {
230
302
  if (listener.id === Number(id)) {
231
303
  listeners.delete(listener);
232
- this.logIfDebug(`Removed listener ` + fullId, { topic, listenerId: id });
304
+ this.logIfDebug(`Removed listener ` + fullId, {
305
+ topic,
306
+ listenerId: id,
307
+ });
233
308
  }
234
309
  });
235
310
  });
@@ -246,7 +321,11 @@ export class EventBusHandler {
246
321
  * @param data - The data of the event.
247
322
  * @returns A promise that resolves to the event.
248
323
  */
249
- public async request<T = EventPayload>(sender: string, topic: string, data?: EventPayload): Promise<EventBusMessage<T>> {
324
+ public async request<T = EventPayload>(
325
+ sender: string,
326
+ topic: string,
327
+ data?: EventPayload
328
+ ): Promise<EventBusMessage<T>> {
250
329
  if (!this.validateTopic(topic)) {
251
330
  this.logAndThrowError(true, `Invalid topic: ` + topic);
252
331
  }
@@ -255,8 +334,12 @@ export class EventBusHandler {
255
334
 
256
335
  this.logIfDebug(`Requesting data from ` + topic, { event });
257
336
 
258
- return new Promise<EventBusMessage<T>>(resolve => {
259
- this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) => resolve(value as EventBusMessage<T>));
337
+ return new Promise<EventBusMessage<T>>((resolve) => {
338
+ this.responseResolvers.set(
339
+ event.eventId,
340
+ (value: EventBusMessage<unknown>) =>
341
+ resolve(value as EventBusMessage<T>)
342
+ );
260
343
  this.emitInternal(sender, topic, data || {}, event.eventId, true);
261
344
  });
262
345
  }
@@ -271,7 +354,9 @@ export class EventBusHandler {
271
354
 
272
355
  // Find wildcard matches
273
356
  const wildcard = [...this.listeners.entries()]
274
- .filter(([key]) => key.endsWith("*") && topic.startsWith(key.slice(0, -1)))
357
+ .filter(
358
+ ([key]) => key.endsWith("*") && topic.startsWith(key.slice(0, -1))
359
+ )
275
360
  .flatMap(([_, handlers]) => [...handlers]);
276
361
  return new Set([...exact, ...wildcard]);
277
362
  }
@@ -293,7 +378,10 @@ export class EventBusHandler {
293
378
  if (parts.length === 2 && plugin !== "*" && area === "*") {
294
379
  return true;
295
380
  }
296
- this.logAndThrowError(false, `Event type must have 3 parts separated by dots. Received: ` + topic);
381
+ this.logAndThrowError(
382
+ false,
383
+ `Event type must have 3 parts separated by dots. Received: ` + topic
384
+ );
297
385
  return false;
298
386
  }
299
387
 
@@ -304,11 +392,17 @@ export class EventBusHandler {
304
392
  // Validate action part
305
393
  const validActions = ["request", "create", "update", "delete", "trigger"];
306
394
 
307
- if (validActions.some(a => action.startsWith(a))) {
395
+ if (validActions.some((a) => action.startsWith(a))) {
308
396
  return true;
309
397
  }
310
398
 
311
- this.logAndThrowError(false, `Invalid event topic name. The action: ` + action + ". Must be or start with one of: " + validActions.join(", "));
399
+ this.logAndThrowError(
400
+ false,
401
+ `Invalid event topic name. The action: ` +
402
+ action +
403
+ ". Must be or start with one of: " +
404
+ validActions.join(", ")
405
+ );
312
406
  return false;
313
407
  }
314
408
 
@@ -318,7 +412,10 @@ export class EventBusHandler {
318
412
  }
319
413
  }
320
414
 
321
- private logAndThrowError(throwError: boolean, ...args: (string | EventPayload)[]) {
415
+ private logAndThrowError(
416
+ throwError: boolean,
417
+ ...args: (string | EventPayload)[]
418
+ ) {
322
419
  const message = `[${this.evName}] ` + args[0];
323
420
  console.error(message, ...args.slice(1));
324
421
  if (throwError) {
@@ -327,4 +424,4 @@ export class EventBusHandler {
327
424
  }
328
425
  }
329
426
 
330
- export const EventBus = EventBusHandler.getInstance();
427
+ export const EventBus = EventBusHandler.getInstance();
package/src/index.ts CHANGED
@@ -12,7 +12,8 @@ export { Translator } from './controller/TranslationController';
12
12
  export type { TOptions } from 'i18next';
13
13
  export type { SharedContent, SharedContentObjectRequest } from './controller/SharedContentController';
14
14
  export type { Exercise } from './controller/ExerciseController';
15
- export type { UserInfo, Language } from './controller/SettingsController';
15
+ export type { UserInfo, Language, UserRole } from './controller/SettingsController';
16
16
  export type { Message, ToolInvocation } from './controller/AIController';
17
17
  export type { TriggerAction } from './controller/ExerciseController';
18
18
  export type { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from './controller/AccomplishmentController';
19
+ export type { EventBusMessage } from './fromRimori/EventBus';
@@ -32,6 +32,7 @@ export interface RimoriInfo {
32
32
  profile: UserInfo;
33
33
  mainPanelPlugin?: ActivePlugin;
34
34
  sidePanelPlugin?: ActivePlugin;
35
+ interfaceLanguage: string;
35
36
  }
36
37
 
37
38
  export class RimoriCommunicationHandler {
@@ -55,16 +56,18 @@ export class RimoriCommunicationHandler {
55
56
 
56
57
  private initMessageChannel(worker = false): void {
57
58
  const listener = (event: MessageEvent) => {
58
- console.log('[PluginController] window message', { origin: event.origin, data: event.data });
59
+ // console.log('[PluginController] window message', { origin: event.origin, data: event.data });
59
60
  const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
60
61
  const [transferredPort] = event.ports || [];
61
62
 
62
63
  if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
63
- console.log('[PluginController] message ignored (not init or wrong plugin)', {
64
- type,
65
- pluginId,
66
- hasPort: !!transferredPort,
67
- });
64
+ // console.log('[PluginController] message ignored (not init or wrong plugin)', {
65
+ // type,
66
+ // pluginId,
67
+ // currentPluginId: this.pluginId,
68
+ // hasPortProperty: !!transferredPort,
69
+ // event
70
+ // });
68
71
  return;
69
72
  }
70
73
 
@@ -217,7 +220,7 @@ export class RimoriCommunicationHandler {
217
220
  } else {
218
221
  // In main thread context, use EventBus
219
222
  const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
220
- console.log({ data });
223
+ // console.log({ data });
221
224
  this.rimoriInfo = data;
222
225
  this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
223
226
  accessToken: () => Promise.resolve(this.getToken()),
@@ -1,5 +1,4 @@
1
1
  import { RimoriClient } from './RimoriClient';
2
- import html2canvas from 'html2canvas';
3
2
 
4
3
  type LogLevel = 'debug' | 'info' | 'warn' | 'error';
5
4
 
@@ -307,16 +306,29 @@ export class Logger {
307
306
 
308
307
  /**
309
308
  * Capture a screenshot of the current page.
309
+ * Dynamically imports html2canvas only in browser environments.
310
310
  * @returns Promise resolving to base64 screenshot or null if failed
311
311
  */
312
312
  private async captureScreenshot(): Promise<string | null> {
313
- if (typeof window !== 'undefined' && typeof document !== 'undefined') {
313
+ // Only attempt to capture screenshot in browser environments
314
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
315
+ return null;
316
+ }
317
+
318
+ try {
319
+ // Dynamically import html2canvas only when window is available
320
+ // This allows tree-shaking for environments without window (e.g., Node.js)
321
+ const html2canvas = (await import('html2canvas')).default;
314
322
  const canvas = await html2canvas(document.body);
315
323
  const screenshot = canvas.toDataURL('image/png');
316
324
  // this.originalConsole.log("screenshot captured", screenshot)
317
325
  return screenshot;
326
+ } catch (error) {
327
+ // html2canvas may not be available or may fail to load
328
+ // Silently fail to avoid breaking logging functionality
329
+ this.originalConsole.warn('[Rimori Logger] Failed to capture screenshot:', error);
330
+ return null;
318
331
  }
319
- return null;
320
332
  }
321
333
 
322
334
  /**
@@ -43,7 +43,7 @@ export class RimoriClient {
43
43
  this.accomplishmentHandler = new AccomplishmentController(info.pluginId);
44
44
  this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
45
45
  this.sharedContentController = new SharedContentController(supabase, this);
46
- this.translator = new Translator(info.profile.mother_tongue.code);
46
+ this.translator = new Translator(info.interfaceLanguage);
47
47
 
48
48
  //only init logger in workers and on main plugin pages
49
49
  if (this.getQueryParam('applicationMode') !== 'sidebar') {
@@ -122,7 +122,16 @@ export class RimoriClient {
122
122
  from: <ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
123
123
  relation: string,
124
124
  ): PostgrestQueryBuilder<GenericSchema, View, ViewName> => {
125
- return this.superbase.from(this.db.getTableName(relation));
125
+ const tableName = this.db.getTableName(relation);
126
+ // Use plugins schema for plugin-specific tables (those with plugin prefix pattern pl[0-9]+_*)
127
+ // Global tables (starting with 'global_') remain in public schema
128
+ // Plugin tables always have the prefix, so use plugins schema for all prefixed tables
129
+ if (relation.startsWith('global_')) {
130
+ // Global tables stay in public schema
131
+ return this.superbase.from(tableName);
132
+ }
133
+ // All plugin tables go to plugins schema
134
+ return this.superbase.schema('plugins').from(tableName);
126
135
  },
127
136
  // storage: this.superbase.storage,
128
137
  // functions: this.superbase.functions,
@@ -243,9 +252,9 @@ export class RimoriClient {
243
252
  // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
244
253
  this.event.emit('action.requestMain');
245
254
  this.event.on<MainPanelAction>('action.requestMain', ({ data }) => {
246
- console.log('Received action ' + data.action);
247
- console.log('Listening to actions', listeningActions);
248
- if (actionsToListen.length === 0 || actionsToListen.includes(data.action)) {
255
+ // console.log('Received action for main panel ' + data.action_key);
256
+ // console.log('Listening to actions', listeningActions);
257
+ if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
249
258
  callback(data);
250
259
  }
251
260
  });
@@ -256,9 +265,10 @@ export class RimoriClient {
256
265
  // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
257
266
  this.event.emit('action.requestSidebar');
258
267
  this.event.on<MainPanelAction>('action.requestSidebar', ({ data }) => {
259
- console.log('Received action ' + data.action);
260
- console.log('Listening to actions', listeningActions);
261
- if (actionsToListen.length === 0 || actionsToListen.includes(data.action)) {
268
+ // console.log("eventHandler .onSidePanelAction", data);
269
+ // console.log('Received action for sidebar ' + data.action);
270
+ // console.log('Listening to actions', listeningActions);
271
+ if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
262
272
  callback(data);
263
273
  }
264
274
  });
@@ -474,3 +484,4 @@ export class RimoriClient {
474
484
  },
475
485
  };
476
486
  }
487
+