1/* ========================================================= 2 HAIRSENSE â Debug-Tool 3 --------------------------------------------------------- 4 Aktivierung (drei Wege): 5 1) URL: ?debug=on â einschalten und merken 6 ?debug=off â ausschalten 7 2) Tastatur: Strg+Shift+D (auf Mac: Cmd+Shift+D) 8 â erstes Mal: einschalten + Panel öffnen 9 â folgende Male: Panel öffnen/schlieÃen 10 3) Konsole: HSDebug.enable() / HSDebug.disable() 11 12 Wenn aktiv: 13 - Alle window-Fehler und Promise-Rejections werden gelogged 14 - console.error wird gespiegelt 15 - Schlüsselereignisse aus Auth/Orders/Chat werden gelogged 16 - Kleiner Indikator unten rechts ("D" in Camel-Farbe) 17 - Panel mit Download-Button (JSON für E-Mail an den Support) 18 19 Speicherung: localStorage 20 hs_debug â "1" oder leer (Aktiv-Flag) 21 hs_debug_log â JSON-Array, max. 200 Einträge (rotierend) 22 ========================================================= */ 23(function () { 24 'use strict'; 25 26 var STORE_FLAG = 'hs_debug'; 27 var STORE_LOG = 'hs_debug_log'; 28 var MAX_ENTRIES = 200; 29 30 // --- State -------------------------------------------------- 31 var enabled = false; 32 try { enabled = localStorage.getItem(STORE_FLAG) === '1'; } catch (e) {} 33 34 // URL-Parameter zum Umschalten (überschreibt aktuellen State) 35 try { 36 var qp = new URLSearchParams(window.location.search); 37 if (qp.has('debug')) { 38 var v = (qp.get('debug') || '').toLowerCase(); 39 if (v === 'on' || v === '1' || v === 'true') { 40 enabled = true; 41 localStorage.setItem(STORE_FLAG, '1'); 42 } else if (v === 'off' || v === '0' || v === 'false') { 43 enabled = false; 44 localStorage.removeItem(STORE_FLAG); 45 } 46 } 47 } catch (e) {} 48 49 // --- Storage ------------------------------------------------ 50 function readLog() { 51 try { return JSON.parse(localStorage.getItem(STORE_LOG) || '[]'); } 52 catch (e) { return []; } 53 } 54 function writeLog(arr) { 55 try { 56 if (arr.length > MAX_ENTRIES) arr = arr.slice(-MAX_ENTRIES); 57 localStorage.setItem(STORE_LOG, JSON.stringify(arr)); 58 } catch (e) { 59 // Speicher voll oder blockiert â Log zurücksetzen 60 try { localStorage.removeItem(STORE_LOG); } catch (_) {} 61 } 62 } 63 64 // --- Helpers ------------------------------------------------ 65 function escapeHtml(s) { 66 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { 67 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; 68 }); 69 } 70 function pageName() { 71 try { 72 var p = window.location.pathname.replace(/\/+$/, ''); 73 return p.split('/').pop() || 'index.html'; 74 } catch (e) { return '?'; } 75 } 76 function safeStringify(o) { 77 try { return JSON.stringify(o, function (k, v) { 78 if (v instanceof Error) return { name: v.name, message: v.message, stack: v.stack }; 79 return v; 80 }); } catch (e) { return '[unserializable]'; } 81 } 82 83 // --- Core log ---------------------------------------------- 84 function log(category, message, data) { 85 if (!enabled) return; 86 var entry = { 87 ts: new Date().toISOString(), 88 cat: String(category || 'info'), 89 msg: String(message == null ? '' : message), 90 data: data === undefined ? null : data, 91 page: pageName() 92 }; 93 var arr = readLog(); 94 arr.push(entry); 95 writeLog(arr); 96 try { 97 var prefix = '[HS:' + entry.cat + ']'; 98 if (data !== undefined) { 99 (window._origLog || console.log).call(console, prefix, message, data); 100 } else { 101 (window._origLog || console.log).call(console, prefix, message); 102 } 103 } catch (_) {} 104 } 105 106 // --- Globale Fehlerfänger ----------------------------------- 107 window.addEventListener('error', function (e) { 108 log('error', e.message || 'Unbekannter Fehler', { 109 file: e.filename, line: e.lineno, col: e.colno, 110 stack: e.error && e.error.stack 111 }); 112 }); 113 window.addEventListener('unhandledrejection', function (e) { 114 log('error', 'Unhandled promise rejection', { 115 reason: e.reason && (e.reason.message || String(e.reason)), 116 stack: e.reason && e.reason.stack 117 }); 118 }); 119 120 // console.error spiegeln (ohne ihn zu unterdrücken) 121 window._origLog = console.log; 122 var origConsoleError = console.error; 123 console.error = function () { 124 if (enabled) {
125 try { 126 var parts = []; 127 for (var i = 0; i < arguments.length; i++) { 128 var a = arguments[i]; 129 parts.push(typeof a === 'string' ? a : safeStringify(a)); 130 } 131 log('console', parts.join(' ')); 132 } catch (_) {} 133 } 134 return origConsoleError.apply(console, arguments); 135 }; 136 137 // --- Download als JSON -------------------------------------- 138 function downloadLog() { 139 var bundle = { 140 hairsenseVersion: window.HS_VERSION || 'unknown', 141 generatedAt: new Date().toISOString(), 142 userAgent: (typeof navigator !== 'undefined' && navigator.userAgent) || '', 143 url: window.location.href, 144 entries: readLog() 145 }; 146 var blob = new Blob([JSON.stringify(bundle, null, 2)], { type: 'application/json' }); 147 var url = URL.createObjectURL(blob); 148 var ts = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); 149 var a = document.createElement('a'); 150 a.href = url; 151 a.download = 'hairsense-debug-' + ts + '.json'; 152 document.body.appendChild(a); 153 a.click(); 154 document.body.removeChild(a); 155 setTimeout(function () { URL.revokeObjectURL(url); }, 1000); 156 } 157 158 // --- Panel -------------------------------------------------- 159 function buildPanelHtml() { 160 var entries = readLog(); 161 var ver = window.HS_VERSION || '?'; 162 var rows; 163 if (entries.length === 0) { 164 rows = '<div style="opacity:0.5; padding:24px 0; text-align:center;">Noch keine Log-Einträge.</div>'; 165 } else { 166 var colors = { 167 error: '#E07B7B', 168 auth: '#9CB4D9', 169 order: '#D9C3A2', 170 chat: '#A8D9A8', 171 nav: '#D9C3A2', 172 system: '#FFFFFF', 173 console: '#E07B7B', 174 info: '#FFFFFF' 175 }; 176 rows = entries.slice().reverse().map(function (e) { 177 var color = colors[e.cat] || '#FFFFFF'; 178 var dataBlock = (e.data && JSON.stringify(e.data) !== 'null') 179 ? '<pre style="margin:4px 0 0; opacity:0.7; font-size:11px; white-space:pre-wrap; word-break:break-word;">' 180 + escapeHtml(safeStringify(e.data)) + '</pre>' 181 : ''; 182 return '<div style="padding:8px 0; border-bottom:1px solid #2a2e36;">' 183 + '<div style="display:flex; justify-content:space-between; gap:8px;">' 184 + '<span style="color:' + color + '; font-weight:600;">[' + escapeHtml(e.cat) + ']</span>' 185 + '<span style="opacity:0.5; font-size:11px;">' + escapeHtml(String(e.ts).slice(11, 19)) + ' · ' + escapeHtml(e.page) + '</span>' 186 + '</div>' 187 + '<div style="margin-top:2px; word-break:break-word;">' + escapeHtml(e.msg) + '</div>' 188 + dataBlock 189 + '</div>'; 190 }).join(''); 191 } 192 return '' 193 + '<div style="padding:10px 14px; background:#0a0d12; border-bottom:1px solid #4F3520; display:flex; justify-content:space-between; align-items:center; flex-shrink:0;">' 194 + '<span style="font-weight:600; letter-spacing:0.05em;">HS Debug · v' + escapeHtml(ver) + ' · ' + entries.length + ' Einträge</span>' 195 + '<div style="display:flex; gap:6px;">' 196 + '<button data-act="download" style="background:#8B5E3C; border:none; color:#fff; padding:5px 10px; border-radius:3px; cursor:pointer; font:11px monospace;">Download</button>' 197 + '<button data-act="clear" style="background:transparent; border:1px solid #797B82; color:#fff; padding:5px 10px; border-radius:3px; cursor:pointer; font:11px monospace;">Leeren</button>' 198 + '<button data-act="close" style="background:transparent; border:1px solid #797B82; color:#fff; padding:5px 10px; border-radius:3px; cursor:pointer; font:11px monospace;">Ã</button>' 199 + '</div>' 200 + '</div>' 201 + '<div style="flex:1; overflow-y:auto; padding:8px 14px;">' + rows + '</div>'; 202 } 203 204 function showPanel() { 205 var existing = document.getElementById('hsDebugPanel'); 206 if (existing) { existing.innerHTML = buildPanelHtml(); wireButtons(existing); return; } 207 var panel = document.createElement('div'); 208 panel.id = 'hsDebugPanel'; 209 panel.style.cssText = 'position:fixed;
209 right:20px; bottom:64px; width:520px; max-width:92vw; height:60vh; max-height:600px; background:#14181F; color:#F7F4EC; border:1px solid #4F3520; box-shadow:0 8px 32px rgba(0,0,0,0.5); border-radius:6px; z-index:99999; font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; display:flex; flex-direction:column; overflow:hidden;'; 210 panel.innerHTML = buildPanelHtml(); 211 document.body.appendChild(panel); 212 wireButtons(panel); 213 } 214 function wireButtons(panel) { 215 var btns = panel.querySelectorAll('button[data-act]'); 216 btns.forEach(function (b) { 217 b.onclick = function () { 218 var act = b.getAttribute('data-act'); 219 if (act === 'download') downloadLog(); 220 else if (act === 'clear') { localStorage.removeItem(STORE_LOG); log('system', 'Log geleert'); panel.innerHTML = buildPanelHtml(); wireButtons(panel); } 221 else if (act === 'close') closePanel(); 222 }; 223 }); 224 } 225 function closePanel() { 226 var p = document.getElementById('hsDebugPanel'); 227 if (p) p.remove(); 228 } 229 function togglePanel() { 230 if (document.getElementById('hsDebugPanel')) closePanel(); 231 else showPanel(); 232 } 233 234 // --- Indikator (unten rechts) ------------------------------- 235 function showIndicator() { 236 if (document.getElementById('hsDebugIndicator')) return; 237 var ind = document.createElement('div'); 238 ind.id = 'hsDebugIndicator'; 239 ind.title = 'Debug aktiv â Klick öffnet Panel · Strg/Cmd+Shift+D'; 240 ind.textContent = 'D'; 241 ind.style.cssText = 'position:fixed; right:14px; bottom:14px; width:38px; height:38px; border-radius:50%; background:#8B5E3C; color:#fff; display:flex; align-items:center; justify-content:center; font:600 14px ui-monospace,Menlo,monospace; cursor:pointer; z-index:99998; box-shadow:0 2px 12px rgba(0,0,0,0.35); user-select:none;'; 242 ind.onclick = togglePanel; 243 document.body.appendChild(ind); 244 } 245 function hideIndicator() { 246 var ind = document.getElementById('hsDebugIndicator'); 247 if (ind) ind.remove(); 248 } 249 250 // --- Tastatur-Shortcut -------------------------------------- 251 document.addEventListener('keydown', function (e) { 252 if (e.shiftKey && (e.ctrlKey || e.metaKey) && (e.key === 'D' || e.key === 'd')) { 253 e.preventDefault(); 254 if (!enabled) { 255 enabled = true; 256 try { localStorage.setItem(STORE_FLAG, '1'); } catch (_) {} 257 log('system', 'Debug aktiviert via Tastenkürzel'); 258 showIndicator(); 259 showPanel(); 260 } else { 261 togglePanel(); 262 } 263 } 264 }); 265 266 // --- Init --------------------------------------------------- 267 function init() { 268 if (enabled) { 269 showIndicator(); 270 log('nav', 'Seite geladen', { 271 page: window.location.pathname, 272 protocol: window.location.protocol, 273 version: window.HS_VERSION || null, 274 referrer: document.referrer || null 275 }); 276 } 277 } 278 if (document.readyState === 'loading') { 279 document.addEventListener('DOMContentLoaded', init); 280 } else { 281 init(); 282 } 283 284 // --- Public API --------------------------------------------- 285 window.HSDebug = { 286 enable: function () { 287 enabled = true; 288 try { localStorage.setItem(STORE_FLAG, '1'); } catch (_) {} 289 log('system', 'Debug aktiviert'); 290 showIndicator(); 291 }, 292 disable: function () { 293 log('system', 'Debug deaktiviert'); 294 enabled = false; 295 try { localStorage.removeItem(STORE_FLAG); } catch (_) {} 296 hideIndicator(); 297 closePanel(); 298 }, 299 isEnabled: function () { return enabled; }, 300 log: log, 301 getLogs: readLog, 302 clearLogs: function () { localStorage.removeItem(STORE_LOG); }, 303 download: downloadLog, 304 show: showPanel, 305 close: closePanel, 306 toggle: togglePanel 307 }; 308})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.