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.querySelector('#clear-logs')?.addEventListener('click', () => this.clearLogs());
76 header.querySelector('#close-debug')?.addEventListener('click', () => this.hide());
77 }
78
79 private setupKeyboardShortcut(): void {
80 document.addEventListener('keydown', (event) => {
81 // F12 to toggle debug console
82 if (event.key === 'F12') {
83 event.preventDefault();
84 this.toggle();
85 }
86 });
87 }
88
89 private interceptConsole(): void {
90 const originalConsole = {
91 log: console.log,
92 warn: console.warn,
93 error: console.error,
94 info: console.info,
95 debug: console.debug
96 };
97
98 console.log = (...args) => {
99 this.addLog('info', 'frontend', 'console', args.join(' '));
100 originalConsole.log(...args);
101 };
102
103 console.warn = (...args) => {
104 this.addLog('warn', 'frontend', 'console', args.join(' '));
105 originalConsole.warn(...args);
106 };
107
108 console.error = (...args) => {
109 this.addLog('error', 'frontend', 'console', args.join(' '));
110 originalConsole.error(...args);
111 };
112
113 console.info = (...args) => {
114 this.addLog('info', 'frontend', 'console', args.join(' '));
115 originalConsole.info(...args);
116 };
117
118 console.debug = (...args) => {
119 this.addLog('debug', 'frontend', 'console', args.join(' '));
120 originalConsole.debug(...args);
121 };
122 }
123
124 public addLog(level: DebugLogEntry['level'], source: DebugLogEntry['source'], category: string, message: string, data?: unknown): void {
125 const entry: DebugLogEntry = {
126 timestamp: new Date().toISOString().split('T')[1]?.split('.')[0] || '', // HH:MM:SS format
127 level,
128 source,
129 category,
130 message,
131 data
132 };
133
134 this.logs.push(entry);
135
136 // Keep only the last maxLogs entries
137 if (this.logs.length > this.maxLogs) {
138 this.logs.shift();
139 }
140
141 this.updateDisplay();
142 }
143
144 private updateDisplay(): void {
145 if (!this.logsContainer) return;
146
147 const logHTML = this.logs.map(log => {
148 const levelColor = {
149 info: '#00ff00',
150 warn: '#ffff00',
151 error: '#ff0000',
152 debug: '#00ffff'
153 }[log.level];
154
155 const sourceIcon = log.source === 'backend' ? '🖥️' : '🌐';
156
157 return `
158 <div style="margin-bottom: 2px; font-size: 11px;">
159 <span style="color: #888;">[${log.timestamp}]</span>
160 <span style="color: ${levelColor};">${sourceIcon} ${log.level.toUpperCase()}</span>
161 <span style="color: #aaa;">[${log.category}]</span>
162 <span style="color: #fff;">${log.message}</span>
163 ${log.data ? `<pre style="color: #ccc; font-size: 10px; margin: 2px 0;">${JSON.stringify(log.data, null, 2)}</pre>` : ''}
164 </div>
165 `;
166 }).join('');
167
168 this.logsContainer.innerHTML = logHTML;
169
170 // Auto-scroll to bottom
171 this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
172 }
173
174 public logKeyboardEvent(event: KeyboardEvent): void {
175 const modifiers = [];
176 if (event.ctrlKey) modifiers.push('Ctrl');
177 if (event.shiftKey) modifiers.push('Shift');
178 if (event.altKey) modifiers.push('Alt');
179 if (event.metaKey) modifiers.push('Meta');
180
181 const shortcut = modifiers.length > 0 ? `${modifiers.join('+')}+${event.key}` : event.key;
182
183 this.addLog('info', 'frontend', 'keyboard', `Key pressed: ${shortcut}`, {
184 key: event.key,
185 code: event.code,
186 modifiers,
187 target: event.target?.constructor.name
188 });
189 }
190
191 public logMouseEvent(event: MouseEvent): void {
192 const buttonNames: Record<number, string> = {
193 0: 'Left',
194 1: 'Middle',
195 2: 'Right',
196 3: 'Back',
197 4: 'Forward'
198 };
199
200 this.addLog('info', 'frontend', 'mouse', `Mouse ${event.type}: ${buttonNames[event.button] || `Button${event.button}`}`, {
201 button: event.button,
202 clientX: event.clientX,
203 clientY: event.clientY,
204 target: event.target?.constructor.name
205 });
206 }
207
208 public logServiceCall(serviceName: string, methodName: string, params: unknown[], result?: unknown, error?: Error): void {
209 if (error) {
210 this.addLog('error', 'frontend', 'service', `${serviceName}.${methodName}() failed`, {
211 params,
212 error: error.message
213 });
214 } else {
215 this.addLog('info', 'frontend', 'service', `${serviceName}.${methodName}() called`, {
216 params,
217 result
218 });
219 }
220 }
221
222 public logBackendMessage(message: string, data?: unknown): void {
223 this.addLog('info', 'backend', 'service', message, data);
224 }
225
226 public show(): void {
227 if (this.debugElement) {
228 this.debugElement.style.display = 'block';
229 this.isVisible = true;
230 this.addLog('info', 'frontend', 'debug', 'Debug console opened');
231 }
232 }
233
234 public hide(): void {
235 if (this.debugElement) {
236 this.debugElement.style.display = 'none';
237 this.isVisible = false;
238 }
239 }
240
241 public toggle(): void {
242 if (this.isVisible) {
243 this.hide();
244 } else {
245 this.show();
246 }
247 }
248
249 public clearLogs(): void {
250 this.logs = [];
251 this.updateDisplay();
252 this.addLog('info', 'frontend', 'debug', 'Logs cleared');
253 }
254
255 public isDebugVisible(): boolean {
256 return this.isVisible;
257 }
258}