@newrelic/browser-agent 1.240.0 → 1.242.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/dist/cjs/cdn/polyfills/lite.js +13 -1
- package/dist/cjs/cdn/polyfills/pro.js +17 -1
- package/dist/cjs/cdn/polyfills/spa.js +18 -1
- package/dist/cjs/common/config/state/init.js +48 -19
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/dom/query-selector.js +16 -0
- package/dist/cjs/features/metrics/aggregate/index.js +10 -7
- package/dist/cjs/features/page_view_timing/aggregate/index.js +9 -9
- package/dist/cjs/features/session_replay/aggregate/index.js +90 -40
- package/dist/cjs/features/utils/instrument-base.js +1 -0
- package/dist/cjs/loaders/browser-agent.js +2 -1
- package/dist/esm/cdn/polyfills/lite.js +8 -1
- package/dist/esm/cdn/polyfills/pro.js +13 -2
- package/dist/esm/cdn/polyfills/spa.js +13 -1
- package/dist/esm/common/config/state/init.js +48 -19
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/dom/query-selector.js +9 -0
- package/dist/esm/features/metrics/aggregate/index.js +10 -7
- package/dist/esm/features/page_view_timing/aggregate/index.js +9 -9
- package/dist/esm/features/session_replay/aggregate/index.js +90 -40
- package/dist/esm/features/utils/instrument-base.js +1 -0
- package/dist/esm/loaders/browser-agent.js +2 -1
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/dom/query-selector.d.ts +2 -0
- package/dist/types/common/dom/query-selector.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +17 -5
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/browser-agent.d.ts.map +1 -1
- package/package.json +2 -3
- package/src/cdn/polyfills/lite.js +14 -1
- package/src/cdn/polyfills/pro.js +23 -2
- package/src/cdn/polyfills/spa.js +24 -1
- package/src/common/config/state/init.js +46 -17
- package/src/common/config/state/init.test.js +40 -0
- package/src/common/dom/query-selector.js +9 -0
- package/src/common/dom/query-selector.test.js +24 -0
- package/src/common/harvest/harvest-scheduler.test.js +2 -2
- package/src/features/metrics/aggregate/index.js +5 -3
- package/src/features/page_view_timing/aggregate/index.js +7 -6
- package/src/features/session_replay/aggregate/index.component-test.js +10 -10
- package/src/features/session_replay/aggregate/index.js +71 -34
- package/src/features/utils/instrument-base.js +1 -0
- package/src/loaders/browser-agent.js +3 -1
|
@@ -1,13 +1,33 @@
|
|
|
1
|
+
import { isValidSelector } from '../../dom/query-selector';
|
|
1
2
|
import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../../session/constants';
|
|
3
|
+
import { warn } from '../../util/console';
|
|
2
4
|
import { gosNREUMInitializedAgents } from '../../window/nreum';
|
|
3
5
|
import { getModeledObject } from './configurable';
|
|
4
6
|
const model = () => {
|
|
5
7
|
const hiddenState = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
mask_selector: '*',
|
|
9
|
+
block_selector: '[data-nr-block]',
|
|
10
|
+
mask_input_options: {
|
|
11
|
+
color: false,
|
|
12
|
+
date: false,
|
|
13
|
+
'datetime-local': false,
|
|
14
|
+
email: false,
|
|
15
|
+
month: false,
|
|
16
|
+
number: false,
|
|
17
|
+
range: false,
|
|
18
|
+
search: false,
|
|
19
|
+
tel: false,
|
|
20
|
+
text: false,
|
|
21
|
+
time: false,
|
|
22
|
+
url: false,
|
|
23
|
+
week: false,
|
|
24
|
+
// unify textarea and select element with text input
|
|
25
|
+
textarea: false,
|
|
26
|
+
select: false,
|
|
27
|
+
password: true // This will be enforced to always be true in the setter
|
|
9
28
|
}
|
|
10
29
|
};
|
|
30
|
+
|
|
11
31
|
return {
|
|
12
32
|
proxy: {
|
|
13
33
|
assets: undefined,
|
|
@@ -78,38 +98,47 @@ const model = () => {
|
|
|
78
98
|
autoStart: true,
|
|
79
99
|
enabled: false,
|
|
80
100
|
harvestTimeSeconds: 60,
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
sampling_rate: 50,
|
|
102
|
+
// float from 0 - 100
|
|
103
|
+
error_sampling_rate: 50,
|
|
104
|
+
// float from 0 - 100
|
|
83
105
|
// recording config settings
|
|
84
|
-
|
|
85
|
-
|
|
106
|
+
mask_all_inputs: true,
|
|
107
|
+
// this has a getter/setter to facilitate validation of the selectors
|
|
108
|
+
get mask_text_selector() {
|
|
109
|
+
return hiddenState.mask_selector;
|
|
110
|
+
},
|
|
111
|
+
set mask_text_selector(val) {
|
|
112
|
+
if (isValidSelector(val)) hiddenState.mask_selector = val + ',[data-nr-mask]';else if (val === null) hiddenState.mask_selector = val; // null is acceptable, which completely disables the behavior
|
|
113
|
+
else warn('An invalid session_replay.mask_selector was provided and will not be used', val);
|
|
114
|
+
},
|
|
86
115
|
// these properties only have getters because they are enforcable constants and should error if someone tries to override them
|
|
87
|
-
get
|
|
116
|
+
get block_class() {
|
|
88
117
|
return 'nr-block';
|
|
89
118
|
},
|
|
90
|
-
get
|
|
119
|
+
get ignore_class() {
|
|
91
120
|
return 'nr-ignore';
|
|
92
121
|
},
|
|
93
|
-
get
|
|
122
|
+
get mask_text_class() {
|
|
94
123
|
return 'nr-mask';
|
|
95
124
|
},
|
|
96
125
|
// props with a getter and setter are used to extend enforcable constants with customer input
|
|
97
126
|
// we must preserve data-nr-block no matter what else the customer sets
|
|
98
|
-
get
|
|
99
|
-
return hiddenState.
|
|
127
|
+
get block_selector() {
|
|
128
|
+
return hiddenState.block_selector;
|
|
100
129
|
},
|
|
101
|
-
set
|
|
102
|
-
hiddenState.
|
|
130
|
+
set block_selector(val) {
|
|
131
|
+
if (isValidSelector(val)) hiddenState.block_selector += ",".concat(val);else if (val !== '') warn('An invalid session_replay.block_selector was provided and will not be used', val);
|
|
103
132
|
},
|
|
104
133
|
// password: must always be present and true no matter what customer sets
|
|
105
|
-
get
|
|
106
|
-
return hiddenState.
|
|
134
|
+
get mask_input_options() {
|
|
135
|
+
return hiddenState.mask_input_options;
|
|
107
136
|
},
|
|
108
|
-
set
|
|
109
|
-
hiddenState.
|
|
137
|
+
set mask_input_options(val) {
|
|
138
|
+
if (val && typeof val === 'object') hiddenState.mask_input_options = {
|
|
110
139
|
...val,
|
|
111
140
|
password: true
|
|
112
|
-
};
|
|
141
|
+
};else warn('An invalid session_replay.mask_input_option was provided and will not be used', val);
|
|
113
142
|
}
|
|
114
143
|
},
|
|
115
144
|
spa: {
|
|
@@ -27,13 +27,16 @@ export class Aggregate extends AggregateBase {
|
|
|
27
27
|
this.singleChecks(); // checks that are run only one time, at script load
|
|
28
28
|
this.eachSessionChecks(); // the start of every time user engages with page
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
this.ee.on("drain-".concat(this.featureName), () => {
|
|
31
|
+
// *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
|
|
32
|
+
scheduler = new HarvestScheduler('jserrors', {
|
|
33
|
+
onUnload: () => this.unload()
|
|
34
|
+
}, this);
|
|
35
|
+
scheduler.harvest.on('jserrors', () => ({
|
|
36
|
+
body: this.aggregator.take(['cm', 'sm'])
|
|
37
|
+
}));
|
|
38
|
+
}); // this is needed to ensure EoL is "on" and sent
|
|
39
|
+
|
|
37
40
|
this.drain();
|
|
38
41
|
}
|
|
39
42
|
storeSupportabilityMetrics(name, value) {
|
|
@@ -52,21 +52,21 @@ export class Aggregate extends AggregateBase {
|
|
|
52
52
|
|
|
53
53
|
/* It's important that CWV api, like "onLCP", is called before this scheduler is initialized. The reason is because they listen to the same
|
|
54
54
|
on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
|
|
55
|
-
|
|
56
|
-
onFinished: function () {
|
|
57
|
-
return _this.onHarvestFinished(...arguments);
|
|
58
|
-
},
|
|
59
|
-
getPayload: function () {
|
|
60
|
-
return _this.prepareHarvest(...arguments);
|
|
61
|
-
}
|
|
62
|
-
}, this);
|
|
63
|
-
registerHandler('timing', (name, value, attrs) => this.addTiming(name, value, attrs), this.featureName, this.ee); // notice CLS is added to all timings via 4th param
|
|
55
|
+
|
|
64
56
|
registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
|
|
65
57
|
registerHandler('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee);
|
|
66
58
|
const initialHarvestSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.initialHarvestSeconds') || 10;
|
|
67
59
|
const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.harvestTimeSeconds') || 30;
|
|
68
60
|
// send initial data sooner, then start regular
|
|
69
61
|
this.ee.on("drain-".concat(this.featureName), () => {
|
|
62
|
+
this.scheduler = new HarvestScheduler('events', {
|
|
63
|
+
onFinished: function () {
|
|
64
|
+
return _this.onHarvestFinished(...arguments);
|
|
65
|
+
},
|
|
66
|
+
getPayload: function () {
|
|
67
|
+
return _this.prepareHarvest(...arguments);
|
|
68
|
+
}
|
|
69
|
+
}, this);
|
|
70
70
|
this.scheduler.startTimer(harvestTimeSeconds, initialHarvestSeconds);
|
|
71
71
|
});
|
|
72
72
|
this.drain();
|
|
@@ -20,6 +20,9 @@ import { AggregateBase } from '../../utils/aggregate-base';
|
|
|
20
20
|
import { sharedChannel } from '../../../common/constants/shared-channel';
|
|
21
21
|
import { obj as encodeObj } from '../../../common/url/encode';
|
|
22
22
|
import { warn } from '../../../common/util/console';
|
|
23
|
+
import { globalScope } from '../../../common/constants/runtime';
|
|
24
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
25
|
+
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
23
26
|
|
|
24
27
|
// would be better to get this dynamically in some way
|
|
25
28
|
export const RRWEB_VERSION = '2.0.0-alpha.8';
|
|
@@ -64,17 +67,30 @@ export class Aggregate extends AggregateBase {
|
|
|
64
67
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
65
68
|
*/
|
|
66
69
|
this.hasSnapshot = false;
|
|
70
|
+
/** Payload metadata -- Should indicate that the payload being sent has a meta node. The meta node should always precede a snapshot node. */
|
|
71
|
+
this.hasMeta = false;
|
|
67
72
|
/** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
|
|
68
73
|
this.hasError = false;
|
|
69
74
|
|
|
70
|
-
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
75
|
+
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
76
|
+
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
77
|
+
*/
|
|
71
78
|
this.timestamp = {
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
event: {
|
|
80
|
+
first: undefined,
|
|
81
|
+
last: undefined
|
|
82
|
+
},
|
|
83
|
+
cycle: {
|
|
84
|
+
first: undefined,
|
|
85
|
+
last: undefined
|
|
86
|
+
}
|
|
74
87
|
};
|
|
75
88
|
|
|
76
89
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
77
90
|
this.payloadBytesEstimation = 0;
|
|
91
|
+
|
|
92
|
+
/** 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 */
|
|
93
|
+
this.lastMeta = undefined;
|
|
78
94
|
const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
|
|
79
95
|
|
|
80
96
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
@@ -82,7 +98,7 @@ export class Aggregate extends AggregateBase {
|
|
|
82
98
|
if (shouldSetup) {
|
|
83
99
|
// 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.
|
|
84
100
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
85
|
-
this.abort();
|
|
101
|
+
this.abort('Session Reset');
|
|
86
102
|
});
|
|
87
103
|
|
|
88
104
|
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
@@ -124,7 +140,7 @@ export class Aggregate extends AggregateBase {
|
|
|
124
140
|
}, this.featureName, this.ee);
|
|
125
141
|
this.waitForFlags(['sr']).then(_ref => {
|
|
126
142
|
let [flagOn] = _ref;
|
|
127
|
-
return this.initializeRecording(flagOn, Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.
|
|
143
|
+
return this.initializeRecording(flagOn, Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate'));
|
|
128
144
|
}).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
|
|
129
145
|
|
|
130
146
|
this.drain();
|
|
@@ -165,6 +181,12 @@ export class Aggregate extends AggregateBase {
|
|
|
165
181
|
if (this.mode === MODE.ERROR && this.errorNoticed) {
|
|
166
182
|
this.mode = MODE.FULL;
|
|
167
183
|
}
|
|
184
|
+
try {
|
|
185
|
+
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
186
|
+
recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
|
|
187
|
+
} catch (err) {
|
|
188
|
+
return this.abort('Recorder failed to import');
|
|
189
|
+
}
|
|
168
190
|
|
|
169
191
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
170
192
|
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
@@ -173,12 +195,6 @@ export class Aggregate extends AggregateBase {
|
|
|
173
195
|
// We only report (harvest) in FULL mode
|
|
174
196
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
175
197
|
}
|
|
176
|
-
try {
|
|
177
|
-
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
178
|
-
recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
|
|
179
|
-
} catch (err) {
|
|
180
|
-
return this.abort();
|
|
181
|
-
}
|
|
182
198
|
try {
|
|
183
199
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
184
200
|
const {
|
|
@@ -213,6 +229,8 @@ export class Aggregate extends AggregateBase {
|
|
|
213
229
|
getHarvestContents() {
|
|
214
230
|
const agentRuntime = getRuntime(this.agentIdentifier);
|
|
215
231
|
const info = getInfo(this.agentIdentifier);
|
|
232
|
+
const firstTimestamp = this.timestamp.event.first || this.timestamp.cycle.first;
|
|
233
|
+
const lastTimestamp = this.timestamp.event.last || this.timestamp.cycle.last;
|
|
216
234
|
return {
|
|
217
235
|
qs: {
|
|
218
236
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -223,11 +241,12 @@ export class Aggregate extends AggregateBase {
|
|
|
223
241
|
...(this.shouldCompress && {
|
|
224
242
|
content_encoding: 'gzip'
|
|
225
243
|
}),
|
|
226
|
-
'replay.firstTimestamp':
|
|
227
|
-
'replay.lastTimestamp':
|
|
228
|
-
'replay.durationMs':
|
|
244
|
+
'replay.firstTimestamp': firstTimestamp,
|
|
245
|
+
'replay.lastTimestamp': lastTimestamp,
|
|
246
|
+
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
229
247
|
agentVersion: agentRuntime.version,
|
|
230
248
|
session: agentRuntime.session.state.value,
|
|
249
|
+
hasMeta: this.hasMeta,
|
|
231
250
|
hasSnapshot: this.hasSnapshot,
|
|
232
251
|
hasError: this.hasError,
|
|
233
252
|
isFirstChunk: this.isFirstChunk,
|
|
@@ -242,7 +261,7 @@ export class Aggregate extends AggregateBase {
|
|
|
242
261
|
onHarvestFinished(result) {
|
|
243
262
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
244
263
|
if (result.status === 429) {
|
|
245
|
-
this.abort();
|
|
264
|
+
this.abort('429: Too many requests');
|
|
246
265
|
}
|
|
247
266
|
if (this.blocked) this.scheduler.stopTimer(true);
|
|
248
267
|
}
|
|
@@ -252,6 +271,7 @@ export class Aggregate extends AggregateBase {
|
|
|
252
271
|
this.events = [];
|
|
253
272
|
this.isFirstChunk = false;
|
|
254
273
|
this.hasSnapshot = false;
|
|
274
|
+
this.hasMeta = false;
|
|
255
275
|
this.hasError = false;
|
|
256
276
|
this.payloadBytesEstimation = 0;
|
|
257
277
|
this.clearTimestamps();
|
|
@@ -261,29 +281,32 @@ export class Aggregate extends AggregateBase {
|
|
|
261
281
|
startRecording() {
|
|
262
282
|
if (!recorder) {
|
|
263
283
|
warn('Recording library was never imported');
|
|
264
|
-
return this.abort();
|
|
284
|
+
return this.abort('Recorder was never imported');
|
|
265
285
|
}
|
|
286
|
+
this.clearTimestamps();
|
|
287
|
+
// set the fallbacks as early as possible
|
|
288
|
+
this.setTimestamps();
|
|
266
289
|
this.recording = true;
|
|
267
290
|
const {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
block_class,
|
|
292
|
+
ignore_class,
|
|
293
|
+
mask_text_class,
|
|
294
|
+
block_selector,
|
|
295
|
+
mask_input_options,
|
|
296
|
+
mask_text_selector,
|
|
297
|
+
mask_all_inputs
|
|
275
298
|
} = getConfigurationValue(this.agentIdentifier, 'session_replay');
|
|
276
299
|
// set up rrweb configurations for maximum privacy --
|
|
277
300
|
// https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
|
|
278
301
|
const stop = recorder({
|
|
279
302
|
emit: this.store.bind(this),
|
|
280
|
-
blockClass,
|
|
281
|
-
ignoreClass,
|
|
282
|
-
maskTextClass,
|
|
283
|
-
blockSelector,
|
|
284
|
-
maskInputOptions,
|
|
285
|
-
maskTextSelector,
|
|
286
|
-
maskAllInputs,
|
|
303
|
+
blockClass: block_class,
|
|
304
|
+
ignoreClass: ignore_class,
|
|
305
|
+
maskTextClass: mask_text_class,
|
|
306
|
+
blockSelector: block_selector,
|
|
307
|
+
maskInputOptions: mask_input_options,
|
|
308
|
+
maskTextSelector: mask_text_selector,
|
|
309
|
+
maskAllInputs: mask_all_inputs,
|
|
287
310
|
checkoutEveryNms: CHECKOUT_MS[this.mode]
|
|
288
311
|
});
|
|
289
312
|
this.stopRecording = () => {
|
|
@@ -294,6 +317,7 @@ export class Aggregate extends AggregateBase {
|
|
|
294
317
|
|
|
295
318
|
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
296
319
|
store(event, isCheckout) {
|
|
320
|
+
this.setTimestamps(event);
|
|
297
321
|
if (this.blocked) return;
|
|
298
322
|
const eventBytes = stringify(event).length;
|
|
299
323
|
/** The estimated size of the payload after compression */
|
|
@@ -301,7 +325,8 @@ export class Aggregate extends AggregateBase {
|
|
|
301
325
|
// Vortex will block payloads at a certain size, we might as well not send.
|
|
302
326
|
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
303
327
|
this.clearBuffer();
|
|
304
|
-
|
|
328
|
+
this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Too-Big/Seen'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
329
|
+
return this.abort('Payload too big');
|
|
305
330
|
}
|
|
306
331
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
307
332
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
@@ -310,8 +335,22 @@ export class Aggregate extends AggregateBase {
|
|
|
310
335
|
// we are still waiting for an error to throw, so keep wiping the buffer over time
|
|
311
336
|
this.clearBuffer();
|
|
312
337
|
}
|
|
313
|
-
|
|
314
|
-
|
|
338
|
+
|
|
339
|
+
// meta event
|
|
340
|
+
if (event.type === 4) {
|
|
341
|
+
this.hasMeta = true;
|
|
342
|
+
this.lastMeta = event;
|
|
343
|
+
}
|
|
344
|
+
// snapshot event
|
|
345
|
+
if (event.type === 2) {
|
|
346
|
+
this.hasSnapshot = true;
|
|
347
|
+
// small chance that the meta event got separated from its matching snapshot across payload harvests
|
|
348
|
+
// it needs to precede the snapshot, so shove it in first.
|
|
349
|
+
if (!this.hasMeta) {
|
|
350
|
+
this.events.push(this.lastMeta);
|
|
351
|
+
this.hasMeta = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
315
354
|
this.events.push(event);
|
|
316
355
|
this.payloadBytesEstimation += eventBytes;
|
|
317
356
|
|
|
@@ -328,15 +367,25 @@ export class Aggregate extends AggregateBase {
|
|
|
328
367
|
if (!recorder) return;
|
|
329
368
|
recorder.takeFullSnapshot();
|
|
330
369
|
}
|
|
331
|
-
setTimestamps(
|
|
332
|
-
if
|
|
333
|
-
|
|
334
|
-
this.timestamp.
|
|
370
|
+
setTimestamps(event) {
|
|
371
|
+
// fallbacks if timestamps cannot be derived from rrweb events
|
|
372
|
+
this.timestamp.cycle.last = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
|
|
373
|
+
if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
|
|
374
|
+
// timestamps based on rrweb events
|
|
375
|
+
if (!event || !event.timestamp) return;
|
|
376
|
+
if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
|
|
377
|
+
this.timestamp.event.last = event.timestamp;
|
|
335
378
|
}
|
|
336
379
|
clearTimestamps() {
|
|
337
380
|
this.timestamp = {
|
|
338
|
-
|
|
339
|
-
|
|
381
|
+
event: {
|
|
382
|
+
first: undefined,
|
|
383
|
+
last: undefined
|
|
384
|
+
},
|
|
385
|
+
cycle: {
|
|
386
|
+
first: undefined,
|
|
387
|
+
last: undefined
|
|
388
|
+
}
|
|
340
389
|
};
|
|
341
390
|
}
|
|
342
391
|
|
|
@@ -348,7 +397,8 @@ export class Aggregate extends AggregateBase {
|
|
|
348
397
|
}
|
|
349
398
|
|
|
350
399
|
/** Abort the feature, once aborted it will not resume */
|
|
351
|
-
abort() {
|
|
400
|
+
abort(reason) {
|
|
401
|
+
warn("SR aborted -- ".concat(reason));
|
|
352
402
|
this.blocked = true;
|
|
353
403
|
this.mode = MODE.OFF;
|
|
354
404
|
this.stopRecording();
|
|
@@ -114,6 +114,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
114
114
|
warn("Downloading and initializing ".concat(this.featureName, " failed..."), e);
|
|
115
115
|
this.abortHandler?.(); // undo any important alterations made to the page
|
|
116
116
|
// not supported yet but nice to do: "abort" this agent's EE for this feature specifically
|
|
117
|
+
drain(this.agentIdentifier, this.featureName);
|
|
117
118
|
loadedSuccessfully(false);
|
|
118
119
|
}
|
|
119
120
|
};
|
|
@@ -7,6 +7,7 @@ import { Instrument as InstrumentXhr } from '../features/ajax/instrument';
|
|
|
7
7
|
import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument';
|
|
8
8
|
import { Instrument as InstrumentSpa } from '../features/spa/instrument';
|
|
9
9
|
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument';
|
|
10
|
+
import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
|
|
@@ -16,7 +17,7 @@ export class BrowserAgent extends Agent {
|
|
|
16
17
|
constructor(args) {
|
|
17
18
|
super({
|
|
18
19
|
...args,
|
|
19
|
-
features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa],
|
|
20
|
+
features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa, InstrumentSessionReplay],
|
|
20
21
|
loaderType: 'browser-agent'
|
|
21
22
|
});
|
|
22
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"AAuGA,+CAIC;AAED,0DAIC;AAED,+DAYC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-selector.d.ts","sourceRoot":"","sources":["../../../../src/common/dom/query-selector.js"],"names":[],"mappings":"AAAO,wDAQN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAYA;IACE,2BAAiC;IACjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAYA;IACE,2BAAiC;IACjC,mDAwBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqCC;IAED,0BAOC;IAED,eA0CC;IAvCG,mCAAyB;CAwC9B;8BAvI6B,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAAiC;IAMjC,mDAoCC;IAjCC,eAAiB;IACjB,mBAAqB;IACrB,4BAA+B;IAuB7B,4BAGQ;IAOZ;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;OAEG;IACH,uCAUC;IAED,mDAqBC;IAED,qCAKC;IAED,gDAWC;IAGD;;;;kBAWC;IAGD,8BAuBC;;CACF;8BAnK6B,4BAA4B;iCANzB,2CAA2C"}
|
|
@@ -28,15 +28,27 @@ export class Aggregate extends AggregateBase {
|
|
|
28
28
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
29
29
|
*/
|
|
30
30
|
hasSnapshot: boolean;
|
|
31
|
+
/** Payload metadata -- Should indicate that the payload being sent has a meta node. The meta node should always precede a snapshot node. */
|
|
32
|
+
hasMeta: boolean;
|
|
31
33
|
/** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
|
|
32
34
|
hasError: boolean;
|
|
33
|
-
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
35
|
+
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
36
|
+
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
37
|
+
*/
|
|
34
38
|
timestamp: {
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
event: {
|
|
40
|
+
first: undefined;
|
|
41
|
+
last: undefined;
|
|
42
|
+
};
|
|
43
|
+
cycle: {
|
|
44
|
+
first: undefined;
|
|
45
|
+
last: undefined;
|
|
46
|
+
};
|
|
37
47
|
};
|
|
38
48
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
39
49
|
payloadBytesEstimation: number;
|
|
50
|
+
/** 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
|
+
lastMeta: any;
|
|
40
52
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
41
53
|
stopRecording: () => void;
|
|
42
54
|
scheduler: HarvestScheduler | undefined;
|
|
@@ -77,12 +89,12 @@ export class Aggregate extends AggregateBase {
|
|
|
77
89
|
store(event: any, isCheckout: any): void;
|
|
78
90
|
/** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
|
|
79
91
|
takeFullSnapshot(): void;
|
|
80
|
-
setTimestamps(
|
|
92
|
+
setTimestamps(event: any): void;
|
|
81
93
|
clearTimestamps(): void;
|
|
82
94
|
/** Estimate the payload size */
|
|
83
95
|
getPayloadSize(newBytes?: number): any;
|
|
84
96
|
/** Abort the feature, once aborted it will not resume */
|
|
85
|
-
abort(): void;
|
|
97
|
+
abort(reason: any): void;
|
|
86
98
|
/** Extensive research has yielded about an 88% compression factor on these payloads.
|
|
87
99
|
* This is an estimation using that factor as to not cause performance issues while evaluating
|
|
88
100
|
* https://staging.onenr.io/037jbJWxbjy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA2BA,4CAA4C;AAE5C,mCAAmC;AAInC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDAmGC;IAjGC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B,gGAAgG;IAChG,sBAAyB;IACzB;;;MAGE;IACF,qBAAwB;IACxB,4IAA4I;IAC5I,iBAAoB;IACpB,+HAA+H;IAC/H,kBAAqB;IAErB;;OAEG;IACH;;;;;;;;;MAA+G;IAE/G,qGAAqG;IACrG,+BAA+B;IAE/B,kIAAkI;IAClI,cAAyB;IAOzB,uIAAuI;IACvI,0BAAyE;IAiBvE,wCAKQ;IA+BZ;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAyDhB;IAED;;;;;;;;;oBAaC;IAED;;;;;;;;;MA4BC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAQC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCA6CC;IAED,0HAA0H;IAC1H,yBAGC;IAED,gCAQC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,yBAQC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA/X6B,4BAA4B;iCALzB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAcA;;;GAGG;AACH;IACE;;;;;;;;OAQG;IACH,6BAPW,MAAM,uCAEN,MAAM,8BA4BhB;IArBC,cAAgB;IAEhB,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,qBAA8B;IAE9B;;;MAGE;IACF,kCAAoC;IAQtC;;;;;OAKG;IACH,
|
|
1
|
+
{"version":3,"file":"instrument-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/instrument-base.js"],"names":[],"mappings":"AAcA;;;GAGG;AACH;IACE;;;;;;;;OAQG;IACH,6BAPW,MAAM,uCAEN,MAAM,8BA4BhB;IArBC,cAAgB;IAEhB,8IAA8I;IAC9I,cADW,WAAW,SAAS,CACF;IAE7B;;;MAGE;IACF,qBAA8B;IAE9B;;;MAGE;IACF,kCAAoC;IAQtC;;;;;OAKG;IACH,mEA4DC;IAED;;;;;KAKC;IACD,6BAJS,MAAM,mCAWd;CACF;4BAhI2B,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/browser-agent.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"browser-agent.d.ts","sourceRoot":"","sources":["../../../src/loaders/browser-agent.js"],"names":[],"mappings":"AAYA;;;GAGG;AACH;IACE,uBAgBC;CACF;sBAlCqB,SAAS"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.242.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"dependencies": {
|
|
177
177
|
"core-js": "^3.26.0",
|
|
178
178
|
"fflate": "^0.7.4",
|
|
179
|
-
"rrweb": "
|
|
179
|
+
"rrweb": "2.0.0-alpha.8",
|
|
180
180
|
"web-vitals": "^3.1.0"
|
|
181
181
|
},
|
|
182
182
|
"devDependencies": {
|
|
@@ -236,7 +236,6 @@
|
|
|
236
236
|
"jest-extended": "^3.2.4",
|
|
237
237
|
"jung": "^2.1.0",
|
|
238
238
|
"just-debounce": "^1.0.0",
|
|
239
|
-
"newrelic": "^9.7.5",
|
|
240
239
|
"node-fetch": "^3.3.0",
|
|
241
240
|
"npm-run-all": "^4.1.5",
|
|
242
241
|
"object-inspect": "^1.5.0",
|
|
@@ -4,4 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import '../polyfills.js'
|
|
7
|
-
import '
|
|
7
|
+
import { Agent } from '../../loaders/agent'
|
|
8
|
+
|
|
9
|
+
import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument'
|
|
10
|
+
import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument'
|
|
11
|
+
import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument'
|
|
12
|
+
|
|
13
|
+
new Agent({
|
|
14
|
+
features: [
|
|
15
|
+
InstrumentPageViewEvent,
|
|
16
|
+
InstrumentPageViewTiming,
|
|
17
|
+
InstrumentMetrics
|
|
18
|
+
],
|
|
19
|
+
loaderType: 'lite-polyfills'
|
|
20
|
+
})
|
package/src/cdn/polyfills/pro.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file Creates a version of the "
|
|
2
|
+
* @file Creates a version of the "PRO" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
|
|
3
3
|
* polyfills for pre-ES6 browsers and IE 11.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import '../polyfills.js'
|
|
7
|
-
import '
|
|
7
|
+
import { Agent } from '../../loaders/agent'
|
|
8
|
+
|
|
9
|
+
import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument'
|
|
10
|
+
import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument'
|
|
11
|
+
import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument'
|
|
12
|
+
import { Instrument as InstrumentErrors } from '../../features/jserrors/instrument'
|
|
13
|
+
import { Instrument as InstrumentXhr } from '../../features/ajax/instrument'
|
|
14
|
+
import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument'
|
|
15
|
+
import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument'
|
|
16
|
+
|
|
17
|
+
new Agent({
|
|
18
|
+
features: [
|
|
19
|
+
InstrumentPageViewEvent,
|
|
20
|
+
InstrumentPageViewTiming,
|
|
21
|
+
InstrumentSessionTrace,
|
|
22
|
+
InstrumentXhr,
|
|
23
|
+
InstrumentMetrics,
|
|
24
|
+
InstrumentPageAction,
|
|
25
|
+
InstrumentErrors
|
|
26
|
+
],
|
|
27
|
+
loaderType: 'pro-polyfills'
|
|
28
|
+
})
|