@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.
- package/.prettierignore +35 -0
- package/LICENSE +201 -0
- package/README copy.md +1216 -0
- package/README.md +1 -0
- package/dist/components/MarkdownEditor.d.ts +8 -0
- package/dist/components/MarkdownEditor.js +48 -0
- package/dist/components/Spinner.d.ts +8 -0
- package/dist/components/Spinner.js +4 -0
- package/dist/components/ai/Assistant.d.ts +9 -0
- package/dist/components/ai/Assistant.js +58 -0
- package/dist/components/ai/Avatar.d.ts +14 -0
- package/dist/components/ai/Avatar.js +59 -0
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.d.ts +7 -0
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +37 -0
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +8 -0
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +79 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +19 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +91 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +27 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +185 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +11 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +95 -0
- package/dist/components/ai/utils.d.ts +6 -0
- package/dist/components/ai/utils.js +13 -0
- package/dist/components/audio/Playbutton.d.ts +15 -0
- package/dist/components/audio/Playbutton.js +80 -0
- package/dist/components/components/ContextMenu.d.ts +10 -0
- package/dist/components/components/ContextMenu.js +135 -0
- package/dist/hooks/I18nHooks.d.ts +11 -0
- package/dist/hooks/I18nHooks.js +25 -0
- package/dist/hooks/UseChatHook.d.ts +10 -0
- package/dist/hooks/UseChatHook.js +29 -0
- package/dist/providers/PluginProvider.d.ts +11 -0
- package/dist/providers/PluginProvider.js +142 -0
- package/dist/react-client/plugin/ThemeSetter.d.ts +2 -0
- package/dist/react-client/plugin/ThemeSetter.js +19 -0
- package/dist/react-client/src/components/ContextMenu.d.ts +10 -0
- package/dist/react-client/src/components/ContextMenu.js +135 -0
- package/dist/react-client/src/components/MarkdownEditor.d.ts +8 -0
- package/dist/react-client/src/components/MarkdownEditor.js +48 -0
- package/dist/react-client/src/components/Spinner.d.ts +8 -0
- package/dist/react-client/src/components/Spinner.js +4 -0
- package/dist/react-client/src/components/ai/Assistant.d.ts +9 -0
- package/dist/react-client/src/components/ai/Assistant.js +58 -0
- package/dist/react-client/src/components/ai/Avatar.d.ts +14 -0
- package/dist/react-client/src/components/ai/Avatar.js +59 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.d.ts +7 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.js +37 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +8 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +79 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +19 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.js +91 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.d.ts +27 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.js +185 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +11 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.js +95 -0
- package/dist/react-client/src/components/ai/utils.d.ts +6 -0
- package/dist/react-client/src/components/ai/utils.js +13 -0
- package/dist/react-client/src/components/audio/Playbutton.d.ts +15 -0
- package/dist/react-client/src/components/audio/Playbutton.js +82 -0
- package/dist/react-client/src/components/components/ContextMenu.d.ts +10 -0
- package/dist/react-client/src/components/components/ContextMenu.js +135 -0
- package/dist/react-client/src/hooks/I18nHooks.d.ts +11 -0
- package/dist/react-client/src/hooks/I18nHooks.js +25 -0
- package/dist/react-client/src/hooks/UseChatHook.d.ts +10 -0
- package/dist/react-client/src/hooks/UseChatHook.js +29 -0
- package/dist/react-client/src/plugin/ThemeSetter.d.ts +2 -0
- package/dist/react-client/src/plugin/ThemeSetter.js +19 -0
- package/dist/react-client/src/providers/PluginProvider.d.ts +12 -0
- package/dist/react-client/src/providers/PluginProvider.js +142 -0
- package/dist/react-client/src/utils/FullscreenUtils.d.ts +2 -0
- package/dist/react-client/src/utils/FullscreenUtils.js +23 -0
- package/dist/react-client/src/utils/PluginUtils.d.ts +2 -0
- package/dist/react-client/src/utils/PluginUtils.js +23 -0
- package/dist/rimori-client/src/cli/types/DatabaseTypes.d.ts +103 -0
- package/dist/rimori-client/src/cli/types/DatabaseTypes.js +2 -0
- package/dist/rimori-client/src/controller/AIController.d.ts +15 -0
- package/dist/rimori-client/src/controller/AIController.js +255 -0
- package/dist/rimori-client/src/controller/AccomplishmentController.d.ts +38 -0
- package/dist/rimori-client/src/controller/AccomplishmentController.js +112 -0
- package/dist/rimori-client/src/controller/AudioController.d.ts +37 -0
- package/dist/rimori-client/src/controller/AudioController.js +68 -0
- package/dist/rimori-client/src/controller/ExerciseController.d.ts +54 -0
- package/dist/rimori-client/src/controller/ExerciseController.js +74 -0
- package/dist/rimori-client/src/controller/ObjectController.d.ts +42 -0
- package/dist/rimori-client/src/controller/ObjectController.js +76 -0
- package/dist/rimori-client/src/controller/SettingsController.d.ts +79 -0
- package/dist/rimori-client/src/controller/SettingsController.js +118 -0
- package/dist/rimori-client/src/controller/SharedContentController.d.ts +106 -0
- package/dist/rimori-client/src/controller/SharedContentController.js +285 -0
- package/dist/rimori-client/src/controller/TranslationController.d.ts +38 -0
- package/dist/rimori-client/src/controller/TranslationController.js +106 -0
- package/dist/rimori-client/src/controller/VoiceController.d.ts +9 -0
- package/dist/rimori-client/src/controller/VoiceController.js +37 -0
- package/dist/rimori-client/src/fromRimori/EventBus.d.ts +101 -0
- package/dist/rimori-client/src/fromRimori/EventBus.js +263 -0
- package/dist/rimori-client/src/fromRimori/PluginTypes.d.ts +174 -0
- package/dist/rimori-client/src/fromRimori/PluginTypes.js +1 -0
- package/dist/rimori-client/src/index.d.ts +11 -0
- package/dist/rimori-client/src/index.js +10 -0
- package/dist/rimori-client/src/plugin/CommunicationHandler.d.ts +48 -0
- package/dist/rimori-client/src/plugin/CommunicationHandler.js +234 -0
- package/dist/rimori-client/src/plugin/Logger.d.ts +73 -0
- package/dist/rimori-client/src/plugin/Logger.js +308 -0
- package/dist/rimori-client/src/plugin/RimoriClient.d.ts +258 -0
- package/dist/rimori-client/src/plugin/RimoriClient.js +375 -0
- package/dist/rimori-client/src/plugin/StandaloneClient.d.ts +17 -0
- package/dist/rimori-client/src/plugin/StandaloneClient.js +115 -0
- package/dist/rimori-client/src/utils/difficultyConverter.d.ts +4 -0
- package/dist/rimori-client/src/utils/difficultyConverter.js +10 -0
- package/dist/rimori-client/src/utils/endpoint.d.ts +2 -0
- package/dist/rimori-client/src/utils/endpoint.js +2 -0
- package/dist/style.css +110 -0
- package/dist/style.css.map +1 -0
- package/dist/utils/PluginUtils.d.ts +2 -0
- package/dist/utils/PluginUtils.js +23 -0
- package/eslint.config.js +53 -0
- package/index.ts +6 -0
- package/package.json +47 -0
- package/prettier.config.js +8 -0
- package/src/components/ContextMenu.tsx +177 -0
- package/src/components/MarkdownEditor.tsx +144 -0
- package/src/components/Spinner.tsx +29 -0
- package/src/components/ai/Assistant.tsx +96 -0
- package/src/components/ai/Avatar.tsx +99 -0
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +73 -0
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +107 -0
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +96 -0
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +197 -0
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +129 -0
- package/src/components/ai/utils.ts +21 -0
- package/src/components/audio/Playbutton.tsx +126 -0
- package/src/hooks/I18nHooks.ts +33 -0
- package/src/hooks/UseChatHook.ts +38 -0
- package/src/plugin/ThemeSetter.ts +23 -0
- package/src/providers/PluginProvider.tsx +197 -0
- package/src/style.scss +136 -0
- package/src/utils/FullscreenUtils.ts +22 -0
- 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
|
+
}
|