Skip to content

Commit 8578a89

Browse files
author
Luca Forstner
authored
Merge branch 'develop' into lforst-sourcemap-debug-meta
2 parents 2da12d1 + f8047f6 commit 8578a89

30 files changed

+298
-52
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"packages/react",
5353
"packages/remix",
5454
"packages/replay",
55+
"packages/replay-worker",
5556
"packages/serverless",
5657
"packages/svelte",
5758
"packages/tracing",

packages/browser/src/integrations/dedupe.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export class Dedupe implements Integration {
2323
*/
2424
public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
2525
const eventProcessor: EventProcessor = currentEvent => {
26+
// We want to ignore any non-error type events, e.g. transactions or replays
27+
// These should never be deduped, and also not be compared against as _previousEvent.
28+
if (currentEvent.type) {
29+
return currentEvent;
30+
}
31+
2632
const self = getCurrentHub().getIntegration(Dedupe);
2733
if (self) {
2834
// Juuust in case something goes wrong

packages/browser/test/integration/suites/api.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,26 @@ describe('API', function () {
112112

113113
// Same exceptions, different stacktrace (different line number), don't dedupe
114114
throwSameConsecutiveErrors('bar');
115+
116+
// Same exception, with transaction in between, dedupe
117+
throwError();
118+
Sentry.captureEvent({
119+
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
120+
message: 'someMessage',
121+
transaction: 'wat',
122+
type: 'transaction',
123+
});
124+
throwError();
115125
}).then(function (summary) {
126+
// We have a length of one here since transactions don't go through beforeSend
127+
// and we add events to summary in beforeSend
128+
assert.equal(summary.events.length, 6);
116129
assert.match(summary.events[0].exception.values[0].value, /Exception no \d+/);
117130
assert.match(summary.events[1].exception.values[0].value, /Exception no \d+/);
118131
assert.equal(summary.events[2].exception.values[0].value, 'foo');
119132
assert.equal(summary.events[3].exception.values[0].value, 'bar');
120133
assert.equal(summary.events[4].exception.values[0].value, 'bar');
134+
assert.equal(summary.events[5].exception.values[0].value, 'foo');
121135
});
122136
});
123137

packages/integrations/src/dedupe.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export class Dedupe implements Integration {
2323
*/
2424
public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
2525
const eventProcessor: EventProcessor = currentEvent => {
26+
// We want to ignore any non-error type events, e.g. transactions or replays
27+
// These should never be deduped, and also not be compared against as _previousEvent.
28+
if (currentEvent.type) {
29+
return currentEvent;
30+
}
31+
2632
const self = getCurrentHub().getIntegration(Dedupe);
2733
if (self) {
2834
// Juuust in case something goes wrong

packages/integrations/test/dedupe.test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types';
1+
import type { Event as SentryEvent, EventProcessor, Exception, Hub, StackFrame, Stacktrace } from '@sentry/types';
22

3-
import { _shouldDropEvent } from '../src/dedupe';
3+
import { _shouldDropEvent, Dedupe } from '../src/dedupe';
44

55
type EventWithException = SentryEvent & {
66
exception: {
@@ -175,4 +175,48 @@ describe('Dedupe', () => {
175175
expect(_shouldDropEvent(eventB, eventC)).toBe(false);
176176
});
177177
});
178+
179+
describe('setupOnce', () => {
180+
let dedupeFunc: EventProcessor;
181+
182+
beforeEach(function () {
183+
const integration = new Dedupe();
184+
const addGlobalEventProcessor = (callback: EventProcessor) => {
185+
dedupeFunc = callback;
186+
};
187+
188+
const getCurrentHub = () => {
189+
return {
190+
getIntegration() {
191+
return integration;
192+
},
193+
} as unknown as Hub;
194+
};
195+
196+
integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
197+
});
198+
199+
it('ignores consecutive errors', () => {
200+
expect(dedupeFunc(clone(exceptionEvent), {})).not.toBeNull();
201+
expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
202+
expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
203+
});
204+
205+
it('ignores transactions between errors', () => {
206+
expect(dedupeFunc(clone(exceptionEvent), {})).not.toBeNull();
207+
expect(
208+
dedupeFunc(
209+
{
210+
event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2',
211+
message: 'someMessage',
212+
transaction: 'wat',
213+
type: 'transaction',
214+
},
215+
{},
216+
),
217+
).not.toBeNull();
218+
expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
219+
expect(dedupeFunc(clone(exceptionEvent), {})).toBeNull();
220+
});
221+
});
178222
});

packages/replay-worker/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

packages/replay-worker/.eslintrc.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file
2+
// lives
3+
4+
// ESLint config docs: https://eslint.org/docs/user-guide/configuring/
5+
6+
module.exports = {
7+
extends: ['../../.eslintrc.js'],
8+
overrides: [
9+
{
10+
files: ['src/**/*.ts'],
11+
rules: {
12+
// We cannot use backticks, as that conflicts with the stringified worker
13+
'prefer-template': 'off',
14+
},
15+
},
16+
],
17+
};

packages/replay-worker/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
/*.tgz
3+
.eslintcache
4+
build
5+
!vendor/*.d.ts

packages/replay-worker/LICENSE

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6+
persons to whom the Software is furnished to do so, subject to the following conditions:
7+
8+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9+
Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

packages/replay-worker/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<p align="center">
2+
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
3+
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
4+
</a>
5+
</p>
6+
7+
# Sentry Session Replay Worker
8+
9+
This is an internal package that is used by @sentry/replay.
10+
It generates a web worker and converts it to a string, so that we can process it easier in replay.
11+
12+
By extracting this into a dedicated (private, internal) package, we can streamline the build of replay.

packages/replay-worker/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../../jest/jest.config.js');

packages/replay-worker/package.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "@sentry-internal/replay-worker",
3+
"version": "7.37.2",
4+
"description": "Worker for @sentry/replay",
5+
"main": "build/index.js",
6+
"module": "build/index.js",
7+
"sideEffects": false,
8+
"private": true,
9+
"scripts": {
10+
"build": "yarn build:transpile",
11+
"build:transpile": "rollup -c rollup.worker.config.js",
12+
"build:types": "yarn build:transpile",
13+
"build:dev": "yarn build",
14+
"build:watch": "run-p build:transpile:watch",
15+
"build:dev:watch": "run-p build:watch",
16+
"build:transpile:watch": "yarn build:rollup --watch",
17+
"clean": "rimraf build",
18+
"fix": "run-s fix:eslint fix:prettier",
19+
"fix:eslint": "eslint . --format stylish --fix",
20+
"fix:prettier": "prettier --write \"{src,test}/**/*.ts\"",
21+
"lint": "run-s lint:prettier lint:eslint",
22+
"lint:eslint": "eslint . --format stylish",
23+
"lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
24+
"test": "jest",
25+
"test:watch": "jest --watch"
26+
},
27+
"repository": {
28+
"type": "git",
29+
"url": "git+https://github.com/getsentry/sentry-javascript.git"
30+
},
31+
"author": "Sentry",
32+
"license": "MIT",
33+
"bugs": {
34+
"url": "https://github.com/getsentry/sentry-javascript/issues"
35+
},
36+
"homepage": "https://docs.sentry.io/platforms/javascript/session-replay/",
37+
"devDependencies": {
38+
"@types/pako": "^2.0.0",
39+
"rollup-plugin-copy": "~3.4.0",
40+
"tslib": "^1.9.3"
41+
},
42+
"dependencies": {
43+
"pako": "^2.1.0"
44+
},
45+
"engines": {
46+
"node": ">=12"
47+
},
48+
"volta": {
49+
"extends": "../../package.json"
50+
}
51+
}

