71 lines
3.1 KiB
JavaScript
71 lines
3.1 KiB
JavaScript
(() => {
|
|
"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 || "Необработанная ошибка"));
|
|
});
|
|
})();
|