@newrelic/browser-agent 1.256.1 → 1.257.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/CHANGELOG.md +16 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/harvest/harvest.js +7 -5
- package/dist/cjs/features/jserrors/aggregate/index.js +25 -12
- package/dist/cjs/features/session_replay/aggregate/index.js +11 -5
- package/dist/cjs/features/session_replay/constants.js +2 -1
- package/dist/cjs/features/session_replay/instrument/index.js +15 -5
- package/dist/cjs/features/session_replay/shared/recorder.js +6 -3
- package/dist/cjs/features/session_replay/shared/utils.js +6 -5
- package/dist/cjs/features/soft_navigations/aggregate/index.js +2 -2
- package/dist/cjs/features/utils/instrument-base.js +11 -14
- package/dist/cjs/features/utils/nr1-debugger.js +27 -0
- package/dist/cjs/loaders/agent.js +4 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/harvest/harvest.js +7 -5
- package/dist/esm/features/jserrors/aggregate/index.js +25 -12
- package/dist/esm/features/session_replay/aggregate/index.js +11 -5
- package/dist/esm/features/session_replay/constants.js +2 -1
- package/dist/esm/features/session_replay/instrument/index.js +16 -6
- package/dist/esm/features/session_replay/shared/recorder.js +6 -3
- package/dist/esm/features/session_replay/shared/utils.js +5 -3
- package/dist/esm/features/soft_navigations/aggregate/index.js +2 -2
- package/dist/esm/features/utils/instrument-base.js +11 -14
- package/dist/esm/features/utils/nr1-debugger.js +21 -0
- package/dist/esm/loaders/agent.js +4 -0
- package/dist/types/common/harvest/harvest.d.ts +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +0 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +0 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/constants.d.ts +1 -0
- package/dist/types/features/session_replay/constants.d.ts.map +1 -1
- package/dist/types/features/session_replay/instrument/index.d.ts +1 -0
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +1 -0
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts +2 -2
- package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/features/utils/nr1-debugger.d.ts +2 -0
- package/dist/types/features/utils/nr1-debugger.d.ts.map +1 -0
- package/dist/types/loaders/agent.d.ts +5 -1
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/drain/__mocks__/drain.js +2 -0
- package/src/common/harvest/harvest.js +8 -6
- package/src/common/window/__mocks__/load.js +3 -0
- package/src/features/jserrors/aggregate/index.js +26 -10
- package/src/features/session_replay/aggregate/index.js +17 -7
- package/src/features/session_replay/constants.js +2 -1
- package/src/features/session_replay/instrument/index.js +16 -6
- package/src/features/session_replay/shared/__mocks__/utils.js +2 -0
- package/src/features/session_replay/shared/recorder.js +7 -4
- package/src/features/session_replay/shared/utils.js +5 -3
- package/src/features/soft_navigations/aggregate/index.js +2 -2
- package/src/features/utils/__mocks__/agent-session.js +1 -0
- package/src/features/utils/__mocks__/feature-base.js +11 -0
- package/src/features/utils/instrument-base.js +11 -14
- package/src/features/utils/nr1-debugger.js +22 -0
- package/src/loaders/agent.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.257.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.256.1...v1.257.0) (2024-04-18)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Decorate errors with hasReplay individually ([#983](https://github.com/newrelic/newrelic-browser-agent/issues/983)) ([b6a7a3e](https://github.com/newrelic/newrelic-browser-agent/commit/b6a7a3ebcf2a69b9cbe9888208bb62330918cdf7))
|
|
12
|
+
* Session Replay preload optimizations ([#982](https://github.com/newrelic/newrelic-browser-agent/issues/982)) ([fa20693](https://github.com/newrelic/newrelic-browser-agent/commit/fa20693d746bed2fa0b8ff972e4b9bee4bbe6956))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* Agent class type declarations ([#987](https://github.com/newrelic/newrelic-browser-agent/issues/987)) ([b682c88](https://github.com/newrelic/newrelic-browser-agent/commit/b682c880bfb149b61f6c00bf821459ea55a37ae8))
|
|
18
|
+
* JSEerrors harvest hasReplay decoration ([#986](https://github.com/newrelic/newrelic-browser-agent/issues/986)) ([6dd09c5](https://github.com/newrelic/newrelic-browser-agent/commit/6dd09c505af87b3a1b08330362eca46951ea22ed))
|
|
19
|
+
* Session replay preload without autoStart ([#985](https://github.com/newrelic/newrelic-browser-agent/issues/985)) ([f50351a](https://github.com/newrelic/newrelic-browser-agent/commit/f50351acb08b65b03e7f4b5530a001a80fc04ece))
|
|
20
|
+
* Soft navigations memory leak on harvest ([#979](https://github.com/newrelic/newrelic-browser-agent/issues/979)) ([53bb120](https://github.com/newrelic/newrelic-browser-agent/commit/53bb1209cb66fe1a52385b2863e35a93fb29afae))
|
|
21
|
+
|
|
6
22
|
## [1.256.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.256.0...v1.256.1) (2024-04-15)
|
|
7
23
|
|
|
8
24
|
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.
|
|
15
|
+
const VERSION = exports.VERSION = "1.257.0";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.
|
|
15
|
+
const VERSION = exports.VERSION = "1.257.0";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -137,7 +137,7 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
137
137
|
let url = "".concat(protocol, "://").concat(perceviedBeacon).concat(endpointURLPart, "/1/").concat(info.licenseKey);
|
|
138
138
|
if (customUrl) url = customUrl;
|
|
139
139
|
if (raw) url = "".concat(protocol, "://").concat(perceviedBeacon, "/").concat(endpoint);
|
|
140
|
-
const baseParams = !raw && includeBaseParams ? this.baseQueryString(qs) : '';
|
|
140
|
+
const baseParams = !raw && includeBaseParams ? this.baseQueryString(qs, endpoint) : '';
|
|
141
141
|
let payloadParams = (0, _encode.obj)(qs, agentRuntime.maxBytes);
|
|
142
142
|
if (!submitMethod) {
|
|
143
143
|
submitMethod = submitData.getSubmitMethod({
|
|
@@ -207,17 +207,19 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
// The stuff that gets sent every time.
|
|
210
|
-
baseQueryString(qs) {
|
|
210
|
+
baseQueryString(qs, endpoint) {
|
|
211
211
|
const runtime = (0, _config.getRuntime)(this.sharedContext.agentIdentifier);
|
|
212
212
|
const info = (0, _config.getInfo)(this.sharedContext.agentIdentifier);
|
|
213
213
|
const location = (0, _cleanUrl.cleanURL)((0, _location.getLocation)());
|
|
214
214
|
const ref = this.obfuscator.shouldObfuscate() ? this.obfuscator.obfuscateString(location) : location;
|
|
215
|
-
|
|
215
|
+
const hr = runtime?.session?.state.sessionReplayMode === 1 && endpoint !== 'jserrors';
|
|
216
|
+
const qps = ['a=' + info.applicationID, (0, _encode.param)('sa', info.sa ? '' + info.sa : ''), (0, _encode.param)('v', _env.VERSION), transactionNameParam(info), (0, _encode.param)('ct', runtime.customTransaction), '&rst=' + (0, _now.now)(), '&ck=0',
|
|
216
217
|
// ck param DEPRECATED - still expected by backend
|
|
217
218
|
'&s=' + (runtime.session?.state.value || '0'),
|
|
218
219
|
// the 0 id encaps all untrackable and default traffic
|
|
219
|
-
(0, _encode.param)('ref', ref), (0, _encode.param)('ptid', runtime.ptid ? '' + runtime.ptid : '')
|
|
220
|
-
|
|
220
|
+
(0, _encode.param)('ref', ref), (0, _encode.param)('ptid', runtime.ptid ? '' + runtime.ptid : '')];
|
|
221
|
+
if (hr) qps.push((0, _encode.param)('hr', '1', qs));
|
|
222
|
+
return qps.join('');
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
/**
|
|
@@ -42,13 +42,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
42
42
|
this.bufferedErrorsUnderSpa = {};
|
|
43
43
|
this.currentBody = undefined;
|
|
44
44
|
this.errorOnPage = false;
|
|
45
|
-
this.replayAborted = false;
|
|
46
45
|
|
|
47
46
|
// this will need to change to match whatever ee we use in the instrument
|
|
48
47
|
this.ee.on('interactionDone', (interaction, wasSaved) => this.onInteractionDone(interaction, wasSaved));
|
|
49
|
-
this.ee.on('REPLAY_ABORTED', () => {
|
|
50
|
-
this.replayAborted = true;
|
|
51
|
-
});
|
|
52
48
|
(0, _registerHandler.registerHandler)('err', function () {
|
|
53
49
|
return _this.storeError(...arguments);
|
|
54
50
|
}, this.featureName, this.ee);
|
|
@@ -94,11 +90,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
94
90
|
payload.qs.ri = releaseIds;
|
|
95
91
|
}
|
|
96
92
|
if (body && body.err && body.err.length) {
|
|
97
|
-
|
|
98
|
-
body.err.forEach(e => {
|
|
99
|
-
delete e.params?.hasReplay;
|
|
100
|
-
});
|
|
101
|
-
}
|
|
93
|
+
this.#runCrossFeatureChecks(body.err);
|
|
102
94
|
if (!this.errorOnPage) {
|
|
103
95
|
payload.qs.pve = '1';
|
|
104
96
|
this.errorOnPage = true;
|
|
@@ -172,14 +164,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
172
164
|
// Notice if filterOutput isn't false|undefined OR our specified object, this func would've returned already (so it's unnecessary to req-check group).
|
|
173
165
|
// Do not modify the name ('errorGroup') of params without DEM approval!
|
|
174
166
|
if (filterOutput?.group) params.errorGroup = filterOutput.group;
|
|
175
|
-
|
|
167
|
+
if (hasReplay) params.hasReplay = hasReplay;
|
|
176
168
|
/**
|
|
177
169
|
* The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
|
|
178
170
|
* stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
|
|
179
171
|
* the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
|
|
180
172
|
* bucketing and ultimately resulting in the loss of data in NR1.
|
|
181
173
|
*/
|
|
182
|
-
var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString));
|
|
174
|
+
var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0));
|
|
183
175
|
if (!this.stackReported[bucketHash]) {
|
|
184
176
|
this.stackReported[bucketHash] = true;
|
|
185
177
|
params.stack_trace = (0, _formatStackTrace.truncateSize)(stackInfo.stackString);
|
|
@@ -198,7 +190,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
198
190
|
params.pageview = 1;
|
|
199
191
|
this.pageviewReported[bucketHash] = true;
|
|
200
192
|
}
|
|
201
|
-
if (hasReplay && !this.replayAborted) params.hasReplay = hasReplay;
|
|
202
193
|
params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
|
|
203
194
|
params.timestamp = this.observedAt[bucketHash];
|
|
204
195
|
var type = internal ? 'ierr' : 'err';
|
|
@@ -293,5 +284,27 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
293
284
|
);
|
|
294
285
|
delete this.bufferedErrorsUnderSpa[interactionId]; // wipe the list of jserrors so they aren't duplicated by another call to the same id
|
|
295
286
|
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Dispatches a cross-feature communication event to allow other
|
|
290
|
+
* features to provide flags and data that can be used to mutation
|
|
291
|
+
* to the payload and to allow features to know about a feature
|
|
292
|
+
* harvest happening.
|
|
293
|
+
* @param {any[]} errors Array of errors from the payload body
|
|
294
|
+
*/
|
|
295
|
+
#runCrossFeatureChecks(errors) {
|
|
296
|
+
const errorHashes = errors.map(error => error.params.stackHash);
|
|
297
|
+
const crossFeatureData = {
|
|
298
|
+
errorHashes
|
|
299
|
+
};
|
|
300
|
+
this.ee.emit("cfc.".concat(this.featureName), [crossFeatureData]);
|
|
301
|
+
let hasReplayFlag = errors.find(err => err.params.hasReplay);
|
|
302
|
+
if (hasReplayFlag && !crossFeatureData.hasReplay) {
|
|
303
|
+
// Some errors have `hasReplay` and a replay is not being recorded
|
|
304
|
+
errors.forEach(error => {
|
|
305
|
+
delete error.params.hasReplay;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
296
309
|
}
|
|
297
310
|
exports.Aggregate = Aggregate;
|
|
@@ -57,9 +57,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
57
57
|
/** set at BCS response, stored in runtime */
|
|
58
58
|
this.timeKeeper = undefined;
|
|
59
59
|
this.recorder = args?.recorder;
|
|
60
|
-
this.preloaded = !!this.recorder;
|
|
61
60
|
this.errorNoticed = args?.errorNoticed || false;
|
|
62
61
|
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
62
|
+
this.ee.on("cfc.".concat(_features.FEATURE_NAMES.jserrors), crossFeatureData => {
|
|
63
|
+
crossFeatureData.hasReplay = !!(this.scheduler?.started && this.recorder && this.mode === _constants3.MODE.FULL && !this.blocked && this.entitled);
|
|
64
|
+
});
|
|
63
65
|
|
|
64
66
|
// The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
|
|
65
67
|
this.ee.on(_constants3.SESSION_EVENTS.RESET, () => {
|
|
@@ -106,6 +108,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
106
108
|
(0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.PAUSE, () => {
|
|
107
109
|
this.forceStop(this.mode !== _constants3.MODE.ERROR);
|
|
108
110
|
}, this.featureName, this.ee);
|
|
111
|
+
(0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
|
|
112
|
+
this.handleError(e);
|
|
113
|
+
}, this.featureName, this.ee);
|
|
109
114
|
const {
|
|
110
115
|
error_sampling_rate,
|
|
111
116
|
sampling_rate,
|
|
@@ -154,15 +159,17 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
switchToFull() {
|
|
162
|
+
if (!this.entitled || this.blocked) return;
|
|
157
163
|
this.mode = _constants3.MODE.FULL;
|
|
158
164
|
// if the error was noticed AFTER the recorder was already imported....
|
|
159
165
|
if (this.recorder && this.initialized) {
|
|
160
|
-
this.recorder.
|
|
161
|
-
this.recorder.startRecording();
|
|
166
|
+
if (!this.recorder.recording) this.recorder.startRecording();
|
|
162
167
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
163
168
|
this.syncWithSessionManager({
|
|
164
169
|
sessionReplayMode: this.mode
|
|
165
170
|
});
|
|
171
|
+
} else {
|
|
172
|
+
this.initializeRecording(false, true, true);
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
|
|
@@ -223,7 +230,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
223
230
|
|
|
224
231
|
// If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
|
|
225
232
|
if (this.mode === _constants3.MODE.ERROR && this.errorNoticed) this.mode = _constants3.MODE.FULL;
|
|
226
|
-
if (!this.preloaded) this.ee.on('err', e => this.handleError(e));
|
|
227
233
|
|
|
228
234
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
229
235
|
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
@@ -317,7 +323,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
317
323
|
return [payload];
|
|
318
324
|
}
|
|
319
325
|
getCorrectedTimestamp(node) {
|
|
320
|
-
if (!node
|
|
326
|
+
if (!node?.timestamp) return;
|
|
321
327
|
if (node.__newrelic) return node.timestamp;
|
|
322
328
|
return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
|
|
323
329
|
}
|
|
@@ -10,7 +10,8 @@ const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.sessionRepla
|
|
|
10
10
|
const SR_EVENT_EMITTER_TYPES = exports.SR_EVENT_EMITTER_TYPES = {
|
|
11
11
|
RECORD: 'recordReplay',
|
|
12
12
|
PAUSE: 'pauseReplay',
|
|
13
|
-
REPLAY_RUNNING: 'replayRunning'
|
|
13
|
+
REPLAY_RUNNING: 'replayRunning',
|
|
14
|
+
ERROR_DURING_REPLAY: 'errorDuringReplay'
|
|
14
15
|
};
|
|
15
16
|
const AVG_COMPRESSION = exports.AVG_COMPRESSION = 0.12;
|
|
16
17
|
const RRWEB_EVENT_TYPES = exports.RRWEB_EVENT_TYPES = {
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.Instrument = void 0;
|
|
7
|
+
var _handle = require("../../../common/event-emitter/handle");
|
|
7
8
|
var _constants = require("../../../common/session/constants");
|
|
8
9
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
9
10
|
var _constants2 = require("../constants");
|
|
@@ -25,19 +26,28 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
25
26
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
26
27
|
super(agentIdentifier, aggregator, _constants2.FEATURE_NAME, auto);
|
|
27
28
|
let session;
|
|
29
|
+
this.replayRunning = false;
|
|
28
30
|
try {
|
|
29
31
|
session = JSON.parse(localStorage.getItem("".concat(_constants.PREFIX, "_").concat(_constants.DEFAULT_KEY)));
|
|
30
32
|
} catch (err) {}
|
|
31
33
|
if (this.#canPreloadRecorder(session)) {
|
|
32
|
-
/** If this is preloaded, set up a buffer, if not, later when sampling we will set up a .on for live events */
|
|
33
|
-
this.ee.on('err', e => {
|
|
34
|
-
this.errorNoticed = true;
|
|
35
|
-
if (this.featAggregate) this.featAggregate.handleError();
|
|
36
|
-
});
|
|
37
34
|
this.#startRecording(session?.sessionReplayMode);
|
|
38
35
|
} else {
|
|
39
36
|
this.importAggregator();
|
|
40
37
|
}
|
|
38
|
+
|
|
39
|
+
/** If the recorder is running, we can pass error events on to the agg to help it switch to full mode later */
|
|
40
|
+
this.ee.on('err', e => {
|
|
41
|
+
if (this.replayRunning) {
|
|
42
|
+
this.errorNoticed = true;
|
|
43
|
+
(0, _handle.handle)(_constants2.SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/** Emitted by the recorder when it starts capturing data, used to determine if we should pass errors on to the agg */
|
|
48
|
+
this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
|
|
49
|
+
this.replayRunning = isRunning;
|
|
50
|
+
});
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
// At this point wherein session state exists already but we haven't init SessionEntity aka verify timers.
|
|
@@ -103,11 +103,10 @@ class Recorder {
|
|
|
103
103
|
collectFonts: collect_fonts,
|
|
104
104
|
checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
|
|
105
105
|
});
|
|
106
|
-
this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode]);
|
|
107
106
|
this.stopRecording = () => {
|
|
108
107
|
this.recording = false;
|
|
109
108
|
this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode]);
|
|
110
|
-
stop();
|
|
109
|
+
stop?.();
|
|
111
110
|
};
|
|
112
111
|
}
|
|
113
112
|
|
|
@@ -150,6 +149,10 @@ class Recorder {
|
|
|
150
149
|
if (!event) return;
|
|
151
150
|
if (!this.parent.scheduler && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
|
|
152
151
|
if (this.parent.blocked) return;
|
|
152
|
+
if (!this.notified) {
|
|
153
|
+
this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode]);
|
|
154
|
+
this.notified = true;
|
|
155
|
+
}
|
|
153
156
|
if (this.parent.timeKeeper?.ready && !event.__newrelic) {
|
|
154
157
|
event.__newrelic = (0, _utils.buildNRMetaNode)(event.timestamp, this.parent.timeKeeper);
|
|
155
158
|
event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
|
|
@@ -180,7 +183,7 @@ class Recorder {
|
|
|
180
183
|
|
|
181
184
|
// We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
|
|
182
185
|
// it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
|
|
183
|
-
if (payloadSize > _constants.IDEAL_PAYLOAD_SIZE && this.parent.mode
|
|
186
|
+
if ((event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot && this.currentBufferTarget.hasMeta || payloadSize > _constants.IDEAL_PAYLOAD_SIZE) && this.parent.mode === _constants2.MODE.FULL) {
|
|
184
187
|
// if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
|
|
185
188
|
if (this.parent.scheduler) {
|
|
186
189
|
this.parent.scheduler.runHarvest();
|
|
@@ -5,16 +5,17 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.buildNRMetaNode = buildNRMetaNode;
|
|
7
7
|
exports.canImportReplayAgg = canImportReplayAgg;
|
|
8
|
-
exports.enableSessionTracking =
|
|
8
|
+
exports.enableSessionTracking = enableSessionTracking;
|
|
9
9
|
exports.isPreloadAllowed = isPreloadAllowed;
|
|
10
10
|
var _config = require("../../../common/config/config");
|
|
11
11
|
var _runtime = require("../../../common/constants/runtime");
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
function enableSessionTracking(agentId) {
|
|
13
|
+
return _runtime.isBrowserScope && (0, _config.getConfigurationValue)(agentId, 'privacy.cookies_enabled') === true;
|
|
14
|
+
}
|
|
14
15
|
function hasReplayPrerequisite(agentId) {
|
|
15
|
-
return _config.originals.MO &&
|
|
16
|
+
return !!_config.originals.MO &&
|
|
16
17
|
// Session Replay cannot work without Mutation Observer
|
|
17
|
-
enableSessionTracking &&
|
|
18
|
+
enableSessionTracking(agentId) &&
|
|
18
19
|
// requires session tracking to be running (hence "session" replay...)
|
|
19
20
|
(0, _config.getConfigurationValue)(agentId, 'session_trace.enabled') === true; // Session Replay as of now is tightly coupled with Session Trace in the UI
|
|
20
21
|
}
|
|
@@ -88,7 +88,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
88
88
|
if (!firstIxnStartTime) firstIxnStartTime = Math.floor(interaction.start);
|
|
89
89
|
}
|
|
90
90
|
const payload = "bel.7;".concat(serializedIxnList.join(';'));
|
|
91
|
-
if (options.retry) this.interactionsAwaitingRetry
|
|
91
|
+
if (options.retry) this.interactionsAwaitingRetry = this.interactionsToHarvest;
|
|
92
92
|
this.interactionsToHarvest = [];
|
|
93
93
|
return {
|
|
94
94
|
body: {
|
|
@@ -99,8 +99,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
99
99
|
onHarvestFinished(result) {
|
|
100
100
|
if (result.sent && result.retry && this.interactionsAwaitingRetry.length > 0) {
|
|
101
101
|
this.interactionsToHarvest = [...this.interactionsAwaitingRetry, ...this.interactionsToHarvest];
|
|
102
|
-
this.interactionsAwaitingRetry = [];
|
|
103
102
|
}
|
|
103
|
+
this.interactionsAwaitingRetry = [];
|
|
104
104
|
}
|
|
105
105
|
startUIInteraction(eventName, startedAt, sourceElem) {
|
|
106
106
|
// this is throttled by instrumentation so that it isn't excessively called
|
|
@@ -12,6 +12,7 @@ var _console = require("../../common/util/console");
|
|
|
12
12
|
var _features = require("../../loaders/features/features");
|
|
13
13
|
var _config = require("../../common/config/config");
|
|
14
14
|
var _utils = require("../session_replay/shared/utils");
|
|
15
|
+
var _invoke = require("../../common/util/invoke");
|
|
15
16
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
16
17
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /**
|
|
17
18
|
* @file Defines `InstrumentBase` to be used as the super of the Instrument classes implemented by each feature.
|
|
@@ -55,7 +56,15 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
55
56
|
/** used in conjunction with newrelic.start() to defer harvesting in features */
|
|
56
57
|
if ((0, _config.getConfigurationValue)(this.agentIdentifier, "".concat(this.featureName, ".autoStart")) === false) this.auto = false;
|
|
57
58
|
/** if the feature requires opt-in (!auto-start), it will get registered once the api has been called */
|
|
58
|
-
if (this.auto) (0, _drain.registerDrain)(agentIdentifier, featureName);
|
|
59
|
+
if (this.auto) (0, _drain.registerDrain)(agentIdentifier, featureName);else {
|
|
60
|
+
this.ee.on("".concat(this.featureName, "-opt-in"), (0, _invoke.single)(() => {
|
|
61
|
+
// register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
|
|
62
|
+
// called by the api in that cycle
|
|
63
|
+
(0, _drain.registerDrain)(this.agentIdentifier, this.featureName);
|
|
64
|
+
this.auto = true;
|
|
65
|
+
this.importAggregator();
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
/**
|
|
@@ -66,19 +75,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
66
75
|
*/
|
|
67
76
|
importAggregator() {
|
|
68
77
|
let argsObjFromInstrument = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
69
|
-
if (this.featAggregate) return;
|
|
70
|
-
if (!this.auto) {
|
|
71
|
-
// this feature requires an opt in...
|
|
72
|
-
// wait for API to be called
|
|
73
|
-
this.ee.on("".concat(this.featureName, "-opt-in"), () => {
|
|
74
|
-
// register the feature to drain only once the API has been called, it will drain when importAggregator finishes for all the features
|
|
75
|
-
// called by the api in that cycle
|
|
76
|
-
(0, _drain.registerDrain)(this.agentIdentifier, this.featureName);
|
|
77
|
-
this.auto = true;
|
|
78
|
-
this.importAggregator();
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
78
|
+
if (this.featAggregate || !this.auto) return;
|
|
82
79
|
let loadedSuccessfully;
|
|
83
80
|
this.onAggregateImported = new Promise(resolve => {
|
|
84
81
|
loadedSuccessfully = resolve;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.debugNR1 = debugNR1;
|
|
7
|
+
var _nreum = require("../../common/window/nreum");
|
|
8
|
+
const debugId = 1;
|
|
9
|
+
const newrelic = (0, _nreum.gosCDN)();
|
|
10
|
+
function debugNR1(agentIdentifier, location, event) {
|
|
11
|
+
let otherprops = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
12
|
+
let debugName = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'SR';
|
|
13
|
+
const api = agentIdentifier ? newrelic.initializedAgents[agentIdentifier].api.addPageAction : newrelic.addPageAction;
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
const locURL = new URL(window.location);
|
|
17
|
+
url = locURL.pathname;
|
|
18
|
+
} catch (err) {}
|
|
19
|
+
api(debugName, {
|
|
20
|
+
debugId,
|
|
21
|
+
url,
|
|
22
|
+
location,
|
|
23
|
+
event,
|
|
24
|
+
now: performance.now(),
|
|
25
|
+
...otherprops
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -30,6 +30,10 @@ var _runtime = require("../common/constants/runtime");
|
|
|
30
30
|
* sensitive to network load, this may result in smaller builds with slightly lower performance impact.
|
|
31
31
|
*/
|
|
32
32
|
class Agent extends _agentBase.AgentBase {
|
|
33
|
+
/**
|
|
34
|
+
* @param {Object} options Options to initialize agent with
|
|
35
|
+
* @param {string} [agentIdentifier] Optional identifier of agent
|
|
36
|
+
*/
|
|
33
37
|
constructor(options, agentIdentifier) {
|
|
34
38
|
super(agentIdentifier);
|
|
35
39
|
if (!_runtime.globalScope) {
|
|
@@ -129,7 +129,7 @@ export class Harvest extends SharedContext {
|
|
|
129
129
|
let url = "".concat(protocol, "://").concat(perceviedBeacon).concat(endpointURLPart, "/1/").concat(info.licenseKey);
|
|
130
130
|
if (customUrl) url = customUrl;
|
|
131
131
|
if (raw) url = "".concat(protocol, "://").concat(perceviedBeacon, "/").concat(endpoint);
|
|
132
|
-
const baseParams = !raw && includeBaseParams ? this.baseQueryString(qs) : '';
|
|
132
|
+
const baseParams = !raw && includeBaseParams ? this.baseQueryString(qs, endpoint) : '';
|
|
133
133
|
let payloadParams = encodeObj(qs, agentRuntime.maxBytes);
|
|
134
134
|
if (!submitMethod) {
|
|
135
135
|
submitMethod = submitData.getSubmitMethod({
|
|
@@ -199,17 +199,19 @@ export class Harvest extends SharedContext {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// The stuff that gets sent every time.
|
|
202
|
-
baseQueryString(qs) {
|
|
202
|
+
baseQueryString(qs, endpoint) {
|
|
203
203
|
const runtime = getRuntime(this.sharedContext.agentIdentifier);
|
|
204
204
|
const info = getInfo(this.sharedContext.agentIdentifier);
|
|
205
205
|
const location = cleanURL(getLocation());
|
|
206
206
|
const ref = this.obfuscator.shouldObfuscate() ? this.obfuscator.obfuscateString(location) : location;
|
|
207
|
-
|
|
207
|
+
const hr = runtime?.session?.state.sessionReplayMode === 1 && endpoint !== 'jserrors';
|
|
208
|
+
const qps = ['a=' + info.applicationID, encodeParam('sa', info.sa ? '' + info.sa : ''), encodeParam('v', VERSION), transactionNameParam(info), encodeParam('ct', runtime.customTransaction), '&rst=' + now(), '&ck=0',
|
|
208
209
|
// ck param DEPRECATED - still expected by backend
|
|
209
210
|
'&s=' + (runtime.session?.state.value || '0'),
|
|
210
211
|
// the 0 id encaps all untrackable and default traffic
|
|
211
|
-
encodeParam('ref', ref), encodeParam('ptid', runtime.ptid ? '' + runtime.ptid : '')
|
|
212
|
-
|
|
212
|
+
encodeParam('ref', ref), encodeParam('ptid', runtime.ptid ? '' + runtime.ptid : '')];
|
|
213
|
+
if (hr) qps.push(encodeParam('hr', '1', qs));
|
|
214
|
+
return qps.join('');
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
/**
|
|
@@ -37,13 +37,9 @@ export class Aggregate extends AggregateBase {
|
|
|
37
37
|
this.bufferedErrorsUnderSpa = {};
|
|
38
38
|
this.currentBody = undefined;
|
|
39
39
|
this.errorOnPage = false;
|
|
40
|
-
this.replayAborted = false;
|
|
41
40
|
|
|
42
41
|
// this will need to change to match whatever ee we use in the instrument
|
|
43
42
|
this.ee.on('interactionDone', (interaction, wasSaved) => this.onInteractionDone(interaction, wasSaved));
|
|
44
|
-
this.ee.on('REPLAY_ABORTED', () => {
|
|
45
|
-
this.replayAborted = true;
|
|
46
|
-
});
|
|
47
43
|
register('err', function () {
|
|
48
44
|
return _this.storeError(...arguments);
|
|
49
45
|
}, this.featureName, this.ee);
|
|
@@ -89,11 +85,7 @@ export class Aggregate extends AggregateBase {
|
|
|
89
85
|
payload.qs.ri = releaseIds;
|
|
90
86
|
}
|
|
91
87
|
if (body && body.err && body.err.length) {
|
|
92
|
-
|
|
93
|
-
body.err.forEach(e => {
|
|
94
|
-
delete e.params?.hasReplay;
|
|
95
|
-
});
|
|
96
|
-
}
|
|
88
|
+
this.#runCrossFeatureChecks(body.err);
|
|
97
89
|
if (!this.errorOnPage) {
|
|
98
90
|
payload.qs.pve = '1';
|
|
99
91
|
this.errorOnPage = true;
|
|
@@ -167,14 +159,14 @@ export class Aggregate extends AggregateBase {
|
|
|
167
159
|
// Notice if filterOutput isn't false|undefined OR our specified object, this func would've returned already (so it's unnecessary to req-check group).
|
|
168
160
|
// Do not modify the name ('errorGroup') of params without DEM approval!
|
|
169
161
|
if (filterOutput?.group) params.errorGroup = filterOutput.group;
|
|
170
|
-
|
|
162
|
+
if (hasReplay) params.hasReplay = hasReplay;
|
|
171
163
|
/**
|
|
172
164
|
* The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
|
|
173
165
|
* stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
|
|
174
166
|
* the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
|
|
175
167
|
* bucketing and ultimately resulting in the loss of data in NR1.
|
|
176
168
|
*/
|
|
177
|
-
var bucketHash = stringHashCode("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString));
|
|
169
|
+
var bucketHash = stringHashCode("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0));
|
|
178
170
|
if (!this.stackReported[bucketHash]) {
|
|
179
171
|
this.stackReported[bucketHash] = true;
|
|
180
172
|
params.stack_trace = truncateSize(stackInfo.stackString);
|
|
@@ -193,7 +185,6 @@ export class Aggregate extends AggregateBase {
|
|
|
193
185
|
params.pageview = 1;
|
|
194
186
|
this.pageviewReported[bucketHash] = true;
|
|
195
187
|
}
|
|
196
|
-
if (hasReplay && !this.replayAborted) params.hasReplay = hasReplay;
|
|
197
188
|
params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
|
|
198
189
|
params.timestamp = this.observedAt[bucketHash];
|
|
199
190
|
var type = internal ? 'ierr' : 'err';
|
|
@@ -288,4 +279,26 @@ export class Aggregate extends AggregateBase {
|
|
|
288
279
|
);
|
|
289
280
|
delete this.bufferedErrorsUnderSpa[interactionId]; // wipe the list of jserrors so they aren't duplicated by another call to the same id
|
|
290
281
|
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Dispatches a cross-feature communication event to allow other
|
|
285
|
+
* features to provide flags and data that can be used to mutation
|
|
286
|
+
* to the payload and to allow features to know about a feature
|
|
287
|
+
* harvest happening.
|
|
288
|
+
* @param {any[]} errors Array of errors from the payload body
|
|
289
|
+
*/
|
|
290
|
+
#runCrossFeatureChecks(errors) {
|
|
291
|
+
const errorHashes = errors.map(error => error.params.stackHash);
|
|
292
|
+
const crossFeatureData = {
|
|
293
|
+
errorHashes
|
|
294
|
+
};
|
|
295
|
+
this.ee.emit("cfc.".concat(this.featureName), [crossFeatureData]);
|
|
296
|
+
let hasReplayFlag = errors.find(err => err.params.hasReplay);
|
|
297
|
+
if (hasReplayFlag && !crossFeatureData.hasReplay) {
|
|
298
|
+
// Some errors have `hasReplay` and a replay is not being recorded
|
|
299
|
+
errors.forEach(error => {
|
|
300
|
+
delete error.params.hasReplay;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
291
304
|
}
|
|
@@ -52,9 +52,11 @@ export class Aggregate extends AggregateBase {
|
|
|
52
52
|
/** set at BCS response, stored in runtime */
|
|
53
53
|
this.timeKeeper = undefined;
|
|
54
54
|
this.recorder = args?.recorder;
|
|
55
|
-
this.preloaded = !!this.recorder;
|
|
56
55
|
this.errorNoticed = args?.errorNoticed || false;
|
|
57
56
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
57
|
+
this.ee.on("cfc.".concat(FEATURE_NAMES.jserrors), crossFeatureData => {
|
|
58
|
+
crossFeatureData.hasReplay = !!(this.scheduler?.started && this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled);
|
|
59
|
+
});
|
|
58
60
|
|
|
59
61
|
// The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
|
|
60
62
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
@@ -101,6 +103,9 @@ export class Aggregate extends AggregateBase {
|
|
|
101
103
|
registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
|
|
102
104
|
this.forceStop(this.mode !== MODE.ERROR);
|
|
103
105
|
}, this.featureName, this.ee);
|
|
106
|
+
registerHandler(SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
|
|
107
|
+
this.handleError(e);
|
|
108
|
+
}, this.featureName, this.ee);
|
|
104
109
|
const {
|
|
105
110
|
error_sampling_rate,
|
|
106
111
|
sampling_rate,
|
|
@@ -149,15 +154,17 @@ export class Aggregate extends AggregateBase {
|
|
|
149
154
|
}
|
|
150
155
|
}
|
|
151
156
|
switchToFull() {
|
|
157
|
+
if (!this.entitled || this.blocked) return;
|
|
152
158
|
this.mode = MODE.FULL;
|
|
153
159
|
// if the error was noticed AFTER the recorder was already imported....
|
|
154
160
|
if (this.recorder && this.initialized) {
|
|
155
|
-
this.recorder.
|
|
156
|
-
this.recorder.startRecording();
|
|
161
|
+
if (!this.recorder.recording) this.recorder.startRecording();
|
|
157
162
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
158
163
|
this.syncWithSessionManager({
|
|
159
164
|
sessionReplayMode: this.mode
|
|
160
165
|
});
|
|
166
|
+
} else {
|
|
167
|
+
this.initializeRecording(false, true, true);
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
170
|
|
|
@@ -218,7 +225,6 @@ export class Aggregate extends AggregateBase {
|
|
|
218
225
|
|
|
219
226
|
// If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
|
|
220
227
|
if (this.mode === MODE.ERROR && this.errorNoticed) this.mode = MODE.FULL;
|
|
221
|
-
if (!this.preloaded) this.ee.on('err', e => this.handleError(e));
|
|
222
228
|
|
|
223
229
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
224
230
|
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
@@ -312,7 +318,7 @@ export class Aggregate extends AggregateBase {
|
|
|
312
318
|
return [payload];
|
|
313
319
|
}
|
|
314
320
|
getCorrectedTimestamp(node) {
|
|
315
|
-
if (!node
|
|
321
|
+
if (!node?.timestamp) return;
|
|
316
322
|
if (node.__newrelic) return node.timestamp;
|
|
317
323
|
return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
|
|
318
324
|
}
|
|
@@ -4,7 +4,8 @@ export const FEATURE_NAME = FEATURE_NAMES.sessionReplay;
|
|
|
4
4
|
export const SR_EVENT_EMITTER_TYPES = {
|
|
5
5
|
RECORD: 'recordReplay',
|
|
6
6
|
PAUSE: 'pauseReplay',
|
|
7
|
-
REPLAY_RUNNING: 'replayRunning'
|
|
7
|
+
REPLAY_RUNNING: 'replayRunning',
|
|
8
|
+
ERROR_DURING_REPLAY: 'errorDuringReplay'
|
|
8
9
|
};
|
|
9
10
|
export const AVG_COMPRESSION = 0.12;
|
|
10
11
|
export const RRWEB_EVENT_TYPES = {
|