135 lines
4 KiB
JavaScript
135 lines
4 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
const MAX_ERRORS = 50;
|
|
const REPORT_ENDPOINT = '/api/client-errors';
|
|
let errorQueue = [];
|
|
let isReporting = false;
|
|
|
|
function formatError(error, context = {}) {
|
|
return {
|
|
type: error.name || 'Error',
|
|
message: error.message || String(error),
|
|
stack: error.stack,
|
|
url: window.location.href,
|
|
userAgent: navigator.userAgent,
|
|
timestamp: new Date().toISOString(),
|
|
context: context
|
|
};
|
|
}
|
|
|
|
async function reportErrors() {
|
|
if (isReporting || errorQueue.length === 0) return;
|
|
|
|
isReporting = true;
|
|
const errorsToReport = errorQueue.splice(0, MAX_ERRORS);
|
|
errorQueue = [];
|
|
|
|
try {
|
|
const response = await fetch(REPORT_ENDPOINT, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ errors: errorsToReport })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.warn('[ErrorReporter] Failed to send errors:', response.status);
|
|
}
|
|
} catch (e) {
|
|
console.warn('[ErrorReporter] Failed to send errors:', e.message);
|
|
errorQueue.unshift(...errorsToReport);
|
|
} finally {
|
|
isReporting = false;
|
|
|
|
if (errorQueue.length > 0) {
|
|
setTimeout(reportErrors, 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
function queueError(errorData) {
|
|
errorQueue.push(errorData);
|
|
if (errorQueue.length >= 10) {
|
|
reportErrors();
|
|
}
|
|
}
|
|
|
|
window.addEventListener('error', (event) => {
|
|
const errorData = formatError(event.error || new Error(event.message), {
|
|
filename: event.filename,
|
|
lineno: event.lineno,
|
|
colno: event.colno
|
|
});
|
|
queueError(errorData);
|
|
});
|
|
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
const errorData = formatError(event.reason || new Error(String(event.reason)), {
|
|
type: 'unhandledRejection'
|
|
});
|
|
queueError(errorData);
|
|
});
|
|
|
|
window.ErrorReporter = {
|
|
report: function(error, context) {
|
|
queueError(formatError(error, context));
|
|
},
|
|
flush: function() {
|
|
reportErrors();
|
|
}
|
|
};
|
|
|
|
if (document.readyState === 'complete') {
|
|
setTimeout(reportErrors, 1000);
|
|
} else {
|
|
window.addEventListener('load', () => {
|
|
setTimeout(reportErrors, 1000);
|
|
});
|
|
}
|
|
|
|
console.log('[ErrorReporter] Client-side error reporting initialized');
|
|
|
|
window.NavigationLogger = {
|
|
log: function(from, to, method) {
|
|
const navEvent = {
|
|
type: 'navigation',
|
|
from: from,
|
|
to: to,
|
|
method: method,
|
|
url: window.location.href,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
queueError({
|
|
name: 'Navigation',
|
|
message: `${method}: ${from} -> ${to}`,
|
|
stack: undefined
|
|
});
|
|
}
|
|
};
|
|
|
|
document.body.addEventListener('click', function(e) {
|
|
const target = e.target.closest('[data-section]');
|
|
if (target) {
|
|
const section = target.getAttribute('data-section');
|
|
const currentHash = window.location.hash.slice(1) || '';
|
|
if (section !== currentHash) {
|
|
setTimeout(() => {
|
|
window.NavigationLogger.log(currentHash || 'home', section, 'click');
|
|
}, 100);
|
|
}
|
|
}
|
|
}, true);
|
|
|
|
window.addEventListener('hashchange', function(e) {
|
|
const oldURL = new URL(e.oldURL);
|
|
const newURL = new URL(e.newURL);
|
|
const fromHash = oldURL.hash.slice(1) || '';
|
|
const toHash = newURL.hash.slice(1) || '';
|
|
window.NavigationLogger.log(fromHash || 'home', toHash, 'hashchange');
|
|
});
|
|
|
|
console.log('[NavigationLogger] Navigation tracking initialized');
|
|
})();
|