@rimori/client 1.3.1 → 1.4.3

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.
Files changed (148) hide show
  1. package/.prettierignore +35 -0
  2. package/README.md +77 -71
  3. package/dist/cli/scripts/init/dev-registration.d.ts +1 -1
  4. package/dist/cli/scripts/init/dev-registration.js +4 -4
  5. package/dist/cli/scripts/init/main.js +1 -1
  6. package/dist/cli/scripts/init/package-setup.d.ts +1 -1
  7. package/dist/cli/scripts/init/package-setup.js +3 -3
  8. package/dist/cli/scripts/init/router-transformer.js +19 -12
  9. package/dist/cli/scripts/init/vite-config.d.ts +2 -2
  10. package/dist/cli/scripts/init/vite-config.js +2 -2
  11. package/dist/cli/scripts/release/release-config-upload.js +9 -9
  12. package/dist/cli/scripts/release/release-db-update.d.ts +1 -1
  13. package/dist/cli/scripts/release/release-db-update.js +9 -9
  14. package/dist/cli/scripts/release/release-file-upload.js +2 -2
  15. package/dist/cli/scripts/release/release.js +2 -2
  16. package/dist/cli/types/DatabaseTypes.d.ts +2 -2
  17. package/dist/components/CRUDModal.d.ts +1 -1
  18. package/dist/components/CRUDModal.js +3 -3
  19. package/dist/components/MarkdownEditor.js +16 -16
  20. package/dist/components/Spinner.js +2 -2
  21. package/dist/components/ai/Assistant.js +7 -8
  22. package/dist/components/ai/Avatar.d.ts +2 -2
  23. package/dist/components/ai/Avatar.js +14 -7
  24. package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +5 -6
  25. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +1 -1
  26. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -2
  27. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -2
  28. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +4 -2
  29. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +1 -1
  30. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -3
  31. package/dist/components/audio/Playbutton.js +10 -7
  32. package/dist/components/components/ContextMenu.d.ts +1 -1
  33. package/dist/components/components/ContextMenu.js +19 -16
  34. package/dist/components.d.ts +10 -10
  35. package/dist/components.js +10 -10
  36. package/dist/core/controller/AIController.d.ts +2 -2
  37. package/dist/core/controller/AIController.js +20 -18
  38. package/dist/core/controller/ExerciseController.d.ts +52 -0
  39. package/dist/core/controller/ExerciseController.js +73 -0
  40. package/dist/core/controller/ObjectController.js +5 -5
  41. package/dist/core/controller/SettingsController.d.ts +22 -7
  42. package/dist/core/controller/SettingsController.js +73 -8
  43. package/dist/core/controller/SharedContentController.d.ts +3 -3
  44. package/dist/core/controller/SharedContentController.js +38 -20
  45. package/dist/core/controller/VoiceController.js +6 -4
  46. package/dist/core/core.d.ts +15 -14
  47. package/dist/core/core.js +7 -7
  48. package/dist/fromRimori/EventBus.js +23 -23
  49. package/dist/fromRimori/PluginTypes.d.ts +4 -4
  50. package/dist/hooks/UseChatHook.d.ts +3 -3
  51. package/dist/hooks/UseChatHook.js +9 -3
  52. package/dist/index.d.ts +10 -10
  53. package/dist/index.js +9 -9
  54. package/dist/plugin/AccomplishmentHandler.d.ts +5 -5
  55. package/dist/plugin/AccomplishmentHandler.js +31 -27
  56. package/dist/plugin/AudioController.d.ts +1 -1
  57. package/dist/plugin/AudioController.js +6 -6
  58. package/dist/plugin/Logger.d.ts +5 -0
  59. package/dist/plugin/Logger.js +65 -13
  60. package/dist/plugin/PluginController.d.ts +7 -1
  61. package/dist/plugin/PluginController.js +32 -27
  62. package/dist/plugin/RimoriClient.d.ts +39 -14
  63. package/dist/plugin/RimoriClient.js +60 -31
  64. package/dist/plugin/StandaloneClient.d.ts +1 -1
  65. package/dist/plugin/StandaloneClient.js +35 -16
  66. package/dist/plugin/ThemeSetter.js +4 -4
  67. package/dist/providers/PluginProvider.js +44 -14
  68. package/dist/utils/Language.js +57 -57
  69. package/dist/utils/PluginUtils.js +3 -3
  70. package/dist/utils/difficultyConverter.d.ts +1 -1
  71. package/dist/utils/difficultyConverter.js +1 -1
  72. package/dist/utils/endpoint.js +2 -2
  73. package/dist/worker/WorkerSetup.d.ts +1 -1
  74. package/dist/worker/WorkerSetup.js +6 -6
  75. package/eslint.config.js +53 -0
  76. package/example/docs/devdocs.md +50 -40
  77. package/example/docs/overview.md +1 -1
  78. package/example/docs/userdocs.md +4 -1
  79. package/example/rimori.config.ts +51 -49
  80. package/example/worker/vite.config.ts +3 -3
  81. package/example/worker/worker.ts +2 -2
  82. package/package.json +17 -4
  83. package/prettier.config.js +8 -0
  84. package/src/cli/scripts/init/dev-registration.ts +5 -8
  85. package/src/cli/scripts/init/env-setup.ts +1 -1
  86. package/src/cli/scripts/init/file-operations.ts +1 -1
  87. package/src/cli/scripts/init/html-cleaner.ts +2 -5
  88. package/src/cli/scripts/init/main.ts +16 -13
  89. package/src/cli/scripts/init/package-setup.ts +11 -15
  90. package/src/cli/scripts/init/router-transformer.ts +40 -37
  91. package/src/cli/scripts/init/tailwind-config.ts +17 -26
  92. package/src/cli/scripts/init/vite-config.ts +3 -3
  93. package/src/cli/scripts/release/release-config-upload.ts +11 -11
  94. package/src/cli/scripts/release/release-db-update.ts +12 -12
  95. package/src/cli/scripts/release/release-file-upload.ts +3 -3
  96. package/src/cli/scripts/release/release.ts +4 -4
  97. package/src/cli/types/DatabaseTypes.ts +7 -8
  98. package/src/components/CRUDModal.tsx +64 -48
  99. package/src/components/MarkdownEditor.tsx +58 -27
  100. package/src/components/Spinner.tsx +24 -17
  101. package/src/components/ai/Assistant.tsx +70 -70
  102. package/src/components/ai/Avatar.tsx +20 -16
  103. package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +63 -54
  104. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +14 -5
  105. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +75 -74
  106. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +177 -178
  107. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +109 -94
  108. package/src/components/ai/utils.ts +4 -4
  109. package/src/components/audio/Playbutton.tsx +101 -93
  110. package/src/components/components/ContextMenu.tsx +47 -35
  111. package/src/components.ts +10 -10
  112. package/src/core/controller/AIController.ts +62 -50
  113. package/src/core/controller/ExerciseController.ts +98 -0
  114. package/src/core/controller/ObjectController.ts +15 -10
  115. package/src/core/controller/SettingsController.ts +89 -16
  116. package/src/core/controller/SharedContentController.ts +80 -44
  117. package/src/core/controller/VoiceController.ts +10 -8
  118. package/src/core/core.ts +15 -15
  119. package/src/fromRimori/EventBus.ts +76 -47
  120. package/src/fromRimori/PluginTypes.ts +26 -17
  121. package/src/fromRimori/readme.md +2 -2
  122. package/src/hooks/UseChatHook.ts +25 -15
  123. package/src/index.ts +10 -10
  124. package/src/plugin/AccomplishmentHandler.ts +53 -35
  125. package/src/plugin/AudioController.ts +18 -12
  126. package/src/plugin/Logger.ts +77 -19
  127. package/src/plugin/PluginController.ts +60 -44
  128. package/src/plugin/RimoriClient.ts +133 -69
  129. package/src/plugin/StandaloneClient.ts +51 -24
  130. package/src/plugin/ThemeSetter.ts +5 -5
  131. package/src/providers/PluginProvider.tsx +90 -36
  132. package/src/style.scss +3 -3
  133. package/src/utils/Language.ts +58 -58
  134. package/src/utils/PluginUtils.ts +16 -20
  135. package/src/utils/difficultyConverter.ts +2 -2
  136. package/src/utils/endpoint.ts +3 -2
  137. package/src/worker/WorkerSetup.ts +8 -9
  138. package/tsconfig.json +2 -4
  139. package/dist/components/LoggerExample.d.ts +0 -6
  140. package/dist/components/LoggerExample.js +0 -79
  141. package/dist/core/controller/AudioController.d.ts +0 -0
  142. package/dist/core/controller/AudioController.js +0 -1
  143. package/dist/hooks/UseLogger.d.ts +0 -30
  144. package/dist/hooks/UseLogger.js +0 -122
  145. package/dist/plugin/LoggerExample.d.ts +0 -16
  146. package/dist/plugin/LoggerExample.js +0 -140
  147. package/dist/utils/audioFormats.d.ts +0 -26
  148. package/dist/utils/audioFormats.js +0 -67
