Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(session replay): opt out should take effect immediately #586

Merged
merged 2 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/session-replay-browser/src/session-replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export class SessionReplay implements AmplitudeSessionReplay {
identityStoreOptOut = identityStore.getIdentity().optOut;
}

return identityStoreOptOut || this.config?.optOut;
return identityStoreOptOut !== undefined ? identityStoreOptOut : this.config?.optOut;
}

getShouldRecord() {
Expand Down Expand Up @@ -241,7 +241,7 @@ export class SessionReplay implements AmplitudeSessionReplay {
this.stopRecordingEvents = record({
emit: (event) => {
const globalScope = getGlobalScope();
if (globalScope && globalScope.document && !globalScope.document.hasFocus()) {
if ((globalScope && globalScope.document && !globalScope.document.hasFocus()) || !this.getShouldRecord()) {
this.stopRecordingAndSendEvents();
return;
}
Expand Down
47 changes: 47 additions & 0 deletions packages/session-replay-browser/test/session-replay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,20 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.init(apiKey, { ...mockOptions, instanceName: 'my_instance' }).promise;
expect(sessionReplay.shouldOptOut()).toEqual(true);
});
test('should return opt out from identity store even if set to false', async () => {
jest.spyOn(AnalyticsClientCommon, 'getAnalyticsConnector').mockReturnValue({
identityStore: {
getIdentity: () => {
return {
optOut: false,
};
},
},
} as unknown as ReturnType<typeof AnalyticsClientCommon.getAnalyticsConnector>);
const sessionReplay = new SessionReplay();
await sessionReplay.init(apiKey, { ...mockOptions, instanceName: 'my_instance', optOut: true }).promise;
expect(sessionReplay.shouldOptOut()).toEqual(false);
});
test('should return config device id if set', async () => {
const sessionReplay = new SessionReplay();
await sessionReplay.init(apiKey, { ...mockOptions, instanceName: 'my_instance', optOut: true }).promise;
Expand Down Expand Up @@ -652,6 +666,15 @@ describe('SessionReplayPlugin', () => {
expect(record).not.toHaveBeenCalled();
expect(sessionReplay.events).toEqual([]);
});

test('should return early if user opts out', async () => {
const sessionReplay = new SessionReplay();
await sessionReplay.init(apiKey, { ...mockOptions, optOut: true }).promise;
sessionReplay.recordEvents();
expect(record).not.toHaveBeenCalled();
expect(sessionReplay.events).toEqual([]);
});

test('should store events in class and in IDB', async () => {
const sessionReplay = new SessionReplay();
await sessionReplay.init(apiKey, mockOptions).promise;
Expand Down Expand Up @@ -763,6 +786,30 @@ describe('SessionReplayPlugin', () => {
expect(sessionReplay.events).toEqual([mockEventString]); // events should not change, emmitted event should be ignored
});

test('should stop recording and send events if user opts out during recording', async () => {
const sessionReplay = new SessionReplay();
await sessionReplay.init(apiKey, mockOptions).promise;
sessionReplay.recordEvents();
const stopRecordingMock = jest.fn();
sessionReplay.stopRecordingEvents = stopRecordingMock;
expect(sessionReplay.events).toEqual([]);
sessionReplay.events = [mockEventString]; // Add one event to list to trigger sending in stopRecordingAndSendEvents
// eslint-disable-next-line @typescript-eslint/no-empty-function
const sendEventsListMock = jest.spyOn(sessionReplay, 'sendEventsList').mockImplementationOnce(() => {});
sessionReplay.shouldOptOut = () => true;
const recordArg = record.mock.calls[0][0];
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsListMock).toHaveBeenCalledTimes(1);
expect(sendEventsListMock).toHaveBeenCalledWith({
events: [mockEventString],
sequenceId: 0,
sessionId: 123,
});
expect(stopRecordingMock).toHaveBeenCalled();
expect(sessionReplay.stopRecordingEvents).toEqual(null);
expect(sessionReplay.events).toEqual([mockEventString]); // events should not change, emmitted event should be ignored
});

test('should add an error handler', async () => {
const sessionReplay = new SessionReplay();
await sessionReplay.init(apiKey, mockOptions).promise;
Expand Down