@rimori/client 1.1.10 → 1.3.0

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 (171) hide show
  1. package/README.md +189 -63
  2. package/dist/cli/scripts/init/dev-registration.d.ts +35 -0
  3. package/dist/cli/scripts/init/dev-registration.js +174 -0
  4. package/dist/cli/scripts/init/env-setup.d.ts +9 -0
  5. package/dist/cli/scripts/init/env-setup.js +43 -0
  6. package/dist/cli/scripts/init/file-operations.d.ts +4 -0
  7. package/dist/cli/scripts/init/file-operations.js +51 -0
  8. package/dist/cli/scripts/init/html-cleaner.d.ts +4 -0
  9. package/dist/cli/scripts/init/html-cleaner.js +38 -0
  10. package/dist/cli/scripts/init/main.d.ts +2 -0
  11. package/dist/cli/scripts/init/main.js +160 -0
  12. package/dist/cli/scripts/init/package-setup.d.ts +32 -0
  13. package/dist/cli/scripts/init/package-setup.js +75 -0
  14. package/dist/cli/scripts/init/router-transformer.d.ts +6 -0
  15. package/dist/cli/scripts/init/router-transformer.js +254 -0
  16. package/dist/cli/scripts/init/tailwind-config.d.ts +4 -0
  17. package/dist/cli/scripts/init/tailwind-config.js +56 -0
  18. package/dist/cli/scripts/init/vite-config.d.ts +20 -0
  19. package/dist/cli/scripts/init/vite-config.js +54 -0
  20. package/dist/cli/scripts/release/release-config-upload.d.ts +7 -0
  21. package/dist/cli/scripts/release/release-config-upload.js +116 -0
  22. package/dist/cli/scripts/release/release-db-update.d.ts +6 -0
  23. package/dist/cli/scripts/release/release-db-update.js +100 -0
  24. package/dist/cli/scripts/release/release-file-upload.d.ts +6 -0
  25. package/dist/cli/scripts/release/release-file-upload.js +136 -0
  26. package/dist/cli/scripts/release/release.d.ts +23 -0
  27. package/dist/cli/scripts/release/release.js +70 -0
  28. package/dist/cli/types/DatabaseTypes.d.ts +103 -0
  29. package/dist/cli/types/DatabaseTypes.js +2 -0
  30. package/dist/components/LoggerExample.d.ts +6 -0
  31. package/dist/components/LoggerExample.js +79 -0
  32. package/dist/components/ai/Assistant.js +5 -5
  33. package/dist/components/ai/Avatar.d.ts +3 -2
  34. package/dist/components/ai/Avatar.js +11 -6
  35. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
  36. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +1 -0
  37. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +48 -33
  38. package/dist/components/ai/utils.js +0 -1
  39. package/dist/components/audio/Playbutton.js +4 -4
  40. package/dist/{core → components}/components/ContextMenu.js +50 -11
  41. package/dist/components.d.ts +5 -5
  42. package/dist/components.js +5 -5
  43. package/dist/core/controller/AIController.d.ts +15 -0
  44. package/dist/core/controller/AIController.js +253 -0
  45. package/dist/core/controller/AudioController.d.ts +0 -0
  46. package/dist/core/controller/AudioController.js +1 -0
  47. package/dist/{controller → core/controller}/ObjectController.d.ts +10 -2
  48. package/dist/{controller → core/controller}/ObjectController.js +8 -8
  49. package/dist/{controller → core/controller}/SettingsController.d.ts +28 -4
  50. package/dist/{controller → core/controller}/SettingsController.js +0 -25
  51. package/dist/{controller → core/controller}/SharedContentController.d.ts +31 -3
  52. package/dist/{controller → core/controller}/SharedContentController.js +77 -26
  53. package/dist/core/controller/VoiceController.d.ts +9 -0
  54. package/dist/{controller → core/controller}/VoiceController.js +11 -4
  55. package/dist/core/core.d.ts +14 -0
  56. package/dist/core/core.js +8 -0
  57. package/dist/{plugin/fromRimori → fromRimori}/EventBus.d.ts +3 -3
  58. package/dist/{plugin/fromRimori → fromRimori}/EventBus.js +26 -9
  59. package/dist/fromRimori/PluginTypes.d.ts +174 -0
  60. package/dist/hooks/UseChatHook.d.ts +2 -1
  61. package/dist/hooks/UseChatHook.js +6 -4
  62. package/dist/hooks/UseLogger.d.ts +30 -0
  63. package/dist/hooks/UseLogger.js +122 -0
  64. package/dist/index.d.ts +6 -3
  65. package/dist/index.js +5 -3
  66. package/dist/plugin/AccomplishmentHandler.d.ts +1 -1
  67. package/dist/plugin/AccomplishmentHandler.js +1 -1
  68. package/dist/plugin/AudioController.d.ts +37 -0
  69. package/dist/plugin/AudioController.js +68 -0
  70. package/dist/plugin/Logger.d.ts +68 -0
  71. package/dist/plugin/Logger.js +256 -0
  72. package/dist/plugin/LoggerExample.d.ts +16 -0
  73. package/dist/plugin/LoggerExample.js +140 -0
  74. package/dist/plugin/PluginController.d.ts +30 -5
  75. package/dist/plugin/PluginController.js +182 -53
  76. package/dist/plugin/RimoriClient.d.ts +68 -21
  77. package/dist/plugin/RimoriClient.js +88 -41
  78. package/dist/plugin/StandaloneClient.d.ts +1 -0
  79. package/dist/plugin/StandaloneClient.js +24 -10
  80. package/dist/plugin/ThemeSetter.d.ts +2 -1
  81. package/dist/plugin/ThemeSetter.js +13 -7
  82. package/dist/providers/PluginProvider.d.ts +4 -1
  83. package/dist/providers/PluginProvider.js +39 -13
  84. package/dist/utils/Language.d.ts +2 -1
  85. package/dist/utils/Language.js +4 -2
  86. package/dist/utils/audioFormats.d.ts +26 -0
  87. package/dist/utils/audioFormats.js +67 -0
  88. package/dist/utils/difficultyConverter.js +1 -1
  89. package/dist/utils/endpoint.d.ts +2 -0
  90. package/dist/utils/endpoint.js +2 -0
  91. package/dist/worker/WorkerSetup.d.ts +3 -2
  92. package/dist/worker/WorkerSetup.js +22 -65
  93. package/example/docs/devdocs.md +231 -0
  94. package/example/docs/overview.md +29 -0
  95. package/example/docs/userdocs.md +123 -0
  96. package/example/rimori.config.ts +89 -0
  97. package/example/worker/vite.config.ts +23 -0
  98. package/example/worker/worker.ts +11 -0
  99. package/package.json +16 -9
  100. package/src/cli/scripts/init/dev-registration.ts +192 -0
  101. package/src/cli/scripts/init/env-setup.ts +44 -0
  102. package/src/cli/scripts/init/file-operations.ts +58 -0
  103. package/src/cli/scripts/init/html-cleaner.ts +48 -0
  104. package/src/cli/scripts/init/main.ts +172 -0
  105. package/src/cli/scripts/init/package-setup.ts +117 -0
  106. package/src/cli/scripts/init/router-transformer.ts +329 -0
  107. package/src/cli/scripts/init/tailwind-config.ts +75 -0
  108. package/src/cli/scripts/init/vite-config.ts +73 -0
  109. package/src/cli/scripts/release/release-config-upload.ts +114 -0
  110. package/src/cli/scripts/release/release-db-update.ts +97 -0
  111. package/src/cli/scripts/release/release-file-upload.ts +138 -0
  112. package/src/cli/scripts/release/release.ts +69 -0
  113. package/src/cli/types/DatabaseTypes.ts +117 -0
  114. package/src/components/ai/Assistant.tsx +5 -5
  115. package/src/components/ai/Avatar.tsx +25 -8
  116. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +1 -1
  117. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +50 -35
  118. package/src/components/ai/utils.ts +0 -2
  119. package/src/components/audio/Playbutton.tsx +4 -4
  120. package/src/{core → components}/components/ContextMenu.tsx +56 -12
  121. package/src/components.ts +6 -6
  122. package/src/core/controller/AIController.ts +283 -0
  123. package/src/core/controller/ObjectController.ts +115 -0
  124. package/src/{controller → core/controller}/SettingsController.ts +29 -29
  125. package/src/{controller → core/controller}/SharedContentController.ts +91 -29
  126. package/src/core/controller/VoiceController.ts +31 -0
  127. package/src/core/core.ts +16 -0
  128. package/src/{plugin/fromRimori → fromRimori}/EventBus.ts +29 -11
  129. package/src/fromRimori/PluginTypes.ts +205 -0
  130. package/src/hooks/UseChatHook.ts +8 -5
  131. package/src/index.ts +6 -3
  132. package/src/plugin/AccomplishmentHandler.ts +1 -1
  133. package/src/plugin/AudioController.ts +58 -0
  134. package/src/plugin/Logger.ts +324 -0
  135. package/src/plugin/PluginController.ts +203 -63
  136. package/src/plugin/RimoriClient.ts +127 -55
  137. package/src/plugin/StandaloneClient.ts +30 -11
  138. package/src/plugin/ThemeSetter.ts +16 -9
  139. package/src/providers/PluginProvider.tsx +46 -13
  140. package/src/utils/Language.ts +4 -2
  141. package/src/utils/difficultyConverter.ts +3 -3
  142. package/src/utils/endpoint.ts +2 -0
  143. package/src/worker/WorkerSetup.ts +13 -60
  144. package/dist/components/PluginController.d.ts +0 -21
  145. package/dist/components/PluginController.js +0 -116
  146. package/dist/controller/AIController.d.ts +0 -23
  147. package/dist/controller/AIController.js +0 -93
  148. package/dist/controller/SidePluginController.d.ts +0 -3
  149. package/dist/controller/SidePluginController.js +0 -31
  150. package/dist/controller/VoiceController.d.ts +0 -10
  151. package/dist/core.d.ts +0 -7
  152. package/dist/core.js +0 -7
  153. package/dist/plugin/ContextMenu.d.ts +0 -17
  154. package/dist/plugin/ContextMenu.js +0 -45
  155. package/dist/plugin/fromRimori/PluginTypes.d.ts +0 -48
  156. package/dist/plugin/fromRimori/SupabaseHandler.d.ts +0 -13
  157. package/dist/plugin/fromRimori/SupabaseHandler.js +0 -55
  158. package/dist/providers/PluginController.d.ts +0 -21
  159. package/dist/providers/PluginController.js +0 -116
  160. package/dist/types/Actions.d.ts +0 -4
  161. package/dist/types/Actions.js +0 -1
  162. package/src/controller/AIController.ts +0 -112
  163. package/src/controller/ObjectController.ts +0 -107
  164. package/src/controller/SidePluginController.ts +0 -25
  165. package/src/controller/VoiceController.ts +0 -26
  166. package/src/core.ts +0 -8
  167. package/src/plugin/fromRimori/PluginTypes.ts +0 -64
  168. package/src/types/Actions.ts +0 -6
  169. /package/dist/{core → components}/components/ContextMenu.d.ts +0 -0
  170. /package/dist/{plugin/fromRimori → fromRimori}/PluginTypes.js +0 -0
  171. /package/src/{plugin/fromRimori → fromRimori}/readme.md +0 -0