@@ -1,12 +1,12 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { RimoriClient } from "../../plugin/RimoriClient";
3
- import { ObjectRequest } from "./ObjectController";
2
+ import { RimoriClient } from '../../plugin/RimoriClient';
3
+ import { ObjectRequest } from './ObjectController';
4
4
 
5
5
  export interface SharedContentObjectRequest extends ObjectRequest {
6
- fixedProperties?: Record<string, string | number | boolean>
6
+ fixedProperties?: Record<string, string | number | boolean>;
7
7
  }
8
8
 
9
- export type SharedContentFilter = Record<string, string | number | boolean>
9
+ export type SharedContentFilter = Record<string, string | number | boolean>;
10
10
 
11
11
  export class SharedContentController {
12
12
  private supabase: SupabaseClient;
@@ -34,10 +34,11 @@ export class SharedContentController {
34
34
  generatorInstructions: SharedContentObjectRequest,
35
35
  //this filter is there if the content should be filtered additionally by a column and value
36
36
  filter?: SharedContentFilter,
37
- options?: { privateTopic?: boolean, skipDbSave?: boolean, alwaysGenerateNew?: boolean, excludeIds?: string[] },
37
+ options?: { privateTopic?: boolean; skipDbSave?: boolean; alwaysGenerateNew?: boolean; excludeIds?: string[] },
38
38
  ): Promise<SharedContent<T>> {
39
- let query = this.supabase.from("shared_content")
40
- .select("*, scc:shared_content_completed(id, state)")
39
+ let query = this.supabase
40
+ .from('shared_content')
41
+ .select('*, scc:shared_content_completed(id, state)')
41
42
  .eq('content_type', contentType)
42
43
  .not('scc.state', 'in', '("completed","ongoing","hidden")')
43
44
  .is('deleted_at', null);
@@ -62,7 +63,7 @@ export class SharedContentController {
62
63
 
63
64
  // console.log('newAssignments:', newAssignments);
64
65
 
65
- if (!(options?.alwaysGenerateNew) && newAssignments.length > 0) {
66
+ if (!options?.alwaysGenerateNew && newAssignments.length > 0) {
66
67
  const index = Math.floor(Math.random() * newAssignments.length);
67
68
  return newAssignments[index];
68
69
  }
@@ -73,13 +74,13 @@ export class SharedContentController {
73
74
 
74
75
  //create the shared content object
75
76
  const data: SharedContent<T> = {
76
- id: "internal-temp-id-" + Math.random().toString(36).substring(2, 15),
77
+ id: 'internal-temp-id-' + Math.random().toString(36).substring(2, 15),
77
78
  contentType,
78
79
  title: instructions.title,
79
80
  keywords: instructions.keywords.map(({ text }: { text: string }) => text),
80
81
  data: { ...instructions, title: undefined, keywords: undefined, ...generatorInstructions.fixedProperties },
81
82
  privateTopic: options?.privateTopic,
82
- }
83
+ };
83
84
 
84
85
  if (options?.skipDbSave) {
85
86
  return data;
@@ -88,7 +89,11 @@ export class SharedContentController {
88
89
  return await this.createSharedContent(data);
89
90
  }
90
91
 
91
- private async generateNewAssignment(contentType: string, generatorInstructions: SharedContentObjectRequest, filter?: SharedContentFilter): Promise<any> {
92
+ private async generateNewAssignment(
93
+ contentType: string,
94
+ generatorInstructions: SharedContentObjectRequest,
95
+ filter?: SharedContentFilter,
96
+ ): Promise<any> {
92
97
  const fullInstructions = await this.getGeneratorInstructions(contentType, generatorInstructions, filter);
93
98
 
94
99
  console.log('fullInstructions:', fullInstructions);
@@ -96,29 +101,34 @@ export class SharedContentController {
96
101
  return await this.rimoriClient.ai.getObject(fullInstructions);
97
102
  }
98
103
 
99
- private async getGeneratorInstructions(contentType: string, generatorInstructions: ObjectRequest, filter?: SharedContentFilter): Promise<ObjectRequest> {
104
+ private async getGeneratorInstructions(
105
+ contentType: string,
106
+ generatorInstructions: ObjectRequest,
107
+ filter?: SharedContentFilter,
108
+ ): Promise<ObjectRequest> {
100
109
  const completedTopics = await this.getCompletedTopics(contentType, filter);
101
110
 
102
111
  generatorInstructions.instructions += `
103
112
  The following topics are already taken: ${completedTopics.join(', ')}`;
104
113
 
105
114
  generatorInstructions.tool.title = {
106
- type: "string",
107
- description: "What the topic is about. Short. ",
108
- }
115
+ type: 'string',
116
+ description: 'What the topic is about. Short. ',
117
+ };
109
118
  generatorInstructions.tool.keywords = {
110
- type: [{ text: { type: "string" } }],
111
- description: "Keywords around the topic of the assignment.",
112
- }
119
+ type: [{ text: { type: 'string' } }],
120
+ description: 'Keywords around the topic of the assignment.',
121
+ };
113
122
  return generatorInstructions;
114
123
  }
115
124
 
116
125
  private async getCompletedTopics(contentType: string, filter?: SharedContentFilter): Promise<string[]> {
117
- const query = this.supabase.from("shared_content")
118
- .select("title, keywords, scc:shared_content_completed(id)")
126
+ const query = this.supabase
127
+ .from('shared_content')
128
+ .select('title, keywords, scc:shared_content_completed(id)')
119
129
  .eq('content_type', contentType)
120
130
  .not('scc.id', 'is', null)
121
- .is('deleted_at', null)
131
+ .is('deleted_at', null);
122
132
 
123
133
  if (filter) {
124
134
  query.contains('data', filter);
@@ -134,7 +144,13 @@ export class SharedContentController {
134
144
  }
135
145
 
136
146
  public async getSharedContent<T>(contentType: string, id: string): Promise<SharedContent<T>> {
137
- const { data, error } = await this.supabase.from("shared_content").select().eq('content_type', contentType).eq('id', id).is('deleted_at', null).single();
147
+ const { data, error } = await this.supabase
148
+ .from('shared_content')
149
+ .select()
150
+ .eq('content_type', contentType)
151
+ .eq('id', id)
152
+ .is('deleted_at', null)
153
+ .single();
138
154
  if (error) {
139
155
  console.error('error fetching shared content:', error);
140
156
  throw new Error('error fetching shared content');
@@ -145,7 +161,7 @@ export class SharedContentController {
145
161
  public async completeSharedContent(contentType: string, assignmentId: string) {
146
162
  // Idempotent completion: upsert on (id, user_id) so repeated calls don't fail
147
163
  const { error } = await this.supabase
148
- .from("shared_content_completed")
164
+ .from('shared_content_completed')
149
165
  .upsert({ content_type: contentType, id: assignmentId } as any, { onConflict: 'id' });
150
166
 
151
167
  if (error) {
@@ -172,11 +188,11 @@ export class SharedContentController {
172
188
  reaction,
173
189
  bookmarked,
174
190
  }: {
175
- contentType: string
176
- id: string
177
- state?: 'completed' | 'ongoing' | 'hidden'
178
- reaction?: 'liked' | 'disliked' | null
179
- bookmarked?: boolean
191
+ contentType: string;
192
+ id: string;
193
+ state?: 'completed' | 'ongoing' | 'hidden';
194
+ reaction?: 'liked' | 'disliked' | null;
195
+ bookmarked?: boolean;
180
196
  }): Promise<void> {
181
197
  const payload: Record<string, unknown> = { content_type: contentType, id };
182
198
  if (state !== undefined) payload.state = state;
@@ -184,9 +200,7 @@ export class SharedContentController {
184
200
  if (bookmarked !== undefined) payload.bookmarked = bookmarked;
185
201
 
186
202
  // Prefer upsert, fall back to insert/update if upsert not allowed
187
- const { error } = await this.supabase
188
- .from('shared_content_completed')
189
- .upsert(payload as any, { onConflict: 'id' });
203
+ const { error } = await this.supabase.from('shared_content_completed').upsert(payload as any, { onConflict: 'id' });
190
204
 
191
205
  if (error) {
192
206
  console.error('error updating shared content state:', error);
@@ -201,8 +215,17 @@ export class SharedContentController {
201
215
  * @param limit - Optional limit for the number of results.
202
216
  * @returns Array of shared content matching the criteria.
203
217
  */
204
- public async getSharedContentList<T>(contentType: string, filter?: SharedContentFilter, limit?: number): Promise<SharedContent<T>[]> {
205
- const query = this.supabase.from("shared_content").select("*").eq('content_type', contentType).is('deleted_at', null).limit(limit ?? 30);
218
+ public async getSharedContentList<T>(
219
+ contentType: string,
220
+ filter?: SharedContentFilter,
221
+ limit?: number,
222
+ ): Promise<SharedContent<T>[]> {
223
+ const query = this.supabase
224
+ .from('shared_content')
225
+ .select('*')
226
+ .eq('content_type', contentType)
227
+ .is('deleted_at', null)
228
+ .limit(limit ?? 30);
206
229
 
207
230
  if (filter) {
208
231
  query.contains('data', filter);
@@ -229,14 +252,23 @@ export class SharedContentController {
229
252
  * @returns The inserted shared content.
230
253
  * @throws {Error} if insertion fails.
231
254
  */
232
- public async createSharedContent<T>({ contentType, title, keywords, data, privateTopic }: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>> {
233
- const { data: newContent, error } = await this.supabase.from("shared_content").insert({
234
- private: privateTopic,
235
- content_type: contentType,
236
- title,
237
- keywords,
238
- data,
239
- }).select();
255
+ public async createSharedContent<T>({
256
+ contentType,
257
+ title,
258
+ keywords,
259
+ data,
260
+ privateTopic,
261
+ }: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>> {
262
+ const { data: newContent, error } = await this.supabase
263
+ .from('shared_content')
264
+ .insert({
265
+ private: privateTopic,
266
+ content_type: contentType,
267
+ title,
268
+ keywords,
269
+ data,
270
+ })
271
+ .select();
240
272
 
241
273
  if (error) {
242
274
  console.error('error inserting shared content:', error);
@@ -262,7 +294,11 @@ export class SharedContentController {
262
294
  if (updates.data) updateData.data = updates.data;
263
295
  if (updates.privateTopic !== undefined) updateData.private = updates.privateTopic;
264
296
 
265
- const { data: updatedContent, error } = await this.supabase.from("shared_content").update(updateData).eq('id', id).select();
297
+ const { data: updatedContent, error } = await this.supabase
298
+ .from('shared_content')
299
+ .update(updateData)
300
+ .eq('id', id)
301
+ .select();
266
302
 
267
303
  if (error) {
268
304
  console.error('error updating shared content:', error);
@@ -284,7 +320,7 @@ export class SharedContentController {
284
320
  */
285
321
  public async removeSharedContent(id: string): Promise<SharedContent<any>> {
286
322
  const { data: deletedContent, error } = await this.supabase
287
- .from("shared_content")
323
+ .from('shared_content')
288
324
  .update({ deleted_at: new Date().toISOString() })
289
325
  .eq('id', id)
290
326
  .select();
@@ -324,4 +360,4 @@ export interface SharedContent<T> {
324
360
 
325
361
  /** Whether this content should only be visible to the creator. Defaults to false if not specified */
326
362
  privateTopic?: boolean;
327
- }
363
+ }
@@ -4,12 +4,14 @@ export async function getSTTResponse(backendUrl: string, audio: Blob, token: str
4
4
 
5
5
  return await fetch(`${backendUrl}/voice/stt`, {
6
6
  method: 'POST',
7
- headers: { 'Authorization': `Bearer ${token}` },
7
+ headers: { Authorization: `Bearer ${token}` },
8
8
  body: formData,
9
- }).then(r => r.json()).then(r => {
10
- // console.log("STT response: ", r);
11
- return r.text;
12
- });
9
+ })
10
+ .then((r) => r.json())
11
+ .then((r) => {
12
+ // console.log("STT response: ", r);
13
+ return r.text;
14
+ });
13
15
  }
14
16
 
15
17
  export async function getTTSResponse(backendUrl: string, request: TTSRequest, token: string) {
@@ -17,10 +19,10 @@ export async function getTTSResponse(backendUrl: string, request: TTSRequest, to
17
19
  method: 'POST',
18
20
  headers: {
19
21
  'Content-Type': 'application/json',
20
- 'Authorization': `Bearer ${token}`
22
+ Authorization: `Bearer ${token}`,
21
23
  },
22
24
  body: JSON.stringify(request),
23
- }).then(r => r.blob());
25
+ }).then((r) => r.blob());
24
26
  }
25
27
 
26
28
  interface TTSRequest {
@@ -28,4 +30,4 @@ interface TTSRequest {
28
30
  voice: string;
29
31
  speed: number;
30
32
  language?: string;
31
- }
33
+ }
package/src/core/core.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  // Core functionality exports
2
- export * from "../fromRimori/PluginTypes";
3
- export * from "../plugin/PluginController";
4
- export * from "../plugin/RimoriClient";
5
- export * from "../utils/difficultyConverter";
6
- export * from "../utils/Language";
7
- export * from "../utils/PluginUtils";
8
- export * from "../worker/WorkerSetup";
9
- export { EventBusMessage } from "../fromRimori/EventBus";
10
- export { Buddy, UserInfo } from "./controller/SettingsController";
11
- export { SharedContent } from "./controller/SharedContentController";
12
- export { Message, OnLLMResponse, ToolInvocation } from "./controller/AIController";
13
- export { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from "../plugin/AccomplishmentHandler";
14
- export { Tool } from "../fromRimori/PluginTypes";
15
- export { SharedContentObjectRequest } from "./controller/SharedContentController";
16
-
2
+ export * from '../fromRimori/PluginTypes';
3
+ export * from '../plugin/PluginController';
4
+ export * from '../plugin/RimoriClient';
5
+ export * from '../utils/difficultyConverter';
6
+ export * from '../utils/Language';
7
+ export * from '../utils/PluginUtils';
8
+ export * from '../worker/WorkerSetup';
9
+ export { EventBusMessage } from '../fromRimori/EventBus';
10
+ export { Buddy, UserInfo } from './controller/SettingsController';
11
+ export { SharedContent } from './controller/SharedContentController';
12
+ export { Exercise, TriggerAction } from './controller/ExerciseController';
13
+ export { Message, OnLLMResponse, ToolInvocation } from './controller/AIController';
14
+ export { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from '../plugin/AccomplishmentHandler';
15
+ export { Tool } from '../fromRimori/PluginTypes';
16
+ export { SharedContentObjectRequest } from './controller/SharedContentController';
@@ -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> {
@@ -40,7 +40,7 @@ export class EventBusHandler {
40
40
  private responseResolvers: Map<number, (value: EventBusMessage<unknown>) => void> = new Map();
41
41
  private static instance: EventBusHandler | null = null;
42
42
  private debugEnabled: boolean = false;
43
- private evName: string = "";
43
+ private evName: string = '';
44
44
 
45
45
  private constructor() {
46
46
  //private constructor
@@ -50,12 +50,14 @@ 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
+ `[${EventBusHandler.instance!.evName}] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`,
57
+ );
56
58
  });
57
59
  }
58
- if (name && EventBusHandler.instance.evName === "") {
60
+ if (name && EventBusHandler.instance.evName === '') {
59
61
  EventBusHandler.instance.evName = name;
60
62
  }
61
63
  return EventBusHandler.instance;
@@ -80,9 +82,9 @@ export class EventBusHandler {
80
82
  * @param topic - The topic of the event.
81
83
  * @param data - The data of the event.
82
84
  * @param eventId - The event id of the event.
83
- *
85
+ *
84
86
  * The topic format is: **pluginId.area.action**
85
- *
87
+ *
86
88
  * Example topics:
87
89
  * - pl1234.card.requestHard
88
90
  * - pl1234.card.requestNew
@@ -96,7 +98,13 @@ export class EventBusHandler {
96
98
  this.emitInternal(sender, topic, data || {}, eventId);
97
99
  }
98
100
 
99
- private emitInternal(sender: string, topic: string, data: EventPayload, eventId?: number, skipResponseTrigger = false): void {
101
+ private emitInternal(
102
+ sender: string,
103
+ topic: string,
104
+ data: EventPayload,
105
+ eventId?: number,
106
+ skipResponseTrigger = false,
107
+ ): void {
100
108
  if (!this.validateTopic(topic)) {
101
109
  this.logAndThrowError(false, `Invalid topic: ` + topic);
102
110
  return;
@@ -105,7 +113,7 @@ export class EventBusHandler {
105
113
  const event = this.createEvent(sender, topic, data, eventId);
106
114
 
107
115
  const handlers = this.getMatchingHandlers(event.topic);
108
- handlers.forEach(handler => {
116
+ handlers.forEach((handler) => {
109
117
  if (handler.ignoreSender && handler.ignoreSender.includes(sender)) {
110
118
  // console.log("ignore event as its in the ignoreSender list", { event, ignoreList: handler.ignoreSender });
111
119
  return;
@@ -132,8 +140,12 @@ export class EventBusHandler {
132
140
  * @param ignoreSender - The senders to ignore.
133
141
  * @returns An EventListener object containing an off() method to unsubscribe the listeners.
134
142
  */
135
- public on<T = EventPayload>(topics: string | string[], handler: EventHandler<T>, ignoreSender: string[] = []): EventListener {
136
- const ids = this.toArray(topics).map(topic => {
143
+ public on<T = EventPayload>(
144
+ topics: string | string[],
145
+ handler: EventHandler<T>,
146
+ ignoreSender: string[] = [],
147
+ ): EventListener {
148
+ const ids = this.toArray(topics).map((topic) => {
137
149
  this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
138
150
  if (!this.validateTopic(topic)) {
139
151
  this.logAndThrowError(true, `Invalid topic: ` + topic);
@@ -154,7 +166,7 @@ export class EventBusHandler {
154
166
  });
155
167
 
156
168
  return {
157
- off: () => this.off(ids)
169
+ off: () => this.off(ids),
158
170
  };
159
171
  }
160
172
 
@@ -165,33 +177,41 @@ export class EventBusHandler {
165
177
  * @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
178
  * @returns An EventListener object containing an off() method to unsubscribe the listeners.
167
179
  */
168
- public respond(sender: string, topic: string | string[], handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): EventListener {
180
+ public respond(
181
+ sender: string,
182
+ topic: string | string[],
183
+ handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>),
184
+ ): EventListener {
169
185
  const topics = Array.isArray(topic) ? topic : [topic];
170
- const listeners = topics.map(topic => {
186
+ const listeners = topics.map((topic) => {
171
187
  const blackListedEventIds: number[] = [];
172
188
  //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 });
189
+ const finalIgnoreSender = !topic.startsWith('self.') ? [sender] : [];
190
+
191
+ const listener = this.on(
192
+ topic,
193
+ async (data: EventBusMessage) => {
194
+ if (blackListedEventIds.includes(data.eventId)) {
195
+ // console.log("BLACKLISTED EVENT ID", data.eventId);
196
+ return;
197
+ }
198
+ blackListedEventIds.push(data.eventId);
199
+ if (blackListedEventIds.length > 20) {
200
+ blackListedEventIds.shift();
201
+ }
202
+ const response = typeof handler === 'function' ? await handler(data) : handler;
203
+ this.emit(sender, topic, response, data.eventId);
204
+ },
205
+ finalIgnoreSender,
206
+ );
207
+
208
+ this.logIfDebug(`Added respond listener ` + sender + ' to topic ' + topic, { listener, sender });
189
209
  return {
190
- off: () => listener.off()
210
+ off: () => listener.off(),
191
211
  };
192
212
  });
193
213
  return {
194
- off: () => listeners.forEach(listener => listener.off())
214
+ off: () => listeners.forEach((listener) => listener.off()),
195
215
  };
196
216
  }
197
217
 
@@ -221,12 +241,12 @@ export class EventBusHandler {
221
241
  * @param listenerIds - The ids of the listeners to unsubscribe from.
222
242
  */
223
243
  private off(listenerIds: string | string[]): void {
224
- this.toArray(listenerIds).forEach(fullId => {
244
+ this.toArray(listenerIds).forEach((fullId) => {
225
245
  const { topic, id } = JSON.parse(atob(fullId));
226
246
 
227
247
  const listeners = this.listeners.get(topic) || new Set();
228
248
 
229
- listeners.forEach(listener => {
249
+ listeners.forEach((listener) => {
230
250
  if (listener.id === Number(id)) {
231
251
  listeners.delete(listener);
232
252
  this.logIfDebug(`Removed listener ` + fullId, { topic, listenerId: id });
@@ -246,7 +266,11 @@ export class EventBusHandler {
246
266
  * @param data - The data of the event.
247
267
  * @returns A promise that resolves to the event.
248
268
  */
249
- public async request<T = EventPayload>(sender: string, topic: string, data?: EventPayload): Promise<EventBusMessage<T>> {
269
+ public async request<T = EventPayload>(
270
+ sender: string,
271
+ topic: string,
272
+ data?: EventPayload,
273
+ ): Promise<EventBusMessage<T>> {
250
274
  if (!this.validateTopic(topic)) {
251
275
  this.logAndThrowError(true, `Invalid topic: ` + topic);
252
276
  }
@@ -255,8 +279,10 @@ export class EventBusHandler {
255
279
 
256
280
  this.logIfDebug(`Requesting data from ` + topic, { event });
257
281
 
258
- return new Promise<EventBusMessage<T>>(resolve => {
259
- this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) => resolve(value as EventBusMessage<T>));
282
+ return new Promise<EventBusMessage<T>>((resolve) => {
283
+ this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) =>
284
+ resolve(value as EventBusMessage<T>),
285
+ );
260
286
  this.emitInternal(sender, topic, data || {}, event.eventId, true);
261
287
  });
262
288
  }
@@ -271,7 +297,7 @@ export class EventBusHandler {
271
297
 
272
298
  // Find wildcard matches
273
299
  const wildcard = [...this.listeners.entries()]
274
- .filter(([key]) => key.endsWith("*") && topic.startsWith(key.slice(0, -1)))
300
+ .filter(([key]) => key.endsWith('*') && topic.startsWith(key.slice(0, -1)))
275
301
  .flatMap(([_, handlers]) => [...handlers]);
276
302
  return new Set([...exact, ...wildcard]);
277
303
  }
@@ -283,32 +309,35 @@ export class EventBusHandler {
283
309
  */
284
310
  private validateTopic(topic: string): boolean {
285
311
  // Split event type into parts
286
- const parts = topic.split(".");
312
+ const parts = topic.split('.');
287
313
  const [plugin, area, action] = parts;
288
314
 
289
315
  if (parts.length !== 3) {
290
- if (parts.length === 1 && plugin === "*") {
316
+ if (parts.length === 1 && plugin === '*') {
291
317
  return true;
292
318
  }
293
- if (parts.length === 2 && plugin !== "*" && area === "*") {
319
+ if (parts.length === 2 && plugin !== '*' && area === '*') {
294
320
  return true;
295
321
  }
296
322
  this.logAndThrowError(false, `Event type must have 3 parts separated by dots. Received: ` + topic);
297
323
  return false;
298
324
  }
299
325
 
300
- if (action === "*") {
326
+ if (action === '*') {
301
327
  return true;
302
328
  }
303
329
 
304
330
  // Validate action part
305
- const validActions = ["request", "create", "update", "delete", "trigger"];
331
+ const validActions = ['request', 'create', 'update', 'delete', 'trigger'];
306
332
 
307
- if (validActions.some(a => action.startsWith(a))) {
333
+ if (validActions.some((a) => action.startsWith(a))) {
308
334
  return true;
309
335
  }
310
336
 
311
- this.logAndThrowError(false, `Invalid event topic name. The action: ` + action + ". Must be or start with one of: " + validActions.join(", "));
337
+ this.logAndThrowError(
338
+ false,
339
+ `Invalid event topic name. The action: ` + action + '. Must be or start with one of: ' + validActions.join(', '),
340
+ );
312
341
  return false;
313
342
  }
314
343
 
@@ -327,4 +356,4 @@ export class EventBusHandler {
327
356
  }
328
357
  }
329
358
 
330
- export const EventBus = EventBusHandler.getInstance();
359
+ export const EventBus = EventBusHandler.getInstance();