@rimori/client 2.4.0 → 2.5.0-next.2

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 (70) hide show
  1. package/dist/cli/scripts/init/dev-registration.js +4 -2
  2. package/dist/cli/scripts/init/main.js +1 -0
  3. package/dist/cli/scripts/release/release.js +0 -0
  4. package/dist/controller/SettingsController.d.ts +1 -1
  5. package/dist/controller/SharedContentController.d.ts +1 -1
  6. package/dist/fromRimori/EventBus.js +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/plugin/CommunicationHandler.d.ts +13 -8
  9. package/dist/plugin/CommunicationHandler.js +44 -59
  10. package/dist/plugin/RimoriClient.d.ts +11 -195
  11. package/dist/plugin/RimoriClient.js +17 -298
  12. package/dist/plugin/StandaloneClient.d.ts +1 -1
  13. package/dist/plugin/StandaloneClient.js +3 -2
  14. package/dist/plugin/module/AIModule.d.ts +49 -0
  15. package/dist/plugin/module/AIModule.js +81 -0
  16. package/dist/plugin/module/DbModule.d.ts +30 -0
  17. package/dist/plugin/module/DbModule.js +51 -0
  18. package/dist/plugin/module/EventModule.d.ts +99 -0
  19. package/dist/plugin/module/EventModule.js +162 -0
  20. package/dist/{controller/ExerciseController.d.ts → plugin/module/ExerciseModule.d.ts} +20 -16
  21. package/dist/{controller/ExerciseController.js → plugin/module/ExerciseModule.js} +27 -20
  22. package/dist/plugin/module/PluginModule.d.ts +76 -0
  23. package/dist/plugin/module/PluginModule.js +88 -0
  24. package/package.json +8 -3
  25. package/.github/workflows/pre-release.yml +0 -126
  26. package/.prettierignore +0 -35
  27. package/eslint.config.js +0 -53
  28. package/example/docs/devdocs.md +0 -241
  29. package/example/docs/overview.md +0 -29
  30. package/example/docs/userdocs.md +0 -126
  31. package/example/rimori.config.ts +0 -91
  32. package/example/worker/vite.config.ts +0 -26
  33. package/example/worker/worker.ts +0 -11
  34. package/prettier.config.js +0 -8
  35. package/src/cli/scripts/init/dev-registration.ts +0 -189
  36. package/src/cli/scripts/init/env-setup.ts +0 -44
  37. package/src/cli/scripts/init/file-operations.ts +0 -58
  38. package/src/cli/scripts/init/html-cleaner.ts +0 -45
  39. package/src/cli/scripts/init/main.ts +0 -175
  40. package/src/cli/scripts/init/package-setup.ts +0 -113
  41. package/src/cli/scripts/init/router-transformer.ts +0 -332
  42. package/src/cli/scripts/init/tailwind-config.ts +0 -66
  43. package/src/cli/scripts/init/vite-config.ts +0 -73
  44. package/src/cli/scripts/release/detect-translation-languages.ts +0 -37
  45. package/src/cli/scripts/release/release-config-upload.ts +0 -119
  46. package/src/cli/scripts/release/release-db-update.ts +0 -97
  47. package/src/cli/scripts/release/release-file-upload.ts +0 -138
  48. package/src/cli/scripts/release/release.ts +0 -85
  49. package/src/cli/types/DatabaseTypes.ts +0 -125
  50. package/src/controller/AIController.ts +0 -295
  51. package/src/controller/AccomplishmentController.ts +0 -188
  52. package/src/controller/AudioController.ts +0 -64
  53. package/src/controller/ExerciseController.ts +0 -117
  54. package/src/controller/ObjectController.ts +0 -120
  55. package/src/controller/SettingsController.ts +0 -186
  56. package/src/controller/SharedContentController.ts +0 -365
  57. package/src/controller/TranslationController.ts +0 -136
  58. package/src/controller/VoiceController.ts +0 -33
  59. package/src/fromRimori/EventBus.ts +0 -382
  60. package/src/fromRimori/PluginTypes.ts +0 -214
  61. package/src/fromRimori/readme.md +0 -2
  62. package/src/index.ts +0 -19
  63. package/src/plugin/CommunicationHandler.ts +0 -310
  64. package/src/plugin/Logger.ts +0 -394
  65. package/src/plugin/RimoriClient.ts +0 -530
  66. package/src/plugin/StandaloneClient.ts +0 -125
  67. package/src/utils/difficultyConverter.ts +0 -15
  68. package/src/utils/endpoint.ts +0 -3
  69. package/src/worker/WorkerSetup.ts +0 -35
  70. package/tsconfig.json +0 -17
