@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
|
@@ -27,7 +27,6 @@ import { now } from '../../../common/timing/now';
|
|
|
27
27
|
import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants';
|
|
28
28
|
import { stringify } from '../../../common/util/stringify';
|
|
29
29
|
import { stylesheetEvaluator } from '../shared/stylesheet-evaluator';
|
|
30
|
-
let gzipper, u8;
|
|
31
30
|
export class Aggregate extends AggregateBase {
|
|
32
31
|
static featureName = FEATURE_NAME;
|
|
33
32
|
// pass the recorder into the aggregator
|
|
@@ -39,8 +38,10 @@ export class Aggregate extends AggregateBase {
|
|
|
39
38
|
this.initialized = false;
|
|
40
39
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
41
40
|
this.blocked = false;
|
|
42
|
-
/**
|
|
43
|
-
this.
|
|
41
|
+
/** populated with the gzipper lib async */
|
|
42
|
+
this.gzipper = undefined;
|
|
43
|
+
/** populated with the u8 string lib async */
|
|
44
|
+
this.u8 = undefined;
|
|
44
45
|
/** the mode to start in. Defaults to off */
|
|
45
46
|
const {
|
|
46
47
|
session
|
|
@@ -51,6 +52,7 @@ export class Aggregate extends AggregateBase {
|
|
|
51
52
|
this.entitled = false;
|
|
52
53
|
this.recorder = args?.recorder;
|
|
53
54
|
if (this.recorder) this.recorder.parent = this;
|
|
55
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
54
56
|
const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
|
|
55
57
|
if (shouldSetup) {
|
|
56
58
|
// 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.
|
|
@@ -87,6 +89,11 @@ export class Aggregate extends AggregateBase {
|
|
|
87
89
|
getPayload: this.prepareHarvest.bind(this),
|
|
88
90
|
raw: true
|
|
89
91
|
}, this);
|
|
92
|
+
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
93
|
+
this.prepUtils().then(() => {
|
|
94
|
+
this.scheduler.runHarvest();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
90
97
|
registerHandler('recordReplay', () => {
|
|
91
98
|
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
92
99
|
if (this.blocked || !this.entitled) return;
|
|
@@ -110,13 +117,37 @@ export class Aggregate extends AggregateBase {
|
|
|
110
117
|
this.switchToFull();
|
|
111
118
|
}
|
|
112
119
|
}, this.featureName, this.ee);
|
|
120
|
+
const {
|
|
121
|
+
error_sampling_rate,
|
|
122
|
+
sampling_rate,
|
|
123
|
+
autoStart,
|
|
124
|
+
block_selector,
|
|
125
|
+
mask_text_selector,
|
|
126
|
+
mask_all_inputs,
|
|
127
|
+
inline_stylesheet,
|
|
128
|
+
inline_images,
|
|
129
|
+
collect_fonts
|
|
130
|
+
} = getConfigurationValue(this.agentIdentifier, 'session_replay');
|
|
113
131
|
this.waitForFlags(['sr']).then(_ref => {
|
|
114
132
|
let [flagOn] = _ref;
|
|
115
133
|
this.entitled = flagOn;
|
|
116
|
-
if (!this.entitled && this.recorder?.recording)
|
|
117
|
-
|
|
134
|
+
if (!this.entitled && this.recorder?.recording) {
|
|
135
|
+
this.recorder.abort(ABORT_REASONS.ENTITLEMENTS);
|
|
136
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
137
|
+
}
|
|
138
|
+
this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
|
|
118
139
|
}).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
|
|
119
140
|
|
|
141
|
+
/** 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 */
|
|
142
|
+
if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
143
|
+
if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
144
|
+
if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
145
|
+
if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
146
|
+
if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
147
|
+
if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
148
|
+
if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
149
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
150
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
120
151
|
this.drain();
|
|
121
152
|
}
|
|
122
153
|
}
|
|
@@ -191,24 +222,29 @@ export class Aggregate extends AggregateBase {
|
|
|
191
222
|
// We only report (harvest) in FULL mode
|
|
192
223
|
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
193
224
|
}
|
|
225
|
+
await this.prepUtils();
|
|
226
|
+
if (!this.recorder.recording) this.recorder.startRecording();
|
|
227
|
+
this.syncWithSessionManager({
|
|
228
|
+
sessionReplayMode: this.mode
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async prepUtils() {
|
|
194
232
|
try {
|
|
195
233
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
196
234
|
const {
|
|
197
235
|
gzipSync,
|
|
198
236
|
strToU8
|
|
199
237
|
} = await import( /* webpackChunkName: "compressor" */'fflate');
|
|
200
|
-
gzipper = gzipSync;
|
|
201
|
-
u8 = strToU8;
|
|
238
|
+
this.gzipper = gzipSync;
|
|
239
|
+
this.u8 = strToU8;
|
|
202
240
|
} catch (err) {
|
|
203
241
|
// compressor failed to load, but we can still record without compression as a last ditch effort
|
|
204
|
-
this.shouldCompress = false;
|
|
205
242
|
}
|
|
206
|
-
if (!this.recorder.recording) this.recorder.startRecording();
|
|
207
|
-
this.syncWithSessionManager({
|
|
208
|
-
sessionReplayMode: this.mode
|
|
209
|
-
});
|
|
210
243
|
}
|
|
211
244
|
prepareHarvest() {
|
|
245
|
+
let {
|
|
246
|
+
opts
|
|
247
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
212
248
|
if (!this.recorder) return;
|
|
213
249
|
const recorderEvents = this.recorder.getEvents();
|
|
214
250
|
// get the event type and use that to trigger another harvest if needed
|
|
@@ -219,8 +255,8 @@ export class Aggregate extends AggregateBase {
|
|
|
219
255
|
return;
|
|
220
256
|
}
|
|
221
257
|
let len = 0;
|
|
222
|
-
if (this.
|
|
223
|
-
payload.body = gzipper(u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
258
|
+
if (!!this.gzipper && !!this.u8) {
|
|
259
|
+
payload.body = this.gzipper(this.u8("[".concat(payload.body.map(e => e.__serialized).join(','), "]")));
|
|
224
260
|
len = payload.body.length;
|
|
225
261
|
this.scheduler.opts.gzip = true;
|
|
226
262
|
} else {
|
|
@@ -246,7 +282,7 @@ export class Aggregate extends AggregateBase {
|
|
|
246
282
|
sessionReplaySentFirstChunk: true
|
|
247
283
|
});
|
|
248
284
|
this.recorder.clearBuffer();
|
|
249
|
-
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest();
|
|
285
|
+
if (recorderEvents.type === 'preloaded') this.scheduler.runHarvest(opts);
|
|
250
286
|
return [payload];
|
|
251
287
|
}
|
|
252
288
|
getHarvestContents(recorderEvents) {
|
|
@@ -277,7 +313,7 @@ export class Aggregate extends AggregateBase {
|
|
|
277
313
|
const relativeNow = now();
|
|
278
314
|
const firstEventTimestamp = events[0]?.timestamp; // from rrweb node
|
|
279
315
|
const lastEventTimestamp = events[events.length - 1]?.timestamp; // from rrweb node
|
|
280
|
-
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp;
|
|
316
|
+
const firstTimestamp = firstEventTimestamp || recorderEvents.cycleTimestamp; // from rrweb node || from when the harvest cycle started
|
|
281
317
|
const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
|
|
282
318
|
return {
|
|
283
319
|
qs: {
|
|
@@ -288,7 +324,7 @@ export class Aggregate extends AggregateBase {
|
|
|
288
324
|
attributes: encodeObj({
|
|
289
325
|
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
290
326
|
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
291
|
-
...(this.
|
|
327
|
+
...(!!this.gzipper && !!this.u8 && {
|
|
292
328
|
content_encoding: 'gzip'
|
|
293
329
|
}),
|
|
294
330
|
'replay.firstTimestamp': firstTimestamp,
|
|
@@ -110,13 +110,13 @@ export class Recorder {
|
|
|
110
110
|
/** 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) */
|
|
111
111
|
if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
|
|
112
112
|
if (incompletes) {
|
|
113
|
-
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css', incompletes], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
114
113
|
/** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
|
|
115
114
|
stylesheetEvaluator.fix().then(failedToFix => {
|
|
116
115
|
if (failedToFix) {
|
|
117
116
|
this.currentBufferTarget.inlinedAllStylesheets = false;
|
|
118
117
|
this.shouldFix = false;
|
|
119
|
-
|
|
118
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
119
|
+
} else handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
120
120
|
this.takeFullSnapshot();
|
|
121
121
|
});
|
|
122
122
|
/** Only start ignoring data if got a faulty snapshot */
|
|
@@ -170,7 +170,12 @@ export class Recorder {
|
|
|
170
170
|
|
|
171
171
|
/** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
|
|
172
172
|
takeFullSnapshot() {
|
|
173
|
-
|
|
173
|
+
try {
|
|
174
|
+
if (!this.recording) return;
|
|
175
|
+
recorder.takeFullSnapshot();
|
|
176
|
+
} catch (err) {
|
|
177
|
+
// 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
|
|
178
|
+
}
|
|
174
179
|
}
|
|
175
180
|
clearTimestamps() {
|
|
176
181
|
this.currentBufferTarget.cycleTimestamp = undefined;
|
|
@@ -188,7 +193,7 @@ export class Recorder {
|
|
|
188
193
|
* https://staging.onenr.io/037jbJWxbjy
|
|
189
194
|
* */
|
|
190
195
|
estimateCompression(data) {
|
|
191
|
-
if (this.
|
|
196
|
+
if (!!this.parent.gzipper && !!this.parent.u8) return data * AVG_COMPRESSION;
|
|
192
197
|
return data;
|
|
193
198
|
}
|
|
194
199
|
}
|
|
@@ -8,7 +8,7 @@ class StylesheetEvaluator {
|
|
|
8
8
|
* Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
|
|
9
9
|
* */
|
|
10
10
|
invalidStylesheetsDetected = false;
|
|
11
|
-
failedToFix =
|
|
11
|
+
failedToFix = 0;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 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.
|
|
@@ -43,7 +43,7 @@ class StylesheetEvaluator {
|
|
|
43
43
|
await Promise.all(this.#fetchProms);
|
|
44
44
|
this.#fetchProms = [];
|
|
45
45
|
const failedToFix = this.failedToFix;
|
|
46
|
-
this.failedToFix =
|
|
46
|
+
this.failedToFix = 0;
|
|
47
47
|
return failedToFix;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -54,34 +54,39 @@ class StylesheetEvaluator {
|
|
|
54
54
|
* @returns {Promise}
|
|
55
55
|
*/
|
|
56
56
|
async #fetchAndOverride(target, href) {
|
|
57
|
-
const stylesheetContents = await originals.FETCH.bind(window)(href);
|
|
58
|
-
if (!stylesheetContents.ok) {
|
|
59
|
-
this.failedToFix = true;
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const stylesheetText = await stylesheetContents.text();
|
|
63
57
|
try {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
const stylesheetContents = await originals.FETCH.bind(window)(href);
|
|
59
|
+
if (!stylesheetContents.ok) {
|
|
60
|
+
this.failedToFix++;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const stylesheetText = await stylesheetContents.text();
|
|
64
|
+
try {
|
|
65
|
+
const cssSheet = new CSSStyleSheet();
|
|
66
|
+
await cssSheet.replace(stylesheetText);
|
|
67
|
+
Object.defineProperty(target, 'cssRules', {
|
|
68
|
+
get() {
|
|
69
|
+
return cssSheet.cssRules;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(target, 'rules', {
|
|
73
|
+
get() {
|
|
74
|
+
return cssSheet.rules;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// cant make new dynamic stylesheets, browser likely doesn't support `.replace()`...
|
|
79
|
+
// this is appended in prep of forking rrweb
|
|
80
|
+
Object.defineProperty(target, 'cssText', {
|
|
81
|
+
get() {
|
|
82
|
+
return stylesheetText;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this.failedToFix++;
|
|
86
|
+
}
|
|
76
87
|
} catch (err) {
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
Object.defineProperty(target, 'cssText', {
|
|
80
|
-
get() {
|
|
81
|
-
return stylesheetText;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
this.failedToFix = true;
|
|
88
|
+
// failed to fetch
|
|
89
|
+
this.failedToFix++;
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
}
|
|
@@ -114,7 +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
|
+
drain(this.agentIdentifier, this.featureName, true);
|
|
118
118
|
loadedSuccessfully(false);
|
|
119
119
|
}
|
|
120
120
|
};
|
|
@@ -200,7 +200,10 @@ export function setAPI(agentIdentifier, forceDrain) {
|
|
|
200
200
|
} = _ref;
|
|
201
201
|
setAPI(agentIdentifier);
|
|
202
202
|
drain(agentIdentifier, 'api');
|
|
203
|
-
}).catch(() =>
|
|
203
|
+
}).catch(() => {
|
|
204
|
+
warn('Downloading runtime APIs failed...');
|
|
205
|
+
drain(agentIdentifier, 'api', true);
|
|
206
|
+
});
|
|
204
207
|
}
|
|
205
208
|
return apiInterface;
|
|
206
209
|
}
|
|
@@ -12,6 +12,7 @@ export function registerDrain(agentIdentifier: string, group: string): void;
|
|
|
12
12
|
* its own named group explicitly, when ready.
|
|
13
13
|
* @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
|
|
14
14
|
* @param {string} featureName - A named group into which the feature's buffered events are bucketed.
|
|
15
|
+
* @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
|
|
15
16
|
*/
|
|
16
|
-
export function drain(agentIdentifier?: string, featureName?: string): void;
|
|
17
|
+
export function drain(agentIdentifier?: string, featureName?: string, force?: boolean): void;
|
|
17
18
|
//# sourceMappingURL=drain.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../../src/common/drain/drain.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,+CAHW,MAAM,SACN,MAAM,QAQhB;AAYD
|
|
1
|
+
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../../../src/common/drain/drain.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,+CAHW,MAAM,SACN,MAAM,QAQhB;AAYD;;;;;;GAMG;AACH,wCAJW,MAAM,gBACN,MAAM,UACN,OAAO,QAuDjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextual-ee.d.ts","sourceRoot":"","sources":["../../../../src/common/event-emitter/contextual-ee.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAYA,+BAA0C;AA0DxC,+FAsBC;AAqBD,6CAEC;AAND,2CAEC;AAMD,mEAaC;AAcH,+
|
|
1
|
+
{"version":3,"file":"contextual-ee.d.ts","sourceRoot":"","sources":["../../../../src/common/event-emitter/contextual-ee.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AAYA,+BAA0C;AA0DxC,+FAsBC;AAqBD,6CAEC;AAND,2CAEC;AAMD,mEAaC;AAcH,+BASC;AArBC,iDAGC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AACH;IAII,0BAA2H;IAC3H,uBAAoD;IAEpD,YAAiB;IAGnB;;;;;OAKG;IACH,aAFW,eAAe,WAWzB;IAED;;;OAGG;IACH,YAFW,eAAe,WAMzB;IAED;;;OAGG;IACH,wBAFW,eAAe,WAMzB;IAED;;;;;;OAMG;IACH,gGAJW,eAAe,GACb,OAAO,
|
|
1
|
+
{"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AACH;IAII,0BAA2H;IAC3H,uBAAoD;IAEpD,YAAiB;IAGnB;;;;;OAKG;IACH,aAFW,eAAe,WAWzB;IAED;;;OAGG;IACH,YAFW,eAAe,WAMzB;IAED;;;OAGG;IACH,wBAFW,eAAe,WAMzB;IAED;;;;;;OAMG;IACH,gGAJW,eAAe,GACb,OAAO,CAmFnB;IAGD,iCAoBC;IAED;;;;;;;OAOG;IACH,wBALW,yBAAyB,WACzB,6BAA6B,GAE3B,cAAc,CA2B1B;IAED;;;;;;;OAOG;IACH,uBAHW,cAAc,GACZ,cAAc,CAuB1B;IAED;;;;;OAKG;IACH,aAHW,yBAAyB,YACzB,sBAAsB,QAQhC;CACF;8BApPY,OAAO,YAAY,EAAE,eAAe;wCACpC,OAAO,YAAY,EAAE,yBAAyB;6BAC9C,OAAO,YAAY,EAAE,cAAc;qCACnC,OAAO,YAAY,EAAE,sBAAsB;4CAC3C,OAAO,YAAY,EAAE,6BAA6B;8BAZjC,2BAA2B;2BAF9B,mBAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA0BA;IACE,2BAAiC;IACjC,mEAqCC;IA/BC,mBAAiC;IAEjC,4EAAkF;CA8BrF;+BAnD8B,6BAA6B;mBAFzC,uBAAuB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IAIjC,mEA4CC;IAvCG,2CAA0C;IAqC5C,yBAA+B;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/instrument/index.js"],"names":[],"mappings":"AAeA;IACE,2BAAiC;IAIjC,mEA4CC;IAvCG,2CAA0C;IAqC5C,yBAA+B;;CAoFlC;+BA3I8B,6BAA6B"}
|
|
@@ -5,8 +5,10 @@ export class Aggregate extends AggregateBase {
|
|
|
5
5
|
harvestTimeSeconds: any;
|
|
6
6
|
/** Set once the recorder has fully initialized after flag checks and sampling */
|
|
7
7
|
initialized: boolean;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/** populated with the gzipper lib async */
|
|
9
|
+
gzipper: typeof import("fflate").gzipSync | undefined;
|
|
10
|
+
/** populated with the u8 string lib async */
|
|
11
|
+
u8: typeof import("fflate").strToU8 | undefined;
|
|
10
12
|
mode: any;
|
|
11
13
|
/** set by BCS response */
|
|
12
14
|
entitled: boolean;
|
|
@@ -23,7 +25,10 @@ export class Aggregate extends AggregateBase {
|
|
|
23
25
|
* @returns {void}
|
|
24
26
|
*/
|
|
25
27
|
initializeRecording(errorSample: boolean, fullSample: boolean, ignoreSession: boolean): void;
|
|
26
|
-
|
|
28
|
+
prepUtils(): Promise<void>;
|
|
29
|
+
prepareHarvest({ opts }?: {
|
|
30
|
+
opts: any;
|
|
31
|
+
}): {
|
|
27
32
|
qs: {
|
|
28
33
|
browser_monitoring_key: any;
|
|
29
34
|
type: string;
|
|
@@ -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":"AA8BA;IACE,2BAAiC;IAEjC,8DAwHC;IAtHC,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IAGxB,2CAA2C;IAC3C,sDAAwB;IACxB,6CAA6C;IAC7C,gDAAmB;IAGnB,UAAuD;IAEvD,0BAA0B;IAC1B,kBAAqB;IAErB,cAA8B;IAoC5B,wCAKQ;IAyBN,sBAAwB;IAqC9B,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAsDhB;IAED,2BASC;IAED;;;;;;;;;;;oBAiCC;IAED;;;;;;;;;MAmEC;IAED,qCAOC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBAUC;IAED,yCAGC;CACF;8BA/W6B,4BAA4B;iCAHzB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAWA;IAUE,yBAeC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAwBC;IAED;;;;;OAKG;IACH,yCAyBC;IAED,0HAA0H;IAC1H,yCA2CC;IAED,0HAA0H;IAC1H,
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/recorder.js"],"names":[],"mappings":"AAWA;IAUE,yBAeC;IAdC,iEAAiE;IACjE,mBAAsB;IACtB,6DAA6D;IAC7D,oCAAuC;IACvC,kIAAkI;IAClI,kBAAqB;IACrB,sDAAsD;IACtD,YAAoB;IACpB,oEAAoE;IACpE,6BAAqH;IACrH,0FAA0F;IAC1F,eAA6C;IAC7C,uIAAuI;IACvI,0BAAyE;IAG3E;;;;;;;;;MAYC;IAED,mFAAmF;IACnF,oBAKC;IAED,qDAAqD;IACrD,uBAwBC;IAED;;;;;OAKG;IACH,yCAyBC;IAED,0HAA0H;IAC1H,yCA2CC;IAED,0HAA0H;IAC1H,yBAOC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;SAGK;IACL,oCAGC;;CACF;+BA9L8B,mBAAmB"}
|
|
@@ -5,7 +5,7 @@ declare class StylesheetEvaluator {
|
|
|
5
5
|
* Used at harvest time to denote that all subsequent payloads are subject to this and customers should be advised to handle crossorigin decoration
|
|
6
6
|
* */
|
|
7
7
|
invalidStylesheetsDetected: boolean;
|
|
8
|
-
failedToFix:
|
|
8
|
+
failedToFix: number;
|
|
9
9
|
/**
|
|
10
10
|
* 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.
|
|
11
11
|
* @returns {Number}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stylesheet-evaluator.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/shared/stylesheet-evaluator.js"],"names":[],"mappings":"AAwFA,sDAA4D;AArF5D;IAGE;;;QAGI;IACJ,oCAAkC;IAClC,oBAAe;IAEf;;;OAGG;IACH,mBAmBC;IAED;;;OAGG;IACH,oBAMC;;CAsCF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IAyDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;IAyDE;;;;OAIG;qBAFQ,MAAM;IAWjB;;;;OAIG;iCAFQ,MAAM,GAAC,IAAI;;;;;;EAwGvB;AAxMD,0CAA0C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.252.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"src/*": [
|
|
18
18
|
"dist/types/*"
|
|
19
19
|
],
|
|
20
|
+
"dist/esm/*": [
|
|
21
|
+
"dist/types/*"
|
|
22
|
+
],
|
|
20
23
|
"loaders/agent": [
|
|
21
24
|
"dist/types/loaders/agent.d.ts"
|
|
22
25
|
],
|
|
@@ -64,8 +64,8 @@ const model = () => {
|
|
|
64
64
|
autoStart: true,
|
|
65
65
|
enabled: false,
|
|
66
66
|
harvestTimeSeconds: 60,
|
|
67
|
-
sampling_rate:
|
|
68
|
-
error_sampling_rate:
|
|
67
|
+
sampling_rate: 10, // float from 0 - 100
|
|
68
|
+
error_sampling_rate: 100, // float from 0 - 100
|
|
69
69
|
collect_fonts: false, // 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
|
|
70
70
|
inline_images: false, // 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
|
|
71
71
|
inline_stylesheet: true, // serialize css for collection without public asset url
|
|
@@ -41,13 +41,14 @@ function curateRegistry (agentIdentifier) {
|
|
|
41
41
|
* its own named group explicitly, when ready.
|
|
42
42
|
* @param {string} agentIdentifier - A unique 16 character ID corresponding to an instantiated agent.
|
|
43
43
|
* @param {string} featureName - A named group into which the feature's buffered events are bucketed.
|
|
44
|
+
* @param {boolean} force - Whether to force the drain to occur immediately, bypassing the registry and staging logic.
|
|
44
45
|
*/
|
|
45
|
-
export function drain (agentIdentifier = '', featureName = 'feature') {
|
|
46
|
+
export function drain (agentIdentifier = '', featureName = 'feature', force = false) {
|
|
46
47
|
curateRegistry(agentIdentifier)
|
|
47
48
|
// If the feature for the specified agent is not in the registry, that means the instrument file was bypassed.
|
|
48
49
|
// This could happen in tests, or loaders that directly import the aggregator. In these cases it is safe to
|
|
49
50
|
// drain the feature group immediately rather than waiting to drain all at once.
|
|
50
|
-
if (!agentIdentifier || !registry[agentIdentifier].get(featureName)) return drainGroup(featureName)
|
|
51
|
+
if (!agentIdentifier || !registry[agentIdentifier].get(featureName) || force) return drainGroup(featureName)
|
|
51
52
|
|
|
52
53
|
// When `drain` is called, this feature is ready to drain (staged).
|
|
53
54
|
registry[agentIdentifier].get(featureName).staged = true
|
|
@@ -144,5 +144,11 @@ function ee (old, debugId) {
|
|
|
144
144
|
|
|
145
145
|
function abort () {
|
|
146
146
|
globalInstance.aborted = true
|
|
147
|
-
|
|
147
|
+
// The global backlog can be referenced directly by other emitters,
|
|
148
|
+
// so we need to delete its contents as opposed to replacing it.
|
|
149
|
+
// Otherwise, these references to the old backlog would still exist
|
|
150
|
+
// and the keys will not be garbage collected.
|
|
151
|
+
Object.keys(globalInstance.backlog).forEach(key => {
|
|
152
|
+
delete globalInstance.backlog[key]
|
|
153
|
+
})
|
|
148
154
|
}
|
|
@@ -102,7 +102,7 @@ export class HarvestScheduler extends SharedContext {
|
|
|
102
102
|
if (!submitMethod) return false
|
|
103
103
|
|
|
104
104
|
const retry = !opts?.unload && submitMethod === submitData.xhr
|
|
105
|
-
payload = this.opts.getPayload({ retry })
|
|
105
|
+
payload = this.opts.getPayload({ retry, opts })
|
|
106
106
|
|
|
107
107
|
if (!payload) {
|
|
108
108
|
if (this.started) {
|
|
@@ -142,9 +142,10 @@ export class Harvest extends SharedContext {
|
|
|
142
142
|
|
|
143
143
|
if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
|
|
144
144
|
const harvestScope = this
|
|
145
|
-
result.addEventListener('
|
|
145
|
+
result.addEventListener('loadend', function () {
|
|
146
146
|
// `this` refers to the XHR object in this scope, do not change this to a fat arrow
|
|
147
|
-
|
|
147
|
+
// status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
|
|
148
|
+
const cbResult = { sent: this.status !== 0, status: this.status }
|
|
148
149
|
if (this.status === 429) {
|
|
149
150
|
cbResult.retry = true
|
|
150
151
|
cbResult.delay = harvestScope.tooManyRequestsDelay
|
|
@@ -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
|
|
|
20
21
|
var handlers = ['load', 'error', 'abort', 'timeout']
|
|
21
22
|
var handlersLen = handlers.length
|
|
@@ -395,6 +396,7 @@ function subscribeToEvents (agentIdentifier, ee, handler, dt) {
|
|
|
395
396
|
if (ctx.sameOrigin) {
|
|
396
397
|
var header = xhr.getResponseHeader('X-NewRelic-App-Data')
|
|
397
398
|
if (header) {
|
|
399
|
+
handle(SUPPORTABILITY_METRIC, ['Ajax/CrossApplicationTracing/Header/Seen'], undefined, FEATURE_NAMES.metrics, ee)
|
|
398
400
|
ctx.params.cat = header.split(', ').pop()
|
|
399
401
|
}
|
|
400
402
|
}
|
|
@@ -128,6 +128,12 @@ export class Instrument extends InstrumentBase {
|
|
|
128
128
|
* @returns {Error|UncaughtError} The error event converted to an Error object
|
|
129
129
|
*/
|
|
130
130
|
#castErrorEvent (errorEvent) {
|
|
131
|
+
if (errorEvent.error instanceof SyntaxError && !/:\d+$/.test(errorEvent.error.stack?.trim())) {
|
|
132
|
+
const error = new UncaughtError(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno)
|
|
133
|
+
error.name = SyntaxError.name
|
|
134
|
+
return error
|
|
135
|
+
}
|
|
136
|
+
|
|
131
137
|
if (errorEvent.error instanceof Error) {
|
|
132
138
|
return errorEvent.error
|
|
133
139
|
}
|
|
@@ -102,7 +102,7 @@ export class Aggregate extends AggregateBase {
|
|
|
102
102
|
|
|
103
103
|
// [Temporary] Report restores from BFCache to NR1 while feature flag is in place in lieu of sending pageshow events.
|
|
104
104
|
windowAddEventListener('pageshow', (evt) => {
|
|
105
|
-
if (evt
|
|
105
|
+
if (evt?.persisted) { this.storeSupportabilityMetrics('Generic/BFCache/PageRestored') }
|
|
106
106
|
})
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -101,7 +101,7 @@ export class Aggregate extends AggregateBase {
|
|
|
101
101
|
payload: { qs: queryParameters, body },
|
|
102
102
|
opts: { needResponse: true, sendEmptyBody: true },
|
|
103
103
|
cbFinished: ({ status, responseText }) => {
|
|
104
|
-
if (status >= 400) {
|
|
104
|
+
if (status >= 400 || status === 0) {
|
|
105
105
|
// Adding retry logic for the rum call will be a separate change
|
|
106
106
|
this.ee.abort()
|
|
107
107
|
return
|