@@ -0,0 +1,324 @@
1
+ import { RimoriClient } from './RimoriClient';
2
+ import html2canvas from 'html2canvas';
3
+
4
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
5
+
6
+ interface LogEntry {
7
+ id: string;
8
+ timestamp: string;
9
+ level: LogLevel;
10
+ message: string;
11
+ data?: any;
12
+ context?: {
13
+ url: string;
14
+ userAgent: string;
15
+ browserInfo: BrowserInfo;
16
+ screenshot?: string;
17
+ mousePosition?: MousePosition;
18
+ };
19
+ }
20
+
21
+ interface BrowserInfo {
22
+ userAgent: string;
23
+ language: string;
24
+ cookieEnabled: boolean;
25
+ onLine: boolean;
26
+ screenResolution: string;
27
+ windowSize: string;
28
+ timestamp: string;
29
+ }
30
+
31
+ interface MousePosition {
32
+ x: number;
33
+ y: number;
34
+ timestamp: string;
35
+ }
36
+
37
+ /**
38
+ * Singleton Logger class for Rimori client plugins.
39
+ * Handles all logging levels, production filtering, and log transmission to Rimori.
40
+ * Overrides console methods globally for seamless integration.
41
+ */
42
+ export class Logger {
43
+ private static instance: Logger;
44
+ private isProduction: boolean;
45
+ private logs: LogEntry[] = [];
46
+ private logIdCounter = 0;
47
+ private originalConsole: {
48
+ log: typeof console.log;
49
+ info: typeof console.info;
50
+ warn: typeof console.warn;
51
+ error: typeof console.error;
52
+ debug: typeof console.debug;
53
+ };
54
+ private mousePosition: MousePosition | null = null;
55
+
56
+ private constructor(rimori: RimoriClient, isProduction?: boolean) {
57
+ this.isProduction = this.validateIsProduction(isProduction);
58
+
59
+ // Store original console methods
60
+ this.originalConsole = {
61
+ log: console.log,
62
+ info: console.info,
63
+ warn: console.warn,
64
+ error: console.error,
65
+ debug: console.debug
66
+ };
67
+
68
+ // Override console methods globally
69
+ this.overrideConsoleMethods();
70
+
71
+ // Track mouse position
72
+ this.trackMousePosition();
73
+
74
+ // Expose logs to global scope for DevTools access
75
+ this.exposeToDevTools();
76
+
77
+ // Set up navigation clearing
78
+ this.setupNavigationClearing();
79
+
80
+ rimori.event.respond('logging.requestPluginLogs', async () => {
81
+ this.addLogEntry(await this.createLogEntry('info', 'Screenshot capture', undefined, true));
82
+ const logs = {
83
+ logs: this.logs,
84
+ pluginId: rimori.plugin.pluginId,
85
+ timestamp: new Date().toISOString()
86
+ }
87
+ this.logs = [];
88
+ this.logIdCounter = 0;
89
+ return logs;
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Initialize the Logger singleton and override console methods globally.
95
+ * @param rimori - Rimori client instance
96
+ * @param isProduction - Whether the environment is production
97
+ * @returns Logger instance
98
+ */
99
+ public static getInstance(rimori: RimoriClient, isProduction?: boolean): Logger {
100
+ if (!Logger.instance) {
101
+ Logger.instance = new Logger(rimori, isProduction);
102
+ }
103
+ return Logger.instance;
104
+ }
105
+
106
+ private validateIsProduction(isProduction?: boolean): boolean {
107
+ if (isProduction !== undefined) {
108
+ return isProduction;
109
+ }
110
+ if (typeof window !== 'undefined' && window.location.href) {
111
+ return !window.location.href.includes('localhost');
112
+ }
113
+ return true;
114
+ }
115
+ /**
116
+ * Expose log access to global scope for DevTools console access.
117
+ */
118
+ private exposeToDevTools(): void {
119
+ if (typeof window !== 'undefined') {
120
+ // Expose a global function to access logs from DevTools console
121
+ (window as any).getRimoriLogs = () => this.logs;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Set up navigation event listeners to clear logs on page changes.
127
+ */
128
+ private setupNavigationClearing(): void {
129
+ if (typeof window === 'undefined' || typeof history === 'undefined') return;
130
+
131
+ // Clear logs on browser back/forward
132
+ window.addEventListener('popstate', () => this.logs = []);
133
+
134
+ // Override history methods to clear logs on programmatic navigation
135
+ const originalPushState = history.pushState;
136
+ const originalReplaceState = history.replaceState;
137
+
138
+ history.pushState = (...args) => {
139
+ originalPushState.apply(history, args);
140
+ this.logs = [];
141
+ };
142
+
143
+ history.replaceState = (...args) => {
144
+ originalReplaceState.apply(history, args);
145
+ this.logs = [];
146
+ };
147
+
148
+ // Listen for URL changes (works with React Router and other SPAs)
149
+ let currentUrl = window.location.href;
150
+ const checkUrlChange = () => {
151
+ if (window.location.href !== currentUrl) {
152
+ currentUrl = window.location.href;
153
+ this.logs = [];
154
+ }
155
+ };
156
+
157
+ // Check for URL changes periodically
158
+ setInterval(checkUrlChange, 100);
159
+
160
+ // Also listen for hash changes (for hash-based routing)
161
+ window.addEventListener('hashchange', () => this.logs = []);
162
+ }
163
+
164
+ /**
165
+ * Override console methods globally to capture all console calls.
166
+ */
167
+ private overrideConsoleMethods(): void {
168
+ // Override console.log
169
+ console.log = (...args: any[]) => {
170
+ this.originalConsole.log(...args);
171
+ this.handleConsoleCall('info', args);
172
+ };
173
+
174
+ // Override console.info
175
+ console.info = (...args: any[]) => {
176
+ this.originalConsole.info(...args);
177
+ this.handleConsoleCall('info', args);
178
+ };
179
+
180
+ // Override console.warn
181
+ console.warn = (...args: any[]) => {
182
+ this.originalConsole.warn(...args);
183
+ this.handleConsoleCall('warn', args);
184
+ };
185
+
186
+ // Override console.error
187
+ console.error = (...args: any[]) => {
188
+ this.originalConsole.error(...args);
189
+ this.handleConsoleCall('error', args);
190
+ };
191
+
192
+ // Override console.debug
193
+ console.debug = (...args: any[]) => {
194
+ this.originalConsole.debug(...args);
195
+ this.handleConsoleCall('debug', args);
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Track mouse position for screenshot context.
201
+ */
202
+ private trackMousePosition(): void {
203
+ if (typeof window !== 'undefined') {
204
+ const updateMousePosition = (event: MouseEvent) => {
205
+ this.mousePosition = {
206
+ x: event.clientX,
207
+ y: event.clientY,
208
+ timestamp: new Date().toISOString()
209
+ };
210
+ };
211
+
212
+ window.addEventListener('mousemove', updateMousePosition);
213
+ window.addEventListener('click', updateMousePosition);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Handle console method calls and create log entries.
219
+ * @param level - Log level
220
+ * @param args - Console arguments
221
+ */
222
+ private async handleConsoleCall(level: LogLevel, args: any[]): Promise<void> {
223
+ // Skip if this is a production log that shouldn't be stored
224
+ if (this.isProduction && (level === 'debug' || level === 'info')) {
225
+ return;
226
+ }
227
+
228
+ // Convert console arguments to message and data
229
+ const message = args.map(arg =>
230
+ typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
231
+ ).join(' ');
232
+
233
+ const data = args.length > 1 ? args.slice(1) : undefined;
234
+
235
+ const entry = await this.createLogEntry(level, message, data);
236
+ this.addLogEntry(entry);
237
+ }
238
+
239
+ /**
240
+ * Get browser and system information for debugging.
241
+ * @returns Object with browser and system information
242
+ */
243
+ private getBrowserInfo(): BrowserInfo {
244
+ return {
245
+ userAgent: navigator.userAgent,
246
+ language: navigator.language,
247
+ cookieEnabled: navigator.cookieEnabled,
248
+ onLine: navigator.onLine,
249
+ screenResolution: `${screen.width}x${screen.height}`,
250
+ windowSize: `${window.innerWidth}x${window.innerHeight}`,
251
+ timestamp: new Date().toISOString()
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Capture a screenshot of the current page.
257
+ * @returns Promise resolving to base64 screenshot or null if failed
258
+ */
259
+ private async captureScreenshot(): Promise<string | null> {
260
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
261
+ const canvas = await html2canvas(document.body);
262
+ const screenshot = canvas.toDataURL('image/png');
263
+ // this.originalConsole.log("screenshot captured", screenshot)
264
+ return screenshot;
265
+ }
266
+ return null;
267
+ }
268
+
269
+ /**
270
+ * Create a log entry with context information.
271
+ * @param level - Log level
272
+ * @param message - Log message
273
+ * @param data - Additional data
274
+ * @returns Log entry
275
+ */
276
+ private async createLogEntry(level: LogLevel, message: string, data?: any, forceScreenshot?: boolean): Promise<LogEntry> {
277
+ const context: Partial<LogEntry['context']> = {};
278
+
279
+ // Add URL if available
280
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
281
+ return {
282
+ id: `log_${++this.logIdCounter}_${Date.now()}`,
283
+ timestamp: new Date().toISOString(),
284
+ level,
285
+ message,
286
+ data,
287
+ }
288
+ }
289
+
290
+ context.url = window.location.href;
291
+
292
+ // Add browser info (this method now handles worker context internally)
293
+ context.browserInfo = this.getBrowserInfo();
294
+ context.userAgent = context.browserInfo.userAgent;
295
+
296
+ // Add screenshot and mouse position if level is error or warn
297
+ if (level === 'error' || level === 'warn' || forceScreenshot) {
298
+ context.screenshot = await this.captureScreenshot() || undefined;
299
+ context.mousePosition = this.mousePosition || undefined;
300
+ }
301
+
302
+ return {
303
+ id: `log_${++this.logIdCounter}_${Date.now()}`,
304
+ timestamp: new Date().toISOString(),
305
+ level,
306
+ message,
307
+ data,
308
+ context: context as LogEntry['context']
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Add a log entry to the internal log array.
314
+ * @param entry - Log entry to add
315
+ */
316
+ private addLogEntry(entry: LogEntry): void {
317
+ this.logs.push(entry);
318
+
319
+ // Maintain log size limit (1000 entries)
320
+ if (this.logs.length > 1000) {
321
+ this.logs = this.logs.slice(-1000);
322
+ }
323
+ }
324
+ }
@@ -1,59 +1,135 @@
1
- import { AuthSessionMissingError, createClient, SupabaseClient } from '@supabase/supabase-js';
2
- import { EventBus, EventBusMessage } from './fromRimori/EventBus';
1
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
2
+ import { UserInfo } from '../core/controller/SettingsController';
3
+ import { EventBus, EventBusMessage } from '../fromRimori/EventBus';
4
+ import { ActivePlugin, Plugin } from '../fromRimori/PluginTypes';
3
5
  import { RimoriClient } from "./RimoriClient";
4
- import { setTheme } from './ThemeSetter';
5
6
  import { StandaloneClient } from './StandaloneClient';
7
+ import { setTheme } from './ThemeSetter';
8
+ import { Logger } from './Logger';
6
9
 
7
10
  // Add declaration for WorkerGlobalScope
8
11
  declare const WorkerGlobalScope: any;
9
12
 
10
- interface SupabaseInfo {
13
+ export interface RimoriInfo {
11
14
  url: string,
12
15
  key: string,
16
+ backendUrl: string,
13
17
  token: string,
14
18
  expiration: Date,
15
19
  tablePrefix: string,
16
20
  pluginId: string
21
+ installedPlugins: Plugin[]
22
+ profile: UserInfo
23
+ mainPanelPlugin?: ActivePlugin
24
+ sidePanelPlugin?: ActivePlugin
17
25
  }
18
26
 
19
27
  export class PluginController {
20
28
  private static client: RimoriClient;
21
29
  private static instance: PluginController;
22
- private communicationSecret: string | null = null;
30
+ private port: MessagePort | null = null;
31
+ private queryParams: Record<string, string> = {};
23
32
  private supabase: SupabaseClient | null = null;
24
- private supabaseInfo: SupabaseInfo | null = null;
33
+ private rimoriInfo: RimoriInfo | null = null;
25
34
  private pluginId: string;
35
+ private isMessageChannelReady: boolean = false;
36
+ private pendingRequests: Array<() => void> = [];
26
37
 
27
38
  private constructor(pluginId: string, standalone: boolean) {
28
39
  this.pluginId = pluginId;
29
40
  this.getClient = this.getClient.bind(this);
30
41
 
31
42
  if (typeof WorkerGlobalScope === 'undefined') {
32
- setTheme();
43
+ // In standalone mode, use URL fallback. In iframe mode, theme will be set after MessageChannel init
44
+ if (standalone) {
45
+ setTheme();
46
+ }
33
47
  }
34
48
 
35
- //no need to forward messages to parent in standalone mode
49
+ //no need to forward messages to parent in standalone mode or worker context
36
50
  if (standalone) return;
37
-
38
- window.addEventListener("message", (event) => {
39
- // console.log("client: message received", event);
40
- const { topic, sender, data, eventId } = event.data.event as EventBusMessage;
41
-
42
- // skip forwarding messages from own plugin
43
- if (sender === pluginId) return;
44
-
45
- EventBus.emit(sender, topic, data, eventId);
46
- });
47
-
48
- const secret = this.getSecret();
49
-
50
- EventBus.on("*", (event) => {
51
- // skip messages which are not from the own plugin
52
- if (event.sender !== this.pluginId) return;
53
- if (event.topic.startsWith("self.")) return;
54
- // console.log("sending event to parent", event);
55
- window.parent.postMessage({ event, secret }, "*")
56
- });
51
+
52
+ this.initMessageChannel(typeof WorkerGlobalScope !== 'undefined');
53
+ }
54
+
55
+ private initMessageChannel(worker: boolean = false) {
56
+ const listener = (event: MessageEvent) => {
57
+ console.log("[PluginController] window message", { origin: event.origin, data: event.data });
58
+ const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
59
+ const [transferredPort] = event.ports || [];
60
+
61
+ if (type !== "rimori:init" || !transferredPort || pluginId !== this.pluginId) {
62
+ console.log("[PluginController] message ignored (not init or wrong plugin)", { type, pluginId, hasPort: !!transferredPort });
63
+ return;
64
+ }
65
+
66
+ this.queryParams = queryParams || {};
67
+ this.port = transferredPort;
68
+
69
+ // Initialize Supabase client immediately with provided info
70
+ if (rimoriInfo) {
71
+ this.rimoriInfo = rimoriInfo;
72
+ this.supabase = createClient(rimoriInfo.url, rimoriInfo.key, {
73
+ accessToken: () => Promise.resolve(rimoriInfo.token)
74
+ });
75
+ }
76
+
77
+ // Handle messages from parent
78
+ this.port.onmessage = ({ data }) => {
79
+ const { event, type, eventId, response, error } = data || {};
80
+
81
+ // no idea why this is needed but it works for now
82
+ if (type === 'response' && eventId) {
83
+ EventBus.emit(this.pluginId, response.topic, response.data, eventId);
84
+ } else if (type === 'error' && eventId) {
85
+ EventBus.emit(this.pluginId, 'error', { error }, eventId);
86
+ } else if (event) {
87
+ const { topic, sender, data: eventData, eventId } = event as EventBusMessage;
88
+ if (sender !== this.pluginId) {
89
+ EventBus.emit(sender, topic, eventData, eventId);
90
+ }
91
+ }
92
+ };
93
+
94
+ // Set theme from MessageChannel query params
95
+ if (!worker) {
96
+ const theme = this.queryParams['rm_theme'];
97
+ setTheme(theme);
98
+ }
99
+
100
+ // Forward plugin events to parent (only after MessageChannel is ready)
101
+ EventBus.on("*", (ev) => {
102
+ if (ev.sender === this.pluginId && !ev.topic.startsWith("self.")) {
103
+ this.port?.postMessage({ event: ev });
104
+ }
105
+ });
106
+
107
+ // Mark MessageChannel as ready and process pending requests
108
+ this.isMessageChannelReady = true;
109
+
110
+ // Process any pending requests
111
+ this.pendingRequests.forEach(request => request());
112
+ this.pendingRequests = [];
113
+ };
114
+ if (worker) {
115
+ self.onmessage = listener;
116
+ } else {
117
+ window.addEventListener("message", listener);
118
+ }
119
+ this.sendHello(worker);
120
+ }
121
+
122
+ private sendHello(isWorker: boolean = false) {
123
+ try {
124
+ const payload = { type: "rimori:hello", pluginId: this.pluginId };
125
+ if (isWorker) {
126
+ self.postMessage(payload);
127
+ } else {
128
+ window.parent.postMessage(payload, "*");
129
+ }
130
+ } catch (e) {
131
+ console.error("[PluginController] Error sending hello:", e);
132
+ }
57
133
  }
58
134
 
59
135
  public static async getInstance(pluginId: string, standalone = false): Promise<RimoriClient> {
@@ -63,62 +139,126 @@ export class PluginController {
63
139
  }
64
140
  PluginController.instance = new PluginController(pluginId, standalone);
65
141
  PluginController.client = await RimoriClient.getInstance(PluginController.instance);
142
+
143
+ //only init logger in workers and on main plugin pages
144
+ if (PluginController.instance.getQueryParam("applicationMode") !== "sidebar") {
145
+ Logger.getInstance(PluginController.client);
146
+ }
66
147
  }
67
148
  return PluginController.client;
68
149
  }
69
150
 
70
- private getSecret(): string | null {
71
- if (!this.communicationSecret) {
72
- const secret = new URLSearchParams(window.location.search).get("secret");
73
- if (!secret) {
74
- console.info("Communication secret not found in URL as query parameter");
75
- }
76
- this.communicationSecret = secret;
77
- }
78
- return this.communicationSecret;
151
+ public getQueryParam(key: string): string | null {
152
+ return this.queryParams[key] || null;
79
153
  }
80
154
 
81
- public async getClient(): Promise<{ supabase: SupabaseClient, tablePrefix: string, pluginId: string }> {
82
- if (
83
- this.supabase &&
84
- this.supabaseInfo &&
85
- this.supabaseInfo.expiration > new Date()
86
- ) {
87
- return { supabase: this.supabase, tablePrefix: this.supabaseInfo.tablePrefix, pluginId: this.supabaseInfo.pluginId };
155
+ public async getClient(): Promise<{ supabase: SupabaseClient, info: RimoriInfo }> {
156
+ // Return cached client if valid
157
+ if (this.supabase && this.rimoriInfo && this.rimoriInfo.expiration > new Date()) {
158
+ return { supabase: this.supabase, info: this.rimoriInfo };
88
159
  }
89
160
 
90
- const { data } = await EventBus.request<SupabaseInfo>(this.pluginId, "global.supabase.requestAccess");
91
- this.supabaseInfo = data;
92
- this.supabase = createClient(this.supabaseInfo.url, this.supabaseInfo.key, {
93
- accessToken: () => Promise.resolve(this.getToken())
94
- });
161
+ // If MessageChannel is not ready yet, queue the request
162
+ if (!this.isMessageChannelReady) {
163
+ return new Promise<{ supabase: SupabaseClient, info: RimoriInfo }>((resolve) => {
164
+ this.pendingRequests.push(async () => {
165
+ const result = await this.getClient();
166
+ resolve(result);
167
+ });
168
+ });
169
+ }
95
170
 
96
- return { supabase: this.supabase, tablePrefix: this.supabaseInfo.tablePrefix, pluginId: this.supabaseInfo.pluginId };
97
- }
171
+ // If we have rimoriInfo from MessageChannel init, use it directly
172
+ if (this.rimoriInfo && this.supabase) {
173
+ return { supabase: this.supabase, info: this.rimoriInfo };
174
+ }
175
+
176
+ // Fallback: request from parent
177
+ if (!this.rimoriInfo) {
178
+ if (typeof WorkerGlobalScope !== 'undefined') {
179
+ // In worker context, send request via self.postMessage to WorkerHandler
180
+ const eventId = Math.floor(Math.random() * 1000000000);
181
+ const requestEvent = {
182
+ event: {
183
+ timestamp: new Date().toISOString(),
184
+ eventId,
185
+ sender: this.pluginId,
186
+ topic: 'global.supabase.requestAccess',
187
+ data: {},
188
+ debug: false
189
+ }
190
+ };
191
+
192
+ return new Promise<{ supabase: SupabaseClient, info: RimoriInfo }>((resolve) => {
193
+ // Listen for the response
194
+ const originalOnMessage = self.onmessage;
195
+ self.onmessage = (event) => {
196
+ if (event.data?.topic === 'global.supabase.requestAccess' && event.data?.eventId === eventId) {
197
+ this.rimoriInfo = event.data.data;
198
+ this.supabase = createClient(this.rimoriInfo!.url, this.rimoriInfo!.key, {
199
+ accessToken: () => Promise.resolve(this.getToken())
200
+ });
201
+ self.onmessage = originalOnMessage; // Restore original handler
202
+ resolve({ supabase: this.supabase, info: this.rimoriInfo! });
203
+ } else if (originalOnMessage) {
204
+ originalOnMessage.call(self, event);
205
+ }
206
+ };
98
207
 
99
- public async getToken() {
100
- if (this.supabaseInfo && this.supabaseInfo.expiration && this.supabaseInfo.expiration > new Date()) {
101
- return this.supabaseInfo.token;
208
+ // Send the request
209
+ self.postMessage(requestEvent);
210
+ });
211
+ } else {
212
+ // In main thread context, use EventBus
213
+ const { data } = await EventBus.request<RimoriInfo>(this.pluginId, "global.supabase.requestAccess");
214
+ this.rimoriInfo = data;
215
+ this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
216
+ accessToken: () => Promise.resolve(this.getToken())
217
+ });
218
+ }
102
219
  }
103
220
 
104
- const { data } = await EventBus.request<{ token: string, expiration: Date }>(this.pluginId, "global.supabase.requestAccess");
221
+ return { supabase: this.supabase!, info: this.rimoriInfo };
222
+ }
105
223
 
106
- if (!this.supabaseInfo) {
107
- throw new Error("Supabase info not found");
224
+ public async getToken(): Promise<string> {
225
+ if (this.rimoriInfo && this.rimoriInfo.expiration && this.rimoriInfo.expiration > new Date()) {
226
+ return this.rimoriInfo.token;
227
+ }
228
+
229
+ // If we don't have rimoriInfo, request it
230
+ if (!this.rimoriInfo) {
231
+ const { data } = await EventBus.request<RimoriInfo>(this.pluginId, "global.supabase.requestAccess");
232
+ this.rimoriInfo = data;
233
+ return this.rimoriInfo.token;
108
234
  }
109
235
 
110
- this.supabaseInfo.token = data.token;
111
- this.supabaseInfo.expiration = data.expiration;
236
+ // If token is expired, request fresh access
237
+ const { data } = await EventBus.request<{ token: string, expiration: Date }>(this.pluginId, "global.supabase.requestAccess");
238
+ this.rimoriInfo.token = data.token;
239
+ this.rimoriInfo.expiration = data.expiration;
112
240
 
113
- return this.supabaseInfo.token;
241
+ return this.rimoriInfo.token;
114
242
  }
115
243
 
244
+ /**
245
+ * Gets the Supabase URL.
246
+ * @returns The Supabase URL.
247
+ * @deprecated All endpoints should use the backend URL instead.
248
+ */
116
249
  public getSupabaseUrl() {
117
- if (!this.supabaseInfo) {
250
+ if (!this.rimoriInfo) {
118
251
  throw new Error("Supabase info not found");
119
252
  }
120
253
 
121
- return this.supabaseInfo.url;
254
+ return this.rimoriInfo.url;
255
+ }
256
+
257
+ public getBackendUrl() {
258
+ if (!this.rimoriInfo) {
259
+ throw new Error("Rimori info not found");
260
+ }
261
+ return this.rimoriInfo.backendUrl;
122
262
  }
123
263
 
124
264
  public getGlobalEventTopic(preliminaryTopic: string) {
@@ -138,7 +278,7 @@ export class PluginController {
138
278
  throw new Error(`The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`);
139
279
  }
140
280
 
141
- const topicRoot = this.supabaseInfo?.pluginId ?? "global";
281
+ const topicRoot = this.rimoriInfo?.pluginId ?? "global";
142
282
  return `${topicRoot}.${preliminaryTopic}`;
143
283
  }
144
284