@newrelic/browser-agent 1.251.0 → 1.252.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 +25 -0
- package/dist/cjs/common/config/state/init.js +2 -2
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/drain/drain.js +3 -1
- package/dist/cjs/common/event-emitter/contextual-ee.js +7 -1
- package/dist/cjs/common/harvest/harvest-scheduler.js +2 -1
- package/dist/cjs/common/harvest/harvest.js +3 -2
- package/dist/cjs/features/ajax/instrument/index.js +2 -0
- package/dist/cjs/features/jserrors/instrument/index.js +5 -0
- package/dist/cjs/features/metrics/aggregate/index.js +1 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +53 -17
- package/dist/cjs/features/session_replay/shared/recorder.js +9 -4
- package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +33 -28
- package/dist/cjs/features/utils/instrument-base.js +1 -1
- package/dist/cjs/loaders/api/api.js +4 -1
- package/dist/esm/common/config/state/init.js +2 -2
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/drain/drain.js +3 -1
- package/dist/esm/common/event-emitter/contextual-ee.js +7 -1
- package/dist/esm/common/harvest/harvest-scheduler.js +2 -1
- package/dist/esm/common/harvest/harvest.js +3 -2
- package/dist/esm/features/ajax/instrument/index.js +2 -0
- package/dist/esm/features/jserrors/instrument/index.js +5 -0
- package/dist/esm/features/metrics/aggregate/index.js +1 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +53 -17
- package/dist/esm/features/session_replay/shared/recorder.js +9 -4
- package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +33 -28
- package/dist/esm/features/utils/instrument-base.js +1 -1
- package/dist/esm/loaders/api/api.js +4 -1
- package/dist/types/common/drain/drain.d.ts +2 -1
- package/dist/types/common/drain/drain.d.ts.map +1 -1
- package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +8 -3
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts +1 -1
- package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/common/config/state/init.js +2 -2
- package/src/common/drain/drain.js +3 -2
- package/src/common/event-emitter/contextual-ee.js +7 -1
- package/src/common/harvest/harvest-scheduler.js +1 -1
- package/src/common/harvest/harvest.js +3 -2
- package/src/features/ajax/instrument/index.js +2 -0
- package/src/features/jserrors/instrument/index.js +6 -0
- package/src/features/metrics/aggregate/index.js +1 -1
- package/src/features/page_view_event/aggregate/index.js +1 -1
- package/src/features/session_replay/aggregate/index.js +47 -19
- package/src/features/session_replay/shared/recorder.js +9 -4
- package/src/features/session_replay/shared/stylesheet-evaluator.js +26 -21
- package/src/features/utils/instrument-base.js +1 -1
- package/src/loaders/api/api.js +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.252.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.1...v1.252.0) (2024-02-12)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add types mappings for esm distribution ([#887](https://github.com/newrelic/newrelic-browser-agent/issues/887)) ([811ed41](https://github.com/newrelic/newrelic-browser-agent/commit/811ed418b74dcb8f25544da79521c384b9fd498a))
|
|
12
|
+
* align browser reported uncaught syntax errors ([#881](https://github.com/newrelic/newrelic-browser-agent/issues/881)) ([d4a0f30](https://github.com/newrelic/newrelic-browser-agent/commit/d4a0f30e0ab4d8edbdb17bf4ebdf282626761045))
|
|
13
|
+
* Capture Internal Metrics for Session Replay Configurations ([#879](https://github.com/newrelic/newrelic-browser-agent/issues/879)) ([f60e7f1](https://github.com/newrelic/newrelic-browser-agent/commit/f60e7f155bb95087ea4af8864b652878f08ccaff))
|
|
14
|
+
* Create more granular metrics about stylesheet fix success ([#882](https://github.com/newrelic/newrelic-browser-agent/issues/882)) ([697f13e](https://github.com/newrelic/newrelic-browser-agent/commit/697f13e6ea5ba0738ffd74dfd214751ab98adf8d))
|
|
15
|
+
* Report config changes away from default state for UX improvement ([#885](https://github.com/newrelic/newrelic-browser-agent/issues/885)) ([aa19a9c](https://github.com/newrelic/newrelic-browser-agent/commit/aa19a9c0737c175c011656f3da3f327dc6442f04))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* Add safe logic to snapshots ([#884](https://github.com/newrelic/newrelic-browser-agent/issues/884)) ([1fcdd8d](https://github.com/newrelic/newrelic-browser-agent/commit/1fcdd8d9a20819911ba7e7350354085a57f1b187))
|
|
21
|
+
* Fix adblock memory leak ([#877](https://github.com/newrelic/newrelic-browser-agent/issues/877)) ([695415b](https://github.com/newrelic/newrelic-browser-agent/commit/695415b0fcaa8b41496fc6556a38ec76dd357539))
|
|
22
|
+
|
|
23
|
+
## [1.251.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.251.0...v1.251.1) (2024-01-29)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* Fix deferred Session Replay payloads ([#868](https://github.com/newrelic/newrelic-browser-agent/issues/868)) ([f69e4b0](https://github.com/newrelic/newrelic-browser-agent/commit/f69e4b0eba5a54f4e67316f5e6a30090cf7360cc))
|
|
29
|
+
* Pass unload options to simultaneous harvests in Session Replay ([#870](https://github.com/newrelic/newrelic-browser-agent/issues/870)) ([655aa5d](https://github.com/newrelic/newrelic-browser-agent/commit/655aa5d261d03f71086d3cfc73cb72db51cb28c7))
|
|
30
|
+
|
|
6
31
|
## [1.251.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.250.0...v1.251.0) (2024-01-24)
|
|
7
32
|
|
|
8
33
|
|
|
@@ -106,9 +106,9 @@ const model = () => {
|
|
|
106
106
|
autoStart: true,
|
|
107
107
|
enabled: false,
|
|
108
108
|
harvestTimeSeconds: 60,
|
|
109
|
-
sampling_rate:
|
|
109
|
+
sampling_rate: 10,
|
|
110
110
|
// float from 0 - 100
|
|
111
|
-
error_sampling_rate:
|
|
111
|
+
error_sampling_rate: 100,
|
|
112
112
|
// float from 0 - 100
|
|
113
113
|
collect_fonts: false,
|
|
114
114
|
// 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
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.
|
|
15
|
+
const VERSION = exports.VERSION = "1.252.0";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
|
|
|
12
12
|
/**
|
|
13
13
|
* Exposes the version of the agent
|
|
14
14
|
*/
|
|
15
|
-
const VERSION = exports.VERSION = "1.
|
|
15
|
+
const VERSION = exports.VERSION = "1.252.0";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Exposes the build type of the agent
|
|
@@ -50,15 +50,17 @@ function curateRegistry(agentIdentifier) {
|
|
|
50
50
|
* its own named group explicitly, when ready.
|
|
51
51
|
* @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
|
|
52
52
|
* @param {string} featureName - A named group into which the feature's buffered events are bucketed.
|
|
53
|
+
* @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
|
|
53
54
|
*/
|
|
54
55
|
function drain() {
|
|
55
56
|
let agentIdentifier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
56
57
|
let featureName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'feature';
|
|
58
|
+
let force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
57
59
|
curateRegistry(agentIdentifier);
|
|
58
60
|
// If the feature for the specified agent is not in the registry, that means the instrument file was bypassed.
|
|
59
61
|
// This could happen in tests, or loaders that directly import the aggregator. In these cases it is safe to
|
|
60
62
|
// drain the feature group immediately rather than waiting to drain all at once.
|
|
61
|
-
if (!agentIdentifier || !registry[agentIdentifier].get(featureName)) return drainGroup(featureName);
|
|
63
|
+
if (!agentIdentifier || !registry[agentIdentifier].get(featureName) || force) return drainGroup(featureName);
|
|
62
64
|
|
|
63
65
|
// When `drain` is called, this feature is ready to drain (staged).
|
|
64
66
|
registry[agentIdentifier].get(featureName).staged = true;
|
|
@@ -135,5 +135,11 @@ function ee(old, debugId) {
|
|
|
135
135
|
}
|
|
136
136
|
function abort() {
|
|
137
137
|
globalInstance.aborted = true;
|
|
138
|
-
|
|
138
|
+
// The global backlog can be referenced directly by other emitters,
|
|
139
|
+
// so we need to delete its contents as opposed to replacing it.
|
|
140
|
+
// Otherwise, these references to the old backlog would still exist
|
|
141
|
+
// and the keys will not be garbage collected.
|
|
142
|
+
Object.keys(globalInstance.backlog).forEach(key => {
|
|
143
|
+
delete globalInstance.backlog[key];
|
|
144
|
+
});
|
|
139
145
|
}
|
|
@@ -109,7 +109,8 @@ class HarvestScheduler extends _sharedContext.SharedContext {
|
|
|
109
109
|
if (!submitMethod) return false;
|
|
110
110
|
const retry = !opts?.unload && submitMethod === submitData.xhr;
|
|
111
111
|
payload = this.opts.getPayload({
|
|
112
|
-
retry
|
|
112
|
+
retry,
|
|
113
|
+
opts
|
|
113
114
|
});
|
|
114
115
|
if (!payload) {
|
|
115
116
|
if (this.started) {
|
|
@@ -180,10 +180,11 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
180
180
|
});
|
|
181
181
|
if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
|
|
182
182
|
const harvestScope = this;
|
|
183
|
-
result.addEventListener('
|
|
183
|
+
result.addEventListener('loadend', function () {
|
|
184
184
|
// `this` refers to the XHR object in this scope, do not change this to a fat arrow
|
|
185
|
+
// status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
|
|
185
186
|
const cbResult = {
|
|
186
|
-
sent:
|
|
187
|
+
sent: this.status !== 0,
|
|
187
188
|
status: this.status
|
|
188
189
|
};
|
|
189
190
|
if (this.status === 429) {
|
|
@@ -18,6 +18,7 @@ var _responseSize = require("./response-size");
|
|
|
18
18
|
var _instrumentBase = require("../../utils/instrument-base");
|
|
19
19
|
var _constants = require("../constants");
|
|
20
20
|
var _features = require("../../../loaders/features/features");
|
|
21
|
+
var _constants2 = require("../../metrics/constants");
|
|
21
22
|
/*
|
|
22
23
|
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
23
24
|
* SPDX-License-Identifier: Apache-2.0
|
|
@@ -363,6 +364,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
|
|
|
363
364
|
if (ctx.sameOrigin) {
|
|
364
365
|
var header = xhr.getResponseHeader('X-NewRelic-App-Data');
|
|
365
366
|
if (header) {
|
|
367
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, _features.FEATURE_NAMES.metrics, ee);
|
|
366
368
|
ctx.params.cat = header.split(', ').pop();
|
|
367
369
|
}
|
|
368
370
|
}
|
|
@@ -117,6 +117,11 @@ class Instrument extends _instrumentBase.InstrumentBase {
|
|
|
117
117
|
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
118
118
|
*/
|
|
119
119
|
#castErrorEvent(errorEvent) {
|
|
120
|
+
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
121
|
+
const error = new _uncaughtError.UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
122
|
+
error.name = SyntaxError.name;
|
|
123
|
+
return error;
|
|
124
|
+
}
|
|
120
125
|
if (errorEvent.error instanceof Error) {
|
|
121
126
|
return errorEvent.error;
|
|
122
127
|
}
|
|
@@ -113,7 +113,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
113
113
|
|
|
114
114
|
// [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
|
|
115
115
|
(0, _eventListenerOpts.windowAddEventListener)('pageshow', evt => {
|
|
116
|
-
if (evt
|
|
116
|
+
if (evt?.persisted) {
|
|
117
117
|
this.storeSupportabilityMetrics('Generic/BFCache/PageRestored');
|
|
118
118
|
}
|
|
119
119
|
});
|
|
@@ -127,7 +127,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
127
127
|
status,
|
|
128
128
|
responseText
|
|
129
129
|
} = _ref3;
|
|
130
|
-
if (status >= 400) {
|
|
130
|
+
if (status >= 400 || status === 0) {
|
|
131
131
|
// Adding retry logic for the rum call will be a separate change
|
|
132
132
|
this.ee.abort();
|
|
133
133
|
return;
|
|
@@ -32,7 +32,6 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
|
|
|
32
32
|
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
|
|
33
33
|
* functionality is validated and a full user experience is curated.
|
|
34
34
|
*/
|
|
35
|
-
let gzipper, u8;
|
|
36
35
|
class Aggregate extends _aggregateBase.AggregateBase {
|
|
37
36
|
static featureName = _constants.FEATURE_NAME;
|
|
38
37
|
// pass the recorder into the aggregator
|
|
@@ -44,8 +43,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
44
43
|
this.initialized = false;
|
|
45
44
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
46
45
|
this.blocked = false;
|
|
47
|
-
/**
|
|
48
|
-
this.
|
|
46
|
+
/** populated with the gzipper lib async */
|
|
47
|
+
this.gzipper = undefined;
|
|
48
|
+
/** populated with the u8 string lib async */
|
|
49
|
+
this.u8 = undefined;
|
|
49
50
|
/** the mode to start in. Defaults to off */
|
|
50
51
|
const {
|
|
51
52
|
session
|
|
@@ -56,6 +57,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
56
57
|
this.entitled = false;
|
|
57
58
|
this.recorder = args?.recorder;
|
|
58
59
|
if (this.recorder) this.recorder.parent = this;
|
|
60
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
59
61
|
const shouldSetup = (0, _config.getConfigurationValue)(agentIdentifier, 'privacy.cookies_enabled') === true && (0, _config.getConfigurationValue)(agentIdentifier, 'session_trace.enabled') === true;
|
|
60
62
|
if (shouldSetup) {
|
|
61
63
|
// 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.
|
|
@@ -92,6 +94,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
92
94
|
getPayload: this.prepareHarvest.bind(this),
|
|
93
95
|
raw: true
|
|
94
96
|
}, this);
|
|
97
|
+
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
98
|
+
this.prepUtils().then(() => {
|
|
99
|
+
this.scheduler.runHarvest();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
95
102
|
(0, _registerHandler.registerHandler)('recordReplay', () => {
|
|
96
103
|
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
97
104
|
if (this.blocked || !this.entitled) return;
|
|
@@ -115,13 +122,37 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
115
122
|
this.switchToFull();
|
|
116
123
|
}
|
|
117
124
|
}, this.featureName, this.ee);
|
|
125
|
+
const {
|
|
126
|
+
error_sampling_rate,
|
|
127
|
+
sampling_rate,
|
|
128
|
+
autoStart,
|
|
129
|
+
block_selector,
|
|
130
|
+
mask_text_selector,
|
|
131
|
+
mask_all_inputs,
|
|
132
|
+
inline_stylesheet,
|
|
133
|
+
inline_images,
|
|
134
|
+
collect_fonts
|
|
135
|
+
} = (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay');
|
|
118
136
|
this.waitForFlags(['sr']).then(_ref => {
|
|
119
137
|
let [flagOn] = _ref;
|
|
120
138
|
this.entitled = flagOn;
|
|
121
|
-
if (!this.entitled && this.recorder?.recording)
|
|
122
|
-
|
|
139
|
+
if (!this.entitled && this.recorder?.recording) {
|
|
140
|
+
this.recorder.abort(_constants.ABORT_REASONS.ENTITLEMENTS);
|
|
141
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
142
|
+
}
|
|
143
|
+
this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
|
|
123
144
|
}).then(() => _sharedChannel.sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
|
|
124
145
|
|
|
146
|
+
/** 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 */
|
|
147
|
+
if (!autoStart) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
148
|
+
if (collect_fonts === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
149
|
+
if (inline_stylesheet !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
150
|
+
if (inline_images === true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
151
|
+
if (mask_all_inputs !== true) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
152
|
+
if (block_selector !== '[data-nr-block]') (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
153
|
+
if (mask_text_selector !== '*') (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
154
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
155
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
125
156
|
this.drain();
|
|
126
157
|
}
|
|
127
158
|
}
|
|
@@ -196,24 +227,29 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
196
227
|
// We only report (harvest) in FULL mode
|
|
197
228
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
198
229
|
}
|
|
230
|
+
await this.prepUtils();
|
|
231
|
+
if (!this.recorder.recording) this.recorder.startRecording();
|
|
232
|
+
this.syncWithSessionManager({
|
|
233
|
+
sessionReplayMode: this.mode
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async prepUtils() {
|
|
199
237
|
try {
|
|
200
238
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
201
239
|
const {
|
|
202
240
|
gzipSync,
|
|
203
241
|
strToU8
|
|
204
242
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "compressor" */'fflate')));
|
|
205
|
-
gzipper = gzipSync;
|
|
206
|
-
u8 = strToU8;
|
|
243
|
+
this.gzipper = gzipSync;
|
|
244
|
+
this.u8 = strToU8;
|
|
207
245
|
} catch (err) {
|
|
208
246
|
// compressor failed to load, but we can still record without compression as a last ditch effort
|
|
209
|
-
this.shouldCompress = false;
|
|
210
247
|
}
|
|
211
|
-
if (!this.recorder.recording) this.recorder.startRecording();
|
|
212
|
-
this.syncWithSessionManager({
|
|
213
|
-
sessionReplayMode: this.mode
|
|
214
|
-
});
|
|
215
248
|
}
|
|
216
249
|
prepareHarvest() {
|
|
250
|
+
let {
|
|
251
|
+
opts
|
|
252
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
217
253
|
if (!this.recorder) return;
|
|
218
254
|
const recorderEvents = this.recorder.getEvents();
|
|
219
255
|
// get the event type and use that to trigger another harvest if needed
|
|
@@ -224,8 +260,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
224
260
|
return;
|
|
225
261
|
}
|
|
226
262
|
let len = 0;
|
|
227
|
-
if (this.
|
|
228
|
-
payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
263
|
+
if (!!this.gzipper && !!this.u8) {
|
|
264
|
+
payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
229
265
|
len = payload.body.length;
|
|
230
266
|
this.scheduler.opts.gzip = true;
|
|
231
267
|
} else {
|
|
@@ -251,7 +287,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
251
287
|
sessionReplaySentFirstChunk: true
|
|
252
288
|
});
|
|
253
289
|
this.recorder.clearBuffer();
|
|
254
|
-
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
|
|
290
|
+
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
|
|
255
291
|
return [payload];
|
|
256
292
|
}
|
|
257
293
|
getHarvestContents(recorderEvents) {
|
|
@@ -282,7 +318,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
282
318
|
const relativeNow = (0, _now.now)();
|
|
283
319
|
const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
|
|
284
320
|
const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
|
|
285
|
-
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
|
|
321
|
+
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
|
|
286
322
|
const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
|
|
287
323
|
return {
|
|
288
324
|
qs: {
|
|
@@ -293,7 +329,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
293
329
|
attributes: (0, _encode.obj)({
|
|
294
330
|
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
295
331
|
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
296
|
-
...(this.
|
|
332
|
+
...(!!this.gzipper && !!this.u8 && {
|
|
297
333
|
content_encoding: 'gzip'
|
|
298
334
|
}),
|
|
299
335
|
'replay.firstTimestamp': firstTimestamp,
|
|
@@ -116,13 +116,13 @@ class Recorder {
|
|
|
116
116
|
/** 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) */
|
|
117
117
|
if (!incompletes && this.#fixing && event.type === _constants.RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
|
|
118
118
|
if (incompletes) {
|
|
119
|
-
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css', incompletes], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
120
119
|
/** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
|
|
121
120
|
_stylesheetEvaluator.stylesheetEvaluator.fix().then(failedToFix => {
|
|
122
121
|
if (failedToFix) {
|
|
123
122
|
this.currentBufferTarget.inlinedAllStylesheets = false;
|
|
124
123
|
this.shouldFix = false;
|
|
125
|
-
|
|
124
|
+
(0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
125
|
+
} else (0, _handle.handle)(_constants3.SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, _features.FEATURE_NAMES.metrics, this.parent.ee);
|
|
126
126
|
this.takeFullSnapshot();
|
|
127
127
|
});
|
|
128
128
|
/** Only start ignoring data if got a faulty snapshot */
|
|
@@ -176,7 +176,12 @@ class Recorder {
|
|
|
176
176
|
|
|
177
177
|
/** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
|
|
178
178
|
takeFullSnapshot() {
|
|
179
|
-
|
|
179
|
+
try {
|
|
180
|
+
if (!this.recording) return;
|
|
181
|
+
_rrweb.record.takeFullSnapshot();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
// in the off chance we think we are recording, but rrweb does not, rrweb's lib will throw an error. This catch is just a precaution
|
|
184
|
+
}
|
|
180
185
|
}
|
|
181
186
|
clearTimestamps() {
|
|
182
187
|
this.currentBufferTarget.cycleTimestamp = undefined;
|
|
@@ -194,7 +199,7 @@ class Recorder {
|
|
|
194
199
|
* https://staging.onenr.io/037jbJWxbjy
|
|
195
200
|
* */
|
|
196
201
|
estimateCompression(data) {
|
|
197
|
-
if (this.
|
|
202
|
+
if (!!this.parent.gzipper && !!this.parent.u8) return data * _constants.AVG_COMPRESSION;
|
|
198
203
|
return data;
|
|
199
204
|
}
|
|
200
205
|
}
|
|
@@ -14,7 +14,7 @@ class StylesheetEvaluator {
|
|
|
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
|
|
15
15
|
* */
|
|
16
16
|
invalidStylesheetsDetected = false;
|
|
17
|
-
failedToFix =
|
|
17
|
+
failedToFix = 0;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* this works by checking (only ever once) each cssRules obj in the style sheets array. The try/catch will catch an error if the cssRules obj blocks access, triggering the module to try to "fix" the asset`. Returns the count of incomplete assets discovered.
|
|
@@ -49,7 +49,7 @@ class StylesheetEvaluator {
|
|
|
49
49
|
await Promise.all(this.#fetchProms);
|
|
50
50
|
this.#fetchProms = [];
|
|
51
51
|
const failedToFix = this.failedToFix;
|
|
52
|
-
this.failedToFix =
|
|
52
|
+
this.failedToFix = 0;
|
|
53
53
|
return failedToFix;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -60,34 +60,39 @@ class StylesheetEvaluator {
|
|
|
60
60
|
* @returns {Promise}
|
|
61
61
|
*/
|
|
62
62
|
async #fetchAndOverride(target, href) {
|
|
63
|
-
const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
|
|
64
|
-
if (!stylesheetContents.ok) {
|
|
65
|
-
this.failedToFix = true;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const stylesheetText = await stylesheetContents.text();
|
|
69
63
|
try {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
const stylesheetContents = await _config.originals.FETCH.bind(window)(href);
|
|
65
|
+
if (!stylesheetContents.ok) {
|
|
66
|
+
this.failedToFix++;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const stylesheetText = await stylesheetContents.text();
|
|
70
|
+
try {
|
|
71
|
+
const cssSheet = new CSSStyleSheet();
|
|
72
|
+
await cssSheet.replace(stylesheetText);
|
|
73
|
+
Object.defineProperty(target, 'cssRules', {
|
|
74
|
+
get() {
|
|
75
|
+
return cssSheet.cssRules;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
Object.defineProperty(target, 'rules', {
|
|
79
|
+
get() {
|
|
80
|
+
return cssSheet.rules;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
// cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
|
|
85
|
+
// this is appended in prep of forking rrweb
|
|
86
|
+
Object.defineProperty(target, 'cssText', {
|
|
87
|
+
get() {
|
|
88
|
+
return stylesheetText;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.failedToFix++;
|
|
92
|
+
}
|
|
82
93
|
} catch (err) {
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
Object.defineProperty(target, 'cssText', {
|
|
86
|
-
get() {
|
|
87
|
-
return stylesheetText;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
this.failedToFix = true;
|
|
94
|
+
// failed to fetch
|
|
95
|
+
this.failedToFix++;
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
}
|
|
@@ -119,7 +119,7 @@ class InstrumentBase extends _featureBase.FeatureBase {
|
|
|
119
119
|
(0, _console.warn)("Downloading and initializing ".concat(this.featureName, " failed..."), e);
|
|
120
120
|
this.abortHandler?.(); // undo any important alterations made to the page
|
|
121
121
|
// not supported yet but nice to do: "abort" this agent's EE for this feature specifically
|
|
122
|
-
(0, _drain.drain)(this.agentIdentifier, this.featureName);
|
|
122
|
+
(0, _drain.drain)(this.agentIdentifier, this.featureName, true);
|
|
123
123
|
loadedSuccessfully(false);
|
|
124
124
|
}
|
|
125
125
|
};
|
|
@@ -209,7 +209,10 @@ function setAPI(agentIdentifier, forceDrain) {
|
|
|
209
209
|
} = _ref;
|
|
210
210
|
setAPI(agentIdentifier);
|
|
211
211
|
(0, _drain.drain)(agentIdentifier, 'api');
|
|
212
|
-
}).catch(() =>
|
|
212
|
+
}).catch(() => {
|
|
213
|
+
(0, _console.warn)('Downloading runtime APIs failed...');
|
|
214
|
+
(0, _drain.drain)(agentIdentifier, 'api', true);
|
|
215
|
+
});
|
|
213
216
|
}
|
|
214
217
|
return apiInterface;
|
|
215
218
|
}
|
|
@@ -98,9 +98,9 @@ const model = () => {
|
|
|
98
98
|
autoStart: true,
|
|
99
99
|
enabled: false,
|
|
100
100
|
harvestTimeSeconds: 60,
|
|
101
|
-
sampling_rate:
|
|
101
|
+
sampling_rate: 10,
|
|
102
102
|
// float from 0 - 100
|
|
103
|
-
error_sampling_rate:
|
|
103
|
+
error_sampling_rate: 100,
|
|
104
104
|
// float from 0 - 100
|
|
105
105
|
collect_fonts: false,
|
|
106
106
|
// 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
|
|
@@ -43,15 +43,17 @@ function curateRegistry(agentIdentifier) {
|
|
|
43
43
|
* its own named group explicitly, when ready.
|
|
44
44
|
* @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
|
|
45
45
|
* @param {string} featureName - A named group into which the feature's buffered events are bucketed.
|
|
46
|
+
* @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
|
|
46
47
|
*/
|
|
47
48
|
export function drain() {
|
|
48
49
|
let agentIdentifier = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
49
50
|
let featureName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'feature';
|
|
51
|
+
let force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
50
52
|
curateRegistry(agentIdentifier);
|
|
51
53
|
// If the feature for the specified agent is not in the registry, that means the instrument file was bypassed.
|
|
52
54
|
// This could happen in tests, or loaders that directly import the aggregator. In these cases it is safe to
|
|
53
55
|
// drain the feature group immediately rather than waiting to drain all at once.
|
|
54
|
-
if (!agentIdentifier || !registry[agentIdentifier].get(featureName)) return drainGroup(featureName);
|
|
56
|
+
if (!agentIdentifier || !registry[agentIdentifier].get(featureName) || force) return drainGroup(featureName);
|
|
55
57
|
|
|
56
58
|
// When `drain` is called, this feature is ready to drain (staged).
|
|
57
59
|
registry[agentIdentifier].get(featureName).staged = true;
|
|
@@ -131,5 +131,11 @@ function ee(old, debugId) {
|
|
|
131
131
|
}
|
|
132
132
|
function abort() {
|
|
133
133
|
globalInstance.aborted = true;
|
|
134
|
-
|
|
134
|
+
// The global backlog can be referenced directly by other emitters,
|
|
135
|
+
// so we need to delete its contents as opposed to replacing it.
|
|
136
|
+
// Otherwise, these references to the old backlog would still exist
|
|
137
|
+
// and the keys will not be garbage collected.
|
|
138
|
+
Object.keys(globalInstance.backlog).forEach(key => {
|
|
139
|
+
delete globalInstance.backlog[key];
|
|
140
|
+
});
|
|
135
141
|
}
|
|
@@ -102,7 +102,8 @@ export class HarvestScheduler extends SharedContext {
|
|
|
102
102
|
if (!submitMethod) return false;
|
|
103
103
|
const retry = !opts?.unload && submitMethod === submitData.xhr;
|
|
104
104
|
payload = this.opts.getPayload({
|
|
105
|
-
retry
|
|
105
|
+
retry,
|
|
106
|
+
opts
|
|
106
107
|
});
|
|
107
108
|
if (!payload) {
|
|
108
109
|
if (this.started) {
|
|
@@ -172,10 +172,11 @@ export class Harvest extends SharedContext {
|
|
|
172
172
|
});
|
|
173
173
|
if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
|
|
174
174
|
const harvestScope = this;
|
|
175
|
-
result.addEventListener('
|
|
175
|
+
result.addEventListener('loadend', function () {
|
|
176
176
|
// `this` refers to the XHR object in this scope, do not change this to a fat arrow
|
|
177
|
+
// status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
|
|
177
178
|
const cbResult = {
|
|
178
|
-
sent:
|
|
179
|
+
sent: this.status !== 0,
|
|
179
180
|
status: this.status
|
|
180
181
|
};
|
|
181
182
|
if (this.status === 429) {
|
|
@@ -16,6 +16,7 @@ import { responseSizeFromXhr } from './response-size';
|
|
|
16
16
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
17
17
|
import { FEATURE_NAME } from '../constants';
|
|
18
18
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
19
|
+
import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
|
|
19
20
|
var handlers = ['load', 'error', 'abort', 'timeout'];
|
|
20
21
|
var handlersLen = handlers.length;
|
|
21
22
|
var origRequest = originals.REQ;
|
|
@@ -355,6 +356,7 @@ function subscribeToEvents(agentIdentifier, ee, handler, dt) {
|
|
|
355
356
|
if (ctx.sameOrigin) {
|
|
356
357
|
var header = xhr.getResponseHeader('X-NewRelic-App-Data');
|
|
357
358
|
if (header) {
|
|
359
|
+
handle(SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, FEATURE_NAMES.metrics, ee);
|
|
358
360
|
ctx.params.cat = header.split(', ').pop();
|
|
359
361
|
}
|
|
360
362
|
}
|
|
@@ -111,6 +111,11 @@ export class Instrument extends InstrumentBase {
|
|
|
111
111
|
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
112
112
|
*/
|
|
113
113
|
#castErrorEvent(errorEvent) {
|
|
114
|
+
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
115
|
+
const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno);
|
|
116
|
+
error.name = SyntaxError.name;
|
|
117
|
+
return error;
|
|
118
|
+
}
|
|
114
119
|
if (errorEvent.error instanceof Error) {
|
|
115
120
|
return errorEvent.error;
|
|
116
121
|
}
|
|
@@ -107,7 +107,7 @@ export class Aggregate extends AggregateBase {
|
|
|
107
107
|
|
|
108
108
|
// [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
|
|
109
109
|
windowAddEventListener('pageshow', evt => {
|
|
110
|
-
if (evt
|
|
110
|
+
if (evt?.persisted) {
|
|
111
111
|
this.storeSupportabilityMetrics('Generic/BFCache/PageRestored');
|
|
112
112
|
}
|
|
113
113
|
});
|
|
@@ -119,7 +119,7 @@ export class Aggregate extends AggregateBase {
|
|
|
119
119
|
status,
|
|
120
120
|
responseText
|
|
121
121
|
} = _ref3;
|
|
122
|
-
if (status >= 400) {
|
|
122
|
+
if (status >= 400 || status === 0) {
|
|
123
123
|
// Adding retry logic for the rum call will be a separate change
|
|
124
124
|
this.ee.abort();
|
|
125
125
|
return;
|