Skip to content

Commit 22206e3

Browse files
authored
ref(replay): Make Session a POJO (#6417)
1 parent 707bbab commit 22206e3

10 files changed

+123
-148
lines changed

Diff for: packages/replay/MIGRATION.md

+5
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,8 @@ If you only imported from `@sentry/replay`, this will not affect you.
5757

5858
It is highly unlikely to affect anybody, but the type `IEventBuffer` was renamed to `EventBuffer` for consistency.
5959
Unless you manually imported this and used it somewhere in your codebase, this will not affect you.
60+
61+
## Session object is now a plain object (https://github.com/getsentry/sentry-javascript/pull/6417)
62+
63+
The `Session` object exported from Replay is now a plain object, instead of a class.
64+
This should not affect you unless you specifically accessed this class & did custom things with it.

Diff for: packages/replay/src/session/Session.ts

+32-51
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { uuid4 } from '@sentry/utils';
22

3-
import { SampleRates } from '../types';
43
import { isSampled } from '../util/isSampled';
54

65
type Sampled = false | 'session' | 'error';
76

8-
interface SessionObject {
7+
export interface Session {
98
id: string;
109

1110
/**
@@ -24,59 +23,41 @@ interface SessionObject {
2423
segmentId: number;
2524

2625
/**
27-
* Is the session sampled? `null` if the sampled, otherwise, `session` or `error`
26+
* The ID of the previous session.
27+
* If this is empty, there was no previous session.
2828
*/
29-
sampled: Sampled;
30-
}
31-
32-
export class Session {
33-
/**
34-
* Session ID
35-
*/
36-
public readonly id: string;
37-
38-
/**
39-
* Start time of current session
40-
*/
41-
public started: number;
42-
43-
/**
44-
* Last known activity of the session
45-
*/
46-
public lastActivity: number;
47-
48-
/**
49-
* Sequence ID specific to replay updates
50-
*/
51-
public segmentId: number;
29+
previousSessionId?: string;
5230

5331
/**
54-
* Previous session ID
32+
* Is the session sampled? `false` if not sampled, otherwise, `session` or `error`
5533
*/
56-
public previousSessionId: string | undefined;
57-
58-
/**
59-
* Is the Session sampled?
60-
*/
61-
public readonly sampled: Sampled;
34+
sampled: Sampled;
35+
}
6236

63-
public constructor(session: Partial<SessionObject> = {}, { sessionSampleRate, errorSampleRate }: SampleRates) {
64-
const now = new Date().getTime();
65-
this.id = session.id || uuid4();
66-
this.started = session.started ?? now;
67-
this.lastActivity = session.lastActivity ?? now;
68-
this.segmentId = session.segmentId ?? 0;
69-
this.sampled =
70-
session.sampled ?? (isSampled(sessionSampleRate) ? 'session' : isSampled(errorSampleRate) ? 'error' : false);
71-
}
37+
/**
38+
* Get a session with defaults & applied sampling.
39+
*/
40+
export function makeSession(session: Partial<Session> & { sampled: Sampled }): Session {
41+
const now = new Date().getTime();
42+
const id = session.id || uuid4();
43+
// Note that this means we cannot set a started/lastActivity of `0`, but this should not be relevant outside of tests.
44+
const started = session.started || now;
45+
const lastActivity = session.lastActivity || now;
46+
const segmentId = session.segmentId || 0;
47+
const sampled = session.sampled;
48+
49+
return {
50+
id,
51+
started,
52+
lastActivity,
53+
segmentId,
54+
sampled,
55+
};
56+
}
7257

73-
toJSON(): SessionObject {
74-
return {
75-
id: this.id,
76-
started: this.started,
77-
lastActivity: this.lastActivity,
78-
segmentId: this.segmentId,
79-
sampled: this.sampled,
80-
} as SessionObject;
81-
}
58+
/**
59+
* Get the sampled status for a session based on sample rates & current sampled status.
60+
*/
61+
export function getSessionSampleType(sessionSampleRate: number, errorSampleRate: number): Sampled {
62+
return isSampled(sessionSampleRate) ? 'session' : isSampled(errorSampleRate) ? 'error' : false;
8263
}

Diff for: packages/replay/src/session/createSession.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ import { logger } from '@sentry/utils';
22

33
import { SessionOptions } from '../types';
44
import { saveSession } from './saveSession';
5-
import { Session } from './Session';
5+
import { getSessionSampleType, makeSession, Session } from './Session';
66

77
/**
88
* Create a new session, which in its current implementation is a Sentry event
99
* that all replays will be saved to as attachments. Currently, we only expect
1010
* one of these Sentry events per "replay session".
1111
*/
1212
export function createSession({ sessionSampleRate, errorSampleRate, stickySession = false }: SessionOptions): Session {
13-
const session = new Session(undefined, {
14-
errorSampleRate,
15-
sessionSampleRate,
13+
const sampled = getSessionSampleType(sessionSampleRate, errorSampleRate);
14+
const session = makeSession({
15+
sampled,
1616
});
1717

1818
__DEBUG_BUILD__ && logger.log(`[Replay] Creating new session: ${session.id}`);

Diff for: packages/replay/src/session/fetchSession.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { REPLAY_SESSION_KEY, WINDOW } from '../constants';
2-
import { SampleRates } from '../types';
3-
import { Session } from './Session';
2+
import { makeSession, Session } from './Session';
43

54
/**
65
* Fetches a session from storage
76
*/
8-
export function fetchSession({ sessionSampleRate, errorSampleRate }: SampleRates): Session | null {
7+
export function fetchSession(): Session | null {
98
const hasSessionStorage = 'sessionStorage' in WINDOW;
109

1110
if (!hasSessionStorage) {
@@ -20,9 +19,9 @@ export function fetchSession({ sessionSampleRate, errorSampleRate }: SampleRates
2019
return null;
2120
}
2221

23-
const sessionObj = JSON.parse(sessionStringFromStorage);
22+
const sessionObj = JSON.parse(sessionStringFromStorage) as Session;
2423

25-
return new Session(sessionObj, { sessionSampleRate, errorSampleRate });
24+
return makeSession(sessionObj);
2625
} catch {
2726
return null;
2827
}

Diff for: packages/replay/src/session/getSession.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function getSession({
2929
errorSampleRate,
3030
}: GetSessionParams): { type: 'new' | 'saved'; session: Session } {
3131
// If session exists and is passed, use it instead of always hitting session storage
32-
const session = currentSession || (stickySession && fetchSession({ sessionSampleRate, errorSampleRate }));
32+
const session = currentSession || (stickySession && fetchSession());
3333

3434
if (session) {
3535
// If there is a session, check if it is valid (e.g. "last activity" time

Diff for: packages/replay/test/unit/session/Session.test.ts

+10-19
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jest.mock('@sentry/utils', () => {
2626
import * as Sentry from '@sentry/browser';
2727

2828
import { WINDOW } from '../../../src/constants';
29-
import { Session } from '../../../src/session/Session';
29+
import { getSessionSampleType, makeSession } from '../../../src/session/Session';
3030

3131
type CaptureEventMockType = jest.MockedFunction<typeof Sentry.captureEvent>;
3232

@@ -39,42 +39,33 @@ afterEach(() => {
3939
});
4040

4141
it('does not sample', function () {
42-
const newSession = new Session(undefined, {
43-
sessionSampleRate: 0.0,
44-
errorSampleRate: 0.0,
42+
const newSession = makeSession({
43+
sampled: getSessionSampleType(0, 0),
4544
});
4645

4746
expect(newSession.sampled).toBe(false);
4847
});
4948

5049
it('samples using `sessionSampleRate`', function () {
51-
const newSession = new Session(undefined, {
52-
sessionSampleRate: 1.0,
53-
errorSampleRate: 0.0,
50+
const newSession = makeSession({
51+
sampled: getSessionSampleType(1.0, 0),
5452
});
5553

5654
expect(newSession.sampled).toBe('session');
5755
});
5856

5957
it('samples using `errorSampleRate`', function () {
60-
const newSession = new Session(undefined, {
61-
sessionSampleRate: 0,
62-
errorSampleRate: 1.0,
58+
const newSession = makeSession({
59+
sampled: getSessionSampleType(0, 1),
6360
});
6461

6562
expect(newSession.sampled).toBe('error');
6663
});
6764

6865
it('does not run sampling function if existing session was sampled', function () {
69-
const newSession = new Session(
70-
{
71-
sampled: 'session',
72-
},
73-
{
74-
sessionSampleRate: 0,
75-
errorSampleRate: 0,
76-
},
77-
);
66+
const newSession = makeSession({
67+
sampled: 'session',
68+
});
7869

7970
expect(newSession.sampled).toBe('session');
8071
});

Diff for: packages/replay/test/unit/session/fetchSession.test.ts

+21-11
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,44 @@ afterEach(() => {
1515
WINDOW.sessionStorage.clear();
1616
});
1717

18-
const SAMPLE_RATES = {
19-
sessionSampleRate: 1.0,
20-
errorSampleRate: 0.0,
21-
};
22-
2318
it('fetches a valid and sampled session', function () {
2419
WINDOW.sessionStorage.setItem(
2520
REPLAY_SESSION_KEY,
26-
'{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": true,"started":1648827162630,"lastActivity":1648827162658}',
21+
'{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": "session","started":1648827162630,"lastActivity":1648827162658}',
22+
);
23+
24+
expect(fetchSession()).toEqual({
25+
id: 'fd09adfc4117477abc8de643e5a5798a',
26+
lastActivity: 1648827162658,
27+
segmentId: 0,
28+
sampled: 'session',
29+
started: 1648827162630,
30+
});
31+
});
32+
33+
it('fetches an unsampled session', function () {
34+
WINDOW.sessionStorage.setItem(
35+
REPLAY_SESSION_KEY,
36+
'{"id":"fd09adfc4117477abc8de643e5a5798a","sampled": false,"started":1648827162630,"lastActivity":1648827162658}',
2737
);
2838

29-
expect(fetchSession(SAMPLE_RATES)?.toJSON()).toEqual({
39+
expect(fetchSession()).toEqual({
3040
id: 'fd09adfc4117477abc8de643e5a5798a',
3141
lastActivity: 1648827162658,
3242
segmentId: 0,
33-
sampled: true,
43+
sampled: false,
3444
started: 1648827162630,
3545
});
3646
});
3747

3848
it('fetches a session that does not exist', function () {
39-
expect(fetchSession(SAMPLE_RATES)).toBe(null);
49+
expect(fetchSession()).toBe(null);
4050
});
4151

4252
it('fetches an invalid session', function () {
4353
WINDOW.sessionStorage.setItem(REPLAY_SESSION_KEY, '{"id":"fd09adfc4117477abc8de643e5a5798a",');
4454

45-
expect(fetchSession(SAMPLE_RATES)).toBe(null);
55+
expect(fetchSession()).toBe(null);
4656
});
4757

4858
it('safely attempts to fetch session when Session Storage is disabled', function () {
@@ -55,5 +65,5 @@ it('safely attempts to fetch session when Session Storage is disabled', function
5565
},
5666
});
5767

58-
expect(fetchSession(SAMPLE_RATES)).toEqual(null);
68+
expect(fetchSession()).toEqual(null);
5969
});

0 commit comments

Comments
 (0)