@newrelic/browser-agent 1.232.0 → 1.233.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/dist/cjs/cdn/polyfills.js +5 -2
- package/dist/cjs/common/config/state/configurable.js +15 -26
- package/dist/cjs/common/config/state/info.js +1 -1
- package/dist/cjs/common/config/state/init.js +101 -56
- package/dist/cjs/common/config/state/loader-config.js +1 -1
- package/dist/cjs/common/config/state/runtime.js +1 -5
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/drain/drain.js +1 -1
- package/dist/cjs/common/harvest/harvest-scheduler.js +30 -10
- package/dist/cjs/common/harvest/harvest.js +119 -55
- package/dist/cjs/common/session/session-entity.js +35 -22
- package/dist/cjs/common/session/session-entity.test.js +73 -49
- package/dist/cjs/common/timer/interaction-timer.js +9 -12
- package/dist/cjs/common/url/canonicalize-url.js +32 -0
- package/dist/cjs/common/url/canonicalize-url.test.js +42 -0
- package/dist/cjs/common/url/clean-url.js +10 -3
- package/dist/cjs/common/url/protocol.test.js +0 -1
- package/dist/cjs/common/util/feature-flags.js +2 -1
- package/dist/cjs/common/util/global-scope.js +4 -2
- package/dist/cjs/common/util/submit-data.js +57 -18
- package/dist/cjs/common/wrap/wrap-fetch.js +1 -3
- package/dist/cjs/common/wrap/wrap-function.js +1 -3
- package/dist/cjs/common/wrap/wrap-promise.js +1 -1
- package/dist/cjs/features/ajax/aggregate/index.js +2 -2
- package/dist/cjs/features/ajax/instrument/index.js +1 -1
- package/dist/cjs/features/jserrors/aggregate/canonical-function-name.js +12 -4
- package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.js +93 -10
- package/dist/cjs/features/jserrors/aggregate/compute-stack-trace.test.js +164 -38
- package/dist/cjs/features/jserrors/aggregate/index.js +29 -48
- package/dist/cjs/features/jserrors/instrument/index.js +0 -2
- package/dist/cjs/features/metrics/aggregate/framework-detection.js +67 -0
- package/dist/cjs/features/metrics/aggregate/framework-detection.test.js +137 -0
- package/dist/cjs/features/metrics/aggregate/index.js +7 -3
- package/dist/cjs/features/metrics/aggregate/polyfill-detection.es5.js +14 -0
- package/dist/cjs/features/metrics/aggregate/polyfill-detection.es5.test.js +17 -0
- package/dist/cjs/features/metrics/aggregate/polyfill-detection.js +53 -0
- package/dist/cjs/features/metrics/aggregate/polyfill-detection.test.js +165 -0
- package/dist/cjs/features/page_action/aggregate/index.js +2 -2
- package/dist/cjs/features/page_view_event/aggregate/index.js +6 -3
- package/dist/cjs/features/page_view_timing/aggregate/index.js +2 -2
- package/dist/cjs/features/session_replay/aggregate/index.js +333 -0
- package/dist/cjs/features/session_replay/constants.js +9 -0
- package/dist/cjs/features/session_replay/index.js +12 -0
- package/dist/cjs/features/session_replay/instrument/index.js +29 -0
- package/dist/cjs/features/session_trace/aggregate/index.js +163 -164
- package/dist/cjs/features/session_trace/constants.js +2 -9
- package/dist/cjs/features/session_trace/instrument/index.js +24 -66
- package/dist/cjs/features/spa/aggregate/index.js +2 -2
- package/dist/cjs/features/utils/agent-session.js +1 -2
- package/dist/cjs/features/utils/aggregate-base.js +64 -0
- package/dist/cjs/features/utils/feature-base.js +0 -31
- package/dist/cjs/features/utils/handler-cache.js +3 -4
- package/dist/cjs/features/utils/instrument-base.js +42 -10
- package/dist/cjs/features/utils/{lazy-loader.js → lazy-feature-loader.js} +4 -2
- package/dist/cjs/loaders/agent.js +1 -1
- package/dist/cjs/loaders/api/apiAsync.js +3 -1
- package/dist/cjs/loaders/configure/configure.js +3 -3
- package/dist/cjs/loaders/features/featureDependencies.js +0 -12
- package/dist/cjs/loaders/features/features.js +3 -1
- package/dist/cjs/loaders/micro-agent.js +6 -6
- package/dist/esm/cdn/polyfills.js +5 -2
- package/dist/esm/common/config/state/configurable.js +14 -24
- package/dist/esm/common/config/state/info.js +2 -2
- package/dist/esm/common/config/state/init.js +102 -57
- package/dist/esm/common/config/state/loader-config.js +2 -2
- package/dist/esm/common/config/state/runtime.js +2 -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/drain/drain.js +1 -1
- package/dist/esm/common/harvest/harvest-scheduler.js +30 -10
- package/dist/esm/common/harvest/harvest.js +121 -56
- package/dist/esm/common/session/session-entity.js +35 -22
- package/dist/esm/common/session/session-entity.test.js +73 -49
- package/dist/esm/common/timer/interaction-timer.js +9 -12
- package/dist/esm/common/url/canonicalize-url.js +27 -0
- package/dist/esm/common/url/canonicalize-url.test.js +38 -0
- package/dist/esm/common/url/clean-url.js +10 -3
- package/dist/esm/common/url/protocol.test.js +0 -1
- package/dist/esm/common/util/feature-flags.js +2 -1
- package/dist/esm/common/util/global-scope.js +1 -0
- package/dist/esm/common/util/submit-data.js +57 -18
- package/dist/esm/common/wrap/wrap-fetch.js +1 -2
- package/dist/esm/common/wrap/wrap-function.js +1 -2
- package/dist/esm/common/wrap/wrap-promise.js +1 -1
- package/dist/esm/features/ajax/aggregate/index.js +2 -2
- package/dist/esm/features/ajax/instrument/index.js +1 -1
- package/dist/esm/features/jserrors/aggregate/canonical-function-name.js +12 -4
- package/dist/esm/features/jserrors/aggregate/compute-stack-trace.js +93 -10
- package/dist/esm/features/jserrors/aggregate/compute-stack-trace.test.js +149 -25
- package/dist/esm/features/jserrors/aggregate/index.js +30 -48
- package/dist/esm/features/jserrors/instrument/index.js +0 -1
- package/dist/esm/features/metrics/aggregate/framework-detection.js +61 -0
- package/dist/esm/features/metrics/aggregate/framework-detection.test.js +133 -0
- package/dist/esm/features/metrics/aggregate/index.js +7 -3
- package/dist/esm/features/metrics/aggregate/polyfill-detection.es5.js +8 -0
- package/dist/esm/features/metrics/aggregate/polyfill-detection.es5.test.js +15 -0
- package/dist/esm/features/metrics/aggregate/polyfill-detection.js +47 -0
- package/dist/esm/features/metrics/aggregate/polyfill-detection.test.js +163 -0
- package/dist/esm/features/page_action/aggregate/index.js +2 -2
- package/dist/esm/features/page_view_event/aggregate/index.js +6 -3
- package/dist/esm/features/page_view_timing/aggregate/index.js +2 -2
- package/dist/esm/features/session_replay/aggregate/index.js +327 -0
- package/dist/esm/features/session_replay/constants.js +2 -0
- package/dist/esm/features/session_replay/index.js +12 -0
- package/dist/esm/features/session_replay/instrument/index.js +21 -0
- package/dist/esm/features/session_trace/aggregate/index.js +163 -163
- package/dist/esm/features/session_trace/constants.js +1 -5
- package/dist/esm/features/session_trace/instrument/index.js +24 -66
- package/dist/esm/features/spa/aggregate/index.js +2 -2
- package/dist/esm/features/utils/agent-session.js +1 -2
- package/dist/esm/features/utils/aggregate-base.js +57 -0
- package/dist/esm/features/utils/feature-base.js +1 -32
- package/dist/esm/features/utils/handler-cache.js +3 -4
- package/dist/esm/features/utils/instrument-base.js +42 -10
- package/dist/esm/features/utils/{lazy-loader.js → lazy-feature-loader.js} +3 -1
- package/dist/esm/loaders/agent.js +1 -1
- package/dist/esm/loaders/api/apiAsync.js +3 -1
- package/dist/esm/loaders/configure/configure.js +3 -3
- package/dist/esm/loaders/features/featureDependencies.js +0 -11
- package/dist/esm/loaders/features/features.js +3 -1
- package/dist/esm/loaders/micro-agent.js +6 -6
- package/dist/types/common/config/state/configurable.d.ts +1 -3
- package/dist/types/common/config/state/configurable.d.ts.map +1 -1
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/config/state/runtime.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts +37 -34
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts +6 -3
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/timer/interaction-timer.d.ts +2 -1
- package/dist/types/common/timer/interaction-timer.d.ts.map +1 -1
- package/dist/types/common/url/canonicalize-url.d.ts +9 -0
- package/dist/types/common/url/canonicalize-url.d.ts.map +1 -0
- package/dist/types/common/url/clean-url.d.ts +7 -1
- package/dist/types/common/url/clean-url.d.ts.map +1 -1
- package/dist/types/common/util/feature-flags.d.ts.map +1 -1
- package/dist/types/common/util/global-scope.d.ts +1 -0
- package/dist/types/common/util/global-scope.d.ts.map +1 -1
- package/dist/types/common/util/submit-data.d.ts +40 -14
- package/dist/types/common/util/submit-data.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-fetch.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts +2 -2
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/canonical-function-name.d.ts +8 -1
- package/dist/types/features/jserrors/aggregate/canonical-function-name.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts +48 -19
- package/dist/types/features/jserrors/aggregate/compute-stack-trace.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +14 -5
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/framework-detection.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts +2 -2
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/polyfill-detection.d.ts +6 -0
- package/dist/types/features/metrics/aggregate/polyfill-detection.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts +7 -0
- package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -0
- package/dist/types/features/page_action/aggregate/index.d.ts +3 -3
- package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -2
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -2
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +96 -0
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -0
- package/dist/types/features/session_replay/constants.d.ts +2 -0
- package/dist/types/features/session_replay/constants.d.ts.map +1 -0
- package/dist/types/features/session_replay/index.d.ts +2 -0
- package/dist/types/features/session_replay/index.d.ts.map +1 -0
- package/dist/types/features/session_replay/instrument/index.d.ts +6 -0
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -0
- package/dist/types/features/session_trace/aggregate/index.d.ts +8 -57
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/constants.d.ts +0 -3
- package/dist/types/features/session_trace/constants.d.ts.map +1 -1
- package/dist/types/features/session_trace/instrument/index.d.ts +1 -3
- package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +2 -2
- package/dist/types/features/spa/aggregate/index.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 +11 -0
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -0
- package/dist/types/features/utils/feature-base.d.ts +0 -5
- package/dist/types/features/utils/feature-base.d.ts.map +1 -1
- package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts +3 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/features/utils/{lazy-loader.d.ts → lazy-feature-loader.d.ts} +2 -2
- package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -0
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/dist/types/loaders/features/featureDependencies.d.ts +0 -1
- package/dist/types/loaders/features/featureDependencies.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +1 -0
- package/dist/types/loaders/features/features.d.ts.map +1 -1
- package/package.json +31 -22
- package/src/cdn/polyfills.js +4 -1
- package/src/common/config/state/configurable.js +18 -24
- package/src/common/config/state/info.js +2 -2
- package/src/common/config/state/init.js +62 -28
- package/src/common/config/state/loader-config.js +2 -2
- package/src/common/config/state/runtime.js +2 -4
- package/src/common/drain/drain.js +1 -1
- package/src/common/harvest/harvest-scheduler.js +35 -10
- package/src/common/harvest/harvest.js +73 -50
- package/src/common/session/session-entity.js +34 -23
- package/src/common/session/session-entity.test.js +57 -51
- package/src/common/timer/interaction-timer.js +9 -12
- package/src/common/url/canonicalize-url.js +28 -0
- package/src/common/url/canonicalize-url.test.js +34 -0
- package/src/common/url/clean-url.js +10 -3
- package/src/common/url/protocol.test.js +0 -1
- package/src/common/util/feature-flags.js +2 -2
- package/src/common/util/global-scope.js +2 -0
- package/src/common/util/submit-data.js +28 -17
- package/src/common/wrap/wrap-fetch.js +1 -2
- package/src/common/wrap/wrap-function.js +1 -2
- package/src/common/wrap/wrap-promise.js +1 -1
- package/src/features/ajax/aggregate/index.js +2 -2
- package/src/features/ajax/instrument/index.js +1 -1
- package/src/features/jserrors/aggregate/canonical-function-name.js +12 -4
- package/src/features/jserrors/aggregate/compute-stack-trace.js +85 -11
- package/src/features/jserrors/aggregate/compute-stack-trace.test.js +141 -24
- package/src/features/jserrors/aggregate/index.js +28 -52
- package/src/features/jserrors/instrument/index.js +0 -1
- package/src/features/metrics/aggregate/framework-detection.js +73 -0
- package/src/features/metrics/aggregate/framework-detection.test.js +201 -0
- package/src/features/metrics/aggregate/index.js +8 -3
- package/src/features/metrics/aggregate/polyfill-detection.es5.js +9 -0
- package/src/features/metrics/aggregate/polyfill-detection.es5.test.js +16 -0
- package/src/features/metrics/aggregate/polyfill-detection.js +48 -0
- package/src/features/metrics/aggregate/polyfill-detection.test.js +163 -0
- package/src/features/page_action/aggregate/index.js +2 -2
- package/src/features/page_view_event/aggregate/index.js +5 -5
- package/src/features/page_view_timing/aggregate/index.js +2 -2
- package/src/features/session_replay/aggregate/index.js +314 -0
- package/src/features/session_replay/constants.js +3 -0
- package/src/features/session_replay/index.js +12 -0
- package/src/features/session_replay/instrument/index.js +22 -0
- package/src/features/session_trace/aggregate/index.js +148 -188
- package/src/features/session_trace/constants.js +0 -4
- package/src/features/session_trace/instrument/index.js +17 -69
- package/src/features/spa/aggregate/index.js +2 -2
- package/src/features/utils/agent-session.js +1 -2
- package/src/features/utils/aggregate-base.js +51 -0
- package/src/features/utils/feature-base.js +1 -31
- package/src/features/utils/handler-cache.js +3 -4
- package/src/features/utils/instrument-base.js +40 -8
- package/src/features/utils/{lazy-loader.js → lazy-feature-loader.js} +3 -1
- package/src/loaders/agent.js +1 -1
- package/src/loaders/api/apiAsync.js +1 -1
- package/src/loaders/configure/configure.js +4 -3
- package/src/loaders/features/featureDependencies.js +0 -12
- package/src/loaders/features/features.js +3 -1
- package/src/loaders/micro-agent.js +4 -4
- package/dist/cjs/common/metrics/framework-detection.js +0 -72
- package/dist/cjs/common/util/user-agent.js +0 -57
- package/dist/cjs/common/window/supports-performance-observer.js +0 -15
- package/dist/esm/common/metrics/framework-detection.js +0 -66
- package/dist/esm/common/util/user-agent.js +0 -48
- package/dist/esm/common/window/supports-performance-observer.js +0 -9
- package/dist/types/common/metrics/framework-detection.d.ts.map +0 -1
- package/dist/types/common/util/user-agent.d.ts +0 -5
- package/dist/types/common/util/user-agent.d.ts.map +0 -1
- package/dist/types/common/window/supports-performance-observer.d.ts +0 -2
- package/dist/types/common/window/supports-performance-observer.d.ts.map +0 -1
- package/dist/types/features/utils/lazy-loader.d.ts.map +0 -1
- package/src/common/metrics/framework-detection.js +0 -71
- package/src/common/util/user-agent.js +0 -56
- package/src/common/window/supports-performance-observer.js +0 -10
- /package/dist/types/{common/metrics → features/metrics/aggregate}/framework-detection.d.ts +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @file Records, aggregates, and harvests session replay data.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
|
|
9
|
+
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
|
|
10
|
+
* functionality is validated and a full user experience is curated.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { drain } from '../../../common/drain/drain'
|
|
14
|
+
import { registerHandler } from '../../../common/event-emitter/register-handler'
|
|
15
|
+
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
|
|
16
|
+
import { FEATURE_NAME } from '../constants'
|
|
17
|
+
import { stringify } from '../../../common/util/stringify'
|
|
18
|
+
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
|
|
19
|
+
import { SESSION_EVENTS } from '../../../common/session/session-entity'
|
|
20
|
+
import { AggregateBase } from '../../utils/aggregate-base'
|
|
21
|
+
|
|
22
|
+
// would be better to get this dynamically in some way
|
|
23
|
+
export const RRWEB_VERSION = '2.0.0-alpha.8'
|
|
24
|
+
|
|
25
|
+
let recorder, gzipper, u8
|
|
26
|
+
|
|
27
|
+
/** The "mode" with which the session replay is recording */
|
|
28
|
+
const MODE = {
|
|
29
|
+
OFF: 0,
|
|
30
|
+
FULL: 1,
|
|
31
|
+
ERROR: 2
|
|
32
|
+
}
|
|
33
|
+
/** Vortex caps payload sizes at 1MB */
|
|
34
|
+
const MAX_PAYLOAD_SIZE = 1000000
|
|
35
|
+
/** Unloading caps around 64kb */
|
|
36
|
+
const IDEAL_PAYLOAD_SIZE = 64000
|
|
37
|
+
/** Interval between forcing new full snapshots in "error" mode */
|
|
38
|
+
const CHECKOUT_MS = 30000
|
|
39
|
+
|
|
40
|
+
export class Aggregate extends AggregateBase {
|
|
41
|
+
static featureName = FEATURE_NAME
|
|
42
|
+
constructor (agentIdentifier, aggregator) {
|
|
43
|
+
super(agentIdentifier, aggregator, FEATURE_NAME)
|
|
44
|
+
|
|
45
|
+
/** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
|
|
46
|
+
this.events = []
|
|
47
|
+
/** The interval to harvest at. This gets overridden if the size of the payload exceeds certain thresholds */
|
|
48
|
+
this.harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'session_replay.harvestTimeSeconds') || 60
|
|
49
|
+
/** Set once the recorder has fully initialized after flag checks and sampling */
|
|
50
|
+
this.initialized = false
|
|
51
|
+
/** Set once an error has been detected on the page. */
|
|
52
|
+
this.errorNoticed = false
|
|
53
|
+
/** The "mode" to record in. Defaults to "OFF" until flags and sampling are checked. See "MODE" constant. */
|
|
54
|
+
this.mode = MODE.OFF
|
|
55
|
+
/** Set once the feature has been "aborted" to prevent other side-effects from continuing */
|
|
56
|
+
this.blocked = false
|
|
57
|
+
|
|
58
|
+
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
59
|
+
this.isFirstChunk = false
|
|
60
|
+
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
61
|
+
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
62
|
+
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
63
|
+
*/
|
|
64
|
+
this.hasSnapshot = false
|
|
65
|
+
/** Payload metadata -- Should indicate that the payload being sent contains an error. Used for query/filter purposes in UI */
|
|
66
|
+
this.hasError = false
|
|
67
|
+
|
|
68
|
+
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
69
|
+
this.payloadBytesEstimation = 0
|
|
70
|
+
|
|
71
|
+
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
72
|
+
this.stopRecording = () => { /* no-op until set by rrweb initializer */ }
|
|
73
|
+
|
|
74
|
+
// 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.
|
|
75
|
+
this.ee.on('session-reset', () => {
|
|
76
|
+
this.abort()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
80
|
+
this.ee.on(SESSION_EVENTS.PAUSE, () => { this.stopRecording() })
|
|
81
|
+
// The SessionEntity class can emit a message indicating the session was resumed (visibility change). This feature must start running again (if already running) if that occurs.
|
|
82
|
+
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
83
|
+
if (!this.initialized || this.mode === MODE.OFF) return
|
|
84
|
+
this.startRecording()
|
|
85
|
+
this.takeFullSnapshot()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
|
|
89
|
+
this.scheduler = new HarvestScheduler('blob', {
|
|
90
|
+
onFinished: this.onHarvestFinished.bind(this),
|
|
91
|
+
retryDelay: this.harvestTimeSeconds,
|
|
92
|
+
getPayload: this.prepareHarvest.bind(this),
|
|
93
|
+
// TODO -- this stuff needs a better way to be handled
|
|
94
|
+
includeBaseParams: false,
|
|
95
|
+
customUrl: 'https://vortex-alb.stg-single-tooth.cell.us.nr-data.net/blob',
|
|
96
|
+
raw: true,
|
|
97
|
+
gzip: true
|
|
98
|
+
}, this)
|
|
99
|
+
|
|
100
|
+
// Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
|
|
101
|
+
// This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
|
|
102
|
+
registerHandler('errorAgg', (e) => {
|
|
103
|
+
this.hasError = true
|
|
104
|
+
// run once
|
|
105
|
+
if (this.mode === MODE.ERROR) {
|
|
106
|
+
this.mode = MODE.FULL
|
|
107
|
+
// if the error was noticed AFTER the recorder was already imported....
|
|
108
|
+
if (recorder && this.initialized) {
|
|
109
|
+
this.stopRecording()
|
|
110
|
+
this.startRecording()
|
|
111
|
+
this.scheduler.startTimer(this.harvestTimeSeconds)
|
|
112
|
+
|
|
113
|
+
const { session } = getRuntime(this.agentIdentifier)
|
|
114
|
+
session.state.sessionReplay = this.mode
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}, this.featureName, this.ee)
|
|
118
|
+
|
|
119
|
+
// new handler for waiting for multiple flags. will be useful if/when backend designs multiple flags, or for evaluating multiple feature flags simultaneously (stn vs sr)
|
|
120
|
+
this.waitForFlags(['sr']).then(([{ value }]) => {
|
|
121
|
+
this.initializeRecording(
|
|
122
|
+
value,
|
|
123
|
+
Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.errorSampleRate'),
|
|
124
|
+
Math.random() < getConfigurationValue(this.agentIdentifier, 'session_replay.sampleRate')
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
drain(this.agentIdentifier, this.featureName)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
|
|
133
|
+
* @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
|
|
134
|
+
* @param {boolean} errorSample - the true/false state of the error sampling decision
|
|
135
|
+
* @param {boolean} fullSample - the true/false state of the full sampling decision
|
|
136
|
+
* @returns {void}
|
|
137
|
+
*/
|
|
138
|
+
async initializeRecording (entitlements, errorSample, fullSample) {
|
|
139
|
+
this.initialized = true
|
|
140
|
+
if (!entitlements) return
|
|
141
|
+
|
|
142
|
+
const { session } = getRuntime(this.agentIdentifier)
|
|
143
|
+
// if theres an existing session replay in progress, there's no need to sample, just check the entitlements response
|
|
144
|
+
// if not, these sample flags need to be checked
|
|
145
|
+
// if this isnt the FIRST load of a session AND
|
|
146
|
+
// we are not actively recording SR... DO NOT import or run the recording library
|
|
147
|
+
// session replay samples can only be decided on the first load of a session
|
|
148
|
+
// session replays can continue if already in progress
|
|
149
|
+
if (!session.isNew) { // inherit the mode of the existing session
|
|
150
|
+
this.mode = session.state.sessionReplay
|
|
151
|
+
} else {
|
|
152
|
+
// The session is new... determine the mode the new session should start in
|
|
153
|
+
if (fullSample) this.mode = MODE.FULL // full mode has precedence over error mode
|
|
154
|
+
else if (errorSample) this.mode = MODE.ERROR
|
|
155
|
+
// If neither are selected, then don't record (early return)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
160
|
+
// ERROR mode will do this until an error is thrown, and then switch into FULL mode.
|
|
161
|
+
// If an error happened in ERROR mode before we've gotten to this stage, it will have already set the mode to FULL
|
|
162
|
+
if (this.mode === MODE.FULL) {
|
|
163
|
+
// We only report (harvest) in FULL mode
|
|
164
|
+
this.scheduler.startTimer(this.harvestTimeSeconds)
|
|
165
|
+
}
|
|
166
|
+
// We record in FULL or ERROR mode
|
|
167
|
+
|
|
168
|
+
recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
|
|
169
|
+
this.startRecording()
|
|
170
|
+
const { gzipSync, strToU8 } = await import(/* webpackChunkName: "compressor" */'fflate')
|
|
171
|
+
gzipper = gzipSync
|
|
172
|
+
u8 = strToU8
|
|
173
|
+
|
|
174
|
+
this.isFirstChunk = !!session.isNew
|
|
175
|
+
|
|
176
|
+
session.state.sessionReplay = this.mode
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
prepareHarvest (options) {
|
|
180
|
+
if (this.events.length === 0) return
|
|
181
|
+
const payload = this.getHarvestContents()
|
|
182
|
+
try {
|
|
183
|
+
payload.body = gzipper(u8(stringify(payload.body)))
|
|
184
|
+
this.scheduler.opts.gzip = true
|
|
185
|
+
} catch (err) {
|
|
186
|
+
// failed to gzip
|
|
187
|
+
this.scheduler.opts.gzip = false
|
|
188
|
+
}
|
|
189
|
+
// TODO -- Gracefully handle the buffer for retries.
|
|
190
|
+
this.clearBuffer()
|
|
191
|
+
return [payload]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getHarvestContents () {
|
|
195
|
+
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
196
|
+
const info = getInfo(this.agentIdentifier)
|
|
197
|
+
return {
|
|
198
|
+
qs: { protocol_version: '0' },
|
|
199
|
+
body: {
|
|
200
|
+
type: 'SessionReplay',
|
|
201
|
+
appId: Number(info.applicationID),
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
blob: JSON.stringify(this.events), // this needs to be a stringified JSON array of rrweb nodes
|
|
204
|
+
attributes: {
|
|
205
|
+
session: agentRuntime.session.state.value,
|
|
206
|
+
hasSnapshot: this.hasSnapshot,
|
|
207
|
+
hasError: this.hasError,
|
|
208
|
+
agentVersion: agentRuntime.version,
|
|
209
|
+
isFirstChunk: this.isFirstChunk,
|
|
210
|
+
'nr.rrweb.version': RRWEB_VERSION
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
onHarvestFinished (result) {
|
|
217
|
+
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
218
|
+
if (result.status === 429) {
|
|
219
|
+
this.abort()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.blocked) this.scheduler.stopTimer(true)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
226
|
+
clearBuffer () {
|
|
227
|
+
this.events = []
|
|
228
|
+
this.isFirstChunk = false
|
|
229
|
+
this.hasSnapshot = false
|
|
230
|
+
this.hasError = false
|
|
231
|
+
this.payloadBytesEstimation = 0
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Begin recording using configured recording lib */
|
|
235
|
+
startRecording () {
|
|
236
|
+
if (!recorder) {
|
|
237
|
+
warn('Recording library was never imported')
|
|
238
|
+
return this.abort()
|
|
239
|
+
}
|
|
240
|
+
const { blockClass, ignoreClass, maskTextClass, blockSelector, maskInputOptions, maskTextSelector, maskAllInputs } = getConfigurationValue(this.agentIdentifier, 'session_replay')
|
|
241
|
+
this.hasSnapshot = true
|
|
242
|
+
// set up rrweb configurations for maximum privacy --
|
|
243
|
+
// https://newrelic.atlassian.net/wiki/spaces/O11Y/pages/2792293280/2023+02+28+Browser+-+Session+Replay#Configuration-options
|
|
244
|
+
this.stopRecording = recorder({
|
|
245
|
+
emit: this.store.bind(this),
|
|
246
|
+
blockClass,
|
|
247
|
+
ignoreClass,
|
|
248
|
+
maskTextClass,
|
|
249
|
+
blockSelector,
|
|
250
|
+
maskInputOptions,
|
|
251
|
+
maskTextSelector,
|
|
252
|
+
maskAllInputs,
|
|
253
|
+
...(this.mode === MODE.ERROR && { checkoutEveryNms: CHECKOUT_MS })
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
258
|
+
store (event, isCheckout) {
|
|
259
|
+
if (this.blocked) return
|
|
260
|
+
const eventBytes = stringify(event).length
|
|
261
|
+
/** The estimated size of the payload after compression */
|
|
262
|
+
const payloadSize = this.getPayloadSize(eventBytes)
|
|
263
|
+
// Vortex will block payloads at a certain size, we might as well not send.
|
|
264
|
+
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
265
|
+
return this.abort()
|
|
266
|
+
}
|
|
267
|
+
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
268
|
+
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
269
|
+
// each time we see a new checkout, we can drop the old data.
|
|
270
|
+
if (this.mode === MODE.ERROR && isCheckout) {
|
|
271
|
+
// we are still waiting for an error to throw, so keep wiping the buffer over time
|
|
272
|
+
this.clearBuffer()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.events.push(event)
|
|
276
|
+
this.payloadBytesEstimation += eventBytes
|
|
277
|
+
|
|
278
|
+
// We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
|
|
279
|
+
// it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
|
|
280
|
+
if (payloadSize > IDEAL_PAYLOAD_SIZE) {
|
|
281
|
+
// if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
|
|
282
|
+
this.scheduler.runHarvest()
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** force the recording lib to take a full DOM snapshot. This needs to occur in certain cases, like visibility changes */
|
|
287
|
+
takeFullSnapshot () {
|
|
288
|
+
if (!recorder) return
|
|
289
|
+
recorder.takeFullSnapshot()
|
|
290
|
+
this.hasSnapshot = true
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Estimate the payload size */
|
|
294
|
+
getPayloadSize (newBytes = 0) {
|
|
295
|
+
// the 1KB gives us some padding for the other metadata
|
|
296
|
+
return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Abort the feature, once aborted it will not resume */
|
|
300
|
+
abort () {
|
|
301
|
+
this.blocked = true
|
|
302
|
+
this.stopRecording()
|
|
303
|
+
const { session } = getRuntime(this.agentIdentifier)
|
|
304
|
+
session.state.sessionReplay = this.mode
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Extensive research has yielded about an 88% compression factor on these payloads.
|
|
308
|
+
* This is an estimation using that factor as to not cause performance issues while evaluating
|
|
309
|
+
* https://staging.onenr.io/037jbJWxbjy
|
|
310
|
+
* */
|
|
311
|
+
estimateCompression (data) {
|
|
312
|
+
return data * 0.12
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @file The top-level entry point for the eventual Session Replay feature.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
|
|
9
|
+
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
|
|
10
|
+
* functionality is validated and a full user experience is curated.
|
|
11
|
+
*/
|
|
12
|
+
export { Instrument as SessionReplay } from './instrument/index'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @file Primes the Session Replay feature for lazy loading.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This code is under development and dormant. It will not download to instrumented pages or record any data.
|
|
9
|
+
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
|
|
10
|
+
* functionality is validated and a full user experience is curated.
|
|
11
|
+
*/
|
|
12
|
+
import { InstrumentBase } from '../../utils/instrument-base'
|
|
13
|
+
import { FEATURE_NAME } from '../constants'
|
|
14
|
+
|
|
15
|
+
export class Instrument extends InstrumentBase {
|
|
16
|
+
static featureName = FEATURE_NAME
|
|
17
|
+
constructor (agentIdentifier, aggregator, auto = true) {
|
|
18
|
+
super(agentIdentifier, aggregator, FEATURE_NAME, auto)
|
|
19
|
+
|
|
20
|
+
this.importAggregator()
|
|
21
|
+
}
|
|
22
|
+
}
|