@newrelic/browser-agent 1.296.0-rc.3 → 1.296.0-rc.5
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/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +10 -41
- package/dist/cjs/features/session_replay/instrument/index.js +6 -7
- package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/cjs/features/session_replay/shared/recorder.js +41 -61
- package/dist/cjs/features/session_replay/shared/utils.js +0 -13
- package/dist/cjs/features/utils/event-buffer.js +3 -2
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +10 -41
- package/dist/esm/features/session_replay/instrument/index.js +7 -8
- package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/esm/features/session_replay/shared/recorder.js +42 -62
- package/dist/esm/features/session_replay/shared/utils.js +0 -12
- package/dist/esm/features/utils/event-buffer.js +3 -2
- 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/shared/recorder-events.d.ts +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +10 -8
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts +0 -8
- package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +2 -1
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/session_replay/aggregate/index.js +8 -35
- package/src/features/session_replay/instrument/index.js +4 -4
- package/src/features/session_replay/shared/recorder-events.js +2 -2
- package/src/features/session_replay/shared/recorder.js +39 -67
- package/src/features/session_replay/shared/utils.js +0 -13
- package/src/features/utils/event-buffer.js +3 -2
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.296.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.296.0-rc.5";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the version of the agent
|
|
19
19
|
*/
|
|
20
|
-
const VERSION = exports.VERSION = "1.296.0-rc.
|
|
20
|
+
const VERSION = exports.VERSION = "1.296.0-rc.5";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Exposes the build type of the agent
|
|
@@ -16,7 +16,6 @@ var _constants2 = require("../../../common/session/constants");
|
|
|
16
16
|
var _stringify = require("../../../common/util/stringify");
|
|
17
17
|
var _stylesheetEvaluator = require("../shared/stylesheet-evaluator");
|
|
18
18
|
var _now = require("../../../common/timing/now");
|
|
19
|
-
var _utils = require("../shared/utils");
|
|
20
19
|
var _agentConstants = require("../../../common/constants/agent-constants");
|
|
21
20
|
var _cleanUrl = require("../../../common/url/clean-url");
|
|
22
21
|
var _featureGates = require("../../utils/feature-gates");
|
|
@@ -101,8 +100,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
101
100
|
}
|
|
102
101
|
return;
|
|
103
102
|
}
|
|
104
|
-
this.
|
|
105
|
-
|
|
103
|
+
this.initializeRecording(srMode).then(() => {
|
|
104
|
+
this.drain();
|
|
105
|
+
});
|
|
106
106
|
}).then(() => {
|
|
107
107
|
if (this.mode === _constants2.MODE.OFF) {
|
|
108
108
|
this.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
|
|
@@ -125,7 +125,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
125
125
|
return Boolean(this.recorder && this.mode === _constants2.MODE.FULL && !this.blocked && this.entitled);
|
|
126
126
|
}
|
|
127
127
|
handleError(e) {
|
|
128
|
-
if (this.recorder) this.recorder.
|
|
128
|
+
if (this.recorder) this.recorder.events.hasError = true;
|
|
129
129
|
// run once
|
|
130
130
|
if (this.mode === _constants2.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
|
|
131
131
|
this.switchToFull();
|
|
@@ -184,7 +184,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
184
184
|
Recorder
|
|
185
185
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require(/* webpackChunkName: "recorder" */'../shared/recorder')));
|
|
186
186
|
this.recorder = new Recorder(this);
|
|
187
|
-
this.recorder.
|
|
187
|
+
this.recorder.events.hasError = this.errorNoticed;
|
|
188
188
|
} catch (err) {
|
|
189
189
|
return this.abort(_constants.ABORT_REASONS.IMPORT);
|
|
190
190
|
}
|
|
@@ -199,10 +199,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
199
199
|
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
200
200
|
// The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
|
|
201
201
|
|
|
202
|
-
// If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
|
|
203
|
-
if (this.mode === _constants2.MODE.FULL && this.recorder?.getEvents().type === 'preloaded') {
|
|
204
|
-
this.prepUtils().then(() => this.agentRef.runtime.harvester.triggerHarvestFor(this));
|
|
205
|
-
}
|
|
206
202
|
await this.prepUtils();
|
|
207
203
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording();
|
|
208
204
|
this.syncWithSessionManager({
|
|
@@ -241,33 +237,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
241
237
|
let len = 0;
|
|
242
238
|
if (!!this.gzipper && !!this.u8) {
|
|
243
239
|
payload.body = this.gzipper(this.u8("[".concat(payload.body.map(({
|
|
244
|
-
__serialized
|
|
245
|
-
|
|
246
|
-
}) => {
|
|
247
|
-
if (e.__newrelic && __serialized) return __serialized;
|
|
248
|
-
const output = {
|
|
249
|
-
...e
|
|
250
|
-
};
|
|
251
|
-
if (!output.__newrelic) {
|
|
252
|
-
output.__newrelic = (0, _utils.buildNRMetaNode)(e.timestamp, this.timeKeeper);
|
|
253
|
-
output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(e.timestamp);
|
|
254
|
-
}
|
|
255
|
-
return (0, _stringify.stringify)(output);
|
|
256
|
-
}).join(','), "]")));
|
|
240
|
+
__serialized
|
|
241
|
+
}) => __serialized).join(','), "]")));
|
|
257
242
|
len = payload.body.length;
|
|
258
243
|
} else {
|
|
259
|
-
payload.body
|
|
260
|
-
__serialized,
|
|
261
|
-
...node
|
|
262
|
-
}) => {
|
|
263
|
-
if (node.__newrelic) return node;
|
|
264
|
-
const output = {
|
|
265
|
-
...node
|
|
266
|
-
};
|
|
267
|
-
output.__newrelic = (0, _utils.buildNRMetaNode)(node.timestamp, this.timeKeeper);
|
|
268
|
-
output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
|
|
269
|
-
return output;
|
|
270
|
-
});
|
|
244
|
+
for (let idx in payload.body) delete payload.body[idx].__serialized;
|
|
271
245
|
len = (0, _stringify.stringify)(payload.body).length;
|
|
272
246
|
}
|
|
273
247
|
if (len > _agentConstants.MAX_PAYLOAD_SIZE) {
|
|
@@ -286,11 +260,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
286
260
|
}
|
|
287
261
|
return [payloadOutput];
|
|
288
262
|
}
|
|
289
|
-
getCorrectedTimestamp(node) {
|
|
290
|
-
if (!node?.timestamp) return;
|
|
291
|
-
if (node.__newrelic) return node.timestamp;
|
|
292
|
-
return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
|
|
293
|
-
}
|
|
294
263
|
|
|
295
264
|
/**
|
|
296
265
|
* returns the timestamps for the earliest and latest nodes in the provided array, even if out of order
|
|
@@ -338,8 +307,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
338
307
|
lastEvent
|
|
339
308
|
} = this.getFirstAndLastNodes(events);
|
|
340
309
|
// from rrweb node || from when the harvest cycle started
|
|
341
|
-
const firstTimestamp =
|
|
342
|
-
const lastTimestamp =
|
|
310
|
+
const firstTimestamp = firstEvent?.timestamp || Math.floor(this.timeKeeper.correctAbsoluteTimestamp(recorderEvents.cycleTimestamp));
|
|
311
|
+
const lastTimestamp = lastEvent?.timestamp || Math.floor(this.timeKeeper.correctRelativeTimestamp(relativeNow));
|
|
343
312
|
const agentMetadata = agentRuntime.appMetadata?.agents?.[0] || {};
|
|
344
313
|
return {
|
|
345
314
|
qs: {
|
|
@@ -70,7 +70,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
70
70
|
/**
|
|
71
71
|
* This func is use for early pre-load recording prior to replay feature (agg) being loaded onto the page. It should only setup once, including if already called and in-progress.
|
|
72
72
|
*/
|
|
73
|
-
async #preloadStartRecording(
|
|
73
|
+
async #preloadStartRecording() {
|
|
74
74
|
if (this.#alreadyStarted) return;
|
|
75
75
|
this.#alreadyStarted = true;
|
|
76
76
|
try {
|
|
@@ -81,12 +81,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
81
81
|
// If startReplay() has been used by this point, we must record in full mode regardless of session preload:
|
|
82
82
|
// Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
|
|
83
83
|
this.recorder ??= new Recorder({
|
|
84
|
+
...this,
|
|
84
85
|
mode: this.#mode,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
agentRef: this.#agentRef
|
|
89
|
-
});
|
|
86
|
+
agentRef: this.#agentRef,
|
|
87
|
+
timeKeeper: this.#agentRef.runtime.timeKeeper
|
|
88
|
+
}); // if TK exists due to deferred state, pass it
|
|
90
89
|
this.recorder.startRecording();
|
|
91
90
|
this.abortHandler = this.recorder.stopRecording;
|
|
92
91
|
} catch (err) {
|
|
@@ -108,7 +107,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
108
107
|
} else {
|
|
109
108
|
// pre-load
|
|
110
109
|
this.#mode = _constants.MODE.FULL;
|
|
111
|
-
this.#preloadStartRecording(
|
|
110
|
+
this.#preloadStartRecording();
|
|
112
111
|
// There's a race here wherein either:
|
|
113
112
|
// a. Recorder has not been initialized, and we've set the enforced mode, so we're good, or;
|
|
114
113
|
// b. Record has been initialized, possibly with the "wrong" mode, so we have to correct that + restart.
|
|
@@ -30,8 +30,8 @@ class RecorderEvents {
|
|
|
30
30
|
/** Payload metadata -- Denotes whether all stylesheet elements were able to be inlined */
|
|
31
31
|
this.inlinedAllStylesheets = shouldInlineStylesheets;
|
|
32
32
|
}
|
|
33
|
-
add(event) {
|
|
34
|
-
this.#events.add(event);
|
|
33
|
+
add(event, evaluatedSize) {
|
|
34
|
+
this.#events.add(event, evaluatedSize);
|
|
35
35
|
}
|
|
36
36
|
get events() {
|
|
37
37
|
return this.#events.get();
|
|
@@ -15,21 +15,16 @@ var _constants3 = require("../../metrics/constants");
|
|
|
15
15
|
var _features = require("../../../loaders/features/features");
|
|
16
16
|
var _utils = require("./utils");
|
|
17
17
|
var _agentConstants = require("../../../common/constants/agent-constants");
|
|
18
|
-
var _aggregateBase = require("../../utils/aggregate-base");
|
|
19
18
|
var _console = require("../../../common/util/console");
|
|
20
19
|
var _invoke = require("../../../common/util/invoke");
|
|
20
|
+
var _registerHandler = require("../../../common/event-emitter/register-handler");
|
|
21
21
|
/**
|
|
22
22
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
23
23
|
* SPDX-License-Identifier: Apache-2.0
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
const RRWEB_DATA_CHANNEL = 'rrweb-data';
|
|
26
27
|
class Recorder {
|
|
27
|
-
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
28
|
-
#events;
|
|
29
|
-
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
30
|
-
#backloggedEvents;
|
|
31
|
-
/** array of recorder events -- Will be filled only if forced harvest was triggered and harvester does not exist */
|
|
32
|
-
#preloaded;
|
|
33
28
|
/** flag that if true, blocks events from being "stored". Only set to true when a full snapshot has incomplete nodes (only stylesheets ATM) */
|
|
34
29
|
#fixing = false;
|
|
35
30
|
#warnCSSOnce = (0, _invoke.single)(() => (0, _console.warn)(47)); // notifies user of potential replayer issue if fix_stylesheets is off
|
|
@@ -39,13 +34,12 @@ class Recorder {
|
|
|
39
34
|
this.parent = parent;
|
|
40
35
|
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
41
36
|
this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/** Only set to true once a snapshot node has been processed. Used to block preload harvests from sending before we know we have a snapshot */
|
|
37
|
+
|
|
38
|
+
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
39
|
+
this.events = new _recorderEvents.RecorderEvents(this.shouldFix);
|
|
40
|
+
/** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
|
|
41
|
+
this.backloggedEvents = new _recorderEvents.RecorderEvents(this.shouldFix);
|
|
42
|
+
/** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
|
|
49
43
|
this.hasSeenSnapshot = false;
|
|
50
44
|
/** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
|
|
51
45
|
this.lastMeta = false;
|
|
@@ -53,32 +47,27 @@ class Recorder {
|
|
|
53
47
|
this.stopRecording = () => {
|
|
54
48
|
this.parent.agentRef.runtime.isRecording = false;
|
|
55
49
|
};
|
|
50
|
+
(0, _registerHandler.registerHandler)(RRWEB_DATA_CHANNEL, (event, isCheckout) => {
|
|
51
|
+
this.audit(event, isCheckout);
|
|
52
|
+
}, this.parent.featureName, this.parent.ee);
|
|
56
53
|
}
|
|
57
54
|
getEvents() {
|
|
58
|
-
if (this.#preloaded[0]?.events.length) {
|
|
59
|
-
return {
|
|
60
|
-
...this.#preloaded[0],
|
|
61
|
-
events: this.#preloaded[0].events,
|
|
62
|
-
payloadBytesEstimation: this.#preloaded[0].payloadBytesEstimation,
|
|
63
|
-
type: 'preloaded'
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
55
|
return {
|
|
67
|
-
events: [...this
|
|
56
|
+
events: [...this.backloggedEvents.events, ...this.events.events].filter(x => x),
|
|
68
57
|
type: 'standard',
|
|
69
|
-
cycleTimestamp: Math.min(this
|
|
70
|
-
payloadBytesEstimation: this
|
|
71
|
-
hasError: this
|
|
72
|
-
hasMeta: this
|
|
73
|
-
hasSnapshot: this
|
|
74
|
-
inlinedAllStylesheets: !!this
|
|
58
|
+
cycleTimestamp: Math.min(this.backloggedEvents.cycleTimestamp, this.events.cycleTimestamp),
|
|
59
|
+
payloadBytesEstimation: this.backloggedEvents.payloadBytesEstimation + this.events.payloadBytesEstimation,
|
|
60
|
+
hasError: this.backloggedEvents.hasError || this.events.hasError,
|
|
61
|
+
hasMeta: this.backloggedEvents.hasMeta || this.events.hasMeta,
|
|
62
|
+
hasSnapshot: this.backloggedEvents.hasSnapshot || this.events.hasSnapshot,
|
|
63
|
+
inlinedAllStylesheets: !!this.backloggedEvents.events.length && this.backloggedEvents.inlinedAllStylesheets || this.events.inlinedAllStylesheets
|
|
75
64
|
};
|
|
76
65
|
}
|
|
77
66
|
|
|
78
|
-
/** Clears the buffer (this
|
|
67
|
+
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
79
68
|
clearBuffer() {
|
|
80
|
-
|
|
81
|
-
this
|
|
69
|
+
this.backloggedEvents = this.parent.mode === _constants2.MODE.ERROR ? this.events : new _recorderEvents.RecorderEvents(this.shouldFix);
|
|
70
|
+
this.events = new _recorderEvents.RecorderEvents(this.shouldFix);
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
/** Begin recording using configured recording lib */
|
|
@@ -101,7 +90,9 @@ class Recorder {
|
|
|
101
90
|
let stop;
|
|
102
91
|
try {
|
|
103
92
|
stop = (0, _rrweb.record)({
|
|
104
|
-
emit:
|
|
93
|
+
emit: (event, isCheckout) => {
|
|
94
|
+
(0, _handle.handle)(RRWEB_DATA_CHANNEL, [event, isCheckout], undefined, this.parent.featureName, this.parent.ee);
|
|
95
|
+
},
|
|
105
96
|
blockClass: block_class,
|
|
106
97
|
ignoreClass: ignore_class,
|
|
107
98
|
maskTextClass: mask_text_class,
|
|
@@ -139,7 +130,7 @@ class Recorder {
|
|
|
139
130
|
/** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
|
|
140
131
|
if (!this.shouldFix) {
|
|
141
132
|
if (incompletes > 0) {
|
|
142
|
-
this.
|
|
133
|
+
this.events.inlinedAllStylesheets = false;
|
|
143
134
|
this.#warnCSSOnce();
|
|
144
135
|
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
145
136
|
}
|
|
@@ -151,7 +142,7 @@ class Recorder {
|
|
|
151
142
|
/** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
|
|
152
143
|
_stylesheetEvaluator.stylesheetEvaluator.fix().then(failedToFix => {
|
|
153
144
|
if (failedToFix > 0) {
|
|
154
|
-
this.
|
|
145
|
+
this.events.inlinedAllStylesheets = false;
|
|
155
146
|
this.shouldFix = false;
|
|
156
147
|
}
|
|
157
148
|
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
@@ -165,15 +156,12 @@ class Recorder {
|
|
|
165
156
|
if (!this.#fixing) this.store(event, isCheckout);
|
|
166
157
|
}
|
|
167
158
|
|
|
168
|
-
/** Store a payload in the buffer (this
|
|
159
|
+
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
169
160
|
store(event, isCheckout) {
|
|
170
|
-
if (!event) return;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
event.__newrelic = (0, _utils.buildNRMetaNode)(event.timestamp, this.parent.timeKeeper);
|
|
175
|
-
event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
|
|
176
|
-
}
|
|
161
|
+
if (!event || this.parent.blocked) return;
|
|
162
|
+
|
|
163
|
+
/** because we've waited until draining to process the buffered rrweb events, we can guarantee the timekeeper exists */
|
|
164
|
+
event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
|
|
177
165
|
event.__serialized = (0, _stringify.stringify)(event);
|
|
178
166
|
const eventBytes = event.__serialized.length;
|
|
179
167
|
/** The estimated size of the payload after compression */
|
|
@@ -188,26 +176,18 @@ class Recorder {
|
|
|
188
176
|
}
|
|
189
177
|
|
|
190
178
|
// meta event
|
|
191
|
-
|
|
192
|
-
this.currentBufferTarget.hasMeta = true;
|
|
193
|
-
}
|
|
179
|
+
this.events.hasMeta ||= event.type === _constants.RRWEB_EVENT_TYPES.Meta;
|
|
194
180
|
// snapshot event
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.currentBufferTarget.add(event);
|
|
181
|
+
this.events.hasSnapshot ||= this.hasSeenSnapshot ||= event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot;
|
|
182
|
+
|
|
183
|
+
//* dont let the EventBuffer class double evaluate the event data size, it's a performance burden and we have special reasons to do it outside the event buffer */
|
|
184
|
+
this.events.add(event, eventBytes);
|
|
200
185
|
|
|
201
186
|
// We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
|
|
202
187
|
// it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
|
|
203
|
-
if ((
|
|
204
|
-
// if we've made it to the ideal size of ~
|
|
205
|
-
|
|
206
|
-
this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
|
|
207
|
-
} else {
|
|
208
|
-
// we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
|
|
209
|
-
this.#preloaded.push(new _recorderEvents.RecorderEvents(this.shouldFix));
|
|
210
|
-
}
|
|
188
|
+
if ((this.events.hasSnapshot && this.events.hasMeta || payloadSize > _agentConstants.IDEAL_PAYLOAD_SIZE) && this.parent.mode === _constants2.MODE.FULL) {
|
|
189
|
+
// if we've made it to the ideal size of ~16kb before the interval timer, we should send early.
|
|
190
|
+
this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent);
|
|
211
191
|
}
|
|
212
192
|
}
|
|
213
193
|
|
|
@@ -221,13 +201,13 @@ class Recorder {
|
|
|
221
201
|
}
|
|
222
202
|
}
|
|
223
203
|
clearTimestamps() {
|
|
224
|
-
this.
|
|
204
|
+
this.events.cycleTimestamp = undefined;
|
|
225
205
|
}
|
|
226
206
|
|
|
227
207
|
/** Estimate the payload size */
|
|
228
208
|
getPayloadSize(newBytes = 0) {
|
|
229
209
|
// the query param padding constant gives us some padding for the other metadata to be safely injected
|
|
230
|
-
return this.estimateCompression(this.
|
|
210
|
+
return this.estimateCompression(this.events.payloadBytesEstimation + newBytes) + _constants.QUERY_PARAM_PADDING;
|
|
231
211
|
}
|
|
232
212
|
|
|
233
213
|
/** Extensive research has yielded about an 88% compression factor on these payloads.
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.buildNRMetaNode = buildNRMetaNode;
|
|
7
6
|
exports.customMasker = customMasker;
|
|
8
7
|
exports.hasReplayPrerequisite = hasReplayPrerequisite;
|
|
9
8
|
exports.isPreloadAllowed = isPreloadAllowed;
|
|
10
9
|
var _nreum = require("../../../common/window/nreum");
|
|
11
10
|
var _featureGates = require("../../utils/feature-gates");
|
|
12
|
-
var _runtime = require("../../../common/constants/runtime");
|
|
13
11
|
/**
|
|
14
12
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
15
13
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -25,17 +23,6 @@ function hasReplayPrerequisite(agentInit) {
|
|
|
25
23
|
function isPreloadAllowed(agentInit) {
|
|
26
24
|
return agentInit?.session_replay.preload === true && hasReplayPrerequisite(agentInit);
|
|
27
25
|
}
|
|
28
|
-
function buildNRMetaNode(timestamp, timeKeeper) {
|
|
29
|
-
const correctedTimestamp = timeKeeper.correctAbsoluteTimestamp(timestamp);
|
|
30
|
-
return {
|
|
31
|
-
originalTimestamp: timestamp,
|
|
32
|
-
correctedTimestamp,
|
|
33
|
-
timestampDiff: timestamp - correctedTimestamp,
|
|
34
|
-
originTime: _runtime.originTime,
|
|
35
|
-
correctedOriginTime: timeKeeper.correctedOriginTime,
|
|
36
|
-
originTimeDiff: Math.floor(_runtime.originTime - timeKeeper.correctedOriginTime)
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
26
|
function customMasker(text, element) {
|
|
40
27
|
try {
|
|
41
28
|
if (typeof element?.type === 'string') {
|
|
@@ -45,10 +45,11 @@ class EventBuffer {
|
|
|
45
45
|
/**
|
|
46
46
|
* Add feature-processed event to our buffer. If this event would cause our total raw size to exceed the set max payload size, it is dropped.
|
|
47
47
|
* @param {any} event - any primitive type or object
|
|
48
|
+
* @param {number} [evaluatedSize] - the evalated size of the event, if already done so before storing in the event buffer
|
|
48
49
|
* @returns {Boolean} true if successfully added; false otherwise
|
|
49
50
|
*/
|
|
50
|
-
add(event) {
|
|
51
|
-
const addSize = (0, _stringify.stringify)(event)?.length || 0; // (estimate) # of bytes a directly stringified event it would take to send
|
|
51
|
+
add(event, evaluatedSize) {
|
|
52
|
+
const addSize = evaluatedSize || (0, _stringify.stringify)(event)?.length || 0; // (estimate) # of bytes a directly stringified event it would take to send
|
|
52
53
|
if (this.#rawBytes + addSize > this.maxPayloadSize) {
|
|
53
54
|
const smTag = inject => "EventBuffer/".concat(inject, "/Dropped/Bytes");
|
|
54
55
|
this.featureAgg?.reportSupportabilityMetric(smTag(this.featureAgg.featureName), addSize); // bytes dropped for this feature will aggregate with this metric tag
|
|
@@ -18,7 +18,6 @@ import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/sessi
|
|
|
18
18
|
import { stringify } from '../../../common/util/stringify';
|
|
19
19
|
import { stylesheetEvaluator } from '../shared/stylesheet-evaluator';
|
|
20
20
|
import { now } from '../../../common/timing/now';
|
|
21
|
-
import { buildNRMetaNode } from '../shared/utils';
|
|
22
21
|
import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
|
|
23
22
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
24
23
|
import { canEnableSessionTracking } from '../../utils/feature-gates';
|
|
@@ -96,8 +95,9 @@ export class Aggregate extends AggregateBase {
|
|
|
96
95
|
}
|
|
97
96
|
return;
|
|
98
97
|
}
|
|
99
|
-
this.
|
|
100
|
-
|
|
98
|
+
this.initializeRecording(srMode).then(() => {
|
|
99
|
+
this.drain();
|
|
100
|
+
});
|
|
101
101
|
}).then(() => {
|
|
102
102
|
if (this.mode === MODE.OFF) {
|
|
103
103
|
this.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
|
|
@@ -120,7 +120,7 @@ export class Aggregate extends AggregateBase {
|
|
|
120
120
|
return Boolean(this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled);
|
|
121
121
|
}
|
|
122
122
|
handleError(e) {
|
|
123
|
-
if (this.recorder) this.recorder.
|
|
123
|
+
if (this.recorder) this.recorder.events.hasError = true;
|
|
124
124
|
// run once
|
|
125
125
|
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
126
126
|
this.switchToFull();
|
|
@@ -179,7 +179,7 @@ export class Aggregate extends AggregateBase {
|
|
|
179
179
|
Recorder
|
|
180
180
|
} = await import(/* webpackChunkName: "recorder" */'../shared/recorder');
|
|
181
181
|
this.recorder = new Recorder(this);
|
|
182
|
-
this.recorder.
|
|
182
|
+
this.recorder.events.hasError = this.errorNoticed;
|
|
183
183
|
} catch (err) {
|
|
184
184
|
return this.abort(ABORT_REASONS.IMPORT);
|
|
185
185
|
}
|
|
@@ -194,10 +194,6 @@ export class Aggregate extends AggregateBase {
|
|
|
194
194
|
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
195
195
|
// The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
|
|
196
196
|
|
|
197
|
-
// If theres preloaded events and we are in full mode, just harvest immediately to clear up space and for consistency
|
|
198
|
-
if (this.mode === MODE.FULL && this.recorder?.getEvents().type === 'preloaded') {
|
|
199
|
-
this.prepUtils().then(() => this.agentRef.runtime.harvester.triggerHarvestFor(this));
|
|
200
|
-
}
|
|
201
197
|
await this.prepUtils();
|
|
202
198
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording();
|
|
203
199
|
this.syncWithSessionManager({
|
|
@@ -236,33 +232,11 @@ export class Aggregate extends AggregateBase {
|
|
|
236
232
|
let len = 0;
|
|
237
233
|
if (!!this.gzipper && !!this.u8) {
|
|
238
234
|
payload.body = this.gzipper(this.u8("[".concat(payload.body.map(({
|
|
239
|
-
__serialized
|
|
240
|
-
|
|
241
|
-
}) => {
|
|
242
|
-
if (e.__newrelic && __serialized) return __serialized;
|
|
243
|
-
const output = {
|
|
244
|
-
...e
|
|
245
|
-
};
|
|
246
|
-
if (!output.__newrelic) {
|
|
247
|
-
output.__newrelic = buildNRMetaNode(e.timestamp, this.timeKeeper);
|
|
248
|
-
output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(e.timestamp);
|
|
249
|
-
}
|
|
250
|
-
return stringify(output);
|
|
251
|
-
}).join(','), "]")));
|
|
235
|
+
__serialized
|
|
236
|
+
}) => __serialized).join(','), "]")));
|
|
252
237
|
len = payload.body.length;
|
|
253
238
|
} else {
|
|
254
|
-
payload.body
|
|
255
|
-
__serialized,
|
|
256
|
-
...node
|
|
257
|
-
}) => {
|
|
258
|
-
if (node.__newrelic) return node;
|
|
259
|
-
const output = {
|
|
260
|
-
...node
|
|
261
|
-
};
|
|
262
|
-
output.__newrelic = buildNRMetaNode(node.timestamp, this.timeKeeper);
|
|
263
|
-
output.timestamp = this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
|
|
264
|
-
return output;
|
|
265
|
-
});
|
|
239
|
+
for (let idx in payload.body) delete payload.body[idx].__serialized;
|
|
266
240
|
len = stringify(payload.body).length;
|
|
267
241
|
}
|
|
268
242
|
if (len > MAX_PAYLOAD_SIZE) {
|
|
@@ -281,11 +255,6 @@ export class Aggregate extends AggregateBase {
|
|
|
281
255
|
}
|
|
282
256
|
return [payloadOutput];
|
|
283
257
|
}
|
|
284
|
-
getCorrectedTimestamp(node) {
|
|
285
|
-
if (!node?.timestamp) return;
|
|
286
|
-
if (node.__newrelic) return node.timestamp;
|
|
287
|
-
return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
|
|
288
|
-
}
|
|
289
258
|
|
|
290
259
|
/**
|
|
291
260
|
* returns the timestamps for the earliest and latest nodes in the provided array, even if out of order
|
|
@@ -333,8 +302,8 @@ export class Aggregate extends AggregateBase {
|
|
|
333
302
|
lastEvent
|
|
334
303
|
} = this.getFirstAndLastNodes(events);
|
|
335
304
|
// from rrweb node || from when the harvest cycle started
|
|
336
|
-
const firstTimestamp =
|
|
337
|
-
const lastTimestamp =
|
|
305
|
+
const firstTimestamp = firstEvent?.timestamp || Math.floor(this.timeKeeper.correctAbsoluteTimestamp(recorderEvents.cycleTimestamp));
|
|
306
|
+
const lastTimestamp = lastEvent?.timestamp || Math.floor(this.timeKeeper.correctRelativeTimestamp(relativeNow));
|
|
338
307
|
const agentMetadata = agentRuntime.appMetadata?.agents?.[0] || {};
|
|
339
308
|
return {
|
|
340
309
|
qs: {
|
|
@@ -10,7 +10,7 @@ import { handle } from '../../../common/event-emitter/handle';
|
|
|
10
10
|
import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants';
|
|
11
11
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
12
12
|
import { hasReplayPrerequisite, isPreloadAllowed } from '../shared/utils';
|
|
13
|
-
import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES
|
|
13
|
+
import { FEATURE_NAME, SR_EVENT_EMITTER_TYPES } from '../constants';
|
|
14
14
|
import { setupRecordReplayAPI } from '../../../loaders/api/recordReplay';
|
|
15
15
|
import { setupPauseReplayAPI } from '../../../loaders/api/pauseReplay';
|
|
16
16
|
export class Instrument extends InstrumentBase {
|
|
@@ -65,7 +65,7 @@ export class Instrument extends InstrumentBase {
|
|
|
65
65
|
/**
|
|
66
66
|
* This func is use for early pre-load recording prior to replay feature (agg) being loaded onto the page. It should only setup once, including if already called and in-progress.
|
|
67
67
|
*/
|
|
68
|
-
async #preloadStartRecording(
|
|
68
|
+
async #preloadStartRecording() {
|
|
69
69
|
if (this.#alreadyStarted) return;
|
|
70
70
|
this.#alreadyStarted = true;
|
|
71
71
|
try {
|
|
@@ -76,12 +76,11 @@ export class Instrument extends InstrumentBase {
|
|
|
76
76
|
// If startReplay() has been used by this point, we must record in full mode regardless of session preload:
|
|
77
77
|
// Note: recorder starts here with w/e the mode is at this time, but this may be changed later (see #apiStartOrRestartReplay else-case)
|
|
78
78
|
this.recorder ??= new Recorder({
|
|
79
|
+
...this,
|
|
79
80
|
mode: this.#mode,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
agentRef: this.#agentRef
|
|
84
|
-
});
|
|
81
|
+
agentRef: this.#agentRef,
|
|
82
|
+
timeKeeper: this.#agentRef.runtime.timeKeeper
|
|
83
|
+
}); // if TK exists due to deferred state, pass it
|
|
85
84
|
this.recorder.startRecording();
|
|
86
85
|
this.abortHandler = this.recorder.stopRecording;
|
|
87
86
|
} catch (err) {
|
|
@@ -103,7 +102,7 @@ export class Instrument extends InstrumentBase {
|
|
|
103
102
|
} else {
|
|
104
103
|
// pre-load
|
|
105
104
|
this.#mode = MODE.FULL;
|
|
106
|
-
this.#preloadStartRecording(
|
|
105
|
+
this.#preloadStartRecording();
|
|
107
106
|
// There's a race here wherein either:
|
|
108
107
|
// a. Recorder has not been initialized, and we've set the enforced mode, so we're good, or;
|
|
109
108
|
// b. Record has been initialized, possibly with the "wrong" mode, so we have to correct that + restart.
|
|
@@ -23,8 +23,8 @@ export class RecorderEvents {
|
|
|
23
23
|
/** Payload metadata -- Denotes whether all stylesheet elements were able to be inlined */
|
|
24
24
|
this.inlinedAllStylesheets = shouldInlineStylesheets;
|
|
25
25
|
}
|
|
26
|
-
add(event) {
|
|
27
|
-
this.#events.add(event);
|
|
26
|
+
add(event, evaluatedSize) {
|
|
27
|
+
this.#events.add(event, evaluatedSize);
|
|
28
28
|
}
|
|
29
29
|
get events() {
|
|
30
30
|
return this.#events.get();
|