1export interface DebugLogEntry {
2 timestamp: string;
3 level: "info" | "warn" | "error" | "debug";
4 source: "frontend" | "backend";
5 category: string;
6 message: string;
7 data?: unknown;
8}
9
10export class DebugConsoleService {
11 private logs: DebugLogEntry[] = [];
12 private maxLogs = 1000;
13 private isVisible = false;
14 private debugElement: HTMLElement | null = null;
15 private logsContainer: HTMLElement | null = null;
16
17 constructor() {
18 this.createDebugUI();
19 this.setupKeyboardShortcut();
20 this.interceptConsole();
21 }
22
23 private createDebugUI(): void {
24 // Create debug panel
25 this.debugElement = document.createElement("div");
26 this.debugElement.id = "debug-console";
27 this.debugElement.style.cssText = `
28 position: fixed;
29 top: 0;
30 right: 0;
31 width: 50%;
32 height: 100%;
33 background: rgba(0, 0, 0, 0.95);
34 color: #00ff00;
35 font-family: 'Courier New', monospace;
36 font-size: 12px;
37 z-index: 10000;
38 display: none;
39 border-left: 2px solid #00ff00;
40 `;
41
42 // Create header
43 const header = document.createElement("div");
44 header.style.cssText = `
45 padding: 10px;
46 background: rgba(0, 255, 0, 0.1);
47 border-bottom: 1px solid #00ff00;
48 font-weight: bold;
49 display: flex;
50 justify-content: space-between;
51 align-items: center;
52 `;
53 header.innerHTML = `
54 <span>🐛 DUMBER DEBUG CONSOLE</span>
55 <div style="font-size: 10px;">
56 <span style="color: #888; margin-right: 10px;">F11: Inspect Services | F12: Toggle</span>
57 <button id="clear-logs" style="background: none; border: 1px solid #00ff00; color: #00ff00; padding: 2px 8px; margin-right: 5px; cursor: pointer;">Clear</button>
58 <button id="close-debug" style="background: none; border: 1px solid #00ff00; color: #00ff00; padding: 2px 8px; cursor: pointer;">Close</button>
59 </div>
60 `;
61
62 // Create logs container
63 this.logsContainer = document.createElement("div");
64 this.logsContainer.style.cssText = `
65 height: calc(100% - 50px);
66 overflow-y: auto;
67 padding: 10px;
68 `;
69
70 this.debugElement.appendChild(header);
71 this.debugElement.appendChild(this.logsContainer);
72 document.body.appendChild(this.debugElement);
73
74 // Setup button handlers
75 header
76 .querySelector("#clear-logs")
77 ?.addEventListener("click", () => this.clearLogs());
78 header
79 .querySelector("#close-debug")
80 ?.addEventListener("click", () => this.hide());
81 }
82
83 private setupKeyboardShortcut(): void {
84 document.addEventListener("keydown", (event) => {
85 // F12 to toggle debug console
86 if (event.key === "F12") {
87 event.preventDefault();
88 this.toggle();
89 }
90 });
91 }
92
93 private interceptConsole(): void {
94 const originalConsole = {
95 log: console.log,
96 warn: console.warn,
97 error: console.error,
98 info: console.info,
99 debug: console.debug,
100 };
101
102 console.log = (...args) => {
103 this.addLog("info", "frontend", "console", args.join(" "));
104 originalConsole.log(...args);
105 };
106
107 console.warn = (...args) => {
108 this.addLog("warn", "frontend", "console", args.join(" "));
109 originalConsole.warn(...args);
110 };
111
112 console.error = (...args) => {
113 this.addLog("error", "frontend", "console", args.join(" "));
114 originalConsole.error(...args);
115 };
116
117 console.info = (...args) => {
118 this.addLog("info", "frontend", "console", args.join(" "));
119 originalConsole.info(...args);
120 };
121
122 console.debug = (...args) => {
123 this.addLog("debug", "frontend", "console", args.join(" "));
124 originalConsole.debug(...args);
125 };
126 }
127
128 public addLog(
129 level: DebugLogEntry["level"],
130 source: DebugLogEntry["source"],
131 category: string,
132 message: string,
133 data?: unknown,
134 ): void {
135 const entry: DebugLogEntry = {
136 timestamp: new Date().toISOString().split("T")[1]?.split(".")[0] || "", // HH:MM:SS format
137 level,
138 source,
139 category,
140 message,
141 data,
142 };
143
144 this.logs.push(entry);
145
146 // Keep only the last maxLogs entries
147 if (this.logs.length > this.maxLogs) {
148 this.logs.shift();
149 }
150
151 this.updateDisplay();
152 }
153
154 private updateDisplay(): void {
155 if (!this.logsContainer) return;
156
157 const logHTML = this.logs
158 .map((log) => {
159 const levelColor = {
160 info: "#00ff00",
161 warn: "#ffff00",
162 error: "#ff0000",
163 debug: "#00ffff",
164 }[log.level];
165
166 const sourceIcon = log.source === "backend" ? "🖥️" : "🌐";
167
168 return `
169 <div style="margin-bottom: 2px; font-size: 11px;">
170 <span style="color: #888;">[${log.timestamp}]</span>
171 <span style="color: ${levelColor};">${sourceIcon} ${log.level.toUpperCase()}</span>
172 <span style="color: #aaa;">[${log.category}]</span>
173 <span style="color: #fff;">${log.message}</span>
174 ${log.data ? `<pre style="color: #ccc; font-size: 10px; margin: 2px 0;">${JSON.stringify(log.data, null, 2)}</pre>` : ""}
175 </div>
176 `;
177 })
178 .join("");
179
180 this.logsContainer.innerHTML = logHTML;
181
182 // Auto-scroll to bottom
183 this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
184 }
185
186 public logKeyboardEvent(event: KeyboardEvent): void {
187 const modifiers = [];
188 if (event.ctrlKey) modifiers.push("Ctrl");
189 if (event.shiftKey) modifiers.push("Shift");
190 if (event.altKey) modifiers.push("Alt");
191 if (event.metaKey) modifiers.push("Meta");
192
193 const shortcut =
194 modifiers.length > 0 ? `${modifiers.join("+")}+${event.key}` : event.key;
195
196 this.addLog("info", "frontend", "keyboard", `Key pressed: ${shortcut}`, {
197 key: event.key,
198 code: event.code,
199 modifiers,
200 target: event.target?.constructor.name,
201 });
202 }
203
204 public logMouseEvent(event: MouseEvent): void {
205 const buttonNames: Record<number, string> = {
206 0: "Left",
207 1: "Middle",
208 2: "Right",
209 3: "Back",
210 4: "Forward",
211 };
212
213 this.addLog(
214 "info",
215 "frontend",
216 "mouse",
217 `Mouse ${event.type}: ${buttonNames[event.button] || `Button${event.button}`}`,
218 {
219 button: event.button,
220 clientX: event.clientX,
221 clientY: event.clientY,
222 target: event.target?.constructor.name,
223 },
224 );
225 }
226
227 public logServiceCall(
228 serviceName: string,
229 methodName: string,
230 params: unknown[],
231 result?: unknown,
232 error?: Error,
233 ): void {
234 if (error) {
235 this.addLog(
236 "error",
237 "frontend",
238 "service",
239 `${serviceName}.${methodName}() failed`,
240 {
241 params,
242 error: error.message,
243 },
244 );
245 } else {
246 this.addLog(
247 "info",
248 "frontend",
249 "service",
250 `${serviceName}.${methodName}() called`,
251 {
252 params,
253 result,
254 },
255 );
256 }
257 }
258
259 public logBackendMessage(message: string, data?: unknown): void {
260 this.addLog("info", "backend", "service", message, data);
261 }
262
263 public show(): void {
264 if (this.debugElement) {
265 this.debugElement.style.display = "block";
266 this.isVisible = true;
267 this.addLog("info", "frontend", "debug", "Debug console opened");
268 }
269 }
270
271 public hide(): void {
272 if (this.debugElement) {
273 this.debugElement.style.display = "none";
274 this.isVisible = false;
275 }
276 }
277
278 public toggle(): void {
279 if (this.isVisible) {
280 this.hide();
281 } else {
282 this.show();
283 }
284 }
285
286 public clearLogs(): void {
287 this.logs = [];
288 this.updateDisplay();
289 this.addLog("info", "frontend", "debug", "Logs cleared");
290 }
291
292 public isDebugVisible(): boolean {
293 return this.isVisible;
294 }
295}