@newrelic/browser-agent 1.233.1 → 1.235.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/README.md +1 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/constants/shared-channel.js +19 -0
- package/dist/cjs/common/event-emitter/contextual-ee.test.js +10 -10
- package/dist/cjs/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +2 -2
- package/dist/cjs/common/harvest/harvest-scheduler.js +21 -5
- package/dist/cjs/common/harvest/harvest.component-test.js +224 -0
- package/dist/cjs/common/harvest/harvest.js +4 -11
- package/dist/cjs/common/session/{session-entity.test.js → session-entity.component-test.js} +79 -42
- package/dist/cjs/common/session/session-entity.js +19 -11
- package/dist/cjs/common/timer/interaction-timer.js +1 -1
- package/dist/cjs/common/url/canonicalize-url.test.js +26 -30
- package/dist/cjs/common/url/encode.js +2 -2
- package/dist/cjs/common/util/console.test.js +30 -0
- package/dist/cjs/common/util/data-size.test.js +37 -20
- package/dist/cjs/common/util/feature-flags.js +23 -12
- package/dist/cjs/common/util/feature-flags.test.js +84 -0
- package/dist/cjs/common/util/get-or-set.js +8 -1
- package/dist/cjs/common/util/get-or-set.test.js +47 -0
- package/dist/cjs/common/util/global-scope.js +1 -32
- package/dist/cjs/common/util/global-scope.test.js +72 -0
- package/dist/cjs/common/util/obfuscate.component-test.js +129 -0
- package/dist/cjs/common/util/obfuscate.js +2 -2
- package/dist/cjs/common/util/stringify.test.js +48 -0
- package/dist/cjs/common/util/submit-data.js +18 -18
- package/dist/cjs/common/util/submit-data.test.js +245 -0
- package/dist/cjs/common/util/traverse.js +19 -27
- package/dist/cjs/common/util/traverse.test.js +44 -0
- package/dist/cjs/common/wrap/wrap-raf.js +1 -1
- package/dist/cjs/common/wrap/wrap-timer.js +1 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +4 -0
- package/dist/cjs/features/jserrors/instrument/index.js +2 -15
- package/dist/cjs/features/metrics/aggregate/endpoint-map.js +14 -0
- package/dist/cjs/features/metrics/aggregate/index.js +3 -2
- package/dist/cjs/features/metrics/instrument/index.js +0 -2
- package/dist/cjs/features/page_view_event/aggregate/index.js +58 -44
- package/dist/cjs/features/session_replay/aggregate/index.component-test.js +457 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +99 -82
- package/dist/cjs/features/session_replay/replay-mode.js +28 -0
- package/dist/cjs/features/session_trace/aggregate/index.js +222 -99
- package/dist/cjs/features/session_trace/constants.js +1 -3
- package/dist/cjs/features/session_trace/instrument/index.js +0 -16
- package/dist/cjs/features/spa/constants.js +0 -1
- package/dist/cjs/features/utils/agent-session.js +20 -36
- package/dist/cjs/features/utils/agent-session.test.js +211 -0
- package/dist/cjs/features/utils/aggregate-base.js +7 -12
- package/dist/cjs/features/utils/aggregate-base.test.js +110 -0
- package/dist/cjs/features/utils/feature-base.test.js +42 -0
- package/dist/cjs/features/utils/handler-cache.js +28 -23
- package/dist/cjs/features/utils/handler-cache.test.js +53 -0
- package/dist/cjs/features/utils/instrument-base.js +58 -39
- package/dist/cjs/features/utils/instrument-base.test.js +179 -0
- package/dist/cjs/features/utils/lazy-feature-loader.test.js +30 -0
- package/dist/cjs/loaders/agent.js +0 -1
- package/dist/cjs/loaders/api/api.js +1 -1
- package/dist/cjs/loaders/configure/configure.js +0 -1
- package/dist/cjs/loaders/features/featureDependencies.js +2 -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/constants/shared-channel.js +12 -0
- package/dist/esm/common/event-emitter/contextual-ee.test.js +10 -10
- package/dist/esm/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +2 -2
- package/dist/esm/common/harvest/harvest-scheduler.js +21 -5
- package/dist/esm/common/harvest/harvest.component-test.js +222 -0
- package/dist/esm/common/harvest/harvest.js +4 -11
- package/dist/esm/common/session/{session-entity.test.js → session-entity.component-test.js} +77 -40
- package/dist/esm/common/session/session-entity.js +17 -11
- package/dist/esm/common/timer/interaction-timer.js +1 -1
- package/dist/esm/common/url/canonicalize-url.test.js +25 -29
- package/dist/esm/common/url/encode.js +2 -2
- package/dist/esm/common/util/console.test.js +28 -0
- package/dist/esm/common/util/data-size.test.js +35 -20
- package/dist/esm/common/util/feature-flags.js +23 -12
- package/dist/esm/common/util/feature-flags.test.js +80 -0
- package/dist/esm/common/util/get-or-set.js +8 -1
- package/dist/esm/common/util/get-or-set.test.js +45 -0
- package/dist/esm/common/util/global-scope.js +1 -29
- package/dist/esm/common/util/global-scope.test.js +70 -0
- package/dist/esm/common/util/obfuscate.component-test.js +125 -0
- package/dist/esm/common/util/obfuscate.js +2 -2
- package/dist/esm/common/util/stringify.test.js +46 -0
- package/dist/esm/common/util/submit-data.js +18 -18
- package/dist/esm/common/util/submit-data.test.js +241 -0
- package/dist/esm/common/util/traverse.js +19 -27
- package/dist/esm/common/util/traverse.test.js +42 -0
- package/dist/esm/common/wrap/wrap-raf.js +1 -1
- package/dist/esm/common/wrap/wrap-timer.js +1 -1
- package/dist/esm/features/jserrors/aggregate/index.js +4 -0
- package/dist/esm/features/jserrors/instrument/index.js +2 -15
- package/dist/esm/features/metrics/aggregate/endpoint-map.js +7 -0
- package/dist/esm/features/metrics/aggregate/index.js +3 -2
- package/dist/esm/features/metrics/instrument/index.js +0 -2
- package/dist/esm/features/page_view_event/aggregate/index.js +58 -44
- package/dist/esm/features/session_replay/aggregate/index.component-test.js +453 -0
- package/dist/esm/features/session_replay/aggregate/index.js +92 -78
- package/dist/esm/features/session_replay/replay-mode.js +23 -0
- package/dist/esm/features/session_trace/aggregate/index.js +223 -100
- package/dist/esm/features/session_trace/constants.js +0 -1
- package/dist/esm/features/session_trace/instrument/index.js +1 -17
- package/dist/esm/features/spa/constants.js +0 -1
- package/dist/esm/features/utils/agent-session.js +21 -37
- package/dist/esm/features/utils/agent-session.test.js +207 -0
- package/dist/esm/features/utils/aggregate-base.js +7 -12
- package/dist/esm/features/utils/aggregate-base.test.js +108 -0
- package/dist/esm/features/utils/feature-base.test.js +40 -0
- package/dist/esm/features/utils/handler-cache.js +28 -23
- package/dist/esm/features/utils/handler-cache.test.js +51 -0
- package/dist/esm/features/utils/instrument-base.js +58 -39
- package/dist/esm/features/utils/instrument-base.test.js +175 -0
- package/dist/esm/features/utils/lazy-feature-loader.test.js +29 -0
- package/dist/esm/loaders/agent.js +0 -1
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/esm/loaders/configure/configure.js +0 -1
- package/dist/esm/loaders/features/featureDependencies.js +2 -0
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/constants/shared-channel.d.ts +5 -0
- package/dist/types/common/constants/shared-channel.d.ts.map +1 -0
- package/dist/types/common/context/shared-context.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts +2 -0
- package/dist/types/common/harvest/harvest-scheduler.component-test.d.ts.map +1 -0
- package/dist/types/common/harvest/harvest-scheduler.d.ts +4 -0
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.component-test.d.ts +2 -0
- package/dist/types/common/harvest/harvest.component-test.d.ts.map +1 -0
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.component-test.d.ts +2 -0
- package/dist/types/common/session/session-entity.component-test.d.ts.map +1 -0
- package/dist/types/common/session/session-entity.d.ts +9 -5
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/timer/interaction-timer.component-test.d.ts +2 -0
- package/dist/types/common/timer/interaction-timer.component-test.d.ts.map +1 -0
- package/dist/types/common/timer/timer.d.ts.map +1 -1
- package/dist/types/common/url/encode.component-test.d.ts +2 -0
- package/dist/types/common/url/encode.component-test.d.ts.map +1 -0
- package/dist/types/common/url/protocol.component-test.d.ts +2 -0
- package/dist/types/common/url/protocol.component-test.d.ts.map +1 -0
- package/dist/types/common/util/feature-flags.d.ts +1 -0
- package/dist/types/common/util/feature-flags.d.ts.map +1 -1
- package/dist/types/common/util/get-or-set.d.ts +9 -1
- package/dist/types/common/util/get-or-set.d.ts.map +1 -1
- package/dist/types/common/util/global-scope.d.ts +0 -9
- package/dist/types/common/util/global-scope.d.ts.map +1 -1
- package/dist/types/common/util/obfuscate.component-test.d.ts +2 -0
- package/dist/types/common/util/obfuscate.component-test.d.ts.map +1 -0
- package/dist/types/common/util/submit-data.d.ts +14 -10
- package/dist/types/common/util/submit-data.d.ts.map +1 -1
- package/dist/types/common/util/traverse.d.ts +10 -1
- package/dist/types/common/util/traverse.d.ts.map +1 -1
- package/dist/types/common/window/nreum.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +1 -0
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +8 -0
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -1
- package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
- 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.component-test.d.ts +2 -0
- package/dist/types/features/session_replay/aggregate/index.component-test.d.ts.map +1 -0
- package/dist/types/features/session_replay/aggregate/index.d.ts +14 -5
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/replay-mode.d.ts +9 -0
- package/dist/types/features/session_replay/replay-mode.d.ts.map +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts +21 -3
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/constants.d.ts +0 -1
- package/dist/types/features/session_trace/constants.d.ts.map +1 -1
- package/dist/types/features/session_trace/instrument/index.d.ts +0 -2
- package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
- package/dist/types/features/spa/constants.d.ts.map +1 -1
- package/dist/types/features/utils/agent-session.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +6 -1
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/handler-cache.d.ts +12 -11
- package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts +17 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
- package/package.json +14 -8
- package/src/common/config/state/init.js +0 -1
- package/src/common/constants/shared-channel.js +13 -0
- package/src/common/context/shared-context.js +0 -1
- package/src/common/event-emitter/contextual-ee.test.js +10 -10
- package/src/common/harvest/{harvest-scheduler.test.js → harvest-scheduler.component-test.js} +2 -2
- package/src/common/harvest/harvest-scheduler.js +17 -6
- package/src/common/harvest/harvest.component-test.js +169 -0
- package/src/common/harvest/harvest.js +5 -9
- package/src/common/session/{session-entity.test.js → session-entity.component-test.js} +70 -48
- package/src/common/session/session-entity.js +15 -12
- package/src/common/timer/interaction-timer.js +1 -1
- package/src/common/timer/timer.js +0 -1
- package/src/common/url/canonicalize-url.test.js +32 -21
- package/src/common/url/encode.js +2 -2
- package/src/common/util/console.test.js +34 -0
- package/src/common/util/data-size.test.js +27 -20
- package/src/common/util/feature-flags.js +24 -12
- package/src/common/util/feature-flags.test.js +98 -0
- package/src/common/util/get-or-set.js +8 -1
- package/src/common/util/get-or-set.test.js +58 -0
- package/src/common/util/global-scope.js +0 -26
- package/src/common/util/global-scope.test.js +87 -0
- package/src/common/util/obfuscate.component-test.js +173 -0
- package/src/common/util/obfuscate.js +2 -2
- package/src/common/util/stringify.test.js +49 -0
- package/src/common/util/submit-data.js +18 -19
- package/src/common/util/submit-data.test.js +226 -0
- package/src/common/util/traverse.js +18 -27
- package/src/common/util/traverse.test.js +50 -0
- package/src/common/window/nreum.js +0 -1
- package/src/common/wrap/wrap-raf.js +1 -1
- package/src/common/wrap/wrap-timer.js +1 -1
- package/src/features/jserrors/aggregate/index.js +5 -0
- package/src/features/jserrors/instrument/index.js +2 -15
- package/src/features/metrics/aggregate/endpoint-map.js +7 -0
- package/src/features/metrics/aggregate/index.js +3 -2
- package/src/features/metrics/aggregate/polyfill-detection.es5.js +0 -1
- package/src/features/metrics/instrument/index.js +0 -2
- package/src/features/page_view_event/aggregate/index.js +48 -51
- package/src/features/page_view_event/instrument/index.js +0 -1
- package/src/features/session_replay/aggregate/index.component-test.js +368 -0
- package/src/features/session_replay/aggregate/index.js +96 -71
- package/src/features/session_replay/instrument/index.js +0 -1
- package/src/features/session_replay/replay-mode.js +23 -0
- package/src/features/session_trace/aggregate/index.js +198 -79
- package/src/features/session_trace/constants.js +0 -1
- package/src/features/session_trace/instrument/index.js +2 -19
- package/src/features/spa/constants.js +0 -1
- package/src/features/utils/agent-session.js +22 -34
- package/src/features/utils/agent-session.test.js +194 -0
- package/src/features/utils/aggregate-base.js +12 -9
- package/src/features/utils/aggregate-base.test.js +122 -0
- package/src/features/utils/feature-base.test.js +45 -0
- package/src/features/utils/handler-cache.js +29 -24
- package/src/features/utils/handler-cache.test.js +72 -0
- package/src/features/utils/instrument-base.js +45 -29
- package/src/features/utils/instrument-base.test.js +190 -0
- package/src/features/utils/lazy-feature-loader.test.js +37 -0
- package/src/loaders/agent.js +0 -1
- package/src/loaders/api/api.js +2 -2
- package/src/loaders/configure/configure.js +0 -1
- package/src/loaders/features/featureDependencies.js +2 -0
- package/dist/cjs/common/storage/local-memory.js +0 -35
- package/dist/cjs/common/storage/local-memory.test.js +0 -20
- package/dist/cjs/common/util/s-hash.js +0 -19
- package/dist/cjs/features/metrics/instrument/workers-helper.js +0 -124
- package/dist/esm/common/storage/local-memory.js +0 -28
- package/dist/esm/common/storage/local-memory.test.js +0 -18
- package/dist/esm/common/util/s-hash.js +0 -13
- package/dist/esm/features/metrics/instrument/workers-helper.js +0 -119
- package/dist/types/common/storage/local-memory.d.ts +0 -8
- package/dist/types/common/storage/local-memory.d.ts.map +0 -1
- package/dist/types/common/util/s-hash.d.ts +0 -2
- package/dist/types/common/util/s-hash.d.ts.map +0 -1
- package/dist/types/features/metrics/instrument/workers-helper.d.ts +0 -7
- package/dist/types/features/metrics/instrument/workers-helper.d.ts.map +0 -1
- package/src/common/storage/local-memory.js +0 -30
- package/src/common/storage/local-memory.test.js +0 -19
- package/src/common/util/s-hash.js +0 -14
- package/src/features/metrics/instrument/workers-helper.js +0 -113
- /package/dist/cjs/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
- /package/dist/cjs/common/url/{encode.test.js → encode.component-test.js} +0 -0
- /package/dist/cjs/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
- /package/dist/esm/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
- /package/dist/esm/common/url/{encode.test.js → encode.component-test.js} +0 -0
- /package/dist/esm/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
- /package/src/common/timer/{interaction-timer.test.js → interaction-timer.component-test.js} +0 -0
- /package/src/common/url/{encode.test.js → encode.component-test.js} +0 -0
- /package/src/common/url/{protocol.test.js → protocol.component-test.js} +0 -0
|
@@ -32,6 +32,7 @@ export class Aggregate extends AggregateBase {
|
|
|
32
32
|
super(agentIdentifier, aggregator, FEATURE_NAME)
|
|
33
33
|
|
|
34
34
|
this.stackReported = {}
|
|
35
|
+
this.observedAt = {}
|
|
35
36
|
this.pageviewReported = {}
|
|
36
37
|
this.errorCache = {}
|
|
37
38
|
this.currentBody
|
|
@@ -171,6 +172,7 @@ export class Aggregate extends AggregateBase {
|
|
|
171
172
|
if (!this.stackReported[bucketHash]) {
|
|
172
173
|
this.stackReported[bucketHash] = true
|
|
173
174
|
params.stack_trace = truncateSize(stackInfo.stackString)
|
|
175
|
+
this.observedAt[bucketHash] = agentRuntime.offset + time
|
|
174
176
|
} else {
|
|
175
177
|
params.browser_stack_hash = stringHashCode(stackInfo.stackString)
|
|
176
178
|
}
|
|
@@ -186,6 +188,9 @@ export class Aggregate extends AggregateBase {
|
|
|
186
188
|
this.pageviewReported[bucketHash] = true
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
if (agentRuntime?.session?.state?.sessionReplay) params.hasReplay = true
|
|
192
|
+
params.firstOccurrenceTimestamp = this.observedAt[bucketHash]
|
|
193
|
+
|
|
189
194
|
var type = internal ? 'ierr' : 'err'
|
|
190
195
|
var newMetrics = { time: time }
|
|
191
196
|
|
|
@@ -38,7 +38,7 @@ export class Instrument extends InstrumentBase {
|
|
|
38
38
|
return true
|
|
39
39
|
})
|
|
40
40
|
this.thrown = true
|
|
41
|
-
|
|
41
|
+
handle('err', [err, now()], undefined, FEATURE_NAMES.jserrors, thisInstrument.ee)
|
|
42
42
|
}
|
|
43
43
|
})
|
|
44
44
|
thisInstrument.ee.on('fn-end', function () {
|
|
@@ -89,7 +89,7 @@ export class Instrument extends InstrumentBase {
|
|
|
89
89
|
|
|
90
90
|
try {
|
|
91
91
|
if (this.skipNext) this.skipNext -= 1
|
|
92
|
-
else
|
|
92
|
+
else handle('err', [errorObj || new UncaughtException(message, filename, lineno), now()], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
93
93
|
} catch (e) {
|
|
94
94
|
try {
|
|
95
95
|
handle('ierr', [e, now(), true], undefined, FEATURE_NAMES.jserrors, this.ee)
|
|
@@ -113,19 +113,6 @@ function UncaughtException (message, filename, lineno) {
|
|
|
113
113
|
this.line = lineno
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/**
|
|
117
|
-
* Adds a timestamp and emits the 'err' event, which the error aggregator listens for
|
|
118
|
-
* @param {Error} err
|
|
119
|
-
* @param {boolean} doNotStamp
|
|
120
|
-
* @param {ContextualEE} ee
|
|
121
|
-
*/
|
|
122
|
-
function notice (err, doNotStamp, ee) {
|
|
123
|
-
// by default add timestamp, unless specifically told not to
|
|
124
|
-
// this is to preserve existing behavior
|
|
125
|
-
var time = (!doNotStamp) ? now() : null
|
|
126
|
-
handle('err', [err, time], undefined, FEATURE_NAMES.jserrors, ee)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
116
|
/**
|
|
130
117
|
* Attempts to cast an unhandledPromiseRejection reason (reject(...)) to an Error object
|
|
131
118
|
* @param {*} reason - The reason property from an unhandled promise rejection
|
|
@@ -13,6 +13,7 @@ import { windowAddEventListener } from '../../../common/event-listener/event-lis
|
|
|
13
13
|
import { isBrowserScope } from '../../../common/util/global-scope'
|
|
14
14
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
15
15
|
import { stringify } from '../../../common/util/stringify'
|
|
16
|
+
import { endpointMap } from './endpoint-map'
|
|
16
17
|
|
|
17
18
|
export class Aggregate extends AggregateBase {
|
|
18
19
|
static featureName = FEATURE_NAME
|
|
@@ -125,7 +126,7 @@ export class Aggregate extends AggregateBase {
|
|
|
125
126
|
// Capture per-agent bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
|
|
126
127
|
Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
|
|
127
128
|
this.storeSupportabilityMetrics(
|
|
128
|
-
`PageSession/Endpoint/${endpoint
|
|
129
|
+
`PageSession/Endpoint/${endpointMap[endpoint]}/BytesSent`,
|
|
129
130
|
agentRuntime.bytesSent[endpoint]
|
|
130
131
|
)
|
|
131
132
|
})
|
|
@@ -133,7 +134,7 @@ export class Aggregate extends AggregateBase {
|
|
|
133
134
|
// Capture per-agent query bytes sent for each endpoint (see harvest) and RUM call (see page_view_event aggregator).
|
|
134
135
|
Object.keys(agentRuntime.bytesSent).forEach(endpoint => {
|
|
135
136
|
this.storeSupportabilityMetrics(
|
|
136
|
-
`PageSession/Endpoint/${endpoint
|
|
137
|
+
`PageSession/Endpoint/${endpointMap[endpoint]}/QueryBytesSent`,
|
|
137
138
|
agentRuntime.queryBytesSent[endpoint]
|
|
138
139
|
)
|
|
139
140
|
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { InstrumentBase } from '../../utils/instrument-base'
|
|
2
|
-
import { insertSupportMetrics } from './workers-helper'
|
|
3
2
|
import { FEATURE_NAME, SUPPORTABILITY_METRIC_CHANNEL } from '../constants'
|
|
4
3
|
import { handle } from '../../../common/event-emitter/handle'
|
|
5
4
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
@@ -8,7 +7,6 @@ export class Instrument extends InstrumentBase {
|
|
|
8
7
|
static featureName = FEATURE_NAME
|
|
9
8
|
constructor (agentIdentifier, aggregator, auto = true) {
|
|
10
9
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto)
|
|
11
|
-
insertSupportMetrics(tag => handle(SUPPORTABILITY_METRIC_CHANNEL, [tag], undefined, FEATURE_NAMES.metrics, this.ee))
|
|
12
10
|
this.importAggregator()
|
|
13
11
|
}
|
|
14
12
|
}
|
|
@@ -2,22 +2,19 @@ import { handle } from '../../../common/event-emitter/handle'
|
|
|
2
2
|
import { FEATURE_NAMES } from '../../../loaders/features/features'
|
|
3
3
|
import { isiOS } from '../../../common/browser-version/ios-version'
|
|
4
4
|
import { onTTFB } from 'web-vitals'
|
|
5
|
-
import { mapOwn } from '../../../common/util/map-own'
|
|
6
|
-
import { param, fromArray } from '../../../common/url/encode'
|
|
7
5
|
import { addPT, addPN } from '../../../common/timing/nav-timing'
|
|
8
6
|
import { stringify } from '../../../common/util/stringify'
|
|
9
7
|
import { paintMetrics } from '../../../common/metrics/paint-metrics'
|
|
10
|
-
import { submitData } from '../../../common/util/submit-data'
|
|
11
8
|
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
|
|
12
|
-
import {
|
|
9
|
+
import { Harvest } from '../../../common/harvest/harvest'
|
|
13
10
|
import * as CONSTANTS from '../constants'
|
|
14
11
|
import { getActivatedFeaturesFlags } from './initialized-features'
|
|
15
12
|
import { globalScope, isBrowserScope } from '../../../common/util/global-scope'
|
|
16
13
|
import { drain } from '../../../common/drain/drain'
|
|
14
|
+
import { activateFeatures } from '../../../common/util/feature-flags'
|
|
15
|
+
import { warn } from '../../../common/util/console'
|
|
17
16
|
import { AggregateBase } from '../../utils/aggregate-base'
|
|
18
17
|
|
|
19
|
-
const jsonp = 'NREUM.setToken'
|
|
20
|
-
|
|
21
18
|
export class Aggregate extends AggregateBase {
|
|
22
19
|
static featureName = CONSTANTS.FEATURE_NAME
|
|
23
20
|
constructor (agentIdentifier, aggregator) {
|
|
@@ -55,10 +52,12 @@ export class Aggregate extends AggregateBase {
|
|
|
55
52
|
|
|
56
53
|
sendRum () {
|
|
57
54
|
const info = getInfo(this.agentIdentifier)
|
|
55
|
+
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
56
|
+
const harvester = new Harvest(this)
|
|
57
|
+
|
|
58
58
|
if (!info.beacon) return
|
|
59
59
|
if (info.queueTime) this.aggregator.store('measures', 'qt', { value: info.queueTime })
|
|
60
60
|
if (info.applicationTime) this.aggregator.store('measures', 'ap', { value: info.applicationTime })
|
|
61
|
-
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
62
61
|
|
|
63
62
|
// These 3 values should've been recorded after load and before this func runs. They are part of the minimum required for PageView events to be created.
|
|
64
63
|
// Following PR #428, which demands that all agents send RUM call, these need to be sent even outside of the main window context where PerformanceTiming
|
|
@@ -67,27 +66,27 @@ export class Aggregate extends AggregateBase {
|
|
|
67
66
|
this.aggregator.store('measures', 'fe', { value: isBrowserScope ? agentRuntime[CONSTANTS.FBTWL] : 0 })
|
|
68
67
|
this.aggregator.store('measures', 'dc', { value: isBrowserScope ? agentRuntime[CONSTANTS.FBTDC] : 0 })
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
const queryParameters = {
|
|
70
|
+
tt: info.ttGuid,
|
|
71
|
+
us: info.user,
|
|
72
|
+
ac: info.account,
|
|
73
|
+
pr: info.product,
|
|
74
|
+
af: getActivatedFeaturesFlags(this.agentIdentifier).join(','),
|
|
75
|
+
...(
|
|
76
|
+
Object.entries(this.aggregator.get('measures') || {}).reduce((aggregator, [metricName, measure]) => {
|
|
77
|
+
aggregator[metricName] = measure.params?.value
|
|
78
|
+
return aggregator
|
|
79
|
+
}, {})
|
|
80
|
+
),
|
|
81
|
+
xx: info.extra,
|
|
82
|
+
ua: info.userAttributes,
|
|
83
|
+
at: info.atts
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
chunksForQueryString.push(param('af', getActivatedFeaturesFlags(this.agentIdentifier).join(',')))
|
|
86
|
+
let body
|
|
87
|
+
if (typeof info.jsAttributes === 'object' && Object.keys(info.jsAttributes).length > 0) {
|
|
88
|
+
body = { ja: info.jsAttributes }
|
|
89
|
+
}
|
|
91
90
|
|
|
92
91
|
if (globalScope.performance) {
|
|
93
92
|
if (typeof PerformanceNavigationTiming !== 'undefined') { // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
|
|
@@ -96,13 +95,13 @@ export class Aggregate extends AggregateBase {
|
|
|
96
95
|
timing: addPT(agentRuntime.offset, navTimingEntry, {}),
|
|
97
96
|
navigation: addPN(navTimingEntry, {})
|
|
98
97
|
})
|
|
99
|
-
|
|
98
|
+
queryParameters.perf = stringify(perf)
|
|
100
99
|
} else if (typeof PerformanceTiming !== 'undefined') { // Safari pre-15 did not support level 2 timing
|
|
101
100
|
const perf = ({
|
|
102
101
|
timing: addPT(agentRuntime.offset, globalScope.performance.timing, {}, true),
|
|
103
102
|
navigation: addPN(globalScope.performance.navigation, {})
|
|
104
103
|
})
|
|
105
|
-
|
|
104
|
+
queryParameters.perf = stringify(perf)
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -112,35 +111,33 @@ export class Aggregate extends AggregateBase {
|
|
|
112
111
|
if (!entry.startTime || entry.startTime <= 0) return
|
|
113
112
|
|
|
114
113
|
if (entry.name === 'first-paint') {
|
|
115
|
-
|
|
114
|
+
queryParameters.fp = String(Math.floor(entry.startTime))
|
|
116
115
|
} else if (entry.name === 'first-contentful-paint') {
|
|
117
|
-
|
|
116
|
+
queryParameters.fcp = String(Math.floor(entry.startTime))
|
|
118
117
|
}
|
|
119
118
|
paintMetrics[entry.name] = Math.floor(entry.startTime) // this is consumed by Spa module
|
|
120
119
|
})
|
|
121
120
|
} catch (e) {}
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
agentRuntime.bytesSent[protocol] = 0 // Set to zero for now until RUM is moved to POST
|
|
134
|
-
|
|
135
|
-
// Capture query bytes sent to RUM call endpoint (currently `1`) as a supportability metric. See metrics aggregator (on unload).
|
|
136
|
-
agentRuntime.queryBytesSent[protocol] = (agentRuntime.queryBytesSent[protocol] || 0) + queryString?.length || 0
|
|
122
|
+
harvester.send({
|
|
123
|
+
endpoint: 'rum',
|
|
124
|
+
payload: { qs: queryParameters, body },
|
|
125
|
+
opts: { needResponse: true, sendEmptyBody: true },
|
|
126
|
+
cbFinished: ({ status, responseText }) => {
|
|
127
|
+
if (status >= 400) {
|
|
128
|
+
// Adding retry logic for the rum call will be a separate change
|
|
129
|
+
this.ee.abort()
|
|
130
|
+
return
|
|
131
|
+
}
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
try {
|
|
134
|
+
activateFeatures(JSON.parse(responseText), this.agentIdentifier)
|
|
135
|
+
drain(this.agentIdentifier, this.featureName)
|
|
136
|
+
} catch (err) {
|
|
137
|
+
this.ee.abort()
|
|
138
|
+
warn('RUM call failed. Agent shutting down.')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
141
|
})
|
|
142
|
-
// Usually `drain` is invoked automatically after processing feature flags contained in the JSONP callback from
|
|
143
|
-
// ingest (see `activateFeatures`), so when JSONP cannot execute (as with module workers), we drain manually.
|
|
144
|
-
if (!isValidJsonp) drain(this.agentIdentifier, CONSTANTS.FEATURE_NAME)
|
|
145
142
|
}
|
|
146
143
|
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { Aggregate as SessionReplayAgg, AVG_COMPRESSION, MAX_PAYLOAD_SIZE, IDEAL_PAYLOAD_SIZE } from '.'
|
|
2
|
+
import { Aggregator } from '../../../common/aggregate/aggregator'
|
|
3
|
+
import { SESSION_EVENTS, SessionEntity, MODE } from '../../../common/session/session-entity'
|
|
4
|
+
import { setConfiguration } from '../../../common/config/config'
|
|
5
|
+
import { configure } from '../../../loaders/configure/configure'
|
|
6
|
+
|
|
7
|
+
class LocalMemory {
|
|
8
|
+
constructor (initialState = {}) {
|
|
9
|
+
this.state = initialState
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get (key) {
|
|
13
|
+
try {
|
|
14
|
+
return this.state[key]
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return ''
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
set (key, value) {
|
|
21
|
+
try {
|
|
22
|
+
if (value === undefined || value === null) return this.remove(key)
|
|
23
|
+
this.state[key] = value
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
remove (key) {
|
|
30
|
+
try {
|
|
31
|
+
delete this.state[key]
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let sr, session
|
|
39
|
+
const agentIdentifier = 'abcd'
|
|
40
|
+
const info = { licenseKey: 1234, applicationID: 9876 }
|
|
41
|
+
const init = { session_replay: { enabled: true, sampleRate: 1, errorSampleRate: 0 } }
|
|
42
|
+
|
|
43
|
+
describe('Session Replay', () => {
|
|
44
|
+
beforeEach(async () => {
|
|
45
|
+
primeSessionAndReplay()
|
|
46
|
+
})
|
|
47
|
+
afterEach(async () => {
|
|
48
|
+
sr.abort()
|
|
49
|
+
jest.clearAllMocks()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('Session Replay Session Behavior', () => {
|
|
53
|
+
test('When Session Ends', async () => {
|
|
54
|
+
const xhrMockClass = () => ({
|
|
55
|
+
open: jest.fn(),
|
|
56
|
+
send: jest.fn(),
|
|
57
|
+
setRequestHeader: jest.fn(),
|
|
58
|
+
addEventListener: jest.fn()
|
|
59
|
+
})
|
|
60
|
+
global.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
|
|
61
|
+
|
|
62
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
63
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
64
|
+
await wait(1)
|
|
65
|
+
expect(sr.initialized).toBeTruthy()
|
|
66
|
+
expect(sr.recording).toBeTruthy()
|
|
67
|
+
sr.ee.emit(SESSION_EVENTS.RESET)
|
|
68
|
+
expect(global.XMLHttpRequest).toHaveBeenCalled()
|
|
69
|
+
expect(sr.recording).toBeFalsy()
|
|
70
|
+
expect(sr.blocked).toBeTruthy()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('When Session Is Paused/Resumed', async () => {
|
|
74
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
75
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
76
|
+
await wait(1)
|
|
77
|
+
expect(sr.initialized).toBeTruthy()
|
|
78
|
+
expect(sr.recording).toBeTruthy()
|
|
79
|
+
sr.ee.emit(SESSION_EVENTS.PAUSE)
|
|
80
|
+
expect(sr.recording).toBeFalsy()
|
|
81
|
+
sr.ee.emit(SESSION_EVENTS.RESUME)
|
|
82
|
+
expect(sr.recording).toBeTruthy()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('Session SR mode matches SR mode -- FULL', async () => {
|
|
86
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
87
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
88
|
+
await wait(1)
|
|
89
|
+
expect(session.state.sessionReplay).toEqual(sr.mode)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('Session SR mode matches SR mode -- ERROR', async () => {
|
|
93
|
+
setConfiguration(agentIdentifier, { session_replay: { sampleRate: 0, errorSampleRate: 1 } })
|
|
94
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
95
|
+
await wait(1)
|
|
96
|
+
expect(session.state.sessionReplay).toEqual(sr.mode)
|
|
97
|
+
return
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('Session SR mode matches SR mode -- OFF', async () => {
|
|
101
|
+
setConfiguration(agentIdentifier, { session_replay: { sampleRate: 0, errorSampleRate: 0 } })
|
|
102
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
103
|
+
await wait(1)
|
|
104
|
+
expect(session.state.sessionReplay).toEqual(sr.mode)
|
|
105
|
+
return
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('Session Replay Initialization Behavior', () => {
|
|
110
|
+
test('Waits for SR', async () => {
|
|
111
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
112
|
+
// do not emit sr flag
|
|
113
|
+
await wait(1000)
|
|
114
|
+
expect(sr.initialized).toEqual(false)
|
|
115
|
+
expect(sr.recording).toEqual(false)
|
|
116
|
+
|
|
117
|
+
// emit a false flag
|
|
118
|
+
sr.ee.emit('rumresp-sr', [false])
|
|
119
|
+
await wait(1)
|
|
120
|
+
expect(sr.initialized).toEqual(true)
|
|
121
|
+
expect(sr.recording).toEqual(false)
|
|
122
|
+
return
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('Does not run if cookies_enabled is false', async () => {
|
|
126
|
+
setConfiguration(agentIdentifier, { ...init, privacy: { cookies_enabled: false } })
|
|
127
|
+
sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
|
|
128
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
129
|
+
await wait(1)
|
|
130
|
+
expect(sr.initialized).toEqual(false)
|
|
131
|
+
expect(sr.recording).toEqual(false)
|
|
132
|
+
return
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('Does not run if session_trace is disabled', async () => {
|
|
136
|
+
setConfiguration(agentIdentifier, { ...init, session_trace: { enabled: false } })
|
|
137
|
+
sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
|
|
138
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
139
|
+
await wait(1)
|
|
140
|
+
expect(sr.initialized).toEqual(false)
|
|
141
|
+
expect(sr.recording).toEqual(false)
|
|
142
|
+
return
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('Session Replay Sample -> Mode Behaviors', () => {
|
|
147
|
+
test('New Session -- Full 1 Error 1 === FULL', async () => {
|
|
148
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 1 } })
|
|
149
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
150
|
+
await wait(1)
|
|
151
|
+
expect(sr.mode).toEqual(MODE.FULL)
|
|
152
|
+
return
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('New Session -- Full 1 Error 0 === FULL', async () => {
|
|
156
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 0, sampleRate: 1 } })
|
|
157
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
158
|
+
await wait(1)
|
|
159
|
+
expect(sr.mode).toEqual(MODE.FULL)
|
|
160
|
+
return
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('New Session -- Full 0 Error 1 === ERROR', async () => {
|
|
164
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
|
|
165
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
166
|
+
await wait(1)
|
|
167
|
+
expect(sr.mode).toEqual(MODE.ERROR)
|
|
168
|
+
return
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('New Session -- Full 0 Error 0 === OFF', async () => {
|
|
172
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 0, sampleRate: 0 } })
|
|
173
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
174
|
+
await wait(1)
|
|
175
|
+
expect(sr.mode).toEqual(MODE.OFF)
|
|
176
|
+
return
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('Existing Session -- Should inherit mode from session entity and ignore samples', async () => {
|
|
180
|
+
const storage = new LocalMemory({ NRBA_SESSION: { value: 'abcdefghijklmnop', expiresAt: Date.now() + 10000, inactiveAt: Date.now() + 10000, updatedAt: Date.now(), sessionReplay: MODE.FULL, sessionTraceMode: MODE.FULL, custom: {} } })
|
|
181
|
+
session = new SessionEntity({ agentIdentifier, key: 'SESSION', storage })
|
|
182
|
+
expect(session.isNew).toBeFalsy()
|
|
183
|
+
primeSessionAndReplay(session)
|
|
184
|
+
// configure to get "error" sample ---> but should inherit FULL from session manager
|
|
185
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
|
|
186
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
187
|
+
await wait(1)
|
|
188
|
+
expect(sr.mode).toEqual(MODE.FULL)
|
|
189
|
+
return
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('Session Replay Error Mode Behaviors', () => {
|
|
194
|
+
test('An error BEFORE rrweb import starts running in FULL from beginning', async () => {
|
|
195
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
|
|
196
|
+
sr.ee.emit('errorAgg')
|
|
197
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
198
|
+
await wait(1)
|
|
199
|
+
expect(sr.mode).toEqual(MODE.FULL)
|
|
200
|
+
expect(sr.scheduler.started).toEqual(true)
|
|
201
|
+
return
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('An error AFTER rrweb import changes mode and starts harvester', async () => {
|
|
205
|
+
setConfiguration(agentIdentifier, { session_replay: { errorSampleRate: 1, sampleRate: 0 } })
|
|
206
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
207
|
+
await wait(1)
|
|
208
|
+
expect(sr.mode).toEqual(MODE.ERROR)
|
|
209
|
+
expect(sr.scheduler.started).toEqual(false)
|
|
210
|
+
sr.ee.emit('errorAgg')
|
|
211
|
+
expect(sr.mode).toEqual(MODE.FULL)
|
|
212
|
+
expect(sr.scheduler.started).toEqual(true)
|
|
213
|
+
return
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('Session Replay Payload Validation', () => {
|
|
218
|
+
test('Payload', async () => {
|
|
219
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
220
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
221
|
+
await wait(1)
|
|
222
|
+
const harvestContents = sr.getHarvestContents()
|
|
223
|
+
// query attrs
|
|
224
|
+
expect(harvestContents.qs).toMatchObject({
|
|
225
|
+
protocol_version: '0',
|
|
226
|
+
content_encoding: 'gzip',
|
|
227
|
+
browser_monitoring_key: info.licenseKey
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
expect(harvestContents.body).toMatchObject({
|
|
231
|
+
type: 'SessionReplay',
|
|
232
|
+
appId: info.applicationID,
|
|
233
|
+
timestamp: expect.any(Number),
|
|
234
|
+
blob: expect.any(String),
|
|
235
|
+
attributes: {
|
|
236
|
+
session: session.state.value,
|
|
237
|
+
hasSnapshot: expect.any(Boolean),
|
|
238
|
+
hasError: expect.any(Boolean),
|
|
239
|
+
agentVersion: expect.any(String),
|
|
240
|
+
isFirstChunk: expect.any(Boolean),
|
|
241
|
+
'nr.rrweb.version': expect.any(String)
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
expect(JSON.parse(harvestContents.body.blob).length).toBeGreaterThan(0)
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
describe('Session Replay Harvest Behaviors', () => {
|
|
250
|
+
test('Compressed payload is provided to harvester', async () => {
|
|
251
|
+
const { gunzipSync, strFromU8 } = await import('fflate')
|
|
252
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
253
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
254
|
+
await wait(1)
|
|
255
|
+
const [harvestContents] = sr.prepareHarvest()
|
|
256
|
+
expect(harvestContents.qs).toMatchObject({
|
|
257
|
+
protocol_version: '0',
|
|
258
|
+
content_encoding: 'gzip',
|
|
259
|
+
browser_monitoring_key: info.licenseKey
|
|
260
|
+
})
|
|
261
|
+
expect(harvestContents.body).toEqual(expect.any(Uint8Array))
|
|
262
|
+
expect(JSON.parse(strFromU8(gunzipSync(harvestContents.body)))).toMatchObject({
|
|
263
|
+
type: 'SessionReplay',
|
|
264
|
+
appId: info.applicationID,
|
|
265
|
+
timestamp: expect.any(Number),
|
|
266
|
+
blob: expect.any(String),
|
|
267
|
+
attributes: {
|
|
268
|
+
session: session.state.value,
|
|
269
|
+
hasSnapshot: expect.any(Boolean),
|
|
270
|
+
hasError: expect.any(Boolean),
|
|
271
|
+
agentVersion: expect.any(String),
|
|
272
|
+
isFirstChunk: expect.any(Boolean),
|
|
273
|
+
'nr.rrweb.version': expect.any(String)
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('Uncompressed payload is provided to harvester', async () => {
|
|
279
|
+
jest.doMock('fflate', () => ({
|
|
280
|
+
__esModule: true,
|
|
281
|
+
gzipSync: jest.fn().mockImplementation(() => { throw new Error() })
|
|
282
|
+
}))
|
|
283
|
+
|
|
284
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
285
|
+
sr.shouldCompress = false
|
|
286
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
287
|
+
await wait(1)
|
|
288
|
+
|
|
289
|
+
const [harvestContents] = sr.prepareHarvest()
|
|
290
|
+
expect(harvestContents.qs).toMatchObject({
|
|
291
|
+
protocol_version: '0',
|
|
292
|
+
// content_encoding is omitted when the payload is not compressed
|
|
293
|
+
browser_monitoring_key: info.licenseKey
|
|
294
|
+
})
|
|
295
|
+
expect(harvestContents.qs.content_encoding).toBeUndefined()
|
|
296
|
+
expect(harvestContents.body).toMatchObject({
|
|
297
|
+
type: 'SessionReplay',
|
|
298
|
+
appId: info.applicationID,
|
|
299
|
+
timestamp: expect.any(Number),
|
|
300
|
+
blob: expect.any(String),
|
|
301
|
+
attributes: {
|
|
302
|
+
session: session.state.value,
|
|
303
|
+
hasSnapshot: expect.any(Boolean),
|
|
304
|
+
hasError: expect.any(Boolean),
|
|
305
|
+
agentVersion: expect.any(String),
|
|
306
|
+
isFirstChunk: expect.any(Boolean),
|
|
307
|
+
'nr.rrweb.version': expect.any(String)
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('Clears the event buffer when staged for harvesting', async () => {
|
|
313
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
314
|
+
sr.shouldCompress = false
|
|
315
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
316
|
+
await wait(1)
|
|
317
|
+
|
|
318
|
+
sr.prepareHarvest()
|
|
319
|
+
expect(sr.events.length).toEqual(0)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('Harvests early if exceeds limit', async () => {
|
|
323
|
+
let after = 0
|
|
324
|
+
const spy = jest.spyOn(sr.scheduler, 'runHarvest').mockImplementation(() => { after = Date.now() })
|
|
325
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
326
|
+
sr.payloadBytesEstimation = IDEAL_PAYLOAD_SIZE / AVG_COMPRESSION
|
|
327
|
+
const before = Date.now()
|
|
328
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
329
|
+
await wait(1)
|
|
330
|
+
expect(spy).toHaveBeenCalled()
|
|
331
|
+
expect(after - before).toBeLessThan(sr.harvestTimeSeconds * 1000)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test('Aborts if exceeds total limit', async () => {
|
|
335
|
+
const spy = jest.spyOn(sr.scheduler, 'runHarvest')
|
|
336
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
337
|
+
sr.payloadBytesEstimation = (MAX_PAYLOAD_SIZE + 1) / AVG_COMPRESSION
|
|
338
|
+
const before = Date.now()
|
|
339
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
340
|
+
await wait(1)
|
|
341
|
+
expect(spy).not.toHaveBeenCalled()
|
|
342
|
+
expect(sr.blocked).toEqual(true)
|
|
343
|
+
expect(sr.mode).toEqual(MODE.OFF)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
test('Aborts if 429 response', async () => {
|
|
347
|
+
setConfiguration(agentIdentifier, { ...init })
|
|
348
|
+
sr.ee.emit('rumresp-sr', [true])
|
|
349
|
+
await wait(1)
|
|
350
|
+
expect(sr.mode).toEqual(MODE.FULL)
|
|
351
|
+
sr.onHarvestFinished({ status: 429 })
|
|
352
|
+
expect(sr.blocked).toEqual(true)
|
|
353
|
+
expect(sr.mode).toEqual(MODE.OFF)
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
function wait (ms = 0) {
|
|
359
|
+
return new Promise((resolve) => {
|
|
360
|
+
setTimeout(resolve, ms)
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function primeSessionAndReplay (sess = new SessionEntity({ agentIdentifier, key: 'SESSION', storage: new LocalMemory() })) {
|
|
365
|
+
session = sess
|
|
366
|
+
configure(agentIdentifier, { info, runtime: { session } }, 'test', true)
|
|
367
|
+
sr = new SessionReplayAgg(agentIdentifier, new Aggregator({}))
|
|
368
|
+
}
|