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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) 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/controller/SettingsController.d.ts +1 -1
  4. package/dist/controller/SharedContentController.d.ts +1 -1
  5. package/dist/plugin/CommunicationHandler.d.ts +3 -1
  6. package/dist/plugin/CommunicationHandler.js +15 -13
  7. package/dist/plugin/StandaloneClient.d.ts +1 -1
  8. package/dist/plugin/StandaloneClient.js +3 -2
  9. package/dist/plugin/module/DbModule.d.ts +4 -4
  10. package/dist/plugin/module/EventModule.d.ts +1 -1
  11. package/dist/plugin/module/EventModule.js +1 -1
  12. package/dist/plugin/module/ExerciseModule.d.ts +1 -1
  13. package/dist/plugin/module/PluginModule.d.ts +1 -1
  14. package/package.json +8 -3
  15. package/.github/workflows/create-release-branch.yml +0 -226
  16. package/.github/workflows/pre-release.yml +0 -126
  17. package/.github/workflows/release-on-merge.yml +0 -195
  18. package/.prettierignore +0 -35
  19. package/eslint.config.js +0 -53
  20. package/example/docs/devdocs.md +0 -241
  21. package/example/docs/overview.md +0 -29
  22. package/example/docs/userdocs.md +0 -126
  23. package/example/rimori.config.ts +0 -91
  24. package/example/worker/vite.config.ts +0 -26
  25. package/example/worker/worker.ts +0 -11
  26. package/prettier.config.js +0 -8
  27. package/src/cli/scripts/init/dev-registration.ts +0 -189
  28. package/src/cli/scripts/init/env-setup.ts +0 -44
  29. package/src/cli/scripts/init/file-operations.ts +0 -58
  30. package/src/cli/scripts/init/html-cleaner.ts +0 -45
  31. package/src/cli/scripts/init/main.ts +0 -175
  32. package/src/cli/scripts/init/package-setup.ts +0 -113
  33. package/src/cli/scripts/init/router-transformer.ts +0 -332
  34. package/src/cli/scripts/init/tailwind-config.ts +0 -66
  35. package/src/cli/scripts/init/vite-config.ts +0 -73
  36. package/src/cli/scripts/release/detect-translation-languages.ts +0 -37
  37. package/src/cli/scripts/release/release-config-upload.ts +0 -119
  38. package/src/cli/scripts/release/release-db-update.ts +0 -97
  39. package/src/cli/scripts/release/release-file-upload.ts +0 -138
  40. package/src/cli/scripts/release/release.ts +0 -85
  41. package/src/cli/types/DatabaseTypes.ts +0 -125
  42. package/src/controller/AIController.ts +0 -295
  43. package/src/controller/AccomplishmentController.ts +0 -188
  44. package/src/controller/AudioController.ts +0 -64
  45. package/src/controller/ObjectController.ts +0 -120
  46. package/src/controller/SettingsController.ts +0 -186
  47. package/src/controller/SharedContentController.ts +0 -365
  48. package/src/controller/TranslationController.ts +0 -136
  49. package/src/controller/VoiceController.ts +0 -33
  50. package/src/fromRimori/EventBus.ts +0 -382
  51. package/src/fromRimori/PluginTypes.ts +0 -214
  52. package/src/fromRimori/readme.md +0 -2
  53. package/src/index.ts +0 -19
  54. package/src/plugin/CommunicationHandler.ts +0 -287
  55. package/src/plugin/Logger.ts +0 -394
  56. package/src/plugin/RimoriClient.ts +0 -199
  57. package/src/plugin/StandaloneClient.ts +0 -125
  58. package/src/plugin/module/AIModule.ts +0 -77
  59. package/src/plugin/module/DbModule.ts +0 -66
  60. package/src/plugin/module/EventModule.ts +0 -192
  61. package/src/plugin/module/ExerciseModule.ts +0 -131
  62. package/src/plugin/module/PluginModule.ts +0 -114
  63. package/src/utils/difficultyConverter.ts +0 -15
  64. package/src/utils/endpoint.ts +0 -3
  65. package/src/worker/WorkerSetup.ts +0 -35
  66. package/tsconfig.json +0 -17
