Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@
posthog._i.push([token, config, 'posthog']);
};

// Suppress only exceptions whose frames are all identifiable third-party code.
// Missing stacks and unfamiliar paths may be first-party failures: retain them.
// Drop an exception only when its stack points at third-party code alone.
// Native and anonymous frames name no source, so skip them.
// Any first-party or unknown frame keeps the exception, so first-party failures stay visible.
var OWN_HOST = String(window.location.hostname).toLowerCase();
var URL_HOST = /^(?:https?:)?\/\/([^/?#]+)/i;

function isNativeFrame(frame) {
// The browser reports built-in frames such as Array.reduce with no source file.
if (!frame || frame.in_app === true) return false;
var ref = frame.filename || frame.source;
return ref === '<anonymous>' || ref === '[native code]';
}

function isThirdPartyFrame(frame) {
// filename is the raw SDK location; source can be a host-stripped path.
var ref = frame && (frame.filename || frame.source);
Expand All @@ -49,13 +57,23 @@
return /^\/onsite\/js\//.test(ref);
}

function isThirdPartyEntry(entry) {
var frames = entry && entry.stacktrace && entry.stacktrace.frames;
if (!Array.isArray(frames) || frames.length === 0) return false;
var sawThirdParty = false;
for (var i = 0; i < frames.length; i++) {
var frame = frames[i];
if (isNativeFrame(frame)) continue;
if (!isThirdPartyFrame(frame)) return false;
sawThirdParty = true;
}
return sawThirdParty;
}

function isThirdPartyException(event) {
var list = event.properties && event.properties.$exception_list;
if (!Array.isArray(list) || list.length === 0) return false;
return list.every(function(entry) {
var frames = entry && entry.stacktrace && entry.stacktrace.frames;
return Array.isArray(frames) && frames.length > 0 && frames.every(isThirdPartyFrame);
});
return list.every(isThirdPartyEntry);
}

function beforeSend(event) {
Expand Down
13 changes: 13 additions & 0 deletions tests/analytics-third-party-filter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ describe('analytics.js before_send filter', () => {
assert.strictEqual(beforeSend(event), null);
});

it('drops a Klaviyo chunk failure that interleaves a native frame', () => {
const beforeSend = loadBeforeSend();
const event = exceptionEvent([
{ source: '/onsite/js/runtime.08a889cad67f0.js', in_app: true },
{ source: '/onsite/js/7130.b41c2e9f5a1d7.js', in_app: true },
{ function: 'Array.reduce', filename: '<anonymous>', in_app: false },
{ source: '/onsite/js/vendor.4d2f8a1c9b3e6.js', in_app: true },
{ filename: 'https://static.klaviyo.com/onsite/js/klaviyo.js?cb=3' },
{ source: '/onsite/js/onsite.2c7e9a4f1d8b0.js', in_app: true }
]);
assert.strictEqual(beforeSend(event), null);
});

it('drops third-party frames served from a foreign host', () => {
const beforeSend = loadBeforeSend();
const event = exceptionEvent([
Expand Down
Loading