@rimori/react-client 0.1.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 (139) hide show
  1. package/.prettierignore +35 -0
  2. package/LICENSE +201 -0
  3. package/README copy.md +1216 -0
  4. package/README.md +1 -0
  5. package/dist/components/MarkdownEditor.d.ts +8 -0
  6. package/dist/components/MarkdownEditor.js +48 -0
  7. package/dist/components/Spinner.d.ts +8 -0
  8. package/dist/components/Spinner.js +4 -0
  9. package/dist/components/ai/Assistant.d.ts +9 -0
  10. package/dist/components/ai/Assistant.js +58 -0
  11. package/dist/components/ai/Avatar.d.ts +14 -0
  12. package/dist/components/ai/Avatar.js +59 -0
  13. package/dist/components/ai/EmbeddedAssistent/AudioInputField.d.ts +7 -0
  14. package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +37 -0
  15. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +8 -0
  16. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +79 -0
  17. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +19 -0
  18. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +91 -0
  19. package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +27 -0
  20. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +185 -0
  21. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +11 -0
  22. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +95 -0
  23. package/dist/components/ai/utils.d.ts +6 -0
  24. package/dist/components/ai/utils.js +13 -0
  25. package/dist/components/audio/Playbutton.d.ts +15 -0
  26. package/dist/components/audio/Playbutton.js +80 -0
  27. package/dist/components/components/ContextMenu.d.ts +10 -0
  28. package/dist/components/components/ContextMenu.js +135 -0
  29. package/dist/hooks/I18nHooks.d.ts +11 -0
  30. package/dist/hooks/I18nHooks.js +25 -0
  31. package/dist/hooks/UseChatHook.d.ts +10 -0
  32. package/dist/hooks/UseChatHook.js +29 -0
  33. package/dist/providers/PluginProvider.d.ts +11 -0
  34. package/dist/providers/PluginProvider.js +142 -0
  35. package/dist/react-client/plugin/ThemeSetter.d.ts +2 -0
  36. package/dist/react-client/plugin/ThemeSetter.js +19 -0
  37. package/dist/react-client/src/components/ContextMenu.d.ts +10 -0
  38. package/dist/react-client/src/components/ContextMenu.js +135 -0
  39. package/dist/react-client/src/components/MarkdownEditor.d.ts +8 -0
  40. package/dist/react-client/src/components/MarkdownEditor.js +48 -0
  41. package/dist/react-client/src/components/Spinner.d.ts +8 -0
  42. package/dist/react-client/src/components/Spinner.js +4 -0
  43. package/dist/react-client/src/components/ai/Assistant.d.ts +9 -0
  44. package/dist/react-client/src/components/ai/Assistant.js +58 -0
  45. package/dist/react-client/src/components/ai/Avatar.d.ts +14 -0
  46. package/dist/react-client/src/components/ai/Avatar.js +59 -0
  47. package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.d.ts +7 -0
  48. package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.js +37 -0
  49. package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +8 -0
  50. package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +79 -0
  51. package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +19 -0
  52. package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.js +91 -0
  53. package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.d.ts +27 -0
  54. package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.js +185 -0
  55. package/dist/react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +11 -0
  56. package/dist/react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.js +95 -0
  57. package/dist/react-client/src/components/ai/utils.d.ts +6 -0
  58. package/dist/react-client/src/components/ai/utils.js +13 -0
  59. package/dist/react-client/src/components/audio/Playbutton.d.ts +15 -0
  60. package/dist/react-client/src/components/audio/Playbutton.js +82 -0
  61. package/dist/react-client/src/components/components/ContextMenu.d.ts +10 -0
  62. package/dist/react-client/src/components/components/ContextMenu.js +135 -0
  63. package/dist/react-client/src/hooks/I18nHooks.d.ts +11 -0
  64. package/dist/react-client/src/hooks/I18nHooks.js +25 -0
  65. package/dist/react-client/src/hooks/UseChatHook.d.ts +10 -0
  66. package/dist/react-client/src/hooks/UseChatHook.js +29 -0
  67. package/dist/react-client/src/plugin/ThemeSetter.d.ts +2 -0
  68. package/dist/react-client/src/plugin/ThemeSetter.js +19 -0
  69. package/dist/react-client/src/providers/PluginProvider.d.ts +12 -0
  70. package/dist/react-client/src/providers/PluginProvider.js +142 -0
  71. package/dist/react-client/src/utils/FullscreenUtils.d.ts +2 -0
  72. package/dist/react-client/src/utils/FullscreenUtils.js +23 -0
  73. package/dist/react-client/src/utils/PluginUtils.d.ts +2 -0
  74. package/dist/react-client/src/utils/PluginUtils.js +23 -0
  75. package/dist/rimori-client/src/cli/types/DatabaseTypes.d.ts +103 -0
  76. package/dist/rimori-client/src/cli/types/DatabaseTypes.js +2 -0
  77. package/dist/rimori-client/src/controller/AIController.d.ts +15 -0
  78. package/dist/rimori-client/src/controller/AIController.js +255 -0
  79. package/dist/rimori-client/src/controller/AccomplishmentController.d.ts +38 -0
  80. package/dist/rimori-client/src/controller/AccomplishmentController.js +112 -0
  81. package/dist/rimori-client/src/controller/AudioController.d.ts +37 -0
  82. package/dist/rimori-client/src/controller/AudioController.js +68 -0
  83. package/dist/rimori-client/src/controller/ExerciseController.d.ts +54 -0
  84. package/dist/rimori-client/src/controller/ExerciseController.js +74 -0
  85. package/dist/rimori-client/src/controller/ObjectController.d.ts +42 -0
  86. package/dist/rimori-client/src/controller/ObjectController.js +76 -0
  87. package/dist/rimori-client/src/controller/SettingsController.d.ts +79 -0
  88. package/dist/rimori-client/src/controller/SettingsController.js +118 -0
  89. package/dist/rimori-client/src/controller/SharedContentController.d.ts +106 -0
  90. package/dist/rimori-client/src/controller/SharedContentController.js +285 -0
  91. package/dist/rimori-client/src/controller/TranslationController.d.ts +38 -0
  92. package/dist/rimori-client/src/controller/TranslationController.js +106 -0
  93. package/dist/rimori-client/src/controller/VoiceController.d.ts +9 -0
  94. package/dist/rimori-client/src/controller/VoiceController.js +37 -0
  95. package/dist/rimori-client/src/fromRimori/EventBus.d.ts +101 -0
  96. package/dist/rimori-client/src/fromRimori/EventBus.js +263 -0
  97. package/dist/rimori-client/src/fromRimori/PluginTypes.d.ts +174 -0
  98. package/dist/rimori-client/src/fromRimori/PluginTypes.js +1 -0
  99. package/dist/rimori-client/src/index.d.ts +11 -0
  100. package/dist/rimori-client/src/index.js +10 -0
  101. package/dist/rimori-client/src/plugin/CommunicationHandler.d.ts +48 -0
  102. package/dist/rimori-client/src/plugin/CommunicationHandler.js +234 -0
  103. package/dist/rimori-client/src/plugin/Logger.d.ts +73 -0
  104. package/dist/rimori-client/src/plugin/Logger.js +308 -0
  105. package/dist/rimori-client/src/plugin/RimoriClient.d.ts +258 -0
  106. package/dist/rimori-client/src/plugin/RimoriClient.js +375 -0
  107. package/dist/rimori-client/src/plugin/StandaloneClient.d.ts +17 -0
  108. package/dist/rimori-client/src/plugin/StandaloneClient.js +115 -0
  109. package/dist/rimori-client/src/utils/difficultyConverter.d.ts +4 -0
  110. package/dist/rimori-client/src/utils/difficultyConverter.js +10 -0
  111. package/dist/rimori-client/src/utils/endpoint.d.ts +2 -0
  112. package/dist/rimori-client/src/utils/endpoint.js +2 -0
  113. package/dist/style.css +110 -0
  114. package/dist/style.css.map +1 -0
  115. package/dist/utils/PluginUtils.d.ts +2 -0
  116. package/dist/utils/PluginUtils.js +23 -0
  117. package/eslint.config.js +53 -0
  118. package/index.ts +6 -0
  119. package/package.json +47 -0
  120. package/prettier.config.js +8 -0
  121. package/src/components/ContextMenu.tsx +177 -0
  122. package/src/components/MarkdownEditor.tsx +144 -0
  123. package/src/components/Spinner.tsx +29 -0
  124. package/src/components/ai/Assistant.tsx +96 -0
  125. package/src/components/ai/Avatar.tsx +99 -0
  126. package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +73 -0
  127. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +107 -0
  128. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +96 -0
  129. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +197 -0
  130. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +129 -0
  131. package/src/components/ai/utils.ts +21 -0
  132. package/src/components/audio/Playbutton.tsx +126 -0
  133. package/src/hooks/I18nHooks.ts +33 -0
  134. package/src/hooks/UseChatHook.ts +38 -0
  135. package/src/plugin/ThemeSetter.ts +23 -0
  136. package/src/providers/PluginProvider.tsx +197 -0
  137. package/src/style.scss +136 -0
  138. package/src/utils/FullscreenUtils.ts +22 -0
  139. package/tsconfig.json +23 -0
