@amplitude/session-replay-browser 1.48.3 → 1.50.0
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/README.md +16 -2
- package/lib/cjs/config/local-config.d.ts +1 -0
- package/lib/cjs/config/local-config.d.ts.map +1 -1
- package/lib/cjs/config/local-config.js +1 -0
- package/lib/cjs/config/local-config.js.map +1 -1
- package/lib/cjs/config/types.d.ts +6 -0
- package/lib/cjs/config/types.d.ts.map +1 -1
- package/lib/cjs/config/types.js.map +1 -1
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +2 -2
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/session-replay-factory.d.ts.map +1 -1
- package/lib/cjs/session-replay-factory.js +2 -0
- package/lib/cjs/session-replay-factory.js.map +1 -1
- package/lib/cjs/session-replay.d.ts +22 -0
- package/lib/cjs/session-replay.d.ts.map +1 -1
- package/lib/cjs/session-replay.js +112 -36
- package/lib/cjs/session-replay.js.map +1 -1
- package/lib/cjs/typings/session-replay.d.ts +11 -0
- package/lib/cjs/typings/session-replay.d.ts.map +1 -1
- package/lib/cjs/typings/session-replay.js.map +1 -1
- package/lib/cjs/utils/rrweb.d.ts +1 -0
- package/lib/cjs/utils/rrweb.d.ts.map +1 -1
- package/lib/cjs/utils/rrweb.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/esm/config/local-config.d.ts +1 -0
- package/lib/esm/config/local-config.d.ts.map +1 -1
- package/lib/esm/config/local-config.js +1 -0
- package/lib/esm/config/local-config.js.map +1 -1
- package/lib/esm/config/types.d.ts +6 -0
- package/lib/esm/config/types.d.ts.map +1 -1
- package/lib/esm/config/types.js.map +1 -1
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/session-replay-factory.d.ts.map +1 -1
- package/lib/esm/session-replay-factory.js +2 -0
- package/lib/esm/session-replay-factory.js.map +1 -1
- package/lib/esm/session-replay.d.ts +22 -0
- package/lib/esm/session-replay.d.ts.map +1 -1
- package/lib/esm/session-replay.js +112 -36
- package/lib/esm/session-replay.js.map +1 -1
- package/lib/esm/typings/session-replay.d.ts +11 -0
- package/lib/esm/typings/session-replay.d.ts.map +1 -1
- package/lib/esm/typings/session-replay.js.map +1 -1
- package/lib/esm/utils/rrweb.d.ts +1 -0
- package/lib/esm/utils/rrweb.d.ts.map +1 -1
- package/lib/esm/utils/rrweb.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +1 -1
- package/lib/scripts/index-min.js +1 -1
- package/lib/scripts/index-min.js.gz +0 -0
- package/lib/scripts/index-min.js.map +1 -1
- package/lib/scripts/session-replay-browser-esm.js +1 -1
- package/lib/scripts/session-replay-browser-esm.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js +1 -1
- package/lib/scripts/session-replay-browser-min.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js.map +1 -1
- package/package.json +3 -3
|
@@ -41,6 +41,24 @@ var SessionReplay = /** @class */ (function () {
|
|
|
41
41
|
// Cache the dynamically imported record function
|
|
42
42
|
this.recordFunction = null;
|
|
43
43
|
this.recordEventsInFlight = false;
|
|
44
|
+
/**
|
|
45
|
+
* When false, capture is paused by a customer `stop()` (or `shutdown()`) call.
|
|
46
|
+
* Focus/targeting/session-change paths still invoke `recordEvents()`, but
|
|
47
|
+
* `getShouldRecord()` will refuse to start rrweb until `start()` or a new `init()`.
|
|
48
|
+
*/
|
|
49
|
+
this.recordingEnabled = true;
|
|
50
|
+
/**
|
|
51
|
+
* Bumped by `stop()` / `shutdown()` so an in-flight `_recordEvents()` that already
|
|
52
|
+
* passed `getShouldRecord()` cannot start rrweb after capture was paused.
|
|
53
|
+
*/
|
|
54
|
+
this.recordingGeneration = 0;
|
|
55
|
+
/**
|
|
56
|
+
* Set by customer `start()` so coordinated child iframes self-start instead of
|
|
57
|
+
* waiting for a parent signal that may never be resent. Kept until recording
|
|
58
|
+
* actually starts so a gated `start()` (targeting, sampling, remote capture)
|
|
59
|
+
* still self-starts when those gates later pass.
|
|
60
|
+
*/
|
|
61
|
+
this.startChildRecordingOnSetup = false;
|
|
44
62
|
this.pendingEmitEvents = [];
|
|
45
63
|
/** Current page URL, kept in sync with SPA navigations for URL-based masking */
|
|
46
64
|
this.currentPageUrl = '';
|
|
@@ -77,6 +95,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
77
95
|
};
|
|
78
96
|
this.focusListener = function () {
|
|
79
97
|
var _a;
|
|
98
|
+
if (!_this.recordingEnabled) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
80
101
|
if (_this.recordCancelCallback && _this.recordFunction) {
|
|
81
102
|
// Recording is already active. The on-focus full snapshot is tunable: when
|
|
82
103
|
// `captureFullSnapshotOnFocus` is false we skip it entirely so high focus-churn pages
|
|
@@ -332,6 +353,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
332
353
|
// Re-init should always tear down any previous URL-change subscription, even when the
|
|
333
354
|
// next config has no targeting config and we don't subscribe again.
|
|
334
355
|
(_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
356
|
+
// A new init always allows capture again. `stop()` only pauses the current instance.
|
|
357
|
+
this.recordingEnabled = true;
|
|
358
|
+
this.startChildRecordingOnSetup = false;
|
|
335
359
|
this.loggerProvider = new SafeLoggerProvider(options.loggerProvider || new Logger());
|
|
336
360
|
Object.prototype.hasOwnProperty.call(options, 'logLevel') &&
|
|
337
361
|
this.loggerProvider.enable(options.logLevel);
|
|
@@ -693,6 +717,10 @@ var SessionReplay = /** @class */ (function () {
|
|
|
693
717
|
this.loggerProvider.warn("Session is not being recorded due to lack of config, please call sessionReplay.init.");
|
|
694
718
|
return false;
|
|
695
719
|
}
|
|
720
|
+
if (!this.recordingEnabled) {
|
|
721
|
+
this.loggerProvider.log("Session ".concat(this.identifiers.sessionId, " not being captured because recording was stopped."));
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
696
724
|
if (!this.config.captureEnabled) {
|
|
697
725
|
this.loggerProvider.log("Session ".concat(this.identifiers.sessionId, " not being captured due to capture being disabled for project or because the remote config could not be fetched."));
|
|
698
726
|
return false;
|
|
@@ -885,15 +913,17 @@ var SessionReplay = /** @class */ (function () {
|
|
|
885
913
|
});
|
|
886
914
|
});
|
|
887
915
|
};
|
|
916
|
+
SessionReplay.prototype.shouldAbandonRecordStart = function (generation) {
|
|
917
|
+
return !this.recordingEnabled || generation !== this.recordingGeneration;
|
|
918
|
+
};
|
|
888
919
|
SessionReplay.prototype._recordEvents = function (shouldLogMetadata) {
|
|
889
920
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
890
921
|
if (shouldLogMetadata === void 0) { shouldLogMetadata = true; }
|
|
891
922
|
return __awaiter(this, void 0, void 0, function () {
|
|
892
|
-
var config, shouldRecord, sessionId, recordFunction, networkLoggingConfig, trackUrl, ignoredUrls, interactionConfig, loggingConfig, hooks, ugcFilterRules, crossOriginIframesEnabled, coordinateChildren, childMode,
|
|
893
|
-
var _m;
|
|
923
|
+
var config, shouldRecord, sessionId, generation, recordFunction, networkLoggingConfig, trackUrl, ignoredUrls, interactionConfig, loggingConfig, hooks, ugcFilterRules, crossOriginIframesEnabled, coordinateChildren, childMode, plugins, error_5;
|
|
894
924
|
var _this = this;
|
|
895
|
-
return __generator(this, function (
|
|
896
|
-
switch (
|
|
925
|
+
return __generator(this, function (_j) {
|
|
926
|
+
switch (_j.label) {
|
|
897
927
|
case 0:
|
|
898
928
|
config = this.config;
|
|
899
929
|
shouldRecord = this.getShouldRecord();
|
|
@@ -902,16 +932,20 @@ var SessionReplay = /** @class */ (function () {
|
|
|
902
932
|
return [2 /*return*/];
|
|
903
933
|
}
|
|
904
934
|
this.stopRecordingEvents();
|
|
935
|
+
generation = this.recordingGeneration;
|
|
905
936
|
return [4 /*yield*/, this.getRecordFunction()];
|
|
906
937
|
case 1:
|
|
907
|
-
recordFunction =
|
|
938
|
+
recordFunction = _j.sent();
|
|
908
939
|
// May be undefined if cannot import rrweb-record
|
|
909
|
-
if (!recordFunction) {
|
|
940
|
+
if (!recordFunction || this.shouldAbandonRecordStart(generation)) {
|
|
910
941
|
return [2 /*return*/];
|
|
911
942
|
}
|
|
912
943
|
return [4 /*yield*/, this.initializeNetworkObservers()];
|
|
913
944
|
case 2:
|
|
914
|
-
|
|
945
|
+
_j.sent();
|
|
946
|
+
if (this.shouldAbandonRecordStart(generation)) {
|
|
947
|
+
return [2 /*return*/];
|
|
948
|
+
}
|
|
915
949
|
networkLoggingConfig = (_b = config.loggingConfig) === null || _b === void 0 ? void 0 : _b.network;
|
|
916
950
|
trackUrl = getServerUrl(config.serverZone, config.trackServerUrl);
|
|
917
951
|
ignoredUrls = [SESSION_REPLAY_SERVER_URL, SESSION_REPLAY_EU_URL, SESSION_REPLAY_STAGING_URL, trackUrl];
|
|
@@ -937,9 +971,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
937
971
|
: {};
|
|
938
972
|
ugcFilterRules = (interactionConfig === null || interactionConfig === void 0 ? void 0 : interactionConfig.enabled) && interactionConfig.ugcFilterRules ? interactionConfig.ugcFilterRules : [];
|
|
939
973
|
this.loggerProvider.log("Session Replay capture beginning for ".concat(sessionId, "."));
|
|
940
|
-
|
|
974
|
+
_j.label = 3;
|
|
941
975
|
case 3:
|
|
942
|
-
|
|
976
|
+
_j.trys.push([3, 5, , 6]);
|
|
943
977
|
crossOriginIframesEnabled = !!((_g = config.crossOriginIframes) === null || _g === void 0 ? void 0 : _g.enabled);
|
|
944
978
|
coordinateChildren = ((_h = config.crossOriginIframes) === null || _h === void 0 ? void 0 : _h.coordinateChildren) !== false;
|
|
945
979
|
childMode = crossOriginIframesEnabled && isInIframe();
|
|
@@ -947,7 +981,12 @@ var SessionReplay = /** @class */ (function () {
|
|
|
947
981
|
// Child mode: don't self-start; wait for a start signal from the parent.
|
|
948
982
|
// (The previous listener, if any, was already removed by stopRecordingEvents above.)
|
|
949
983
|
this.crossOriginParentSignalCleanup = listenForParentSignals({
|
|
950
|
-
onStart: function () {
|
|
984
|
+
onStart: function () {
|
|
985
|
+
if (!_this.recordingEnabled) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
_this._recordEventsInChildMode(recordFunction, sessionId, config, hooks);
|
|
989
|
+
},
|
|
951
990
|
onStop: function () {
|
|
952
991
|
try {
|
|
953
992
|
// Only cancel the rrweb recording — do NOT call stopRecordingEvents() here,
|
|
@@ -962,34 +1001,43 @@ var SessionReplay = /** @class */ (function () {
|
|
|
962
1001
|
}
|
|
963
1002
|
},
|
|
964
1003
|
});
|
|
1004
|
+
// Customer start() must not wait for a parent signal: the parent may already
|
|
1005
|
+
// be recording and will not send another start. Init still waits.
|
|
1006
|
+
if (this.startChildRecordingOnSetup) {
|
|
1007
|
+
this.startChildRecordingOnSetup = false;
|
|
1008
|
+
this._recordEventsInChildMode(recordFunction, sessionId, config, hooks);
|
|
1009
|
+
}
|
|
965
1010
|
return [2 /*return*/];
|
|
966
1011
|
}
|
|
967
|
-
|
|
968
|
-
_k = recordFunction;
|
|
969
|
-
_l = [__assign({}, this.buildRRWebRecordOptions(config, hooks, function (event) {
|
|
970
|
-
if (_this.shouldOptOut()) {
|
|
971
|
-
_this.loggerProvider.log("Opting session ".concat(sessionId, " out of recording due to optOut config."));
|
|
972
|
-
_this.stopRecordingEvents();
|
|
973
|
-
_this.sendEvents();
|
|
974
|
-
return;
|
|
975
|
-
}
|
|
976
|
-
if (event.type === RRWebEventType.Meta) {
|
|
977
|
-
event.data.href = getPageUrl(event.data.href, ugcFilterRules);
|
|
978
|
-
}
|
|
979
|
-
if (_this.eventCompressor) {
|
|
980
|
-
// Schedule processing during idle time if the browser supports requestIdleCallback
|
|
981
|
-
_this.eventCompressor.enqueueEvent(event, sessionId);
|
|
982
|
-
}
|
|
983
|
-
else {
|
|
984
|
-
// eventCompressor is not yet ready (concurrent call racing _init()).
|
|
985
|
-
// Buffer the event so it can be flushed once the compressor is initialized.
|
|
986
|
-
_this.pendingEmitEvents.push({ event: event, sessionId: sessionId });
|
|
987
|
-
}
|
|
988
|
-
}, 'Error while capturing replay: '))];
|
|
989
|
-
_m = {};
|
|
1012
|
+
this.startChildRecordingOnSetup = false;
|
|
990
1013
|
return [4 /*yield*/, this.getRecordingPlugins(loggingConfig)];
|
|
991
1014
|
case 4:
|
|
992
|
-
|
|
1015
|
+
plugins = _j.sent();
|
|
1016
|
+
if (this.shouldAbandonRecordStart(generation)) {
|
|
1017
|
+
return [2 /*return*/];
|
|
1018
|
+
}
|
|
1019
|
+
this.recordCancelCallback = recordFunction(__assign(__assign({}, this.buildRRWebRecordOptions(config, hooks, function (event) {
|
|
1020
|
+
if (_this.shouldOptOut() || !_this.recordingEnabled) {
|
|
1021
|
+
_this.loggerProvider.log(_this.shouldOptOut()
|
|
1022
|
+
? "Opting session ".concat(sessionId, " out of recording due to optOut config.")
|
|
1023
|
+
: "Session Replay capture stopping for ".concat(sessionId, "."));
|
|
1024
|
+
_this.stopRecordingEvents();
|
|
1025
|
+
_this.sendEvents();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (event.type === RRWebEventType.Meta) {
|
|
1029
|
+
event.data.href = getPageUrl(event.data.href, ugcFilterRules);
|
|
1030
|
+
}
|
|
1031
|
+
if (_this.eventCompressor) {
|
|
1032
|
+
// Schedule processing during idle time if the browser supports requestIdleCallback
|
|
1033
|
+
_this.eventCompressor.enqueueEvent(event, sessionId);
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
// eventCompressor is not yet ready (concurrent call racing _init()).
|
|
1037
|
+
// Buffer the event so it can be flushed once the compressor is initialized.
|
|
1038
|
+
_this.pendingEmitEvents.push({ event: event, sessionId: sessionId });
|
|
1039
|
+
}
|
|
1040
|
+
}, 'Error while capturing replay: ')), { plugins: plugins, recordCrossOriginIframes: crossOriginIframesEnabled }));
|
|
993
1041
|
if (crossOriginIframesEnabled && !childMode && coordinateChildren) {
|
|
994
1042
|
if (!this.crossOriginIframeCoordinator) {
|
|
995
1043
|
this.crossOriginIframeCoordinator = new CrossOriginIframeCoordinator();
|
|
@@ -1002,7 +1050,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
1002
1050
|
}
|
|
1003
1051
|
return [3 /*break*/, 6];
|
|
1004
1052
|
case 5:
|
|
1005
|
-
error_5 =
|
|
1053
|
+
error_5 = _j.sent();
|
|
1006
1054
|
this.loggerProvider.warn('Failed to initialize session replay:', error_5);
|
|
1007
1055
|
return [3 /*break*/, 6];
|
|
1008
1056
|
case 6: return [2 /*return*/];
|
|
@@ -1013,7 +1061,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
1013
1061
|
SessionReplay.prototype.buildRRWebRecordOptions = function (config, hooks, emit, errorLogPrefix) {
|
|
1014
1062
|
var _this = this;
|
|
1015
1063
|
var privacyConfig = config.privacyConfig;
|
|
1016
|
-
return __assign(__assign({ emit: emit, inlineStylesheet: config.shouldInlineStylesheet, hooks: hooks, maskAllInputs: true, maskTextClass: MASK_TEXT_CLASS, blockClass: BLOCK_CLASS, blockSelector: this.getBlockSelectors(), applyBackgroundColorToBlockedElements: config.applyBackgroundColorToBlockedElements, maskInputFn: maskFn('input', privacyConfig, function () { return _this.currentPageUrl; }), maskTextFn: maskFn('text', privacyConfig, function () { return _this.currentPageUrl; }), maskAttributeFn: maskAttributeFn(privacyConfig, function () { return _this.currentPageUrl; }), maskTextSelector: this.getMaskTextSelectors() }, (config.fullSnapshotIntervalMs !== undefined && { checkoutEveryNms: config.fullSnapshotIntervalMs })), { recordCanvas: false, captureAdoptedStyleSheets: config.captureAdoptedStyleSheets,
|
|
1064
|
+
return __assign(__assign({ emit: emit, inlineStylesheet: config.shouldInlineStylesheet, inlineImages: config.inlineImages, hooks: hooks, maskAllInputs: true, maskTextClass: MASK_TEXT_CLASS, blockClass: BLOCK_CLASS, blockSelector: this.getBlockSelectors(), applyBackgroundColorToBlockedElements: config.applyBackgroundColorToBlockedElements, maskInputFn: maskFn('input', privacyConfig, function () { return _this.currentPageUrl; }), maskTextFn: maskFn('text', privacyConfig, function () { return _this.currentPageUrl; }), maskAttributeFn: maskAttributeFn(privacyConfig, function () { return _this.currentPageUrl; }), maskTextSelector: this.getMaskTextSelectors() }, (config.fullSnapshotIntervalMs !== undefined && { checkoutEveryNms: config.fullSnapshotIntervalMs })), { recordCanvas: false, captureAdoptedStyleSheets: config.captureAdoptedStyleSheets,
|
|
1017
1065
|
// Strip nodes that are never rendered by the rrweb replay player.
|
|
1018
1066
|
// None of these affect visual fidelity; omitting them reduces snapshot size.
|
|
1019
1067
|
slimDOMOptions: {
|
|
@@ -1072,6 +1120,31 @@ var SessionReplay = /** @class */ (function () {
|
|
|
1072
1120
|
var _a;
|
|
1073
1121
|
return (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
|
|
1074
1122
|
};
|
|
1123
|
+
SessionReplay.prototype.start = function () {
|
|
1124
|
+
return returnWrapper(this._start());
|
|
1125
|
+
};
|
|
1126
|
+
SessionReplay.prototype._start = function () {
|
|
1127
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1128
|
+
return __generator(this, function (_a) {
|
|
1129
|
+
switch (_a.label) {
|
|
1130
|
+
case 0:
|
|
1131
|
+
this.recordingEnabled = true;
|
|
1132
|
+
this.startChildRecordingOnSetup = true;
|
|
1133
|
+
return [4 /*yield*/, this.recordEvents()];
|
|
1134
|
+
case 1:
|
|
1135
|
+
_a.sent();
|
|
1136
|
+
return [2 /*return*/];
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
};
|
|
1141
|
+
SessionReplay.prototype.stop = function () {
|
|
1142
|
+
this.recordingEnabled = false;
|
|
1143
|
+
this.startChildRecordingOnSetup = false;
|
|
1144
|
+
this.recordingGeneration++;
|
|
1145
|
+
this.stopRecordingEvents();
|
|
1146
|
+
this.sendEvents();
|
|
1147
|
+
};
|
|
1075
1148
|
SessionReplay.prototype.flush = function (useRetry) {
|
|
1076
1149
|
var _a;
|
|
1077
1150
|
if (useRetry === void 0) { useRetry = false; }
|
|
@@ -1088,6 +1161,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
1088
1161
|
};
|
|
1089
1162
|
SessionReplay.prototype.shutdown = function () {
|
|
1090
1163
|
var _a, _b;
|
|
1164
|
+
this.recordingEnabled = false;
|
|
1165
|
+
this.startChildRecordingOnSetup = false;
|
|
1166
|
+
this.recordingGeneration++;
|
|
1091
1167
|
(_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1092
1168
|
(_b = this.crossOriginParentSignalCleanup) === null || _b === void 0 ? void 0 : _b.call(this);
|
|
1093
1169
|
this.crossOriginParentSignalCleanup = null;
|