@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 +2 -2
- package/src/SessionReplay.js +11 -3
- package/src/SessionReplayBase.d.ts +1 -1
- package/src/SessionReplayBase.js +7 -6
- package/src/SessionReplayUtils.d.ts +3 -2
- package/src/SessionReplayUtils.js +50 -1
- package/src/TriggeredSessionReplay.d.ts +2 -3
- package/src/TriggeredSessionReplay.js +14 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statsig/session-replay",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
14
|
+
"@statsig/client-core": "3.31.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@rrweb/types": "2.0.0-alpha.16"
|
package/src/SessionReplay.js
CHANGED
|
@@ -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.
|
|
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),
|
|
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;
|
package/src/SessionReplayBase.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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?:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
}
|