@rimori/client 2.2.0 → 2.3.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.
- package/.github/workflows/pre-release.yml +126 -0
- package/README.md +9 -13
- package/dist/cli/scripts/init/main.js +0 -0
- package/dist/cli/scripts/release/release.js +0 -0
- package/dist/controller/ExerciseController.d.ts +3 -1
- package/dist/controller/ExerciseController.js +4 -1
- package/dist/controller/SettingsController.d.ts +6 -2
- package/dist/controller/TranslationController.d.ts +4 -2
- package/dist/controller/TranslationController.js +40 -18
- package/dist/fromRimori/EventBus.js +49 -29
- package/dist/fromRimori/PluginTypes.d.ts +6 -6
- package/dist/index.d.ts +2 -1
- package/dist/plugin/CommunicationHandler.d.ts +11 -0
- package/dist/plugin/CommunicationHandler.js +9 -7
- package/dist/plugin/Logger.d.ts +1 -0
- package/dist/plugin/Logger.js +15 -3
- package/dist/plugin/RimoriClient.d.ts +12 -0
- package/dist/plugin/RimoriClient.js +32 -9
- package/example/worker/vite.config.ts +3 -0
- package/package.json +7 -1
- package/src/controller/ExerciseController.ts +6 -1
- package/src/controller/SettingsController.ts +7 -2
- package/src/controller/TranslationController.ts +51 -22
- package/src/fromRimori/EventBus.ts +105 -53
- package/src/fromRimori/PluginTypes.ts +28 -19
- package/src/index.ts +2 -1
- package/src/plugin/CommunicationHandler.ts +20 -7
- package/src/plugin/Logger.ts +15 -3
- package/src/plugin/RimoriClient.ts +32 -9
|
@@ -26,15 +26,17 @@ export class RimoriCommunicationHandler {
|
|
|
26
26
|
}
|
|
27
27
|
initMessageChannel(worker = false) {
|
|
28
28
|
const listener = (event) => {
|
|
29
|
-
console.log('[PluginController] window message', { origin: event.origin, data: event.data });
|
|
29
|
+
// console.log('[PluginController] window message', { origin: event.origin, data: event.data });
|
|
30
30
|
const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
|
|
31
31
|
const [transferredPort] = event.ports || [];
|
|
32
32
|
if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
|
|
33
|
-
console.log('[PluginController] message ignored (not init or wrong plugin)', {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// console.log('[PluginController] message ignored (not init or wrong plugin)', {
|
|
34
|
+
// type,
|
|
35
|
+
// pluginId,
|
|
36
|
+
// currentPluginId: this.pluginId,
|
|
37
|
+
// hasPortProperty: !!transferredPort,
|
|
38
|
+
// event
|
|
39
|
+
// });
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
this.queryParams = queryParams || {};
|
|
@@ -182,7 +184,7 @@ export class RimoriCommunicationHandler {
|
|
|
182
184
|
else {
|
|
183
185
|
// In main thread context, use EventBus
|
|
184
186
|
const { data } = yield EventBus.request(this.pluginId, 'global.supabase.requestAccess');
|
|
185
|
-
console.log({ data });
|
|
187
|
+
// console.log({ data });
|
|
186
188
|
this.rimoriInfo = data;
|
|
187
189
|
this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
|
|
188
190
|
accessToken: () => Promise.resolve(this.getToken()),
|
package/dist/plugin/Logger.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ export declare class Logger {
|
|
|
54
54
|
private getBrowserInfo;
|
|
55
55
|
/**
|
|
56
56
|
* Capture a screenshot of the current page.
|
|
57
|
+
* Dynamically imports html2canvas only in browser environments.
|
|
57
58
|
* @returns Promise resolving to base64 screenshot or null if failed
|
|
58
59
|
*/
|
|
59
60
|
private captureScreenshot;
|
package/dist/plugin/Logger.js
CHANGED
|
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import html2canvas from 'html2canvas';
|
|
11
10
|
/**
|
|
12
11
|
* Singleton Logger class for Rimori client plugins.
|
|
13
12
|
* Handles all logging levels, production filtering, and log transmission to Rimori.
|
|
@@ -242,17 +241,30 @@ export class Logger {
|
|
|
242
241
|
}
|
|
243
242
|
/**
|
|
244
243
|
* Capture a screenshot of the current page.
|
|
244
|
+
* Dynamically imports html2canvas only in browser environments.
|
|
245
245
|
* @returns Promise resolving to base64 screenshot or null if failed
|
|
246
246
|
*/
|
|
247
247
|
captureScreenshot() {
|
|
248
248
|
return __awaiter(this, void 0, void 0, function* () {
|
|
249
|
-
|
|
249
|
+
// Only attempt to capture screenshot in browser environments
|
|
250
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
// Dynamically import html2canvas only when window is available
|
|
255
|
+
// html2canvas is an optional peer dependency - provided by @rimori/react-client
|
|
256
|
+
// In worker builds, this import should be marked as external to prevent bundling
|
|
257
|
+
const html2canvas = (yield import('html2canvas')).default;
|
|
250
258
|
const canvas = yield html2canvas(document.body);
|
|
251
259
|
const screenshot = canvas.toDataURL('image/png');
|
|
252
260
|
// this.originalConsole.log("screenshot captured", screenshot)
|
|
253
261
|
return screenshot;
|
|
254
262
|
}
|
|
255
|
-
|
|
263
|
+
catch (error) {
|
|
264
|
+
// html2canvas may not be available (e.g., in workers or when not installed)
|
|
265
|
+
// Silently fail to avoid breaking logging functionality
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
256
268
|
});
|
|
257
269
|
}
|
|
258
270
|
/**
|
|
@@ -22,6 +22,11 @@ export declare class RimoriClient {
|
|
|
22
22
|
private constructor();
|
|
23
23
|
get plugin(): {
|
|
24
24
|
pluginId: string;
|
|
25
|
+
/**
|
|
26
|
+
* The release channel of this plugin installation.
|
|
27
|
+
* Determines which database schema is used for plugin tables.
|
|
28
|
+
*/
|
|
29
|
+
releaseChannel: "alpha" | "beta" | "stable";
|
|
25
30
|
/**
|
|
26
31
|
* Set the settings for the plugin.
|
|
27
32
|
* @param settings The settings to set.
|
|
@@ -67,6 +72,13 @@ export declare class RimoriClient {
|
|
|
67
72
|
* The table prefix for of database tables of the plugin.
|
|
68
73
|
*/
|
|
69
74
|
tablePrefix: string;
|
|
75
|
+
/**
|
|
76
|
+
* The database schema used for plugin tables.
|
|
77
|
+
* Determined by rimori-main based on release channel:
|
|
78
|
+
* - 'plugins_alpha' for alpha release channel
|
|
79
|
+
* - 'plugins' for beta and stable release channels
|
|
80
|
+
*/
|
|
81
|
+
schema: "plugins" | "plugins_alpha";
|
|
70
82
|
/**
|
|
71
83
|
* Get the table name for a given plugin table.
|
|
72
84
|
* Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
|
|
@@ -101,9 +101,9 @@ export class RimoriClient {
|
|
|
101
101
|
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
102
102
|
this.event.emit('action.requestMain');
|
|
103
103
|
this.event.on('action.requestMain', ({ data }) => {
|
|
104
|
-
console.log('Received action ' + data.
|
|
105
|
-
console.log('Listening to actions', listeningActions);
|
|
106
|
-
if (
|
|
104
|
+
// console.log('Received action for main panel ' + data.action_key);
|
|
105
|
+
// console.log('Listening to actions', listeningActions);
|
|
106
|
+
if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
|
|
107
107
|
callback(data);
|
|
108
108
|
}
|
|
109
109
|
});
|
|
@@ -113,9 +113,10 @@ export class RimoriClient {
|
|
|
113
113
|
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
114
114
|
this.event.emit('action.requestSidebar');
|
|
115
115
|
this.event.on('action.requestSidebar', ({ data }) => {
|
|
116
|
-
console.log(
|
|
117
|
-
console.log('
|
|
118
|
-
|
|
116
|
+
// console.log("eventHandler .onSidePanelAction", data);
|
|
117
|
+
// console.log('Received action for sidebar ' + data.action);
|
|
118
|
+
// console.log('Listening to actions', listeningActions);
|
|
119
|
+
if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
|
|
119
120
|
callback(data);
|
|
120
121
|
}
|
|
121
122
|
});
|
|
@@ -272,11 +273,12 @@ export class RimoriClient {
|
|
|
272
273
|
this.rimoriInfo = info;
|
|
273
274
|
this.superbase = supabase;
|
|
274
275
|
this.pluginController = controller;
|
|
275
|
-
this.exerciseController = new ExerciseController(supabase);
|
|
276
|
+
this.exerciseController = new ExerciseController(supabase, this);
|
|
276
277
|
this.accomplishmentHandler = new AccomplishmentController(info.pluginId);
|
|
277
278
|
this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
|
|
278
279
|
this.sharedContentController = new SharedContentController(supabase, this);
|
|
279
|
-
|
|
280
|
+
const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
|
|
281
|
+
this.translator = new Translator(info.interfaceLanguage, (currentPlugin === null || currentPlugin === void 0 ? void 0 : currentPlugin.endpoint) || '');
|
|
280
282
|
//only init logger in workers and on main plugin pages
|
|
281
283
|
if (this.getQueryParam('applicationMode') !== 'sidebar') {
|
|
282
284
|
Logger.getInstance(this);
|
|
@@ -285,6 +287,11 @@ export class RimoriClient {
|
|
|
285
287
|
get plugin() {
|
|
286
288
|
return {
|
|
287
289
|
pluginId: this.rimoriInfo.pluginId,
|
|
290
|
+
/**
|
|
291
|
+
* The release channel of this plugin installation.
|
|
292
|
+
* Determines which database schema is used for plugin tables.
|
|
293
|
+
*/
|
|
294
|
+
releaseChannel: this.rimoriInfo.releaseChannel,
|
|
288
295
|
/**
|
|
289
296
|
* Set the settings for the plugin.
|
|
290
297
|
* @param settings The settings to set.
|
|
@@ -337,7 +344,16 @@ export class RimoriClient {
|
|
|
337
344
|
// relation: ViewName,
|
|
338
345
|
// ): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
|
|
339
346
|
from: (relation) => {
|
|
340
|
-
|
|
347
|
+
const tableName = this.db.getTableName(relation);
|
|
348
|
+
// Use the schema determined by rimori-main based on release channel
|
|
349
|
+
// Global tables (starting with 'global_') remain in public schema
|
|
350
|
+
// Plugin tables use the schema provided by rimori-main (plugins or plugins_alpha)
|
|
351
|
+
if (relation.startsWith('global_')) {
|
|
352
|
+
// Global tables stay in public schema
|
|
353
|
+
return this.superbase.from(tableName);
|
|
354
|
+
}
|
|
355
|
+
// Plugin tables go to the schema provided by rimori-main
|
|
356
|
+
return this.superbase.schema(this.rimoriInfo.dbSchema).from(tableName);
|
|
341
357
|
},
|
|
342
358
|
// storage: this.superbase.storage,
|
|
343
359
|
// functions: this.superbase.functions,
|
|
@@ -345,6 +361,13 @@ export class RimoriClient {
|
|
|
345
361
|
* The table prefix for of database tables of the plugin.
|
|
346
362
|
*/
|
|
347
363
|
tablePrefix: this.rimoriInfo.tablePrefix,
|
|
364
|
+
/**
|
|
365
|
+
* The database schema used for plugin tables.
|
|
366
|
+
* Determined by rimori-main based on release channel:
|
|
367
|
+
* - 'plugins_alpha' for alpha release channel
|
|
368
|
+
* - 'plugins' for beta and stable release channels
|
|
369
|
+
*/
|
|
370
|
+
schema: this.rimoriInfo.dbSchema,
|
|
348
371
|
/**
|
|
349
372
|
* Get the table name for a given plugin table.
|
|
350
373
|
* Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
|
|
@@ -14,6 +14,9 @@ export default defineConfig({
|
|
|
14
14
|
outDir: path.resolve(__dirname, '../public'),
|
|
15
15
|
emptyOutDir: false,
|
|
16
16
|
rollupOptions: {
|
|
17
|
+
// Exclude DOM-only libraries that can't run in workers
|
|
18
|
+
// html2canvas is provided by @rimori/react-client for browser contexts
|
|
19
|
+
external: ['html2canvas'],
|
|
17
20
|
output: {
|
|
18
21
|
inlineDynamicImports: true,
|
|
19
22
|
entryFileNames: 'web-worker.js',
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0-next.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/rimori-org/rimori-client.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "apache-2.0",
|
|
6
11
|
"bin": {
|
|
7
12
|
"rimori-release": "./dist/cli/scripts/release/release.js",
|
|
8
13
|
"rimori-init": "./dist/cli/scripts/init/main.js"
|
|
@@ -32,6 +37,7 @@
|
|
|
32
37
|
"eslint-plugin-prettier": "^5.5.4",
|
|
33
38
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
34
39
|
"eslint-plugin-react-refresh": "^0.4.23",
|
|
40
|
+
"html2canvas": "^1.4.1",
|
|
35
41
|
"globals": "^16.4.0",
|
|
36
42
|
"prettier": "^3.6.2",
|
|
37
43
|
"typescript": "^5.7.2",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { RimoriClient } from '../plugin/RimoriClient';
|
|
2
3
|
|
|
3
4
|
export type TriggerAction = { action_key: string } & Record<string, string | number | boolean>;
|
|
4
5
|
|
|
@@ -27,9 +28,11 @@ export interface Exercise {
|
|
|
27
28
|
|
|
28
29
|
export class ExerciseController {
|
|
29
30
|
private supabase: SupabaseClient;
|
|
31
|
+
private rimoriClient: RimoriClient;
|
|
30
32
|
|
|
31
|
-
constructor(supabase: SupabaseClient) {
|
|
33
|
+
constructor(supabase: SupabaseClient, rimoriClient: RimoriClient) {
|
|
32
34
|
this.supabase = supabase;
|
|
35
|
+
this.rimoriClient = rimoriClient;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -68,6 +71,7 @@ export class ExerciseController {
|
|
|
68
71
|
const errorText = await response.text();
|
|
69
72
|
throw new Error(`Failed to create exercise: ${errorText}`);
|
|
70
73
|
}
|
|
74
|
+
this.rimoriClient.event.emit('global.exercises.triggerChange');
|
|
71
75
|
|
|
72
76
|
return await response.json();
|
|
73
77
|
}
|
|
@@ -95,6 +99,7 @@ export class ExerciseController {
|
|
|
95
99
|
const errorText = await response.text();
|
|
96
100
|
throw new Error(`Failed to delete exercise: ${errorText}`);
|
|
97
101
|
}
|
|
102
|
+
this.rimoriClient.event.emit('global.exercises.triggerChange');
|
|
98
103
|
|
|
99
104
|
return await response.json();
|
|
100
105
|
}
|
|
@@ -19,6 +19,8 @@ export interface Language {
|
|
|
19
19
|
uppercase: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export type UserRole = 'user' | 'plugin_moderator' | 'lang_moderator' | 'admin';
|
|
23
|
+
|
|
22
24
|
export interface UserInfo {
|
|
23
25
|
skill_level_reading: LanguageLevel;
|
|
24
26
|
skill_level_writing: LanguageLevel;
|
|
@@ -32,8 +34,7 @@ export interface UserInfo {
|
|
|
32
34
|
story_genre: string;
|
|
33
35
|
study_duration: number;
|
|
34
36
|
/**
|
|
35
|
-
* The
|
|
36
|
-
* With the function getLanguageName, the language name can be retrieved.
|
|
37
|
+
* The language the user speaks natively.
|
|
37
38
|
*/
|
|
38
39
|
mother_tongue: Language;
|
|
39
40
|
/**
|
|
@@ -52,6 +53,10 @@ export interface UserInfo {
|
|
|
52
53
|
* Optional: nearest big city (>100,000) near user's location
|
|
53
54
|
*/
|
|
54
55
|
target_city?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The user's role: 'user', 'plugin_moderator', 'lang_moderator', or 'admin'
|
|
58
|
+
*/
|
|
59
|
+
user_role: UserRole;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
export class SettingsController {
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { createInstance, ThirdPartyModule, TOptions, i18n as i18nType } from 'i18next';
|
|
2
2
|
|
|
3
|
+
type InitializationState = 'not-inited' | 'initing' | 'finished';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Translator class for handling internationalization
|
|
5
7
|
*/
|
|
6
8
|
export class Translator {
|
|
7
9
|
private currentLanguage: string;
|
|
8
|
-
private
|
|
10
|
+
private initializationState: InitializationState;
|
|
11
|
+
private initializationPromise: Promise<void> | null;
|
|
9
12
|
private i18n: i18nType | undefined;
|
|
13
|
+
private translationUrl: string;
|
|
10
14
|
|
|
11
|
-
constructor(initialLanguage: string) {
|
|
12
|
-
this.isInitialized = false;
|
|
15
|
+
constructor(initialLanguage: string, translationUrl: string) {
|
|
13
16
|
this.currentLanguage = initialLanguage;
|
|
17
|
+
this.initializationState = 'not-inited';
|
|
18
|
+
this.initializationPromise = null;
|
|
19
|
+
this.translationUrl = translationUrl;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -18,23 +24,46 @@ export class Translator {
|
|
|
18
24
|
* @param userLanguage - Language code from user info
|
|
19
25
|
*/
|
|
20
26
|
async initialize(): Promise<void> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
// If already finished, return immediately
|
|
28
|
+
if (this.initializationState === 'finished') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If currently initializing, wait for the existing initialization to complete
|
|
33
|
+
if (this.initializationState === 'initing' && this.initializationPromise) {
|
|
34
|
+
return this.initializationPromise;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Start initialization
|
|
38
|
+
this.initializationState = 'initing';
|
|
39
|
+
|
|
40
|
+
// Create a promise that will be resolved when initialization completes
|
|
41
|
+
this.initializationPromise = (async (): Promise<void> => {
|
|
42
|
+
try {
|
|
43
|
+
const translations = await this.fetchTranslations(this.currentLanguage);
|
|
44
|
+
|
|
45
|
+
const instance = createInstance({
|
|
46
|
+
lng: this.currentLanguage,
|
|
47
|
+
resources: {
|
|
48
|
+
[this.currentLanguage]: {
|
|
49
|
+
translation: translations,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
debug: window.location.hostname === 'localhost',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await instance.init();
|
|
56
|
+
this.i18n = instance;
|
|
57
|
+
this.initializationState = 'finished';
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Reset state on error so it can be retried
|
|
60
|
+
this.initializationState = 'not-inited';
|
|
61
|
+
this.initializationPromise = null;
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
return this.initializationPromise;
|
|
38
67
|
}
|
|
39
68
|
|
|
40
69
|
private getTranslationUrl(language: string): string {
|
|
@@ -45,7 +74,7 @@ export class Translator {
|
|
|
45
74
|
return `${window.location.origin}/locales/${filename}.json`;
|
|
46
75
|
}
|
|
47
76
|
|
|
48
|
-
return
|
|
77
|
+
return `${this.translationUrl}/locales/${language}.json`;
|
|
49
78
|
}
|
|
50
79
|
|
|
51
80
|
public usePlugin(plugin: ThirdPartyModule): void {
|
|
@@ -102,6 +131,6 @@ export class Translator {
|
|
|
102
131
|
* Check if translator is initialized
|
|
103
132
|
*/
|
|
104
133
|
isReady(): boolean {
|
|
105
|
-
return this.
|
|
134
|
+
return this.initializationState === 'finished';
|
|
106
135
|
}
|
|
107
136
|
}
|