packages/replay/rollup.config.worker.js renamed to packages/replay-worker/rollup.worker.config.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ import resolve from '@rollup/plugin-node-resolve';
44
import typescript from '@rollup/plugin-typescript';
55
import { defineConfig } from 'rollup';
66
import { terser } from 'rollup-plugin-terser';
7+
import copy from 'rollup-plugin-copy';
78

89
const config = defineConfig({
9-
input: ['./worker/src/worker.ts'],
10+
input: ['./src/worker.ts'],
1011
output: {
11-
dir: './src/worker/',
12+
dir: './build/',
1213
format: 'esm',
1314
},
1415
plugins: [
15-
typescript({ tsconfig: './tsconfig.worker.json' }),
16+
typescript({ tsconfig: './tsconfig.json', inlineSourceMap: false, sourceMap: false, inlineSources: false }),
1617
resolve(),
1718
terser({
1819
mangle: {
@@ -25,6 +26,9 @@ const config = defineConfig({
2526
return `export default \`${code}\`;`;
2627
},
2728
},
29+
copy({
30+
targets: [{ src: 'vendor/*', dest: 'build' }],
31+
}),
2832
],
2933
});
3034

packages/replay/worker/src/Compressor.ts renamed to packages/replay-worker/src/Compressor.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { constants, Deflate } from 'pako';
1+
import { constants, Deflate, deflate } from 'pako';
22

3+
/**
4+
* A stateful compressor that can be used to batch compress events.
5+
*/
36
export class Compressor {
47
/**
58
* pako deflator instance
@@ -15,10 +18,16 @@ export class Compressor {
1518
this._init();
1619
}
1720

21+
/**
22+
* Clear the compressor buffer.
23+
*/
1824
public clear(): void {
1925
this._init();
2026
}
2127

28+
/**
29+
* Add an event to the compressor buffer.
30+
*/
2231
public addEvent(data: string): void {
2332
if (!data) {
2433
throw new Error('Adding invalid event');
@@ -34,6 +43,9 @@ export class Compressor {
3443
this._hasEvents = true;
3544
}
3645

46+
/**
47+
* Finish compression of the current buffer.
48+
*/
3749
public finish(): Uint8Array {
3850
// We should always have a list, it can be empty
3951
this.deflate.push(']', constants.Z_FINISH);
@@ -51,6 +63,9 @@ export class Compressor {
5163
return result;
5264
}
5365

66+
/**
67+
* Re-initialize the compressor buffer.
68+
*/
5469
private _init(): void {
5570
this._hasEvents = false;
5671
this.deflate = new Deflate();
@@ -59,3 +74,10 @@ export class Compressor {
5974
this.deflate.push('[', constants.Z_NO_FLUSH);
6075
}
6176
}
77+
78+
/**
79+
* Compress a string.
80+
*/
81+
export function compress(data: string): Uint8Array {
82+
return deflate(data);
83+
}

packages/replay/worker/src/handleMessage.ts renamed to packages/replay-worker/src/handleMessage.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
2-
import { Compressor } from './Compressor';
2+
import { compress, Compressor } from './Compressor';
33

44
const compressor = new Compressor();
55

66
interface Handlers {
77
clear: () => void;
88
addEvent: (data: string) => void;
9-
finish: () => void;
9+
finish: () => Uint8Array;
10+
compress: (data: string) => Uint8Array;
1011
}
1112

1213
const handlers: Handlers = {
1314
clear: () => {
1415
compressor.clear();
15-
return '';
1616
},
1717

1818
addEvent: (data: string) => {
@@ -22,8 +22,15 @@ const handlers: Handlers = {
2222
finish: () => {
2323
return compressor.finish();
2424
},
25+
26+
compress: (data: string) => {
27+
return compress(data);
28+
},
2529
};
2630

31+
/**
32+
* Handler for worker messages.
33+
*/
2734
export function handleMessage(e: MessageEvent): void {
2835
const method = e.data.method as string;
2936
const id = e.data.id as number;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// TODO Once https://github.com/microsoft/TypeScript/issues/33094 is done (if it ever is), this file can disappear, as
2+
// it's purely a placeholder to satisfy VSCode.
3+
{
4+
"extends": "../tsconfig.test.json",
5+
6+
"include": ["./**/*"]
7+
}

packages/replay/test/unit/worker/Compressor.test.ts renamed to packages/replay-worker/test/unit/Compressor.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import pako from 'pako';
22

3-
import { Compressor } from '../../../worker/src/Compressor';
3+
import { Compressor } from '../../src/Compressor';
44

5-
describe('Unit | worker | Compressor', () => {
5+
describe('Compressor', () => {
66
it('compresses multiple events', () => {
77
const compressor = new Compressor();
88

packages/replay-worker/tsconfig.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"module": "esnext",
5+
"lib": ["webworker", "scripthost"],
6+
"esModuleInterop": true,
7+
"target": "es6",
8+
"strictPropertyInitialization": false
9+
},
10+
"include": ["src/**/*.ts"]
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"include": ["test/**/*.ts"],
4+
"compilerOptions": {
5+
"types": ["node", "jest"]
6+
}
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function getWorkerURL(): string;

0 commit comments

Comments
 (0)