@rimori/client 2.4.0-next.6 → 2.4.0-next.7

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 (53) hide show
  1. package/package.json +5 -1
  2. package/.github/workflows/create-release-branch.yml +0 -226
  3. package/.github/workflows/pre-release.yml +0 -126
  4. package/.github/workflows/release-on-merge.yml +0 -195
  5. package/.prettierignore +0 -35
  6. package/eslint.config.js +0 -53
  7. package/example/docs/devdocs.md +0 -241
  8. package/example/docs/overview.md +0 -29
  9. package/example/docs/userdocs.md +0 -126
  10. package/example/rimori.config.ts +0 -91
  11. package/example/worker/vite.config.ts +0 -26
  12. package/example/worker/worker.ts +0 -11
  13. package/prettier.config.js +0 -8
  14. package/src/cli/scripts/init/dev-registration.ts +0 -191
  15. package/src/cli/scripts/init/env-setup.ts +0 -44
  16. package/src/cli/scripts/init/file-operations.ts +0 -58
  17. package/src/cli/scripts/init/html-cleaner.ts +0 -45
  18. package/src/cli/scripts/init/main.ts +0 -176
  19. package/src/cli/scripts/init/package-setup.ts +0 -113
  20. package/src/cli/scripts/init/router-transformer.ts +0 -332
  21. package/src/cli/scripts/init/tailwind-config.ts +0 -66
  22. package/src/cli/scripts/init/vite-config.ts +0 -73
  23. package/src/cli/scripts/release/detect-translation-languages.ts +0 -37
  24. package/src/cli/scripts/release/release-config-upload.ts +0 -119
  25. package/src/cli/scripts/release/release-db-update.ts +0 -97
  26. package/src/cli/scripts/release/release-file-upload.ts +0 -138
  27. package/src/cli/scripts/release/release.ts +0 -85
  28. package/src/cli/types/DatabaseTypes.ts +0 -125
  29. package/src/controller/AIController.ts +0 -295
  30. package/src/controller/AccomplishmentController.ts +0 -188
  31. package/src/controller/AudioController.ts +0 -64
  32. package/src/controller/ObjectController.ts +0 -120
  33. package/src/controller/SettingsController.ts +0 -186
  34. package/src/controller/SharedContentController.ts +0 -365
  35. package/src/controller/TranslationController.ts +0 -136
  36. package/src/controller/VoiceController.ts +0 -33
  37. package/src/fromRimori/EventBus.ts +0 -382
  38. package/src/fromRimori/PluginTypes.ts +0 -214
  39. package/src/fromRimori/readme.md +0 -2
  40. package/src/index.ts +0 -19
  41. package/src/plugin/CommunicationHandler.ts +0 -291
  42. package/src/plugin/Logger.ts +0 -394
  43. package/src/plugin/RimoriClient.ts +0 -199
  44. package/src/plugin/StandaloneClient.ts +0 -127
  45. package/src/plugin/module/AIModule.ts +0 -77
  46. package/src/plugin/module/DbModule.ts +0 -67
  47. package/src/plugin/module/EventModule.ts +0 -192
  48. package/src/plugin/module/ExerciseModule.ts +0 -131
  49. package/src/plugin/module/PluginModule.ts +0 -114
  50. package/src/utils/difficultyConverter.ts +0 -15
  51. package/src/utils/endpoint.ts +0 -3
  52. package/src/worker/WorkerSetup.ts +0 -35
  53. package/tsconfig.json +0 -17
