@newrelic/browser-agent 1.286.0 → 1.288.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 +21 -0
- package/dist/cjs/common/config/init-types.js +96 -0
- package/dist/cjs/common/config/init.js +9 -79
- package/dist/cjs/common/config/runtime.js +7 -6
- 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 +2 -2
- package/dist/cjs/common/session/session-entity.js +10 -3
- package/dist/cjs/common/util/feature-flags.js +4 -5
- package/dist/cjs/common/util/target.js +34 -0
- package/dist/cjs/features/ajax/aggregate/index.js +2 -1
- package/dist/cjs/features/generic_events/aggregate/index.js +10 -6
- package/dist/cjs/features/jserrors/aggregate/index.js +44 -22
- package/dist/cjs/features/logging/aggregate/index.js +21 -13
- package/dist/cjs/features/logging/shared/utils.js +3 -2
- package/dist/cjs/features/metrics/aggregate/index.js +6 -4
- package/dist/cjs/features/page_view_event/aggregate/index.js +60 -11
- package/dist/cjs/features/page_view_event/instrument/index.js +4 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +9 -7
- package/dist/cjs/features/session_replay/instrument/index.js +1 -1
- package/dist/cjs/features/session_replay/shared/recorder-events.js +4 -2
- package/dist/cjs/features/session_replay/shared/recorder.js +17 -11
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +8 -1
- package/dist/cjs/features/soft_navigations/aggregate/index.js +13 -2
- package/dist/cjs/features/spa/aggregate/index.js +4 -3
- package/dist/cjs/features/spa/aggregate/interaction.js +6 -9
- package/dist/cjs/features/utils/aggregate-base.js +49 -20
- package/dist/cjs/features/utils/entity-manager.js +47 -0
- package/dist/cjs/features/utils/event-store-manager.js +79 -54
- package/dist/cjs/interfaces/registered-entity.js +114 -0
- package/dist/cjs/loaders/api/api-methods.js +1 -1
- package/dist/cjs/loaders/api/api.js +50 -18
- package/dist/cjs/loaders/api/register-api-types.js +35 -0
- package/dist/cjs/loaders/api/register-api.js +165 -0
- package/dist/cjs/loaders/micro-agent-base.js +16 -0
- package/dist/cjs/loaders/micro-agent.js +1 -0
- package/dist/esm/common/config/init-types.js +92 -0
- package/dist/esm/common/config/init.js +9 -79
- package/dist/esm/common/config/runtime.js +7 -6
- 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 +2 -2
- package/dist/esm/common/session/session-entity.js +10 -3
- package/dist/esm/common/util/feature-flags.js +4 -5
- package/dist/esm/common/util/target.js +27 -0
- package/dist/esm/features/ajax/aggregate/index.js +2 -1
- package/dist/esm/features/generic_events/aggregate/index.js +10 -6
- package/dist/esm/features/jserrors/aggregate/index.js +44 -22
- package/dist/esm/features/logging/aggregate/index.js +21 -13
- package/dist/esm/features/logging/shared/utils.js +3 -2
- package/dist/esm/features/metrics/aggregate/index.js +6 -4
- package/dist/esm/features/page_view_event/aggregate/index.js +60 -11
- package/dist/esm/features/page_view_event/instrument/index.js +4 -0
- package/dist/esm/features/session_replay/aggregate/index.js +9 -7
- package/dist/esm/features/session_replay/instrument/index.js +1 -1
- package/dist/esm/features/session_replay/shared/recorder-events.js +4 -2
- package/dist/esm/features/session_replay/shared/recorder.js +17 -11
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +8 -1
- package/dist/esm/features/soft_navigations/aggregate/index.js +13 -2
- package/dist/esm/features/spa/aggregate/index.js +4 -3
- package/dist/esm/features/spa/aggregate/interaction.js +6 -9
- package/dist/esm/features/utils/aggregate-base.js +49 -20
- package/dist/esm/features/utils/entity-manager.js +40 -0
- package/dist/esm/features/utils/event-store-manager.js +79 -54
- package/dist/esm/interfaces/registered-entity.js +107 -0
- package/dist/esm/loaders/api/api-methods.js +1 -1
- package/dist/esm/loaders/api/api.js +50 -18
- package/dist/esm/loaders/api/register-api-types.js +33 -0
- package/dist/esm/loaders/api/register-api.js +159 -0
- package/dist/esm/loaders/micro-agent-base.js +16 -0
- package/dist/esm/loaders/micro-agent.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/common/config/init-types.d.ts +275 -0
- package/dist/types/common/config/init-types.d.ts.map +1 -0
- package/dist/types/common/config/init.d.ts +1 -262
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/config/runtime.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts +3 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/util/feature-flags.d.ts +1 -1
- package/dist/types/common/util/feature-flags.d.ts.map +1 -1
- package/dist/types/common/util/target.d.ts +18 -0
- package/dist/types/common/util/target.d.ts.map +1 -0
- package/dist/types/features/ajax/aggregate/index.d.ts +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +2 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +9 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +3 -3
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/shared/utils.d.ts +2 -1
- package/dist/types/features/logging/shared/utils.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +10 -2
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +3 -12
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts +1 -0
- package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +5 -5
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +1 -0
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/interaction.d.ts +3 -4
- package/dist/types/features/spa/aggregate/interaction.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +22 -5
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/entity-manager.d.ts +15 -0
- package/dist/types/features/utils/entity-manager.d.ts.map +1 -0
- package/dist/types/features/utils/event-store-manager.d.ts +48 -24
- package/dist/types/features/utils/event-store-manager.d.ts.map +1 -1
- package/dist/types/interfaces/registered-entity.d.ts +72 -0
- package/dist/types/interfaces/registered-entity.d.ts.map +1 -0
- package/dist/types/loaders/agent.d.ts +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/api/register-api-types.d.ts +56 -0
- package/dist/types/loaders/api/register-api-types.d.ts.map +1 -0
- package/dist/types/loaders/api/register-api.d.ts +14 -0
- package/dist/types/loaders/api/register-api.d.ts.map +1 -0
- package/dist/types/loaders/micro-agent-base.d.ts +17 -0
- package/dist/types/loaders/micro-agent-base.d.ts.map +1 -1
- package/dist/types/loaders/micro-agent.d.ts +1 -0
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +10 -1
- package/src/common/config/init-types.js +92 -0
- package/src/common/config/init.js +9 -79
- package/src/common/config/runtime.js +7 -6
- package/src/common/harvest/harvester.js +2 -2
- package/src/common/session/session-entity.js +10 -3
- package/src/common/util/feature-flags.js +4 -5
- package/src/common/util/target.js +27 -0
- package/src/features/ajax/aggregate/index.js +2 -1
- package/src/features/generic_events/aggregate/index.js +8 -6
- package/src/features/jserrors/aggregate/index.js +42 -20
- package/src/features/logging/aggregate/index.js +18 -12
- package/src/features/logging/shared/utils.js +3 -2
- package/src/features/metrics/aggregate/index.js +7 -5
- package/src/features/page_view_event/aggregate/index.js +50 -8
- package/src/features/page_view_event/instrument/index.js +4 -0
- package/src/features/session_replay/aggregate/index.js +7 -4
- package/src/features/session_replay/instrument/index.js +1 -1
- package/src/features/session_replay/shared/recorder-events.js +5 -2
- package/src/features/session_replay/shared/recorder.js +17 -11
- package/src/features/session_trace/aggregate/trace/storage.js +9 -1
- package/src/features/soft_navigations/aggregate/index.js +12 -2
- package/src/features/spa/aggregate/index.js +4 -3
- package/src/features/spa/aggregate/interaction.js +6 -9
- package/src/features/utils/aggregate-base.js +54 -22
- package/src/features/utils/entity-manager.js +45 -0
- package/src/features/utils/event-store-manager.js +72 -49
- package/src/interfaces/registered-entity.js +107 -0
- package/src/loaders/api/api-methods.js +1 -1
- package/src/loaders/api/api.js +49 -13
- package/src/loaders/api/register-api-types.js +33 -0
- package/src/loaders/api/register-api.js +152 -0
- package/src/loaders/micro-agent-base.js +16 -0
- package/src/loaders/micro-agent.js +1 -0
|
@@ -19,6 +19,8 @@ import { AggregateBase } from '../../utils/aggregate-base'
|
|
|
19
19
|
import { now } from '../../../common/timing/now'
|
|
20
20
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
21
21
|
import { evaluateInternalError } from './internal-errors'
|
|
22
|
+
import { isContainerAgentTarget } from '../../../common/util/target'
|
|
23
|
+
import { warn } from '../../../common/util/console'
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
|
|
@@ -107,10 +109,13 @@ export class Aggregate extends AggregateBase {
|
|
|
107
109
|
* @param {object=} customAttributes any custom attributes to be included in the error payload
|
|
108
110
|
* @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
|
|
109
111
|
* @param {string=} swallowReason a string indicating pre-defined reason if swallowing the error. Mainly used by the internal error SMs.
|
|
112
|
+
* @param {object=} target the target to buffer and harvest to, if undefined the default configuration target is used
|
|
110
113
|
* @returns
|
|
111
114
|
*/
|
|
112
|
-
storeError (err, time, internal, customAttributes, hasReplay, swallowReason) {
|
|
115
|
+
storeError (err, time, internal, customAttributes, hasReplay, swallowReason, targetEntityGuid) {
|
|
113
116
|
if (!err) return
|
|
117
|
+
const target = this.agentRef.runtime.entityManager.get(targetEntityGuid)
|
|
118
|
+
if (!target) return warn(56, this.featureName)
|
|
114
119
|
// are we in an interaction
|
|
115
120
|
time = time || now()
|
|
116
121
|
let filterOutput
|
|
@@ -145,7 +150,8 @@ export class Aggregate extends AggregateBase {
|
|
|
145
150
|
// Do not modify the name ('errorGroup') of params without DEM approval!
|
|
146
151
|
if (filterOutput?.group) params.errorGroup = filterOutput.group
|
|
147
152
|
|
|
148
|
-
|
|
153
|
+
// Should only decorate "hasReplay" for the container agent, so check if the target matches the config
|
|
154
|
+
if (hasReplay && isContainerAgentTarget(target, this.agentRef)) params.hasReplay = hasReplay
|
|
149
155
|
/**
|
|
150
156
|
* The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
|
|
151
157
|
* stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
|
|
@@ -181,7 +187,7 @@ export class Aggregate extends AggregateBase {
|
|
|
181
187
|
|
|
182
188
|
// Trace sends the error in its payload, and both trace & replay simply listens for any error to occur.
|
|
183
189
|
const jsErrorEvent = [type, bucketHash, params, newMetrics, customAttributes]
|
|
184
|
-
handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
|
|
190
|
+
if (this.shouldAllowMainAgentToCapture(targetEntityGuid)) handle('trace-jserror', jsErrorEvent, undefined, FEATURE_NAMES.sessionTrace, this.ee)
|
|
185
191
|
// still send EE events for other features such as above, but stop this one from aggregating internal data
|
|
186
192
|
if (this.blocked) return
|
|
187
193
|
|
|
@@ -190,27 +196,32 @@ export class Aggregate extends AggregateBase {
|
|
|
190
196
|
params._interactionNodeId = err.__newrelic[this.agentIdentifier].interactionNodeId
|
|
191
197
|
}
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
if (this.shouldAllowMainAgentToCapture(targetEntityGuid)) {
|
|
200
|
+
const softNavInUse = Boolean(this.agentRef.features?.[FEATURE_NAMES.softNav])
|
|
201
|
+
// Note: the following are subject to potential race cond wherein if the other feature aren't fully initialized, it'll be treated as there being no associated interaction.
|
|
202
|
+
// They each will also tack on their respective properties to the params object as part of the decision flow.
|
|
203
|
+
if (softNavInUse) handle('jserror', [params, time], undefined, FEATURE_NAMES.softNav, this.ee)
|
|
204
|
+
else handle('spa-jserror', jsErrorEvent, undefined, FEATURE_NAMES.spa, this.ee)
|
|
205
|
+
|
|
206
|
+
if (params.browserInteractionId && !params._softNavFinished) { // hold onto the error until the in-progress interaction is done, eithered saved or discarded
|
|
207
|
+
this.bufferedErrorsUnderSpa[params.browserInteractionId] ??= []
|
|
208
|
+
this.bufferedErrorsUnderSpa[params.browserInteractionId].push(jsErrorEvent)
|
|
209
|
+
} else if (params._interactionId != null) { // same as above, except tailored for the way old spa does it
|
|
210
|
+
this.bufferedErrorsUnderSpa[params._interactionId] = this.bufferedErrorsUnderSpa[params._interactionId] || []
|
|
211
|
+
this.bufferedErrorsUnderSpa[params._interactionId].push(jsErrorEvent)
|
|
212
|
+
} else {
|
|
206
213
|
// Either there is no interaction (then all these params properties will be undefined) OR there's a related soft navigation that's already completed.
|
|
207
214
|
// The old spa does not look up completed interactions at all, so there's no need to consider it.
|
|
208
|
-
|
|
215
|
+
this.#storeJserrorForHarvest(jsErrorEvent, params.browserInteractionId !== undefined, params._softNavAttributes)
|
|
216
|
+
}
|
|
209
217
|
}
|
|
218
|
+
|
|
219
|
+
// always add directly if scoped to a sub-entity, the other pathways above will be deterministic if the main agent should procede
|
|
220
|
+
if (targetEntityGuid) this.#storeJserrorForHarvest([...jsErrorEvent, targetEntityGuid], false, params._softNavAttributes)
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
#storeJserrorForHarvest (errorInfoArr, softNavOccurredFinished, softNavCustomAttrs = {}) {
|
|
213
|
-
let [type, bucketHash, params, newMetrics, localAttrs] = errorInfoArr
|
|
224
|
+
let [type, bucketHash, params, newMetrics, localAttrs, targetEntityGuid] = errorInfoArr
|
|
214
225
|
const allCustomAttrs = {}
|
|
215
226
|
|
|
216
227
|
if (softNavOccurredFinished) {
|
|
@@ -227,13 +238,24 @@ export class Aggregate extends AggregateBase {
|
|
|
227
238
|
|
|
228
239
|
const jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
|
|
229
240
|
const aggregateHash = bucketHash + ':' + jsAttributesHash
|
|
230
|
-
|
|
241
|
+
|
|
242
|
+
this.events.add([type, aggregateHash, params, newMetrics, allCustomAttrs], targetEntityGuid)
|
|
231
243
|
|
|
232
244
|
function setCustom (key, val) {
|
|
233
245
|
allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
|
|
234
246
|
}
|
|
235
247
|
}
|
|
236
248
|
|
|
249
|
+
/**
|
|
250
|
+
* If the event lacks an entityGuid (the default behavior), the main agent should capture the data. If the data is assigned to a sub-entity target
|
|
251
|
+
* the main agent should not capture events unless it is configured to do so.
|
|
252
|
+
* @param {string} entityGuid - the context object for the event
|
|
253
|
+
* @returns {boolean} - whether the main agent should capture the event to its internal target
|
|
254
|
+
*/
|
|
255
|
+
shouldAllowMainAgentToCapture (entityGuid) {
|
|
256
|
+
return (!entityGuid || this.agentRef.init.api.duplicate_registered_data)
|
|
257
|
+
}
|
|
258
|
+
|
|
237
259
|
// TO-DO: Remove this function when old spa is taken out. #storeJserrorForHarvest handles the work with the softnav feature.
|
|
238
260
|
onInteractionDone (interaction, wasSaved) {
|
|
239
261
|
if (!this.bufferedErrorsUnderSpa[interaction.id] || this.blocked) return
|
|
@@ -257,7 +279,7 @@ export class Aggregate extends AggregateBase {
|
|
|
257
279
|
var jsAttributesHash = stringHashCode(stringify(allCustomAttrs))
|
|
258
280
|
var aggregateHash = hash + ':' + jsAttributesHash
|
|
259
281
|
|
|
260
|
-
this.events.add([item[0], aggregateHash, params, item[3], allCustomAttrs])
|
|
282
|
+
this.events.add([item[0], aggregateHash, params, item[3], allCustomAttrs], item[5])
|
|
261
283
|
|
|
262
284
|
function setCustom ([key, val]) {
|
|
263
285
|
allCustomAttrs[key] = (val && typeof val === 'object' ? stringify(val) : val)
|
|
@@ -11,6 +11,7 @@ import { Log } from '../shared/log'
|
|
|
11
11
|
import { isValidLogLevel } from '../shared/utils'
|
|
12
12
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
13
13
|
import { MAX_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
|
|
14
|
+
import { isContainerAgentTarget } from '../../../common/util/target'
|
|
14
15
|
import { SESSION_EVENT_TYPES, SESSION_EVENTS } from '../../../common/session/constants'
|
|
15
16
|
import { ABORT_REASONS } from '../../session_replay/constants'
|
|
16
17
|
import { canEnableSessionTracking } from '../../utils/feature-gates'
|
|
@@ -61,7 +62,8 @@ export class Aggregate extends AggregateBase {
|
|
|
61
62
|
})
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
handleLog (timestamp, message, attributes = {}, level = LOG_LEVELS.INFO) {
|
|
65
|
+
handleLog (timestamp, message, attributes = {}, level = LOG_LEVELS.INFO, targetEntityGuid) {
|
|
66
|
+
if (!this.agentRef.runtime.entityManager.get(targetEntityGuid)) return warn(56, this.featureName)
|
|
65
67
|
if (this.blocked || !this.loggingMode) return
|
|
66
68
|
|
|
67
69
|
if (!attributes || typeof attributes !== 'object') attributes = {}
|
|
@@ -105,12 +107,12 @@ export class Aggregate extends AggregateBase {
|
|
|
105
107
|
return
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
if (this.events.wouldExceedMaxSize(logBytes)) {
|
|
110
|
+
if (this.events.wouldExceedMaxSize(logBytes, targetEntityGuid)) {
|
|
109
111
|
this.reportSupportabilityMetric('Logging/Harvest/Early/Seen', this.events.byteSize() + logBytes)
|
|
110
|
-
this.agentRef.runtime.harvester.triggerHarvestFor(this) // force a harvest synchronously to try adding again
|
|
112
|
+
this.agentRef.runtime.harvester.triggerHarvestFor(this, { targetEntityGuid }) // force a harvest synchronously to try adding again
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
if (!this.events.add(log)) { // still failed after a harvest attempt despite not being too large would mean harvest failed with options.retry
|
|
115
|
+
if (!this.events.add(log, targetEntityGuid)) { // still failed after a harvest attempt despite not being too large would mean harvest failed with options.retry
|
|
114
116
|
this.reportSupportabilityMetric(failToHarvestMessage, logBytes)
|
|
115
117
|
warn(31, log.message.slice(0, 25) + '...')
|
|
116
118
|
} else {
|
|
@@ -118,20 +120,21 @@ export class Aggregate extends AggregateBase {
|
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
serializer (eventBuffer) {
|
|
123
|
+
serializer (eventBuffer, targetEntityGuid) {
|
|
124
|
+
const target = this.agentRef.runtime.entityManager.get(targetEntityGuid)
|
|
122
125
|
const sessionEntity = this.agentRef.runtime.session
|
|
123
126
|
return [{
|
|
124
127
|
common: {
|
|
125
128
|
/** Attributes in the `common` section are added to `all` logs generated in the payload */
|
|
126
129
|
attributes: {
|
|
127
|
-
'entity.guid':
|
|
130
|
+
'entity.guid': target.entityGuid, // browser entity guid as provided API target OR the default from RUM response if not supplied
|
|
128
131
|
...(sessionEntity && {
|
|
129
132
|
session: sessionEntity.state.value || '0', // The session ID that we generate and keep across page loads
|
|
130
|
-
hasReplay: sessionEntity.state.sessionReplayMode === 1, // True if a session replay recording is running
|
|
133
|
+
hasReplay: sessionEntity.state.sessionReplayMode === 1 && isContainerAgentTarget(target, this.agentRef), // True if a session replay recording is running
|
|
131
134
|
hasTrace: sessionEntity.state.sessionTraceMode === 1 // True if a session trace recording is running
|
|
132
135
|
}),
|
|
133
136
|
ptid: this.agentRef.runtime.ptid, // page trace id
|
|
134
|
-
appId: this.agentRef.info.applicationID, // Application ID from info object,
|
|
137
|
+
appId: target.applicationID || this.agentRef.info.applicationID, // Application ID from info object,
|
|
135
138
|
standalone: Boolean(this.agentRef.info.sa), // copy paste (true) vs APM (false)
|
|
136
139
|
agentVersion: this.agentRef.runtime.version, // browser agent version
|
|
137
140
|
// The following 3 attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB:
|
|
@@ -150,16 +153,19 @@ export class Aggregate extends AggregateBase {
|
|
|
150
153
|
}]
|
|
151
154
|
}
|
|
152
155
|
|
|
153
|
-
queryStringsBuilder () {
|
|
154
|
-
|
|
156
|
+
queryStringsBuilder (_, targetEntityGuid) {
|
|
157
|
+
const target = this.agentRef.runtime.entityManager.get(targetEntityGuid)
|
|
158
|
+
return { browser_monitoring_key: target.licenseKey }
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
/** Abort the feature, once aborted it will not resume */
|
|
158
162
|
abort (reason = {}) {
|
|
159
163
|
this.reportSupportabilityMetric(`Logging/Abort/${reason.sm}`)
|
|
160
164
|
this.blocked = true
|
|
161
|
-
this.events
|
|
162
|
-
|
|
165
|
+
if (this.events) {
|
|
166
|
+
this.events.clear()
|
|
167
|
+
this.events.clearSave()
|
|
168
|
+
}
|
|
163
169
|
this.updateLoggingMode(LOGGING_MODE.OFF)
|
|
164
170
|
this.deregisterDrain()
|
|
165
171
|
}
|
|
@@ -13,10 +13,11 @@ import { LOGGING_EVENT_EMITTER_CHANNEL, LOG_LEVELS } from '../constants'
|
|
|
13
13
|
* @param {string} message - the log message string
|
|
14
14
|
* @param {{[key: string]: *}} customAttributes - The log's custom attributes if any
|
|
15
15
|
* @param {enum} level - the log level enum
|
|
16
|
+
* @param {object=} targetEntityGuid - the optional target entity guid provided by an api call
|
|
16
17
|
*/
|
|
17
|
-
export function bufferLog (ee, message, customAttributes = {}, level = LOG_LEVELS.INFO) {
|
|
18
|
+
export function bufferLog (ee, message, customAttributes = {}, level = LOG_LEVELS.INFO, targetEntityGuid, timestamp = now()) {
|
|
18
19
|
handle(SUPPORTABILITY_METRIC_CHANNEL, [`API/logging/${level.toLowerCase()}/called`], undefined, FEATURE_NAMES.metrics, ee)
|
|
19
|
-
handle(LOGGING_EVENT_EMITTER_CHANNEL, [
|
|
20
|
+
handle(LOGGING_EVENT_EMITTER_CHANNEL, [timestamp, message, customAttributes, level, targetEntityGuid], undefined, FEATURE_NAMES.logging, ee)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -21,8 +21,14 @@ export class Aggregate extends AggregateBase {
|
|
|
21
21
|
this.harvestOpts.aggregatorTypes = ['cm', 'sm'] // the types in EventAggregator this feature cares about
|
|
22
22
|
// This feature only harvests once per potential EoL of the page, which is handled by the central harvester.
|
|
23
23
|
|
|
24
|
+
// this must be read/stored synchronously, as the currentScript is removed from the DOM after this script is executed and this lookup will be void
|
|
25
|
+
// its used to report a SM later in the lifecycle
|
|
26
|
+
this.agentNonce = isBrowserScope && document.currentScript?.nonce
|
|
27
|
+
|
|
24
28
|
this.waitForFlags(['err']).then(([errFlag]) => {
|
|
25
29
|
if (errFlag) {
|
|
30
|
+
this.singleChecks() // checks that are run only one time, at script load
|
|
31
|
+
this.eachSessionChecks() // the start of every time user engages with page
|
|
26
32
|
this.drain()
|
|
27
33
|
} else {
|
|
28
34
|
this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
|
|
@@ -33,9 +39,6 @@ export class Aggregate extends AggregateBase {
|
|
|
33
39
|
// Allow features external to the metrics feature to capture SMs and CMs through the event emitter
|
|
34
40
|
registerHandler(SUPPORTABILITY_METRIC_CHANNEL, this.storeSupportabilityMetrics.bind(this), this.featureName, this.ee)
|
|
35
41
|
registerHandler(CUSTOM_METRIC_CHANNEL, this.storeEventMetrics.bind(this), this.featureName, this.ee)
|
|
36
|
-
|
|
37
|
-
this.singleChecks() // checks that are run only one time, at script load
|
|
38
|
-
this.eachSessionChecks() // the start of every time user engages with page
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
preHarvestChecks (opts) { return this.drained && opts.isFinalHarvest } // only allow any metrics to be sent after we get the right RUM flag and only on EoL
|
|
@@ -65,8 +68,7 @@ export class Aggregate extends AggregateBase {
|
|
|
65
68
|
if (isBrowserScope) {
|
|
66
69
|
this.storeSupportabilityMetrics('Generic/Runtime/Browser/Detected')
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
if (nonce && nonce !== '') {
|
|
71
|
+
if (this.agentNonce && this.agentNonce !== '') {
|
|
70
72
|
this.storeSupportabilityMetrics('Generic/Runtime/Nonce/Detected')
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -17,6 +17,8 @@ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
|
|
|
17
17
|
import { now } from '../../../common/timing/now'
|
|
18
18
|
import { TimeKeeper } from '../../../common/timing/time-keeper'
|
|
19
19
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
20
|
+
import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
21
|
+
import { isContainerAgentTarget } from '../../../common/util/target'
|
|
20
22
|
|
|
21
23
|
export class Aggregate extends AggregateBase {
|
|
22
24
|
static featureName = CONSTANTS.FEATURE_NAME
|
|
@@ -27,6 +29,10 @@ export class Aggregate extends AggregateBase {
|
|
|
27
29
|
this.firstByteToWindowLoad = 0 // our "frontend" duration
|
|
28
30
|
this.firstByteToDomContent = 0 // our "dom processing" duration
|
|
29
31
|
|
|
32
|
+
registerHandler('send-rum', (customAttibutes, target) => {
|
|
33
|
+
this.sendRum(customAttibutes, target)
|
|
34
|
+
}, this.featureName, this.ee)
|
|
35
|
+
|
|
30
36
|
if (!isValid(agentRef.agentIdentifier)) {
|
|
31
37
|
this.ee.abort()
|
|
32
38
|
return warn(43)
|
|
@@ -48,7 +54,13 @@ export class Aggregate extends AggregateBase {
|
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
|
|
60
|
+
* @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
|
|
61
|
+
* @param {*} target The target to harvest to - Since we will not know the entityGuid before harvesting, this must be an object directly supplied from the info object or API, not an entityGuid string for lookup with the entityManager - Defaults to { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }
|
|
62
|
+
*/
|
|
63
|
+
sendRum (customAttributes = this.agentRef.info.jsAttributes, target = { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }) {
|
|
52
64
|
const info = this.agentRef.info
|
|
53
65
|
const measures = {}
|
|
54
66
|
|
|
@@ -77,8 +89,8 @@ export class Aggregate extends AggregateBase {
|
|
|
77
89
|
if (this.agentRef.runtime.session) queryParameters.fsh = Number(this.agentRef.runtime.session.isNew) // "first session harvest" aka RUM request or PageView event of a session
|
|
78
90
|
|
|
79
91
|
let body
|
|
80
|
-
if (typeof
|
|
81
|
-
body = applyFnToProps({ ja:
|
|
92
|
+
if (typeof customAttributes === 'object' && Object.keys(customAttributes).length > 0) {
|
|
93
|
+
body = applyFnToProps({ ja: customAttributes }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
if (globalScope.performance) {
|
|
@@ -107,9 +119,10 @@ export class Aggregate extends AggregateBase {
|
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
this.rumStartTime = now()
|
|
122
|
+
|
|
110
123
|
this.agentRef.runtime.harvester.triggerHarvestFor(this, {
|
|
111
124
|
directSend: {
|
|
112
|
-
targetApp:
|
|
125
|
+
targetApp: target,
|
|
113
126
|
payload: { qs: queryParameters, body }
|
|
114
127
|
},
|
|
115
128
|
needResponse: true,
|
|
@@ -117,8 +130,19 @@ export class Aggregate extends AggregateBase {
|
|
|
117
130
|
})
|
|
118
131
|
}
|
|
119
132
|
|
|
120
|
-
postHarvestCleanup ({ status, responseText, xhr }) {
|
|
133
|
+
postHarvestCleanup ({ status, responseText, xhr, targetApp }) {
|
|
121
134
|
const rumEndTime = now()
|
|
135
|
+
let app, flags
|
|
136
|
+
try {
|
|
137
|
+
({ app, ...flags } = JSON.parse(responseText))
|
|
138
|
+
this.processEntities(app.agents, targetApp)
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
|
|
141
|
+
warn(53, error)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Only run agent-wide side-effects if the harvest was for the main agent */
|
|
145
|
+
if (!isContainerAgentTarget(targetApp, this.agentRef)) return
|
|
122
146
|
|
|
123
147
|
if (status >= 400 || status === 0) {
|
|
124
148
|
warn(18, status)
|
|
@@ -127,8 +151,8 @@ export class Aggregate extends AggregateBase {
|
|
|
127
151
|
return
|
|
128
152
|
}
|
|
129
153
|
|
|
130
|
-
const { app, ...flags } = JSON.parse(responseText)
|
|
131
154
|
try {
|
|
155
|
+
// will do nothing if already done
|
|
132
156
|
this.agentRef.runtime.timeKeeper.processRumRequest(xhr, this.rumStartTime, rumEndTime, app.nrServerTime)
|
|
133
157
|
if (!this.agentRef.runtime.timeKeeper.ready) throw new Error('TimeKeeper not ready')
|
|
134
158
|
} catch (error) {
|
|
@@ -136,9 +160,27 @@ export class Aggregate extends AggregateBase {
|
|
|
136
160
|
warn(17, error)
|
|
137
161
|
return
|
|
138
162
|
}
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
|
|
164
|
+
// set the agent runtime objects that require the rum response or entity guid
|
|
165
|
+
if (!Object.keys(this.agentRef.runtime.appMetadata).length) this.agentRef.runtime.appMetadata = app
|
|
166
|
+
|
|
141
167
|
this.drain()
|
|
142
168
|
this.agentRef.runtime.harvester.startTimer()
|
|
169
|
+
activateFeatures(flags, this.agentRef)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
processEntities (entities, targetApp) {
|
|
173
|
+
if (!entities || !targetApp) return
|
|
174
|
+
entities.forEach(agent => {
|
|
175
|
+
const entityManager = this.agentRef.runtime.entityManager
|
|
176
|
+
const entityGuid = agent.entityGuid
|
|
177
|
+
const entity = entityManager.get(entityGuid)
|
|
178
|
+
if (entity) return // already processed
|
|
179
|
+
|
|
180
|
+
if (isContainerAgentTarget(targetApp, this.agentRef)) {
|
|
181
|
+
entityManager.setDefaultEntity({ ...targetApp, entityGuid })
|
|
182
|
+
}
|
|
183
|
+
entityManager.set(agent.entityGuid, { ...targetApp, entityGuid })
|
|
184
|
+
})
|
|
143
185
|
}
|
|
144
186
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright 2020-2025 New Relic, Inc. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
+
import { handle } from '../../../common/event-emitter/handle'
|
|
5
6
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
6
7
|
import * as CONSTANTS from '../constants'
|
|
7
8
|
|
|
@@ -10,6 +11,9 @@ export class Instrument extends InstrumentBase {
|
|
|
10
11
|
constructor (agentRef, auto = true) {
|
|
11
12
|
super(agentRef, CONSTANTS.FEATURE_NAME, auto)
|
|
12
13
|
|
|
14
|
+
/** messages from the register API that can trigger a new RUM call */
|
|
15
|
+
this.ee.on('api-send-rum', (attrs, target) => handle('send-rum', [attrs, target], undefined, this.featureName, this.ee))
|
|
16
|
+
|
|
13
17
|
this.importAggregator(agentRef)
|
|
14
18
|
}
|
|
15
19
|
}
|
|
@@ -48,7 +48,7 @@ export class Aggregate extends AggregateBase {
|
|
|
48
48
|
this.errorNoticed = args?.errorNoticed || false
|
|
49
49
|
this.harvestOpts.raw = true
|
|
50
50
|
|
|
51
|
-
this.isSessionTrackingEnabled = canEnableSessionTracking(this.agentIdentifier) && this.agentRef.runtime.session
|
|
51
|
+
this.isSessionTrackingEnabled = canEnableSessionTracking(this.agentIdentifier) && !!this.agentRef.runtime.session
|
|
52
52
|
|
|
53
53
|
this.reportSupportabilityMetric('Config/SessionReplay/Enabled')
|
|
54
54
|
|
|
@@ -213,6 +213,7 @@ export class Aggregate extends AggregateBase {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
makeHarvestPayload (shouldRetryOnFail) {
|
|
216
|
+
const payloadOutput = { targetApp: undefined, payload: undefined }
|
|
216
217
|
if (this.mode !== MODE.FULL || this.blocked) return
|
|
217
218
|
if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return
|
|
218
219
|
|
|
@@ -223,7 +224,7 @@ export class Aggregate extends AggregateBase {
|
|
|
223
224
|
const payload = this.getHarvestContents(recorderEvents)
|
|
224
225
|
if (!payload.body.length) {
|
|
225
226
|
this.recorder.clearBuffer()
|
|
226
|
-
return
|
|
227
|
+
return [payloadOutput]
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
this.reportSupportabilityMetric('SessionReplay/Harvest/Attempts')
|
|
@@ -253,13 +254,15 @@ export class Aggregate extends AggregateBase {
|
|
|
253
254
|
|
|
254
255
|
if (len > MAX_PAYLOAD_SIZE) {
|
|
255
256
|
this.abort(ABORT_REASONS.TOO_BIG, len)
|
|
256
|
-
return
|
|
257
|
+
return [payloadOutput]
|
|
257
258
|
}
|
|
258
259
|
// TODO -- Gracefully handle the buffer for retries.
|
|
259
260
|
if (!this.agentRef.runtime.session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({ sessionReplaySentFirstChunk: true })
|
|
260
261
|
this.recorder.clearBuffer()
|
|
261
262
|
if (recorderEvents.type === 'preloaded') this.agentRef.runtime.harvester.triggerHarvestFor(this)
|
|
262
|
-
|
|
263
|
+
payloadOutput.payload = payload
|
|
264
|
+
|
|
265
|
+
return [payloadOutput]
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
getCorrectedTimestamp (node) {
|
|
@@ -80,7 +80,7 @@ export class Instrument extends InstrumentBase {
|
|
|
80
80
|
this.recorder ??= new Recorder({ mode: this.#mode, agentIdentifier: this.agentIdentifier, trigger, ee: this.ee, agentRef: this.#agentRef })
|
|
81
81
|
this.recorder.startRecording()
|
|
82
82
|
this.abortHandler = this.recorder.stopRecording
|
|
83
|
-
} catch (e) {}
|
|
83
|
+
} catch (e) {} // TODO add internal error handling
|
|
84
84
|
this.importAggregator(this.#agentRef, { recorder: this.recorder, errorNoticed: this.errorNoticed })
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -20,8 +20,11 @@ export class RecorderEvents {
|
|
|
20
20
|
hasMeta = false
|
|
21
21
|
/** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
|
|
22
22
|
hasError = false
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
constructor (shouldInlineStylesheets = true) {
|
|
25
|
+
/** Payload metadata -- Denotes whether all stylesheet elements were able to be inlined */
|
|
26
|
+
this.inlinedAllStylesheets = shouldInlineStylesheets
|
|
27
|
+
}
|
|
25
28
|
|
|
26
29
|
add (event) {
|
|
27
30
|
this.#events.add(event)
|
|
@@ -30,9 +30,14 @@ export class Recorder {
|
|
|
30
30
|
#warnCSSOnce = single(() => warn(47)) // notifies user of potential replayer issue if fix_stylesheets is off
|
|
31
31
|
|
|
32
32
|
constructor (parent) {
|
|
33
|
-
|
|
34
|
-
this
|
|
35
|
-
|
|
33
|
+
/** The parent class that instantiated the recorder */
|
|
34
|
+
this.parent = parent
|
|
35
|
+
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
36
|
+
this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets
|
|
37
|
+
/** Event Buffers */
|
|
38
|
+
this.#events = new RecorderEvents(this.shouldFix)
|
|
39
|
+
this.#backloggedEvents = new RecorderEvents(this.shouldFix)
|
|
40
|
+
this.#preloaded = [new RecorderEvents(this.shouldFix)]
|
|
36
41
|
/** True when actively recording, false when paused or stopped */
|
|
37
42
|
this.recording = false
|
|
38
43
|
/** The pointer to the current bucket holding rrweb events */
|
|
@@ -41,10 +46,6 @@ export class Recorder {
|
|
|
41
46
|
this.hasSeenSnapshot = false
|
|
42
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 */
|
|
43
48
|
this.lastMeta = false
|
|
44
|
-
/** The parent class that instantiated the recorder */
|
|
45
|
-
this.parent = parent
|
|
46
|
-
/** A flag that can be set to false by failing conversions to stop the fetching process */
|
|
47
|
-
this.shouldFix = this.parent.agentRef.init.session_replay.fix_stylesheets
|
|
48
49
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
49
50
|
this.stopRecording = () => { /* no-op until set by rrweb initializer */ }
|
|
50
51
|
}
|
|
@@ -74,8 +75,8 @@ export class Recorder {
|
|
|
74
75
|
clearBuffer () {
|
|
75
76
|
if (this.#preloaded[0]?.events.length) this.#preloaded.shift()
|
|
76
77
|
else if (this.parent.mode === MODE.ERROR) this.#backloggedEvents = this.#events
|
|
77
|
-
else this.#backloggedEvents = new RecorderEvents()
|
|
78
|
-
this.#events = new RecorderEvents()
|
|
78
|
+
else this.#backloggedEvents = new RecorderEvents(this.shouldFix)
|
|
79
|
+
this.#events = new RecorderEvents(this.shouldFix)
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/** Begin recording using configured recording lib */
|
|
@@ -105,6 +106,7 @@ export class Recorder {
|
|
|
105
106
|
|
|
106
107
|
this.stopRecording = () => {
|
|
107
108
|
this.recording = false
|
|
109
|
+
this.notified = false
|
|
108
110
|
this.parent.ee.emit(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode])
|
|
109
111
|
stop?.()
|
|
110
112
|
}
|
|
@@ -118,7 +120,7 @@ export class Recorder {
|
|
|
118
120
|
*/
|
|
119
121
|
audit (event, isCheckout) {
|
|
120
122
|
/** An count of stylesheet objects that were blocked from accessing contents via JS */
|
|
121
|
-
const incompletes = stylesheetEvaluator.evaluate()
|
|
123
|
+
const incompletes = this.parent.agentRef.init.session_replay.fix_stylesheets ? stylesheetEvaluator.evaluate() : 0
|
|
122
124
|
const missingInlineSMTag = 'SessionReplay/Payload/Missing-Inline-Css/'
|
|
123
125
|
/** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
|
|
124
126
|
if (!this.shouldFix) {
|
|
@@ -152,6 +154,10 @@ export class Recorder {
|
|
|
152
154
|
/** Store a payload in the buffer (this.#events). This should be the callback to the recording lib noticing a mutation */
|
|
153
155
|
store (event, isCheckout) {
|
|
154
156
|
if (!event) return
|
|
157
|
+
if (this.parent.agentRef.runtime?.session?.isAfterSessionExpiry(event.timestamp)) {
|
|
158
|
+
this.parent.reportSupportabilityMetric('Session/Expired/SessionReplay/Seen')
|
|
159
|
+
return
|
|
160
|
+
}
|
|
155
161
|
|
|
156
162
|
if (!(this.parent instanceof AggregateBase) && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1]
|
|
157
163
|
else this.currentBufferTarget = this.#events
|
|
@@ -199,7 +205,7 @@ export class Recorder {
|
|
|
199
205
|
this.parent.agentRef.runtime.harvester.triggerHarvestFor(this.parent)
|
|
200
206
|
} else {
|
|
201
207
|
// we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
|
|
202
|
-
this.#preloaded.push(new RecorderEvents())
|
|
208
|
+
this.#preloaded.push(new RecorderEvents(this.shouldFix))
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
}
|
|
@@ -41,6 +41,10 @@ export class TraceStorage {
|
|
|
41
41
|
this.parent = parent
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
isAfterSessionExpiry (entryTimestamp) {
|
|
45
|
+
return this.parent.agentRef.runtime?.session?.isAfterSessionExpiry((this.parent.timeKeeper?.ready && this.parent.timeKeeper.convertRelativeTimestamp(entryTimestamp)) ?? undefined)
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
/** Central function called by all the other store__ & addToTrace API to append a trace node. */
|
|
45
49
|
storeSTN (stn) {
|
|
46
50
|
if (this.parent.blocked) return
|
|
@@ -49,6 +53,10 @@ export class TraceStorage {
|
|
|
49
53
|
const openedSpace = this.trimSTNs(ERROR_MODE_SECONDS_WINDOW) // but maybe we could make some space by discarding irrelevant nodes if we're in sessioned Error mode
|
|
50
54
|
if (openedSpace === 0) return
|
|
51
55
|
}
|
|
56
|
+
if (this.isAfterSessionExpiry(stn.s)) {
|
|
57
|
+
this.parent.reportSupportabilityMetric('Session/Expired/SessionTrace/Seen')
|
|
58
|
+
return
|
|
59
|
+
}
|
|
52
60
|
|
|
53
61
|
if (this.trace[stn.n]) this.trace[stn.n].push(stn)
|
|
54
62
|
else this.trace[stn.n] = [stn]
|
|
@@ -259,7 +267,7 @@ export class TraceStorage {
|
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
get () {
|
|
262
|
-
return [{ targetApp: this.parent.agentRef.
|
|
270
|
+
return [{ targetApp: this.parent.agentRef.runtime.entityManager.get(), data: this.takeSTNs() }]
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
clear () {
|
|
@@ -23,8 +23,18 @@ export class Aggregate extends AggregateBase {
|
|
|
23
23
|
|
|
24
24
|
this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef.agentIdentifier)
|
|
25
25
|
this.initialPageLoadInteraction.onDone.push(() => { // this ensures the .end() method also works with iPL
|
|
26
|
+
if (agentRef.runtime.session?.isNew) this.initialPageLoadInteraction.customAttributes.isFirstOfSession = true // mark the hard page load as first of its session
|
|
26
27
|
this.initialPageLoadInteraction.forceSave = true // unless forcibly ignored, iPL always finish by default
|
|
27
|
-
this.
|
|
28
|
+
const ixn = this.initialPageLoadInteraction
|
|
29
|
+
/** this.events (ixns to harvest) has already been set up, use it immediately */
|
|
30
|
+
if (this.interactionsToHarvest) this.interactionsToHarvest.add(ixn)
|
|
31
|
+
else {
|
|
32
|
+
/** this.events (ixns to harvest) hasnt been initialized yet... wait for it */
|
|
33
|
+
this.ee.on('entity-added', () => {
|
|
34
|
+
this.interactionsToHarvest = this.events
|
|
35
|
+
this.interactionsToHarvest.add(ixn)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
28
38
|
this.initialPageLoadInteraction = null
|
|
29
39
|
})
|
|
30
40
|
timeToFirstByte.subscribe(({ attrs }) => {
|
|
@@ -131,7 +141,7 @@ export class Aggregate extends AggregateBase {
|
|
|
131
141
|
*/
|
|
132
142
|
if (this.interactionInProgress?.isActiveDuring(timestamp)) return this.interactionInProgress
|
|
133
143
|
let saveIxn
|
|
134
|
-
const interactionsBuffer = this.interactionsToHarvest.get(
|
|
144
|
+
const [{ data: interactionsBuffer }] = this.interactionsToHarvest.get()
|
|
135
145
|
for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) { // reverse search for the latest completed interaction for efficiency
|
|
136
146
|
const finishedInteraction = interactionsBuffer[idx]
|
|
137
147
|
if (finishedInteraction.isActiveDuring(timestamp)) {
|
|
@@ -108,8 +108,9 @@ export class Aggregate extends AggregateBase {
|
|
|
108
108
|
|
|
109
109
|
if (agentRef.init.spa.enabled !== true) return
|
|
110
110
|
|
|
111
|
-
state.initialPageLoad = new Interaction('initialPageLoad', 0, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef
|
|
111
|
+
state.initialPageLoad = new Interaction('initialPageLoad', 0, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef)
|
|
112
112
|
state.initialPageLoad.save = true
|
|
113
|
+
if (agentRef.runtime.session?.isNew) state.initialPageLoad.root.attrs.custom.isFirstOfSession = true // mark the hard page load as first of its session
|
|
113
114
|
state.prevInteraction = state.initialPageLoad
|
|
114
115
|
state.currentNode = state.initialPageLoad.root // hint
|
|
115
116
|
// ensure that checkFinish calls are safe during initialPageLoad
|
|
@@ -199,7 +200,7 @@ export class Aggregate extends AggregateBase {
|
|
|
199
200
|
// Otherwise, if no interaction is currently active, create a new node ID,
|
|
200
201
|
// and let the aggregator know that we entered a new event handler callback
|
|
201
202
|
// so that it has a chance to possibly start an interaction.
|
|
202
|
-
var ixn = new Interaction(evName, this[FN_START], state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef
|
|
203
|
+
var ixn = new Interaction(evName, this[FN_START], state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef)
|
|
203
204
|
|
|
204
205
|
// Store the interaction as prevInteraction in case it is prematurely discarded
|
|
205
206
|
state.prevInteraction = ixn
|
|
@@ -535,7 +536,7 @@ export class Aggregate extends AggregateBase {
|
|
|
535
536
|
var interaction
|
|
536
537
|
if (state?.currentNode?.[INTERACTION]) interaction = this.ixn = state.currentNode[INTERACTION]
|
|
537
538
|
else if (state?.prevNode?.end === null && state?.prevNode?.[INTERACTION]?.root?.[INTERACTION]?.eventName !== 'initialPageLoad') interaction = this.ixn = state.prevNode[INTERACTION]
|
|
538
|
-
else interaction = this.ixn = new Interaction('api', t, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef
|
|
539
|
+
else interaction = this.ixn = new Interaction('api', t, state.lastSeenUrl, state.lastSeenRouteName, onInteractionFinished, agentRef)
|
|
539
540
|
if (!state.currentNode) {
|
|
540
541
|
interaction.checkFinish()
|
|
541
542
|
if (state.depth) setCurrentNode(interaction.root)
|