Skip to content

Commit 3db34f3

Browse files
authored
chore: Only set credentials for sampled sessions (#487)
1 parent 24e4467 commit 3db34f3

File tree

7 files changed

+180
-4
lines changed

7 files changed

+180
-4
lines changed

src/event-cache/EventCache.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ export class EventCache {
8080
}
8181
};
8282

83+
/**
84+
* Returns true if the session is sampled, false otherwise.
85+
*/
86+
public isSessionSampled(): boolean {
87+
return this.sessionManager.isSampled();
88+
}
89+
8390
/**
8491
* Add an event to the cache and reset the session timer.
8592
*

src/event-cache/__tests__/EventCache.integ.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,31 @@ describe('EventCache tests', () => {
116116
expect(JSON.parse(event.metadata)).toMatchObject(expectedMetaData);
117117
});
118118
});
119+
120+
test('when a session is not sampled then return false', async () => {
121+
// Init
122+
const config = {
123+
...DEFAULT_CONFIG,
124+
...{
125+
sessionSampleRate: 0
126+
}
127+
};
128+
129+
const eventCache: EventCache = Utils.createEventCache(config);
130+
131+
// Assert
132+
expect(eventCache.isSessionSampled()).toBeFalsy();
133+
});
134+
135+
test('when a session is sampled then return true', async () => {
136+
// Init
137+
const config = {
138+
...DEFAULT_CONFIG
139+
};
140+
141+
const eventCache: EventCache = Utils.createEventCache(config);
142+
143+
// Assert
144+
expect(eventCache.isSessionSampled()).toBeTruthy();
145+
});
119146
});

src/event-cache/__tests__/EventCache.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ const getUserId = jest.fn(() => 'b');
1818
const getAttributes = jest.fn();
1919
const incrementSessionEventCount = jest.fn();
2020
const addSessionAttributes = jest.fn();
21+
let samplingDecision = true;
22+
const isSampled = jest.fn().mockImplementation(() => samplingDecision);
2123
jest.mock('../../sessions/SessionManager', () => ({
2224
SessionManager: jest.fn().mockImplementation(() => ({
2325
getSession,
2426
getUserId,
2527
getAttributes,
2628
incrementSessionEventCount,
27-
addSessionAttributes
29+
addSessionAttributes,
30+
isSampled
2831
}))
2932
}));
3033

@@ -408,6 +411,41 @@ describe('EventCache tests', () => {
408411
expect(eventBatch).toEqual(expect.arrayContaining(expectedEvents));
409412
});
410413

414+
test('when a session is not sampled then return false', async () => {
415+
// Init
416+
samplingDecision = false;
417+
418+
const config = {
419+
...DEFAULT_CONFIG,
420+
...{
421+
sessionSampleRate: 0
422+
}
423+
};
424+
425+
const eventCache: EventCache = Utils.createEventCache(config);
426+
427+
// Assert
428+
expect(eventCache.isSessionSampled()).toBeFalsy();
429+
430+
// Reset
431+
samplingDecision = true;
432+
});
433+
434+
test('when a session is sampled then return true', async () => {
435+
// Init
436+
const config = {
437+
...DEFAULT_CONFIG,
438+
...{
439+
sessionSampleRate: 1
440+
}
441+
};
442+
443+
const eventCache: EventCache = Utils.createEventCache(config);
444+
445+
// Assert
446+
expect(eventCache.isSessionSampled()).toBeTruthy();
447+
});
448+
411449
test('when session.record is false then event is not recorded', async () => {
412450
// Init
413451
const getSession = jest.fn(() => ({ sessionId: 'a', record: true }));

src/orchestration/Orchestration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,13 @@ export class Orchestration {
415415
this.config
416416
);
417417

418+
// Only retrieves and sets credentials if the session is sampled.
419+
// The nil session created during initialization will have the same sampling decision as
420+
// the new session created when the first event is recorded.
421+
if (!this.eventCache.isSessionSampled()) {
422+
return dispatch;
423+
}
424+
418425
if (this.config.identityPoolId && this.config.guestRoleArn) {
419426
dispatch.setAwsCredentials(
420427
new Authentication(this.config)

src/orchestration/__tests__/Orchestration.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ global.fetch = jest.fn();
1313

1414
const enableDispatch = jest.fn();
1515
const disableDispatch = jest.fn();
16+
const setAwsCredentials = jest.fn();
1617

1718
jest.mock('../../dispatch/Dispatch', () => ({
1819
Dispatch: jest.fn().mockImplementation(() => ({
1920
enable: enableDispatch,
20-
disable: disableDispatch
21+
disable: disableDispatch,
22+
setAwsCredentials
2123
}))
2224
}));
2325

@@ -36,13 +38,16 @@ const recordPageView = jest.fn();
3638
const addSessionAttributes = jest.fn();
3739
const recordEvent = jest.fn();
3840

41+
let samplingDecision = true;
42+
const isSessionSampled = jest.fn().mockImplementation(() => samplingDecision);
3943
jest.mock('../../event-cache/EventCache', () => ({
4044
EventCache: jest.fn().mockImplementation(() => ({
4145
enable: enableEventCache,
4246
disable: disableEventCache,
4347
recordPageView,
4448
addSessionAttributes,
45-
recordEvent
49+
recordEvent,
50+
isSessionSampled
4651
}))
4752
}));
4853

@@ -513,4 +518,32 @@ describe('Orchestration tests', () => {
513518
INSTALL_SCRIPT
514519
);
515520
});
521+
522+
test('when session is not recorded then credentials are not set', async () => {
523+
samplingDecision = false;
524+
// Init
525+
const orchestration = new Orchestration('a', 'c', 'us-east-1', {
526+
telemetries: [],
527+
identityPoolId: 'dummyPoolId',
528+
guestRoleArn: 'dummyRoleArn'
529+
});
530+
531+
// Assert
532+
expect(setAwsCredentials).toHaveBeenCalledTimes(0);
533+
534+
// Reset
535+
samplingDecision = true;
536+
});
537+
538+
test('when session is recorded then credentials are set', async () => {
539+
// Init
540+
const orchestration = new Orchestration('a', 'c', 'us-east-1', {
541+
telemetries: [],
542+
identityPoolId: 'dummyPoolId',
543+
guestRoleArn: 'dummyRoleArn'
544+
});
545+
546+
// Assert
547+
expect(setAwsCredentials).toHaveBeenCalledTimes(1);
548+
});
516549
});

src/sessions/SessionManager.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,21 @@ export class SessionManager {
101101
this.getSessionFromCookie();
102102
}
103103

104+
/**
105+
* Returns true if the session is sampled, false otherwise.
106+
*/
107+
public isSampled(): boolean {
108+
return this.session.record;
109+
}
110+
104111
/**
105112
* Returns the session ID. If no session ID exists, one will be created.
106113
*/
107114
public getSession(): Session {
108115
if (this.session.sessionId === NIL_UUID) {
109116
// The session does not exist. Create a new one.
117+
// If it is created before the page view is recorded, the session start event metadata will
118+
// not have page attributes as the page does not exist yet.
110119
this.createSession();
111120
} else if (
112121
this.session.sessionId !== NIL_UUID &&
@@ -219,9 +228,15 @@ export class SessionManager {
219228
}
220229

221230
private createSession() {
231+
// The semantics of the nil session (created during initialization) are that there is no session.
232+
// We ensure the nil session and new session created right after initialization have the same sampling decision.
233+
// Otherwise, we will always reevaluate the sample decision.
222234
this.session = {
223235
sessionId: v4(),
224-
record: this.sample(),
236+
record:
237+
this.session.sessionId === NIL_UUID
238+
? this.session.record
239+
: this.sample(),
225240
eventCount: 0
226241
};
227242
this.session.page = this.pageManager.getPage();

src/sessions/__tests__/SessionManager.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
DEFAULT_CONFIG,
2323
mockFetch
2424
} from '../../test-utils/test-utils';
25+
import { advanceTo } from 'jest-date-mock';
2526

2627
global.fetch = mockFetch;
2728
const NAVIGATION = 'navigation';
@@ -469,6 +470,7 @@ describe('SessionManager tests', () => {
469470

470471
// Assert
471472
expect(session.record).toBeTruthy();
473+
expect(sessionManager.isSampled()).toBeTruthy();
472474
});
473475

474476
test('when sessionSampleRate is zero then session.record is false', async () => {
@@ -482,6 +484,53 @@ describe('SessionManager tests', () => {
482484

483485
// Assert
484486
expect(session.record).toBeFalsy();
487+
expect(sessionManager.isSampled()).toBeFalsy();
488+
});
489+
490+
test('when sessionId is nil then create new session with same sampling decision', async () => {
491+
mockRandom([0.01]);
492+
// Init
493+
const sessionManager = defaultSessionManager({
494+
...DEFAULT_CONFIG,
495+
...{ sessionSampleRate: 0.5, allowCookies: true }
496+
});
497+
498+
// Ensure isSampled() returns a different value
499+
mockRandom([0.99]);
500+
const session = sessionManager.getSession();
501+
502+
// Assert
503+
expect(session.record).toBeTruthy();
504+
expect(sessionManager.isSampled()).toBeTruthy();
505+
});
506+
507+
test('when sessionId is not nil then create new session with different sampling decision', async () => {
508+
const dateNow = new Date();
509+
mockRandom([0.01]);
510+
// Init
511+
const sessionManager = defaultSessionManager({
512+
...DEFAULT_CONFIG,
513+
...{ sessionSampleRate: 0.5 }
514+
});
515+
516+
// create new session
517+
// new session should have same sampling decision as nil session
518+
let session = sessionManager.getSession();
519+
expect(session.sessionId).not.toEqual(NIL_UUID);
520+
expect(session.record).toBeTruthy();
521+
expect(sessionManager.isSampled()).toBeTruthy();
522+
523+
// create new session after previous has expired
524+
jest.useFakeTimers();
525+
jest.setSystemTime(new Date(dateNow.getTime() + 86400000)); // set to 24 hours from current date
526+
527+
// new session should have different sampling decision as previous session
528+
mockRandom([0.99]);
529+
session = sessionManager.getSession();
530+
531+
// Assert
532+
expect(session.record).toBeFalsy();
533+
expect(sessionManager.isSampled()).toBeFalsy();
485534
});
486535

487536
test('when random value equals sessionSampleRate then session.record is false', async () => {

0 commit comments

Comments
 (0)