@@ -1,214 +0,0 @@
1
- // whole configuration of a plugin (from the database)
2
- export type Plugin<T extends object = object> = Omit<RimoriPluginConfig<T>, 'context_menu_actions'> & {
3
- version: string;
4
- endpoint: string;
5
- assetEndpoint: string;
6
- context_menu_actions: MenuEntry[];
7
- release_channel: 'alpha' | 'beta' | 'stable';
8
- };
9
-
10
- export type ActivePlugin = Plugin<{ active?: boolean }>;
11
-
12
- // browsable page of a plugin
13
- export interface PluginPage {
14
- id: string;
15
- name: string;
16
- url: string;
17
- // Whether the page should be shown in the navbar
18
- show: boolean;
19
- description: string;
20
- root:
21
- | 'vocabulary'
22
- | 'grammar'
23
- | 'reading'
24
- | 'listening'
25
- | 'watching'
26
- | 'writing'
27
- | 'speaking'
28
- | 'other'
29
- | 'community';
30
- // The actions that can be triggered in the plugin
31
- // The key is the action key. The other entries are additional properties needed when triggering the action
32
- action?: {
33
- key: string;
34
- parameters: ObjectTool;
35
- };
36
- }
37
-
38
- // a sidebar page of a plugin
39
- export interface SidebarPage {
40
- // identifier of the page. Used to know which page to trigger when clicking on the sidebar
41
- id: string;
42
- // name of the page. Shown in the settings
43
- name: string;
44
- // description of the page. Shown in the settings
45
- description: string;
46
- // relative or absolute URL or path to the plugin's page
47
- url: string;
48
- // relative or absolute URL or path to the plugin's icon image
49
- icon: string;
50
- }
51
-
52
- // context menu entry being configured in the plugin configuration
53
- export interface MenuEntry {
54
- // id of the plugin that the menu entry belongs to
55
- plugin_id: string;
56
- // identifier of the menu entry action. Used to know which entry to trigger when clicking on the context menu
57
- action_key: string;
58
- // text of the menu entry. Shown in the context menu
59
- text: string;
60
- // icon of the menu entry. Shown in the context menu
61
- iconUrl?: string;
62
- }
63
-
64
- // an action from the main panel that can be triggered and performs an action in the main panel
65
- export type MainPanelAction = {
66
- plugin_id: string;
67
- action_key: string;
68
- } & Record<string, string>;
69
-
70
- // an action from the context menu that can be triggered and performs an action in the sidebar plugin
71
- export interface ContextMenuAction {
72
- // selected text when clicking on the context menu
73
- text: string;
74
- // id of the plugin that the action belongs to
75
- plugin_id: string;
76
- // key of the action. Used to know which action to trigger when clicking on the context menu
77
- action_key: string;
78
- }
79
-
80
- /**
81
- * Rimori plugin structure representing the complete configuration
82
- * of a Rimori plugin with all metadata and configuration options.
83
- */
84
- export interface RimoriPluginConfig<T extends object = object> {
85
- id: string;
86
- /**
87
- * Basic information about the plugin including branding and core details.
88
- */
89
- info: {
90
- /** The display name of the plugin shown to users */
91
- title: string;
92
- /** Detailed description introducing the plugin */
93
- description: string;
94
- /** relative or absolute URL or path to the plugin's logo/icon image */
95
- logo: string;
96
- /** Optional website URL for the plugin's homepage or link to plugins owner for contributions */
97
- website?: string;
98
- };
99
- /**
100
- * Configuration for different types of pages.
101
- */
102
- pages: {
103
- /** Optional external URL where the plugin is hosted instead of the default CDN */
104
- external_hosted_url?: string;
105
- /** Array of main plugin pages that appear in the application's main navigation (can be disabled using the 'show' flag) */
106
- main: (PluginPage & T)[];
107
- /** Array of sidebar pages that appear in the sidebar for quick access (can be disabled using the 'show' flag) */
108
- sidebar: (SidebarPage & T)[];
109
- /** Optional path to the plugin's settings/configuration page */
110
- settings?: string;
111
- /** Optional array of event topics the plugin pages can listen to for cross-plugin communication */
112
- topics?: string[];
113
- };
114
- /**
115
- * Context menu actions that the plugin registers to appear in right-click menus throughout the application.
116
- */
117
- context_menu_actions: Omit<MenuEntry, 'plugin_id'>[];
118
- /**
119
- * Documentation paths for different types of plugin documentation.
120
- */
121
- documentation: {
122
- /** Path to the general overview documentation. It's shown upon installation of the plugin. */
123
- overview_path: string;
124
- /** Path to user-facing documentation and guides */
125
- user_path: string;
126
- /** Path to developer documentation for plugin development */
127
- developer_path: string;
128
- };
129
- /**
130
- * Configuration for the plugin's web worker if it uses background processing or exposes actions to other plugins.
131
- */
132
- worker?: {
133
- /** Relative path to the web worker JavaScript file. Mostly it's 'web-worker.js' which is located in the public folder. */
134
- url: string;
135
- /** Optional array of event topics the worker should listen to in addition to events having the pluginId in the topic. Can be a wildcard. Example: 'global.topic.*' or 'pluginId.*' */
136
- topics?: string[];
137
- };
138
- }
139
-
140
- // copied from llm edge function
141
-
142
- export interface Tool {
143
- name: string;
144
- description: string;
145
- parameters: {
146
- name: string;
147
- description: string;
148
- type: 'string' | 'number' | 'boolean';
149
- }[];
150
- execute?: (args: Record<string, any>) => Promise<unknown> | unknown | void;
151
- }
152
-
153
- /**
154
- * The tool definition structure is used for LLM function calling and plugin action parameters.
155
- * It defines the schema for tools that can be used by Language Learning Models (LLMs)
156
- * and plugin actions.
157
- *
158
- * @example
159
- * ```typescript
160
- * const flashcardTool: Tool = {
161
- * total_amount: {
162
- * type: 'string',
163
- * enum: ['default', '10', '20', '50'],
164
- * description: 'Number of flashcards to practice'
165
- * },
166
- * deck: {
167
- * type: 'string',
168
- * enum: ['latest', 'random', 'oldest', 'mix', 'best_known'],
169
- * description: 'Type of deck to practice'
170
- * }
171
- * };
172
- * ```
173
- *
174
- */
175
- export type ObjectTool = {
176
- [key: string]: ToolParameter;
177
- };
178
-
179
- /**
180
- * Parameter definition for LLM tools and plugin actions.
181
- * Defines the structure, validation rules, and metadata for individual tool parameters.
182
- * Used to create type-safe interfaces between LLMs, plugins, and the Rimori platform.
183
- */
184
- interface ToolParameter {
185
- /** The data type of the parameter - can be primitive, nested object, or array */
186
- type: ToolParameterType;
187
- /** Human-readable description of the parameter's purpose and usage */
188
- description: string;
189
- /** Optional array of allowed values for enumerated parameters */
190
- enum?: string[];
191
- /** Whether the parameter is optional */
192
- optional?: boolean;
193
- }
194
-
195
- /**
196
- * Union type defining all possible parameter types for LLM tools.
197
- * Supports primitive types, nested objects for complex data structures,
198
- * and arrays of objects for collections. The tuple notation [{}] indicates
199
- * arrays of objects with a specific structure.
200
- *
201
- * @example Primitive: 'string' | 'number' | 'boolean'
202
- * @example Nested object: { name: { type: 'string' }, age: { type: 'number' } }
203
- * @example Array of objects: [{ id: { type: 'string' }, value: { type: 'number' } }]
204
- */
205
- type ToolParameterType =
206
- | PrimitiveType
207
- | { [key: string]: ToolParameter } // for nested objects
208
- | [{ [key: string]: ToolParameter }]; // for arrays of objects (notice the tuple type)
209
-
210
- /**
211
- * Primitive data types supported by the LLM tool system.
212
- * These align with JSON schema primitive types and TypeScript basic types.
213
- */
214
- type PrimitiveType = 'string' | 'number' | 'boolean';
@@ -1,2 +0,0 @@
1
- Files inside this folder are copied from Rimori main repository and should not be changed.
2
- If change is needed adapt it in Rimori and copy the file then over.
package/src/index.ts DELETED
@@ -1,19 +0,0 @@
1
- // Re-export everything
2
- export * from './plugin/CommunicationHandler';
3
- export * from './cli/types/DatabaseTypes';
4
- export * from './utils/difficultyConverter';
5
- export * from './fromRimori/PluginTypes';
6
- export * from './fromRimori/EventBus';
7
- export * from './plugin/RimoriClient';
8
- export * from './plugin/StandaloneClient';
9
- export { setupWorker } from './worker/WorkerSetup';
10
- export { AudioController } from './controller/AudioController';
11
- export { Translator } from './controller/TranslationController';
12
- export type { TOptions } from 'i18next';
13
- export type { SharedContent, SharedContentObjectRequest } from './controller/SharedContentController';
14
- export type { Exercise } from './controller/ExerciseController';
15
- export type { UserInfo, Language, UserRole } from './controller/SettingsController';
16
- export type { Message, ToolInvocation } from './controller/AIController';
17
- export type { TriggerAction } from './controller/ExerciseController';
18
- export type { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from './controller/AccomplishmentController';
19
- export type { EventBusMessage } from './fromRimori/EventBus';
@@ -1,310 +0,0 @@
1
- import { createClient, SupabaseClient } from '@supabase/supabase-js';
2
- import { UserInfo } from '../controller/SettingsController';
3
- import { EventBus, EventBusMessage } from '../fromRimori/EventBus';
4
- import { ActivePlugin, Plugin } from '../fromRimori/PluginTypes';
5
-
6
- // Add declaration for WorkerGlobalScope
7
- declare const WorkerGlobalScope: any;
8
-
9
- export interface Guild {
10
- allowUserPluginSettings: boolean;
11
- city: string | null;
12
- country: string | null;
13
- description: string | null;
14
- id: string;
15
- isPublic: boolean;
16
- name: string;
17
- ownerId: string;
18
- primaryLanguage: string;
19
- scope: string;
20
- }
21
-
22
- export interface RimoriInfo {
23
- url: string;
24
- key: string;
25
- backendUrl: string;
26
- token: string;
27
- expiration: Date;
28
- tablePrefix: string;
29
- pluginId: string;
30
- guild: Guild;
31
- installedPlugins: Plugin[];
32
- profile: UserInfo;
33
- mainPanelPlugin?: ActivePlugin;
34
- sidePanelPlugin?: ActivePlugin;
35
- interfaceLanguage: string;
36
- /**
37
- * The release channel of the plugin installation.
38
- */
39
- releaseChannel: 'alpha' | 'beta' | 'stable';
40
- /**
41
- * The database schema to use for plugin tables.
42
- * Determined by rimori-main based on release channel:
43
- * - 'plugins_alpha' for alpha release channel
44
- * - 'plugins' for beta and stable release channels
45
- */
46
- dbSchema: 'plugins' | 'plugins_alpha';
47
- }
48
-
49
- export class RimoriCommunicationHandler {
50
- private port: MessagePort | null = null;
51
- private queryParams: Record<string, string> = {};
52
- private supabase: SupabaseClient | null = null;
53
- private rimoriInfo: RimoriInfo | null = null;
54
- private pluginId: string;
55
- private isMessageChannelReady = false;
56
- private pendingRequests: Array<() => void> = [];
57
-
58
- public constructor(pluginId: string, standalone: boolean) {
59
- this.pluginId = pluginId;
60
- this.getClient = this.getClient.bind(this);
61
-
62
- //no need to forward messages to parent in standalone mode or worker context
63
- if (standalone) return;
64
-
65
- this.initMessageChannel(typeof WorkerGlobalScope !== 'undefined');
66
- }
67
-
68
- private initMessageChannel(worker = false): void {
69
- const listener = (event: MessageEvent) => {
70
- // console.log('[PluginController] window message', { origin: event.origin, data: event.data });
71
- const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
72
- const [transferredPort] = event.ports || [];
73
-
74
- if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
75
- // console.log('[PluginController] message ignored (not init or wrong plugin)', {
76
- // type,
77
- // pluginId,
78
- // currentPluginId: this.pluginId,
79
- // hasPortProperty: !!transferredPort,
80
- // event
81
- // });
82
- return;
83
- }
84
-
85
- this.queryParams = queryParams || {};
86
- this.port = transferredPort;
87
-
88
- // Initialize Supabase client immediately with provided info
89
- if (rimoriInfo) {
90
- this.rimoriInfo = rimoriInfo;
91
- this.supabase = createClient(rimoriInfo.url, rimoriInfo.key, {
92
- accessToken: () => Promise.resolve(rimoriInfo.token),
93
- });
94
- }
95
-
96
- // Handle messages from parent
97
- this.port.onmessage = ({ data }) => {
98
- const { event, type, eventId, response, error } = data || {};
99
-
100
- // no idea why this is needed but it works for now
101
- if (type === 'response' && eventId) {
102
- EventBus.emit(this.pluginId, response.topic, response.data, eventId);
103
- } else if (type === 'error' && eventId) {
104
- EventBus.emit(this.pluginId, 'error', { error }, eventId);
105
- } else if (event) {
106
- const { topic, sender, data: eventData, eventId } = event as EventBusMessage;
107
- if (sender !== this.pluginId) {
108
- EventBus.emit(sender, topic, eventData, eventId);
109
- }
110
- }
111
- };
112
-
113
- // Set theme from MessageChannel query params
114
- if (!worker) {
115
- // const theme = this.queryParams['rm_theme'];
116
- // setTheme(theme);
117
- // console.log('TODO: set theme from MessageChannel query params');
118
- }
119
-
120
- // Forward plugin events to parent (only after MessageChannel is ready)
121
- EventBus.on('*', (ev) => {
122
- if (ev.sender === this.pluginId && !ev.topic.startsWith('self.')) {
123
- this.port?.postMessage({ event: ev });
124
- }
125
- });
126
-
127
- // Mark MessageChannel as ready and process pending requests
128
- this.isMessageChannelReady = true;
129
-
130
- // Process any pending requests
131
- this.pendingRequests.forEach((request) => request());
132
- this.pendingRequests = [];
133
- };
134
- if (worker) {
135
- self.onmessage = listener;
136
- } else {
137
- window.addEventListener('message', listener);
138
- }
139
- this.sendHello(worker);
140
- EventBus.on('self.rimori.triggerInitFinished', () => {
141
- this.sendFinishedInit(worker);
142
- });
143
- }
144
-
145
- private sendHello(isWorker = false): void {
146
- try {
147
- const payload = { type: 'rimori:hello', pluginId: this.pluginId };
148
- if (isWorker) {
149
- self.postMessage(payload);
150
- } else {
151
- window.parent.postMessage(payload, '*');
152
- }
153
- } catch (e) {
154
- console.error('[PluginController] Error sending hello:', e);
155
- }
156
- }
157
-
158
- private sendFinishedInit(isWorker = false): void {
159
- try {
160
- const payload = { type: 'rimori:acknowledged', pluginId: this.pluginId };
161
- if (isWorker) {
162
- self.postMessage(payload);
163
- } else {
164
- window.parent.postMessage(payload, '*');
165
- }
166
- } catch (e) {
167
- console.error('[PluginController] Error sending finished init:', e);
168
- }
169
- }
170
-
171
- public getQueryParam(key: string): string | null {
172
- return this.queryParams[key] || null;
173
- }
174
-
175
- public async getClient(): Promise<{ supabase: SupabaseClient; info: RimoriInfo }> {
176
- // Return cached client if valid
177
- if (this.supabase && this.rimoriInfo && this.rimoriInfo.expiration > new Date()) {
178
- return { supabase: this.supabase, info: this.rimoriInfo };
179
- }
180
-
181
- // If MessageChannel is not ready yet, queue the request
182
- if (!this.isMessageChannelReady) {
183
- return new Promise<{ supabase: SupabaseClient; info: RimoriInfo }>((resolve) => {
184
- this.pendingRequests.push(async () => {
185
- const result = await this.getClient();
186
- resolve(result);
187
- });
188
- });
189
- }
190
-
191
- // If we have rimoriInfo from MessageChannel init, use it directly
192
- if (this.rimoriInfo && this.supabase) {
193
- return { supabase: this.supabase, info: this.rimoriInfo };
194
- }
195
-
196
- // Fallback: request from parent
197
- if (!this.rimoriInfo) {
198
- if (typeof WorkerGlobalScope !== 'undefined') {
199
- // In worker context, send request via self.postMessage to WorkerHandler
200
- const eventId = Math.floor(Math.random() * 1000000000);
201
- const requestEvent = {
202
- event: {
203
- timestamp: new Date().toISOString(),
204
- eventId,
205
- sender: this.pluginId,
206
- topic: 'global.supabase.requestAccess',
207
- data: {},
208
- debug: false,
209
- },
210
- };
211
-
212
- return new Promise<{ supabase: SupabaseClient; info: RimoriInfo }>((resolve) => {
213
- // Listen for the response
214
- const originalOnMessage = self.onmessage;
215
- self.onmessage = (event) => {
216
- if (event.data?.topic === 'global.supabase.requestAccess' && event.data?.eventId === eventId) {
217
- this.rimoriInfo = event.data.data;
218
- this.supabase = createClient(this.rimoriInfo!.url, this.rimoriInfo!.key, {
219
- accessToken: () => Promise.resolve(this.getToken()),
220
- });
221
- self.onmessage = originalOnMessage; // Restore original handler
222
- resolve({ supabase: this.supabase, info: this.rimoriInfo! });
223
- } else if (originalOnMessage) {
224
- originalOnMessage.call(self, event);
225
- }
226
- };
227
-
228
- // Send the request
229
- self.postMessage(requestEvent);
230
- });
231
- } else {
232
- // In main thread context, use EventBus
233
- const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
234
- // console.log({ data });
235
- this.rimoriInfo = data;
236
- this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
237
- accessToken: () => Promise.resolve(this.getToken()),
238
- });
239
- }
240
- }
241
-
242
- return { supabase: this.supabase!, info: this.rimoriInfo };
243
- }
244
-
245
- public async getToken(): Promise<string> {
246
- if (this.rimoriInfo && this.rimoriInfo.expiration && this.rimoriInfo.expiration > new Date()) {
247
- return this.rimoriInfo.token;
248
- }
249
-
250
- // If we don't have rimoriInfo, request it
251
- if (!this.rimoriInfo) {
252
- const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
253
- this.rimoriInfo = data;
254
- return this.rimoriInfo.token;
255
- }
256
-
257
- // If token is expired, request fresh access
258
- const { data } = await EventBus.request<{ token: string; expiration: Date }>(
259
- this.pluginId,
260
- 'global.supabase.requestAccess',
261
- );
262
- this.rimoriInfo.token = data.token;
263
- this.rimoriInfo.expiration = data.expiration;
264
-
265
- return this.rimoriInfo.token;
266
- }
267
-
268
- /**
269
- * Gets the Supabase URL.
270
- * @returns The Supabase URL.
271
- * @deprecated All endpoints should use the backend URL instead.
272
- */
273
- public getSupabaseUrl(): string {
274
- if (!this.rimoriInfo) {
275
- throw new Error('Supabase info not found');
276
- }
277
-
278
- return this.rimoriInfo.url;
279
- }
280
-
281
- public getBackendUrl(): string {
282
- if (!this.rimoriInfo) {
283
- throw new Error('Rimori info not found');
284
- }
285
- return this.rimoriInfo.backendUrl;
286
- }
287
-
288
- public getGlobalEventTopic(preliminaryTopic: string): string {
289
- if (preliminaryTopic.startsWith('global.')) {
290
- return preliminaryTopic;
291
- }
292
- if (preliminaryTopic.startsWith('self.')) {
293
- return preliminaryTopic;
294
- }
295
- const topicParts = preliminaryTopic.split('.');
296
- if (topicParts.length === 3) {
297
- if (!topicParts[0].startsWith('pl') && topicParts[0] !== 'global') {
298
- throw new Error("The event topic must start with the plugin id or 'global'.");
299
- }
300
- return preliminaryTopic;
301
- } else if (topicParts.length > 3) {
302
- throw new Error(
303
- `The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`,
304
- );
305
- }
306
-
307
- const topicRoot = this.rimoriInfo?.pluginId ?? 'global';
308
- return `${topicRoot}.${preliminaryTopic}`;
309
- }
310
- }