export interface DebugLogEntry {
timestamp: string;
level: 'info' | 'warn' | 'error' | 'debug';
source: 'frontend' | 'backend';
category: string;
message: string;
data?: any;
}
export class DebugConsoleService {
private logs: DebugLogEntry[] = [];
private maxLogs = 1000;
private isVisible = false;
private debugElement: HTMLElement | null = null;
private logsContainer: HTMLElement | null = null;
constructor() {
this.createDebugUI();
this.setupKeyboardShortcut();
this.interceptConsole();
}
private createDebugUI(): void {
// Create debug panel
this.debugElement = document.createElement('div');
this.debugElement.id = 'debug-console';
this.debugElement.style.cssText = `
position: fixed;
top: 0;
right: 0;
width: 50%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
color: #00ff00;
font-family: 'Courier New', monospace;
font-size: 12px;
z-index: 10000;
display: none;
border-left: 2px solid #00ff00;
`;
// Create header
const header = document.createElement('div');
header.style.cssText = `
padding: 10px;
background: rgba(0, 255, 0, 0.1);
border-bottom: 1px solid #00ff00;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
`;
header.innerHTML = `
🐛 DUMBER DEBUG CONSOLE
F11: Inspect Services | F12: Toggle
`;
// Create logs container
this.logsContainer = document.createElement('div');
this.logsContainer.style.cssText = `
height: calc(100% - 50px);
overflow-y: auto;
padding: 10px;
`;
this.debugElement.appendChild(header);
this.debugElement.appendChild(this.logsContainer);
document.body.appendChild(this.debugElement);
// Setup button handlers
header.querySelector('#clear-logs')?.addEventListener('click', () => this.clearLogs());
header.querySelector('#close-debug')?.addEventListener('click', () => this.hide());
}
private setupKeyboardShortcut(): void {
document.addEventListener('keydown', (event) => {
// F12 to toggle debug console
if (event.key === 'F12') {
event.preventDefault();
this.toggle();
}
});
}
private interceptConsole(): void {
const originalConsole = {
log: console.log,
warn: console.warn,
error: console.error,
info: console.info,
debug: console.debug
};
console.log = (...args) => {
this.addLog('info', 'frontend', 'console', args.join(' '));
originalConsole.log(...args);
};
console.warn = (...args) => {
this.addLog('warn', 'frontend', 'console', args.join(' '));
originalConsole.warn(...args);
};
console.error = (...args) => {
this.addLog('error', 'frontend', 'console', args.join(' '));
originalConsole.error(...args);
};
console.info = (...args) => {
this.addLog('info', 'frontend', 'console', args.join(' '));
originalConsole.info(...args);
};
console.debug = (...args) => {
this.addLog('debug', 'frontend', 'console', args.join(' '));
originalConsole.debug(...args);
};
}
public addLog(level: DebugLogEntry['level'], source: DebugLogEntry['source'], category: string, message: string, data?: any): void {
const entry: DebugLogEntry = {
timestamp: new Date().toISOString().split('T')[1].split('.')[0], // HH:MM:SS format
level,
source,
category,
message,
data
};
this.logs.push(entry);
// Keep only the last maxLogs entries
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
this.updateDisplay();
}
private updateDisplay(): void {
if (!this.logsContainer) return;
const logHTML = this.logs.map(log => {
const levelColor = {
info: '#00ff00',
warn: '#ffff00',
error: '#ff0000',
debug: '#00ffff'
}[log.level];
const sourceIcon = log.source === 'backend' ? '🖥️' : '🌐';
return `
[${log.timestamp}]
${sourceIcon} ${log.level.toUpperCase()}
[${log.category}]
${log.message}
${log.data ? `
${JSON.stringify(log.data, null, 2)}` : ''}
`;
}).join('');
this.logsContainer.innerHTML = logHTML;
// Auto-scroll to bottom
this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
}
public logKeyboardEvent(event: KeyboardEvent): void {
const modifiers = [];
if (event.ctrlKey) modifiers.push('Ctrl');
if (event.shiftKey) modifiers.push('Shift');
if (event.altKey) modifiers.push('Alt');
if (event.metaKey) modifiers.push('Meta');
const shortcut = modifiers.length > 0 ? `${modifiers.join('+')}+${event.key}` : event.key;
this.addLog('info', 'frontend', 'keyboard', `Key pressed: ${shortcut}`, {
key: event.key,
code: event.code,
modifiers: modifiers,
target: event.target?.constructor.name
});
}
public logMouseEvent(event: MouseEvent): void {
const buttonNames = {
0: 'Left',
1: 'Middle',
2: 'Right',
3: 'Back',
4: 'Forward'
};
this.addLog('info', 'frontend', 'mouse', `Mouse ${event.type}: ${buttonNames[event.button] || `Button${event.button}`}`, {
button: event.button,
clientX: event.clientX,
clientY: event.clientY,
target: event.target?.constructor.name
});
}
public logServiceCall(serviceName: string, methodName: string, params: any[], result?: any, error?: Error): void {
if (error) {
this.addLog('error', 'frontend', 'service', `${serviceName}.${methodName}() failed`, {
params,
error: error.message
});
} else {
this.addLog('info', 'frontend', 'service', `${serviceName}.${methodName}() called`, {
params,
result
});
}
}
public logBackendMessage(message: string, data?: any): void {
this.addLog('info', 'backend', 'service', message, data);
}
public show(): void {
if (this.debugElement) {
this.debugElement.style.display = 'block';
this.isVisible = true;
this.addLog('info', 'frontend', 'debug', 'Debug console opened');
}
}
public hide(): void {
if (this.debugElement) {
this.debugElement.style.display = 'none';
this.isVisible = false;
}
}
public toggle(): void {
if (this.isVisible) {
this.hide();
} else {
this.show();
}
}
public clearLogs(): void {
this.logs = [];
this.updateDisplay();
this.addLog('info', 'frontend', 'debug', 'Logs cleared');
}
public isDebugVisible(): boolean {
return this.isVisible;
}
}