@rimori/client 2.4.0 → 2.5.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.
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 +16 -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,530 +0,0 @@
1
- import { PostgrestQueryBuilder } from '@supabase/postgrest-js';
2
- import { SupabaseClient } from '@supabase/supabase-js';
3
- import { GenericSchema } from '@supabase/supabase-js/dist/module/lib/types';
4
- import { generateText, Message, OnLLMResponse, streamChatGPT } from '../controller/AIController';
5
- import { generateObject, ObjectRequest } from '../controller/ObjectController';
6
- import { SettingsController, UserInfo } from '../controller/SettingsController';
7
- import {
8
- SharedContent,
9
- SharedContentController,
10
- SharedContentFilter,
11
- SharedContentObjectRequest,
12
- } from '../controller/SharedContentController';
13
- import { getSTTResponse, getTTSResponse } from '../controller/VoiceController';
14
- import { ExerciseController, CreateExerciseParams } from '../controller/ExerciseController';
15
- import { EventBus, EventBusMessage, EventHandler, EventPayload, EventListener } from '../fromRimori/EventBus';
16
- import { ActivePlugin, MainPanelAction, Plugin, Tool } from '../fromRimori/PluginTypes';
17
- import { AccomplishmentController, AccomplishmentPayload } from '../controller/AccomplishmentController';
18
- import { RimoriCommunicationHandler, RimoriInfo } from './CommunicationHandler';
19
- import { Translator } from '../controller/TranslationController';
20
- import { Logger } from './Logger';
21
- // import { setTheme } from '../../../react-client/src/plugin/ThemeSetter';
22
- // import { StandaloneClient } from './StandaloneClient';
23
-
24
- // Add declaration for WorkerGlobalScope
25
- declare const WorkerGlobalScope: any;
26
-
27
- export class RimoriClient {
28
- private static instance: RimoriClient;
29
- private superbase: SupabaseClient;
30
- private pluginController: RimoriCommunicationHandler;
31
- private settingsController: SettingsController;
32
- private sharedContentController: SharedContentController;
33
- private exerciseController: ExerciseController;
34
- private accomplishmentHandler: AccomplishmentController;
35
- private rimoriInfo: RimoriInfo;
36
- private translator: Translator;
37
-
38
- private constructor(controller: RimoriCommunicationHandler, supabase: SupabaseClient, info: RimoriInfo) {
39
- this.rimoriInfo = info;
40
- this.superbase = supabase;
41
- this.pluginController = controller;
42
- this.exerciseController = new ExerciseController(supabase, this);
43
- this.accomplishmentHandler = new AccomplishmentController(info.pluginId);
44
- this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
45
- this.sharedContentController = new SharedContentController(supabase, this);
46
- const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
47
- this.translator = new Translator(info.interfaceLanguage, currentPlugin?.endpoint || '');
48
-
49
- //only init logger in workers and on main plugin pages
50
- if (this.getQueryParam('applicationMode') !== 'sidebar') {
51
- Logger.getInstance(this);
52
- }
53
- }
54
-
55
- public get plugin() {
56
- return {
57
- pluginId: this.rimoriInfo.pluginId,
58
- /**
59
- * The release channel of this plugin installation.
60
- * Determines which database schema is used for plugin tables.
61
- */
62
- releaseChannel: this.rimoriInfo.releaseChannel,
63
- /**
64
- * Set the settings for the plugin.
65
- * @param settings The settings to set.
66
- */
67
- setSettings: async (settings: any): Promise<void> => {
68
- await this.settingsController.setSettings(settings);
69
- },
70
- /**
71
- * Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
72
- * @param defaultSettings The default settings to use if no settings are found.
73
- * @param genericSettings The type of settings to get.
74
- * @returns The settings for the plugin.
75
- */
76
- getSettings: async <T extends object>(defaultSettings: T): Promise<T> => {
77
- return await this.settingsController.getSettings<T>(defaultSettings);
78
- },
79
- getUserInfo: (): UserInfo => {
80
- return this.rimoriInfo.profile;
81
- },
82
- /**
83
- * Retrieves information about plugins, including:
84
- * - All installed plugins
85
- * - The currently active plugin in the main panel
86
- * - The currently active plugin in the side panel
87
- */
88
- getPluginInfo: (): {
89
- /**
90
- * All installed plugins.
91
- */
92
- installedPlugins: Plugin[];
93
- /**
94
- * The plugin that is loaded in the main panel.
95
- */
96
- mainPanelPlugin?: ActivePlugin;
97
- /**
98
- * The plugin that is loaded in the side panel.
99
- */
100
- sidePanelPlugin?: ActivePlugin;
101
- } => {
102
- return {
103
- installedPlugins: this.rimoriInfo.installedPlugins,
104
- mainPanelPlugin: this.rimoriInfo.mainPanelPlugin,
105
- sidePanelPlugin: this.rimoriInfo.sidePanelPlugin,
106
- };
107
- },
108
- /**
109
- * Get the translator for the plugin.
110
- * @returns The translator for the plugin.
111
- */
112
- getTranslator: async (): Promise<Translator> => {
113
- await this.translator.initialize();
114
- return this.translator;
115
- },
116
- };
117
- }
118
-
119
- public get db() {
120
- return {
121
- // private from<
122
- // TableName extends string & keyof GenericSchema['Tables'],
123
- // Table extends GenericSchema['Tables'][TableName],
124
- // >(relation: TableName): PostgrestQueryBuilder<GenericSchema, Table, TableName>;
125
- // private from<ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
126
- // relation: ViewName,
127
- // ): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
128
- from: <ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
129
- relation: string,
130
- ): PostgrestQueryBuilder<GenericSchema, View, ViewName> => {
131
- const tableName = this.db.getTableName(relation);
132
- // Use the schema determined by rimori-main based on release channel
133
- // Global tables (starting with 'global_') remain in public schema
134
- // Plugin tables use the schema provided by rimori-main (plugins or plugins_alpha)
135
- if (relation.startsWith('global_')) {
136
- // Global tables stay in public schema
137
- return this.superbase.from(tableName);
138
- }
139
- // Plugin tables go to the schema provided by rimori-main
140
- return this.superbase.schema(this.rimoriInfo.dbSchema).from(tableName);
141
- },
142
- // storage: this.superbase.storage,
143
- // functions: this.superbase.functions,
144
- /**
145
- * The table prefix for of database tables of the plugin.
146
- */
147
- tablePrefix: this.rimoriInfo.tablePrefix,
148
- /**
149
- * The database schema used for plugin tables.
150
- * Determined by rimori-main based on release channel:
151
- * - 'plugins_alpha' for alpha release channel
152
- * - 'plugins' for beta and stable release channels
153
- */
154
- schema: this.rimoriInfo.dbSchema,
155
- /**
156
- * Get the table name for a given plugin table.
157
- * Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
158
- * @param table The plugin table name to get the full table name for.
159
- * @returns The full table name.
160
- */
161
- getTableName: (table: string): string => {
162
- if (/[A-Z]/.test(table)) {
163
- throw new Error('Table name cannot include uppercase letters. Please use snake_case for table names.');
164
- }
165
- if (table.startsWith('global_')) {
166
- return table.replace('global_', '');
167
- }
168
- return this.db.tablePrefix + '_' + table;
169
- },
170
- };
171
- }
172
-
173
- public event = {
174
- /**
175
- * Emit an event to Rimori or a plugin.
176
- * The topic schema is:
177
- * {pluginId}.{eventId}
178
- * Check out the event bus documentation for more information.
179
- * For triggering events from Rimori like context menu actions use the "global" keyword.
180
- * @param topic The topic to emit the event on.
181
- * @param data The data to emit.
182
- * @param eventId The event id.
183
- */
184
- emit: (topic: string, data?: any, eventId?: number) => {
185
- const globalTopic = this.pluginController.getGlobalEventTopic(topic);
186
- EventBus.emit(this.plugin.pluginId, globalTopic, data, eventId);
187
- },
188
- /**
189
- * Request an event.
190
- * @param topic The topic to request the event on.
191
- * @param data The data to request.
192
- * @returns The response from the event.
193
- */
194
- request: <T>(topic: string, data?: any): Promise<EventBusMessage<T>> => {
195
- const globalTopic = this.pluginController.getGlobalEventTopic(topic);
196
- return EventBus.request<T>(this.plugin.pluginId, globalTopic, data);
197
- },
198
- /**
199
- * Subscribe to an event.
200
- * @param topic The topic to subscribe to.
201
- * @param callback The callback to call when the event is emitted.
202
- * @returns An EventListener object containing an off() method to unsubscribe the listeners.
203
- */
204
- on: <T = EventPayload>(topic: string | string[], callback: EventHandler<T>): EventListener => {
205
- const topics = Array.isArray(topic) ? topic : [topic];
206
- return EventBus.on<T>(
207
- topics.map((t) => this.pluginController.getGlobalEventTopic(t)),
208
- callback,
209
- );
210
- },
211
- /**
212
- * Subscribe to an event once.
213
- * @param topic The topic to subscribe to.
214
- * @param callback The callback to call when the event is emitted.
215
- */
216
- once: <T = EventPayload>(topic: string, callback: EventHandler<T>) => {
217
- EventBus.once<T>(this.pluginController.getGlobalEventTopic(topic), callback);
218
- },
219
- /**
220
- * Respond to an event.
221
- * @param topic The topic to respond to.
222
- * @param data The data to respond with.
223
- */
224
- respond: <T = EventPayload>(
225
- topic: string | string[],
226
- data: EventPayload | ((data: EventBusMessage<T>) => EventPayload | Promise<EventPayload>),
227
- ) => {
228
- const topics = Array.isArray(topic) ? topic : [topic];
229
- EventBus.respond(
230
- this.plugin.pluginId,
231
- topics.map((t) => this.pluginController.getGlobalEventTopic(t)),
232
- data,
233
- );
234
- },
235
- /**
236
- * Emit an accomplishment.
237
- * @param payload The payload to emit.
238
- */
239
- emitAccomplishment: (payload: AccomplishmentPayload) => {
240
- this.accomplishmentHandler.emitAccomplishment(payload);
241
- },
242
- /**
243
- * Subscribe to an accomplishment.
244
- * @param accomplishmentTopic The topic to subscribe to.
245
- * @param callback The callback to call when the accomplishment is emitted.
246
- */
247
- onAccomplishment: (
248
- accomplishmentTopic: string,
249
- callback: (payload: EventBusMessage<AccomplishmentPayload>) => void,
250
- ) => {
251
- this.accomplishmentHandler.subscribe(accomplishmentTopic, callback);
252
- },
253
- /**
254
- * Trigger an action that opens the sidebar and triggers an action in the designated plugin.
255
- * @param pluginId The id of the plugin to trigger the action for.
256
- * @param actionKey The key of the action to trigger.
257
- * @param text Optional text to be used for the action like for example text that the translator would look up.
258
- */
259
- emitSidebarAction: (pluginId: string, actionKey: string, text?: string) => {
260
- this.event.emit('global.sidebar.triggerAction', { plugin_id: pluginId, action_key: actionKey, text });
261
- },
262
-
263
- /**
264
- * Subscribe to main panel actions triggered by the user from the dashboard.
265
- * @param callback Handler function that receives the action data when a matching action is triggered.
266
- * @param actionsToListen Optional filter to listen only to specific action keys. If empty or not provided, all actions will trigger the callback.
267
- * @returns An EventListener object with an `off()` method for cleanup.
268
- *
269
- * @example
270
- * ```ts
271
- * const listener = client.event.onMainPanelAction((data) => {
272
- * console.log('Action received:', data.action_key);
273
- * }, ['startSession', 'pauseSession']);
274
- *
275
- * // Clean up when component unmounts to prevent events from firing
276
- * // when navigating away or returning to the page
277
- * useEffect(() => {
278
- * return () => listener.off();
279
- * }, []);
280
- * ```
281
- *
282
- * **Important:** Always call `listener.off()` when your component unmounts or when you no longer need to listen.
283
- * This prevents the event handler from firing when navigating away from or returning to the page, which could
284
- * cause unexpected behavior or duplicate event handling.
285
- */
286
- onMainPanelAction: (
287
- callback: (data: MainPanelAction) => void,
288
- actionsToListen: string | string[] = [],
289
- ): EventListener => {
290
- const listeningActions = Array.isArray(actionsToListen) ? actionsToListen : [actionsToListen];
291
- // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
292
- this.event.emit('action.requestMain');
293
- return this.event.on<MainPanelAction>('action.requestMain', ({ data }) => {
294
- // console.log('Received action for main panel ' + data.action_key);
295
- // console.log('Listening to actions', listeningActions);
296
- if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
297
- callback(data);
298
- }
299
- });
300
- },
301
-
302
- onSidePanelAction: (
303
- callback: (data: MainPanelAction) => void,
304
- actionsToListen: string | string[] = [],
305
- ): EventListener => {
306
- const listeningActions = Array.isArray(actionsToListen) ? actionsToListen : [actionsToListen];
307
- // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
308
- this.event.emit('action.requestSidebar');
309
- return this.event.on<MainPanelAction>('action.requestSidebar', ({ data }) => {
310
- // console.log("eventHandler .onSidePanelAction", data);
311
- // console.log('Received action for sidebar ' + data.action);
312
- // console.log('Listening to actions', listeningActions);
313
- if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
314
- callback(data);
315
- }
316
- });
317
- },
318
- };
319
-
320
- public navigation = {
321
- toDashboard: (): void => {
322
- this.event.emit('global.navigation.triggerToDashboard');
323
- },
324
- };
325
-
326
- /**
327
- * Get a query parameter value that was passed via MessageChannel
328
- * @param key The query parameter key
329
- * @returns The query parameter value or null if not found
330
- */
331
- public getQueryParam(key: string): string | null {
332
- return this.pluginController.getQueryParam(key);
333
- }
334
-
335
- public static async getInstance(pluginId?: string): Promise<RimoriClient> {
336
- if (!RimoriClient.instance) {
337
- if (!pluginId) throw new Error('Plugin ID is required');
338
-
339
- const controller = new RimoriCommunicationHandler(pluginId, false);
340
-
341
- if (typeof WorkerGlobalScope === 'undefined') {
342
- // In standalone mode, use URL fallback. In iframe mode, theme will be set after MessageChannel init
343
- // setTheme();
344
- // await StandaloneClient.initListeners(pluginId);
345
- }
346
- const client = await controller.getClient();
347
- RimoriClient.instance = new RimoriClient(controller, client.supabase, client.info);
348
- }
349
- return RimoriClient.instance;
350
- }
351
-
352
- public ai = {
353
- getText: async (messages: Message[], tools?: Tool[]): Promise<string> => {
354
- const token = await this.pluginController.getToken();
355
- return generateText(this.pluginController.getBackendUrl(), messages, tools || [], token).then(
356
- ({ messages }) => messages[0].content[0].text,
357
- );
358
- },
359
- getSteamedText: async (messages: Message[], onMessage: OnLLMResponse, tools?: Tool[]) => {
360
- const token = await this.pluginController.getToken();
361
- streamChatGPT(this.pluginController.getBackendUrl(), messages, tools || [], onMessage, token);
362
- },
363
- getVoice: async (text: string, voice = 'alloy', speed = 1, language?: string): Promise<Blob> => {
364
- const token = await this.pluginController.getToken();
365
- return getTTSResponse(this.pluginController.getBackendUrl(), { input: text, voice, speed, language }, token);
366
- },
367
- getTextFromVoice: async (file: Blob): Promise<string> => {
368
- const token = await this.pluginController.getToken();
369
- return getSTTResponse(this.pluginController.getBackendUrl(), file, token);
370
- },
371
- getObject: async <T = any>(request: ObjectRequest): Promise<T> => {
372
- const token = await this.pluginController.getToken();
373
- return generateObject<T>(this.pluginController.getBackendUrl(), request, token);
374
- },
375
- // getSteamedObject: this.generateObjectStream,
376
- };
377
-
378
- public runtime = {
379
- fetchBackend: async (url: string, options: RequestInit) => {
380
- const token = await this.pluginController.getToken();
381
- return fetch(this.pluginController.getBackendUrl() + url, {
382
- ...options,
383
- headers: {
384
- ...options.headers,
385
- Authorization: `Bearer ${token}`,
386
- },
387
- });
388
- },
389
- };
390
-
391
- public community = {
392
- /**
393
- * Shared content is a way to share completable content with other users using this plugin.
394
- * Typical examples are assignments, exercises, stories, etc.
395
- * Users generate new shared content items and others can complete the content too.
396
- */
397
- sharedContent: {
398
- /**
399
- * Get one dedicated shared content item by id. It does not matter if it is completed or not.
400
- * @param contentType The type of shared content to get. E.g. assignments, exercises, etc.
401
- * @param id The id of the shared content item.
402
- * @returns The shared content item.
403
- */
404
- get: async <T = any>(contentType: string, id: string): Promise<SharedContent<T>> => {
405
- return await this.sharedContentController.getSharedContent(contentType, id);
406
- },
407
- /**
408
- * Get a list of shared content items.
409
- * @param contentType The type of shared content to get. E.g. assignments, exercises, etc.
410
- * @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.
411
- * @param limit The optional limit for the number of results.
412
- * @returns The list of shared content items.
413
- */
414
- getList: async <T = any>(
415
- contentType: string,
416
- filter?: SharedContentFilter,
417
- limit?: number,
418
- ): Promise<SharedContent<T>[]> => {
419
- return await this.sharedContentController.getSharedContentList(contentType, filter, limit);
420
- },
421
- /**
422
- * Get new shared content.
423
- * @param contentType The type of shared content to fetch. E.g. assignments, exercises, etc.
424
- * @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.
425
- * @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.
426
- * @param options An optional object with options for the new shared content.
427
- * @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.
428
- * @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.
429
- * @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.
430
- * @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.
431
- * @returns The new shared content.
432
- */
433
- getNew: async <T = any>(
434
- contentType: string,
435
- generatorInstructions: SharedContentObjectRequest,
436
- filter?: SharedContentFilter,
437
- options?: { privateTopic?: boolean; skipDbSave?: boolean; alwaysGenerateNew?: boolean; excludeIds?: string[] },
438
- ): Promise<SharedContent<T>> => {
439
- return await this.sharedContentController.getNewSharedContent(
440
- contentType,
441
- generatorInstructions,
442
- filter,
443
- options,
444
- );
445
- },
446
- /**
447
- * Create a new shared content item.
448
- * @param content The content to create.
449
- * @returns The new shared content item.
450
- */
451
- create: async <T = any>(content: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>> => {
452
- return await this.sharedContentController.createSharedContent(content);
453
- },
454
- /**
455
- * Update a shared content item.
456
- * @param id The id of the shared content item to update.
457
- * @param content The content to update.
458
- * @returns The updated shared content item.
459
- */
460
- update: async <T = any>(id: string, content: Partial<SharedContent<T>>): Promise<SharedContent<T>> => {
461
- return await this.sharedContentController.updateSharedContent(id, content);
462
- },
463
- /**
464
- * Complete a shared content item.
465
- * @param contentType The type of shared content to complete. E.g. assignments, exercises, etc.
466
- * @param assignmentId The id of the shared content item to complete.
467
- */
468
- complete: async (contentType: string, assignmentId: string) => {
469
- return await this.sharedContentController.completeSharedContent(contentType, assignmentId);
470
- },
471
- /**
472
- /**
473
- * Update the state of a shared content item for a specific user.
474
- * Useful for marking content as completed, ongoing, hidden, liked, disliked, or bookmarked.
475
- */
476
- updateState: async (params: {
477
- contentType: string;
478
- id: string;
479
- state?: 'completed' | 'ongoing' | 'hidden';
480
- reaction?: 'liked' | 'disliked' | null;
481
- bookmarked?: boolean;
482
- }): Promise<void> => {
483
- return await this.sharedContentController.updateSharedContentState(params);
484
- },
485
- /**
486
- * Remove a shared content item.
487
- * @param id The id of the shared content item to remove.
488
- * @returns The removed shared content item.
489
- */
490
- remove: async (id: string): Promise<SharedContent<any>> => {
491
- return await this.sharedContentController.removeSharedContent(id);
492
- },
493
- },
494
- };
495
-
496
- public exercise = {
497
- /**
498
- * Fetches weekly exercises from the weekly_exercises view.
499
- * Shows exercises for the current week that haven't expired.
500
- * @returns Array of exercise objects.
501
- */
502
- view: async () => {
503
- return this.exerciseController.viewWeeklyExercises();
504
- },
505
-
506
- /**
507
- * Creates a new exercise or multiple exercises via the backend API.
508
- * When creating multiple exercises, all requests are made in parallel but only one event is emitted.
509
- * @param params Exercise creation parameters (single or array).
510
- * @returns Created exercise objects.
511
- */
512
- add: async (params: CreateExerciseParams | CreateExerciseParams[]) => {
513
- const token = await this.pluginController.getToken();
514
- const backendUrl = this.pluginController.getBackendUrl();
515
- const exercises = Array.isArray(params) ? params : [params];
516
- return this.exerciseController.addExercise(token, backendUrl, exercises);
517
- },
518
-
519
- /**
520
- * Deletes an exercise via the backend API.
521
- * @param id The exercise ID to delete.
522
- * @returns Success status.
523
- */
524
- delete: async (id: string) => {
525
- const token = await this.pluginController.getToken();
526
- const backendUrl = this.pluginController.getBackendUrl();
527
- return this.exerciseController.deleteExercise(token, backendUrl, id);
528
- },
529
- };
530
- }
@@ -1,125 +0,0 @@
1
- import { createClient, SupabaseClient } from '@supabase/supabase-js';
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;
15
-
16
- private constructor(config: StandaloneConfig) {
17
- this.supabase = createClient(config.url, config.key);
18
- this.config = config;
19
- }
20
-
21
- public static async getInstance(): Promise<StandaloneClient> {
22
- if (!StandaloneClient.instance) {
23
- const config = await fetch('https://app.rimori.se/config.json')
24
- .then((res) => res.json())
25
- .catch((err) => {
26
- console.warn('Error fetching config.json, using default values', err);
27
- });
28
- StandaloneClient.instance = new StandaloneClient({
29
- url: config?.SUPABASE_URL || DEFAULT_ENDPOINT,
30
- key: config?.SUPABASE_ANON_KEY || DEFAULT_ANON_KEY,
31
- backendUrl: config?.BACKEND_URL || 'https://api.rimori.se',
32
- });
33
- }
34
- return StandaloneClient.instance;
35
- }
36
-
37
- public async getClient(): Promise<SupabaseClient> {
38
- return this.supabase;
39
- }
40
-
41
- public async needsLogin(): Promise<boolean> {
42
- const { error } = await this.supabase.auth.getUser();
43
- return error !== null;
44
- }
45
-
46
- public async login(email: string, password: string) {
47
- const { error } = await this.supabase.auth.signInWithPassword({ email, password });
48
- if (error) {
49
- console.error('Login failed:', error);
50
- return false;
51
- }
52
- console.log('Successfully logged in');
53
- return true;
54
- }
55
-
56
- public static async initListeners(pluginId: string) {
57
- console.warn(
58
- 'The plugin seams to not be running inside the Rimori platform. Switching to development standalone mode.',
59
- );
60
- // console.log("event that needs to be handled", event);
61
- const { supabase, config } = await StandaloneClient.getInstance();
62
-
63
- // EventBus.on("*", async (event) => {
64
- EventBus.respond('standalone', 'global.supabase.requestAccess', async () => {
65
- const session = await supabase.auth.getSession();
66
- console.log('session', session);
67
-
68
- // Call the NestJS backend endpoint instead of the Supabase edge function
69
- // get current guild id if any
70
- let guildId: string | null = null;
71
- try {
72
- const {
73
- data: { user },
74
- } = await supabase.auth.getUser();
75
- if (user) {
76
- const { data: profile } = await supabase
77
- .from('profiles')
78
- .select('current_guild_id')
79
- .eq('user_id', user.id)
80
- .maybeSingle();
81
- guildId = (profile as { current_guild_id?: string | null } | null)?.current_guild_id || null;
82
- }
83
- } catch (_) {
84
- guildId = null;
85
- }
86
-
87
- const response = await fetch(`${config.backendUrl}/plugin/token`, {
88
- method: 'POST',
89
- headers: {
90
- 'Content-Type': 'application/json',
91
- Authorization: `Bearer ${session.data.session?.access_token}`,
92
- },
93
- body: JSON.stringify({
94
- pluginId: pluginId,
95
- guildId: guildId,
96
- }),
97
- });
98
-
99
- if (!response.ok) {
100
- const errorText = await response.text();
101
- throw new Error(`Failed to get plugin token. ${response.status}: ${errorText}`);
102
- }
103
-
104
- const data = await response.json();
105
-
106
- return {
107
- token: data.token,
108
- pluginId: pluginId,
109
- url: config.url,
110
- key: config.key,
111
- backendUrl: config.backendUrl,
112
- tablePrefix: pluginId,
113
- expiration: new Date(Date.now() + 1000 * 60 * 60 * 1.5), // 1.5 hours
114
- };
115
- });
116
-
117
- EventBus.on(
118
- '*',
119
- async (event) => {
120
- console.log('[standalone] would send event to parent', event);
121
- },
122
- ['standalone'],
123
- );
124
- }
125
- }
@@ -1,15 +0,0 @@
1
- const codes = ['Pre-A1', 'A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'Post-C2'];
2
-
3
- export type LanguageLevel = 'Pre-A1' | 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2' | 'Post-C2';
4
-
5
- export function getDifficultyLevel(difficulty: LanguageLevel): number {
6
- return codes.indexOf(difficulty) + 1;
7
- }
8
-
9
- export function getDifficultyLabel(difficulty: number): LanguageLevel {
10
- return codes[difficulty] as LanguageLevel;
11
- }
12
-
13
- export function getNeighborDifficultyLevel(difficulty: LanguageLevel, difficultyAdjustment: number): LanguageLevel {
14
- return getDifficultyLabel(getDifficultyLevel(difficulty) + difficultyAdjustment - 1);
15
- }
@@ -1,3 +0,0 @@
1
- export const DEFAULT_ENDPOINT = 'https://pheptqdoqsdnadgoihvr.supabase.co';
2
- export const DEFAULT_ANON_KEY =
3
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBoZXB0cWRvcXNkbmFkZ29paHZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzE2OTY2ODcsImV4cCI6MjA0NzI3MjY4N30.4GPFAXTF8685FaXISdAPNCIM-H3RGLo8GbyhQpu1mP0';