Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

feat: Stop recording when user is idle#261

Merged
billyvg merged 4 commits intomainfrom
feat/stop-recording-if-user-is-idle
Oct 31, 2022
Merged

feat: Stop recording when user is idle#261
billyvg merged 4 commits intomainfrom
feat/stop-recording-if-user-is-idle

Conversation

@billyvg
Copy link
Copy Markdown
Member

@billyvg billyvg commented Oct 26, 2022

If a user has not performed any user activity for MAX_SESSION_LIFE (30 minutes currently), stop dom recording until a user performs an action (currently: mouse click, navigation, tab focus).

Previously, dom recording would trigger new sessions indefinitely.

Fixes #204

If a user has not performed any user activity for MAX_SESSION_LIFE (30 minutes currently), stop dom recording until a user performs an action (currently: mouse click, navigation, tab focus).

Previously, dom recording would trigger new sessions indefinitely.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Oct 26, 2022

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
dist/index.js 33.65 KB (+2.03% 🔺) 674 ms (+2.03% 🔺) 248 ms (+245.2% 🔺) 921 ms

import { record } from 'rrweb';

import createBreadcrumb from '../util/createBreadcrumb';
import { createBreadcrumb } from '../util/createBreadcrumb';
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency

Comment on lines +436 to +464
/**
* We want to batch uploads of replay events. Save events only if
* `<flushMinDelay>` milliseconds have elapsed since the last event
* *OR* if `<flushMaxDelay>` milliseconds have elapsed.
*
* Accepts a callback to perform side-effects and returns true to stop batch
* processing and hand back control to caller.
*/
addUpdate(cb?: AddUpdateCallback) {
// We need to always run `cb` (e.g. in the case of captureOnlyOnError == true)
const cbResult = cb?.();

// If this option is turned on then we will only want to call `flush`
// explicitly
if (this.options.captureOnlyOnError) {
return;
}

// If callback is true, we do not want to continue with flushing -- the
// caller will need to handle it.
if (cbResult === true) {
return;
}

// addUpdate is called quite frequently - use debouncedFlush so that it
// respects the flush delays and does not flush immediately
this.debouncedFlush();
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchanged, just moved.

Comment on lines -716 to -717
this.loadSession({ expiry: VISIBILITY_CHANGE_TIMEOUT });
this.triggerFullSnapshot();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is replaced by checkAndHandleExpiredSession call.

src/index.ts Outdated
Comment on lines +880 to +903
if (updateUserActivity) {
this.lastActivity = new Date().getTime();
}

// Prevent starting a new session if the last user activity is older than
// MAX_SESSION_LIFE. Otherwise non-user activity can trigger a new
// session+recording. This creates noisy replays that do not have much
// content in them.
if (this.lastActivity && isExpired(this.lastActivity, MAX_SESSION_LIFE)) {
// We should stop recording emitter if it exists
if (this.stopRecording) {
this.stopRecording();
this.stopRecording = undefined;
}

return;
}

// --- There is recent user activity --- //

// Ensure that recording is resumed
if (!this.stopRecording) {
this.startRecording();
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically the bulk of the change.

src/index.ts Outdated
}

if (result.category === 'ui.click') {
this.checkAndHandleExpiredSession({ updateUserActivity: true });
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this actually fixes a bug where we update the session last activity without checking if it's expired yet. This means we could extend beyond the 5 minute expiry.

@billyvg billyvg marked this pull request as ready for review October 26, 2022 22:30
@billyvg billyvg requested a review from a team October 26, 2022 22:30
src/index.ts Outdated

// Ensure that recording is resumed
if (!this.stopRecording) {
this.startRecording();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually caused an infinite loop since it would trigger a fullsnapshot, and eventually back into this function. I've added an intermediate "paused" state to handle this.

src/index.ts Outdated
Comment on lines -630 to -631
this.checkAndHandleExpiredSession({ updateUserActivity: true });
this.updateLastActivity();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored this a bit so we don't end up in a cycle

Comment on lines +789 to +793
if (this.isPaused) {
// Do not add to event buffer when recording is paused
return;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to handle a separate bug I found. The core SDK event listeners were adding to the eventBuffer which was not being cleared on new DOM checkouts, so it was possible to have stale events in here. Now when a session goes idle, we do not add to event buffer at all.

@billyvg billyvg requested a review from a team October 28, 2022 20:41
@billyvg
Copy link
Copy Markdown
Member Author

billyvg commented Oct 28, 2022

Found a bug with the session idle pause/resume logic so had to make some changes.

src/index.ts Outdated
// Create a new session, otherwise when the user action is flushed, it will get rejected due to an expired session.
this.loadSession({ expiry: SESSION_IDLE_DURATION });

// Note: This will cause a new checkout
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guaranteed to start a new checkout? or will it do so only if needed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah guaranteed.

@billyvg billyvg merged commit dbcdb19 into main Oct 31, 2022
@billyvg billyvg deleted the feat/stop-recording-if-user-is-idle branch October 31, 2022 16:44
mydea pushed a commit to getsentry/sentry-javascript that referenced this pull request Nov 23, 2022
If a user has not performed any user activity for MAX_SESSION_LIFE (30
minutes currently), stop dom recording until a user performs an action
(currently: mouse click, navigation, tab focus).

Previously, dom recording would trigger new sessions indefinitely.

Fixes getsentry/sentry-replay#204
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Figure out why some replays have large gaps between events

2 participants