Correctly capturing client-side errors in JavaScript, issues with extensions and cache

I’m working on a project where I need to capture client-side JavaScript errors accurately. My goal is to get the exact location or file where the error occurred. However, I’m encountering issues based on whether browser extensions are active or the cache is not cleared. Here’s what happens:

  • With extensions turned on and cache not cleared, I tend to capture
    information from my extensions instead of the actual error from my
    code.

  • When I clear the cache and turn off all extensions, the error is not
    captured as expected.

I’m trying to find a way to consistently capture the actual error regardless of the browser state. Below is the code snippet that handles error capturing and reporting:

getSourceCode(lineNumber) {
    return this.getInlineSourceCode(lineNumber) || this.getFullSourceWithHighlight(lineNumber);
}

getInlineSourceCode(lineNumber) {
    const scripts = document.querySelectorAll('script');
    for (let script of scripts) {
        if (!script.src) { 
            const scriptLines = script.textContent.split("n");
            const scriptPosition = this.getErrorPosition(script);
            if (lineNumber >= scriptPosition.startLine && lineNumber <= scriptPosition.endLine) {
                return this.highlightSource(scriptLines, lineNumber - scriptPosition.startLine);
            }
        }
    }
    return null;
}

getFullSourceWithHighlight(lineNumber) {
    const lines = document.documentElement.outerHTML.split("n");
    return this.highlightSource(lines, lineNumber - 1);
}

highlightSource(lines, lineNumber) {
    const start = Math.max(lineNumber - 2, 0);
    const end = Math.min(lineNumber + 2, lines.length - 1);
    lines[lineNumber] = '>> ' + lines[lineNumber] + ' <<'; 
    return lines.slice(start, end + 1).join("n");
}

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) => {
        this.reportError(message, url, lineNumber, column, error.stack, 'JavaScript Error');
        return false;
    };

    window.addEventListener('unhandledrejection', event => {
        this.reportError(event.reason.toString(), document.location.href, 0, 0, event.reason.stack, 'Promise Rejection');
    });
}

reportError(message, url, lineNumber, column, stack, type = 'JavaScript Error') {
    fetch(this.url ? `${location.protocol}//${this.url}/${this.apikey}` : window.location.href, {
        method: "post",
        headers: {
            'Accept': "application/json",
            'Content-Type': "application/json"
        },
        body: JSON.stringify({
            type,
            message,
            url,
            lineNumber,
            column,
            stack,
            location: window.location.href,
            source: this.getSourceCode(lineNumber)
        })
    }).then(response => response.json()).then(console.info);
}