@@ -0,0 +1,234 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { createClient } from '@supabase/supabase-js';
11
+ import { EventBus } from '../fromRimori/EventBus';
12
+ export class RimoriCommunicationHandler {
13
+ constructor(pluginId, standalone) {
14
+ this.port = null;
15
+ this.queryParams = {};
16
+ this.supabase = null;
17
+ this.rimoriInfo = null;
18
+ this.isMessageChannelReady = false;
19
+ this.pendingRequests = [];
20
+ this.pluginId = pluginId;
21
+ this.getClient = this.getClient.bind(this);
22
+ //no need to forward messages to parent in standalone mode or worker context
23
+ if (standalone)
24
+ return;
25
+ this.initMessageChannel(typeof WorkerGlobalScope !== 'undefined');
26
+ }
27
+ initMessageChannel(worker = false) {
28
+ const listener = (event) => {
29
+ console.log('[PluginController] window message', { origin: event.origin, data: event.data });
30
+ const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
31
+ const [transferredPort] = event.ports || [];
32
+ if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
33
+ console.log('[PluginController] message ignored (not init or wrong plugin)', {
34
+ type,
35
+ pluginId,
36
+ hasPort: !!transferredPort,
37
+ });
38
+ return;
39
+ }
40
+ this.queryParams = queryParams || {};
41
+ this.port = transferredPort;
42
+ // Initialize Supabase client immediately with provided info
43
+ if (rimoriInfo) {
44
+ this.rimoriInfo = rimoriInfo;
45
+ this.supabase = createClient(rimoriInfo.url, rimoriInfo.key, {
46
+ accessToken: () => Promise.resolve(rimoriInfo.token),
47
+ });
48
+ }
49
+ // Handle messages from parent
50
+ this.port.onmessage = ({ data }) => {
51
+ const { event, type, eventId, response, error } = data || {};
52
+ // no idea why this is needed but it works for now
53
+ if (type === 'response' && eventId) {
54
+ EventBus.emit(this.pluginId, response.topic, response.data, eventId);
55
+ }
56
+ else if (type === 'error' && eventId) {
57
+ EventBus.emit(this.pluginId, 'error', { error }, eventId);
58
+ }
59
+ else if (event) {
60
+ const { topic, sender, data: eventData, eventId } = event;
61
+ if (sender !== this.pluginId) {
62
+ EventBus.emit(sender, topic, eventData, eventId);
63
+ }
64
+ }
65
+ };
66
+ // Set theme from MessageChannel query params
67
+ if (!worker) {
68
+ const theme = this.queryParams['rm_theme'];
69
+ // setTheme(theme);
70
+ console.log('TODO: set theme from MessageChannel query params');
71
+ }
72
+ // Forward plugin events to parent (only after MessageChannel is ready)
73
+ EventBus.on('*', (ev) => {
74
+ var _a;
75
+ if (ev.sender === this.pluginId && !ev.topic.startsWith('self.')) {
76
+ (_a = this.port) === null || _a === void 0 ? void 0 : _a.postMessage({ event: ev });
77
+ }
78
+ });
79
+ // Mark MessageChannel as ready and process pending requests
80
+ this.isMessageChannelReady = true;
81
+ // Process any pending requests
82
+ this.pendingRequests.forEach((request) => request());
83
+ this.pendingRequests = [];
84
+ };
85
+ if (worker) {
86
+ self.onmessage = listener;
87
+ }
88
+ else {
89
+ window.addEventListener('message', listener);
90
+ }
91
+ this.sendHello(worker);
92
+ }
93
+ sendHello(isWorker = false) {
94
+ try {
95
+ const payload = { type: 'rimori:hello', pluginId: this.pluginId };
96
+ if (isWorker) {
97
+ self.postMessage(payload);
98
+ }
99
+ else {
100
+ window.parent.postMessage(payload, '*');
101
+ }
102
+ }
103
+ catch (e) {
104
+ console.error('[PluginController] Error sending hello:', e);
105
+ }
106
+ }
107
+ getQueryParam(key) {
108
+ return this.queryParams[key] || null;
109
+ }
110
+ getClient() {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ // Return cached client if valid
113
+ if (this.supabase && this.rimoriInfo && this.rimoriInfo.expiration > new Date()) {
114
+ return { supabase: this.supabase, info: this.rimoriInfo };
115
+ }
116
+ // If MessageChannel is not ready yet, queue the request
117
+ if (!this.isMessageChannelReady) {
118
+ return new Promise((resolve) => {
119
+ this.pendingRequests.push(() => __awaiter(this, void 0, void 0, function* () {
120
+ const result = yield this.getClient();
121
+ resolve(result);
122
+ }));
123
+ });
124
+ }
125
+ // If we have rimoriInfo from MessageChannel init, use it directly
126
+ if (this.rimoriInfo && this.supabase) {
127
+ return { supabase: this.supabase, info: this.rimoriInfo };
128
+ }
129
+ // Fallback: request from parent
130
+ if (!this.rimoriInfo) {
131
+ if (typeof WorkerGlobalScope !== 'undefined') {
132
+ // In worker context, send request via self.postMessage to WorkerHandler
133
+ const eventId = Math.floor(Math.random() * 1000000000);
134
+ const requestEvent = {
135
+ event: {
136
+ timestamp: new Date().toISOString(),
137
+ eventId,
138
+ sender: this.pluginId,
139
+ topic: 'global.supabase.requestAccess',
140
+ data: {},
141
+ debug: false,
142
+ },
143
+ };
144
+ return new Promise((resolve) => {
145
+ // Listen for the response
146
+ const originalOnMessage = self.onmessage;
147
+ self.onmessage = (event) => {
148
+ var _a, _b;
149
+ if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.topic) === 'global.supabase.requestAccess' && ((_b = event.data) === null || _b === void 0 ? void 0 : _b.eventId) === eventId) {
150
+ this.rimoriInfo = event.data.data;
151
+ this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
152
+ accessToken: () => Promise.resolve(this.getToken()),
153
+ });
154
+ self.onmessage = originalOnMessage; // Restore original handler
155
+ resolve({ supabase: this.supabase, info: this.rimoriInfo });
156
+ }
157
+ else if (originalOnMessage) {
158
+ originalOnMessage.call(self, event);
159
+ }
160
+ };
161
+ // Send the request
162
+ self.postMessage(requestEvent);
163
+ });
164
+ }
165
+ else {
166
+ // In main thread context, use EventBus
167
+ const { data } = yield EventBus.request(this.pluginId, 'global.supabase.requestAccess');
168
+ console.log({ data });
169
+ this.rimoriInfo = data;
170
+ this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
171
+ accessToken: () => Promise.resolve(this.getToken()),
172
+ });
173
+ }
174
+ }
175
+ return { supabase: this.supabase, info: this.rimoriInfo };
176
+ });
177
+ }
178
+ getToken() {
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ if (this.rimoriInfo && this.rimoriInfo.expiration && this.rimoriInfo.expiration > new Date()) {
181
+ return this.rimoriInfo.token;
182
+ }
183
+ // If we don't have rimoriInfo, request it
184
+ if (!this.rimoriInfo) {
185
+ const { data } = yield EventBus.request(this.pluginId, 'global.supabase.requestAccess');
186
+ this.rimoriInfo = data;
187
+ return this.rimoriInfo.token;
188
+ }
189
+ // If token is expired, request fresh access
190
+ const { data } = yield EventBus.request(this.pluginId, 'global.supabase.requestAccess');
191
+ this.rimoriInfo.token = data.token;
192
+ this.rimoriInfo.expiration = data.expiration;
193
+ return this.rimoriInfo.token;
194
+ });
195
+ }
196
+ /**
197
+ * Gets the Supabase URL.
198
+ * @returns The Supabase URL.
199
+ * @deprecated All endpoints should use the backend URL instead.
200
+ */
201
+ getSupabaseUrl() {
202
+ if (!this.rimoriInfo) {
203
+ throw new Error('Supabase info not found');
204
+ }
205
+ return this.rimoriInfo.url;
206
+ }
207
+ getBackendUrl() {
208
+ if (!this.rimoriInfo) {
209
+ throw new Error('Rimori info not found');
210
+ }
211
+ return this.rimoriInfo.backendUrl;
212
+ }
213
+ getGlobalEventTopic(preliminaryTopic) {
214
+ var _a, _b;
215
+ if (preliminaryTopic.startsWith('global.')) {
216
+ return preliminaryTopic;
217
+ }
218
+ if (preliminaryTopic.startsWith('self.')) {
219
+ return preliminaryTopic;
220
+ }
221
+ const topicParts = preliminaryTopic.split('.');
222
+ if (topicParts.length === 3) {
223
+ if (!topicParts[0].startsWith('pl') && topicParts[0] !== 'global') {
224
+ throw new Error("The event topic must start with the plugin id or 'global'.");
225
+ }
226
+ return preliminaryTopic;
227
+ }
228
+ else if (topicParts.length > 3) {
229
+ throw new Error(`The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`);
230
+ }
231
+ const topicRoot = (_b = (_a = this.rimoriInfo) === null || _a === void 0 ? void 0 : _a.pluginId) !== null && _b !== void 0 ? _b : 'global';
232
+ return `${topicRoot}.${preliminaryTopic}`;
233
+ }
234
+ }
@@ -0,0 +1,73 @@
1
+ import { RimoriClient } from './RimoriClient';
2
+ /**
3
+ * Singleton Logger class for Rimori client plugins.
4
+ * Handles all logging levels, production filtering, and log transmission to Rimori.
5
+ * Overrides console methods globally for seamless integration.
6
+ */
7
+ export declare class Logger {
8
+ private static instance;
9
+ private isProduction;
10
+ private logs;
11
+ private logIdCounter;
12
+ private originalConsole;
13
+ private mousePosition;
14
+ private constructor();
15
+ /**
16
+ * Initialize the Logger singleton and override console methods globally.
17
+ * @param rimori - Rimori client instance
18
+ * @param isProduction - Whether the environment is production
19
+ * @returns Logger instance
20
+ */
21
+ static getInstance(rimori: RimoriClient, isProduction?: boolean): Logger;
22
+ private validateIsProduction;
23
+ /**
24
+ * Expose log access to global scope for DevTools console access.
25
+ */
26
+ private exposeToDevTools;
27
+ /**
28
+ * Set up navigation event listeners to clear logs on page changes.
29
+ */
30
+ private setupNavigationClearing;
31
+ /**
32
+ * Override console methods globally to capture all console calls.
33
+ */
34
+ private overrideConsoleMethods;
35
+ /**
36
+ * Get caller information from stack trace.
37
+ * @returns Object with location string and CSS style, or empty values for production
38
+ */
39
+ private getCallerLocation;
40
+ /**
41
+ * Track mouse position for screenshot context.
42
+ */
43
+ private trackMousePosition;
44
+ /**
45
+ * Handle console method calls and create log entries.
46
+ * @param level - Log level
47
+ * @param args - Console arguments
48
+ */
49
+ private handleConsoleCall;
50
+ /**
51
+ * Get browser and system information for debugging.
52
+ * @returns Object with browser and system information
53
+ */
54
+ private getBrowserInfo;
55
+ /**
56
+ * Capture a screenshot of the current page.
57
+ * @returns Promise resolving to base64 screenshot or null if failed
58
+ */
59
+ private captureScreenshot;
60
+ /**
61
+ * Create a log entry with context information.
62
+ * @param level - Log level
63
+ * @param message - Log message
64
+ * @param data - Additional data
65
+ * @returns Log entry
66
+ */
67
+ private createLogEntry;
68
+ /**
69
+ * Add a log entry to the internal log array.
70
+ * @param entry - Log entry to add
71
+ */
72
+ private addLogEntry;
73
+ }
@@ -0,0 +1,308 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import html2canvas from 'html2canvas';
11
+ /**
12
+ * Singleton Logger class for Rimori client plugins.
13
+ * Handles all logging levels, production filtering, and log transmission to Rimori.
14
+ * Overrides console methods globally for seamless integration.
15
+ */
16
+ export class Logger {
17
+ constructor(rimori, isProduction) {
18
+ this.logs = [];
19
+ this.logIdCounter = 0;
20
+ this.mousePosition = null;
21
+ this.isProduction = this.validateIsProduction(isProduction);
22
+ // Store original console methods
23
+ this.originalConsole = {
24
+ log: console.log,
25
+ info: console.info,
26
+ warn: console.warn,
27
+ error: console.error,
28
+ debug: console.debug,
29
+ };
30
+ // Override console methods globally
31
+ this.overrideConsoleMethods();
32
+ // Track mouse position
33
+ this.trackMousePosition();
34
+ // Expose logs to global scope for DevTools access
35
+ this.exposeToDevTools();
36
+ // Set up navigation clearing
37
+ this.setupNavigationClearing();
38
+ rimori.event.respond('logging.requestPluginLogs', () => __awaiter(this, void 0, void 0, function* () {
39
+ this.addLogEntry(yield this.createLogEntry('info', 'Screenshot capture', undefined, true));
40
+ const logs = {
41
+ logs: this.logs,
42
+ pluginId: rimori.plugin.pluginId,
43
+ timestamp: new Date().toISOString(),
44
+ };
45
+ this.logs = [];
46
+ this.logIdCounter = 0;
47
+ return logs;
48
+ }));
49
+ }
50
+ /**
51
+ * Initialize the Logger singleton and override console methods globally.
52
+ * @param rimori - Rimori client instance
53
+ * @param isProduction - Whether the environment is production
54
+ * @returns Logger instance
55
+ */
56
+ static getInstance(rimori, isProduction) {
57
+ if (!Logger.instance) {
58
+ Logger.instance = new Logger(rimori, isProduction);
59
+ }
60
+ return Logger.instance;
61
+ }
62
+ validateIsProduction(isProduction) {
63
+ if (isProduction !== undefined) {
64
+ return isProduction;
65
+ }
66
+ if (typeof window !== 'undefined' && window.location.href) {
67
+ return !window.location.href.includes('localhost');
68
+ }
69
+ return true;
70
+ }
71
+ /**
72
+ * Expose log access to global scope for DevTools console access.
73
+ */
74
+ exposeToDevTools() {
75
+ if (typeof window !== 'undefined') {
76
+ // Expose a global function to access logs from DevTools console
77
+ window.getRimoriLogs = () => this.logs;
78
+ }
79
+ }
80
+ /**
81
+ * Set up navigation event listeners to clear logs on page changes.
82
+ */
83
+ setupNavigationClearing() {
84
+ if (typeof window === 'undefined' || typeof history === 'undefined')
85
+ return;
86
+ // Clear logs on browser back/forward
87
+ window.addEventListener('popstate', () => (this.logs = []));
88
+ // Override history methods to clear logs on programmatic navigation
89
+ const originalPushState = history.pushState;
90
+ const originalReplaceState = history.replaceState;
91
+ history.pushState = (...args) => {
92
+ originalPushState.apply(history, args);
93
+ this.logs = [];
94
+ };
95
+ history.replaceState = (...args) => {
96
+ originalReplaceState.apply(history, args);
97
+ this.logs = [];
98
+ };
99
+ // Listen for URL changes (works with React Router and other SPAs)
100
+ let currentUrl = window.location.href;
101
+ const checkUrlChange = () => {
102
+ if (window.location.href !== currentUrl) {
103
+ currentUrl = window.location.href;
104
+ this.logs = [];
105
+ }
106
+ };
107
+ // Check for URL changes periodically
108
+ setInterval(checkUrlChange, 100);
109
+ // Also listen for hash changes (for hash-based routing)
110
+ window.addEventListener('hashchange', () => (this.logs = []));
111
+ }
112
+ /**
113
+ * Override console methods globally to capture all console calls.
114
+ */
115
+ overrideConsoleMethods() {
116
+ // Override console.log
117
+ console.log = (...args) => {
118
+ const { location, style } = this.getCallerLocation();
119
+ this.originalConsole.log(location, style, ...args);
120
+ this.handleConsoleCall('info', args);
121
+ };
122
+ // Override console.info
123
+ console.info = (...args) => {
124
+ const { location, style } = this.getCallerLocation();
125
+ this.originalConsole.info(location, style, ...args);
126
+ this.handleConsoleCall('info', args);
127
+ };
128
+ // Override console.warn
129
+ console.warn = (...args) => {
130
+ const { location, style } = this.getCallerLocation();
131
+ this.originalConsole.warn(location, style, ...args);
132
+ this.handleConsoleCall('warn', args);
133
+ };
134
+ // Override console.error
135
+ console.error = (...args) => {
136
+ const { location, style } = this.getCallerLocation();
137
+ this.originalConsole.error(location, style, ...args);
138
+ this.handleConsoleCall('error', args);
139
+ };
140
+ // Override console.debug
141
+ console.debug = (...args) => {
142
+ const { location, style } = this.getCallerLocation();
143
+ this.originalConsole.debug(location, style, ...args);
144
+ this.handleConsoleCall('debug', args);
145
+ };
146
+ }
147
+ /**
148
+ * Get caller information from stack trace.
149
+ * @returns Object with location string and CSS style, or empty values for production
150
+ */
151
+ getCallerLocation() {
152
+ const emptyResult = { location: '', style: '' };
153
+ const style = 'color: #0063A2; font-weight: bold;';
154
+ if (this.isProduction)
155
+ return emptyResult;
156
+ try {
157
+ const stack = new Error().stack;
158
+ if (!stack)
159
+ return emptyResult;
160
+ const stackLines = stack.split('\n');
161
+ // Skip the first 3 lines: Error, getCallerLocation, overrideConsoleMethods wrapper
162
+ const callerLine = stackLines[3];
163
+ if (!callerLine)
164
+ return emptyResult;
165
+ // Extract file name and line number from stack trace
166
+ // Format: "at functionName (file:line:column)" or "at file:line:column"
167
+ const match = callerLine.match(/(?:at\s+.*?\s+\()?([^/\\(]+\.(?:ts|tsx|js|jsx)):(\d+):(\d+)\)?/);
168
+ if (match) {
169
+ const [, fileName, lineNumber] = match;
170
+ return { style, location: `%c[${fileName}:${lineNumber}]` };
171
+ }
172
+ // Fallback: try to extract just the file name
173
+ const simpleMatch = callerLine.match(/([^/\\]+\.(?:ts|tsx|js|jsx))/);
174
+ if (simpleMatch) {
175
+ return { style, location: `%c[${simpleMatch[1]}]` };
176
+ }
177
+ return emptyResult;
178
+ }
179
+ catch (error) {
180
+ return emptyResult;
181
+ }
182
+ }
183
+ /**
184
+ * Track mouse position for screenshot context.
185
+ */
186
+ trackMousePosition() {
187
+ if (typeof window !== 'undefined') {
188
+ const updateMousePosition = (event) => {
189
+ this.mousePosition = {
190
+ x: event.clientX,
191
+ y: event.clientY,
192
+ timestamp: new Date().toISOString(),
193
+ };
194
+ };
195
+ window.addEventListener('mousemove', updateMousePosition);
196
+ window.addEventListener('click', updateMousePosition);
197
+ }
198
+ }
199
+ /**
200
+ * Handle console method calls and create log entries.
201
+ * @param level - Log level
202
+ * @param args - Console arguments
203
+ */
204
+ handleConsoleCall(level, args) {
205
+ return __awaiter(this, void 0, void 0, function* () {
206
+ // Skip if this is a production log that shouldn't be stored
207
+ if (this.isProduction && (level === 'debug' || level === 'info')) {
208
+ return;
209
+ }
210
+ // Convert console arguments to message and data
211
+ const message = args
212
+ .map((arg) => {
213
+ if (typeof arg !== 'object')
214
+ return arg;
215
+ try {
216
+ return JSON.stringify(arg);
217
+ }
218
+ catch (error) {
219
+ return 'Error adding object to log: ' + error.message + ' ' + String(arg);
220
+ }
221
+ })
222
+ .join(' ');
223
+ const data = args.length > 1 ? args.slice(1) : undefined;
224
+ const entry = yield this.createLogEntry(level, message, data);
225
+ this.addLogEntry(entry);
226
+ });
227
+ }
228
+ /**
229
+ * Get browser and system information for debugging.
230
+ * @returns Object with browser and system information
231
+ */
232
+ getBrowserInfo() {
233
+ return {
234
+ userAgent: navigator.userAgent,
235
+ language: navigator.language,
236
+ cookieEnabled: navigator.cookieEnabled,
237
+ onLine: navigator.onLine,
238
+ screenResolution: `${screen.width}x${screen.height}`,
239
+ windowSize: `${window.innerWidth}x${window.innerHeight}`,
240
+ timestamp: new Date().toISOString(),
241
+ };
242
+ }
243
+ /**
244
+ * Capture a screenshot of the current page.
245
+ * @returns Promise resolving to base64 screenshot or null if failed
246
+ */
247
+ captureScreenshot() {
248
+ return __awaiter(this, void 0, void 0, function* () {
249
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
250
+ const canvas = yield html2canvas(document.body);
251
+ const screenshot = canvas.toDataURL('image/png');
252
+ // this.originalConsole.log("screenshot captured", screenshot)
253
+ return screenshot;
254
+ }
255
+ return null;
256
+ });
257
+ }
258
+ /**
259
+ * Create a log entry with context information.
260
+ * @param level - Log level
261
+ * @param message - Log message
262
+ * @param data - Additional data
263
+ * @returns Log entry
264
+ */
265
+ createLogEntry(level, message, data, forceScreenshot) {
266
+ return __awaiter(this, void 0, void 0, function* () {
267
+ const context = {};
268
+ // Add URL if available
269
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
270
+ return {
271
+ id: `log_${++this.logIdCounter}_${Date.now()}`,
272
+ timestamp: new Date().toISOString(),
273
+ level,
274
+ message,
275
+ data,
276
+ };
277
+ }
278
+ context.url = window.location.href;
279
+ // Add browser info (this method now handles worker context internally)
280
+ context.browserInfo = this.getBrowserInfo();
281
+ context.userAgent = context.browserInfo.userAgent;
282
+ // Add screenshot and mouse position if level is error or warn
283
+ if (level === 'error' || level === 'warn' || forceScreenshot) {
284
+ context.screenshot = (yield this.captureScreenshot()) || undefined;
285
+ context.mousePosition = this.mousePosition || undefined;
286
+ }
287
+ return {
288
+ id: `log_${++this.logIdCounter}_${Date.now()}`,
289
+ timestamp: new Date().toISOString(),
290
+ level,
291
+ message,
292
+ data,
293
+ context: context,
294
+ };
295
+ });
296
+ }
297
+ /**
298
+ * Add a log entry to the internal log array.
299
+ * @param entry - Log entry to add
300
+ */
301
+ addLogEntry(entry) {
302
+ this.logs.push(entry);
303
+ // Maintain log size limit (1000 entries)
304
+ if (this.logs.length > 1000) {
305
+ this.logs = this.logs.slice(-1000);
306
+ }
307
+ }
308
+ }