@@ -1,199 +0,0 @@
1
- import {
2
- SharedContent,
3
- SharedContentController,
4
- SharedContentFilter,
5
- SharedContentObjectRequest,
6
- } from '../controller/SharedContentController';
7
- import { RimoriCommunicationHandler, RimoriInfo } from './CommunicationHandler';
8
- import { Logger } from './Logger';
9
- import { PluginModule } from './module/PluginModule';
10
- import { DbModule } from './module/DbModule';
11
- import { EventModule } from './module/EventModule';
12
- import { AIModule } from './module/AIModule';
13
- import { ExerciseModule } from './module/ExerciseModule';
14
- import { PostgrestClient } from '@supabase/postgrest-js';
15
-
16
- // Add declaration for WorkerGlobalScope
17
- declare const WorkerGlobalScope: any;
18
-
19
- export class RimoriClient {
20
- private static instance: RimoriClient;
21
- private pluginController: RimoriCommunicationHandler;
22
- private sharedContentController: SharedContentController;
23
- public db: DbModule;
24
- public event: EventModule;
25
- public plugin: PluginModule;
26
- public ai: AIModule;
27
- public exercise: ExerciseModule;
28
- private rimoriInfo: RimoriInfo;
29
-
30
- private constructor(controller: RimoriCommunicationHandler, supabase: PostgrestClient, info: RimoriInfo) {
31
- this.rimoriInfo = info;
32
- this.pluginController = controller;
33
- this.sharedContentController = new SharedContentController(supabase, this);
34
- this.ai = new AIModule(controller, info);
35
- this.event = new EventModule(info.pluginId);
36
- this.db = new DbModule(supabase, controller, info);
37
- this.plugin = new PluginModule(supabase, controller, info);
38
- this.exercise = new ExerciseModule(supabase, controller, info, this.event);
39
-
40
- controller.onUpdate((updatedInfo) => {
41
- this.rimoriInfo = updatedInfo;
42
- });
43
-
44
- //only init logger in workers and on main plugin pages
45
- if (this.plugin.applicationMode !== 'sidebar') {
46
- Logger.getInstance(this);
47
- }
48
- }
49
-
50
- public navigation = {
51
- toDashboard: (): void => {
52
- this.event.emit('global.navigation.triggerToDashboard');
53
- },
54
- };
55
-
56
- /**
57
- * Get a query parameter value that was passed via MessageChannel
58
- * @param key The query parameter key
59
- * @deprecated Use the plugin.applicationMode and plugin.theme properties instead
60
- * @returns The query parameter value or null if not found
61
- */
62
- public getQueryParam(key: string): string | null {
63
- return this.pluginController.getQueryParam(key);
64
- }
65
-
66
- public static async getInstance(pluginId?: string): Promise<RimoriClient> {
67
- if (!RimoriClient.instance) {
68
- if (!pluginId) throw new Error('Plugin ID is required');
69
-
70
- const controller = new RimoriCommunicationHandler(pluginId, false);
71
-
72
- if (typeof WorkerGlobalScope === 'undefined') {
73
- // In standalone mode, use URL fallback. In iframe mode, theme will be set after MessageChannel init
74
- // setTheme();
75
- // await StandaloneClient.initListeners(pluginId);
76
- }
77
- const client = await controller.getClient();
78
- RimoriClient.instance = new RimoriClient(controller, client.supabase, client.info);
79
- }
80
- return RimoriClient.instance;
81
- }
82
-
83
- public runtime = {
84
- fetchBackend: async (url: string, options: RequestInit) => {
85
- return fetch(this.rimoriInfo.backendUrl + url, {
86
- ...options,
87
- headers: {
88
- ...options.headers,
89
- Authorization: `Bearer ${this.rimoriInfo.token}`,
90
- },
91
- });
92
- },
93
- };
94
-
95
- public community = {
96
- /**
97
- * Shared content is a way to share completable content with other users using this plugin.
98
- * Typical examples are assignments, exercises, stories, etc.
99
- * Users generate new shared content items and others can complete the content too.
100
- */
101
- sharedContent: {
102
- /**
103
- * Get one dedicated shared content item by id. It does not matter if it is completed or not.
104
- * @param contentType The type of shared content to get. E.g. assignments, exercises, etc.
105
- * @param id The id of the shared content item.
106
- * @returns The shared content item.
107
- */
108
- get: async <T = any>(contentType: string, id: string): Promise<SharedContent<T>> => {
109
- return await this.sharedContentController.getSharedContent(contentType, id);
110
- },
111
- /**
112
- * Get a list of shared content items.
113
- * @param contentType The type of shared content to get. E.g. assignments, exercises, etc.
114
- * @param filter The optional additional filter for checking new shared content based on a column and value. This is useful if the aditional information stored on the shared content is used to further narrow down the kind of shared content wanted to be received. E.g. only adjective grammar exercises.
115
- * @param limit The optional limit for the number of results.
116
- * @returns The list of shared content items.
117
- */
118
- getList: async <T = any>(
119
- contentType: string,
120
- filter?: SharedContentFilter,
121
- limit?: number,
122
- ): Promise<SharedContent<T>[]> => {
123
- return await this.sharedContentController.getSharedContentList(contentType, filter, limit);
124
- },
125
- /**
126
- * Get new shared content.
127
- * @param contentType The type of shared content to fetch. E.g. assignments, exercises, etc.
128
- * @param generatorInstructions The instructions for the creation of new shared content. The object will automatically be extended with a tool property with a topic and keywords property to let a new unique topic be generated.
129
- * @param filter The optional additional filter for checking new shared content based on a column and value. This is useful if the aditional information stored on the shared content is used to further narrow down the kind of shared content wanted to be received. E.g. only adjective grammar exercises.
130
- * @param options An optional object with options for the new shared content.
131
- * @param options.privateTopic An optional flag to indicate if the topic should be private and only be visible to the user. This is useful if the topic is not meant to be shared with other users. Like for personal topics or if the content is based on the personal study goal.
132
- * @param options.skipDbSave An optional flag to indicate if the new shared content should not be saved to the database. This is useful if the new shared content is not meant to be saved to the database.
133
- * @param options.alwaysGenerateNew An optional flag to indicate if the new shared content should always be generated even if there is already a content with the same filter. This is useful if the new shared content is not meant to be saved to the database.
134
- * @param options.excludeIds An optional list of ids to exclude from the selection. This is useful if the new shared content is not meant to be saved to the database.
135
- * @returns The new shared content.
136
- */
137
- getNew: async <T = any>(
138
- contentType: string,
139
- generatorInstructions: SharedContentObjectRequest,
140
- filter?: SharedContentFilter,
141
- options?: { privateTopic?: boolean; skipDbSave?: boolean; alwaysGenerateNew?: boolean; excludeIds?: string[] },
142
- ): Promise<SharedContent<T>> => {
143
- return await this.sharedContentController.getNewSharedContent(
144
- contentType,
145
- generatorInstructions,
146
- filter,
147
- options,
148
- );
149
- },
150
- /**
151
- * Create a new shared content item.
152
- * @param content The content to create.
153
- * @returns The new shared content item.
154
- */
155
- create: async <T = any>(content: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>> => {
156
- return await this.sharedContentController.createSharedContent(content);
157
- },
158
- /**
159
- * Update a shared content item.
160
- * @param id The id of the shared content item to update.
161
- * @param content The content to update.
162
- * @returns The updated shared content item.
163
- */
164
- update: async <T = any>(id: string, content: Partial<SharedContent<T>>): Promise<SharedContent<T>> => {
165
- return await this.sharedContentController.updateSharedContent(id, content);
166
- },
167
- /**
168
- * Complete a shared content item.
169
- * @param contentType The type of shared content to complete. E.g. assignments, exercises, etc.
170
- * @param assignmentId The id of the shared content item to complete.
171
- */
172
- complete: async (contentType: string, assignmentId: string) => {
173
- return await this.sharedContentController.completeSharedContent(contentType, assignmentId);
174
- },
175
- /**
176
- /**
177
- * Update the state of a shared content item for a specific user.
178
- * Useful for marking content as completed, ongoing, hidden, liked, disliked, or bookmarked.
179
- */
180
- updateState: async (params: {
181
- contentType: string;
182
- id: string;
183
- state?: 'completed' | 'ongoing' | 'hidden';
184
- reaction?: 'liked' | 'disliked' | null;
185
- bookmarked?: boolean;
186
- }): Promise<void> => {
187
- return await this.sharedContentController.updateSharedContentState(params);
188
- },
189
- /**
190
- * Remove a shared content item.
191
- * @param id The id of the shared content item to remove.
192
- * @returns The removed shared content item.
193
- */
194
- remove: async (id: string): Promise<SharedContent<any>> => {
195
- return await this.sharedContentController.removeSharedContent(id);
196
- },
197
- },
198
- };
199
- }
@@ -1,127 +0,0 @@
1
- import { SupabaseClient } from './CommunicationHandler';
2
- import { EventBus } from '../fromRimori/EventBus';
3
- import { DEFAULT_ANON_KEY, DEFAULT_ENDPOINT } from '../utils/endpoint';
4
-
5
- export interface StandaloneConfig {
6
- url: string;
7
- key: string;
8
- backendUrl?: string;
9
- }
10
-
11
- export class StandaloneClient {
12
- private static instance: StandaloneClient;
13
- private config: StandaloneConfig;
14
- private supabase: SupabaseClient & { auth: any }; // TODO: remove any
15
-
16
- private constructor(config: StandaloneConfig) {
17
- throw new Error('Authentication is disabled until new developer platform is released.');
18
- // this.supabase = createClient(config.url, config.key);
19
- this.supabase = {} as any;
20
- this.config = config;
21
- }
22
-
23
- public static async getInstance(): Promise<StandaloneClient> {
24
- if (!StandaloneClient.instance) {
25
- const config = await fetch('https://app.rimori.se/config.json')
26
- .then((res) => res.json())
27
- .catch((err) => {
28
- console.warn('Error fetching config.json, using default values', err);
29
- });
30
- StandaloneClient.instance = new StandaloneClient({
31
- url: config?.SUPABASE_URL || DEFAULT_ENDPOINT,
32
- key: config?.SUPABASE_ANON_KEY || DEFAULT_ANON_KEY,
33
- backendUrl: config?.BACKEND_URL || 'https://api.rimori.se',
34
- });
35
- }
36
- return StandaloneClient.instance;
37
- }
38
-
39
- public async getClient(): Promise<SupabaseClient> {
40
- return this.supabase;
41
- }
42
-
43
- public async needsLogin(): Promise<boolean> {
44
- const { error } = await this.supabase.auth.getUser();
45
- return error !== null;
46
- }
47
-
48
- public async login(email: string, password: string) {
49
- const { error } = await this.supabase.auth.signInWithPassword({ email, password });
50
- if (error) {
51
- console.error('Login failed:', error);
52
- return false;
53
- }
54
- console.log('Successfully logged in');
55
- return true;
56
- }
57
-
58
- public static async initListeners(pluginId: string) {
59
- console.warn(
60
- 'The plugin seams to not be running inside the Rimori platform. Switching to development standalone mode.',
61
- );
62
- // console.log("event that needs to be handled", event);
63
- const { supabase, config } = await StandaloneClient.getInstance();
64
-
65
- // EventBus.on("*", async (event) => {
66
- EventBus.respond('standalone', 'global.supabase.requestAccess', async () => {
67
- const session = await supabase.auth.getSession();
68
- console.log('session', session);
69
-
70
- // Call the NestJS backend endpoint instead of the Supabase edge function
71
- // get current guild id if any
72
- let guildId: string | null = null;
73
- try {
74
- const {
75
- data: { user },
76
- } = await supabase.auth.getUser();
77
- if (user) {
78
- const { data: profile } = await supabase
79
- .from('profiles')
80
- .select('current_guild_id')
81
- .eq('user_id', user.id)
82
- .maybeSingle();
83
- guildId = (profile as { current_guild_id?: string | null } | null)?.current_guild_id || null;
84
- }
85
- } catch (_) {
86
- guildId = null;
87
- }
88
-
89
- const response = await fetch(`${config.backendUrl}/plugin/token`, {
90
- method: 'POST',
91
- headers: {
92
- 'Content-Type': 'application/json',
93
- Authorization: `Bearer ${session.data.session?.access_token}`,
94
- },
95
- body: JSON.stringify({
96
- pluginId: pluginId,
97
- guildId: guildId,
98
- }),
99
- });
100
-
101
- if (!response.ok) {
102
- const errorText = await response.text();
103
- throw new Error(`Failed to get plugin token. ${response.status}: ${errorText}`);
104
- }
105
-
106
- const data = await response.json();
107
-
108
- return {
109
- token: data.token,
110
- pluginId: pluginId,
111
- url: config.url,
112
- key: config.key,
113
- backendUrl: config.backendUrl,
114
- tablePrefix: pluginId,
115
- expiration: new Date(Date.now() + 1000 * 60 * 60 * 1.5), // 1.5 hours
116
- };
117
- });
118
-
119
- EventBus.on(
120
- '*',
121
- async (event) => {
122
- console.log('[standalone] would send event to parent', event);
123
- },
124
- ['standalone'],
125
- );
126
- }
127
- }
@@ -1,77 +0,0 @@
1
- import { RimoriCommunicationHandler, RimoriInfo } from '../CommunicationHandler';
2
- import { generateText, Message, OnLLMResponse, streamChatGPT } from '../../controller/AIController';
3
- import { generateObject, ObjectRequest } from '../../controller/ObjectController';
4
- import { getSTTResponse, getTTSResponse } from '../../controller/VoiceController';
5
- import { Tool } from '../../fromRimori/PluginTypes';
6
-
7
- /**
8
- * Controller for AI-related operations.
9
- * Provides access to text generation, voice synthesis, and object generation.
10
- */
11
- export class AIModule {
12
- private communicationHandler: RimoriCommunicationHandler;
13
- private backendUrl: string;
14
- private token: string;
15
-
16
- constructor(communicationHandler: RimoriCommunicationHandler, info: RimoriInfo) {
17
- this.token = info.token;
18
- this.backendUrl = info.backendUrl;
19
- this.communicationHandler = communicationHandler;
20
-
21
- this.communicationHandler.onUpdate((updatedInfo) => {
22
- this.token = updatedInfo.token;
23
- });
24
- }
25
-
26
- /**
27
- * Generate text from messages using AI.
28
- * @param messages The messages to generate text from.
29
- * @param tools Optional tools to use for generation.
30
- * @returns The generated text.
31
- */
32
- async getText(messages: Message[], tools?: Tool[]): Promise<string> {
33
- return generateText(this.backendUrl, messages, tools || [], this.token).then(
34
- ({ messages }) => messages[0].content[0].text,
35
- );
36
- }
37
-
38
- /**
39
- * Stream text generation from messages using AI.
40
- * @param messages The messages to generate text from.
41
- * @param onMessage Callback for each message chunk.
42
- * @param tools Optional tools to use for generation.
43
- */
44
- async getSteamedText(messages: Message[], onMessage: OnLLMResponse, tools?: Tool[]): Promise<void> {
45
- streamChatGPT(this.backendUrl, messages, tools || [], onMessage, this.token);
46
- }
47
-
48
- /**
49
- * Generate voice audio from text using AI.
50
- * @param text The text to convert to voice.
51
- * @param voice The voice to use (default: 'alloy').
52
- * @param speed The speed of the voice (default: 1).
53
- * @param language Optional language for the voice.
54
- * @returns The generated audio as a Blob.
55
- */
56
- async getVoice(text: string, voice = 'alloy', speed = 1, language?: string): Promise<Blob> {
57
- return getTTSResponse(this.backendUrl, { input: text, voice, speed, language }, this.token);
58
- }
59
-
60
- /**
61
- * Convert voice audio to text using AI.
62
- * @param file The audio file to convert.
63
- * @returns The transcribed text.
64
- */
65
- async getTextFromVoice(file: Blob): Promise<string> {
66
- return getSTTResponse(this.backendUrl, file, this.token);
67
- }
68
-
69
- /**
70
- * Generate a structured object from a request using AI.
71
- * @param request The object generation request.
72
- * @returns The generated object.
73
- */
74
- async getObject<T = any>(request: ObjectRequest): Promise<T> {
75
- return generateObject<T>(this.backendUrl, request, this.token);
76
- }
77
- }
@@ -1,67 +0,0 @@
1
- import { PostgrestClientOptions, PostgrestQueryBuilder } from '@supabase/postgrest-js';
2
- import { SupabaseClient } from '../CommunicationHandler';
3
- // import { GenericSchema } from '@supabase/postgrest-js/dist/module/lib/types';
4
- import { RimoriCommunicationHandler, RimoriInfo } from '../CommunicationHandler';
5
- import { GenericSchema, GenericTable } from '@supabase/postgrest-js/dist/cjs/types/common/common';
6
-
7
- /**
8
- * Database module for plugin database operations.
9
- * Provides access to plugin tables with automatic prefixing and schema management.
10
- */
11
- export class DbModule {
12
- private supabase: SupabaseClient;
13
- private rimoriInfo: RimoriInfo;
14
- public tablePrefix: string;
15
- public schema: string;
16
-
17
- constructor(supabase: SupabaseClient, communicationHandler: RimoriCommunicationHandler, info: RimoriInfo) {
18
- this.supabase = supabase;
19
- this.rimoriInfo = info;
20
- this.tablePrefix = info.tablePrefix;
21
- this.schema = info.dbSchema;
22
-
23
- communicationHandler.onUpdate((updatedInfo) => {
24
- this.rimoriInfo = updatedInfo;
25
- this.tablePrefix = updatedInfo.tablePrefix;
26
- this.schema = updatedInfo.dbSchema;
27
- });
28
- }
29
-
30
- /**
31
- * Query a database table.
32
- * Global tables (starting with 'global_') remain in public schema.
33
- * Plugin tables use the schema provided by rimori-main (plugins or plugins_alpha).
34
- * @param relation The table name (without prefix for plugin tables, with 'global_' for global tables).
35
- * @returns A Postgrest query builder for the table.
36
- */
37
- from<ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
38
- relation: string,
39
- ): PostgrestQueryBuilder<PostgrestClientOptions, GenericSchema, GenericTable, ViewName, View> {
40
- const tableName = this.getTableName(relation);
41
- // Use the schema determined by rimori-main based on release channel
42
- // Global tables (starting with 'global_') remain in public schema
43
- // Plugin tables use the schema provided by rimori-main (plugins or plugins_alpha)
44
- if (relation.startsWith('global_')) {
45
- // Global tables stay in public schema
46
- return this.supabase.from(tableName);
47
- }
48
- // Plugin tables go to the schema provided by rimori-main
49
- return this.supabase.schema(this.rimoriInfo.dbSchema).from(tableName);
50
- }
51
-
52
- /**
53
- * Get the table name for a given plugin table.
54
- * Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
55
- * @param table The plugin table name to get the full table name for.
56
- * @returns The full table name.
57
- */
58
- getTableName(table: string): string {
59
- if (/[A-Z]/.test(table)) {
60
- throw new Error('Table name cannot include uppercase letters. Please use snake_case for table names.');
61
- }
62
- if (table.startsWith('global_')) {
63
- return table.replace('global_', '');
64
- }
65
- return this.tablePrefix + '_' + table;
66
- }
67
- }
@@ -1,192 +0,0 @@
1
- import { MainPanelAction } from '../../fromRimori/PluginTypes';
2
- import { AccomplishmentController, AccomplishmentPayload } from '../../controller/AccomplishmentController';
3
- import { EventBus, EventBusMessage, EventHandler, EventPayload, EventListener } from '../../fromRimori/EventBus';
4
-
5
- /**
6
- * Event module for plugin event bus operations.
7
- * Provides methods for emitting, listening to, and responding to events.
8
- */
9
- export class EventModule {
10
- private pluginId: string;
11
- private accomplishmentController: AccomplishmentController;
12
-
13
- constructor(pluginId: string) {
14
- this.pluginId = pluginId;
15
- this.accomplishmentController = new AccomplishmentController(pluginId);
16
- }
17
-
18
- public getGlobalEventTopic(preliminaryTopic: string): string {
19
- if (preliminaryTopic.startsWith('global.')) {
20
- return preliminaryTopic;
21
- }
22
- if (preliminaryTopic.startsWith('self.')) {
23
- return preliminaryTopic;
24
- }
25
- const topicParts = preliminaryTopic.split('.');
26
- if (topicParts.length === 3) {
27
- if (!topicParts[0].startsWith('pl') && topicParts[0] !== 'global') {
28
- throw new Error("The event topic must start with the plugin id or 'global'.");
29
- }
30
- return preliminaryTopic;
31
- } else if (topicParts.length > 3) {
32
- throw new Error(
33
- `The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`,
34
- );
35
- }
36
-
37
- const topicRoot = this.pluginId ?? 'global';
38
- return `${topicRoot}.${preliminaryTopic}`;
39
- }
40
-
41
- /**
42
- * Emit an event to Rimori or a plugin.
43
- * The topic schema is:
44
- * {pluginId}.{eventId}
45
- * Check out the event bus documentation for more information.
46
- * For triggering events from Rimori like context menu actions use the "global" keyword.
47
- * @param topic The topic to emit the event on.
48
- * @param data The data to emit.
49
- * @param eventId The event id.
50
- */
51
- emit(topic: string, data?: any, eventId?: number): void {
52
- const globalTopic = this.getGlobalEventTopic(topic);
53
- EventBus.emit(this.pluginId, globalTopic, data, eventId);
54
- }
55
-
56
- /**
57
- * Request an event.
58
- * @param topic The topic to request the event on.
59
- * @param data The data to request.
60
- * @returns The response from the event.
61
- */
62
- request<T>(topic: string, data?: any): Promise<EventBusMessage<T>> {
63
- const globalTopic = this.getGlobalEventTopic(topic);
64
- return EventBus.request<T>(this.pluginId, globalTopic, data);
65
- }
66
-
67
- /**
68
- * Subscribe to an event.
69
- * @param topic The topic to subscribe to.
70
- * @param callback The callback to call when the event is emitted.
71
- * @returns An EventListener object containing an off() method to unsubscribe the listeners.
72
- */
73
- on<T = EventPayload>(topic: string | string[], callback: EventHandler<T>): EventListener {
74
- const topics = Array.isArray(topic) ? topic : [topic];
75
- return EventBus.on<T>(
76
- topics.map((t) => this.getGlobalEventTopic(t)),
77
- callback,
78
- );
79
- }
80
-
81
- /**
82
- * Subscribe to an event once.
83
- * @param topic The topic to subscribe to.
84
- * @param callback The callback to call when the event is emitted.
85
- */
86
- once<T = EventPayload>(topic: string, callback: EventHandler<T>): void {
87
- EventBus.once<T>(this.getGlobalEventTopic(topic), callback);
88
- }
89
-
90
- /**
91
- * Respond to an event.
92
- * @param topic The topic to respond to.
93
- * @param data The data to respond with.
94
- */
95
- respond<T = EventPayload>(
96
- topic: string | string[],
97
- data: EventPayload | ((data: EventBusMessage<T>) => EventPayload | Promise<EventPayload>),
98
- ): void {
99
- const topics = Array.isArray(topic) ? topic : [topic];
100
- EventBus.respond(
101
- this.pluginId,
102
- topics.map((t) => this.getGlobalEventTopic(t)),
103
- data,
104
- );
105
- }
106
-
107
- /**
108
- * Emit an accomplishment.
109
- * @param payload The payload to emit.
110
- */
111
- emitAccomplishment(payload: AccomplishmentPayload): void {
112
- this.accomplishmentController.emitAccomplishment(payload);
113
- }
114
-
115
- /**
116
- * Subscribe to an accomplishment.
117
- * @param accomplishmentTopic The topic to subscribe to.
118
- * @param callback The callback to call when the accomplishment is emitted.
119
- */
120
- onAccomplishment(
121
- accomplishmentTopic: string,
122
- callback: (payload: EventBusMessage<AccomplishmentPayload>) => void,
123
- ): void {
124
- this.accomplishmentController.subscribe(accomplishmentTopic, callback);
125
- }
126
-
127
- /**
128
- * Trigger an action that opens the sidebar and triggers an action in the designated plugin.
129
- * @param pluginId The id of the plugin to trigger the action for.
130
- * @param actionKey The key of the action to trigger.
131
- * @param text Optional text to be used for the action like for example text that the translator would look up.
132
- */
133
- emitSidebarAction(pluginId: string, actionKey: string, text?: string): void {
134
- this.emit('global.sidebar.triggerAction', { plugin_id: pluginId, action_key: actionKey, text });
135
- }
136
-
137
- /**
138
- * Subscribe to main panel actions triggered by the user from the dashboard.
139
- * @param callback Handler function that receives the action data when a matching action is triggered.
140
- * @param actionsToListen Optional filter to listen only to specific action keys. If empty or not provided, all actions will trigger the callback.
141
- * @returns An EventListener object with an `off()` method for cleanup.
142
- *
143
- * @example
144
- * ```ts
145
- * const listener = client.event.onMainPanelAction((data) => {
146
- * console.log('Action received:', data.action_key);
147
- * }, ['startSession', 'pauseSession']);
148
- *
149
- * // Clean up when component unmounts to prevent events from firing
150
- * // when navigating away or returning to the page
151
- * useEffect(() => {
152
- * return () => listener.off();
153
- * }, []);
154
- * ```
155
- *
156
- * **Important:** Always call `listener.off()` when your component unmounts or when you no longer need to listen.
157
- * This prevents the event handler from firing when navigating away from or returning to the page, which could
158
- * cause unexpected behavior or duplicate event handling.
159
- */
160
- onMainPanelAction(callback: (data: MainPanelAction) => void, actionsToListen: string | string[] = []): EventListener {
161
- const listeningActions = Array.isArray(actionsToListen) ? actionsToListen : [actionsToListen];
162
- // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
163
- this.emit('action.requestMain');
164
- return this.on<MainPanelAction>('action.requestMain', ({ data }) => {
165
- // console.log('Received action for main panel ' + data.action_key);
166
- // console.log('Listening to actions', listeningActions);
167
- if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
168
- callback(data);
169
- }
170
- });
171
- }
172
-
173
- /**
174
- * Subscribe to side panel actions triggered by the user from the dashboard.
175
- * @param callback Handler function that receives the action data when a matching action is triggered.
176
- * @param actionsToListen Optional filter to listen only to specific action keys. If empty or not provided, all actions will trigger the callback.
177
- * @returns An EventListener object with an `off()` method for cleanup.
178
- */
179
- onSidePanelAction(callback: (data: MainPanelAction) => void, actionsToListen: string | string[] = []): EventListener {
180
- const listeningActions = Array.isArray(actionsToListen) ? actionsToListen : [actionsToListen];
181
- // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
182
- this.emit('action.requestSidebar');
183
- return this.on<MainPanelAction>('action.requestSidebar', ({ data }) => {
184
- // console.log("eventHandler .onSidePanelAction", data);
185
- // console.log('Received action for sidebar ' + data.action);
186
- // console.log('Listening to actions', listeningActions);
187
- if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
188
- callback(data);
189
- }
190
- });
191
- }
192
- }