108 lines
3.3 KiB
JavaScript

class ErrorHandler {
constructor(options) {
if (!options) throw new Error("Options not provided");
if (!options.url) throw new Error("URL is required");
if (!options.apikey) throw new Error("API key is required");
this.url = options.url;
this.apikey = options.apikey;
getErrorPosition(script) {
let totalLines = 0;
let element = script.previousElementSibling;
while (element) {
totalLines += (element.outerHTML || element.textContent).split("\n").length;
element = element.previousElementSibling;
}
return {
startLine: totalLines + 1,
endLine: totalLines + script.textContent.split("\n").length
};
}
catchError() {
window.onerror = (message, url, lineNumber, column, error) => {
let type = 'JavaScript Error';
if (message.indexOf(':')) {
type = message.substring(0, message.indexOf(':'));
message = message.substring(message.indexOf(':') + 1).trim();
}
this.reportError(message, url, lineNumber, column, error.stack, type);
return false;
};
window.addEventListener('unhandledrejection', event => {
this.reportError(event.reason.toString(), document.location.href, 0, 0, event.reason.stack, 'Promise Rejection');
});
this.createErrorAgain();
}
async reportError(message, url, line, column, stack, type = 'JavaScript Error') {
await new Promise(r => setTimeout(r, 2000));
fetch(this.url ? `${this.url}/${this.apikey}` : window.location.href, {
method: "POST",
headers: {
'Accept': "application/json",
'Content-Type': "application/json"
},
body: JSON.stringify({
type,
message,
url,
line,
code: column,
backtrace: stack ? stack.split("\n").filter((e) => !!e).map((e) => this.parseErrorLine(e)) : [],
file: window.location.href,
source: await this.getSourceCode(url, line),
})
}).then(response => response.json()).then(console.info);
}
async getSourceCode(url, errorLineNumber) {
try {
const response = await fetch(url);
const originalHtml = await response.text();
const lines = originalHtml.split('\n');
const start = Math.max(0, errorLineNumber - 5);
const end = Math.min(lines.length, errorLineNumber + 5);
return lines.slice(start, end).join('\n');
}
catch (error) {
console.error('Failed to fetch original HTML:', error);
}
}
parseErrorLine(errorLine) {
const obj = {};
// Pattern for: functionName@url:line:column
const pattern = /^(.*)@(.+?):(\d+):(\d+)$/;
const match = errorLine.match(pattern);
if (match) {
obj.function = match[1];
obj.file = match[2];
obj.line = match[3];
// column would be match[4] if needed
console.log(match);
}
// Remove undefined/empty values
Object.keys(obj).forEach(key => {
if (!obj[key]) delete obj[key];
});
return obj;
}
}