(() => { "use strict"; if (window.HockeyPerformance) return; const metrics = { script_started_ms: performance.now() }; let errors = 0; let readySent = false; function post(event, message = "", values = {}) { const payload = { event, build: String(window.UI_BUILDER_BOOT?.buildVersion || ""), message: String(message).slice(0, 2000), path: location.pathname, metrics: values, }; // A diagnostic must not slow first paint or produce a diagnostic loop. fetch("/api/hockey/client-events", { method: "POST", credentials: "same-origin", cache: "no-store", keepalive: true, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }).catch(() => {}); } window.HockeyPerformance = { mark(name, value) { if (Number.isFinite(value)) metrics[name] = value; }, async measure(name, work) { const start = performance.now(); try { return await work(); } finally { metrics[name] = performance.now() - start; } }, getMetrics() { return { ...metrics }; }, reportError(message) { if (errors++ < 10) post("error", message); }, }; window.addEventListener("hockey:runtime-ready", () => { if (readySent) return; readySent = true; metrics.runtime_ready_ms = performance.now(); const navigation = performance.getEntriesByType("navigation")[0]; if (navigation) { metrics.server_response_ms = Math.max(0, navigation.responseStart - navigation.requestStart); metrics.document_download_ms = Math.max(0, navigation.responseEnd - navigation.responseStart); metrics.connection_ms = Math.max(0, navigation.connectEnd - navigation.connectStart); metrics.dom_interactive_ms = Math.max(0, navigation.domInteractive); } const resources = performance.getEntriesByType("resource"); metrics.resource_count = resources.length; metrics.transfer_bytes = resources.reduce((sum, item) => sum + (item.transferSize || 0), 0); metrics.decoded_bytes = resources.reduce((sum, item) => sum + (item.decodedBodySize || 0), 0); metrics.cached_resources = resources.filter(item => item.transferSize === 0 && item.decodedBodySize > 0).length; const send = () => { metrics.first_ready_paint_ms = performance.now(); post("ready", "Интерфейс готов к работе", { ...metrics }); }; // Two frames include a paint, with a fallback for an inactive browser tab. let sent = false; const once = () => { if (!sent) { sent = true; send(); } }; requestAnimationFrame(() => requestAnimationFrame(once)); setTimeout(once, 1000); }); window.addEventListener("error", event => { if (errors++ >= 10) return; const file = String(event.filename || "").split("?")[0]; post("error", `${event.message || "Ошибка браузера"} · ${file}:${event.lineno || 0}:${event.colno || 0}`); }); window.addEventListener("unhandledrejection", event => { if (errors++ >= 10) return; post("unhandledrejection", String(event.reason?.message || event.reason || "Необработанная ошибка")); }); })();