@newrelic/browser-agent 1.271.0 → 1.273.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 +15 -0
- package/dist/cjs/common/aggregate/aggregator.js +23 -30
- package/dist/cjs/common/aggregate/event-aggregator.js +84 -0
- package/dist/cjs/common/config/init.js +8 -4
- 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/harvest-scheduler.js +1 -1
- package/dist/cjs/common/harvest/harvest.js +1 -5
- package/dist/cjs/common/harvest/types.js +0 -1
- package/dist/cjs/features/ajax/aggregate/index.js +52 -62
- package/dist/cjs/features/generic_events/aggregate/index.js +57 -36
- package/dist/cjs/features/generic_events/instrument/index.js +1 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +23 -69
- package/dist/cjs/features/logging/aggregate/index.js +52 -59
- package/dist/cjs/features/metrics/aggregate/index.js +8 -5
- package/dist/cjs/features/page_view_timing/aggregate/index.js +8 -25
- package/dist/cjs/features/session_replay/aggregate/index.js +11 -10
- package/dist/cjs/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/cjs/features/session_trace/aggregate/index.js +77 -88
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +22 -13
- package/dist/cjs/features/soft_navigations/aggregate/index.js +10 -20
- package/dist/cjs/features/soft_navigations/instrument/index.js +5 -9
- package/dist/cjs/features/spa/aggregate/index.js +10 -26
- package/dist/cjs/features/utils/aggregate-base.js +37 -0
- package/dist/cjs/features/utils/event-buffer.js +36 -87
- package/dist/cjs/features/utils/instrument-base.js +3 -3
- package/dist/cjs/loaders/features/features.js +13 -1
- package/dist/esm/common/aggregate/aggregator.js +23 -30
- package/dist/esm/common/aggregate/event-aggregator.js +78 -0
- package/dist/esm/common/config/init.js +8 -4
- 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/harvest-scheduler.js +1 -1
- package/dist/esm/common/harvest/harvest.js +1 -5
- package/dist/esm/common/harvest/types.js +0 -1
- package/dist/esm/features/ajax/aggregate/index.js +53 -62
- package/dist/esm/features/generic_events/aggregate/index.js +57 -36
- package/dist/esm/features/generic_events/instrument/index.js +1 -1
- package/dist/esm/features/jserrors/aggregate/index.js +24 -70
- package/dist/esm/features/logging/aggregate/index.js +52 -59
- package/dist/esm/features/metrics/aggregate/index.js +8 -5
- package/dist/esm/features/page_view_timing/aggregate/index.js +9 -26
- package/dist/esm/features/session_replay/aggregate/index.js +12 -11
- package/dist/esm/features/session_replay/shared/recorder-events.js +2 -2
- package/dist/esm/features/session_trace/aggregate/index.js +77 -88
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +22 -13
- package/dist/esm/features/soft_navigations/aggregate/index.js +11 -21
- package/dist/esm/features/soft_navigations/instrument/index.js +5 -9
- package/dist/esm/features/spa/aggregate/index.js +11 -27
- package/dist/esm/features/utils/aggregate-base.js +37 -0
- package/dist/esm/features/utils/event-buffer.js +36 -88
- package/dist/esm/features/utils/instrument-base.js +3 -3
- package/dist/esm/loaders/features/features.js +12 -0
- package/dist/types/common/aggregate/aggregator.d.ts +4 -6
- package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
- package/dist/types/common/aggregate/event-aggregator.d.ts +26 -0
- package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -0
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/harvest/types.d.ts +1 -4
- package/dist/types/common/harvest/types.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts +2 -10
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +5 -11
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +4 -7
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +10 -28
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -9
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +3 -4
- 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 -1
- package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +17 -19
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +10 -6
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -9
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +2 -3
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +14 -0
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +19 -56
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +3 -0
- package/dist/types/loaders/features/features.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/common/aggregate/aggregator.js +22 -32
- package/src/common/aggregate/event-aggregator.js +76 -0
- package/src/common/config/init.js +6 -2
- package/src/common/harvest/harvest-scheduler.js +1 -1
- package/src/common/harvest/harvest.js +1 -5
- package/src/common/harvest/types.js +0 -1
- package/src/features/ajax/aggregate/index.js +60 -67
- package/src/features/generic_events/aggregate/index.js +48 -38
- package/src/features/generic_events/instrument/index.js +2 -0
- package/src/features/jserrors/aggregate/index.js +21 -77
- package/src/features/logging/aggregate/index.js +46 -60
- package/src/features/metrics/aggregate/index.js +6 -4
- package/src/features/page_view_timing/aggregate/index.js +9 -30
- package/src/features/session_replay/aggregate/index.js +10 -14
- package/src/features/session_replay/shared/recorder-events.js +2 -2
- package/src/features/session_trace/aggregate/index.js +64 -73
- package/src/features/session_trace/aggregate/trace/storage.js +25 -14
- package/src/features/soft_navigations/aggregate/index.js +11 -22
- package/src/features/soft_navigations/instrument/index.js +6 -9
- package/src/features/spa/aggregate/index.js +12 -27
- package/src/features/utils/aggregate-base.js +39 -0
- package/src/features/utils/event-buffer.js +36 -83
- package/src/features/utils/instrument-base.js +3 -3
- package/src/loaders/features/features.js +13 -0
- package/dist/cjs/features/ajax/aggregate/chunk.js +0 -51
- package/dist/esm/features/ajax/aggregate/chunk.js +0 -44
- package/dist/types/features/ajax/aggregate/chunk.d.ts +0 -8
- package/dist/types/features/ajax/aggregate/chunk.d.ts.map +0 -1
- package/src/features/ajax/aggregate/chunk.js +0 -52
|
@@ -7,6 +7,7 @@ import { onDOMContentLoaded } from '../../../common/window/load'
|
|
|
7
7
|
import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
|
|
8
8
|
import { isBrowserScope, isWorkerScope } from '../../../common/constants/runtime'
|
|
9
9
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
10
|
+
import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
10
11
|
import { isIFrameWindow } from '../../../common/dom/iframe'
|
|
11
12
|
// import { WEBSOCKET_TAG } from '../../../common/wrap/wrap-websocket'
|
|
12
13
|
// import { handleWebsocketEvents } from './websocket-detection'
|
|
@@ -15,13 +16,14 @@ export class Aggregate extends AggregateBase {
|
|
|
15
16
|
static featureName = FEATURE_NAME
|
|
16
17
|
constructor (agentRef) {
|
|
17
18
|
super(agentRef, FEATURE_NAME)
|
|
19
|
+
const aggregatorTypes = ['cm', 'sm'] // the types in EventAggregator this feature cares about
|
|
18
20
|
|
|
19
21
|
this.waitForFlags(['err']).then(([errFlag]) => {
|
|
20
22
|
if (errFlag) {
|
|
21
23
|
// *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
|
|
22
|
-
const scheduler = new HarvestScheduler(
|
|
24
|
+
const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], { onUnload: () => this.unload() }, this)
|
|
23
25
|
// this is needed to ensure EoL is "on" and sent
|
|
24
|
-
scheduler.harvest.on(
|
|
26
|
+
scheduler.harvest.on(FEATURE_TO_ENDPOINT[this.featureName], () => this.makeHarvestPayload(undefined, { aggregatorTypes }))
|
|
25
27
|
this.drain()
|
|
26
28
|
} else {
|
|
27
29
|
this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
|
|
@@ -41,14 +43,14 @@ export class Aggregate extends AggregateBase {
|
|
|
41
43
|
if (this.blocked) return
|
|
42
44
|
const type = SUPPORTABILITY_METRIC
|
|
43
45
|
const params = { name }
|
|
44
|
-
this.
|
|
46
|
+
this.events.addMetric(type, name, params, value)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
storeEventMetrics (name, metrics) {
|
|
48
50
|
if (this.blocked) return
|
|
49
51
|
const type = CUSTOM_METRIC
|
|
50
52
|
const params = { name }
|
|
51
|
-
this.
|
|
53
|
+
this.events.add(type, name, params, metrics)
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
singleChecks () {
|
|
@@ -8,7 +8,7 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
|
|
|
8
8
|
import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
9
9
|
import { handle } from '../../../common/event-emitter/handle'
|
|
10
10
|
import { FEATURE_NAME } from '../constants'
|
|
11
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
11
|
+
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
12
12
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
13
13
|
import { cumulativeLayoutShift } from '../../../common/vitals/cumulative-layout-shift'
|
|
14
14
|
import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint'
|
|
@@ -19,7 +19,6 @@ import { largestContentfulPaint } from '../../../common/vitals/largest-contentfu
|
|
|
19
19
|
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
|
|
20
20
|
import { subscribeToVisibilityChange } from '../../../common/window/page-visibility'
|
|
21
21
|
import { VITAL_NAMES } from '../../../common/vitals/constants'
|
|
22
|
-
import { EventBuffer } from '../../utils/event-buffer'
|
|
23
22
|
|
|
24
23
|
export class Aggregate extends AggregateBase {
|
|
25
24
|
static featureName = FEATURE_NAME
|
|
@@ -30,8 +29,6 @@ export class Aggregate extends AggregateBase {
|
|
|
30
29
|
|
|
31
30
|
constructor (agentRef) {
|
|
32
31
|
super(agentRef, FEATURE_NAME)
|
|
33
|
-
|
|
34
|
-
this.timings = new EventBuffer()
|
|
35
32
|
this.curSessEndRecorded = false
|
|
36
33
|
|
|
37
34
|
registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee)
|
|
@@ -60,9 +57,9 @@ export class Aggregate extends AggregateBase {
|
|
|
60
57
|
this.addTiming(name, value * 1000, attrs)
|
|
61
58
|
}, true) // CLS node should only reports on vis change rather than on every change
|
|
62
59
|
|
|
63
|
-
const scheduler = new HarvestScheduler(
|
|
64
|
-
onFinished: (
|
|
65
|
-
getPayload: (
|
|
60
|
+
const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
61
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
|
|
62
|
+
getPayload: (options) => this.makeHarvestPayload(options.retry)
|
|
66
63
|
}, this)
|
|
67
64
|
scheduler.startTimer(harvestTimeSeconds)
|
|
68
65
|
|
|
@@ -97,7 +94,7 @@ export class Aggregate extends AggregateBase {
|
|
|
97
94
|
attrs.cls = cumulativeLayoutShift.current.value
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
this.
|
|
97
|
+
this.events.add({
|
|
101
98
|
name,
|
|
102
99
|
value,
|
|
103
100
|
attrs
|
|
@@ -106,11 +103,6 @@ export class Aggregate extends AggregateBase {
|
|
|
106
103
|
handle('pvtAdded', [name, value, attrs], undefined, FEATURE_NAMES.sessionTrace, this.ee)
|
|
107
104
|
}
|
|
108
105
|
|
|
109
|
-
onHarvestFinished (result) {
|
|
110
|
-
if (result.retry && this.timings.held.hasData) this.timings.unhold()
|
|
111
|
-
else this.timings.held.clear()
|
|
112
|
-
}
|
|
113
|
-
|
|
114
106
|
appendGlobalCustomAttributes (timing) {
|
|
115
107
|
var timingAttributes = timing.attrs || {}
|
|
116
108
|
|
|
@@ -123,27 +115,14 @@ export class Aggregate extends AggregateBase {
|
|
|
123
115
|
})
|
|
124
116
|
}
|
|
125
117
|
|
|
126
|
-
// serialize and return current timing data, clear and save current data for retry
|
|
127
|
-
prepareHarvest (options) {
|
|
128
|
-
if (!this.timings.hasData) return
|
|
129
|
-
|
|
130
|
-
var payload = this.getPayload(this.timings.buffer)
|
|
131
|
-
if (options.retry) this.timings.hold()
|
|
132
|
-
else this.timings.clear()
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
body: { e: payload }
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
118
|
// serialize array of timing data
|
|
140
|
-
|
|
119
|
+
serializer (eventBuffer) {
|
|
141
120
|
var addString = getAddStringContext(this.agentIdentifier)
|
|
142
121
|
|
|
143
122
|
var payload = 'bel.6;'
|
|
144
123
|
|
|
145
|
-
for (var i = 0; i <
|
|
146
|
-
var timing =
|
|
124
|
+
for (var i = 0; i < eventBuffer.length; i++) {
|
|
125
|
+
var timing = eventBuffer[i]
|
|
147
126
|
|
|
148
127
|
payload += 'e,'
|
|
149
128
|
payload += addString(timing.name) + ','
|
|
@@ -156,7 +135,7 @@ export class Aggregate extends AggregateBase {
|
|
|
156
135
|
payload += numeric(attrParts.length) + ';' + attrParts.join(';')
|
|
157
136
|
}
|
|
158
137
|
|
|
159
|
-
if ((i + 1) <
|
|
138
|
+
if ((i + 1) < eventBuffer.length) payload += ';'
|
|
160
139
|
}
|
|
161
140
|
|
|
162
141
|
return payload
|
|
@@ -16,7 +16,7 @@ import { warn } from '../../../common/util/console'
|
|
|
16
16
|
import { globalScope } from '../../../common/constants/runtime'
|
|
17
17
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
18
18
|
import { handle } from '../../../common/event-emitter/handle'
|
|
19
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
19
|
+
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
20
20
|
import { RRWEB_VERSION } from '../../../common/constants/env'
|
|
21
21
|
import { MODE, SESSION_EVENTS, SESSION_EVENT_TYPES } from '../../../common/session/constants'
|
|
22
22
|
import { stringify } from '../../../common/util/stringify'
|
|
@@ -54,14 +54,6 @@ export class Aggregate extends AggregateBase {
|
|
|
54
54
|
|
|
55
55
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee)
|
|
56
56
|
|
|
57
|
-
this.ee.on(`cfc.${FEATURE_NAMES.jserrors}`, (crossFeatureData) => {
|
|
58
|
-
crossFeatureData.hasReplay = !!(this.scheduler?.started &&
|
|
59
|
-
this.recorder &&
|
|
60
|
-
this.mode === MODE.FULL &&
|
|
61
|
-
!this.blocked &&
|
|
62
|
-
this.entitled)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
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.
|
|
66
58
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
67
59
|
this.abort(ABORT_REASONS.RESET)
|
|
@@ -85,10 +77,10 @@ export class Aggregate extends AggregateBase {
|
|
|
85
77
|
})
|
|
86
78
|
|
|
87
79
|
// Bespoke logic for blobs endpoint.
|
|
88
|
-
this.scheduler = new HarvestScheduler(
|
|
89
|
-
onFinished: this.
|
|
80
|
+
this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
81
|
+
onFinished: (result) => this.postHarvestCleanup(result),
|
|
90
82
|
retryDelay: this.harvestTimeSeconds,
|
|
91
|
-
getPayload: this.
|
|
83
|
+
getPayload: ({ retry, ...opts }) => this.makeHarvestPayload(retry, opts),
|
|
92
84
|
raw: true
|
|
93
85
|
}, this)
|
|
94
86
|
|
|
@@ -134,6 +126,10 @@ export class Aggregate extends AggregateBase {
|
|
|
134
126
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee)
|
|
135
127
|
}
|
|
136
128
|
|
|
129
|
+
replayIsActive () {
|
|
130
|
+
return Boolean(this.scheduler?.started && this.recorder && this.mode === MODE.FULL && !this.blocked && this.entitled)
|
|
131
|
+
}
|
|
132
|
+
|
|
137
133
|
handleError (e) {
|
|
138
134
|
if (this.recorder) this.recorder.currentBufferTarget.hasError = true
|
|
139
135
|
// run once
|
|
@@ -236,7 +232,7 @@ export class Aggregate extends AggregateBase {
|
|
|
236
232
|
}
|
|
237
233
|
}
|
|
238
234
|
|
|
239
|
-
|
|
235
|
+
makeHarvestPayload (shouldRetryOnFail, opts) {
|
|
240
236
|
if (!this.recorder || !this.timeKeeper?.ready || !this.recorder.hasSeenSnapshot) return
|
|
241
237
|
const recorderEvents = this.recorder.getEvents()
|
|
242
238
|
// get the event type and use that to trigger another harvest if needed
|
|
@@ -365,7 +361,7 @@ export class Aggregate extends AggregateBase {
|
|
|
365
361
|
}
|
|
366
362
|
}
|
|
367
363
|
|
|
368
|
-
|
|
364
|
+
postHarvestCleanup (result) {
|
|
369
365
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
370
366
|
if (result.status === 429) {
|
|
371
367
|
this.abort(ABORT_REASONS.TOO_MANY)
|
|
@@ -24,11 +24,11 @@ export class RecorderEvents {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
get events () {
|
|
27
|
-
return this.#events.
|
|
27
|
+
return this.#events.get()
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
31
31
|
get payloadBytesEstimation () {
|
|
32
|
-
return this.#events.
|
|
32
|
+
return this.#events.byteSize()
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -7,6 +7,7 @@ import { obj as encodeObj } from '../../../common/url/encode'
|
|
|
7
7
|
import { globalScope } from '../../../common/constants/runtime'
|
|
8
8
|
import { MODE, SESSION_EVENTS } from '../../../common/session/constants'
|
|
9
9
|
import { applyFnToProps } from '../../../common/util/traverse'
|
|
10
|
+
import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
10
11
|
import { cleanURL } from '../../../common/url/clean-url'
|
|
11
12
|
|
|
12
13
|
const ERROR_MODE_SECONDS_WINDOW = 30 * 1000 // sliding window of nodes to track when simply monitoring (but not harvesting) in error mode
|
|
@@ -18,8 +19,6 @@ export class Aggregate extends AggregateBase {
|
|
|
18
19
|
constructor (agentRef) {
|
|
19
20
|
super(agentRef, FEATURE_NAME)
|
|
20
21
|
|
|
21
|
-
/** A buffer to hold on to harvested traces in the case that a retry must be made later */
|
|
22
|
-
this.sentTrace = null
|
|
23
22
|
this.harvestTimeSeconds = agentRef.init.session_trace.harvestTimeSeconds || 30
|
|
24
23
|
/** Tied to the entitlement flag response from BCS. Will short circuit operations of the agg if false */
|
|
25
24
|
this.entitled = undefined
|
|
@@ -28,7 +27,7 @@ export class Aggregate extends AggregateBase {
|
|
|
28
27
|
/** If the harvest module is harvesting */
|
|
29
28
|
this.harvesting = false
|
|
30
29
|
/** TraceStorage is the mechanism that holds, normalizes and aggregates ST nodes. It will be accessed and purged when harvests occur */
|
|
31
|
-
this.
|
|
30
|
+
this.events = new TraceStorage(this)
|
|
32
31
|
/** This agg needs information about sampling (sts) and entitlements (st) to make the appropriate decisions on running */
|
|
33
32
|
this.waitForFlags(['sts', 'st'])
|
|
34
33
|
.then(([stMode, stEntitled]) => this.initialize(stMode, stEntitled))
|
|
@@ -60,9 +59,9 @@ export class Aggregate extends AggregateBase {
|
|
|
60
59
|
})
|
|
61
60
|
|
|
62
61
|
if (typeof PerformanceNavigationTiming !== 'undefined') {
|
|
63
|
-
this.
|
|
62
|
+
this.events.storeTiming(globalScope.performance?.getEntriesByType?.('navigation')[0])
|
|
64
63
|
} else {
|
|
65
|
-
this.
|
|
64
|
+
this.events.storeTiming(globalScope.performance?.timing, true)
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
|
|
@@ -77,21 +76,21 @@ export class Aggregate extends AggregateBase {
|
|
|
77
76
|
|
|
78
77
|
this.timeKeeper ??= this.agentRef.runtime.timeKeeper
|
|
79
78
|
|
|
80
|
-
this.scheduler = new HarvestScheduler(
|
|
81
|
-
onFinished: this.
|
|
79
|
+
this.scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
80
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
|
|
82
81
|
retryDelay: this.harvestTimeSeconds,
|
|
83
|
-
getPayload: this.
|
|
82
|
+
getPayload: (options) => this.makeHarvestPayload(options.retry),
|
|
84
83
|
raw: true
|
|
85
84
|
}, this)
|
|
86
85
|
|
|
87
86
|
/** The handlers set up by the Inst file */
|
|
88
|
-
registerHandler('bst', (...args) => this.
|
|
89
|
-
registerHandler('bstResource', (...args) => this.
|
|
90
|
-
registerHandler('bstHist', (...args) => this.
|
|
91
|
-
registerHandler('bstXhrAgg', (...args) => this.
|
|
92
|
-
registerHandler('bstApi', (...args) => this.
|
|
93
|
-
registerHandler('trace-jserror', (...args) => this.
|
|
94
|
-
registerHandler('pvtAdded', (...args) => this.
|
|
87
|
+
registerHandler('bst', (...args) => this.events.storeEvent(...args), this.featureName, this.ee)
|
|
88
|
+
registerHandler('bstResource', (...args) => this.events.storeResources(...args), this.featureName, this.ee)
|
|
89
|
+
registerHandler('bstHist', (...args) => this.events.storeHist(...args), this.featureName, this.ee)
|
|
90
|
+
registerHandler('bstXhrAgg', (...args) => this.events.storeXhrAgg(...args), this.featureName, this.ee)
|
|
91
|
+
registerHandler('bstApi', (...args) => this.events.storeSTN(...args), this.featureName, this.ee)
|
|
92
|
+
registerHandler('trace-jserror', (...args) => this.events.storeErrorAgg(...args), this.featureName, this.ee)
|
|
93
|
+
registerHandler('pvtAdded', (...args) => this.events.processPVT(...args), this.featureName, this.ee)
|
|
95
94
|
|
|
96
95
|
/** Only start actually harvesting if running in full mode at init time */
|
|
97
96
|
if (this.mode === MODE.FULL) this.startHarvesting()
|
|
@@ -112,28 +111,32 @@ export class Aggregate extends AggregateBase {
|
|
|
112
111
|
this.scheduler.startTimer(this.harvestTimeSeconds)
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.traceStorage.prevStoredEvents.clear() // release references to past events for GC
|
|
114
|
+
preHarvestChecks () {
|
|
115
|
+
if (this.mode !== MODE.FULL) return // only allow harvest if running in full mode
|
|
118
116
|
if (!this.timeKeeper?.ready) return // this should likely never happen, but just to be safe, we should never harvest if we cant correct time
|
|
119
|
-
if (this.
|
|
120
|
-
if (this.sessionId !== this.agentRef.runtime.session
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (options.retry) {
|
|
125
|
-
this.sentTrace = stns
|
|
117
|
+
if (!this.agentRef.runtime.session) return // session entity is required for trace to run and continue running
|
|
118
|
+
if (this.sessionId !== this.agentRef.runtime.session.state.value || this.ptid !== this.agentRef.runtime.ptid) {
|
|
119
|
+
// If something unexpected happened and we somehow still got to harvesting after a session identifier changed, we should force-exit instead of harvesting:
|
|
120
|
+
this.abort(3)
|
|
121
|
+
return
|
|
126
122
|
}
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
serializer ({ stns }) {
|
|
127
|
+
if (!stns.length) return // there are no processed nodes
|
|
128
|
+
this.everHarvested = true
|
|
129
|
+
return applyFnToProps(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
|
|
130
|
+
}
|
|
127
131
|
|
|
132
|
+
queryStringsBuilder ({ stns, earliestTimeStamp, latestTimeStamp }) {
|
|
128
133
|
const firstSessionHarvest = !this.agentRef.runtime.session.state.traceHarvestStarted
|
|
129
134
|
if (firstSessionHarvest) this.agentRef.runtime.session.write({ traceHarvestStarted: true })
|
|
135
|
+
const hasReplay = this.agentRef.runtime.session.state.sessionReplayMode === 1
|
|
136
|
+
const endUserId = this.agentRef.info.jsAttributes['enduser.id']
|
|
137
|
+
const entityGuid = this.agentRef.runtime.appMetadata.agents?.[0]?.entityGuid
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
const endUserId = this.agentRef.info?.jsAttributes?.['enduser.id']
|
|
133
|
-
|
|
134
|
-
this.everHarvested = true
|
|
135
|
-
|
|
136
|
-
/** The blob consumer expects the following and will reject if not supplied:
|
|
139
|
+
/* The blob consumer expects the following and will reject if not supplied:
|
|
137
140
|
* browser_monitoring_key
|
|
138
141
|
* type
|
|
139
142
|
* app_id
|
|
@@ -142,47 +145,34 @@ export class Aggregate extends AggregateBase {
|
|
|
142
145
|
*
|
|
143
146
|
* For data that does not fit the schema of the above, it should be url-encoded and placed into `attributes`
|
|
144
147
|
*/
|
|
145
|
-
const agentMetadata = this.agentRef.runtime.appMetadata?.agents?.[0] || {}
|
|
146
|
-
return {
|
|
147
|
-
qs: {
|
|
148
|
-
browser_monitoring_key: this.agentRef.info.licenseKey,
|
|
149
|
-
type: 'BrowserSessionChunk',
|
|
150
|
-
app_id: this.agentRef.info.applicationID,
|
|
151
|
-
protocol_version: '0',
|
|
152
|
-
timestamp: Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
|
|
153
|
-
attributes: encodeObj({
|
|
154
|
-
...(agentMetadata.entityGuid && { entityGuid: agentMetadata.entityGuid }),
|
|
155
|
-
harvestId: `${this.agentRef.runtime.session?.state.value}_${this.agentRef.runtime.ptid}_${this.agentRef.runtime.harvestCount}`,
|
|
156
|
-
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
157
|
-
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
158
|
-
// trace payload metadata
|
|
159
|
-
'trace.firstTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
|
|
160
|
-
'trace.lastTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(latestTimeStamp)),
|
|
161
|
-
'trace.nodes': stns.length,
|
|
162
|
-
'trace.originTimestamp': this.timeKeeper.correctedOriginTime,
|
|
163
|
-
// other payload metadata
|
|
164
|
-
agentVersion: this.agentRef.runtime.version,
|
|
165
|
-
...(firstSessionHarvest && { firstSessionHarvest }),
|
|
166
|
-
...(hasReplay && { hasReplay }),
|
|
167
|
-
ptid: `${this.ptid}`,
|
|
168
|
-
session: `${this.sessionId}`,
|
|
169
|
-
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
170
|
-
...(endUserId && { 'enduser.id': this.obfuscator.obfuscateString(endUserId) }),
|
|
171
|
-
currentUrl: this.obfuscator.obfuscateString(cleanURL('' + location))
|
|
172
|
-
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
173
|
-
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
174
|
-
},
|
|
175
|
-
body: applyFnToProps(stns, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
148
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
149
|
+
return {
|
|
150
|
+
browser_monitoring_key: this.agentRef.info.licenseKey,
|
|
151
|
+
type: 'BrowserSessionChunk',
|
|
152
|
+
app_id: this.agentRef.info.applicationID,
|
|
153
|
+
protocol_version: '0',
|
|
154
|
+
timestamp: Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
|
|
155
|
+
attributes: encodeObj({
|
|
156
|
+
...(entityGuid && { entityGuid }),
|
|
157
|
+
harvestId: `${this.agentRef.runtime.session.state.value}_${this.agentRef.runtime.ptid}_${this.agentRef.runtime.harvestCount}`,
|
|
158
|
+
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
159
|
+
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
160
|
+
// trace payload metadata
|
|
161
|
+
'trace.firstTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(earliestTimeStamp)),
|
|
162
|
+
'trace.lastTimestamp': Math.floor(this.timeKeeper.correctRelativeTimestamp(latestTimeStamp)),
|
|
163
|
+
'trace.nodes': stns.length,
|
|
164
|
+
'trace.originTimestamp': this.timeKeeper.correctedOriginTime,
|
|
165
|
+
// other payload metadata
|
|
166
|
+
agentVersion: this.agentRef.runtime.version,
|
|
167
|
+
...(firstSessionHarvest && { firstSessionHarvest }),
|
|
168
|
+
...(hasReplay && { hasReplay }),
|
|
169
|
+
ptid: `${this.ptid}`,
|
|
170
|
+
session: `${this.sessionId}`,
|
|
171
|
+
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
172
|
+
...(endUserId && { 'enduser.id': this.obfuscator.obfuscateString(endUserId) }),
|
|
173
|
+
currentUrl: this.obfuscator.obfuscateString(cleanURL('' + location))
|
|
174
|
+
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
175
|
+
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
186
176
|
}
|
|
187
177
|
}
|
|
188
178
|
|
|
@@ -194,16 +184,17 @@ export class Aggregate extends AggregateBase {
|
|
|
194
184
|
this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
|
|
195
185
|
if (prevMode === MODE.OFF || !this.initialized) return this.initialize(this.mode, this.entitled)
|
|
196
186
|
if (this.initialized) {
|
|
197
|
-
this.
|
|
187
|
+
this.events.trimSTNs(ERROR_MODE_SECONDS_WINDOW) // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
|
|
198
188
|
}
|
|
199
189
|
this.startHarvesting()
|
|
200
190
|
}
|
|
201
191
|
|
|
202
192
|
/** Stop running for the remainder of the page lifecycle */
|
|
203
|
-
abort (
|
|
193
|
+
abort () {
|
|
204
194
|
this.blocked = true
|
|
205
195
|
this.mode = MODE.OFF
|
|
206
196
|
this.agentRef.runtime.session.write({ sessionTraceMode: this.mode })
|
|
207
197
|
this.scheduler?.stopTimer()
|
|
198
|
+
this.events.clear()
|
|
208
199
|
}
|
|
209
200
|
}
|
|
@@ -29,8 +29,8 @@ export class TraceStorage {
|
|
|
29
29
|
trace = {}
|
|
30
30
|
earliestTimeStamp = Infinity
|
|
31
31
|
latestTimeStamp = 0
|
|
32
|
-
tempStorage = []
|
|
33
32
|
prevStoredEvents = new Set()
|
|
33
|
+
#backupTrace
|
|
34
34
|
|
|
35
35
|
constructor (parent) {
|
|
36
36
|
this.parent = parent
|
|
@@ -44,9 +44,6 @@ export class TraceStorage {
|
|
|
44
44
|
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
|
|
45
45
|
if (openedSpace === 0) return
|
|
46
46
|
}
|
|
47
|
-
while (this.tempStorage.length) {
|
|
48
|
-
this.storeSTN(this.tempStorage.shift())
|
|
49
|
-
}
|
|
50
47
|
|
|
51
48
|
if (this.trace[stn.n]) this.trace[stn.n].push(stn)
|
|
52
49
|
else this.trace[stn.n] = [stn]
|
|
@@ -96,15 +93,9 @@ export class TraceStorage {
|
|
|
96
93
|
const partitionListByOriginMap = listOfSTNodes.sort((a, b) => a.s - b.s).reduce(reindexByOriginFn, {})
|
|
97
94
|
return Object.values(partitionListByOriginMap).flat() // join the partitions back into 1-D, now ordered by origin then start time
|
|
98
95
|
}, this)
|
|
99
|
-
if (stns.length === 0) return {}
|
|
100
96
|
|
|
101
|
-
this.trace = {}
|
|
102
|
-
this.nodeCount = 0
|
|
103
97
|
const earliestTimeStamp = this.earliestTimeStamp
|
|
104
|
-
this.earliestTimeStamp = Infinity
|
|
105
98
|
const latestTimeStamp = this.latestTimeStamp
|
|
106
|
-
this.latestTimeStamp = 0
|
|
107
|
-
|
|
108
99
|
return { stns, earliestTimeStamp, latestTimeStamp }
|
|
109
100
|
}
|
|
110
101
|
|
|
@@ -282,10 +273,30 @@ export class TraceStorage {
|
|
|
282
273
|
this.storeSTN(new TraceNode('Ajax', metrics.time, metrics.time + metrics.duration, `${params.status} ${params.method}: ${params.host}${params.pathname}`, 'ajax'))
|
|
283
274
|
}
|
|
284
275
|
|
|
285
|
-
|
|
286
|
-
|
|
276
|
+
/* Below are the interface expected & required of whatever storage is used across all features on an individual basis. This allows a common `.events` property on Trace. */
|
|
277
|
+
isEmpty () {
|
|
278
|
+
return this.nodeCount === 0
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
save () {
|
|
282
|
+
this.#backupTrace = this.trace
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
get = this.takeSTNs
|
|
286
|
+
|
|
287
|
+
clear () {
|
|
288
|
+
this.trace = {}
|
|
289
|
+
this.nodeCount = 0
|
|
290
|
+
this.prevStoredEvents.clear() // release references to past events for GC
|
|
291
|
+
this.earliestTimeStamp = Infinity
|
|
292
|
+
this.latestTimeStamp = 0
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
reloadSave () {
|
|
296
|
+
Object.values(this.#backupTrace).forEach(stnsArray => stnsArray.forEach(stn => this.storeSTN(stn)))
|
|
297
|
+
}
|
|
287
298
|
|
|
288
|
-
|
|
289
|
-
this
|
|
299
|
+
clearSave () {
|
|
300
|
+
this.#backupTrace = undefined
|
|
290
301
|
}
|
|
291
302
|
}
|
|
@@ -3,10 +3,9 @@ import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
|
3
3
|
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
|
|
4
4
|
import { single } from '../../../common/util/invoke'
|
|
5
5
|
import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte'
|
|
6
|
-
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
6
|
+
import { FEATURE_NAMES, FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
|
|
7
7
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
8
8
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
9
|
-
import { EventBuffer } from '../../utils/event-buffer'
|
|
10
9
|
import { API_TRIGGER_NAME, FEATURE_NAME, INTERACTION_STATUS } from '../constants'
|
|
11
10
|
import { AjaxNode } from './ajax-node'
|
|
12
11
|
import { InitialPageLoadInteraction } from './initial-page-load-interaction'
|
|
@@ -18,7 +17,7 @@ export class Aggregate extends AggregateBase {
|
|
|
18
17
|
super(agentRef, FEATURE_NAME)
|
|
19
18
|
|
|
20
19
|
const harvestTimeSeconds = agentRef.init.soft_navigations.harvestTimeSeconds || 10
|
|
21
|
-
this.interactionsToHarvest =
|
|
20
|
+
this.interactionsToHarvest = this.events
|
|
22
21
|
this.domObserver = domObserver
|
|
23
22
|
|
|
24
23
|
this.initialPageLoadInteraction = new InitialPageLoadInteraction(agentRef.agentIdentifier)
|
|
@@ -39,12 +38,12 @@ export class Aggregate extends AggregateBase {
|
|
|
39
38
|
this.waitForFlags(['spa']).then(([spaOn]) => {
|
|
40
39
|
if (spaOn) {
|
|
41
40
|
this.drain()
|
|
42
|
-
const scheduler = new HarvestScheduler(
|
|
43
|
-
onFinished: this.
|
|
41
|
+
const scheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
|
|
42
|
+
onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
|
|
43
|
+
getPayload: (options) => this.makeHarvestPayload(options.retry),
|
|
44
44
|
retryDelay: harvestTimeSeconds,
|
|
45
45
|
onUnload: () => this.interactionInProgress?.done() // return any held ajax or jserr events so they can be sent with EoL harvest
|
|
46
46
|
}, this)
|
|
47
|
-
scheduler.harvest.on('events', this.onHarvestStarted.bind(this))
|
|
48
47
|
scheduler.startTimer(harvestTimeSeconds, 0)
|
|
49
48
|
} else {
|
|
50
49
|
this.blocked = true // if rum response determines that customer lacks entitlements for spa endpoint, this feature shouldn't harvest
|
|
@@ -66,27 +65,16 @@ export class Aggregate extends AggregateBase {
|
|
|
66
65
|
registerHandler('jserror', this.#handleJserror.bind(this), this.featureName, this.ee)
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
if (!this.interactionsToHarvest.hasData || this.blocked) return
|
|
68
|
+
serializer (eventBuffer) {
|
|
71
69
|
// The payload depacker takes the first ixn of a payload (if there are multiple ixns) and positively offset the subsequent ixns timestamps by that amount.
|
|
72
70
|
// In order to accurately portray the real start & end times of the 2nd & onward ixns, we hence need to negatively offset their start timestamps with that of the 1st ixn.
|
|
73
71
|
let firstIxnStartTime = 0 // the very 1st ixn does not require any offsetting
|
|
74
72
|
const serializedIxnList = []
|
|
75
|
-
for (const interaction of
|
|
73
|
+
for (const interaction of eventBuffer) {
|
|
76
74
|
serializedIxnList.push(interaction.serialize(firstIxnStartTime))
|
|
77
75
|
if (!firstIxnStartTime) firstIxnStartTime = Math.floor(interaction.start)
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (options.retry) this.interactionsToHarvest.hold()
|
|
82
|
-
else this.interactionsToHarvest.clear()
|
|
83
|
-
|
|
84
|
-
return { body: { e: payload } }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
onHarvestFinished (result) {
|
|
88
|
-
if (result.sent && result.retry && this.interactionsToHarvest.held.hasData) this.interactionsToHarvest.unhold()
|
|
89
|
-
else this.interactionsToHarvest.held.clear()
|
|
77
|
+
return `bel.7;${serializedIxnList.join(';')}`
|
|
90
78
|
}
|
|
91
79
|
|
|
92
80
|
startUIInteraction (eventName, startedAt, sourceElem) { // this is throttled by instrumentation so that it isn't excessively called
|
|
@@ -139,8 +127,9 @@ export class Aggregate extends AggregateBase {
|
|
|
139
127
|
*/
|
|
140
128
|
if (this.interactionInProgress?.isActiveDuring(timestamp)) return this.interactionInProgress
|
|
141
129
|
let saveIxn
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
const interactionsBuffer = this.interactionsToHarvest.get()
|
|
131
|
+
for (let idx = interactionsBuffer.length - 1; idx >= 0; idx--) { // reverse search for the latest completed interaction for efficiency
|
|
132
|
+
const finishedInteraction = interactionsBuffer[idx]
|
|
144
133
|
if (finishedInteraction.isActiveDuring(timestamp)) {
|
|
145
134
|
if (finishedInteraction.trigger !== 'initialPageLoad') return finishedInteraction
|
|
146
135
|
// It's possible that a complete interaction occurs before page is fully loaded, so we need to consider if a route-change ixn may have overlapped this iPL
|
|
@@ -3,7 +3,6 @@ import { isBrowserScope } from '../../../common/constants/runtime'
|
|
|
3
3
|
import { handle } from '../../../common/event-emitter/handle'
|
|
4
4
|
import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
|
|
5
5
|
import { debounce } from '../../../common/util/invoke'
|
|
6
|
-
import { wrapEvents } from '../../../common/wrap/wrap-events'
|
|
7
6
|
import { wrapHistory } from '../../../common/wrap/wrap-history'
|
|
8
7
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
9
8
|
import { FEATURE_NAME, INTERACTION_TRIGGERS } from '../constants'
|
|
@@ -23,7 +22,12 @@ export class Instrument extends InstrumentBase {
|
|
|
23
22
|
if (!isBrowserScope || !gosNREUMOriginals().o.MO) return // soft navigations is not supported outside web env or browsers without the mutation observer API
|
|
24
23
|
|
|
25
24
|
const historyEE = wrapHistory(this.ee)
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
INTERACTION_TRIGGERS.forEach((trigger) => {
|
|
27
|
+
windowAddEventListener(trigger, (evt) => {
|
|
28
|
+
processUserInteraction(evt)
|
|
29
|
+
}, true)
|
|
30
|
+
})
|
|
27
31
|
|
|
28
32
|
const trackURLChange = () => handle('newURL', [now(), '' + window.location], undefined, this.featureName, this.ee)
|
|
29
33
|
historyEE.on('pushState-end', trackURLChange)
|
|
@@ -50,13 +54,6 @@ export class Instrument extends InstrumentBase {
|
|
|
50
54
|
domObserver.observe(document.body, { attributes: true, childList: true, subtree: true, characterData: true })
|
|
51
55
|
}, UI_WAIT_INTERVAL, { leading: true })
|
|
52
56
|
|
|
53
|
-
eventsEE.on('fn-start', ([evt]) => { // set up a new user ixn before the callback for the triggering event executes
|
|
54
|
-
if (INTERACTION_TRIGGERS.includes(evt?.type)) {
|
|
55
|
-
processUserInteraction(evt)
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
for (let eventType of INTERACTION_TRIGGERS) document.addEventListener(eventType, () => { /* no-op, this ensures the UI events are monitored by our callback above */ })
|
|
59
|
-
|
|
60
57
|
this.abortHandler = abort
|
|
61
58
|
this.importAggregator(agentRef, { domObserver })
|
|
62
59
|
|