@newrelic/browser-agent 1.242.0 → 1.243.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 +1465 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/session/session-entity.js +20 -2
- package/dist/cjs/common/wrap/wrap-function.js +1 -1
- package/dist/cjs/features/ajax/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +84 -54
- package/dist/cjs/features/utils/feature-base.js +1 -2
- package/dist/cjs/loaders/api/api.js +2 -2
- package/dist/cjs/loaders/api/apiAsync.js +0 -37
- package/dist/cjs/loaders/configure/configure.js +1 -1
- package/dist/cjs/loaders/configure/public-path.js +6 -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/session/session-entity.js +18 -1
- package/dist/esm/common/wrap/wrap-function.js +1 -1
- package/dist/esm/features/ajax/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +83 -54
- package/dist/esm/features/utils/feature-base.js +1 -2
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/esm/loaders/api/apiAsync.js +1 -36
- package/dist/esm/loaders/configure/configure.js +1 -1
- package/dist/esm/loaders/configure/public-path.js +6 -3
- package/dist/types/common/session/session-entity.d.ts +5 -0
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/feature-base.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
- package/dist/types/loaders/configure/public-path.d.ts +1 -1
- package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/session/session-entity.js +20 -1
- package/src/common/wrap/wrap-function.js +1 -1
- package/src/features/ajax/aggregate/index.js +2 -2
- package/src/features/session_replay/aggregate/index.js +81 -37
- package/src/features/utils/feature-base.js +1 -2
- package/src/loaders/api/api.js +1 -2
- package/src/loaders/api/apiAsync.js +1 -39
- package/src/loaders/configure/configure.js +1 -1
- package/src/loaders/configure/public-path.js +6 -3
- package/src/common/aggregate/aggregator.test.js +0 -107
- package/src/common/config/state/configurable.test.js +0 -73
- package/src/common/config/state/info.test.js +0 -31
- package/src/common/config/state/init.test.js +0 -68
- package/src/common/config/state/loader-config.test.js +0 -21
- package/src/common/config/state/runtime.test.js +0 -21
- package/src/common/constants/env.cdn.test.js +0 -7
- package/src/common/constants/env.npm.test.js +0 -7
- package/src/common/constants/env.test.js +0 -7
- package/src/common/constants/runtime.test.js +0 -176
- package/src/common/deny-list/deny-list.test.js +0 -104
- package/src/common/dom/query-selector.test.js +0 -24
- package/src/common/drain/drain.test.js +0 -74
- package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
- package/src/common/event-emitter/handle.test.js +0 -56
- package/src/common/event-emitter/register-handler.test.js +0 -61
- package/src/common/harvest/harvest-scheduler.test.js +0 -492
- package/src/common/harvest/harvest.test.js +0 -813
- package/src/common/ids/id.test.js +0 -92
- package/src/common/ids/unique-id.test.js +0 -58
- package/src/common/session/session-entity.component-test.js +0 -346
- package/src/common/storage/local-storage.test.js +0 -17
- package/src/common/timer/interaction-timer.component-test.js +0 -212
- package/src/common/timer/timer.test.js +0 -99
- package/src/common/timing/nav-timing.test.js +0 -161
- package/src/common/url/canonicalize-url.test.js +0 -45
- package/src/common/url/clean-url.test.js +0 -25
- package/src/common/url/encode.test.js +0 -81
- package/src/common/url/location.test.js +0 -15
- package/src/common/url/parse-url.test.js +0 -110
- package/src/common/url/protocol.test.js +0 -17
- package/src/common/util/console.test.js +0 -34
- package/src/common/util/data-size.test.js +0 -56
- package/src/common/util/feature-flags.test.js +0 -94
- package/src/common/util/get-or-set.test.js +0 -58
- package/src/common/util/invoke.test.js +0 -65
- package/src/common/util/map-own.test.js +0 -52
- package/src/common/util/obfuscate.component-test.js +0 -173
- package/src/common/util/stringify.test.js +0 -49
- package/src/common/util/submit-data.test.js +0 -183
- package/src/common/util/traverse.test.js +0 -50
- package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
- package/src/common/vitals/first-contentful-paint.test.js +0 -124
- package/src/common/vitals/first-input-delay.test.js +0 -88
- package/src/common/vitals/first-paint.test.js +0 -127
- package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
- package/src/common/vitals/largest-contentful-paint.test.js +0 -94
- package/src/common/vitals/long-task.test.js +0 -122
- package/src/common/vitals/time-to-first-byte.test.js +0 -147
- package/src/common/vitals/vital-metric.test.js +0 -171
- package/src/common/wrap/wrap-promise.component-test.js +0 -110
- package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
- package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
- package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
- package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
- package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
- package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
- package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
- package/src/features/session_replay/aggregate/index.component-test.js +0 -317
- package/src/features/spa/aggregate/interaction-node.test.js +0 -17
- package/src/features/utils/agent-session.test.js +0 -194
- package/src/features/utils/aggregate-base.test.js +0 -123
- package/src/features/utils/feature-base.test.js +0 -45
- package/src/features/utils/handler-cache.test.js +0 -72
- package/src/features/utils/instrument-base.test.js +0 -216
- package/src/features/utils/lazy-feature-loader.test.js +0 -37
- package/src/loaders/api/api.component-test.js +0 -45
- package/src/loaders/api/api.test.js +0 -85
- package/src/loaders/api/apiAsync.test.js +0 -17
|
@@ -15,18 +15,43 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
|
15
15
|
import { FEATURE_NAME } from '../constants';
|
|
16
16
|
import { stringify } from '../../../common/util/stringify';
|
|
17
17
|
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
|
|
18
|
-
import { SESSION_EVENTS, MODE } from '../../../common/session/session-entity';
|
|
18
|
+
import { SESSION_EVENTS, MODE, SESSION_EVENT_TYPES } from '../../../common/session/session-entity';
|
|
19
19
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
20
20
|
import { sharedChannel } from '../../../common/constants/shared-channel';
|
|
21
21
|
import { obj as encodeObj } from '../../../common/url/encode';
|
|
22
22
|
import { warn } from '../../../common/util/console';
|
|
23
23
|
import { globalScope } from '../../../common/constants/runtime';
|
|
24
24
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
25
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
26
25
|
|
|
27
26
|
// would be better to get this dynamically in some way
|
|
28
27
|
export const RRWEB_VERSION = '2.0.0-alpha.8';
|
|
29
28
|
export const AVG_COMPRESSION = 0.12;
|
|
29
|
+
export const RRWEB_EVENT_TYPES = {
|
|
30
|
+
DomContentLoaded: 0,
|
|
31
|
+
Load: 1,
|
|
32
|
+
FullSnapshot: 2,
|
|
33
|
+
IncrementalSnapshot: 3,
|
|
34
|
+
Meta: 4,
|
|
35
|
+
Custom: 5
|
|
36
|
+
};
|
|
37
|
+
const ABORT_REASONS = {
|
|
38
|
+
RESET: {
|
|
39
|
+
message: 'Session was reset',
|
|
40
|
+
sm: 'Reset'
|
|
41
|
+
},
|
|
42
|
+
IMPORT: {
|
|
43
|
+
message: 'Recorder failed to import',
|
|
44
|
+
sm: 'Import'
|
|
45
|
+
},
|
|
46
|
+
TOO_MANY: {
|
|
47
|
+
message: '429: Too Many Requests',
|
|
48
|
+
sm: 'Too-Many'
|
|
49
|
+
},
|
|
50
|
+
TOO_BIG: {
|
|
51
|
+
message: 'Payload was too large',
|
|
52
|
+
sm: 'Too-Big'
|
|
53
|
+
}
|
|
54
|
+
};
|
|
30
55
|
let recorder, gzipper, u8;
|
|
31
56
|
|
|
32
57
|
/** Vortex caps payload sizes at 1MB */
|
|
@@ -60,8 +85,6 @@ export class Aggregate extends AggregateBase {
|
|
|
60
85
|
/** can shut off efforts to compress the data */
|
|
61
86
|
this.shouldCompress = true;
|
|
62
87
|
|
|
63
|
-
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
64
|
-
this.isFirstChunk = false;
|
|
65
88
|
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
66
89
|
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
67
90
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
@@ -75,16 +98,7 @@ export class Aggregate extends AggregateBase {
|
|
|
75
98
|
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
76
99
|
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
77
100
|
*/
|
|
78
|
-
this.
|
|
79
|
-
event: {
|
|
80
|
-
first: undefined,
|
|
81
|
-
last: undefined
|
|
82
|
-
},
|
|
83
|
-
cycle: {
|
|
84
|
-
first: undefined,
|
|
85
|
-
last: undefined
|
|
86
|
-
}
|
|
87
|
-
};
|
|
101
|
+
this.cycleTimestamp = undefined;
|
|
88
102
|
|
|
89
103
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
90
104
|
this.payloadBytesEstimation = 0;
|
|
@@ -98,7 +112,7 @@ export class Aggregate extends AggregateBase {
|
|
|
98
112
|
if (shouldSetup) {
|
|
99
113
|
// 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.
|
|
100
114
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
101
|
-
this.abort(
|
|
115
|
+
this.abort(ABORT_REASONS.RESET);
|
|
102
116
|
});
|
|
103
117
|
|
|
104
118
|
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
@@ -107,9 +121,19 @@ export class Aggregate extends AggregateBase {
|
|
|
107
121
|
});
|
|
108
122
|
// The SessionEntity class can emit a message indicating the session was resumed (visibility change). This feature must start running again (if already running) if that occurs.
|
|
109
123
|
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
124
|
+
// if the mode changed on a different tab, it needs to update this instance to match
|
|
125
|
+
const {
|
|
126
|
+
session
|
|
127
|
+
} = getRuntime(this.agentIdentifier);
|
|
128
|
+
this.mode = session.state.sessionReplay;
|
|
110
129
|
if (!this.initialized || this.mode === MODE.OFF) return;
|
|
111
130
|
this.startRecording();
|
|
112
131
|
});
|
|
132
|
+
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
133
|
+
if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
134
|
+
if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
|
|
135
|
+
this.mode = data.sessionReplay;
|
|
136
|
+
});
|
|
113
137
|
|
|
114
138
|
// Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
|
|
115
139
|
this.scheduler = new HarvestScheduler('browser/blobs', {
|
|
@@ -125,7 +149,7 @@ export class Aggregate extends AggregateBase {
|
|
|
125
149
|
this.hasError = true;
|
|
126
150
|
this.errorNoticed = true;
|
|
127
151
|
// run once
|
|
128
|
-
if (this.mode === MODE.ERROR) {
|
|
152
|
+
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
129
153
|
this.mode = MODE.FULL;
|
|
130
154
|
// if the error was noticed AFTER the recorder was already imported....
|
|
131
155
|
if (recorder && this.initialized) {
|
|
@@ -185,7 +209,7 @@ export class Aggregate extends AggregateBase {
|
|
|
185
209
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
186
210
|
recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
|
|
187
211
|
} catch (err) {
|
|
188
|
-
return this.abort(
|
|
212
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
189
213
|
}
|
|
190
214
|
|
|
191
215
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
@@ -208,7 +232,6 @@ export class Aggregate extends AggregateBase {
|
|
|
208
232
|
this.shouldCompress = false;
|
|
209
233
|
}
|
|
210
234
|
this.startRecording();
|
|
211
|
-
this.isFirstChunk = !!session.isNew;
|
|
212
235
|
this.syncWithSessionManager({
|
|
213
236
|
sessionReplay: this.mode
|
|
214
237
|
});
|
|
@@ -223,14 +246,39 @@ export class Aggregate extends AggregateBase {
|
|
|
223
246
|
this.scheduler.opts.gzip = false;
|
|
224
247
|
}
|
|
225
248
|
// TODO -- Gracefully handle the buffer for retries.
|
|
249
|
+
const {
|
|
250
|
+
session
|
|
251
|
+
} = getRuntime(this.agentIdentifier);
|
|
252
|
+
if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
253
|
+
sessionReplaySentFirstChunk: true
|
|
254
|
+
});
|
|
226
255
|
this.clearBuffer();
|
|
227
256
|
return [payload];
|
|
228
257
|
}
|
|
229
258
|
getHarvestContents() {
|
|
230
259
|
const agentRuntime = getRuntime(this.agentIdentifier);
|
|
231
260
|
const info = getInfo(this.agentIdentifier);
|
|
232
|
-
|
|
233
|
-
|
|
261
|
+
|
|
262
|
+
// do not let the last node be a meta node, since this NEEDS to precede a snapshot
|
|
263
|
+
// we will manually inject it later if we find a payload that is missing a meta node
|
|
264
|
+
const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
|
|
265
|
+
if (payloadEndsWithMeta) {
|
|
266
|
+
this.lastMeta = this.events[this.events.length - 1];
|
|
267
|
+
this.events = this.events.slice(0, this.events.length - 1);
|
|
268
|
+
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
272
|
+
// we will manually inject it if this happens
|
|
273
|
+
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
|
|
274
|
+
if (payloadStartsWithFullSnapshot) {
|
|
275
|
+
this.hasMeta = true;
|
|
276
|
+
this.events.unshift(this.lastMeta);
|
|
277
|
+
}
|
|
278
|
+
const firstEventTimestamp = this.events[0]?.timestamp;
|
|
279
|
+
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
|
|
280
|
+
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
|
|
281
|
+
const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
|
|
234
282
|
return {
|
|
235
283
|
qs: {
|
|
236
284
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -244,12 +292,13 @@ export class Aggregate extends AggregateBase {
|
|
|
244
292
|
'replay.firstTimestamp': firstTimestamp,
|
|
245
293
|
'replay.lastTimestamp': lastTimestamp,
|
|
246
294
|
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
295
|
+
'replay.nodes': this.events.length,
|
|
247
296
|
agentVersion: agentRuntime.version,
|
|
248
297
|
session: agentRuntime.session.state.value,
|
|
249
298
|
hasMeta: this.hasMeta,
|
|
250
299
|
hasSnapshot: this.hasSnapshot,
|
|
251
300
|
hasError: this.hasError,
|
|
252
|
-
isFirstChunk:
|
|
301
|
+
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
253
302
|
decompressedBytes: this.payloadBytesEstimation,
|
|
254
303
|
'nr.rrweb.version': RRWEB_VERSION
|
|
255
304
|
}, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
|
|
@@ -261,7 +310,7 @@ export class Aggregate extends AggregateBase {
|
|
|
261
310
|
onHarvestFinished(result) {
|
|
262
311
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
263
312
|
if (result.status === 429) {
|
|
264
|
-
this.abort(
|
|
313
|
+
this.abort(ABORT_REASONS.TOO_MANY);
|
|
265
314
|
}
|
|
266
315
|
if (this.blocked) this.scheduler.stopTimer(true);
|
|
267
316
|
}
|
|
@@ -269,7 +318,6 @@ export class Aggregate extends AggregateBase {
|
|
|
269
318
|
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
270
319
|
clearBuffer() {
|
|
271
320
|
this.events = [];
|
|
272
|
-
this.isFirstChunk = false;
|
|
273
321
|
this.hasSnapshot = false;
|
|
274
322
|
this.hasMeta = false;
|
|
275
323
|
this.hasError = false;
|
|
@@ -281,7 +329,7 @@ export class Aggregate extends AggregateBase {
|
|
|
281
329
|
startRecording() {
|
|
282
330
|
if (!recorder) {
|
|
283
331
|
warn('Recording library was never imported');
|
|
284
|
-
return this.abort(
|
|
332
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
285
333
|
}
|
|
286
334
|
this.clearTimestamps();
|
|
287
335
|
// set the fallbacks as early as possible
|
|
@@ -317,7 +365,7 @@ export class Aggregate extends AggregateBase {
|
|
|
317
365
|
|
|
318
366
|
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
319
367
|
store(event, isCheckout) {
|
|
320
|
-
this.setTimestamps(
|
|
368
|
+
this.setTimestamps();
|
|
321
369
|
if (this.blocked) return;
|
|
322
370
|
const eventBytes = stringify(event).length;
|
|
323
371
|
/** The estimated size of the payload after compression */
|
|
@@ -325,8 +373,7 @@ export class Aggregate extends AggregateBase {
|
|
|
325
373
|
// Vortex will block payloads at a certain size, we might as well not send.
|
|
326
374
|
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
327
375
|
this.clearBuffer();
|
|
328
|
-
this.
|
|
329
|
-
return this.abort('Payload too big');
|
|
376
|
+
return this.abort(ABORT_REASONS.TOO_BIG);
|
|
330
377
|
}
|
|
331
378
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
332
379
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
@@ -337,19 +384,13 @@ export class Aggregate extends AggregateBase {
|
|
|
337
384
|
}
|
|
338
385
|
|
|
339
386
|
// meta event
|
|
340
|
-
if (event.type ===
|
|
387
|
+
if (event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
341
388
|
this.hasMeta = true;
|
|
342
389
|
this.lastMeta = event;
|
|
343
390
|
}
|
|
344
391
|
// snapshot event
|
|
345
|
-
if (event.type ===
|
|
392
|
+
if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
346
393
|
this.hasSnapshot = true;
|
|
347
|
-
// small chance that the meta event got separated from its matching snapshot across payload harvests
|
|
348
|
-
// it needs to precede the snapshot, so shove it in first.
|
|
349
|
-
if (!this.hasMeta) {
|
|
350
|
-
this.events.push(this.lastMeta);
|
|
351
|
-
this.hasMeta = true;
|
|
352
|
-
}
|
|
353
394
|
}
|
|
354
395
|
this.events.push(event);
|
|
355
396
|
this.payloadBytesEstimation += eventBytes;
|
|
@@ -367,26 +408,12 @@ export class Aggregate extends AggregateBase {
|
|
|
367
408
|
if (!recorder) return;
|
|
368
409
|
recorder.takeFullSnapshot();
|
|
369
410
|
}
|
|
370
|
-
setTimestamps(
|
|
411
|
+
setTimestamps() {
|
|
371
412
|
// fallbacks if timestamps cannot be derived from rrweb events
|
|
372
|
-
this.
|
|
373
|
-
if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
|
|
374
|
-
// timestamps based on rrweb events
|
|
375
|
-
if (!event || !event.timestamp) return;
|
|
376
|
-
if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
|
|
377
|
-
this.timestamp.event.last = event.timestamp;
|
|
413
|
+
if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
|
|
378
414
|
}
|
|
379
415
|
clearTimestamps() {
|
|
380
|
-
this.
|
|
381
|
-
event: {
|
|
382
|
-
first: undefined,
|
|
383
|
-
last: undefined
|
|
384
|
-
},
|
|
385
|
-
cycle: {
|
|
386
|
-
first: undefined,
|
|
387
|
-
last: undefined
|
|
388
|
-
}
|
|
389
|
-
};
|
|
416
|
+
this.cycleTimestamp = undefined;
|
|
390
417
|
}
|
|
391
418
|
|
|
392
419
|
/** Estimate the payload size */
|
|
@@ -397,8 +424,10 @@ export class Aggregate extends AggregateBase {
|
|
|
397
424
|
}
|
|
398
425
|
|
|
399
426
|
/** Abort the feature, once aborted it will not resume */
|
|
400
|
-
abort(
|
|
401
|
-
|
|
427
|
+
abort() {
|
|
428
|
+
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
429
|
+
warn("SR aborted -- ".concat(reason.message));
|
|
430
|
+
this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
|
|
402
431
|
this.blocked = true;
|
|
403
432
|
this.mode = MODE.OFF;
|
|
404
433
|
this.stopRecording();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getRuntime } from '../../common/config/config';
|
|
2
1
|
import { ee } from '../../common/event-emitter/contextual-ee';
|
|
3
2
|
export class FeatureBase {
|
|
4
3
|
constructor(agentIdentifier, aggregator, featureName) {
|
|
@@ -7,7 +6,7 @@ export class FeatureBase {
|
|
|
7
6
|
/** @type {Aggregator} */
|
|
8
7
|
this.aggregator = aggregator;
|
|
9
8
|
/** @type {ContextualEE} */
|
|
10
|
-
this.ee = ee.get(agentIdentifier
|
|
9
|
+
this.ee = ee.get(agentIdentifier);
|
|
11
10
|
/** @type {string} */
|
|
12
11
|
this.featureName = featureName;
|
|
13
12
|
/**
|
|
@@ -17,7 +17,7 @@ export const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stor
|
|
|
17
17
|
|
|
18
18
|
export function setTopLevelCallers() {
|
|
19
19
|
const nr = gosCDN();
|
|
20
|
-
const funcs = ['setErrorHandler', 'finished', 'addToTrace', '
|
|
20
|
+
const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
|
|
21
21
|
funcs.forEach(f => {
|
|
22
22
|
nr[f] = function () {
|
|
23
23
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
@@ -44,7 +44,7 @@ export function setAPI(agentIdentifier, forceDrain) {
|
|
|
44
44
|
const apiInterface = {};
|
|
45
45
|
var instanceEE = ee.get(agentIdentifier);
|
|
46
46
|
var tracerEE = instanceEE.get('tracer');
|
|
47
|
-
var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', '
|
|
47
|
+
var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
|
|
48
48
|
var prefix = 'api-';
|
|
49
49
|
var spaPrefix = prefix + 'ixn-';
|
|
50
50
|
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { FEATURE_NAMES } from '../features/features';
|
|
2
|
-
import {
|
|
2
|
+
import { getRuntime } from '../../common/config/config';
|
|
3
3
|
import { ee } from '../../common/event-emitter/contextual-ee';
|
|
4
4
|
import { handle } from '../../common/event-emitter/handle';
|
|
5
5
|
import { registerHandler } from '../../common/event-emitter/register-handler';
|
|
6
6
|
import { single } from '../../common/util/invoke';
|
|
7
|
-
import * as submitData from '../../common/util/submit-data';
|
|
8
|
-
import { isBrowserScope } from '../../common/constants/runtime';
|
|
9
7
|
import { CUSTOM_METRIC_CHANNEL } from '../../features/metrics/constants';
|
|
10
8
|
export function setAPI(agentIdentifier) {
|
|
11
9
|
var instanceEE = ee.get(agentIdentifier);
|
|
12
|
-
var cycle = 0;
|
|
13
10
|
var api = {
|
|
14
11
|
finished: single(finished),
|
|
15
12
|
setErrorHandler,
|
|
16
13
|
addToTrace,
|
|
17
|
-
inlineHit,
|
|
18
14
|
addRelease
|
|
19
15
|
};
|
|
20
16
|
|
|
@@ -50,37 +46,6 @@ export function setAPI(agentIdentifier) {
|
|
|
50
46
|
};
|
|
51
47
|
handle('bstApi', [report], undefined, FEATURE_NAMES.sessionTrace, instanceEE);
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
// NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
|
|
55
|
-
//
|
|
56
|
-
// requestName - the 'web page' name or service name
|
|
57
|
-
// queueTime - the amount of time spent in the app tier queue
|
|
58
|
-
// appTime - the amount of time spent in the application code
|
|
59
|
-
// totalBackEndTime - the total roundtrip time of the remote service call
|
|
60
|
-
// domTime - the time spent processing the result of the service call (or user defined)
|
|
61
|
-
// frontEndTime - the time spent rendering the result of the service call (or user defined)
|
|
62
|
-
function inlineHit(t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
|
|
63
|
-
if (!isBrowserScope) return;
|
|
64
|
-
requestName = window.encodeURIComponent(requestName);
|
|
65
|
-
cycle += 1;
|
|
66
|
-
const agentInfo = getInfo(agentIdentifier);
|
|
67
|
-
if (!agentInfo.beacon) return;
|
|
68
|
-
const agentInit = getConfiguration(agentIdentifier);
|
|
69
|
-
const scheme = agentInit.ssl === false ? 'http' : 'https';
|
|
70
|
-
const beacon = agentInit.proxy.beacon || agentInfo.beacon;
|
|
71
|
-
let url = "".concat(scheme, "://").concat(beacon, "/1/").concat(agentInfo.licenseKey);
|
|
72
|
-
url += '?a=' + agentInfo.applicationID + '&';
|
|
73
|
-
url += 't=' + requestName + '&';
|
|
74
|
-
url += 'qt=' + ~~queueTime + '&';
|
|
75
|
-
url += 'ap=' + ~~appTime + '&';
|
|
76
|
-
url += 'be=' + ~~totalBackEndTime + '&';
|
|
77
|
-
url += 'dc=' + ~~domTime + '&';
|
|
78
|
-
url += 'fe=' + ~~frontEndTime + '&';
|
|
79
|
-
url += 'c=' + cycle;
|
|
80
|
-
submitData.xhr({
|
|
81
|
-
url
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
49
|
function setErrorHandler(t, handler) {
|
|
85
50
|
getRuntime(agentIdentifier).onerror = handler;
|
|
86
51
|
}
|
|
@@ -41,7 +41,7 @@ export function configure(agentIdentifier) {
|
|
|
41
41
|
if (!alreadySetOnce) {
|
|
42
42
|
alreadySetOnce = true;
|
|
43
43
|
if (updatedInit.proxy.assets) {
|
|
44
|
-
redefinePublicPath(updatedInit.proxy.assets
|
|
44
|
+
redefinePublicPath(updatedInit.proxy.assets);
|
|
45
45
|
internalTrafficList.push(updatedInit.proxy.assets);
|
|
46
46
|
}
|
|
47
47
|
if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
|
|
2
2
|
|
|
3
|
-
export const redefinePublicPath =
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const redefinePublicPath = urlString => {
|
|
4
|
+
const isOrigin = urlString.startsWith('http');
|
|
5
|
+
// Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
|
|
6
|
+
urlString += '/';
|
|
7
|
+
// If there's no existing HTTP scheme, the secure protocol is prepended by default.
|
|
8
|
+
__webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString; // eslint-disable-line
|
|
6
9
|
};
|
|
@@ -7,6 +7,11 @@ export namespace SESSION_EVENTS {
|
|
|
7
7
|
const PAUSE: string;
|
|
8
8
|
const RESET: string;
|
|
9
9
|
const RESUME: string;
|
|
10
|
+
const UPDATE: string;
|
|
11
|
+
}
|
|
12
|
+
export namespace SESSION_EVENT_TYPES {
|
|
13
|
+
const SAME_TAB: string;
|
|
14
|
+
const CROSS_TAB: string;
|
|
10
15
|
}
|
|
11
16
|
export class SessionEntity {
|
|
12
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-entity.d.ts","sourceRoot":"","sources":["../../../../src/common/session/session-entity.js"],"names":[],"mappings":";;;;;;;;;;;;;;;AA4CA;IACE;;;;;OAKG;IACH,uBA0BC;IApBC,qBAAsC;IACtC,aAAsB;IACtB,UAAe;IAGf,SAAc;IAEd,QAAiC;IAenC;;;;aAsEC;IA/DC,8BAA0B;IAC1B,+BAA4B;IAc1B,gCAOqC;IAUrC,4CAiBsC;IAOV,2BAA6C;IAM3E,iCAAuB;IAIzB,wBAEC;IAED,sBAEC;IAED;;;OAGG;IACH,QAFa,MAAM,CA6BlB;IAED;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,MAAM,CAkBlB;IAED,gBAuBC;IAED;;OAEG;IACH,gBAIC;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,gBAHW,MAAM,GACJ,OAAO,CAKnB;IAED,yDAYC;IAED;;;OAGG;IACH,6BAHW,MAAM,GACJ,MAAM,CAIlB;IAED,gDAaC;IAHG,YAAuD;CAI5D;sBA7SqB,gBAAgB;iCAGL,4BAA4B"}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export const RRWEB_VERSION: "2.0.0-alpha.8";
|
|
2
2
|
export const AVG_COMPRESSION: 0.12;
|
|
3
|
+
export namespace RRWEB_EVENT_TYPES {
|
|
4
|
+
const DomContentLoaded: number;
|
|
5
|
+
const Load: number;
|
|
6
|
+
const FullSnapshot: number;
|
|
7
|
+
const IncrementalSnapshot: number;
|
|
8
|
+
const Meta: number;
|
|
9
|
+
const Custom: number;
|
|
10
|
+
}
|
|
3
11
|
/** Vortex caps payload sizes at 1MB */
|
|
4
12
|
export const MAX_PAYLOAD_SIZE: 1000000;
|
|
5
13
|
/** Unloading caps around 64kb */
|
|
@@ -21,8 +29,6 @@ export class Aggregate extends AggregateBase {
|
|
|
21
29
|
recording: boolean;
|
|
22
30
|
/** can shut off efforts to compress the data */
|
|
23
31
|
shouldCompress: boolean;
|
|
24
|
-
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
25
|
-
isFirstChunk: boolean;
|
|
26
32
|
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
27
33
|
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
28
34
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
@@ -35,16 +41,7 @@ export class Aggregate extends AggregateBase {
|
|
|
35
41
|
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
36
42
|
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
37
43
|
*/
|
|
38
|
-
|
|
39
|
-
event: {
|
|
40
|
-
first: undefined;
|
|
41
|
-
last: undefined;
|
|
42
|
-
};
|
|
43
|
-
cycle: {
|
|
44
|
-
first: undefined;
|
|
45
|
-
last: undefined;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
44
|
+
cycleTimestamp: any;
|
|
48
45
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
49
46
|
payloadBytesEstimation: number;
|
|
50
47
|
/** 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 */
|
|
@@ -89,12 +86,12 @@ export class Aggregate extends AggregateBase {
|
|
|
89
86
|
store(event: any, isCheckout: any): void;
|
|
90
87
|
/** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
|
|
91
88
|
takeFullSnapshot(): void;
|
|
92
|
-
setTimestamps(
|
|
89
|
+
setTimestamps(): void;
|
|
93
90
|
clearTimestamps(): void;
|
|
94
91
|
/** Estimate the payload size */
|
|
95
92
|
getPayloadSize(newBytes?: number): any;
|
|
96
93
|
/** Abort the feature, once aborted it will not resume */
|
|
97
|
-
abort(reason
|
|
94
|
+
abort(reason?: {}): void;
|
|
98
95
|
/** Extensive research has yielded about an 88% compression factor on these payloads.
|
|
99
96
|
* This is an estimation using that factor as to not cause performance issues while evaluating
|
|
100
97
|
* https://staging.onenr.io/037jbJWxbjy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA0BA,4CAA4C;AAE5C,mCAAmC;;;;;;;;;AAgCnC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAIvC;IACE,2BAAiC;IACjC,mDA2GC;IAzGC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B;;;MAGE;IACF,qBAAwB;IACxB,4IAA4I;IAC5I,iBAAoB;IACpB,+HAA+H;IAC/H,kBAAqB;IAErB;;OAEG;IACH,oBAA+B;IAE/B,qGAAqG;IACrG,+BAA+B;IAE/B,kIAAkI;IAClI,cAAyB;IAOzB,uIAAuI;IACvI,0BAAyE;IA0BvE,wCAKQ;IAgCZ;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAuDhB;IAED;;;;;;;;;oBAeC;IAED;;;;;;;;;MAiDC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAOC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCAsCC;IAED,0HAA0H;IAC1H,yBAGC;IAED,sBAGC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED,yDAAyD;IACzD,yBASC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA3a6B,4BAA4B;iCALzB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/feature-base.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"feature-base.d.ts","sourceRoot":"","sources":["../../../../src/features/utils/feature-base.js"],"names":[],"mappings":"AAEA;IACE,qEAeC;IAdC,qBAAqB;IACrB,iBADW,MAAM,CACqB;IACtC,yBAAyB;IACzB,uBAA4B;IAC5B,2BAA2B;IAC3B,iBAAiC;IACjC,qBAAqB;IACrB,aADW,MAAM,CACa;IAC9B;;;;OAIG;IACH,SAFU,OAAO,CAEG;CAEvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/api.js"],"names":[],"mappings":"AAkBA,2CAoBC;AAED;;;;;
|
|
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;;;;EA0FvB;AA1LD,0CAA0C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiAsync.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/apiAsync.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apiAsync.d.ts","sourceRoot":"","sources":["../../../../src/loaders/api/apiAsync.js"],"names":[],"mappings":"AAQA,mDA8CC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export function redefinePublicPath(
|
|
1
|
+
export function redefinePublicPath(urlString: any): void;
|
|
2
2
|
//# sourceMappingURL=public-path.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-path.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.js"],"names":[],"mappings":"AAEO,
|
|
1
|
+
{"version":3,"file":"public-path.d.ts","sourceRoot":"","sources":["../../../../src/loaders/configure/public-path.js"],"names":[],"mappings":"AAEO,yDAMN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.243.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -266,6 +266,6 @@
|
|
|
266
266
|
"src",
|
|
267
267
|
"LICENSE",
|
|
268
268
|
"README.md",
|
|
269
|
-
"
|
|
269
|
+
"CHANGELOG.md"
|
|
270
270
|
]
|
|
271
271
|
}
|
|
@@ -11,6 +11,7 @@ import { getModeledObject } from '../config/state/configurable'
|
|
|
11
11
|
import { handle } from '../event-emitter/handle'
|
|
12
12
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants'
|
|
13
13
|
import { FEATURE_NAMES } from '../../loaders/features/features'
|
|
14
|
+
import { windowAddEventListener } from '../event-listener/event-listener-opts'
|
|
14
15
|
|
|
15
16
|
export const MODE = {
|
|
16
17
|
OFF: 0,
|
|
@@ -25,13 +26,20 @@ const model = {
|
|
|
25
26
|
expiresAt: 0,
|
|
26
27
|
updatedAt: Date.now(),
|
|
27
28
|
sessionReplay: MODE.OFF,
|
|
29
|
+
sessionReplaySentFirstChunk: false,
|
|
28
30
|
sessionTraceMode: MODE.OFF,
|
|
29
31
|
custom: {}
|
|
30
32
|
}
|
|
31
33
|
export const SESSION_EVENTS = {
|
|
32
34
|
PAUSE: 'session-pause',
|
|
33
35
|
RESET: 'session-reset',
|
|
34
|
-
RESUME: 'session-resume'
|
|
36
|
+
RESUME: 'session-resume',
|
|
37
|
+
UPDATE: 'session-update'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const SESSION_EVENT_TYPES = {
|
|
41
|
+
SAME_TAB: 'same-tab',
|
|
42
|
+
CROSS_TAB: 'cross-tab'
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
export class SessionEntity {
|
|
@@ -57,6 +65,16 @@ export class SessionEntity {
|
|
|
57
65
|
this.ee = ee.get(agentIdentifier)
|
|
58
66
|
wrapEvents(this.ee)
|
|
59
67
|
this.setup(opts)
|
|
68
|
+
|
|
69
|
+
if (isBrowserScope) {
|
|
70
|
+
windowAddEventListener('storage', (event) => {
|
|
71
|
+
if (event.key === this.lookupKey) {
|
|
72
|
+
const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue
|
|
73
|
+
this.sync(obj)
|
|
74
|
+
this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state])
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
setup ({ value = generateRandomHexString(16), expiresMs = DEFAULT_EXPIRES_MS, inactiveMs = DEFAULT_INACTIVE_MS }) {
|
|
@@ -189,6 +207,7 @@ export class SessionEntity {
|
|
|
189
207
|
//
|
|
190
208
|
// TODO - compression would need happen here if we decide to do it
|
|
191
209
|
this.storage.set(this.lookupKey, stringify(this.state))
|
|
210
|
+
this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state])
|
|
192
211
|
return data
|
|
193
212
|
} catch (e) {
|
|
194
213
|
// storage is inaccessible
|
|
@@ -215,5 +215,5 @@ function copy (from, to, emitter) {
|
|
|
215
215
|
* @returns {boolean} Whether the passed function is ineligible to be wrapped.
|
|
216
216
|
*/
|
|
217
217
|
function notWrappable (fn) {
|
|
218
|
-
return !(fn && fn
|
|
218
|
+
return !(fn && typeof fn === 'function' && fn.apply && !fn[flag])
|
|
219
219
|
}
|
|
@@ -81,8 +81,6 @@ export class Aggregate extends AggregateBase {
|
|
|
81
81
|
hash = stringify([params.status, params.host, params.pathname])
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee)
|
|
85
|
-
|
|
86
84
|
// store as metric
|
|
87
85
|
aggregator.store('xhr', hash, params, metrics)
|
|
88
86
|
|
|
@@ -98,6 +96,8 @@ export class Aggregate extends AggregateBase {
|
|
|
98
96
|
return
|
|
99
97
|
}
|
|
100
98
|
|
|
99
|
+
handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee)
|
|
100
|
+
|
|
101
101
|
var xhrContext = this
|
|
102
102
|
|
|
103
103
|
var event = {
|