PageSourceSearch

https://hairsense.de/assets/js/debug.js

js hairsense.de collected 2026-10-07 20:47:06 UTC 12,282 bytes, 308 lines download raw bytes

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 { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[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.