@newrelic/browser-agent 1.316.0 → 1.317.0-rc.1
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 +15 -0
- package/dist/cjs/common/config/configurable.js +1 -1
- package/dist/cjs/common/config/init-types.js +1 -0
- package/dist/cjs/common/config/init.js +6 -4
- package/dist/cjs/common/constants/env.cdn.js +2 -2
- package/dist/cjs/common/constants/env.npm.js +2 -2
- package/dist/cjs/common/constants/events.js +26 -0
- package/dist/cjs/common/harvest/harvester.js +9 -217
- package/dist/cjs/common/harvest/send.js +232 -0
- package/dist/cjs/common/harvest/types.js +9 -5
- package/dist/cjs/common/payloads/payloads.js +127 -0
- package/dist/cjs/common/serialize/bel-serializer.js +4 -3
- package/dist/cjs/common/session/session-entity.js +8 -8
- package/dist/cjs/common/util/data-size.js +2 -2
- package/dist/cjs/common/util/obfuscate.js +147 -3
- package/dist/cjs/common/v2/mfe-vitals.js +286 -0
- package/dist/cjs/common/wrap/wrap-websocket.js +2 -1
- package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
- package/dist/cjs/features/ajax/aggregate/gql.js +40 -1
- package/dist/cjs/features/ajax/aggregate/index.js +51 -13
- package/dist/cjs/features/ajax/constants.js +6 -1
- package/dist/cjs/features/ajax/instrument/index.js +105 -17
- package/dist/cjs/features/generic_events/aggregate/index.js +15 -10
- package/dist/cjs/features/generic_events/constants.js +3 -2
- package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +2 -2
- package/dist/cjs/features/jserrors/aggregate/index.js +12 -3
- package/dist/cjs/features/logging/aggregate/index.js +12 -8
- package/dist/cjs/features/metrics/aggregate/index.js +5 -1
- package/dist/cjs/features/page_view_event/aggregate/index.js +72 -33
- package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +39 -29
- package/dist/cjs/features/session_trace/aggregate/index.js +35 -29
- package/dist/cjs/features/session_trace/constants.js +17 -3
- package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +36 -5
- package/dist/cjs/features/soft_navigations/aggregate/index.js +7 -1
- package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
- package/dist/cjs/features/soft_navigations/aggregate/interaction.js +6 -4
- package/dist/cjs/features/utils/aggregate-base.js +4 -3
- package/dist/cjs/loaders/api/register.js +26 -4
- package/dist/esm/common/config/configurable.js +1 -1
- package/dist/esm/common/config/init-types.js +1 -0
- package/dist/esm/common/config/init.js +3 -1
- package/dist/esm/common/constants/env.cdn.js +2 -2
- package/dist/esm/common/constants/env.npm.js +2 -2
- package/dist/esm/common/constants/events.js +20 -0
- package/dist/esm/common/harvest/harvester.js +9 -217
- package/dist/esm/common/harvest/send.js +226 -0
- package/dist/esm/common/harvest/types.js +9 -5
- package/dist/esm/common/payloads/payloads.js +118 -0
- package/dist/esm/common/serialize/bel-serializer.js +4 -3
- package/dist/esm/common/session/session-entity.js +9 -9
- package/dist/esm/common/util/data-size.js +2 -2
- package/dist/esm/common/util/obfuscate.js +147 -3
- package/dist/esm/common/v2/mfe-vitals.js +281 -0
- package/dist/esm/common/wrap/wrap-websocket.js +2 -1
- package/dist/esm/common/wrap/wrap-xhr.js +1 -1
- package/dist/esm/features/ajax/aggregate/gql.js +39 -1
- package/dist/esm/features/ajax/aggregate/index.js +52 -14
- package/dist/esm/features/ajax/constants.js +5 -0
- package/dist/esm/features/ajax/instrument/index.js +106 -18
- package/dist/esm/features/generic_events/aggregate/index.js +15 -10
- package/dist/esm/features/generic_events/constants.js +3 -2
- package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +2 -2
- package/dist/esm/features/jserrors/aggregate/index.js +12 -3
- package/dist/esm/features/logging/aggregate/index.js +12 -8
- package/dist/esm/features/metrics/aggregate/index.js +5 -1
- package/dist/esm/features/page_view_event/aggregate/index.js +71 -32
- package/dist/esm/features/page_view_timing/aggregate/index.js +6 -1
- package/dist/esm/features/session_replay/aggregate/index.js +39 -29
- package/dist/esm/features/session_trace/aggregate/index.js +36 -30
- package/dist/esm/features/session_trace/constants.js +16 -2
- package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +36 -5
- package/dist/esm/features/soft_navigations/aggregate/index.js +7 -1
- package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
- package/dist/esm/features/soft_navigations/aggregate/interaction.js +6 -4
- package/dist/esm/features/utils/aggregate-base.js +4 -3
- package/dist/esm/loaders/api/register.js +26 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +4 -0
- package/dist/types/common/config/init-types.d.ts.map +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/constants/events.d.ts +16 -0
- package/dist/types/common/constants/events.d.ts.map +1 -0
- package/dist/types/common/harvest/harvester.d.ts +2 -16
- package/dist/types/common/harvest/harvester.d.ts.map +1 -1
- package/dist/types/common/harvest/send.d.ts +9 -0
- package/dist/types/common/harvest/send.d.ts.map +1 -0
- package/dist/types/common/harvest/types.d.ts +39 -10
- package/dist/types/common/harvest/types.d.ts.map +1 -1
- package/dist/types/common/payloads/payloads.d.ts +42 -0
- package/dist/types/common/payloads/payloads.d.ts.map +1 -0
- package/dist/types/common/serialize/bel-serializer.d.ts +1 -1
- package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/util/obfuscate.d.ts +23 -2
- package/dist/types/common/util/obfuscate.d.ts.map +1 -1
- package/dist/types/common/v2/mfe-vitals.d.ts +14 -0
- package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -0
- package/dist/types/common/wrap/wrap-websocket.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/gql.d.ts +8 -0
- package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts +2 -0
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/ajax/constants.d.ts +5 -0
- package/dist/types/features/ajax/constants.d.ts.map +1 -1
- package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +3 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/constants.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +8 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +3 -1
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts +2 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +3 -0
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -0
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +4 -2
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +3 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/constants.d.ts +17 -0
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +6 -1
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -0
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -1
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +0 -1
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/common/config/configurable.js +1 -1
- package/src/common/config/init-types.js +1 -0
- package/src/common/config/init.js +2 -1
- package/src/common/constants/events.js +20 -0
- package/src/common/harvest/harvester.js +9 -201
- package/src/common/harvest/send.js +209 -0
- package/src/common/harvest/types.js +9 -5
- package/src/common/payloads/payloads.js +135 -0
- package/src/common/serialize/bel-serializer.js +4 -3
- package/src/common/session/session-entity.js +8 -9
- package/src/common/util/data-size.js +2 -2
- package/src/common/util/obfuscate.js +154 -3
- package/src/common/v2/mfe-vitals.js +260 -0
- package/src/common/wrap/wrap-websocket.js +2 -1
- package/src/common/wrap/wrap-xhr.js +1 -1
- package/src/features/ajax/aggregate/gql.js +42 -1
- package/src/features/ajax/aggregate/index.js +43 -13
- package/src/features/ajax/constants.js +5 -1
- package/src/features/ajax/instrument/index.js +114 -18
- package/src/features/generic_events/aggregate/index.js +14 -9
- package/src/features/generic_events/constants.js +3 -2
- package/src/features/jserrors/aggregate/format-stack-trace.js +3 -3
- package/src/features/jserrors/aggregate/index.js +12 -3
- package/src/features/logging/aggregate/index.js +12 -11
- package/src/features/metrics/aggregate/index.js +5 -1
- package/src/features/page_view_event/aggregate/index.js +58 -29
- package/src/features/page_view_timing/aggregate/index.js +6 -1
- package/src/features/session_replay/aggregate/index.js +37 -28
- package/src/features/session_trace/aggregate/index.js +37 -30
- package/src/features/session_trace/constants.js +7 -1
- package/src/features/soft_navigations/aggregate/ajax-node.js +26 -5
- package/src/features/soft_navigations/aggregate/index.js +7 -1
- package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
- package/src/features/soft_navigations/aggregate/interaction.js +6 -4
- package/src/features/utils/aggregate-base.js +3 -3
- package/src/loaders/api/register.js +24 -4
|
@@ -9,6 +9,7 @@ import { handle } from '../../../common/event-emitter/handle';
|
|
|
9
9
|
import { FEATURE_NAME } from '../constants';
|
|
10
10
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
11
11
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
12
|
+
import { Obfuscator } from '../../../common/util/obfuscate';
|
|
12
13
|
import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift';
|
|
13
14
|
import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint';
|
|
14
15
|
import { firstPaint } from '../../../common/vitals/first-paint';
|
|
@@ -21,6 +22,7 @@ import { eventOrigin } from '../../../common/util/event-origin';
|
|
|
21
22
|
import { loadTime } from '../../../common/vitals/load-time';
|
|
22
23
|
import { webdriverDetected } from '../../../common/util/webdriver-detection';
|
|
23
24
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
25
|
+
import { EVENT_TYPES } from '../../../common/constants/events';
|
|
24
26
|
export class Aggregate extends AggregateBase {
|
|
25
27
|
static featureName = FEATURE_NAME;
|
|
26
28
|
#handleVitalMetric = ({
|
|
@@ -35,6 +37,9 @@ export class Aggregate extends AggregateBase {
|
|
|
35
37
|
this.curSessEndRecorded = false;
|
|
36
38
|
this.firstIxnRecorded = false;
|
|
37
39
|
super.customAttributesAreSeparate = true;
|
|
40
|
+
|
|
41
|
+
// Create obfuscator for page view timing events
|
|
42
|
+
this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.PVT);
|
|
38
43
|
registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
|
|
39
44
|
// Add the time of _window pagehide event_ firing to the next PVT harvest == NRDB windowUnload attr:
|
|
40
45
|
registerHandler('winPagehide', msTimestamp => this.addTiming('unload', msTimestamp, null), this.featureName, this.ee);
|
|
@@ -140,7 +145,7 @@ export class Aggregate extends AggregateBase {
|
|
|
140
145
|
// serialize array of timing data
|
|
141
146
|
serializer(eventBuffer) {
|
|
142
147
|
if (!eventBuffer?.length) return '';
|
|
143
|
-
var addString = getAddStringContext(this.
|
|
148
|
+
var addString = getAddStringContext(this.obfuscator);
|
|
144
149
|
var payload = 'bel.6;';
|
|
145
150
|
for (var i = 0; i < eventBuffer.length; i++) {
|
|
146
151
|
var timing = eventBuffer[i];
|
|
@@ -19,16 +19,22 @@ import { stringify } from '../../../common/util/stringify';
|
|
|
19
19
|
import { stylesheetEvaluator } from '../shared/stylesheet-evaluator';
|
|
20
20
|
import { now } from '../../../common/timing/now';
|
|
21
21
|
import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants';
|
|
22
|
+
import { EVENT_TYPES } from '../../../common/constants/events';
|
|
22
23
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
23
24
|
import { canEnableSessionTracking } from '../../utils/feature-gates';
|
|
24
25
|
import { PAUSE_REPLAY } from '../../../loaders/api/constants';
|
|
26
|
+
import { Obfuscator } from '../../../common/util/obfuscate';
|
|
25
27
|
export class Aggregate extends AggregateBase {
|
|
26
28
|
static featureName = FEATURE_NAME;
|
|
27
|
-
mode =
|
|
29
|
+
mode = null;
|
|
28
30
|
|
|
29
31
|
// pass the recorder into the aggregator
|
|
30
32
|
constructor(agentRef, args) {
|
|
31
33
|
super(agentRef, FEATURE_NAME);
|
|
34
|
+
|
|
35
|
+
// Create obfuscator for session replay query params
|
|
36
|
+
this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.SR);
|
|
37
|
+
|
|
32
38
|
/** Set once the recorder has fully initialized after flag checks and sampling */
|
|
33
39
|
this.initialized = false;
|
|
34
40
|
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
@@ -65,7 +71,7 @@ export class Aggregate extends AggregateBase {
|
|
|
65
71
|
if (!this.recorder) return;
|
|
66
72
|
// if the mode changed on a different tab, it needs to update this instance to match
|
|
67
73
|
this.mode = agentRef.runtime.session.state.sessionReplayMode;
|
|
68
|
-
if (!this.initialized || this.mode === MODE.OFF) return;
|
|
74
|
+
if (!this.initialized || this.mode === MODE.OFF || !this.mode) return;
|
|
69
75
|
this.recorder?.startRecording(TRIGGERS.RESUME, this.mode);
|
|
70
76
|
});
|
|
71
77
|
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
@@ -92,6 +98,10 @@ export class Aggregate extends AggregateBase {
|
|
|
92
98
|
this.waitForFlags(['srs', 'sr']).then(([srMode, entitled]) => {
|
|
93
99
|
this.entitled = !!entitled;
|
|
94
100
|
if (!this.entitled) {
|
|
101
|
+
this.mode = MODE.OFF;
|
|
102
|
+
this.#writeToStorage({
|
|
103
|
+
sessionReplayMode: this.mode
|
|
104
|
+
});
|
|
95
105
|
this.deregisterDrain();
|
|
96
106
|
if (this.agentRef.runtime.isRecording) {
|
|
97
107
|
this.abort(ABORT_REASONS.ENTITLEMENTS);
|
|
@@ -103,7 +113,7 @@ export class Aggregate extends AggregateBase {
|
|
|
103
113
|
this.drain();
|
|
104
114
|
});
|
|
105
115
|
}).then(() => {
|
|
106
|
-
if (this.mode
|
|
116
|
+
if (!this.mode) {
|
|
107
117
|
this.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
|
|
108
118
|
while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
|
|
109
119
|
}
|
|
@@ -136,7 +146,7 @@ export class Aggregate extends AggregateBase {
|
|
|
136
146
|
// if the error was noticed AFTER the recorder was already imported....
|
|
137
147
|
if (this.recorder && this.initialized) {
|
|
138
148
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(TRIGGERS.SWITCH_TO_FULL, this.mode); // off --> full
|
|
139
|
-
this
|
|
149
|
+
this.#writeToStorage({
|
|
140
150
|
sessionReplayMode: this.mode
|
|
141
151
|
});
|
|
142
152
|
} else {
|
|
@@ -168,35 +178,35 @@ export class Aggregate extends AggregateBase {
|
|
|
168
178
|
this.timeKeeper = timeKeeper;
|
|
169
179
|
if (this.recorder?.trigger === TRIGGERS.API && this.agentRef.runtime.isRecording) {
|
|
170
180
|
this.mode = MODE.FULL;
|
|
171
|
-
} else if (
|
|
181
|
+
} else if (session.state.sessionReplayMode !== null && !ignoreSession) {
|
|
172
182
|
// inherit the mode of the existing session
|
|
173
183
|
this.mode = session.state.sessionReplayMode;
|
|
174
184
|
} else {
|
|
175
185
|
// The session is new... determine the mode the new session should start in
|
|
176
186
|
this.mode = srMode;
|
|
177
187
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
188
|
+
if (this.mode !== MODE.OFF) {
|
|
189
|
+
try {
|
|
190
|
+
/** will return a recorder instance if already imported, otherwise, will fetch the recorder and initialize it */
|
|
191
|
+
this.recorder ??= await this.instrumentClass.importRecorder();
|
|
192
|
+
} catch (err) {
|
|
193
|
+
/** if the recorder fails to import, abort the feature */
|
|
194
|
+
return this.abort(ABORT_REASONS.IMPORT, err);
|
|
195
|
+
}
|
|
187
196
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
// If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
|
|
198
|
+
if (this.mode === MODE.ERROR && this.instrumentClass.errorNoticed) {
|
|
199
|
+
this.mode = MODE.FULL;
|
|
200
|
+
}
|
|
192
201
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
202
|
+
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
203
|
+
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
204
|
+
// The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
|
|
196
205
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
await this.prepUtils();
|
|
207
|
+
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(trigger, this.mode);
|
|
208
|
+
}
|
|
209
|
+
this.#writeToStorage({
|
|
200
210
|
sessionReplayMode: this.mode
|
|
201
211
|
});
|
|
202
212
|
}
|
|
@@ -243,7 +253,7 @@ export class Aggregate extends AggregateBase {
|
|
|
243
253
|
this.abort(ABORT_REASONS.TOO_BIG, len);
|
|
244
254
|
return;
|
|
245
255
|
}
|
|
246
|
-
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this
|
|
256
|
+
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.#writeToStorage({
|
|
247
257
|
sessionReplaySentFirstChunk: true
|
|
248
258
|
});
|
|
249
259
|
this.recorder.clearBuffer();
|
|
@@ -306,7 +316,7 @@ export class Aggregate extends AggregateBase {
|
|
|
306
316
|
return {
|
|
307
317
|
qs: {
|
|
308
318
|
browser_monitoring_key: this.agentRef.info.licenseKey,
|
|
309
|
-
type:
|
|
319
|
+
type: EVENT_TYPES.SR,
|
|
310
320
|
app_id: this.agentRef.info.applicationID,
|
|
311
321
|
protocol_version: '0',
|
|
312
322
|
timestamp: firstTimestamp,
|
|
@@ -372,7 +382,7 @@ export class Aggregate extends AggregateBase {
|
|
|
372
382
|
if (forceHarvest) this.agentRef.runtime.harvester.triggerHarvestFor(this);
|
|
373
383
|
this.mode = MODE.OFF;
|
|
374
384
|
this.recorder?.stopRecording?.();
|
|
375
|
-
this
|
|
385
|
+
this.#writeToStorage({
|
|
376
386
|
sessionReplayMode: this.mode
|
|
377
387
|
});
|
|
378
388
|
}
|
|
@@ -384,13 +394,13 @@ export class Aggregate extends AggregateBase {
|
|
|
384
394
|
this.blocked = true;
|
|
385
395
|
this.mode = MODE.OFF;
|
|
386
396
|
this.recorder?.stopRecording?.();
|
|
387
|
-
this
|
|
397
|
+
this.#writeToStorage({
|
|
388
398
|
sessionReplayMode: this.mode
|
|
389
399
|
});
|
|
390
400
|
this.recorder?.clearTimestamps?.();
|
|
391
401
|
while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
|
|
392
402
|
}
|
|
393
|
-
|
|
403
|
+
#writeToStorage(state = {}) {
|
|
394
404
|
if (this.isSessionTrackingEnabled) {
|
|
395
405
|
this.agentRef.runtime.session.write(state);
|
|
396
406
|
}
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
6
|
-
import { FEATURE_NAME } from '../constants';
|
|
6
|
+
import { ABORT_REASONS, FEATURE_NAME } from '../constants';
|
|
7
7
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
8
8
|
import { TraceStorage } from './trace/storage';
|
|
9
9
|
import { obj as encodeObj } from '../../../common/url/encode';
|
|
10
10
|
import { globalScope } from '../../../common/constants/runtime';
|
|
11
11
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants';
|
|
12
|
-
import {
|
|
12
|
+
import { Obfuscator } from '../../../common/util/obfuscate';
|
|
13
13
|
import { cleanURL } from '../../../common/url/clean-url';
|
|
14
14
|
import { warn } from '../../../common/util/console';
|
|
15
|
+
import { EVENT_TYPES } from '../../../common/constants/events';
|
|
15
16
|
|
|
16
17
|
/** Reserved room for query param attrs */
|
|
17
18
|
const QUERY_PARAM_PADDING = 5000;
|
|
@@ -21,6 +22,9 @@ export class Aggregate extends AggregateBase {
|
|
|
21
22
|
super(agentRef, FEATURE_NAME);
|
|
22
23
|
this.harvestOpts.raw = true;
|
|
23
24
|
|
|
25
|
+
// Create obfuscator for session trace nodes
|
|
26
|
+
this.obfuscator = new Obfuscator(agentRef, EVENT_TYPES.ST);
|
|
27
|
+
|
|
24
28
|
/** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
|
|
25
29
|
this.entitled = undefined;
|
|
26
30
|
/** A flag used to decide if the 30 node threshold should be ignored on the first harvest to ensure sending on the first payload */
|
|
@@ -37,8 +41,10 @@ export class Aggregate extends AggregateBase {
|
|
|
37
41
|
/** Sets up event listeners, and initializes this module to run in the correct "mode". Can be triggered from a few places, but makes an effort to only set up listeners once */
|
|
38
42
|
initialize(stMode, stEntitled, ignoreSession) {
|
|
39
43
|
this.entitled ??= stEntitled;
|
|
40
|
-
if (!this.entitled)
|
|
41
|
-
|
|
44
|
+
if (!this.entitled) {
|
|
45
|
+
this.abort(ABORT_REASONS.ENTITLEMENTS);
|
|
46
|
+
return this.deregisterDrain();
|
|
47
|
+
}
|
|
42
48
|
this.timeKeeper ??= this.agentRef.runtime.timeKeeper;
|
|
43
49
|
if (!this.initialized) {
|
|
44
50
|
this.initialized = true;
|
|
@@ -48,7 +54,7 @@ export class Aggregate extends AggregateBase {
|
|
|
48
54
|
// 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.
|
|
49
55
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
50
56
|
if (this.blocked) return;
|
|
51
|
-
this.abort(
|
|
57
|
+
this.abort(ABORT_REASONS.RESET);
|
|
52
58
|
});
|
|
53
59
|
// The SessionEntity can have updates (locally or across tabs for SR mode changes), (across tabs for ST mode changes).
|
|
54
60
|
// Those updates should be sync'd here to ensure this page also honors the mode after initialization
|
|
@@ -57,7 +63,7 @@ export class Aggregate extends AggregateBase {
|
|
|
57
63
|
// this will only have an effect if ST is NOT already in full mode
|
|
58
64
|
if (this.mode !== MODE.FULL && (sessionState.sessionReplayMode === MODE.FULL || sessionState.sessionTraceMode === MODE.FULL)) this.switchToFull();
|
|
59
65
|
// if another page's session entity has expired, or another page has transitioned to off and this one hasn't... we can just abort straight away here
|
|
60
|
-
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF) this.abort(
|
|
66
|
+
if (this.sessionId !== sessionState.value || eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
|
|
61
67
|
});
|
|
62
68
|
if (typeof PerformanceNavigationTiming !== 'undefined' && globalScope.performance?.getEntriesByType('navigation')?.length > 0) {
|
|
63
69
|
this.traceStorage.storeTiming(globalScope.performance.getEntriesByType('navigation')[0]);
|
|
@@ -68,30 +74,30 @@ export class Aggregate extends AggregateBase {
|
|
|
68
74
|
|
|
69
75
|
/** ST/SR sampling flow in BCS - https://drive.google.com/file/d/19hwt2oft-8Hh4RrjpLqEXfpP_9wYBLcq/view?usp=sharing */
|
|
70
76
|
/** ST will run in the mode provided by BCS if the session IS NEW. If not... it will use the state of the session entity to determine what mode to run in */
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
registerHandler('trace-jserror', () => {
|
|
88
|
-
if (this.mode === MODE.ERROR) this.switchToFull();
|
|
89
|
-
}, this.featureName, this.ee);
|
|
77
|
+
if (this.agentRef.runtime.session.state.sessionTraceMode !== null && !ignoreSession) this.mode = this.agentRef.runtime.session.state.sessionTraceMode;else this.mode = stMode;
|
|
78
|
+
if (this.mode !== MODE.OFF) {
|
|
79
|
+
/** The handlers set up by the Inst file */
|
|
80
|
+
registerHandler('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee);
|
|
81
|
+
registerHandler('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee);
|
|
82
|
+
registerHandler('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee);
|
|
83
|
+
registerHandler('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee);
|
|
84
|
+
registerHandler('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee);
|
|
85
|
+
registerHandler('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee);
|
|
86
|
+
registerHandler('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee);
|
|
87
|
+
if (this.mode === MODE.ERROR) {
|
|
88
|
+
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
89
|
+
registerHandler('trace-jserror', () => {
|
|
90
|
+
if (this.mode === MODE.ERROR) this.switchToFull();
|
|
91
|
+
}, this.featureName, this.ee);
|
|
92
|
+
}
|
|
90
93
|
}
|
|
91
94
|
this.agentRef.runtime.session.write({
|
|
92
95
|
sessionTraceMode: this.mode
|
|
93
96
|
});
|
|
94
|
-
|
|
97
|
+
|
|
98
|
+
/** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
|
|
99
|
+
* If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
|
|
100
|
+
this.mode === MODE.OFF ? this.deregisterDrain() : this.drain();
|
|
95
101
|
}
|
|
96
102
|
preHarvestChecks() {
|
|
97
103
|
if (this.blocked || this.mode !== MODE.FULL) return; // only allow harvest if running in full mode
|
|
@@ -99,7 +105,7 @@ export class Aggregate extends AggregateBase {
|
|
|
99
105
|
if (!this.agentRef.runtime.session) return; // session entity is required for trace to run and continue running
|
|
100
106
|
if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
|
|
101
107
|
// If something unexpected happened and we somehow still got to harvesting after a session identifier changed, we should force-exit instead of harvesting:
|
|
102
|
-
this.abort(
|
|
108
|
+
this.abort(ABORT_REASONS.SESSION_CHANGED);
|
|
103
109
|
return;
|
|
104
110
|
}
|
|
105
111
|
return true;
|
|
@@ -107,7 +113,7 @@ export class Aggregate extends AggregateBase {
|
|
|
107
113
|
serializer(stns) {
|
|
108
114
|
if (!stns.length) return; // there are no processed nodes
|
|
109
115
|
this.everHarvested = true;
|
|
110
|
-
return
|
|
116
|
+
return this.obfuscator.traverseAndObfuscateEvents(stns);
|
|
111
117
|
}
|
|
112
118
|
queryStringsBuilder(stns) {
|
|
113
119
|
const firstSessionHarvest = !this.agentRef.runtime.session.state.traceHarvestStarted;
|
|
@@ -184,8 +190,8 @@ export class Aggregate extends AggregateBase {
|
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
/** Stop running for the remainder of the page lifecycle */
|
|
187
|
-
abort(
|
|
188
|
-
warn(60,
|
|
193
|
+
abort(reason = {}) {
|
|
194
|
+
warn(60, reason.message);
|
|
189
195
|
this.blocked = true;
|
|
190
196
|
this.mode = MODE.OFF;
|
|
191
197
|
this.agentRef.runtime.session.write({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
@@ -12,4 +12,18 @@ export const FN_START = 'fn' + START;
|
|
|
12
12
|
export const FN_END = 'fn' + END;
|
|
13
13
|
export const PUSH_STATE = 'pushState';
|
|
14
14
|
export const MAX_NODES_PER_HARVEST = 1000;
|
|
15
|
-
export const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
15
|
+
export const ERROR_MODE_SECONDS_WINDOW = 30 * 1000; // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
16
|
+
export const ABORT_REASONS = {
|
|
17
|
+
CROSS_TAB: {
|
|
18
|
+
message: 'Session Entity was set to OFF on another tab'
|
|
19
|
+
},
|
|
20
|
+
ENTITLEMENTS: {
|
|
21
|
+
message: 'Session Trace is not allowed and will not be started'
|
|
22
|
+
},
|
|
23
|
+
RESET: {
|
|
24
|
+
message: 'Session was reset'
|
|
25
|
+
},
|
|
26
|
+
SESSION_CHANGED: {
|
|
27
|
+
message: 'Session identifier changed unexpectedly'
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
|
|
6
|
+
import { createStringAdders } from '../../../common/payloads/payloads';
|
|
6
7
|
import { AJAX_ID } from '../../ajax/constants';
|
|
7
8
|
import { NODE_TYPE } from '../constants';
|
|
8
9
|
import { BelNode } from './bel-node';
|
|
@@ -24,6 +25,12 @@ export class AjaxNode extends BelNode {
|
|
|
24
25
|
this.targetAttributes = ajaxEvent.targetAttributes;
|
|
25
26
|
this[AJAX_ID] = ajaxEvent[AJAX_ID]; // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
|
|
26
27
|
|
|
28
|
+
// optional payload metadata attributes
|
|
29
|
+
this.requestBody = ajaxEvent.requestBody;
|
|
30
|
+
this.requestHeaders = ajaxEvent.requestHeaders;
|
|
31
|
+
this.requestQuery = ajaxEvent.requestQuery;
|
|
32
|
+
this.responseBody = ajaxEvent.responseBody;
|
|
33
|
+
this.responseHeaders = ajaxEvent.responseHeaders;
|
|
27
34
|
this.start = ajaxEvent.startTime;
|
|
28
35
|
this.end = ajaxEvent.endTime;
|
|
29
36
|
if (ajaxContext?.latestLongtaskEnd) {
|
|
@@ -31,8 +38,11 @@ export class AjaxNode extends BelNode {
|
|
|
31
38
|
this.callbackDuration = this.callbackEnd - this.end; // callbackDuration is the time from ajax loaded to last long task observed from it
|
|
32
39
|
} else this.callbackEnd = this.end; // if no long task was observed, callbackEnd is the same as end
|
|
33
40
|
}
|
|
34
|
-
serialize(parentStartTimestamp, agentRef) {
|
|
35
|
-
const
|
|
41
|
+
serialize(parentStartTimestamp, agentRef, ajaxObfuscator) {
|
|
42
|
+
const {
|
|
43
|
+
addString,
|
|
44
|
+
addStringWithTruncation
|
|
45
|
+
} = createStringAdders(getAddStringContext, ajaxObfuscator);
|
|
36
46
|
const nodeList = [];
|
|
37
47
|
|
|
38
48
|
// IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
|
|
@@ -47,11 +57,32 @@ export class AjaxNode extends BelNode {
|
|
|
47
57
|
numeric(this.callbackDuration),
|
|
48
58
|
// not relative
|
|
49
59
|
addString(this.method), numeric(this.status), addString(this.domain), addString(this.path), numeric(this.txSize), numeric(this.rxSize), this.requestedWith, addString(this.nodeId), nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)];
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
// Regular attributes: obfuscate only
|
|
61
|
+
const regularAttrs = addCustomAttributes({
|
|
62
|
+
[AJAX_ID]: this[AJAX_ID],
|
|
52
63
|
...(this.targetAttributes || {}),
|
|
53
|
-
|
|
64
|
+
...(this.gql || {})
|
|
54
65
|
}, addString);
|
|
66
|
+
|
|
67
|
+
// Payload attributes: obfuscate then truncate
|
|
68
|
+
const payloadAttrs = addCustomAttributes({
|
|
69
|
+
...(this.requestBody ? {
|
|
70
|
+
requestBody: this.requestBody
|
|
71
|
+
} : {}),
|
|
72
|
+
...(this.requestHeaders ? {
|
|
73
|
+
requestHeaders: this.requestHeaders
|
|
74
|
+
} : {}),
|
|
75
|
+
...(this.requestQuery ? {
|
|
76
|
+
requestQuery: this.requestQuery
|
|
77
|
+
} : {}),
|
|
78
|
+
...(this.responseBody ? {
|
|
79
|
+
responseBody: this.responseBody
|
|
80
|
+
} : {}),
|
|
81
|
+
...(this.responseHeaders ? {
|
|
82
|
+
responseHeaders: this.responseHeaders
|
|
83
|
+
} : {})
|
|
84
|
+
}, addStringWithTruncation);
|
|
85
|
+
let allAttachedNodes = [...regularAttrs, ...payloadAttrs];
|
|
55
86
|
this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
|
|
56
87
|
|
|
57
88
|
fields[1] = numeric(allAttachedNodes.length);
|
|
@@ -8,10 +8,12 @@ import { webdriverDetected } from '../../../common/util/webdriver-detection';
|
|
|
8
8
|
import { loadTime } from '../../../common/vitals/load-time';
|
|
9
9
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
10
10
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
11
|
+
import { Obfuscator } from '../../../common/util/obfuscate';
|
|
11
12
|
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS, INTERACTION_TRIGGERS, IPL_TRIGGER_NAME, NO_LONG_TASK_WINDOW, POPSTATE_MERGE_WINDOW, POPSTATE_TRIGGER } from '../constants';
|
|
12
13
|
import { AjaxNode } from './ajax-node';
|
|
13
14
|
import { InitialPageLoadInteraction } from './initial-page-load-interaction';
|
|
14
15
|
import { Interaction } from './interaction';
|
|
16
|
+
import { EVENT_TYPES } from '../../../common/constants/events';
|
|
15
17
|
export class Aggregate extends AggregateBase {
|
|
16
18
|
static featureName = FEATURE_NAME;
|
|
17
19
|
constructor(agentRef, {
|
|
@@ -19,6 +21,10 @@ export class Aggregate extends AggregateBase {
|
|
|
19
21
|
}) {
|
|
20
22
|
super(agentRef, FEATURE_NAME);
|
|
21
23
|
super.customAttributesAreSeparate = true;
|
|
24
|
+
|
|
25
|
+
// Create obfuscators for browser interactions and nested AJAX requests
|
|
26
|
+
this.interactionObfuscator = new Obfuscator(agentRef, EVENT_TYPES.BI);
|
|
27
|
+
this.ajaxObfuscator = new Obfuscator(agentRef, EVENT_TYPES.AJAX);
|
|
22
28
|
this.interactionsToHarvest = this.events;
|
|
23
29
|
this.domObserver = domObserver;
|
|
24
30
|
this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef);
|
|
@@ -82,7 +88,7 @@ export class Aggregate extends AggregateBase {
|
|
|
82
88
|
let firstIxnStartTime;
|
|
83
89
|
const serializedIxnList = [];
|
|
84
90
|
for (const interaction of eventBuffer) {
|
|
85
|
-
serializedIxnList.push(interaction.serialize(firstIxnStartTime, this.agentRef));
|
|
91
|
+
serializedIxnList.push(interaction.serialize(firstIxnStartTime, this.agentRef, this.interactionObfuscator, this.ajaxObfuscator));
|
|
86
92
|
if (firstIxnStartTime === undefined) firstIxnStartTime = Math.floor(interaction.start); // careful not to match or overwrite on 0 value!
|
|
87
93
|
}
|
|
88
94
|
return "bel.7;".concat(serializedIxnList.join(';'));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2020-
|
|
2
|
+
* Copyright 2020-2026 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { globalScope, initialLocation } from '../../../common/constants/runtime';
|
|
@@ -121,11 +121,13 @@ export class Interaction extends BelNode {
|
|
|
121
121
|
* Serializes (BEL) the interaction data for transmission.
|
|
122
122
|
* @param {Number} firstStartTimeOfPayload timestamp
|
|
123
123
|
* @param {Agent} agentRef Pass in the agent reference directly so that the event itself doesnt need to store the pointers and ruin the evaluation of the event size by including unused object references.
|
|
124
|
+
* @param {Obfuscator} interactionObfuscator Obfuscator for BrowserInteraction event type
|
|
125
|
+
* @param {Obfuscator} ajaxObfuscator Obfuscator for AjaxRequest event type (passed to child AjaxNodes)
|
|
124
126
|
* @returns {String} A string that is the serialized representation of this interaction.
|
|
125
127
|
*/
|
|
126
|
-
serialize(firstStartTimeOfPayload, agentRef) {
|
|
128
|
+
serialize(firstStartTimeOfPayload, agentRef, interactionObfuscator, ajaxObfuscator) {
|
|
127
129
|
const isFirstIxnOfPayload = firstStartTimeOfPayload === undefined;
|
|
128
|
-
const addString = getAddStringContext(
|
|
130
|
+
const addString = getAddStringContext(interactionObfuscator);
|
|
129
131
|
const nodeList = [];
|
|
130
132
|
let ixnType;
|
|
131
133
|
if (this.trigger === IPL_TRIGGER_NAME) ixnType = INTERACTION_TYPE.INITIAL_PAGE_LOAD;else if (this.newURL !== this.oldURL) ixnType = INTERACTION_TYPE.ROUTE_CHANGE;else ixnType = INTERACTION_TYPE.UNSPECIFIED;
|
|
@@ -151,7 +153,7 @@ export class Interaction extends BelNode {
|
|
|
151
153
|
/* Querypack encoder+decoder quirkiness:
|
|
152
154
|
- If first ixn node of payload is being processed, its children's start time must be offset by this node's start. (firstStartTime should be undefined.)
|
|
153
155
|
- Else for subsequent ixns in the same payload, we go back to using that first ixn node's start to offset their children's start. */
|
|
154
|
-
this.children.forEach(node => allAttachedNodes.push(node.serialize(isFirstIxnOfPayload ? this.start : firstStartTimeOfPayload, agentRef))); // recursively add the serialized string of every child of this (ixn) bel node
|
|
156
|
+
this.children.forEach(node => allAttachedNodes.push(node.serialize(isFirstIxnOfPayload ? this.start : firstStartTimeOfPayload, agentRef, ajaxObfuscator))); // recursively add the serialized string of every child of this (ixn) bel node
|
|
155
157
|
|
|
156
158
|
fields[1] = numeric(allAttachedNodes.length);
|
|
157
159
|
nodeList.push(fields);
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { FeatureBase } from './feature-base';
|
|
6
6
|
import { drain } from '../../common/drain/drain';
|
|
7
|
-
import { Obfuscator } from '../../common/util/obfuscate';
|
|
8
7
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
9
8
|
import { Harvester } from '../../common/harvest/harvester';
|
|
10
9
|
import { EventBuffer } from './event-buffer';
|
|
@@ -12,6 +11,7 @@ import { handle } from '../../common/event-emitter/handle';
|
|
|
12
11
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../metrics/constants';
|
|
13
12
|
import { EventAggregator } from '../../common/aggregate/event-aggregator';
|
|
14
13
|
import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants';
|
|
14
|
+
import { warn } from '../../common/util/console';
|
|
15
15
|
export class AggregateBase extends FeatureBase {
|
|
16
16
|
/**
|
|
17
17
|
* Create an AggregateBase instance.
|
|
@@ -111,6 +111,7 @@ export class AggregateBase extends FeatureBase {
|
|
|
111
111
|
}
|
|
112
112
|
function buildOutput(ref) {
|
|
113
113
|
return flagNames.map(flag => {
|
|
114
|
+
if (ref[flag] === undefined || ref[flag] === null) warn(71, flag);
|
|
114
115
|
if (!ref[flag]) return 0;
|
|
115
116
|
return ref[flag];
|
|
116
117
|
});
|
|
@@ -176,8 +177,8 @@ export class AggregateBase extends FeatureBase {
|
|
|
176
177
|
* This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
|
|
177
178
|
*/
|
|
178
179
|
doOnceForAllAggregate(agentRef) {
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
// Note: obfuscator is now created per-feature for their specific event types
|
|
181
|
+
|
|
181
182
|
if (!agentRef.runtime.harvester) agentRef.runtime.harvester = new Harvester(agentRef);
|
|
182
183
|
}
|
|
183
184
|
|
|
@@ -18,6 +18,7 @@ import { measure } from './measure';
|
|
|
18
18
|
import { recordCustomEvent } from './recordCustomEvent';
|
|
19
19
|
import { subscribeToPageUnload } from '../../common/window/page-visibility';
|
|
20
20
|
import { findScriptTimings } from '../../common/v2/script-tracker';
|
|
21
|
+
import { trackMFEVitals } from '../../common/v2/mfe-vitals';
|
|
21
22
|
import { generateRandomHexString } from '../../common/ids/unique-id';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -77,6 +78,9 @@ function register(agentRef, target) {
|
|
|
77
78
|
type: V2_TYPES.BA
|
|
78
79
|
};
|
|
79
80
|
const timings = findScriptTimings();
|
|
81
|
+
|
|
82
|
+
// Track MFE vitals for this entity
|
|
83
|
+
const vitals = trackMFEVitals(target.id);
|
|
80
84
|
const attrs = {};
|
|
81
85
|
|
|
82
86
|
// Only define attributes getter if it doesn't already exist
|
|
@@ -165,7 +169,8 @@ function register(agentRef, target) {
|
|
|
165
169
|
return attrs;
|
|
166
170
|
},
|
|
167
171
|
target,
|
|
168
|
-
timings
|
|
172
|
+
timings,
|
|
173
|
+
vitals
|
|
169
174
|
}
|
|
170
175
|
};
|
|
171
176
|
|
|
@@ -193,9 +198,17 @@ function register(agentRef, target) {
|
|
|
193
198
|
// only ever report the timings the first time this is called
|
|
194
199
|
if (timings.reportedAt) return;
|
|
195
200
|
timings.reportedAt = now();
|
|
201
|
+
|
|
202
|
+
// Disconnect observers and capture current values, store in timings obj for visibility
|
|
203
|
+
vitals.disconnect();
|
|
204
|
+
timings.fcp = vitals.fcp;
|
|
205
|
+
timings.lcp = vitals.lcp;
|
|
206
|
+
timings.cls = vitals.cls;
|
|
207
|
+
timings.inp = vitals.inp;
|
|
196
208
|
const timeToFetch = timings.fetchEnd - timings.fetchStart; // fetchStart to fetchEnd
|
|
197
209
|
const timeToExecute = timings.scriptEnd - timings.scriptStart; // scriptStart to scriptEnd
|
|
198
|
-
|
|
210
|
+
|
|
211
|
+
const eventData = {
|
|
199
212
|
assetUrl: timings.asset,
|
|
200
213
|
// the url of the script that was registered, or undefined if it could not be determined (inline or no match)
|
|
201
214
|
assetType: timings.type,
|
|
@@ -210,8 +223,17 @@ function register(agentRef, target) {
|
|
|
210
223
|
// fetchStart to fetchEnd
|
|
211
224
|
timeToLoad: timeToFetch + timeToExecute,
|
|
212
225
|
// fetch time and script time together
|
|
213
|
-
timeToRegister: timings.registeredAt
|
|
214
|
-
|
|
226
|
+
timeToRegister: timings.registeredAt,
|
|
227
|
+
// timestamp when register() was called
|
|
228
|
+
'nr.vitals.fcp': vitals.fcp || null,
|
|
229
|
+
// FCP vital object with value and metadata
|
|
230
|
+
'nr.vitals.lcp': vitals.lcp || null,
|
|
231
|
+
// LCP vital object with value and metadata
|
|
232
|
+
'nr.vitals.cls': vitals.cls || null,
|
|
233
|
+
// CLS vital object with value and metadata
|
|
234
|
+
'nr.vitals.inp': vitals.inp || null // INP vital object with value and metadata
|
|
235
|
+
};
|
|
236
|
+
api.recordCustomEvent('MicroFrontEndTiming', eventData);
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
/**
|