@statsig/session-replay 3.31.0 → 3.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statsig/session-replay",
3
- "version": "3.31.0",
3
+ "version": "3.31.1",
4
4
  "license": "ISC",
5
5
  "homepage": "https://github.com/statsig-io/js-client-monorepo",
6
6
  "repository": {
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "rrweb": "2.0.0-alpha.17",
13
13
  "@rrweb/record": "2.0.0-alpha.17",
14
- "@statsig/client-core": "3.31.0"
14
+ "@statsig/client-core": "3.31.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@rrweb/types": "2.0.0-alpha.16"
@@ -24,30 +24,25 @@ function runStatsigSessionReplay(client, options) {
24
24
  exports.runStatsigSessionReplay = runStatsigSessionReplay;
25
25
  class SessionReplay extends SessionReplayBase_1.SessionReplayBase {
26
26
  constructor(client, options) {
27
- var _a, _b;
28
- let newOptions = options;
29
- const privacySettings = (_a = client.getContext().values) === null || _a === void 0 ? void 0 : _a.session_recording_privacy_settings;
30
- if (privacySettings) {
31
- newOptions = (0, SessionReplayUtils_1.getNewOptionsWithPrivacySettings)(newOptions !== null && newOptions !== void 0 ? newOptions : {}, privacySettings);
32
- }
33
- super(client, newOptions);
27
+ var _a;
28
+ super(client, options);
34
29
  this._client.$on('values_updated', () => {
35
30
  var _a;
36
31
  if (!this._wasStopped) {
37
32
  this._attemptToStartRecording((_a = this._options) === null || _a === void 0 ? void 0 : _a.forceRecording);
38
33
  }
39
34
  });
40
- this._attemptToStartRecording((_b = this._options) === null || _b === void 0 ? void 0 : _b.forceRecording);
35
+ this._attemptToStartRecording((_a = this._options) === null || _a === void 0 ? void 0 : _a.forceRecording);
41
36
  }
42
37
  _shutdown(endReason) {
43
38
  super._shutdownImpl(endReason);
44
39
  }
45
40
  _attemptToStartRecording(force = false) {
46
- var _a, _b;
41
+ var _a, _b, _c, _d, _e;
47
42
  if (this._totalLogs >= SessionReplayUtils_1.MAX_LOGS) {
48
43
  return;
49
44
  }
50
- const values = this._client.getContext().values;
45
+ const values = this._client.getContextHandle().values;
51
46
  if ((values === null || values === void 0 ? void 0 : values.recording_blocked) === true) {
52
47
  this._shutdown();
53
48
  return;
@@ -63,9 +58,17 @@ class SessionReplay extends SessionReplayBase_1.SessionReplayBase {
63
58
  if (this._replayer.isRecording()) {
64
59
  return;
65
60
  }
61
+ const usePrivacySettings = (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings) &&
62
+ ((values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.privacy_mode) !== 'min' ||
63
+ (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.blocked_elements) ||
64
+ (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.masked_elements) ||
65
+ (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.unmasked_elements));
66
+ const newRRWebConfig = usePrivacySettings
67
+ ? (0, SessionReplayUtils_1.getNewRRWebConfigWithPrivacySettings)((_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.rrwebConfig) !== null && _b !== void 0 ? _b : {}, (_c = values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings) !== null && _c !== void 0 ? _c : {})
68
+ : (_e = (_d = this._options) === null || _d === void 0 ? void 0 : _d.rrwebConfig) !== null && _e !== void 0 ? _e : {};
66
69
  this._wasStopped = false;
67
70
  client_core_1.StatsigMetadataProvider.add({ isRecordingSession: 'true' });
68
- this._replayer.record((e, d) => this._onRecordingEvent(e, d), (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.rrwebConfig) !== null && _b !== void 0 ? _b : {}, () => {
71
+ this._replayer.record((e, d) => this._onRecordingEvent(e, d), newRRWebConfig, () => {
69
72
  this._shutdown();
70
73
  });
71
74
  }
@@ -30,7 +30,7 @@ export declare abstract class SessionReplayBase {
30
30
  protected _subscribeToVisibilityChanged(): void;
31
31
  protected _logRecordingWithSessionID(sessionID: string, endReason?: EndReason): void;
32
32
  protected _bumpSessionIdleTimerAndLogRecording(): void;
33
- protected _getSessionIdFromClient(): string;
33
+ protected _getSessionIdFromClient(bumpSession?: boolean): string;
34
34
  protected abstract _shutdown(endReason?: EndReason): void;
35
35
  protected _shutdownImpl(endReason?: EndReason): void;
36
36
  private _makeEmptySessionData;
@@ -39,7 +39,7 @@ class SessionReplayBase {
39
39
  this._totalLogs = 0;
40
40
  this._client = client;
41
41
  this._options = options;
42
- const { sdkKey, errorBoundary } = this._client.getContext();
42
+ const { sdkKey, errorBoundary } = this._client.getContextHandle();
43
43
  this._errorBoundary = errorBoundary;
44
44
  this._errorBoundary.wrap(this);
45
45
  this._replayer = new SessionReplayClient_1.SessionReplayClient();
@@ -91,7 +91,7 @@ class SessionReplayBase {
91
91
  }
92
92
  _subscribeToVisibilityChanged() {
93
93
  // Note: this exists as a separate function to ensure closure scope only contains `sdkKey`
94
- const { sdkKey } = this._client.getContext();
94
+ const { sdkKey } = this._client.getContextHandle();
95
95
  (0, client_core_1._subscribeToVisiblityChanged)((vis) => {
96
96
  var _a, _b;
97
97
  const inst = (_b = (_a = (0, client_core_1._getStatsigGlobal)()) === null || _a === void 0 ? void 0 : _a.srInstances) === null || _b === void 0 ? void 0 : _b[sdkKey];
@@ -110,7 +110,7 @@ class SessionReplayBase {
110
110
  const slicedID = parts.length > 1 ? (0, client_core_1.getUUID)() : null;
111
111
  for (let i = 0; i < parts.length; i++) {
112
112
  const slice = parts[i];
113
- const event = (0, SessionReplayUtils_1._makeLoggableRrwebEvent)(slice, payload, sessionID, data, this._client.getContext().sdkInstanceID);
113
+ const event = (0, SessionReplayUtils_1._makeLoggableRrwebEvent)(slice, payload, sessionID, data, this._client.getContextHandle().sdkInstanceID);
114
114
  if (slicedID != null) {
115
115
  (0, SessionReplayUtils_1._appendSlicedMetadata)(event.metadata, slicedID, i, parts.length, slice.length);
116
116
  }
@@ -134,8 +134,9 @@ class SessionReplayBase {
134
134
  this._getSessionIdFromClient();
135
135
  this._logRecording();
136
136
  }
137
- _getSessionIdFromClient() {
138
- return this._client.getContext().session.data.sessionID;
137
+ _getSessionIdFromClient(bumpSession) {
138
+ return this._client.getContextHandle().getSession(bumpSession).data
139
+ .sessionID;
139
140
  }
140
141
  _shutdownImpl(endReason) {
141
142
  if (this._replayer.isRecording()) {
@@ -170,7 +171,7 @@ class SessionReplayBase {
170
171
  }
171
172
  _onRecordingEvent(event, data) {
172
173
  // The session has expired so we should stop recording
173
- if (this._currentSessionID !== this._getSessionIdFromClient()) {
174
+ if (this._currentSessionID !== this._getSessionIdFromClient(false)) {
174
175
  this._shutdown('session_expired');
175
176
  return;
176
177
  }
@@ -1,6 +1,5 @@
1
1
  import { SessionReplayPrivacySettings, StatsigEvent } from '@statsig/client-core';
2
- import { ReplaySessionData } from './SessionReplayClient';
3
- import { SessionReplayOptions } from './TriggeredSessionReplay';
2
+ import { RRWebConfig, ReplaySessionData } from './SessionReplayClient';
4
3
  export type RRWebPayload = {
5
4
  session_start_ts: string;
6
5
  session_end_ts: string;
@@ -24,4 +23,4 @@ export declare function _makeLoggableRrwebEvent(slice: string, payload: string,
24
23
  };
25
24
  export declare function _slicePayload(payload: string): string[];
26
25
  export declare function _appendSlicedMetadata(metadata: RRWebPayload, slicedID: string, sliceIndex: number, sliceCount: number, sliceByteSize: number): void;
27
- export declare function getNewOptionsWithPrivacySettings(originalOptions: SessionReplayOptions, privacySettings: SessionReplayPrivacySettings): SessionReplayOptions;
26
+ export declare function getNewRRWebConfigWithPrivacySettings(originalOptions: RRWebConfig, privacySettings: SessionReplayPrivacySettings): RRWebConfig;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNewOptionsWithPrivacySettings = exports._appendSlicedMetadata = exports._slicePayload = exports._makeLoggableRrwebEvent = exports.MAX_LOGS = exports.MAX_INDIVIDUAL_EVENT_BYTES = exports.REPLAY_ENQUEUE_TRIGGER_BYTES = void 0;
3
+ exports.getNewRRWebConfigWithPrivacySettings = exports._appendSlicedMetadata = exports._slicePayload = exports._makeLoggableRrwebEvent = exports.MAX_LOGS = exports.MAX_INDIVIDUAL_EVENT_BYTES = exports.REPLAY_ENQUEUE_TRIGGER_BYTES = void 0;
4
4
  const client_core_1 = require("@statsig/client-core");
5
5
  const REPLAY_SLICE_BYTES = 1024 * 1024; // 1 MB
6
6
  exports.REPLAY_ENQUEUE_TRIGGER_BYTES = 1024 * 10; // 10 KB
@@ -38,16 +38,39 @@ function _appendSlicedMetadata(metadata, slicedID, sliceIndex, sliceCount, slice
38
38
  metadata.slice_byte_size = String(sliceByteSize);
39
39
  }
40
40
  exports._appendSlicedMetadata = _appendSlicedMetadata;
41
- function getNewOptionsWithPrivacySettings(originalOptions, privacySettings) {
41
+ function getNewRRWebConfigWithPrivacySettings(originalOptions, privacySettings) {
42
42
  const maskValue = (value) => {
43
43
  return value.replace(/./g, '*');
44
44
  };
45
+ function getNearestPrivacyMatch(element, maskedSelectors = [], unmaskedSelectors = []) {
46
+ let current = element;
47
+ while (current !== null) {
48
+ if (maskedSelectors.some((sel) => current === null || current === void 0 ? void 0 : current.matches(sel))) {
49
+ return 'masked';
50
+ }
51
+ if (unmaskedSelectors.some((sel) => current === null || current === void 0 ? void 0 : current.matches(sel))) {
52
+ return 'unmasked';
53
+ }
54
+ current = current.parentElement;
55
+ }
56
+ return null;
57
+ }
45
58
  const blockSelector = privacySettings.blocked_elements
46
59
  ? privacySettings.blocked_elements.join(', ')
47
60
  : undefined;
48
- const maskTextSelector = privacySettings.masked_elements
49
- ? privacySettings.masked_elements.join(', ')
50
- : undefined;
61
+ const maskTextFn = (value, element) => {
62
+ if (!element) {
63
+ return privacySettings.privacy_mode === 'max' ? maskValue(value) : value;
64
+ }
65
+ const nearest = getNearestPrivacyMatch(element, privacySettings.masked_elements, privacySettings.unmasked_elements);
66
+ if (nearest === 'masked') {
67
+ return maskValue(value);
68
+ }
69
+ if (nearest === 'unmasked') {
70
+ return value;
71
+ }
72
+ return privacySettings.privacy_mode === 'max' ? maskValue(value) : value;
73
+ };
51
74
  const maskInputFn = (value, element) => {
52
75
  var _a, _b;
53
76
  if ((_a = privacySettings.masked_elements) === null || _a === void 0 ? void 0 : _a.some((sel) => element.closest(sel))) {
@@ -61,7 +84,6 @@ function getNewOptionsWithPrivacySettings(originalOptions, privacySettings) {
61
84
  ? maskValue(value)
62
85
  : value;
63
86
  };
64
- return Object.assign(Object.assign({}, originalOptions), { rrwebConfig: Object.assign(Object.assign({}, originalOptions === null || originalOptions === void 0 ? void 0 : originalOptions.rrwebConfig), { maskTextFn: maskValue, maskInputFn,
65
- maskTextSelector, maskAllInputs: true, maskInputOptions: undefined, blockSelector }) });
87
+ return Object.assign(Object.assign({}, originalOptions), { maskTextFn: maskTextFn, maskInputFn, maskTextSelector: '*', maskAllInputs: true, maskInputOptions: undefined, blockSelector });
66
88
  }
67
- exports.getNewOptionsWithPrivacySettings = getNewOptionsWithPrivacySettings;
89
+ exports.getNewRRWebConfigWithPrivacySettings = getNewRRWebConfigWithPrivacySettings;
@@ -22,20 +22,15 @@ function runStatsigTriggeredSessionReplay(client, options) {
22
22
  exports.runStatsigTriggeredSessionReplay = runStatsigTriggeredSessionReplay;
23
23
  class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
24
24
  constructor(client, options) {
25
- var _a, _b;
26
- let newOptions = options;
27
- const privacySettings = (_a = client.getContext().values) === null || _a === void 0 ? void 0 : _a.session_recording_privacy_settings;
28
- if (privacySettings) {
29
- newOptions = (0, SessionReplayUtils_1.getNewOptionsWithPrivacySettings)(newOptions !== null && newOptions !== void 0 ? newOptions : {}, privacySettings);
30
- }
31
- super(client, newOptions);
25
+ var _a;
26
+ super(client, options);
32
27
  this._runningEventData = [];
33
28
  this._isActiveRecording = false;
34
- this._subscribeToClientEvents(newOptions);
35
- if (newOptions === null || newOptions === void 0 ? void 0 : newOptions.autoStartRecording) {
36
- this._attemptToStartRecording((_b = this._options) === null || _b === void 0 ? void 0 : _b.forceRecording);
29
+ this._subscribeToClientEvents(options);
30
+ if (options === null || options === void 0 ? void 0 : options.autoStartRecording) {
31
+ this._attemptToStartRecording((_a = this._options) === null || _a === void 0 ? void 0 : _a.forceRecording);
37
32
  }
38
- else if (newOptions === null || newOptions === void 0 ? void 0 : newOptions.keepRollingWindow) {
33
+ else if (options === null || options === void 0 ? void 0 : options.keepRollingWindow) {
39
34
  this._attemptToStartRollingWindow();
40
35
  }
41
36
  }
@@ -64,7 +59,7 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
64
59
  if (this._wasStopped) {
65
60
  return;
66
61
  }
67
- const values = this._client.getContext().values;
62
+ const values = this._client.getContextHandle().values;
68
63
  const passedTargeting = values === null || values === void 0 ? void 0 : values.passes_session_recording_targeting;
69
64
  if (passedTargeting === false ||
70
65
  (values === null || values === void 0 ? void 0 : values.session_recording_event_triggers) == null) {
@@ -104,7 +99,7 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
104
99
  if (this._wasStopped) {
105
100
  return;
106
101
  }
107
- const values = this._client.getContext().values;
102
+ const values = this._client.getContextHandle().values;
108
103
  const passedTargeting = values === null || values === void 0 ? void 0 : values.passes_session_recording_targeting;
109
104
  if (passedTargeting === false ||
110
105
  (values === null || values === void 0 ? void 0 : values.session_recording_exposure_triggers) == null) {
@@ -201,7 +196,7 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
201
196
  }
202
197
  _attemptToStartRollingWindow() {
203
198
  var _a, _b;
204
- const values = this._client.getContext().values;
199
+ const values = this._client.getContextHandle().values;
205
200
  if ((values === null || values === void 0 ? void 0 : values.passes_session_recording_targeting) === false) {
206
201
  this._shutdown();
207
202
  return;
@@ -214,11 +209,11 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
214
209
  }, true);
215
210
  }
216
211
  _attemptToStartRecording(force = false) {
217
- var _a, _b;
212
+ var _a, _b, _c, _d, _e;
218
213
  if (this._totalLogs >= SessionReplayUtils_1.MAX_LOGS) {
219
214
  return;
220
215
  }
221
- const values = this._client.getContext().values;
216
+ const values = this._client.getContextHandle().values;
222
217
  if ((values === null || values === void 0 ? void 0 : values.recording_blocked) === true) {
223
218
  this._shutdown();
224
219
  return;
@@ -237,7 +232,15 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
237
232
  if (this._replayer.isRecording()) {
238
233
  return;
239
234
  }
240
- this._replayer.record((e, d, isCheckOut) => this._onRecordingEvent(e, d, isCheckOut), (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.rrwebConfig) !== null && _b !== void 0 ? _b : {}, () => {
235
+ const usePrivacySettings = (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings) &&
236
+ ((values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.privacy_mode) !== 'min' ||
237
+ (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.blocked_elements) ||
238
+ (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.masked_elements) ||
239
+ (values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings.unmasked_elements));
240
+ const newRRWebConfig = usePrivacySettings
241
+ ? (0, SessionReplayUtils_1.getNewRRWebConfigWithPrivacySettings)((_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.rrwebConfig) !== null && _b !== void 0 ? _b : {}, (_c = values === null || values === void 0 ? void 0 : values.session_recording_privacy_settings) !== null && _c !== void 0 ? _c : {})
242
+ : (_e = (_d = this._options) === null || _d === void 0 ? void 0 : _d.rrwebConfig) !== null && _e !== void 0 ? _e : {};
243
+ this._replayer.record((e, d, isCheckOut) => this._onRecordingEvent(e, d, isCheckOut), newRRWebConfig, () => {
241
244
  this._shutdown();
242
245
  });
243
246
  }