@newrelic/browser-agent 1.317.0 → 1.318.0-rc.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 +9 -0
- package/dist/cjs/common/config/configurable.js +1 -1
- package/dist/cjs/common/config/init-types.js +2 -0
- package/dist/cjs/common/config/init.js +3 -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/harvest/harvester.js +3 -219
- package/dist/cjs/common/harvest/send.js +232 -0
- package/dist/cjs/common/harvest/types.js +9 -5
- package/dist/cjs/common/session/session-entity.js +8 -8
- package/dist/cjs/common/v2/mfe-vitals.js +303 -0
- package/dist/cjs/features/generic_events/aggregate/index.js +1 -1
- package/dist/cjs/features/generic_events/instrument/index.js +1 -1
- package/dist/cjs/features/logging/aggregate/index.js +5 -5
- package/dist/cjs/features/page_view_event/aggregate/index.js +65 -30
- package/dist/cjs/features/session_replay/aggregate/index.js +32 -28
- package/dist/cjs/features/session_trace/aggregate/index.js +29 -27
- package/dist/cjs/features/session_trace/constants.js +17 -3
- package/dist/cjs/features/utils/aggregate-base.js +2 -0
- package/dist/cjs/loaders/api/register.js +27 -4
- package/dist/esm/common/config/configurable.js +1 -1
- package/dist/esm/common/config/init-types.js +2 -0
- package/dist/esm/common/config/init.js +3 -0
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/harvest/harvester.js +3 -219
- package/dist/esm/common/harvest/send.js +226 -0
- package/dist/esm/common/harvest/types.js +9 -5
- package/dist/esm/common/session/session-entity.js +9 -9
- package/dist/esm/common/v2/mfe-vitals.js +298 -0
- package/dist/esm/features/generic_events/aggregate/index.js +1 -1
- package/dist/esm/features/generic_events/instrument/index.js +1 -1
- package/dist/esm/features/logging/aggregate/index.js +5 -5
- package/dist/esm/features/page_view_event/aggregate/index.js +64 -29
- package/dist/esm/features/session_replay/aggregate/index.js +32 -28
- package/dist/esm/features/session_trace/aggregate/index.js +30 -28
- package/dist/esm/features/session_trace/constants.js +16 -2
- package/dist/esm/features/utils/aggregate-base.js +2 -0
- package/dist/esm/loaders/api/register.js +27 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +6 -0
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts +0 -17
- 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/session/session-entity.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/features/page_view_event/aggregate/index.d.ts +1 -0
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +2 -2
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +1 -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/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/loaders/api/register.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/common/config/configurable.js +1 -1
- package/src/common/config/init-types.js +2 -0
- package/src/common/config/init.js +2 -1
- package/src/common/harvest/harvester.js +3 -202
- package/src/common/harvest/send.js +209 -0
- package/src/common/harvest/types.js +9 -5
- package/src/common/session/session-entity.js +8 -9
- package/src/common/v2/mfe-vitals.js +285 -0
- package/src/features/generic_events/aggregate/index.js +1 -1
- package/src/features/generic_events/instrument/index.js +1 -1
- package/src/features/logging/aggregate/index.js +5 -5
- package/src/features/page_view_event/aggregate/index.js +52 -27
- package/src/features/session_replay/aggregate/index.js +30 -27
- package/src/features/session_trace/aggregate/index.js +31 -28
- package/src/features/session_trace/constants.js +7 -1
- package/src/features/utils/aggregate-base.js +2 -0
- package/src/loaders/api/register.js +25 -4
|
@@ -15,8 +15,9 @@ import { firstPaint } from '../../../common/vitals/first-paint'
|
|
|
15
15
|
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
|
|
16
16
|
import { now } from '../../../common/timing/now'
|
|
17
17
|
import { TimeKeeper } from '../../../common/timing/time-keeper'
|
|
18
|
+
import { send } from '../../../common/harvest/send'
|
|
19
|
+
import { canEnableSessionTracking } from '../../utils/feature-gates'
|
|
18
20
|
import { Obfuscator } from '../../../common/util/obfuscate'
|
|
19
|
-
import { send } from '../../../common/harvest/harvester'
|
|
20
21
|
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
21
22
|
import { getSubmitMethod } from '../../../common/util/submit-data'
|
|
22
23
|
import { webdriverDetected } from '../../../common/util/webdriver-detection'
|
|
@@ -28,6 +29,7 @@ export class Aggregate extends AggregateBase {
|
|
|
28
29
|
constructor (agentRef) {
|
|
29
30
|
super(agentRef, CONSTANTS.FEATURE_NAME)
|
|
30
31
|
|
|
32
|
+
this.isSessionTrackingEnabled = canEnableSessionTracking(agentRef.init) && !!agentRef.runtime.session
|
|
31
33
|
this.sentRum = false // flag to facilitate calling sendRum() once externally (by the consent API in agent-session.js)
|
|
32
34
|
|
|
33
35
|
this.timeToFirstByte = 0
|
|
@@ -41,6 +43,17 @@ export class Aggregate extends AggregateBase {
|
|
|
41
43
|
agentRef.runtime.timeKeeper = new TimeKeeper(agentRef.runtime.session)
|
|
42
44
|
|
|
43
45
|
if (isBrowserScope) {
|
|
46
|
+
const cached = this.isSessionTrackingEnabled && agentRef.runtime.session?.state.cachedRumResponse
|
|
47
|
+
if (cached) {
|
|
48
|
+
const { app: cachedApp, ...cachedFlags } = cached
|
|
49
|
+
|
|
50
|
+
// set the agent runtime objects that require the rum response or entity guid
|
|
51
|
+
if (!Object.keys(this.agentRef.runtime.appMetadata).length) agentRef.runtime.appMetadata = cachedApp
|
|
52
|
+
this.drain()
|
|
53
|
+
agentRef.runtime.harvester.startTimer()
|
|
54
|
+
activateFeatures(cachedFlags, agentRef)
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
timeToFirstByte.subscribe(({ value, attrs }) => {
|
|
45
58
|
const navEntry = attrs.navigationEntry
|
|
46
59
|
this.timeToFirstByte = Math.max(value, this.timeToFirstByte)
|
|
@@ -85,7 +98,7 @@ export class Aggregate extends AggregateBase {
|
|
|
85
98
|
at: info.atts
|
|
86
99
|
}
|
|
87
100
|
|
|
88
|
-
if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.
|
|
101
|
+
if (this.agentRef.runtime.session) queryParameters.fsh = Number(!this.agentRef.runtime.session.state.cachedRumResponse)
|
|
89
102
|
|
|
90
103
|
let body = this.obfuscator.traverseAndObfuscateEvents({ ja: { ...customAttributes, webdriverDetected } })
|
|
91
104
|
|
|
@@ -131,6 +144,7 @@ export class Aggregate extends AggregateBase {
|
|
|
131
144
|
postHarvestCleanup ({ sent, status, responseText, xhr, retry }) {
|
|
132
145
|
const rumEndTime = now()
|
|
133
146
|
let app, flags
|
|
147
|
+
const hasCachedRumResponse = !!this.agentRef.runtime.session?.state.cachedRumResponse
|
|
134
148
|
try {
|
|
135
149
|
({ app, ...flags } = JSON.parse(responseText))
|
|
136
150
|
} catch (error) {
|
|
@@ -138,16 +152,24 @@ export class Aggregate extends AggregateBase {
|
|
|
138
152
|
warn(53, error)
|
|
139
153
|
}
|
|
140
154
|
|
|
141
|
-
|
|
155
|
+
const shouldCacheResponse = this.isSessionTrackingEnabled && !hasCachedRumResponse && !!app
|
|
156
|
+
if (hasCachedRumResponse) {
|
|
157
|
+
let { app: cachedApp, ...cachedFlags } = this.agentRef.runtime.session.state.cachedRumResponse
|
|
158
|
+
app ??= cachedApp
|
|
159
|
+
flags = cachedFlags
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
super.postHarvestCleanup({ sent, retry: retry && !hasCachedRumResponse }) // this will set isRetrying & re-buffer the body if request is to be retried
|
|
163
|
+
|
|
142
164
|
if (this.isRetrying && this.retries++ < 1) { // Only retry once
|
|
143
165
|
setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
144
166
|
sendEmptyBody: true
|
|
145
167
|
}), 5000) // Retry sending the RUM event after 5 seconds
|
|
146
168
|
return
|
|
147
169
|
}
|
|
170
|
+
|
|
148
171
|
if (status >= 400 || status === 0) {
|
|
149
172
|
warn(18, status)
|
|
150
|
-
this.blocked = true
|
|
151
173
|
|
|
152
174
|
// Get estimated payload size of our backlog
|
|
153
175
|
const textEncoder = new TextEncoder()
|
|
@@ -159,16 +181,24 @@ export class Aggregate extends AggregateBase {
|
|
|
159
181
|
}, 0)
|
|
160
182
|
const BCSError = 'BCS/Error/'
|
|
161
183
|
// Send SMs about failed RUM request
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
name: BCSError + status
|
|
166
|
-
},
|
|
167
|
-
stats: {
|
|
168
|
-
c: 1
|
|
169
|
-
}
|
|
184
|
+
const sm = [{
|
|
185
|
+
params: {
|
|
186
|
+
name: BCSError + status
|
|
170
187
|
},
|
|
171
|
-
{
|
|
188
|
+
stats: {
|
|
189
|
+
c: 1
|
|
190
|
+
}
|
|
191
|
+
}, {
|
|
192
|
+
params: {
|
|
193
|
+
name: BCSError + 'Duration/Ms'
|
|
194
|
+
},
|
|
195
|
+
stats: {
|
|
196
|
+
c: 1,
|
|
197
|
+
t: rumEndTime - this.rumStartTime
|
|
198
|
+
}
|
|
199
|
+
}]
|
|
200
|
+
if (!hasCachedRumResponse) {
|
|
201
|
+
sm.push({
|
|
172
202
|
params: {
|
|
173
203
|
name: BCSError + 'Dropped/Bytes'
|
|
174
204
|
},
|
|
@@ -176,28 +206,23 @@ export class Aggregate extends AggregateBase {
|
|
|
176
206
|
c: 1,
|
|
177
207
|
t: payloadSize
|
|
178
208
|
}
|
|
179
|
-
}
|
|
180
|
-
{
|
|
181
|
-
params: {
|
|
182
|
-
name: BCSError + 'Duration/Ms'
|
|
183
|
-
},
|
|
184
|
-
stats: {
|
|
185
|
-
c: 1,
|
|
186
|
-
t: rumEndTime - this.rumStartTime
|
|
187
|
-
}
|
|
188
|
-
}]
|
|
209
|
+
})
|
|
189
210
|
}
|
|
190
211
|
|
|
191
212
|
send(this.agentRef, {
|
|
192
213
|
endpoint: FEATURE_TO_ENDPOINT[FEATURE_NAMES.metrics],
|
|
193
|
-
payload: { body },
|
|
214
|
+
payload: { body: { sm } },
|
|
194
215
|
submitMethod: getSubmitMethod(),
|
|
195
216
|
featureName: FEATURE_NAMES.metrics
|
|
196
217
|
})
|
|
197
218
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
if (!hasCachedRumResponse) {
|
|
220
|
+
this.blocked = true
|
|
221
|
+
this.ee.abort()
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
} else if (shouldCacheResponse) {
|
|
225
|
+
this.agentRef.runtime.session.write({ cachedRumResponse: { app, ...flags } })
|
|
201
226
|
}
|
|
202
227
|
|
|
203
228
|
try {
|
|
@@ -27,7 +27,7 @@ import { Obfuscator } from '../../../common/util/obfuscate'
|
|
|
27
27
|
|
|
28
28
|
export class Aggregate extends AggregateBase {
|
|
29
29
|
static featureName = FEATURE_NAME
|
|
30
|
-
mode =
|
|
30
|
+
mode = null
|
|
31
31
|
|
|
32
32
|
// pass the recorder into the aggregator
|
|
33
33
|
constructor (agentRef, args) {
|
|
@@ -74,7 +74,7 @@ export class Aggregate extends AggregateBase {
|
|
|
74
74
|
if (!this.recorder) return
|
|
75
75
|
// if the mode changed on a different tab, it needs to update this instance to match
|
|
76
76
|
this.mode = agentRef.runtime.session.state.sessionReplayMode
|
|
77
|
-
if (!this.initialized || this.mode === MODE.OFF) return
|
|
77
|
+
if (!this.initialized || this.mode === MODE.OFF || !this.mode) return
|
|
78
78
|
this.recorder?.startRecording(TRIGGERS.RESUME, this.mode)
|
|
79
79
|
})
|
|
80
80
|
|
|
@@ -97,6 +97,8 @@ export class Aggregate extends AggregateBase {
|
|
|
97
97
|
this.waitForFlags(['srs', 'sr']).then(([srMode, entitled]) => {
|
|
98
98
|
this.entitled = !!entitled
|
|
99
99
|
if (!this.entitled) {
|
|
100
|
+
this.mode = MODE.OFF
|
|
101
|
+
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
100
102
|
this.deregisterDrain()
|
|
101
103
|
if (this.agentRef.runtime.isRecording) {
|
|
102
104
|
this.abort(ABORT_REASONS.ENTITLEMENTS)
|
|
@@ -106,7 +108,7 @@ export class Aggregate extends AggregateBase {
|
|
|
106
108
|
}
|
|
107
109
|
this.initializeRecording(srMode).then(() => { this.drain() })
|
|
108
110
|
}).then(() => {
|
|
109
|
-
if (this.mode
|
|
111
|
+
if (!this.mode) {
|
|
110
112
|
this.recorder?.stopRecording() // stop any conservative preload recording launched by instrument
|
|
111
113
|
while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.()
|
|
112
114
|
}
|
|
@@ -143,7 +145,7 @@ export class Aggregate extends AggregateBase {
|
|
|
143
145
|
// if the error was noticed AFTER the recorder was already imported....
|
|
144
146
|
if (this.recorder && this.initialized) {
|
|
145
147
|
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(TRIGGERS.SWITCH_TO_FULL, this.mode) // off --> full
|
|
146
|
-
this
|
|
148
|
+
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
147
149
|
} else {
|
|
148
150
|
this.initializeRecording(MODE.FULL, true, TRIGGERS.SWITCH_TO_FULL)
|
|
149
151
|
}
|
|
@@ -171,35 +173,36 @@ export class Aggregate extends AggregateBase {
|
|
|
171
173
|
|
|
172
174
|
if (this.recorder?.trigger === TRIGGERS.API && this.agentRef.runtime.isRecording) {
|
|
173
175
|
this.mode = MODE.FULL
|
|
174
|
-
} else if (
|
|
176
|
+
} else if (session.state.sessionReplayMode !== null && !ignoreSession) { // inherit the mode of the existing session
|
|
175
177
|
this.mode = session.state.sessionReplayMode
|
|
176
178
|
} else {
|
|
177
179
|
// The session is new... determine the mode the new session should start in
|
|
178
180
|
this.mode = srMode
|
|
179
181
|
}
|
|
180
|
-
// If off, then don't record (early return)
|
|
181
|
-
if (this.mode === MODE.OFF) return
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
|
|
192
|
-
if (this.mode === MODE.ERROR && this.instrumentClass.errorNoticed) { this.mode = MODE.FULL }
|
|
183
|
+
if (this.mode !== MODE.OFF) {
|
|
184
|
+
try {
|
|
185
|
+
/** will return a recorder instance if already imported, otherwise, will fetch the recorder and initialize it */
|
|
186
|
+
this.recorder ??= await this.instrumentClass.importRecorder()
|
|
187
|
+
} catch (err) {
|
|
188
|
+
/** if the recorder fails to import, abort the feature */
|
|
189
|
+
return this.abort(ABORT_REASONS.IMPORT, err)
|
|
190
|
+
}
|
|
193
191
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
// If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
|
|
193
|
+
if (this.mode === MODE.ERROR && this.instrumentClass.errorNoticed) {
|
|
194
|
+
this.mode = MODE.FULL
|
|
195
|
+
}
|
|
197
196
|
|
|
198
|
-
|
|
197
|
+
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
198
|
+
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
199
|
+
// The makeHarvestPayload should ensure that no payload is returned if we're not in FULL mode...
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
await this.prepUtils()
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
if (!this.agentRef.runtime.isRecording) this.recorder.startRecording(trigger, this.mode)
|
|
204
|
+
}
|
|
205
|
+
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
async prepUtils () {
|
|
@@ -246,7 +249,7 @@ export class Aggregate extends AggregateBase {
|
|
|
246
249
|
return
|
|
247
250
|
}
|
|
248
251
|
|
|
249
|
-
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this
|
|
252
|
+
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.#writeToStorage({ sessionReplaySentFirstChunk: true })
|
|
250
253
|
this.recorder.clearBuffer()
|
|
251
254
|
|
|
252
255
|
if (!this.agentRef.runtime.session.state.traceHarvestStarted) {
|
|
@@ -370,7 +373,7 @@ export class Aggregate extends AggregateBase {
|
|
|
370
373
|
if (forceHarvest) this.agentRef.runtime.harvester.triggerHarvestFor(this)
|
|
371
374
|
this.mode = MODE.OFF
|
|
372
375
|
this.recorder?.stopRecording?.()
|
|
373
|
-
this
|
|
376
|
+
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
/** Abort the feature, once aborted it will not resume */
|
|
@@ -380,12 +383,12 @@ export class Aggregate extends AggregateBase {
|
|
|
380
383
|
this.blocked = true
|
|
381
384
|
this.mode = MODE.OFF
|
|
382
385
|
this.recorder?.stopRecording?.()
|
|
383
|
-
this
|
|
386
|
+
this.#writeToStorage({ sessionReplayMode: this.mode })
|
|
384
387
|
this.recorder?.clearTimestamps?.()
|
|
385
388
|
while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.()
|
|
386
389
|
}
|
|
387
390
|
|
|
388
|
-
|
|
391
|
+
#writeToStorage (state = {}) {
|
|
389
392
|
if (this.isSessionTrackingEnabled) {
|
|
390
393
|
this.agentRef.runtime.session.write(state)
|
|
391
394
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
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'
|
|
@@ -43,8 +43,10 @@ export class Aggregate extends AggregateBase {
|
|
|
43
43
|
/** 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 */
|
|
44
44
|
initialize (stMode, stEntitled, ignoreSession) {
|
|
45
45
|
this.entitled ??= stEntitled
|
|
46
|
-
if (!this.entitled)
|
|
47
|
-
|
|
46
|
+
if (!this.entitled) {
|
|
47
|
+
this.abort(ABORT_REASONS.ENTITLEMENTS)
|
|
48
|
+
return this.deregisterDrain()
|
|
49
|
+
}
|
|
48
50
|
this.timeKeeper ??= this.agentRef.runtime.timeKeeper
|
|
49
51
|
|
|
50
52
|
if (!this.initialized) {
|
|
@@ -55,7 +57,7 @@ export class Aggregate extends AggregateBase {
|
|
|
55
57
|
// 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.
|
|
56
58
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
57
59
|
if (this.blocked) return
|
|
58
|
-
this.abort(
|
|
60
|
+
this.abort(ABORT_REASONS.RESET)
|
|
59
61
|
})
|
|
60
62
|
// The SessionEntity can have updates (locally or across tabs for SR mode changes), (across tabs for ST mode changes).
|
|
61
63
|
// Those updates should be sync'd here to ensure this page also honors the mode after initialization
|
|
@@ -64,7 +66,7 @@ export class Aggregate extends AggregateBase {
|
|
|
64
66
|
// this will only have an effect if ST is NOT already in full mode
|
|
65
67
|
if (this.mode !== MODE.FULL && (sessionState.sessionReplayMode === MODE.FULL || sessionState.sessionTraceMode === MODE.FULL)) this.switchToFull()
|
|
66
68
|
// 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
|
|
67
|
-
if (this.sessionId !== sessionState.value || (eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF)) this.abort(
|
|
69
|
+
if (this.sessionId !== sessionState.value || (eventType === 'cross-tab' && sessionState.sessionTraceMode === MODE.OFF)) this.abort(ABORT_REASONS.CROSS_TAB)
|
|
68
70
|
})
|
|
69
71
|
|
|
70
72
|
if (typeof PerformanceNavigationTiming !== 'undefined' && globalScope.performance?.getEntriesByType('navigation')?.length > 0) {
|
|
@@ -76,30 +78,31 @@ export class Aggregate extends AggregateBase {
|
|
|
76
78
|
|
|
77
79
|
/** ST/SR sampling flow in BCS - https://drive.google.com/file/d/19hwt2oft-8Hh4RrjpLqEXfpP_9wYBLcq/view?usp=sharing */
|
|
78
80
|
/** 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 */
|
|
79
|
-
if (
|
|
81
|
+
if (this.agentRef.runtime.session.state.sessionTraceMode !== null && !ignoreSession) this.mode = this.agentRef.runtime.session.state.sessionTraceMode
|
|
80
82
|
else this.mode = stMode
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (this.mode === MODE.ERROR) this.switchToFull()
|
|
99
|
-
}, this.featureName, this.ee)
|
|
84
|
+
if (this.mode !== MODE.OFF) {
|
|
85
|
+
/** The handlers set up by the Inst file */
|
|
86
|
+
registerHandler('bst', (...args) => this.traceStorage.storeEvent(...args), this.featureName, this.ee)
|
|
87
|
+
registerHandler('bstResource', (...args) => this.traceStorage.storeResources(...args), this.featureName, this.ee)
|
|
88
|
+
registerHandler('bstHist', (...args) => this.traceStorage.storeHist(...args), this.featureName, this.ee)
|
|
89
|
+
registerHandler('bstXhrAgg', (...args) => this.traceStorage.storeXhrAgg(...args), this.featureName, this.ee)
|
|
90
|
+
registerHandler('bstApi', (...args) => this.traceStorage.storeNode(...args), this.featureName, this.ee)
|
|
91
|
+
registerHandler('trace-jserror', (...args) => this.traceStorage.storeErrorAgg(...args), this.featureName, this.ee)
|
|
92
|
+
registerHandler('pvtAdded', (...args) => this.traceStorage.processPVT(...args), this.featureName, this.ee)
|
|
93
|
+
|
|
94
|
+
if (this.mode === MODE.ERROR) {
|
|
95
|
+
/** A separate handler for noticing errors, and switching to "full" mode if running in "error" mode */
|
|
96
|
+
registerHandler('trace-jserror', () => {
|
|
97
|
+
if (this.mode === MODE.ERROR) this.switchToFull()
|
|
98
|
+
}, this.featureName, this.ee)
|
|
99
|
+
}
|
|
100
100
|
}
|
|
101
101
|
this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
/** If the mode is off, we do not want to hold up draining for other features, so we deregister the feature for now.
|
|
104
|
+
* If it drains later (due to a mode change), data and handlers will instantly drain instead of waiting for the registry. */
|
|
105
|
+
this.mode === MODE.OFF ? this.deregisterDrain() : this.drain()
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
preHarvestChecks () {
|
|
@@ -108,7 +111,7 @@ export class Aggregate extends AggregateBase {
|
|
|
108
111
|
if (!this.agentRef.runtime.session) return // session entity is required for trace to run and continue running
|
|
109
112
|
if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
|
|
110
113
|
// If something unexpected happened and we somehow still got to harvesting after a session identifier changed, we should force-exit instead of harvesting:
|
|
111
|
-
this.abort(
|
|
114
|
+
this.abort(ABORT_REASONS.SESSION_CHANGED)
|
|
112
115
|
return
|
|
113
116
|
}
|
|
114
117
|
|
|
@@ -185,8 +188,8 @@ export class Aggregate extends AggregateBase {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
/** Stop running for the remainder of the page lifecycle */
|
|
188
|
-
abort (
|
|
189
|
-
warn(60,
|
|
191
|
+
abort (reason = {}) {
|
|
192
|
+
warn(60, reason.message)
|
|
190
193
|
this.blocked = true
|
|
191
194
|
this.mode = MODE.OFF
|
|
192
195
|
this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
|
|
@@ -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'
|
|
@@ -14,3 +14,9 @@ export const FN_END = 'fn' + END
|
|
|
14
14
|
export const PUSH_STATE = 'pushState'
|
|
15
15
|
export const MAX_NODES_PER_HARVEST = 1000
|
|
16
16
|
export const ERROR_MODE_SECONDS_WINDOW = 30 * 1000 // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
17
|
+
export const ABORT_REASONS = {
|
|
18
|
+
CROSS_TAB: { message: 'Session Entity was set to OFF on another tab' },
|
|
19
|
+
ENTITLEMENTS: { message: 'Session Trace is not allowed and will not be started' },
|
|
20
|
+
RESET: { message: 'Session was reset' },
|
|
21
|
+
SESSION_CHANGED: { message: 'Session identifier changed unexpectedly' }
|
|
22
|
+
}
|
|
@@ -11,6 +11,7 @@ import { handle } from '../../common/event-emitter/handle'
|
|
|
11
11
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../metrics/constants'
|
|
12
12
|
import { EventAggregator } from '../../common/aggregate/event-aggregator'
|
|
13
13
|
import { MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE, SUPPORTS_REGISTERED_ENTITIES } from '../../common/constants/agent-constants'
|
|
14
|
+
import { warn } from '../../common/util/console'
|
|
14
15
|
|
|
15
16
|
export class AggregateBase extends FeatureBase {
|
|
16
17
|
/**
|
|
@@ -114,6 +115,7 @@ export class AggregateBase extends FeatureBase {
|
|
|
114
115
|
}
|
|
115
116
|
function buildOutput (ref) {
|
|
116
117
|
return flagNames.map(flag => {
|
|
118
|
+
if (ref[flag] === undefined || ref[flag] === null) warn(71, flag)
|
|
117
119
|
if (!ref[flag]) return 0
|
|
118
120
|
return ref[flag]
|
|
119
121
|
})
|
|
@@ -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
|
/**
|
|
@@ -83,6 +84,9 @@ function register (agentRef, target) {
|
|
|
83
84
|
|
|
84
85
|
const timings = findScriptTimings()
|
|
85
86
|
|
|
87
|
+
// Track MFE vitals for this entity
|
|
88
|
+
const vitals = trackMFEVitals(target.id, timings)
|
|
89
|
+
|
|
86
90
|
const attrs = {}
|
|
87
91
|
|
|
88
92
|
// Only define attributes getter if it doesn't already exist
|
|
@@ -149,7 +153,8 @@ function register (agentRef, target) {
|
|
|
149
153
|
metadata: {
|
|
150
154
|
get customAttributes () { return attrs },
|
|
151
155
|
target,
|
|
152
|
-
timings
|
|
156
|
+
timings,
|
|
157
|
+
vitals
|
|
153
158
|
}
|
|
154
159
|
}
|
|
155
160
|
|
|
@@ -177,9 +182,18 @@ function register (agentRef, target) {
|
|
|
177
182
|
// only ever report the timings the first time this is called
|
|
178
183
|
if (timings.reportedAt) return
|
|
179
184
|
timings.reportedAt = now()
|
|
185
|
+
|
|
186
|
+
// Disconnect observers and capture current values, store in timings obj for visibility
|
|
187
|
+
vitals.disconnect()
|
|
188
|
+
timings.fcp = vitals.fcp
|
|
189
|
+
timings.lcp = vitals.lcp
|
|
190
|
+
timings.cls = vitals.cls
|
|
191
|
+
timings.inp = vitals.inp
|
|
192
|
+
|
|
180
193
|
const timeToFetch = timings.fetchEnd - timings.fetchStart // fetchStart to fetchEnd
|
|
181
194
|
const timeToExecute = timings.scriptEnd - timings.scriptStart // scriptStart to scriptEnd
|
|
182
|
-
|
|
195
|
+
|
|
196
|
+
const eventData = {
|
|
183
197
|
assetUrl: timings.asset, // the url of the script that was registered, or undefined if it could not be determined (inline or no match)
|
|
184
198
|
assetType: timings.type, // the type of asset that was associated with the timings, one of 'script', 'link' (if preloaded and found in the resource timing buffer), 'preload' (if preloaded but not found in the resource timing buffer), or "unknown" if it could not be determined
|
|
185
199
|
timeAlive: timings.reportedAt - timings.registeredAt, // registeredAt to reportedAt
|
|
@@ -187,8 +201,15 @@ function register (agentRef, target) {
|
|
|
187
201
|
timeToExecute, // scriptStart to scriptEnd
|
|
188
202
|
timeToFetch, // fetchStart to fetchEnd
|
|
189
203
|
timeToLoad: timeToFetch + timeToExecute, // fetch time and script time together
|
|
190
|
-
timeToRegister: timings.registeredAt // timestamp when register() was called
|
|
191
|
-
|
|
204
|
+
timeToRegister: timings.registeredAt, // timestamp when register() was called
|
|
205
|
+
// leave room to extend these with more data keys as needed
|
|
206
|
+
'nr.vitals.fcp.value': vitals.fcp?.value ?? null, // FCP vital object with value and metadata
|
|
207
|
+
'nr.vitals.lcp.value': vitals.lcp?.value ?? null, // LCP vital object with value and metadata
|
|
208
|
+
'nr.vitals.cls.value': vitals.cls?.value ?? null, // CLS vital object with value and metadata
|
|
209
|
+
'nr.vitals.inp.value': vitals.inp?.value ?? null // INP vital object with value and metadata
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
api.recordCustomEvent('MicroFrontEndTiming', eventData)
|
|
192
213
|
}
|
|
193
214
|
|
|
194
215
|
/**
|