Skip to content

Commit 3ca7008

Browse files
committed
add experimental integration
1 parent e386281 commit 3ca7008

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-0
lines changed

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export {
5656
moduleMetadataIntegration,
5757
zodErrorsIntegration,
5858
thirdPartyErrorFilterIntegration,
59+
_experimentalDomainBasedErrorsFilterIntegration,
5960
} from '@sentry/core';
6061
export type { Span } from '@sentry/core';
6162
export { makeBrowserOfflineTransport } from './transports/offline';

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export { extraErrorDataIntegration } from './integrations/extraerrordata';
108108
export { rewriteFramesIntegration } from './integrations/rewriteframes';
109109
export { zodErrorsIntegration } from './integrations/zoderrors';
110110
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter';
111+
export { _experimentalDomainBasedErrorsFilterIntegration } from './integrations/domain-based-errors-filter';
111112
export { profiler } from './profiling';
112113
export { instrumentFetchRequest } from './fetch';
113114
export { trpcMiddleware } from './trpc';
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { defineIntegration } from '../integration';
2+
import type { StackFrame } from '../types-hoist';
3+
import { GLOBAL_OBJ, isErrorEvent } from '../utils-hoist';
4+
import { getFramesFromEvent } from '../utils-hoist/stacktrace';
5+
6+
type DomainBasedErrorsFilterOptions = {
7+
/**
8+
* List of domains that are considered "first-party" (your application domains).
9+
* Errors from these domains will not be filtered.
10+
* Example: ['myapp.com', 'cdn.myapp.com']
11+
*/
12+
appDomains: string[];
13+
14+
/**
15+
* List of third-party domains that should be allowed despite not being in appDomains.
16+
* Errors from these domains will not be filtered.
17+
*
18+
*/
19+
allowlistedDomains?: string[];
20+
21+
/**
22+
* Defines how the integration should behave with third-party errors.
23+
*
24+
* - `drop-error-if-contains-third-party-frames`: Drop error events that contain at least one third-party stack frame.
25+
* - `drop-error-if-exclusively-contains-third-party-frames`: Drop error events that exclusively contain third-party stack frames.
26+
* - `apply-tag-if-contains-third-party-frames`: Keep all error events, but apply a `third_party_domain: true` tag in case the error contains at least one third-party stack frame.
27+
* - `apply-tag-if-exclusively-contains-third-party-frames`: Keep all error events, but apply a `third_party_domain: true` tag in case the error exclusively contains third-party stack frames.
28+
*/
29+
behaviour:
30+
| 'drop-error-if-contains-third-party-frames'
31+
| 'drop-error-if-exclusively-contains-third-party-frames'
32+
| 'apply-tag-if-contains-third-party-frames'
33+
| 'apply-tag-if-exclusively-contains-third-party-frames';
34+
35+
/**
36+
* Whether to apply the `is_external` flag to stack frames from third-party domains.
37+
*
38+
* Default: `false`
39+
*/
40+
applyIsExternalFrameFlag?: boolean;
41+
};
42+
43+
export const _experimentalDomainBasedErrorsFilterIntegration = defineIntegration(
44+
(options: DomainBasedErrorsFilterOptions) => {
45+
const isRunningOnLocalhost = (): boolean => {
46+
// Check if we're in a browser environment
47+
const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
48+
if (WINDOW?.location?.href) {
49+
const href = WINDOW.location.href;
50+
51+
// todo: add a more advanced check
52+
if (href.includes('://localhost:') || href.includes('://127.0.0.1')) {
53+
return true;
54+
}
55+
}
56+
57+
return false;
58+
};
59+
60+
const isLocalhost = isRunningOnLocalhost();
61+
62+
return {
63+
name: '_experimentalDomainBasedErrorsFilter',
64+
processEvent(event) {
65+
// skip for non error events and locally running apps
66+
if (isLocalhost || !isErrorEvent(event)) {
67+
return event;
68+
}
69+
70+
const frames = getFramesFromEvent(event);
71+
if (!frames || frames.length === 0) {
72+
return event;
73+
}
74+
75+
// collect firstParty domains
76+
// todo: get a sensible default, maybe href + subdomains
77+
const appDomains = options.appDomains || [];
78+
79+
// todo: merge this list with clientOptions.allowUrls
80+
const allowlistedDomains = options.allowlistedDomains || [];
81+
82+
let hasThirdPartyFrames = false;
83+
let allFramesAreThirdParty = true;
84+
85+
frames.forEach(frame => {
86+
// todo: check abs_path or filename here?
87+
if (frame.abs_path) {
88+
try {
89+
const url = new URL(frame.abs_path);
90+
const domain = url.hostname;
91+
92+
const isExternal = isThirdPartyDomain(domain, appDomains, allowlistedDomains);
93+
94+
// Add is_external flag to the frame
95+
if (options.applyIsExternalFrameFlag) {
96+
(frame as StackFrame & { is_external?: boolean }).is_external = isExternal;
97+
}
98+
99+
if (isExternal) {
100+
hasThirdPartyFrames = true;
101+
} else {
102+
allFramesAreThirdParty = false;
103+
}
104+
} catch (e) {
105+
// can't get URL
106+
allFramesAreThirdParty = false;
107+
}
108+
} else {
109+
// No abs path
110+
allFramesAreThirdParty = false;
111+
}
112+
});
113+
114+
let applyTag = false;
115+
116+
if (hasThirdPartyFrames) {
117+
if (options.behaviour === 'drop-error-if-contains-third-party-frames') {
118+
return null;
119+
}
120+
if (options.behaviour === 'apply-tag-if-contains-third-party-frames') {
121+
applyTag = true;
122+
}
123+
}
124+
125+
if (allFramesAreThirdParty) {
126+
if (options.behaviour === 'drop-error-if-exclusively-contains-third-party-frames') {
127+
return null;
128+
}
129+
if (options.behaviour === 'apply-tag-if-exclusively-contains-third-party-frames') {
130+
applyTag = true;
131+
}
132+
}
133+
134+
if (applyTag) {
135+
event.tags = {
136+
...event.tags,
137+
third_party_code: true,
138+
};
139+
}
140+
141+
return event;
142+
},
143+
};
144+
},
145+
);
146+
147+
const isThirdPartyDomain = (domain: string, appDomains: string[], allowlistedDomains: string[]): boolean => {
148+
const isAppDomain = appDomains.some(appDomain => domain === appDomain || domain.endsWith(`.${appDomain}`));
149+
150+
if (isAppDomain) {
151+
return false;
152+
}
153+
154+
// todo: extend this check also check for regexes
155+
const isAllowlisted = allowlistedDomains?.some(
156+
allowedDomain => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`),
157+
);
158+
159+
return !isAllowlisted;
160+
};

packages/core/src/integrations/eventFilters.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ function _shouldDropEvent(event: Event, options: Partial<EventFiltersOptions>):
148148
);
149149
return true;
150150
}
151+
151152
return false;
152153
}
153154

0 commit comments

Comments
 (0)