Skip to content

Commit bf52938

Browse files
authored
feat: Make dedupe integration default for browser (#3730)
* feat: Make dedupe integration default for browser * ref: Also add dedupe as default integration
1 parent 0b42582 commit bf52938

File tree

3 files changed

+204
-1
lines changed

3 files changed

+204
-1
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { Event, EventProcessor, Exception, Hub, Integration, StackFrame } from '@sentry/types';
2+
3+
/** Deduplication filter */
4+
export class Dedupe implements Integration {
5+
/**
6+
* @inheritDoc
7+
*/
8+
public static id: string = 'Dedupe';
9+
10+
/**
11+
* @inheritDoc
12+
*/
13+
public name: string = Dedupe.id;
14+
15+
/**
16+
* @inheritDoc
17+
*/
18+
private _previousEvent?: Event;
19+
20+
/**
21+
* @inheritDoc
22+
*/
23+
public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
24+
addGlobalEventProcessor((currentEvent: Event) => {
25+
const self = getCurrentHub().getIntegration(Dedupe);
26+
if (self) {
27+
// Juuust in case something goes wrong
28+
try {
29+
if (self._shouldDropEvent(currentEvent, self._previousEvent)) {
30+
return null;
31+
}
32+
} catch (_oO) {
33+
return (self._previousEvent = currentEvent);
34+
}
35+
36+
return (self._previousEvent = currentEvent);
37+
}
38+
return currentEvent;
39+
});
40+
}
41+
42+
/** JSDoc */
43+
private _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean {
44+
if (!previousEvent) {
45+
return false;
46+
}
47+
48+
if (this._isSameMessageEvent(currentEvent, previousEvent)) {
49+
return true;
50+
}
51+
52+
if (this._isSameExceptionEvent(currentEvent, previousEvent)) {
53+
return true;
54+
}
55+
56+
return false;
57+
}
58+
59+
/** JSDoc */
60+
private _isSameMessageEvent(currentEvent: Event, previousEvent: Event): boolean {
61+
const currentMessage = currentEvent.message;
62+
const previousMessage = previousEvent.message;
63+
64+
// If neither event has a message property, they were both exceptions, so bail out
65+
if (!currentMessage && !previousMessage) {
66+
return false;
67+
}
68+
69+
// If only one event has a stacktrace, but not the other one, they are not the same
70+
if ((currentMessage && !previousMessage) || (!currentMessage && previousMessage)) {
71+
return false;
72+
}
73+
74+
if (currentMessage !== previousMessage) {
75+
return false;
76+
}
77+
78+
if (!this._isSameFingerprint(currentEvent, previousEvent)) {
79+
return false;
80+
}
81+
82+
if (!this._isSameStacktrace(currentEvent, previousEvent)) {
83+
return false;
84+
}
85+
86+
return true;
87+
}
88+
89+
/** JSDoc */
90+
private _getFramesFromEvent(event: Event): StackFrame[] | undefined {
91+
const exception = event.exception;
92+
93+
if (exception) {
94+
try {
95+
// @ts-ignore Object could be undefined
96+
return exception.values[0].stacktrace.frames;
97+
} catch (_oO) {
98+
return undefined;
99+
}
100+
} else if (event.stacktrace) {
101+
return event.stacktrace.frames;
102+
}
103+
return undefined;
104+
}
105+
106+
/** JSDoc */
107+
private _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean {
108+
let currentFrames = this._getFramesFromEvent(currentEvent);
109+
let previousFrames = this._getFramesFromEvent(previousEvent);
110+
111+
// If neither event has a stacktrace, they are assumed to be the same
112+
if (!currentFrames && !previousFrames) {
113+
return true;
114+
}
115+
116+
// If only one event has a stacktrace, but not the other one, they are not the same
117+
if ((currentFrames && !previousFrames) || (!currentFrames && previousFrames)) {
118+
return false;
119+
}
120+
121+
currentFrames = currentFrames as StackFrame[];
122+
previousFrames = previousFrames as StackFrame[];
123+
124+
// If number of frames differ, they are not the same
125+
if (previousFrames.length !== currentFrames.length) {
126+
return false;
127+
}
128+
129+
// Otherwise, compare the two
130+
for (let i = 0; i < previousFrames.length; i++) {
131+
const frameA = previousFrames[i];
132+
const frameB = currentFrames[i];
133+
134+
if (
135+
frameA.filename !== frameB.filename ||
136+
frameA.lineno !== frameB.lineno ||
137+
frameA.colno !== frameB.colno ||
138+
frameA.function !== frameB.function
139+
) {
140+
return false;
141+
}
142+
}
143+
144+
return true;
145+
}
146+
147+
/** JSDoc */
148+
private _getExceptionFromEvent(event: Event): Exception | undefined {
149+
return event.exception && event.exception.values && event.exception.values[0];
150+
}
151+
152+
/** JSDoc */
153+
private _isSameExceptionEvent(currentEvent: Event, previousEvent: Event): boolean {
154+
const previousException = this._getExceptionFromEvent(previousEvent);
155+
const currentException = this._getExceptionFromEvent(currentEvent);
156+
157+
if (!previousException || !currentException) {
158+
return false;
159+
}
160+
161+
if (previousException.type !== currentException.type || previousException.value !== currentException.value) {
162+
return false;
163+
}
164+
165+
if (!this._isSameFingerprint(currentEvent, previousEvent)) {
166+
return false;
167+
}
168+
169+
if (!this._isSameStacktrace(currentEvent, previousEvent)) {
170+
return false;
171+
}
172+
173+
return true;
174+
}
175+
176+
/** JSDoc */
177+
private _isSameFingerprint(currentEvent: Event, previousEvent: Event): boolean {
178+
let currentFingerprint = currentEvent.fingerprint;
179+
let previousFingerprint = previousEvent.fingerprint;
180+
181+
// If neither event has a fingerprint, they are assumed to be the same
182+
if (!currentFingerprint && !previousFingerprint) {
183+
return true;
184+
}
185+
186+
// If only one event has a fingerprint, but not the other one, they are not the same
187+
if ((currentFingerprint && !previousFingerprint) || (!currentFingerprint && previousFingerprint)) {
188+
return false;
189+
}
190+
191+
currentFingerprint = currentFingerprint as string[];
192+
previousFingerprint = previousFingerprint as string[];
193+
194+
// Otherwise, compare the two
195+
try {
196+
return !!(currentFingerprint.join('') === previousFingerprint.join(''));
197+
} catch (_oO) {
198+
return false;
199+
}
200+
}
201+
}

packages/browser/src/integrations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { TryCatch } from './trycatch';
33
export { Breadcrumbs } from './breadcrumbs';
44
export { LinkedErrors } from './linkederrors';
55
export { UserAgent } from './useragent';
6+
export { Dedupe } from './dedupe';

packages/browser/src/sdk.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { addInstrumentationHandler, getGlobalObject, logger, SyncPromise } from
44
import { BrowserOptions } from './backend';
55
import { BrowserClient } from './client';
66
import { ReportDialogOptions, wrap as internalWrap } from './helpers';
7-
import { Breadcrumbs, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations';
7+
import { Breadcrumbs, Dedupe, GlobalHandlers, LinkedErrors, TryCatch, UserAgent } from './integrations';
88

99
export const defaultIntegrations = [
1010
new CoreIntegrations.InboundFilters(),
@@ -13,6 +13,7 @@ export const defaultIntegrations = [
1313
new Breadcrumbs(),
1414
new GlobalHandlers(),
1515
new LinkedErrors(),
16+
new Dedupe(),
1617
new UserAgent(),
1718
];
1819

0 commit comments

Comments
 (0)