@statsig/session-replay 3.30.2 → 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.30.2",
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.30.2"
14
+ "@statsig/client-core": "3.31.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@rrweb/types": "2.0.0-alpha.16"
@@ -38,11 +38,11 @@ class SessionReplay extends SessionReplayBase_1.SessionReplayBase {
38
38
  super._shutdownImpl(endReason);
39
39
  }
40
40
  _attemptToStartRecording(force = false) {
41
- var _a, _b;
41
+ var _a, _b, _c, _d, _e;
42
42
  if (this._totalLogs >= SessionReplayUtils_1.MAX_LOGS) {
43
43
  return;
44
44
  }
45
- const values = this._client.getContext().values;
45
+ const values = this._client.getContextHandle().values;
46
46
  if ((values === null || values === void 0 ? void 0 : values.recording_blocked) === true) {
47
47
  this._shutdown();
48
48
  return;
@@ -58,9 +58,17 @@ class SessionReplay extends SessionReplayBase_1.SessionReplayBase {
58
58
  if (this._replayer.isRecording()) {
59
59
  return;
60
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 : {};
61
69
  this._wasStopped = false;
62
70
  client_core_1.StatsigMetadataProvider.add({ isRecordingSession: 'true' });
63
- 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, () => {
64
72
  this._shutdown();
65
73
  });
66
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,5 +1,5 @@
1
- import { StatsigEvent } from '@statsig/client-core';
2
- import { ReplaySessionData } from './SessionReplayClient';
1
+ import { SessionReplayPrivacySettings, StatsigEvent } from '@statsig/client-core';
2
+ import { RRWebConfig, ReplaySessionData } from './SessionReplayClient';
3
3
  export type RRWebPayload = {
4
4
  session_start_ts: string;
5
5
  session_end_ts: string;
@@ -23,3 +23,4 @@ export declare function _makeLoggableRrwebEvent(slice: string, payload: string,
23
23
  };
24
24
  export declare function _slicePayload(payload: string): string[];
25
25
  export declare function _appendSlicedMetadata(metadata: RRWebPayload, slicedID: string, sliceIndex: number, sliceCount: number, sliceByteSize: number): void;
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._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,3 +38,52 @@ function _appendSlicedMetadata(metadata, slicedID, sliceIndex, sliceCount, slice
38
38
  metadata.slice_byte_size = String(sliceByteSize);
39
39
  }
40
40
  exports._appendSlicedMetadata = _appendSlicedMetadata;
41
+ function getNewRRWebConfigWithPrivacySettings(originalOptions, privacySettings) {
42
+ const maskValue = (value) => {
43
+ return value.replace(/./g, '*');
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
+ }
58
+ const blockSelector = privacySettings.blocked_elements
59
+ ? privacySettings.blocked_elements.join(', ')
60
+ : 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
+ };
74
+ const maskInputFn = (value, element) => {
75
+ var _a, _b;
76
+ if ((_a = privacySettings.masked_elements) === null || _a === void 0 ? void 0 : _a.some((sel) => element.closest(sel))) {
77
+ return maskValue(value);
78
+ }
79
+ if ((_b = privacySettings.unmasked_elements) === null || _b === void 0 ? void 0 : _b.some((sel) => element.closest(sel))) {
80
+ return value;
81
+ }
82
+ return privacySettings.privacy_mode === 'max' ||
83
+ privacySettings.privacy_mode === 'input'
84
+ ? maskValue(value)
85
+ : value;
86
+ };
87
+ return Object.assign(Object.assign({}, originalOptions), { maskTextFn: maskTextFn, maskInputFn, maskTextSelector: '*', maskAllInputs: true, maskInputOptions: undefined, blockSelector });
88
+ }
89
+ exports.getNewRRWebConfigWithPrivacySettings = getNewRRWebConfigWithPrivacySettings;
@@ -1,7 +1,7 @@
1
1
  import { PrecomputedEvaluationsInterface, StatsigPlugin } from '@statsig/client-core';
2
2
  import { EndReason, SessionReplayBase } from './SessionReplayBase';
3
3
  import { RRWebConfig, ReplayEvent, ReplaySessionData } from './SessionReplayClient';
4
- type SessionReplayOptions = {
4
+ export type SessionReplayOptions = {
5
5
  rrwebConfig?: RRWebConfig;
6
6
  forceRecording?: boolean;
7
7
  };
@@ -15,7 +15,7 @@ export declare class StatsigTriggeredSessionReplayPlugin implements StatsigPlugi
15
15
  constructor(options?: TriggeredSessionReplayOptions | undefined);
16
16
  bind(client: PrecomputedEvaluationsInterface): void;
17
17
  }
18
- export declare function runStatsigTriggeredSessionReplay(client: PrecomputedEvaluationsInterface, options?: SessionReplayOptions): void;
18
+ export declare function runStatsigTriggeredSessionReplay(client: PrecomputedEvaluationsInterface, options?: TriggeredSessionReplayOptions): void;
19
19
  export declare class TriggeredSessionReplay extends SessionReplayBase {
20
20
  private _runningEventData;
21
21
  private _isActiveRecording;
@@ -34,4 +34,3 @@ export declare class TriggeredSessionReplay extends SessionReplayBase {
34
34
  protected _attemptToStartRollingWindow(): void;
35
35
  protected _attemptToStartRecording(force?: boolean): void;
36
36
  }
37
- export {};
@@ -59,7 +59,7 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
59
59
  if (this._wasStopped) {
60
60
  return;
61
61
  }
62
- const values = this._client.getContext().values;
62
+ const values = this._client.getContextHandle().values;
63
63
  const passedTargeting = values === null || values === void 0 ? void 0 : values.passes_session_recording_targeting;
64
64
  if (passedTargeting === false ||
65
65
  (values === null || values === void 0 ? void 0 : values.session_recording_event_triggers) == null) {
@@ -99,7 +99,7 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
99
99
  if (this._wasStopped) {
100
100
  return;
101
101
  }
102
- const values = this._client.getContext().values;
102
+ const values = this._client.getContextHandle().values;
103
103
  const passedTargeting = values === null || values === void 0 ? void 0 : values.passes_session_recording_targeting;
104
104
  if (passedTargeting === false ||
105
105
  (values === null || values === void 0 ? void 0 : values.session_recording_exposure_triggers) == null) {
@@ -196,7 +196,7 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
196
196
  }
197
197
  _attemptToStartRollingWindow() {
198
198
  var _a, _b;
199
- const values = this._client.getContext().values;
199
+ const values = this._client.getContextHandle().values;
200
200
  if ((values === null || values === void 0 ? void 0 : values.passes_session_recording_targeting) === false) {
201
201
  this._shutdown();
202
202
  return;
@@ -209,11 +209,11 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
209
209
  }, true);
210
210
  }
211
211
  _attemptToStartRecording(force = false) {
212
- var _a, _b;
212
+ var _a, _b, _c, _d, _e;
213
213
  if (this._totalLogs >= SessionReplayUtils_1.MAX_LOGS) {
214
214
  return;
215
215
  }
216
- const values = this._client.getContext().values;
216
+ const values = this._client.getContextHandle().values;
217
217
  if ((values === null || values === void 0 ? void 0 : values.recording_blocked) === true) {
218
218
  this._shutdown();
219
219
  return;
@@ -232,7 +232,15 @@ class TriggeredSessionReplay extends SessionReplayBase_1.SessionReplayBase {
232
232
  if (this._replayer.isRecording()) {
233
233
  return;
234
234
  }
235
- 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, () => {
236
244
  this._shutdown();
237
245
  });
238
246
  }