@newrelic/browser-agent 1.266.0 → 1.268.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 +29 -0
- package/dist/cjs/common/config/init.js +4 -3
- 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/iframe.js +10 -0
- package/dist/cjs/common/dom/selector-path.js +48 -0
- package/dist/cjs/common/event-listener/event-listener-opts.js +4 -26
- package/dist/cjs/common/timing/time-keeper.js +14 -12
- package/dist/cjs/common/util/stringify.js +3 -1
- package/dist/cjs/common/vitals/interaction-to-next-paint.js +11 -3
- package/dist/cjs/common/vitals/largest-contentful-paint.js +3 -1
- package/dist/cjs/features/generic_events/aggregate/index.js +80 -9
- package/dist/cjs/features/generic_events/aggregate/user-actions/aggregated-user-action.js +39 -0
- package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +77 -0
- package/dist/cjs/features/generic_events/constants.js +6 -2
- package/dist/cjs/features/generic_events/instrument/index.js +12 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +2 -2
- package/dist/cjs/features/logging/aggregate/index.js +1 -1
- package/dist/cjs/features/metrics/aggregate/index.js +2 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +10 -10
- package/dist/cjs/features/session_replay/aggregate/index.js +6 -6
- package/dist/cjs/features/session_replay/shared/recorder.js +22 -14
- package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +5 -4
- package/dist/cjs/features/session_trace/aggregate/index.js +6 -4
- package/dist/cjs/features/utils/event-buffer.js +2 -1
- package/dist/cjs/loaders/browser-agent.js +2 -1
- package/dist/esm/common/config/init.js +4 -3
- 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/iframe.js +4 -0
- package/dist/esm/common/dom/selector-path.js +41 -0
- package/dist/esm/common/event-listener/event-listener-opts.js +4 -27
- package/dist/esm/common/timing/time-keeper.js +14 -11
- package/dist/esm/common/util/stringify.js +3 -1
- package/dist/esm/common/vitals/interaction-to-next-paint.js +11 -3
- package/dist/esm/common/vitals/largest-contentful-paint.js +3 -1
- package/dist/esm/features/generic_events/aggregate/index.js +82 -11
- package/dist/esm/features/generic_events/aggregate/user-actions/aggregated-user-action.js +32 -0
- package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +70 -0
- package/dist/esm/features/generic_events/constants.js +5 -1
- package/dist/esm/features/generic_events/instrument/index.js +14 -3
- package/dist/esm/features/jserrors/aggregate/index.js +2 -2
- package/dist/esm/features/logging/aggregate/index.js +1 -1
- package/dist/esm/features/metrics/aggregate/index.js +2 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +10 -10
- package/dist/esm/features/session_replay/aggregate/index.js +6 -6
- package/dist/esm/features/session_replay/shared/recorder.js +22 -14
- package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +5 -4
- package/dist/esm/features/session_trace/aggregate/index.js +6 -4
- package/dist/esm/features/utils/event-buffer.js +2 -1
- package/dist/esm/loaders/browser-agent.js +2 -1
- package/dist/types/common/dom/iframe.d.ts +2 -0
- package/dist/types/common/dom/iframe.d.ts.map +1 -0
- package/dist/types/common/dom/selector-path.d.ts +2 -0
- package/dist/types/common/dom/selector-path.d.ts.map +1 -0
- package/dist/types/common/event-listener/event-listener-opts.d.ts +2 -2
- package/dist/types/common/event-listener/event-listener-opts.d.ts.map +1 -1
- package/dist/types/common/timing/time-keeper.d.ts +8 -1
- package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
- package/dist/types/common/util/stringify.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +16 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts +22 -0
- package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map +1 -0
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts +12 -0
- package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -0
- package/dist/types/features/generic_events/constants.d.ts +4 -0
- package/dist/types/features/generic_events/constants.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +2 -2
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.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/dist/types/loaders/browser-agent.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/common/config/init.js +2 -2
- package/src/common/dom/iframe.js +4 -0
- package/src/common/dom/selector-path.js +45 -0
- package/src/common/event-listener/event-listener-opts.js +5 -30
- package/src/common/timing/__mocks__/time-keeper.js +1 -0
- package/src/common/timing/time-keeper.js +14 -12
- package/src/common/util/stringify.js +3 -1
- package/src/common/vitals/interaction-to-next-paint.js +9 -3
- package/src/common/vitals/largest-contentful-paint.js +2 -1
- package/src/features/generic_events/aggregate/index.js +74 -14
- package/src/features/generic_events/aggregate/user-actions/aggregated-user-action.js +33 -0
- package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +73 -0
- package/src/features/generic_events/constants.js +6 -0
- package/src/features/generic_events/instrument/index.js +19 -3
- package/src/features/jserrors/aggregate/index.js +2 -6
- package/src/features/logging/aggregate/index.js +1 -3
- package/src/features/metrics/aggregate/index.js +2 -1
- package/src/features/page_view_event/aggregate/index.js +10 -14
- package/src/features/session_replay/aggregate/index.js +7 -10
- package/src/features/session_replay/shared/recorder.js +23 -14
- package/src/features/session_replay/shared/stylesheet-evaluator.js +5 -4
- package/src/features/session_trace/aggregate/index.js +6 -10
- package/src/features/utils/event-buffer.js +2 -1
- package/src/loaders/browser-agent.js +2 -0
|
@@ -118,7 +118,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
118
118
|
queryParameters.fp = _firstPaint.firstPaint.current.value;
|
|
119
119
|
queryParameters.fcp = _firstContentfulPaint.firstContentfulPaint.current.value;
|
|
120
120
|
if (this.timeKeeper?.ready) {
|
|
121
|
-
queryParameters.timestamp = Math.floor(this.timeKeeper.
|
|
121
|
+
queryParameters.timestamp = Math.floor(this.timeKeeper.correctRelativeTimestamp((0, _now.now)()));
|
|
122
122
|
}
|
|
123
123
|
const rumStartTime = (0, _now.now)();
|
|
124
124
|
harvester.send({
|
|
@@ -142,20 +142,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
142
142
|
this.ee.abort();
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
-
try {
|
|
146
|
-
this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
|
|
147
|
-
if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
148
|
-
agentRuntime.timeKeeper = this.timeKeeper;
|
|
149
|
-
} catch (error) {
|
|
150
|
-
this.ee.abort();
|
|
151
|
-
(0, _console.warn)(17, error);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
145
|
try {
|
|
155
146
|
const {
|
|
156
147
|
app,
|
|
157
148
|
...flags
|
|
158
149
|
} = JSON.parse(responseText);
|
|
150
|
+
try {
|
|
151
|
+
this.timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime, app.nrServerTime);
|
|
152
|
+
if (!this.timeKeeper.ready) throw new Error('TimeKeeper not ready');
|
|
153
|
+
agentRuntime.timeKeeper = this.timeKeeper;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.ee.abort();
|
|
156
|
+
(0, _console.warn)(17, error);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
159
|
agentRuntime.appMetadata = app;
|
|
160
160
|
(0, _featureFlags.activateFeatures)(flags, this.agentIdentifier);
|
|
161
161
|
this.drain();
|
|
@@ -26,6 +26,7 @@ var _drain = require("../../../common/drain/drain");
|
|
|
26
26
|
var _now = require("../../../common/timing/now");
|
|
27
27
|
var _utils = require("../shared/utils");
|
|
28
28
|
var _agentConstants = require("../../../common/constants/agent-constants");
|
|
29
|
+
var _cleanUrl = require("../../../common/url/clean-url");
|
|
29
30
|
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); }
|
|
30
31
|
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 && {}.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; } /*
|
|
31
32
|
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
@@ -108,7 +109,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
108
109
|
block_selector,
|
|
109
110
|
mask_text_selector,
|
|
110
111
|
mask_all_inputs,
|
|
111
|
-
inline_stylesheet,
|
|
112
112
|
inline_images,
|
|
113
113
|
collect_fonts
|
|
114
114
|
} = (0, _init.getConfigurationValue)(this.agentIdentifier, 'session_replay');
|
|
@@ -135,7 +135,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
135
135
|
/** Detect if the default configs have been altered and report a SM. This is useful to evaluate what the reasonable defaults are across a customer base over time */
|
|
136
136
|
if (!autoStart) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
137
137
|
if (collect_fonts === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
138
|
-
if (inline_stylesheet !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
139
138
|
if (inline_images === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
140
139
|
if (mask_all_inputs !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
141
140
|
if (block_selector !== '[data-nr-block]') (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
@@ -253,7 +252,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
253
252
|
prepareHarvest({
|
|
254
253
|
opts
|
|
255
254
|
} = {}) {
|
|
256
|
-
if (!this.recorder || !this.timeKeeper?.ready) return;
|
|
255
|
+
if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return;
|
|
257
256
|
const recorderEvents = this.recorder.getEvents();
|
|
258
257
|
// get the event type and use that to trigger another harvest if needed
|
|
259
258
|
if (!recorderEvents.events.length || this.mode !== _constants3.MODE.FULL || this.blocked) return;
|
|
@@ -345,8 +344,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
345
344
|
const firstEventTimestamp = this.getCorrectedTimestamp(events[0]); // from rrweb node
|
|
346
345
|
const lastEventTimestamp = this.getCorrectedTimestamp(events[events.length - 1]); // from rrweb node
|
|
347
346
|
// from rrweb node || from when the harvest cycle started
|
|
348
|
-
const firstTimestamp = firstEventTimestamp || Math.floor(this.timeKeeper.correctAbsoluteTimestamp(
|
|
349
|
-
const lastTimestamp = lastEventTimestamp || Math.floor(this.timeKeeper.
|
|
347
|
+
const firstTimestamp = firstEventTimestamp || Math.floor(this.timeKeeper.correctAbsoluteTimestamp(recorderEvents.cycleTimestamp));
|
|
348
|
+
const lastTimestamp = lastEventTimestamp || Math.floor(this.timeKeeper.correctRelativeTimestamp(relativeNow));
|
|
350
349
|
const agentMetadata = agentRuntime.appMetadata?.agents?.[0] || {};
|
|
351
350
|
return {
|
|
352
351
|
qs: {
|
|
@@ -384,7 +383,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
384
383
|
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
385
384
|
...(endUserId && {
|
|
386
385
|
'enduser.id': this.obfuscator.obfuscateString(endUserId)
|
|
387
|
-
})
|
|
386
|
+
}),
|
|
387
|
+
currentUrl: this.obfuscator.obfuscateString((0, _cleanUrl.cleanURL)('' + location))
|
|
388
388
|
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
389
389
|
}, _constants.QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
390
390
|
},
|
|
@@ -33,14 +33,14 @@ class Recorder {
|
|
|
33
33
|
this.recording = false;
|
|
34
34
|
/** The pointer to the current bucket holding rrweb events */
|
|
35
35
|
this.currentBufferTarget = this.#events;
|
|
36
|
+
/** 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
|
+
this.hasSeenSnapshot = false;
|
|
36
38
|
/** 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 */
|
|
37
39
|
this.lastMeta = false;
|
|
38
40
|
/** The parent class that instantiated the recorder */
|
|
39
41
|
this.parent = parent;
|
|
40
|
-
/** Config to inform to inline stylesheet contents (true default) */
|
|
41
|
-
this.shouldInlineStylesheets = (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay.inline_stylesheet');
|
|
42
42
|
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
43
|
-
this.shouldFix =
|
|
43
|
+
this.shouldFix = (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay.fix_stylesheets');
|
|
44
44
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
45
45
|
this.stopRecording = () => {/* no-op until set by rrweb initializer */};
|
|
46
46
|
}
|
|
@@ -82,13 +82,16 @@ class Recorder {
|
|
|
82
82
|
mask_input_options,
|
|
83
83
|
mask_text_selector,
|
|
84
84
|
mask_all_inputs,
|
|
85
|
-
inline_stylesheet,
|
|
86
85
|
inline_images,
|
|
87
86
|
collect_fonts
|
|
88
87
|
} = (0, _init.getConfigurationValue)(this.parent.agentIdentifier, 'session_replay');
|
|
89
88
|
const customMasker = (text, element) => {
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
try {
|
|
90
|
+
if (typeof element?.type === 'string' && element.type.toLowerCase() !== 'password' && (element?.dataset?.nrUnmask !== undefined || element?.classList?.contains('nr-unmask'))) return text;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// likely an element was passed to this handler that was invalid and was missing attributes or methods
|
|
93
|
+
}
|
|
94
|
+
return '*'.repeat(text?.length || 0);
|
|
92
95
|
};
|
|
93
96
|
// set up rrweb configurations for maximum privacy --
|
|
94
97
|
// https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
|
|
@@ -103,7 +106,7 @@ class Recorder {
|
|
|
103
106
|
maskTextFn: customMasker,
|
|
104
107
|
maskAllInputs: mask_all_inputs,
|
|
105
108
|
maskInputFn: customMasker,
|
|
106
|
-
inlineStylesheet:
|
|
109
|
+
inlineStylesheet: true,
|
|
107
110
|
inlineImages: inline_images,
|
|
108
111
|
collectFonts: collect_fonts,
|
|
109
112
|
checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode],
|
|
@@ -130,13 +133,17 @@ class Recorder {
|
|
|
130
133
|
* @param {*} isCheckout - Flag indicating if the payload was triggered as a checkout
|
|
131
134
|
*/
|
|
132
135
|
audit(event, isCheckout) {
|
|
133
|
-
/** only run the audit if inline_stylesheets is configured as on (default behavior) */
|
|
134
|
-
if (this.shouldInlineStylesheets === false || !this.shouldFix) {
|
|
135
|
-
this.currentBufferTarget.inlinedAllStylesheets = false;
|
|
136
|
-
return this.store(event, isCheckout);
|
|
137
|
-
}
|
|
138
136
|
/** An count of stylesheet objects that were blocked from accessing contents via JS */
|
|
139
137
|
const incompletes = _stylesheetEvaluator.stylesheetEvaluator.evaluate();
|
|
138
|
+
const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/';
|
|
139
|
+
/** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
|
|
140
|
+
if (!this.shouldFix) {
|
|
141
|
+
if (incompletes > 0) {
|
|
142
|
+
this.currentBufferTarget.inlinedAllStylesheets = false;
|
|
143
|
+
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Skipped', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
144
|
+
}
|
|
145
|
+
return this.store(event, isCheckout);
|
|
146
|
+
}
|
|
140
147
|
/** Only stop ignoring data if already ignoring and a new valid snapshap is taking place (0 incompletes and we get a meta node for the snap) */
|
|
141
148
|
if (!incompletes && this.#fixing && event.type === _constants.RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
|
|
142
149
|
if (incompletes > 0) {
|
|
@@ -146,8 +153,8 @@ class Recorder {
|
|
|
146
153
|
this.currentBufferTarget.inlinedAllStylesheets = false;
|
|
147
154
|
this.shouldFix = false;
|
|
148
155
|
}
|
|
149
|
-
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['
|
|
150
|
-
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['
|
|
156
|
+
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
157
|
+
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, [missingInlineSMTag + 'Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
151
158
|
this.takeFullSnapshot();
|
|
152
159
|
});
|
|
153
160
|
/** Only start ignoring data if got a faulty snapshot */
|
|
@@ -190,6 +197,7 @@ class Recorder {
|
|
|
190
197
|
// snapshot event
|
|
191
198
|
if (event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
192
199
|
this.currentBufferTarget.hasSnapshot = true;
|
|
200
|
+
this.hasSeenSnapshot = true;
|
|
193
201
|
}
|
|
194
202
|
this.currentBufferTarget.add(event);
|
|
195
203
|
|
|
@@ -8,7 +8,7 @@ var _nreum = require("../../../common/window/nreum");
|
|
|
8
8
|
var _runtime = require("../../../common/constants/runtime");
|
|
9
9
|
class StylesheetEvaluator {
|
|
10
10
|
#evaluated = new WeakSet();
|
|
11
|
-
#
|
|
11
|
+
#brokenSheets = [];
|
|
12
12
|
/**
|
|
13
13
|
* Flipped to true if stylesheets that cannot be natively inlined are detected by the stylesheetEvaluator class
|
|
14
14
|
* Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
|
|
@@ -22,6 +22,7 @@ class StylesheetEvaluator {
|
|
|
22
22
|
*/
|
|
23
23
|
evaluate() {
|
|
24
24
|
let incompletes = 0;
|
|
25
|
+
this.#brokenSheets = [];
|
|
25
26
|
if (_runtime.isBrowserScope) {
|
|
26
27
|
for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
|
|
27
28
|
if (!this.#evaluated.has(document.styleSheets[i])) {
|
|
@@ -32,7 +33,7 @@ class StylesheetEvaluator {
|
|
|
32
33
|
} catch (err) {
|
|
33
34
|
if (!document.styleSheets[i].href) return;
|
|
34
35
|
incompletes++;
|
|
35
|
-
this.#
|
|
36
|
+
this.#brokenSheets.push(document.styleSheets[i]);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -46,8 +47,8 @@ class StylesheetEvaluator {
|
|
|
46
47
|
* @returns {Promise}
|
|
47
48
|
*/
|
|
48
49
|
async fix() {
|
|
49
|
-
await Promise.all(this.#
|
|
50
|
-
this.#
|
|
50
|
+
await Promise.all(this.#brokenSheets.map(sheet => this.#fetchAndOverride(sheet)));
|
|
51
|
+
this.#brokenSheets = [];
|
|
51
52
|
const failedToFix = this.failedToFix;
|
|
52
53
|
this.failedToFix = 0;
|
|
53
54
|
return failedToFix;
|
|
@@ -17,6 +17,7 @@ var _drain = require("../../../common/drain/drain");
|
|
|
17
17
|
var _runtime2 = require("../../../common/constants/runtime");
|
|
18
18
|
var _constants2 = require("../../../common/session/constants");
|
|
19
19
|
var _traverse = require("../../../common/util/traverse");
|
|
20
|
+
var _cleanUrl = require("../../../common/url/clean-url");
|
|
20
21
|
const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
21
22
|
/** Reserved room for query param attrs */
|
|
22
23
|
const QUERY_PARAM_PADDING = 5000;
|
|
@@ -156,7 +157,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
156
157
|
type: 'BrowserSessionChunk',
|
|
157
158
|
app_id: this.agentInfo.applicationID,
|
|
158
159
|
protocol_version: '0',
|
|
159
|
-
timestamp: Math.floor(this.timeKeeper.
|
|
160
|
+
timestamp: Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
|
|
160
161
|
attributes: (0, _encode.obj)({
|
|
161
162
|
...(agentMetadata.entityGuid && {
|
|
162
163
|
entityGuid: agentMetadata.entityGuid
|
|
@@ -165,8 +166,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
165
166
|
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
166
167
|
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
167
168
|
// trace payload metadata
|
|
168
|
-
'trace.firstTimestamp': Math.floor(this.timeKeeper.
|
|
169
|
-
'trace.lastTimestamp': Math.floor(this.timeKeeper.
|
|
169
|
+
'trace.firstTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
|
|
170
|
+
'trace.lastTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(latestTimeStamp)),
|
|
170
171
|
'trace.nodes': stns.length,
|
|
171
172
|
'trace.originTimestamp': this.timeKeeper.correctedOriginTime,
|
|
172
173
|
// other payload metadata
|
|
@@ -182,7 +183,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
182
183
|
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
183
184
|
...(endUserId && {
|
|
184
185
|
'enduser.id': this.obfuscator.obfuscateString(endUserId)
|
|
185
|
-
})
|
|
186
|
+
}),
|
|
187
|
+
currentUrl: this.obfuscator.obfuscateString((0, _cleanUrl.cleanURL)('' + location))
|
|
186
188
|
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
187
189
|
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
188
190
|
},
|
|
@@ -63,7 +63,8 @@ class EventBuffer {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Adds an event object to the buffer while tallying size
|
|
66
|
+
* Adds an event object to the buffer while tallying size. Only adds the event if it is valid
|
|
67
|
+
* and would not make the event buffer exceed the maxPayloadSize.
|
|
67
68
|
* @param {Object} event the event object to add to the buffer
|
|
68
69
|
* @returns {EventBuffer} returns the event buffer for chaining
|
|
69
70
|
*/
|
|
@@ -15,6 +15,7 @@ var _instrument7 = require("../features/spa/instrument");
|
|
|
15
15
|
var _instrument8 = require("../features/session_replay/instrument");
|
|
16
16
|
var _instrument9 = require("../features/generic_events/instrument");
|
|
17
17
|
var _instrument10 = require("../features/logging/instrument");
|
|
18
|
+
var _instrument11 = require("../features/soft_navigations/instrument");
|
|
18
19
|
/**
|
|
19
20
|
* An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
|
|
20
21
|
* The BrowserAgent class is the most convenient and reliable option for most use cases.
|
|
@@ -23,7 +24,7 @@ class BrowserAgent extends _agent.Agent {
|
|
|
23
24
|
constructor(args) {
|
|
24
25
|
super({
|
|
25
26
|
...args,
|
|
26
|
-
features: [_instrument5.Instrument, _instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument3.Instrument, _instrument4.Instrument, _instrument7.Instrument, _instrument8.Instrument, _instrument9.Instrument, _instrument10.Instrument],
|
|
27
|
+
features: [_instrument5.Instrument, _instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument3.Instrument, _instrument4.Instrument, _instrument7.Instrument, _instrument11.Instrument, _instrument8.Instrument, _instrument9.Instrument, _instrument10.Instrument],
|
|
27
28
|
loaderType: 'browser-agent'
|
|
28
29
|
});
|
|
29
30
|
}
|
|
@@ -72,6 +72,9 @@ const model = () => {
|
|
|
72
72
|
page_action: {
|
|
73
73
|
enabled: true
|
|
74
74
|
},
|
|
75
|
+
user_actions: {
|
|
76
|
+
enabled: true
|
|
77
|
+
},
|
|
75
78
|
page_view_event: {
|
|
76
79
|
enabled: true,
|
|
77
80
|
autoStart: true
|
|
@@ -109,10 +112,8 @@ const model = () => {
|
|
|
109
112
|
// serialize fonts for collection without public asset url, this is currently broken in RRWeb -- https://github.com/rrweb-io/rrweb/issues/1304. When fixed, revisit with test cases
|
|
110
113
|
inline_images: false,
|
|
111
114
|
// serialize images for collection without public asset url -- right now this is only useful for testing as it easily generates payloads too large to be harvested
|
|
112
|
-
inline_stylesheet: true,
|
|
113
|
-
// serialize css for collection without public asset url
|
|
114
115
|
fix_stylesheets: true,
|
|
115
|
-
// fetch missing stylesheet resources for inlining
|
|
116
|
+
// fetch missing stylesheet resources for inlining
|
|
116
117
|
// recording config settings
|
|
117
118
|
mask_all_inputs: true,
|
|
118
119
|
// this has a getter/setter to facilitate validation of the selectors
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a CSS selector path for the given element, if possible
|
|
3
|
+
* @param {HTMLElement} elem
|
|
4
|
+
* @param {boolean} includeId
|
|
5
|
+
* @param {boolean} includeClass
|
|
6
|
+
* @returns {string|undefined}
|
|
7
|
+
*/
|
|
8
|
+
export const generateSelectorPath = elem => {
|
|
9
|
+
if (!elem) return;
|
|
10
|
+
const getNthOfTypeIndex = node => {
|
|
11
|
+
try {
|
|
12
|
+
let i = 1;
|
|
13
|
+
const {
|
|
14
|
+
tagName
|
|
15
|
+
} = node;
|
|
16
|
+
while (node.previousElementSibling) {
|
|
17
|
+
if (node.previousElementSibling.tagName === tagName) i++;
|
|
18
|
+
node = node.previousElementSibling;
|
|
19
|
+
}
|
|
20
|
+
return i;
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// do nothing for now. An invalid child count will make the path selector not return a nth-of-type selector statement
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
let pathSelector = '';
|
|
26
|
+
let index = getNthOfTypeIndex(elem);
|
|
27
|
+
try {
|
|
28
|
+
while (elem?.tagName) {
|
|
29
|
+
const {
|
|
30
|
+
id,
|
|
31
|
+
localName
|
|
32
|
+
} = elem;
|
|
33
|
+
const selector = [localName, id ? "#".concat(id) : '', pathSelector ? ">".concat(pathSelector) : ''].join('');
|
|
34
|
+
pathSelector = selector;
|
|
35
|
+
elem = elem.parentNode;
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// do nothing for now
|
|
39
|
+
}
|
|
40
|
+
return pathSelector ? index ? "".concat(pathSelector, ":nth-of-type(").concat(index, ")") : pathSelector : undefined;
|
|
41
|
+
};
|
|
@@ -1,32 +1,9 @@
|
|
|
1
|
-
import { globalScope } from '../constants/runtime';
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#safely_detecting_option_support
|
|
5
|
-
*/
|
|
6
|
-
let passiveSupported = false;
|
|
7
|
-
let signalSupported = false;
|
|
8
|
-
try {
|
|
9
|
-
const options = {
|
|
10
|
-
get passive() {
|
|
11
|
-
// this function will be called when the browser attempts to access the passive property
|
|
12
|
-
passiveSupported = true;
|
|
13
|
-
return false;
|
|
14
|
-
},
|
|
15
|
-
get signal() {
|
|
16
|
-
signalSupported = true;
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
globalScope.addEventListener('test', null, options);
|
|
21
|
-
globalScope.removeEventListener('test', null, options);
|
|
22
|
-
} catch (err) {}
|
|
23
1
|
export function eventListenerOpts(useCapture, abortSignal) {
|
|
24
|
-
return
|
|
25
|
-
capture:
|
|
26
|
-
passive:
|
|
27
|
-
// passive defaults to false
|
|
2
|
+
return {
|
|
3
|
+
capture: useCapture,
|
|
4
|
+
passive: false,
|
|
28
5
|
signal: abortSignal
|
|
29
|
-
}
|
|
6
|
+
};
|
|
30
7
|
}
|
|
31
8
|
|
|
32
9
|
/** Do not use this within the worker context. */
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { originTime } from '../constants/runtime';
|
|
2
2
|
import { getRuntime } from '../config/runtime';
|
|
3
|
-
const rfc2616Regex = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0-3][0-9]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) ([01][0-9]|2[0-3])(:[0-5][0-9]){2} GMT$/;
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Class used to adjust the timestamp of harvested data to New Relic server time. This
|
|
@@ -52,26 +51,21 @@ export class TimeKeeper {
|
|
|
52
51
|
* @param rumRequest {XMLHttpRequest} The xhr for the rum request
|
|
53
52
|
* @param startTime {number} The start time of the RUM request
|
|
54
53
|
* @param endTime {number} The end time of the RUM request
|
|
54
|
+
* @param nrServerTime {number} the unix number value of the NR server time in MS, returned in the RUM request body
|
|
55
55
|
*/
|
|
56
|
-
processRumRequest(rumRequest, startTime, endTime) {
|
|
56
|
+
processRumRequest(rumRequest, startTime, endTime, nrServerTime) {
|
|
57
57
|
this.processStoredDiff(); // Check session entity for stored time diff
|
|
58
58
|
if (this.#ready) return; // Server time calculated from session entity
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
if (!responseDateHeader) {
|
|
62
|
-
throw new Error('Missing date header on rum response.');
|
|
63
|
-
}
|
|
64
|
-
if (!rfc2616Regex.test(responseDateHeader)) {
|
|
65
|
-
throw new Error('Date header invalid format.');
|
|
66
|
-
}
|
|
60
|
+
if (!nrServerTime) throw new Error('nrServerTime not found');
|
|
67
61
|
const medianRumOffset = (endTime - startTime) / 2;
|
|
68
62
|
const serverOffset = startTime + medianRumOffset;
|
|
69
63
|
|
|
70
64
|
// Corrected page origin time
|
|
71
|
-
this.#correctedOriginTime = Math.floor(
|
|
65
|
+
this.#correctedOriginTime = Math.floor(nrServerTime - serverOffset);
|
|
72
66
|
this.#localTimeDiff = originTime - this.#correctedOriginTime;
|
|
73
67
|
if (isNaN(this.#correctedOriginTime)) {
|
|
74
|
-
throw new Error('
|
|
68
|
+
throw new Error('Failed to correct browser time to server time');
|
|
75
69
|
}
|
|
76
70
|
this.#session?.write({
|
|
77
71
|
serverTimeDiff: this.#localTimeDiff
|
|
@@ -108,6 +102,15 @@ export class TimeKeeper {
|
|
|
108
102
|
return timestamp - this.#localTimeDiff;
|
|
109
103
|
}
|
|
110
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Corrects relative timestamp to NR server time (epoch).
|
|
107
|
+
* @param {DOMHighResTimeStamp} relativeTime
|
|
108
|
+
* @returns {number}
|
|
109
|
+
*/
|
|
110
|
+
correctRelativeTimestamp(relativeTime) {
|
|
111
|
+
return this.correctAbsoluteTimestamp(this.convertRelativeTimestamp(relativeTime));
|
|
112
|
+
}
|
|
113
|
+
|
|
111
114
|
/** Process the session entity and use the info to set the main time calculations if present */
|
|
112
115
|
processStoredDiff() {
|
|
113
116
|
if (this.#ready) return; // Time diff has already been calculated
|
|
@@ -31,12 +31,14 @@ const getCircularReplacer = () => {
|
|
|
31
31
|
*/
|
|
32
32
|
export function stringify(val) {
|
|
33
33
|
try {
|
|
34
|
-
return JSON.stringify(val, getCircularReplacer());
|
|
34
|
+
return JSON.stringify(val, getCircularReplacer()) ?? '';
|
|
35
35
|
} catch (e) {
|
|
36
36
|
try {
|
|
37
37
|
ee.emit('internal-error', [e]);
|
|
38
38
|
} catch (err) {
|
|
39
39
|
// do nothing
|
|
40
40
|
}
|
|
41
|
+
// return a string so that downstream users of the method do not throw errors
|
|
42
|
+
return '';
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -12,9 +12,17 @@ if (isBrowserScope) {
|
|
|
12
12
|
}) => {
|
|
13
13
|
const attrs = {
|
|
14
14
|
metricId: id,
|
|
15
|
-
eventTarget: attribution.
|
|
16
|
-
|
|
17
|
-
eventTime: attribution.
|
|
15
|
+
eventTarget: attribution.interactionTarget,
|
|
16
|
+
// event* attrs deprecated in v4, kept for NR backwards compatibility
|
|
17
|
+
eventTime: attribution.interactionTime,
|
|
18
|
+
// event* attrs deprecated in v4, kept for NR backwards compatibility
|
|
19
|
+
interactionTarget: attribution.interactionTarget,
|
|
20
|
+
interactionTime: attribution.interactionTime,
|
|
21
|
+
interactionType: attribution.interactionType,
|
|
22
|
+
inputDelay: attribution.inputDelay,
|
|
23
|
+
nextPaintTime: attribution.nextPaintTime,
|
|
24
|
+
processingDuration: attribution.processingDuration,
|
|
25
|
+
presentationDelay: attribution.presentationDelay,
|
|
18
26
|
loadState: attribution.loadState
|
|
19
27
|
};
|
|
20
28
|
interactionToNextPaint.update({
|
|
@@ -20,7 +20,9 @@ if (isBrowserScope) {
|
|
|
20
20
|
element: attribution.element,
|
|
21
21
|
timeToFirstByte: attribution.timeToFirstByte,
|
|
22
22
|
resourceLoadDelay: attribution.resourceLoadDelay,
|
|
23
|
-
|
|
23
|
+
resourceLoadDuration: attribution.resourceLoadDuration,
|
|
24
|
+
resourceLoadTime: attribution.resourceLoadDuration,
|
|
25
|
+
// kept for NR backwards compatibility, deprecated in v3->v4
|
|
24
26
|
elementRenderDelay: attribution.elementRenderDelay
|
|
25
27
|
};
|
|
26
28
|
if (attribution.url) attrs.elUrl = cleanURL(attribution.url);
|