@graphty/remote-logger 0.0.1 → 1.2.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/README.md +944 -28
- package/bin/remote-log-server.js +3 -0
- package/dist/client/RemoteLogClient.d.ts +116 -0
- package/dist/client/RemoteLogClient.d.ts.map +1 -0
- package/dist/client/RemoteLogClient.js +269 -0
- package/dist/client/RemoteLogClient.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +6 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +60 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +6 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +32 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +270 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +14 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +14 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/logs-clear.d.ts +76 -0
- package/dist/mcp/tools/logs-clear.d.ts.map +1 -0
- package/dist/mcp/tools/logs-clear.js +58 -0
- package/dist/mcp/tools/logs-clear.js.map +1 -0
- package/dist/mcp/tools/logs-get-all.d.ts +60 -0
- package/dist/mcp/tools/logs-get-all.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-all.js +50 -0
- package/dist/mcp/tools/logs-get-all.js.map +1 -0
- package/dist/mcp/tools/logs-get-errors.d.ts +65 -0
- package/dist/mcp/tools/logs-get-errors.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-errors.js +46 -0
- package/dist/mcp/tools/logs-get-errors.js.map +1 -0
- package/dist/mcp/tools/logs-get-file-path.d.ts +75 -0
- package/dist/mcp/tools/logs-get-file-path.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-file-path.js +90 -0
- package/dist/mcp/tools/logs-get-file-path.js.map +1 -0
- package/dist/mcp/tools/logs-get-recent.d.ts +89 -0
- package/dist/mcp/tools/logs-get-recent.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-recent.js +74 -0
- package/dist/mcp/tools/logs-get-recent.js.map +1 -0
- package/dist/mcp/tools/logs-list-sessions.d.ts +64 -0
- package/dist/mcp/tools/logs-list-sessions.d.ts.map +1 -0
- package/dist/mcp/tools/logs-list-sessions.js +48 -0
- package/dist/mcp/tools/logs-list-sessions.js.map +1 -0
- package/dist/mcp/tools/logs-receive.d.ts +150 -0
- package/dist/mcp/tools/logs-receive.d.ts.map +1 -0
- package/dist/mcp/tools/logs-receive.js +68 -0
- package/dist/mcp/tools/logs-receive.js.map +1 -0
- package/dist/mcp/tools/logs-search.d.ts +91 -0
- package/dist/mcp/tools/logs-search.d.ts.map +1 -0
- package/dist/mcp/tools/logs-search.js +68 -0
- package/dist/mcp/tools/logs-search.js.map +1 -0
- package/dist/mcp/tools/logs-status.d.ts +45 -0
- package/dist/mcp/tools/logs-status.d.ts.map +1 -0
- package/dist/mcp/tools/logs-status.js +45 -0
- package/dist/mcp/tools/logs-status.js.map +1 -0
- package/dist/server/dual-server.d.ts +76 -0
- package/dist/server/dual-server.d.ts.map +1 -0
- package/dist/server/dual-server.js +214 -0
- package/dist/server/dual-server.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +12 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/jsonl-writer.d.ts +93 -0
- package/dist/server/jsonl-writer.d.ts.map +1 -0
- package/dist/server/jsonl-writer.js +205 -0
- package/dist/server/jsonl-writer.js.map +1 -0
- package/dist/server/log-server.d.ts +126 -0
- package/dist/server/log-server.d.ts.map +1 -0
- package/dist/server/log-server.js +589 -0
- package/dist/server/log-server.js.map +1 -0
- package/dist/server/log-storage.d.ts +301 -0
- package/dist/server/log-storage.d.ts.map +1 -0
- package/dist/server/log-storage.js +408 -0
- package/dist/server/log-storage.js.map +1 -0
- package/dist/server/marker-utils.d.ts +69 -0
- package/dist/server/marker-utils.d.ts.map +1 -0
- package/dist/server/marker-utils.js +118 -0
- package/dist/server/marker-utils.js.map +1 -0
- package/dist/server/self-signed-cert.d.ts +30 -0
- package/dist/server/self-signed-cert.d.ts.map +1 -0
- package/dist/server/self-signed-cert.js +83 -0
- package/dist/server/self-signed-cert.js.map +1 -0
- package/dist/ui/ConsoleCaptureUI.d.ts +118 -0
- package/dist/ui/ConsoleCaptureUI.d.ts.map +1 -0
- package/dist/ui/ConsoleCaptureUI.js +571 -0
- package/dist/ui/ConsoleCaptureUI.js.map +1 -0
- package/dist/ui/index.d.ts +15 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/vite/index.d.ts +8 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +8 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/plugin.d.ts +42 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/vite/plugin.js +46 -0
- package/dist/vite/plugin.js.map +1 -0
- package/package.json +90 -7
- package/src/client/RemoteLogClient.ts +328 -0
- package/src/client/index.ts +7 -0
- package/src/client/types.ts +62 -0
- package/src/index.ts +28 -0
- package/src/mcp/index.ts +25 -0
- package/src/mcp/mcp-server.ts +364 -0
- package/src/mcp/tools/index.ts +69 -0
- package/src/mcp/tools/logs-clear.ts +86 -0
- package/src/mcp/tools/logs-get-all.ts +78 -0
- package/src/mcp/tools/logs-get-errors.ts +71 -0
- package/src/mcp/tools/logs-get-file-path.ts +121 -0
- package/src/mcp/tools/logs-get-recent.ts +104 -0
- package/src/mcp/tools/logs-list-sessions.ts +71 -0
- package/src/mcp/tools/logs-receive.ts +96 -0
- package/src/mcp/tools/logs-search.ts +95 -0
- package/src/mcp/tools/logs-status.ts +69 -0
- package/src/server/dual-server.ts +308 -0
- package/src/server/index.ts +54 -0
- package/src/server/jsonl-writer.ts +277 -0
- package/src/server/log-server.ts +763 -0
- package/src/server/log-storage.ts +651 -0
- package/src/server/marker-utils.ts +144 -0
- package/src/server/self-signed-cert.ts +93 -0
- package/src/ui/ConsoleCaptureUI.ts +649 -0
- package/src/ui/index.ts +15 -0
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin.ts +59 -0
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsoleCaptureUI - Console capture with floating UI widget
|
|
3
|
+
*
|
|
4
|
+
* Intercepts console output (log, error, warn, info, debug) and provides
|
|
5
|
+
* a floating button UI with copy, download, view, and clear functionality.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Floating button in top-right corner
|
|
9
|
+
* - Menu with Copy, Download, Clear, and Show Logs options
|
|
10
|
+
* - Works on mobile devices and in XR environments
|
|
11
|
+
* - Global window.__console__ methods for programmatic access
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* import { initConsoleCaptureUI } from "@graphty/remote-logger/ui";
|
|
15
|
+
* initConsoleCaptureUI();
|
|
16
|
+
*
|
|
17
|
+
* Or programmatically:
|
|
18
|
+
* const ui = new ConsoleCaptureUI();
|
|
19
|
+
* console.log("This is captured");
|
|
20
|
+
* ui.destroy(); // Clean up when done
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// Extended window interface for global console methods
|
|
24
|
+
declare global {
|
|
25
|
+
interface Window {
|
|
26
|
+
__console__?: {
|
|
27
|
+
copy: () => Promise<void>;
|
|
28
|
+
download: () => void;
|
|
29
|
+
clear: () => void;
|
|
30
|
+
get: () => string;
|
|
31
|
+
logs: CapturedLogEntry[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A single captured log entry */
|
|
37
|
+
interface CapturedLogEntry {
|
|
38
|
+
type: string;
|
|
39
|
+
args: unknown[];
|
|
40
|
+
timestamp: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Console method names we intercept */
|
|
44
|
+
type ConsoleMethod = "log" | "error" | "warn" | "info" | "debug";
|
|
45
|
+
|
|
46
|
+
// Get reference to the global console object
|
|
47
|
+
const globalConsole = globalThis.console;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ConsoleCaptureUI class - captures console output and provides floating UI widget
|
|
51
|
+
*/
|
|
52
|
+
export class ConsoleCaptureUI {
|
|
53
|
+
private logs: CapturedLogEntry[] = [];
|
|
54
|
+
private originalMethods: Record<ConsoleMethod, typeof globalConsole.log>;
|
|
55
|
+
private buttonContainer: HTMLElement | null = null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a new ConsoleCaptureUI instance that intercepts console methods
|
|
59
|
+
* and creates a floating UI widget
|
|
60
|
+
*/
|
|
61
|
+
constructor() {
|
|
62
|
+
// Store original methods at construction time
|
|
63
|
+
this.originalMethods = {
|
|
64
|
+
log: globalConsole.log.bind(globalConsole),
|
|
65
|
+
error: globalConsole.error.bind(globalConsole),
|
|
66
|
+
warn: globalConsole.warn.bind(globalConsole),
|
|
67
|
+
info: globalConsole.info.bind(globalConsole),
|
|
68
|
+
debug: globalConsole.debug.bind(globalConsole),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.interceptConsole();
|
|
72
|
+
this.createUI();
|
|
73
|
+
this.setupGlobalMethods();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Intercept all console methods to capture output
|
|
78
|
+
*/
|
|
79
|
+
private interceptConsole(): void {
|
|
80
|
+
const methods: ConsoleMethod[] = [
|
|
81
|
+
"log",
|
|
82
|
+
"error",
|
|
83
|
+
"warn",
|
|
84
|
+
"info",
|
|
85
|
+
"debug",
|
|
86
|
+
];
|
|
87
|
+
for (const method of methods) {
|
|
88
|
+
const original = this.originalMethods[method];
|
|
89
|
+
|
|
90
|
+
// Override the console method - this is intentional for this module
|
|
91
|
+
globalConsole[method] = (...args: unknown[]): void => {
|
|
92
|
+
// Capture the log entry
|
|
93
|
+
this.logs.push({
|
|
94
|
+
type: method,
|
|
95
|
+
args: args,
|
|
96
|
+
timestamp: new Date().toISOString(),
|
|
97
|
+
});
|
|
98
|
+
this.updateButtonBadge();
|
|
99
|
+
// Still call the original method
|
|
100
|
+
original.apply(globalConsole, args);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create the floating UI button and menu
|
|
107
|
+
*/
|
|
108
|
+
private createUI(): void {
|
|
109
|
+
if (typeof document === "undefined") {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create floating button container
|
|
114
|
+
this.buttonContainer = document.createElement("div");
|
|
115
|
+
this.buttonContainer.id = "console-capture-container";
|
|
116
|
+
this.buttonContainer.innerHTML = `
|
|
117
|
+
<button id="console-capture-btn" style="
|
|
118
|
+
position: fixed;
|
|
119
|
+
top: 20px;
|
|
120
|
+
right: 20px;
|
|
121
|
+
width: 32px;
|
|
122
|
+
height: 32px;
|
|
123
|
+
border-radius: 16px;
|
|
124
|
+
background: rgba(128, 128, 128, 0.3);
|
|
125
|
+
color: rgba(255, 255, 255, 0.8);
|
|
126
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
|
|
129
|
+
z-index: 99999;
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
transition: all 0.2s;
|
|
135
|
+
opacity: 0.6;
|
|
136
|
+
">📋</button>
|
|
137
|
+
<div id="console-capture-menu" style="
|
|
138
|
+
position: fixed;
|
|
139
|
+
top: 60px;
|
|
140
|
+
right: 20px;
|
|
141
|
+
background: white;
|
|
142
|
+
border-radius: 8px;
|
|
143
|
+
box-shadow: 0 2px 20px rgba(0,0,0,0.2);
|
|
144
|
+
padding: 10px;
|
|
145
|
+
z-index: 99998;
|
|
146
|
+
display: none;
|
|
147
|
+
">
|
|
148
|
+
<button id="cc-copy" style="
|
|
149
|
+
display: block;
|
|
150
|
+
width: 100%;
|
|
151
|
+
padding: 8px 16px;
|
|
152
|
+
margin: 4px 0;
|
|
153
|
+
border: none;
|
|
154
|
+
background: #4CAF50;
|
|
155
|
+
color: white;
|
|
156
|
+
border-radius: 4px;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
">📋 Copy Logs</button>
|
|
159
|
+
<button id="cc-download" style="
|
|
160
|
+
display: block;
|
|
161
|
+
width: 100%;
|
|
162
|
+
padding: 8px 16px;
|
|
163
|
+
margin: 4px 0;
|
|
164
|
+
border: none;
|
|
165
|
+
background: #FF9800;
|
|
166
|
+
color: white;
|
|
167
|
+
border-radius: 4px;
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
">💾 Download</button>
|
|
170
|
+
<button id="cc-clear" style="
|
|
171
|
+
display: block;
|
|
172
|
+
width: 100%;
|
|
173
|
+
padding: 8px 16px;
|
|
174
|
+
margin: 4px 0;
|
|
175
|
+
border: none;
|
|
176
|
+
background: #f44336;
|
|
177
|
+
color: white;
|
|
178
|
+
border-radius: 4px;
|
|
179
|
+
cursor: pointer;
|
|
180
|
+
">🗑️ Clear</button>
|
|
181
|
+
<button id="cc-show" style="
|
|
182
|
+
display: block;
|
|
183
|
+
width: 100%;
|
|
184
|
+
padding: 8px 16px;
|
|
185
|
+
margin: 4px 0;
|
|
186
|
+
border: none;
|
|
187
|
+
background: #9C27B0;
|
|
188
|
+
color: white;
|
|
189
|
+
border-radius: 4px;
|
|
190
|
+
cursor: pointer;
|
|
191
|
+
">👁️ Show Logs</button>
|
|
192
|
+
<div style="
|
|
193
|
+
margin-top: 10px;
|
|
194
|
+
padding-top: 10px;
|
|
195
|
+
border-top: 1px solid #ddd;
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
color: #666;
|
|
198
|
+
text-align: center;
|
|
199
|
+
">
|
|
200
|
+
<div>Logs: <span id="cc-count">0</span></div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
document.body.appendChild(this.buttonContainer);
|
|
206
|
+
|
|
207
|
+
// Add event listeners
|
|
208
|
+
const btn = document.getElementById("console-capture-btn");
|
|
209
|
+
const menu = document.getElementById("console-capture-menu");
|
|
210
|
+
|
|
211
|
+
if (btn && menu) {
|
|
212
|
+
// Add hover effect to make button more visible on hover
|
|
213
|
+
btn.addEventListener("mouseenter", () => {
|
|
214
|
+
btn.style.opacity = "1";
|
|
215
|
+
btn.style.background = "rgba(128, 128, 128, 0.5)";
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
btn.addEventListener("mouseleave", () => {
|
|
219
|
+
btn.style.opacity = "0.6";
|
|
220
|
+
btn.style.background = "rgba(128, 128, 128, 0.3)";
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
btn.addEventListener("click", () => {
|
|
224
|
+
menu.style.display =
|
|
225
|
+
menu.style.display === "none" ? "block" : "none";
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const copyBtn = document.getElementById("cc-copy");
|
|
229
|
+
const downloadBtn = document.getElementById("cc-download");
|
|
230
|
+
const clearBtn = document.getElementById("cc-clear");
|
|
231
|
+
const showBtn = document.getElementById("cc-show");
|
|
232
|
+
|
|
233
|
+
if (copyBtn) {
|
|
234
|
+
copyBtn.addEventListener("click", () => {
|
|
235
|
+
void this.copyLogs();
|
|
236
|
+
menu.style.display = "none";
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (downloadBtn) {
|
|
241
|
+
downloadBtn.addEventListener("click", () => {
|
|
242
|
+
this.downloadLogs();
|
|
243
|
+
menu.style.display = "none";
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (clearBtn) {
|
|
248
|
+
clearBtn.addEventListener("click", () => {
|
|
249
|
+
this.clearLogs();
|
|
250
|
+
menu.style.display = "none";
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (showBtn) {
|
|
255
|
+
showBtn.addEventListener("click", () => {
|
|
256
|
+
this.showLogsModal();
|
|
257
|
+
menu.style.display = "none";
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Close menu when clicking outside
|
|
262
|
+
document.addEventListener("click", (e) => {
|
|
263
|
+
if (
|
|
264
|
+
this.buttonContainer &&
|
|
265
|
+
!this.buttonContainer.contains(e.target as Node)
|
|
266
|
+
) {
|
|
267
|
+
menu.style.display = "none";
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Update the log count badge in the menu
|
|
275
|
+
*/
|
|
276
|
+
private updateButtonBadge(): void {
|
|
277
|
+
if (typeof document === "undefined") {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const count = document.getElementById("cc-count");
|
|
281
|
+
if (count) {
|
|
282
|
+
count.textContent = String(this.logs.length);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Format a single log entry as a string
|
|
288
|
+
* @param log - The log entry to format
|
|
289
|
+
* @returns Formatted log string
|
|
290
|
+
*/
|
|
291
|
+
private formatLog(log: CapturedLogEntry): string {
|
|
292
|
+
const args = log.args
|
|
293
|
+
.map((arg) => {
|
|
294
|
+
if (typeof arg === "string") {
|
|
295
|
+
return arg;
|
|
296
|
+
}
|
|
297
|
+
if (arg instanceof Error) {
|
|
298
|
+
return `${arg.name}: ${arg.message}\n${arg.stack ?? ""}`;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
return JSON.stringify(arg, null, 2);
|
|
302
|
+
} catch {
|
|
303
|
+
return String(arg);
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
.join(" ");
|
|
307
|
+
|
|
308
|
+
return `[${log.timestamp}] [${log.type.toUpperCase()}] ${args}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Setup global window.__console__ methods for easy access
|
|
313
|
+
*/
|
|
314
|
+
private setupGlobalMethods(): void {
|
|
315
|
+
if (typeof window !== "undefined") {
|
|
316
|
+
window.__console__ = {
|
|
317
|
+
copy: () => this.copyLogs(),
|
|
318
|
+
download: () => {
|
|
319
|
+
this.downloadLogs();
|
|
320
|
+
},
|
|
321
|
+
clear: () => {
|
|
322
|
+
this.clearLogs();
|
|
323
|
+
},
|
|
324
|
+
get: () => this.getLogs(),
|
|
325
|
+
logs: this.logs,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Show success indicator on the button
|
|
332
|
+
*/
|
|
333
|
+
private showCopySuccess(): void {
|
|
334
|
+
if (typeof document === "undefined") {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const btn = document.getElementById("console-capture-btn");
|
|
338
|
+
if (btn) {
|
|
339
|
+
btn.innerHTML = "✅";
|
|
340
|
+
setTimeout(() => {
|
|
341
|
+
btn.innerHTML = "📋";
|
|
342
|
+
}, 1500);
|
|
343
|
+
}
|
|
344
|
+
globalConsole.log("📋 Console logs copied to clipboard!");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Show error indicator on the button and fallback message
|
|
349
|
+
*/
|
|
350
|
+
private showCopyError(): void {
|
|
351
|
+
if (typeof document === "undefined") {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const btn = document.getElementById("console-capture-btn");
|
|
355
|
+
if (btn) {
|
|
356
|
+
btn.innerHTML = "❌";
|
|
357
|
+
setTimeout(() => {
|
|
358
|
+
btn.innerHTML = "📋";
|
|
359
|
+
}, 1500);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Show logs in console as fallback
|
|
363
|
+
globalConsole.log(
|
|
364
|
+
"❌ Failed to copy to clipboard. Here are your logs:"
|
|
365
|
+
);
|
|
366
|
+
globalConsole.log("═".repeat(50));
|
|
367
|
+
globalConsole.log(this.getLogs());
|
|
368
|
+
globalConsole.log("═".repeat(50));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Fallback copy method using textarea (for mobile devices)
|
|
373
|
+
* @param text - The text to copy
|
|
374
|
+
*/
|
|
375
|
+
private copyUsingTextarea(text: string): void {
|
|
376
|
+
if (typeof document === "undefined") {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// Create temporary textarea
|
|
380
|
+
const textarea = document.createElement("textarea");
|
|
381
|
+
textarea.value = text;
|
|
382
|
+
textarea.style.cssText =
|
|
383
|
+
"position: absolute; left: -9999px; top: -9999px;";
|
|
384
|
+
document.body.appendChild(textarea);
|
|
385
|
+
|
|
386
|
+
// Select and copy
|
|
387
|
+
textarea.select();
|
|
388
|
+
textarea.setSelectionRange(0, 99999); // For mobile devices
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
// execCommand is deprecated but still necessary as a fallback for:
|
|
392
|
+
// - Older mobile browsers that don't support Clipboard API
|
|
393
|
+
// - iOS Safari in certain contexts where Clipboard API fails
|
|
394
|
+
// - XR/VR browsers with limited API support
|
|
395
|
+
// This provides the best compatibility across all target platforms
|
|
396
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- Required fallback for mobile/XR compatibility
|
|
397
|
+
const success = document.execCommand("copy");
|
|
398
|
+
if (success) {
|
|
399
|
+
this.showCopySuccess();
|
|
400
|
+
} else {
|
|
401
|
+
this.showCopyError();
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
this.showCopyError();
|
|
405
|
+
} finally {
|
|
406
|
+
document.body.removeChild(textarea);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Show a modal dialog with all captured logs
|
|
412
|
+
*/
|
|
413
|
+
private showLogsModal(): void {
|
|
414
|
+
if (typeof document === "undefined") {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Remove existing modal if any
|
|
419
|
+
const existing = document.getElementById("console-logs-modal");
|
|
420
|
+
if (existing) {
|
|
421
|
+
existing.remove();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const modal = document.createElement("div");
|
|
425
|
+
modal.id = "console-logs-modal";
|
|
426
|
+
modal.innerHTML = `
|
|
427
|
+
<div style="
|
|
428
|
+
position: fixed;
|
|
429
|
+
top: 0;
|
|
430
|
+
left: 0;
|
|
431
|
+
right: 0;
|
|
432
|
+
bottom: 0;
|
|
433
|
+
background: rgba(0,0,0,0.5);
|
|
434
|
+
z-index: 100001;
|
|
435
|
+
display: flex;
|
|
436
|
+
align-items: center;
|
|
437
|
+
justify-content: center;
|
|
438
|
+
">
|
|
439
|
+
<div style="
|
|
440
|
+
background: white;
|
|
441
|
+
border-radius: 8px;
|
|
442
|
+
width: 90%;
|
|
443
|
+
max-width: 800px;
|
|
444
|
+
height: 85vh;
|
|
445
|
+
max-height: 85vh;
|
|
446
|
+
display: flex;
|
|
447
|
+
flex-direction: column;
|
|
448
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
449
|
+
">
|
|
450
|
+
<div style="
|
|
451
|
+
padding: 16px;
|
|
452
|
+
border-bottom: 1px solid #ddd;
|
|
453
|
+
display: flex;
|
|
454
|
+
justify-content: space-between;
|
|
455
|
+
align-items: center;
|
|
456
|
+
">
|
|
457
|
+
<h3 style="margin: 0; color: #333;">Console Logs</h3>
|
|
458
|
+
<button id="close-modal" style="
|
|
459
|
+
background: none;
|
|
460
|
+
border: none;
|
|
461
|
+
font-size: 24px;
|
|
462
|
+
cursor: pointer;
|
|
463
|
+
color: #666;
|
|
464
|
+
">×</button>
|
|
465
|
+
</div>
|
|
466
|
+
<textarea id="logs-textarea" style="
|
|
467
|
+
flex: 1;
|
|
468
|
+
margin: 16px;
|
|
469
|
+
padding: 12px;
|
|
470
|
+
border: 1px solid #ddd;
|
|
471
|
+
border-radius: 4px;
|
|
472
|
+
font-family: monospace;
|
|
473
|
+
font-size: 12px;
|
|
474
|
+
resize: none;
|
|
475
|
+
background: #f5f5f5;
|
|
476
|
+
min-height: 50vh;
|
|
477
|
+
overflow-y: auto;
|
|
478
|
+
" readonly>${this.getLogs()}</textarea>
|
|
479
|
+
<div style="
|
|
480
|
+
padding: 16px;
|
|
481
|
+
border-top: 1px solid #ddd;
|
|
482
|
+
text-align: center;
|
|
483
|
+
">
|
|
484
|
+
<button id="select-all-logs" style="
|
|
485
|
+
padding: 8px 24px;
|
|
486
|
+
background: #2196F3;
|
|
487
|
+
color: white;
|
|
488
|
+
border: none;
|
|
489
|
+
border-radius: 4px;
|
|
490
|
+
cursor: pointer;
|
|
491
|
+
font-size: 14px;
|
|
492
|
+
">Select All</button>
|
|
493
|
+
<span style="
|
|
494
|
+
margin-left: 16px;
|
|
495
|
+
color: #666;
|
|
496
|
+
font-size: 14px;
|
|
497
|
+
">Press Ctrl+C (or Cmd+C) to copy</span>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
`;
|
|
502
|
+
|
|
503
|
+
document.body.appendChild(modal);
|
|
504
|
+
|
|
505
|
+
// Event listeners
|
|
506
|
+
const closeBtn = document.getElementById("close-modal");
|
|
507
|
+
if (closeBtn) {
|
|
508
|
+
closeBtn.addEventListener("click", () => {
|
|
509
|
+
modal.remove();
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
modal.addEventListener("click", (e) => {
|
|
514
|
+
if (e.target === modal.firstElementChild) {
|
|
515
|
+
modal.remove();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const textarea = document.getElementById(
|
|
520
|
+
"logs-textarea"
|
|
521
|
+
) as HTMLTextAreaElement | null;
|
|
522
|
+
const selectAllBtn = document.getElementById("select-all-logs");
|
|
523
|
+
|
|
524
|
+
if (selectAllBtn && textarea) {
|
|
525
|
+
selectAllBtn.addEventListener("click", () => {
|
|
526
|
+
textarea.select();
|
|
527
|
+
textarea.focus();
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Auto-select on open
|
|
532
|
+
if (textarea) {
|
|
533
|
+
setTimeout(() => {
|
|
534
|
+
textarea.select();
|
|
535
|
+
textarea.focus();
|
|
536
|
+
}, 100);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Get all captured logs as a formatted string
|
|
542
|
+
* @returns Formatted log string with timestamps and levels
|
|
543
|
+
*/
|
|
544
|
+
getLogs(): string {
|
|
545
|
+
return this.logs.map((log) => this.formatLog(log)).join("\n");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Clear all captured logs
|
|
550
|
+
*/
|
|
551
|
+
clearLogs(): void {
|
|
552
|
+
this.logs = [];
|
|
553
|
+
this.updateButtonBadge();
|
|
554
|
+
globalConsole.log("🗑️ Console logs cleared");
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Copy logs to clipboard
|
|
559
|
+
*/
|
|
560
|
+
async copyLogs(): Promise<void> {
|
|
561
|
+
const text = this.getLogs();
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
// Try modern clipboard API first
|
|
565
|
+
if (
|
|
566
|
+
typeof navigator !== "undefined" &&
|
|
567
|
+
navigator.clipboard &&
|
|
568
|
+
navigator.clipboard.writeText
|
|
569
|
+
) {
|
|
570
|
+
await navigator.clipboard.writeText(text);
|
|
571
|
+
this.showCopySuccess();
|
|
572
|
+
} else {
|
|
573
|
+
// Fallback method using textarea
|
|
574
|
+
this.copyUsingTextarea(text);
|
|
575
|
+
}
|
|
576
|
+
} catch {
|
|
577
|
+
// If modern API fails, try fallback
|
|
578
|
+
globalConsole.warn("Clipboard API failed, using fallback");
|
|
579
|
+
this.copyUsingTextarea(text);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Download logs as a text file
|
|
585
|
+
*/
|
|
586
|
+
downloadLogs(): void {
|
|
587
|
+
if (typeof document === "undefined") {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const text = this.getLogs();
|
|
592
|
+
const blob = new Blob([text], { type: "text/plain" });
|
|
593
|
+
const url = URL.createObjectURL(blob);
|
|
594
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
595
|
+
const filename = `console-logs-${timestamp}.txt`;
|
|
596
|
+
|
|
597
|
+
const link = document.createElement("a");
|
|
598
|
+
link.href = url;
|
|
599
|
+
link.download = filename;
|
|
600
|
+
document.body.appendChild(link);
|
|
601
|
+
link.click();
|
|
602
|
+
document.body.removeChild(link);
|
|
603
|
+
|
|
604
|
+
URL.revokeObjectURL(url);
|
|
605
|
+
globalConsole.log("💾 Console logs downloaded!");
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Restore original console methods and clean up UI
|
|
610
|
+
*/
|
|
611
|
+
destroy(): void {
|
|
612
|
+
// Restore original console methods
|
|
613
|
+
globalConsole.log = this.originalMethods.log;
|
|
614
|
+
globalConsole.error = this.originalMethods.error;
|
|
615
|
+
globalConsole.warn = this.originalMethods.warn;
|
|
616
|
+
globalConsole.info = this.originalMethods.info;
|
|
617
|
+
globalConsole.debug = this.originalMethods.debug;
|
|
618
|
+
|
|
619
|
+
// Remove UI elements
|
|
620
|
+
if (
|
|
621
|
+
typeof document !== "undefined" &&
|
|
622
|
+
this.buttonContainer &&
|
|
623
|
+
this.buttonContainer.parentNode
|
|
624
|
+
) {
|
|
625
|
+
this.buttonContainer.parentNode.removeChild(this.buttonContainer);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Remove modal if open
|
|
629
|
+
if (typeof document !== "undefined") {
|
|
630
|
+
const modal = document.getElementById("console-logs-modal");
|
|
631
|
+
if (modal) {
|
|
632
|
+
modal.remove();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Remove global methods
|
|
637
|
+
if (typeof window !== "undefined") {
|
|
638
|
+
delete window.__console__;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Convenience function to initialize ConsoleCaptureUI
|
|
645
|
+
* @returns A new ConsoleCaptureUI instance
|
|
646
|
+
*/
|
|
647
|
+
export function initConsoleCaptureUI(): ConsoleCaptureUI {
|
|
648
|
+
return new ConsoleCaptureUI();
|
|
649
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI components for console capture and display.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { initConsoleCaptureUI } from "@graphty/remote-logger/ui";
|
|
6
|
+
* initConsoleCaptureUI();
|
|
7
|
+
*
|
|
8
|
+
* Or access programmatically:
|
|
9
|
+
* import { ConsoleCaptureUI } from "@graphty/remote-logger/ui";
|
|
10
|
+
* const ui = new ConsoleCaptureUI();
|
|
11
|
+
* console.log("This will be captured");
|
|
12
|
+
* console.log(ui.getLogs());
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { ConsoleCaptureUI, initConsoleCaptureUI } from "./ConsoleCaptureUI.js";
|