@@ -1,287 +0,0 @@
1
- import { createClient, SupabaseClient } from '@supabase/supabase-js';
2
- import { UserInfo } from '../controller/SettingsController';
3
- import { EventBus, EventBusMessage } from '../fromRimori/EventBus';
4
- import { ActivePlugin, Plugin } from '../fromRimori/PluginTypes';
5
-
6
- // Add declaration for WorkerGlobalScope
7
- declare const WorkerGlobalScope: any;
8
-
9
- export interface Guild {
10
- allowUserPluginSettings: boolean;
11
- city: string | null;
12
- country: string | null;
13
- description: string | null;
14
- id: string;
15
- isPublic: boolean;
16
- name: string;
17
- ownerId: string;
18
- primaryLanguage: string;
19
- scope: string;
20
- }
21
-
22
- export interface RimoriInfo {
23
- url: string;
24
- key: string;
25
- backendUrl: string;
26
- token: string;
27
- expiration: Date;
28
- tablePrefix: string;
29
- pluginId: string;
30
- guild: Guild;
31
- installedPlugins: Plugin[];
32
- profile: UserInfo;
33
- mainPanelPlugin?: ActivePlugin;
34
- sidePanelPlugin?: ActivePlugin;
35
- interfaceLanguage: string;
36
- /**
37
- * The release channel of the plugin installation.
38
- */
39
- releaseChannel: 'alpha' | 'beta' | 'stable';
40
- /**
41
- * The database schema to use for plugin tables.
42
- * Determined by rimori-main based on release channel:
43
- * - 'plugins_alpha' for alpha release channel
44
- * - 'plugins' for beta and stable release channels
45
- */
46
- dbSchema: 'plugins' | 'plugins_alpha';
47
- }
48
-
49
- export class RimoriCommunicationHandler {
50
- private port: MessagePort | null = null;
51
- private queryParams: Record<string, string> = {};
52
- private supabase: SupabaseClient | null = null;
53
- private rimoriInfo: RimoriInfo | null = null;
54
- private pluginId: string;
55
- private isMessageChannelReady = false;
56
- private pendingRequests: Array<() => void> = [];
57
- private updateCallbacks: Set<(info: RimoriInfo) => void> = new Set();
58
-
59
- public constructor(pluginId: string, standalone: boolean) {
60
- this.pluginId = pluginId;
61
- this.getClient = this.getClient.bind(this);
62
-
63
- //no need to forward messages to parent in standalone mode or worker context
64
- if (standalone) return;
65
-
66
- this.initMessageChannel(typeof WorkerGlobalScope !== 'undefined');
67
- }
68
-
69
- private initMessageChannel(worker = false): void {
70
- const listener = (event: MessageEvent) => {
71
- // console.log('[PluginController] window message', { origin: event.origin, data: event.data });
72
- const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
73
- const [transferredPort] = event.ports || [];
74
-
75
- if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
76
- // console.log('[PluginController] message ignored (not init or wrong plugin)', {
77
- // type,
78
- // pluginId,
79
- // currentPluginId: this.pluginId,
80
- // hasPortProperty: !!transferredPort,
81
- // event
82
- // });
83
- return;
84
- }
85
-
86
- this.queryParams = queryParams || {};
87
- this.port = transferredPort;
88
-
89
- // Initialize Supabase client immediately with provided info
90
- if (rimoriInfo) {
91
- this.rimoriInfo = rimoriInfo;
92
- this.supabase = createClient(rimoriInfo.url, rimoriInfo.key, {
93
- accessToken: () => Promise.resolve(rimoriInfo.token),
94
- });
95
- }
96
-
97
- // Handle messages from parent
98
- this.port.onmessage = ({ data }) => {
99
- const { event, type, eventId, response, error } = data || {};
100
-
101
- // no idea why this is needed but it works for now
102
- if (type === 'response' && eventId) {
103
- EventBus.emit(this.pluginId, response.topic, response.data, eventId);
104
- } else if (type === 'error' && eventId) {
105
- EventBus.emit(this.pluginId, 'error', { error }, eventId);
106
- } else if (event) {
107
- const { topic, sender, data: eventData, eventId } = event as EventBusMessage;
108
- if (sender !== this.pluginId) {
109
- EventBus.emit(sender, topic, eventData, eventId);
110
- }
111
- }
112
- };
113
-
114
- // Set theme from MessageChannel query params
115
- if (!worker) {
116
- // const theme = this.queryParams['rm_theme'];
117
- // setTheme(theme);
118
- // console.log('TODO: set theme from MessageChannel query params');
119
- }
120
-
121
- // Forward plugin events to parent (only after MessageChannel is ready)
122
- EventBus.on('*', (ev) => {
123
- if (ev.sender === this.pluginId && !ev.topic.startsWith('self.')) {
124
- this.port?.postMessage({ event: ev });
125
- }
126
- });
127
-
128
- // Listen for updates from rimori-main (data changes, token refresh, etc.)
129
- // Topic format: {pluginId}.supabase.triggerUpdate
130
- EventBus.on(`${this.pluginId}.supabase.triggerUpdate`, (ev) => {
131
- console.log('[RimoriCommunicationHandler] Received update from rimori-main');
132
- this.handleRimoriInfoUpdate(ev.data as RimoriInfo);
133
- });
134
-
135
- // Mark MessageChannel as ready and process pending requests
136
- this.isMessageChannelReady = true;
137
-
138
- // Process any pending requests
139
- this.pendingRequests.forEach((request) => request());
140
- this.pendingRequests = [];
141
- };
142
- if (worker) {
143
- self.onmessage = listener;
144
- } else {
145
- window.addEventListener('message', listener);
146
- }
147
- this.sendHello(worker);
148
- EventBus.on('self.rimori.triggerInitFinished', () => {
149
- this.sendFinishedInit(worker);
150
- });
151
- }
152
-
153
- private sendHello(isWorker = false): void {
154
- try {
155
- const payload = { type: 'rimori:hello', pluginId: this.pluginId };
156
- if (isWorker) {
157
- self.postMessage(payload);
158
- } else {
159
- window.parent.postMessage(payload, '*');
160
- }
161
- } catch (e) {
162
- console.error('[PluginController] Error sending hello:', e);
163
- }
164
- }
165
-
166
- private sendFinishedInit(isWorker = false): void {
167
- try {
168
- const payload = { type: 'rimori:acknowledged', pluginId: this.pluginId };
169
- if (isWorker) {
170
- self.postMessage(payload);
171
- } else {
172
- window.parent.postMessage(payload, '*');
173
- }
174
- } catch (e) {
175
- console.error('[PluginController] Error sending finished init:', e);
176
- }
177
- }
178
-
179
- public getQueryParam(key: string): string | null {
180
- return this.queryParams[key] || null;
181
- }
182
-
183
- public async getClient(): Promise<{ supabase: SupabaseClient; info: RimoriInfo }> {
184
- // Return cached client if valid
185
- if (this.supabase && this.rimoriInfo && this.rimoriInfo.expiration > new Date()) {
186
- return { supabase: this.supabase, info: this.rimoriInfo };
187
- }
188
-
189
- // If MessageChannel is not ready yet, queue the request
190
- if (!this.isMessageChannelReady) {
191
- return new Promise<{ supabase: SupabaseClient; info: RimoriInfo }>((resolve) => {
192
- this.pendingRequests.push(async () => {
193
- const result = await this.getClient();
194
- resolve(result);
195
- });
196
- });
197
- }
198
-
199
- // If we have rimoriInfo from MessageChannel init, use it directly
200
- if (this.rimoriInfo && this.supabase) {
201
- return { supabase: this.supabase, info: this.rimoriInfo };
202
- }
203
-
204
- // Fallback: request from parent
205
- if (!this.rimoriInfo) {
206
- if (typeof WorkerGlobalScope !== 'undefined') {
207
- // In worker context, send request via self.postMessage to WorkerHandler
208
- const eventId = Math.floor(Math.random() * 1000000000);
209
- const requestEvent = {
210
- event: {
211
- timestamp: new Date().toISOString(),
212
- eventId,
213
- sender: this.pluginId,
214
- topic: 'global.supabase.requestAccess',
215
- data: {},
216
- debug: false,
217
- },
218
- };
219
-
220
- return new Promise<{ supabase: SupabaseClient; info: RimoriInfo }>((resolve) => {
221
- // Listen for the response
222
- const originalOnMessage = self.onmessage;
223
- self.onmessage = (event) => {
224
- if (event.data?.topic === 'global.supabase.requestAccess' && event.data?.eventId === eventId) {
225
- this.rimoriInfo = event.data.data;
226
- this.supabase = createClient(this.rimoriInfo!.url, this.rimoriInfo!.key, {
227
- accessToken: () => Promise.resolve(this.rimoriInfo!.token),
228
- });
229
- self.onmessage = originalOnMessage; // Restore original handler
230
- resolve({ supabase: this.supabase, info: this.rimoriInfo! });
231
- } else if (originalOnMessage) {
232
- originalOnMessage.call(self, event);
233
- }
234
- };
235
-
236
- // Send the request
237
- self.postMessage(requestEvent);
238
- });
239
- } else {
240
- // In main thread context, use EventBus
241
- const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
242
- // console.log({ data });
243
- this.rimoriInfo = data;
244
- this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
245
- accessToken: () => Promise.resolve(this.rimoriInfo!.token),
246
- });
247
- }
248
- }
249
-
250
- return { supabase: this.supabase!, info: this.rimoriInfo };
251
- }
252
-
253
- /**
254
- * Handles updates to RimoriInfo from rimori-main.
255
- * Updates the cached info and Supabase client, then notifies all registered callbacks.
256
- */
257
- private handleRimoriInfoUpdate(newInfo: RimoriInfo): void {
258
- // Update cached rimoriInfo
259
- this.rimoriInfo = newInfo;
260
-
261
- // Update Supabase client with new token
262
- this.supabase = createClient(newInfo.url, newInfo.key, {
263
- accessToken: () => Promise.resolve(newInfo.token),
264
- });
265
-
266
- // Notify all registered callbacks
267
- this.updateCallbacks.forEach((callback) => {
268
- try {
269
- callback(newInfo);
270
- } catch (error) {
271
- console.error('[RimoriCommunicationHandler] Error in update callback:', error);
272
- }
273
- });
274
- }
275
-
276
- /**
277
- * Registers a callback to be called when RimoriInfo is updated.
278
- * @param callback - Function to call with the new RimoriInfo
279
- * @returns Cleanup function to unregister the callback
280
- */
281
- public onUpdate(callback: (info: RimoriInfo) => void): () => void {
282
- this.updateCallbacks.add(callback);
283
- return () => {
284
- this.updateCallbacks.delete(callback);
285
- };
286
- }
287
- }
@@ -1,394 +0,0 @@
1
- import { RimoriClient } from './RimoriClient';
2
-
3
- type LogLevel = 'debug' | 'info' | 'warn' | 'error';
4
-
5
- interface LogEntry {
6
- id: string;
7
- timestamp: string;
8
- level: LogLevel;
9
- message: string;
10
- data?: any;
11
- context?: {
12
- url: string;
13
- userAgent: string;
14
- browserInfo: BrowserInfo;
15
- screenshot?: string;
16
- mousePosition?: MousePosition;
17
- };
18
- }
19
-
20
- interface BrowserInfo {
21
- userAgent: string;
22
- language: string;
23
- cookieEnabled: boolean;
24
- onLine: boolean;
25
- screenResolution: string;
26
- windowSize: string;
27
- timestamp: string;
28
- }
29
-
30
- interface MousePosition {
31
- x: number;
32
- y: number;
33
- timestamp: string;
34
- }
35
-
36
- /**
37
- * Singleton Logger class for Rimori client plugins.
38
- * Handles all logging levels, production filtering, and log transmission to Rimori.
39
- * Overrides console methods globally for seamless integration.
40
- */
41
- export class Logger {
42
- private static instance: Logger;
43
- private isProduction: boolean;
44
- private logs: LogEntry[] = [];
45
- private logIdCounter = 0;
46
- private originalConsole: {
47
- log: typeof console.log;
48
- info: typeof console.info;
49
- warn: typeof console.warn;
50
- error: typeof console.error;
51
- debug: typeof console.debug;
52
- };
53
- private mousePosition: MousePosition | null = null;
54
-
55
- private constructor(rimori: RimoriClient, isProduction?: boolean) {
56
- this.isProduction = this.validateIsProduction(isProduction);
57
-
58
- // Store original console methods
59
- this.originalConsole = {
60
- log: console.log,
61
- info: console.info,
62
- warn: console.warn,
63
- error: console.error,
64
- debug: console.debug,
65
- };
66
-
67
- // Override console methods globally
68
- this.overrideConsoleMethods();
69
-
70
- // Track mouse position
71
- this.trackMousePosition();
72
-
73
- // Expose logs to global scope for DevTools access
74
- this.exposeToDevTools();
75
-
76
- // Set up navigation clearing
77
- this.setupNavigationClearing();
78
-
79
- rimori.event.respond('logging.requestPluginLogs', async () => {
80
- this.addLogEntry(await this.createLogEntry('info', 'Screenshot capture', undefined, true));
81
- const logs = {
82
- logs: this.logs,
83
- pluginId: rimori.plugin.pluginId,
84
- timestamp: new Date().toISOString(),
85
- };
86
- this.logs = [];
87
- this.logIdCounter = 0;
88
- return logs;
89
- });
90
- }
91
-
92
- /**
93
- * Initialize the Logger singleton and override console methods globally.
94
- * @param rimori - Rimori client instance
95
- * @param isProduction - Whether the environment is production
96
- * @returns Logger instance
97
- */
98
- public static getInstance(rimori: RimoriClient, isProduction?: boolean): Logger {
99
- if (!Logger.instance) {
100
- Logger.instance = new Logger(rimori, isProduction);
101
- }
102
- return Logger.instance;
103
- }
104
-
105
- private validateIsProduction(isProduction?: boolean): boolean {
106
- if (isProduction !== undefined) {
107
- return isProduction;
108
- }
109
- if (typeof window !== 'undefined' && window.location.href) {
110
- return !window.location.href.includes('localhost');
111
- }
112
- return true;
113
- }
114
- /**
115
- * Expose log access to global scope for DevTools console access.
116
- */
117
- private exposeToDevTools(): void {
118
- if (typeof window !== 'undefined') {
119
- // Expose a global function to access logs from DevTools console
120
- (window as any).getRimoriLogs = () => this.logs;
121
- }
122
- }
123
-
124
- /**
125
- * Set up navigation event listeners to clear logs on page changes.
126
- */
127
- private setupNavigationClearing(): void {
128
- if (typeof window === 'undefined' || typeof history === 'undefined') return;
129
-
130
- // Clear logs on browser back/forward
131
- window.addEventListener('popstate', () => (this.logs = []));
132
-
133
- // Override history methods to clear logs on programmatic navigation
134
- const originalPushState = history.pushState;
135
- const originalReplaceState = history.replaceState;
136
-
137
- history.pushState = (...args) => {
138
- originalPushState.apply(history, args);
139
- this.logs = [];
140
- };
141
-
142
- history.replaceState = (...args) => {
143
- originalReplaceState.apply(history, args);
144
- this.logs = [];
145
- };
146
-
147
- // Listen for URL changes (works with React Router and other SPAs)
148
- let currentUrl = window.location.href;
149
- const checkUrlChange = () => {
150
- if (window.location.href !== currentUrl) {
151
- currentUrl = window.location.href;
152
- this.logs = [];
153
- }
154
- };
155
-
156
- // Check for URL changes periodically
157
- setInterval(checkUrlChange, 100);
158
-
159
- // Also listen for hash changes (for hash-based routing)
160
- window.addEventListener('hashchange', () => (this.logs = []));
161
- }
162
-
163
- /**
164
- * Override console methods globally to capture all console calls.
165
- */
166
- private overrideConsoleMethods(): void {
167
- // Override console.log
168
- console.log = (...args: any[]) => {
169
- const { location, style } = this.getCallerLocation();
170
- this.originalConsole.log(location, style, ...args);
171
- this.handleConsoleCall('info', args);
172
- };
173
-
174
- // Override console.info
175
- console.info = (...args: any[]) => {
176
- const { location, style } = this.getCallerLocation();
177
- this.originalConsole.info(location, style, ...args);
178
- this.handleConsoleCall('info', args);
179
- };
180
-
181
- // Override console.warn
182
- console.warn = (...args: any[]) => {
183
- const { location, style } = this.getCallerLocation();
184
- this.originalConsole.warn(location, style, ...args);
185
- this.handleConsoleCall('warn', args);
186
- };
187
-
188
- // Override console.error
189
- console.error = (...args: any[]) => {
190
- const { location, style } = this.getCallerLocation();
191
- this.originalConsole.error(location, style, ...args);
192
- this.handleConsoleCall('error', args);
193
- };
194
-
195
- // Override console.debug
196
- console.debug = (...args: any[]) => {
197
- const { location, style } = this.getCallerLocation();
198
- this.originalConsole.debug(location, style, ...args);
199
- this.handleConsoleCall('debug', args);
200
- };
201
- }
202
-
203
- /**
204
- * Get caller information from stack trace.
205
- * @returns Object with location string and CSS style, or empty values for production
206
- */
207
- private getCallerLocation(): { location: string; style: string } {
208
- const emptyResult = { location: '', style: '' };
209
- const style = 'color: #0063A2; font-weight: bold;';
210
-
211
- if (this.isProduction) return emptyResult;
212
-
213
- try {
214
- const stack = new Error().stack;
215
- if (!stack) return emptyResult;
216
-
217
- const stackLines = stack.split('\n');
218
- // Skip the first 3 lines: Error, getCallerLocation, overrideConsoleMethods wrapper
219
- const callerLine = stackLines[3];
220
-
221
- if (!callerLine) return emptyResult;
222
-
223
- // Extract file name and line number from stack trace
224
- // Format: "at functionName (file:line:column)" or "at file:line:column"
225
- const match = callerLine.match(/(?:at\s+.*?\s+\()?([^/\\(]+\.(?:ts|tsx|js|jsx)):(\d+):(\d+)\)?/);
226
-
227
- if (match) {
228
- const [, fileName, lineNumber] = match;
229
- return { style, location: `%c[${fileName}:${lineNumber}]` };
230
- }
231
-
232
- // Fallback: try to extract just the file name
233
- const simpleMatch = callerLine.match(/([^/\\]+\.(?:ts|tsx|js|jsx))/);
234
- if (simpleMatch) {
235
- return { style, location: `%c[${simpleMatch[1]}]` };
236
- }
237
-
238
- return emptyResult;
239
- } catch (error) {
240
- return emptyResult;
241
- }
242
- }
243
-
244
- /**
245
- * Track mouse position for screenshot context.
246
- */
247
- private trackMousePosition(): void {
248
- if (typeof window !== 'undefined') {
249
- const updateMousePosition = (event: MouseEvent) => {
250
- this.mousePosition = {
251
- x: event.clientX,
252
- y: event.clientY,
253
- timestamp: new Date().toISOString(),
254
- };
255
- };
256
-
257
- window.addEventListener('mousemove', updateMousePosition);
258
- window.addEventListener('click', updateMousePosition);
259
- }
260
- }
261
-
262
- /**
263
- * Handle console method calls and create log entries.
264
- * @param level - Log level
265
- * @param args - Console arguments
266
- */
267
- private async handleConsoleCall(level: LogLevel, args: any[]): Promise<void> {
268
- // Skip if this is a production log that shouldn't be stored
269
- if (this.isProduction && (level === 'debug' || level === 'info')) {
270
- return;
271
- }
272
-
273
- // Convert console arguments to message and data
274
- const message = args
275
- .map((arg) => {
276
- if (typeof arg !== 'object') return arg;
277
- try {
278
- return JSON.stringify(arg);
279
- } catch (error: any) {
280
- return 'Error adding object to log: ' + error.message + ' ' + String(arg);
281
- }
282
- })
283
- .join(' ');
284
-
285
- const data = args.length > 1 ? args.slice(1) : undefined;
286
-
287
- const entry = await this.createLogEntry(level, message, data);
288
- this.addLogEntry(entry);
289
- }
290
-
291
- /**
292
- * Get browser and system information for debugging.
293
- * @returns Object with browser and system information
294
- */
295
- private getBrowserInfo(): BrowserInfo {
296
- return {
297
- userAgent: navigator.userAgent,
298
- language: navigator.language,
299
- cookieEnabled: navigator.cookieEnabled,
300
- onLine: navigator.onLine,
301
- screenResolution: `${screen.width}x${screen.height}`,
302
- windowSize: `${window.innerWidth}x${window.innerHeight}`,
303
- timestamp: new Date().toISOString(),
304
- };
305
- }
306
-
307
- /**
308
- * Capture a screenshot of the current page.
309
- * Dynamically imports html2canvas only in browser environments.
310
- * @returns Promise resolving to base64 screenshot or null if failed
311
- */
312
- private async captureScreenshot(): Promise<string | null> {
313
- // Only attempt to capture screenshot in browser environments
314
- if (typeof window === 'undefined' || typeof document === 'undefined') {
315
- return null;
316
- }
317
-
318
- try {
319
- // Dynamically import html2canvas only when window is available
320
- // html2canvas is an optional peer dependency - provided by @rimori/react-client
321
- // In worker builds, this import should be marked as external to prevent bundling
322
- const html2canvas = (await import('html2canvas')).default;
323
- const canvas = await html2canvas(document.body);
324
- const screenshot = canvas.toDataURL('image/png');
325
- // this.originalConsole.log("screenshot captured", screenshot)
326
- return screenshot;
327
- } catch (error) {
328
- // html2canvas may not be available (e.g., in workers or when not installed)
329
- // Silently fail to avoid breaking logging functionality
330
- return null;
331
- }
332
- }
333
-
334
- /**
335
- * Create a log entry with context information.
336
- * @param level - Log level
337
- * @param message - Log message
338
- * @param data - Additional data
339
- * @returns Log entry
340
- */
341
- private async createLogEntry(
342
- level: LogLevel,
343
- message: string,
344
- data?: any,
345
- forceScreenshot?: boolean,
346
- ): Promise<LogEntry> {
347
- const context: Partial<LogEntry['context']> = {};
348
-
349
- // Add URL if available
350
- if (typeof window === 'undefined' || typeof document === 'undefined') {
351
- return {
352
- id: `log_${++this.logIdCounter}_${Date.now()}`,
353
- timestamp: new Date().toISOString(),
354
- level,
355
- message,
356
- data,
357
- };
358
- }
359
-
360
- context.url = window.location.href;
361
-
362
- // Add browser info (this method now handles worker context internally)
363
- context.browserInfo = this.getBrowserInfo();
364
- context.userAgent = context.browserInfo.userAgent;
365
-
366
- // Add screenshot and mouse position if level is error or warn
367
- if (level === 'error' || level === 'warn' || forceScreenshot) {
368
- context.screenshot = (await this.captureScreenshot()) || undefined;
369
- context.mousePosition = this.mousePosition || undefined;
370
- }
371
-
372
- return {
373
- id: `log_${++this.logIdCounter}_${Date.now()}`,
374
- timestamp: new Date().toISOString(),
375
- level,
376
- message,
377
- data,
378
- context: context as LogEntry['context'],
379
- };
380
- }
381
-
382
- /**
383
- * Add a log entry to the internal log array.
384
- * @param entry - Log entry to add
385
- */
386
- private addLogEntry(entry: LogEntry): void {
387
- this.logs.push(entry);
388
-
389
- // Maintain log size limit (1000 entries)
390
- if (this.logs.length > 1000) {
391
- this.logs = this.logs.slice(-1000);
392
- }
393
- }
394
- }