@newrelic/browser-agent 1.252.0 → 1.253.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 +18 -0
- package/README.md +6 -6
- package/dist/cjs/cdn/experimental.js +6 -2
- package/dist/cjs/cdn/spa.js +5 -3
- package/dist/cjs/common/aggregate/aggregator.js +1 -8
- package/dist/cjs/common/config/state/init.js +7 -0
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/context/observation-context-manager.js +56 -0
- package/dist/cjs/common/event-emitter/contextual-ee.js +12 -9
- package/dist/cjs/common/session/constants.js +2 -1
- package/dist/cjs/common/session/session-entity.js +3 -1
- package/dist/cjs/common/timing/nav-timing.js +8 -3
- package/dist/cjs/common/timing/now.js +1 -1
- package/dist/cjs/common/util/feature-flags.js +1 -1
- package/dist/cjs/common/wrap/index.js +0 -7
- package/dist/cjs/common/wrap/wrap-events.js +2 -2
- package/dist/cjs/common/wrap/wrap-fetch.js +2 -1
- package/dist/cjs/common/wrap/wrap-function.js +5 -7
- package/dist/cjs/common/wrap/wrap-promise.js +2 -1
- package/dist/cjs/features/ajax/aggregate/index.js +34 -16
- package/dist/cjs/features/jserrors/aggregate/index.js +77 -66
- package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
- package/dist/cjs/features/page_view_event/aggregate/initialized-features.js +1 -0
- package/dist/cjs/features/session_replay/aggregate/index.js +96 -94
- package/dist/cjs/features/session_replay/constants.js +5 -1
- package/dist/cjs/features/session_replay/instrument/index.js +24 -8
- package/dist/cjs/features/session_replay/shared/recorder.js +5 -4
- package/dist/cjs/features/session_replay/shared/stylesheet-evaluator.js +8 -7
- package/dist/cjs/features/session_replay/shared/utils.js +26 -0
- package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +50 -0
- package/dist/cjs/features/soft_navigations/aggregate/bel-node.js +29 -0
- package/dist/cjs/features/soft_navigations/aggregate/index.js +263 -0
- package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +62 -0
- package/dist/cjs/features/soft_navigations/aggregate/interaction.js +146 -0
- package/dist/cjs/features/soft_navigations/constants.js +31 -0
- package/dist/cjs/features/soft_navigations/index.js +12 -0
- package/dist/cjs/features/soft_navigations/instrument/index.js +79 -0
- package/dist/cjs/features/spa/aggregate/index.js +4 -4
- package/dist/cjs/features/utils/agent-session.js +2 -1
- package/dist/cjs/features/utils/instrument-base.js +6 -9
- package/dist/cjs/features/utils/lazy-feature-loader.js +2 -0
- package/dist/cjs/loaders/agent-base.js +18 -3
- package/dist/cjs/loaders/agent.js +15 -18
- package/dist/cjs/loaders/api/api-methods.js +9 -0
- package/dist/cjs/loaders/api/api.js +17 -18
- package/dist/cjs/loaders/configure/configure.js +5 -2
- package/dist/cjs/loaders/features/enabled-features.js +1 -1
- package/dist/cjs/loaders/features/features.js +3 -1
- package/dist/esm/cdn/experimental.js +5 -2
- package/dist/esm/cdn/spa.js +3 -1
- package/dist/esm/common/aggregate/aggregator.js +1 -8
- package/dist/esm/common/config/state/init.js +7 -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/context/observation-context-manager.js +49 -0
- package/dist/esm/common/event-emitter/contextual-ee.js +12 -9
- package/dist/esm/common/session/constants.js +1 -0
- package/dist/esm/common/session/session-entity.js +3 -1
- package/dist/esm/common/timing/nav-timing.js +8 -3
- package/dist/esm/common/timing/now.js +1 -1
- package/dist/esm/common/util/feature-flags.js +1 -1
- package/dist/esm/common/wrap/index.js +1 -2
- package/dist/esm/common/wrap/wrap-events.js +3 -3
- package/dist/esm/common/wrap/wrap-fetch.js +3 -2
- package/dist/esm/common/wrap/wrap-function.js +4 -5
- package/dist/esm/common/wrap/wrap-promise.js +3 -2
- package/dist/esm/features/ajax/aggregate/index.js +36 -18
- package/dist/esm/features/jserrors/aggregate/index.js +77 -66
- package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
- package/dist/esm/features/page_view_event/aggregate/initialized-features.js +1 -0
- package/dist/esm/features/session_replay/aggregate/index.js +97 -95
- package/dist/esm/features/session_replay/constants.js +4 -0
- package/dist/esm/features/session_replay/instrument/index.js +25 -9
- package/dist/esm/features/session_replay/shared/recorder.js +5 -4
- package/dist/esm/features/session_replay/shared/stylesheet-evaluator.js +8 -7
- package/dist/esm/features/session_replay/shared/utils.js +17 -0
- package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +43 -0
- package/dist/esm/features/soft_navigations/aggregate/bel-node.js +22 -0
- package/dist/esm/features/soft_navigations/aggregate/index.js +256 -0
- package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +55 -0
- package/dist/esm/features/soft_navigations/aggregate/interaction.js +140 -0
- package/dist/esm/features/soft_navigations/constants.js +25 -0
- package/dist/esm/features/soft_navigations/index.js +1 -0
- package/dist/esm/features/soft_navigations/instrument/index.js +73 -0
- package/dist/esm/features/spa/aggregate/index.js +4 -4
- package/dist/esm/features/utils/agent-session.js +2 -1
- package/dist/esm/features/utils/instrument-base.js +7 -10
- package/dist/esm/features/utils/lazy-feature-loader.js +2 -0
- package/dist/esm/loaders/agent-base.js +18 -3
- package/dist/esm/loaders/agent.js +15 -18
- package/dist/esm/loaders/api/api-methods.js +3 -0
- package/dist/esm/loaders/api/api.js +17 -17
- package/dist/esm/loaders/configure/configure.js +5 -2
- package/dist/esm/loaders/features/enabled-features.js +1 -1
- package/dist/esm/loaders/features/features.js +3 -1
- package/dist/types/common/aggregate/aggregator.d.ts.map +1 -1
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/context/event-context.d.ts.map +1 -0
- package/dist/types/common/context/observation-context-manager.d.ts +28 -0
- package/dist/types/common/context/observation-context-manager.d.ts.map +1 -0
- package/dist/types/common/event-emitter/contextual-ee.d.ts +2 -2
- package/dist/types/common/event-emitter/contextual-ee.d.ts.map +1 -1
- package/dist/types/common/session/constants.d.ts +1 -0
- package/dist/types/common/session/constants.d.ts.map +1 -1
- package/dist/types/common/session/session-entity.d.ts +0 -1
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/timing/nav-timing.d.ts.map +1 -1
- package/dist/types/common/wrap/index.d.ts +1 -2
- package/dist/types/common/wrap/index.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 +0 -1
- package/dist/types/common/wrap/wrap-function.d.ts.map +1 -1
- package/dist/types/common/wrap/wrap-promise.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts +4 -3
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/initialized-features.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/constants.d.ts +4 -0
- package/dist/types/features/session_replay/constants.d.ts.map +1 -1
- package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/stylesheet-evaluator.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/utils.d.ts +4 -0
- package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +19 -0
- package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts +16 -0
- package/dist/types/features/soft_navigations/aggregate/bel-node.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/aggregate/index.d.ts +36 -0
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts +12 -0
- package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +50 -0
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/constants.d.ts +20 -0
- package/dist/types/features/soft_navigations/constants.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/index.d.ts +2 -0
- package/dist/types/features/soft_navigations/index.d.ts.map +1 -0
- package/dist/types/features/soft_navigations/instrument/index.d.ts +7 -0
- package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -0
- 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/instrument-base.d.ts +1 -7
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/features/utils/lazy-feature-loader.d.ts.map +1 -1
- package/dist/types/loaders/agent-base.d.ts +5 -1
- package/dist/types/loaders/agent-base.d.ts.map +1 -1
- package/dist/types/loaders/agent.d.ts +2 -2
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/dist/types/loaders/api/api-methods.d.ts +3 -0
- package/dist/types/loaders/api/api-methods.d.ts.map +1 -0
- package/dist/types/loaders/api/api.d.ts +3 -6
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.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/dist/types/loaders/micro-agent.d.ts +0 -1
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cdn/experimental.js +4 -2
- package/src/cdn/spa.js +3 -1
- package/src/common/aggregate/aggregator.js +2 -11
- package/src/common/config/state/init.js +3 -1
- package/src/common/context/observation-context-manager.js +55 -0
- package/src/common/event-emitter/contextual-ee.js +20 -10
- package/src/common/session/constants.js +1 -0
- package/src/common/session/session-entity.js +3 -1
- package/src/common/timing/nav-timing.js +7 -3
- package/src/common/timing/now.js +1 -1
- package/src/common/util/feature-flags.js +1 -1
- package/src/common/wrap/index.js +1 -2
- package/src/common/wrap/wrap-events.js +3 -3
- package/src/common/wrap/wrap-fetch.js +3 -2
- package/src/common/wrap/wrap-function.js +4 -6
- package/src/common/wrap/wrap-promise.js +3 -2
- package/src/features/ajax/aggregate/index.js +36 -18
- package/src/features/jserrors/aggregate/index.js +70 -73
- package/src/features/page_view_event/aggregate/index.js +1 -1
- package/src/features/page_view_event/aggregate/initialized-features.js +1 -0
- package/src/features/session_replay/aggregate/index.js +92 -95
- package/src/features/session_replay/constants.js +5 -0
- package/src/features/session_replay/instrument/index.js +24 -9
- package/src/features/session_replay/shared/recorder.js +5 -4
- package/src/features/session_replay/shared/stylesheet-evaluator.js +8 -7
- package/src/features/session_replay/shared/utils.js +19 -0
- package/src/features/soft_navigations/aggregate/ajax-node.js +57 -0
- package/src/features/soft_navigations/aggregate/bel-node.js +26 -0
- package/src/features/soft_navigations/aggregate/index.js +254 -0
- package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +53 -0
- package/src/features/soft_navigations/aggregate/interaction.js +159 -0
- package/src/features/soft_navigations/constants.js +29 -0
- package/src/features/soft_navigations/index.js +1 -0
- package/src/features/soft_navigations/instrument/index.js +67 -0
- package/src/features/spa/aggregate/index.js +5 -4
- package/src/features/utils/agent-session.js +2 -1
- package/src/features/utils/instrument-base.js +7 -10
- package/src/features/utils/lazy-feature-loader.js +2 -0
- package/src/loaders/agent-base.js +18 -3
- package/src/loaders/agent.js +18 -17
- package/src/loaders/api/api-methods.js +12 -0
- package/src/loaders/api/api.js +17 -28
- package/src/loaders/configure/configure.js +4 -1
- package/src/loaders/features/enabled-features.js +1 -1
- package/src/loaders/features/features.js +3 -1
- package/dist/cjs/common/wrap/wrap-raf.js +0 -55
- package/dist/esm/common/wrap/wrap-raf.js +0 -48
- package/dist/types/common/event-emitter/event-context.d.ts.map +0 -1
- package/dist/types/common/wrap/wrap-raf.d.ts +0 -16
- package/dist/types/common/wrap/wrap-raf.d.ts.map +0 -1
- package/src/common/wrap/wrap-raf.js +0 -52
- /package/dist/cjs/common/{event-emitter → context}/event-context.js +0 -0
- /package/dist/esm/common/{event-emitter → context}/event-context.js +0 -0
- /package/dist/types/common/{event-emitter → context}/event-context.d.ts +0 -0
- /package/src/common/{event-emitter → context}/event-context.js +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { registerHandler } from '../../../common/event-emitter/register-handler';
|
|
14
14
|
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
15
|
-
import { ABORT_REASONS, FEATURE_NAME, MAX_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES } from '../constants';
|
|
15
|
+
import { ABORT_REASONS, FEATURE_NAME, MAX_PAYLOAD_SIZE, QUERY_PARAM_PADDING, RRWEB_EVENT_TYPES, SR_EVENT_EMITTER_TYPES } from '../constants';
|
|
16
16
|
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
|
|
17
17
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
18
18
|
import { sharedChannel } from '../../../common/constants/shared-channel';
|
|
@@ -53,103 +53,100 @@ export class Aggregate extends AggregateBase {
|
|
|
53
53
|
this.recorder = args?.recorder;
|
|
54
54
|
if (this.recorder) this.recorder.parent = this;
|
|
55
55
|
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
56
|
-
const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
|
|
57
|
-
if (shouldSetup) {
|
|
58
|
-
// 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.
|
|
59
|
-
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
60
|
-
this.scheduler.runHarvest();
|
|
61
|
-
this.abort(ABORT_REASONS.RESET);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
65
|
-
this.ee.on(SESSION_EVENTS.PAUSE, () => {
|
|
66
|
-
this.recorder?.stopRecording();
|
|
67
|
-
});
|
|
68
|
-
// 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.
|
|
69
|
-
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
70
|
-
if (!this.recorder) return;
|
|
71
|
-
// if the mode changed on a different tab, it needs to update this instance to match
|
|
72
|
-
const {
|
|
73
|
-
session
|
|
74
|
-
} = getRuntime(this.agentIdentifier);
|
|
75
|
-
this.mode = session.state.sessionReplayMode;
|
|
76
|
-
if (!this.initialized || this.mode === MODE.OFF) return;
|
|
77
|
-
this.recorder?.startRecording();
|
|
78
|
-
});
|
|
79
|
-
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
80
|
-
if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
81
|
-
if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
|
|
82
|
-
this.mode = data.sessionReplay;
|
|
83
|
-
});
|
|
84
56
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
raw: true
|
|
91
|
-
}, this);
|
|
92
|
-
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
93
|
-
this.prepUtils().then(() => {
|
|
94
|
-
this.scheduler.runHarvest();
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
registerHandler('recordReplay', () => {
|
|
98
|
-
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
99
|
-
if (this.blocked || !this.entitled) return;
|
|
100
|
-
// if it isnt already (fully) initialized... initialize it
|
|
101
|
-
if (!this.recorder) this.initializeRecording(false, true, true);
|
|
102
|
-
// its been initialized and imported the recorder but its not recording (mode === off || error)
|
|
103
|
-
else if (this.mode !== MODE.FULL) this.switchToFull();
|
|
104
|
-
// if it gets all the way to here, that means a full session is already recording... do nothing
|
|
105
|
-
}, this.featureName, this.ee);
|
|
106
|
-
registerHandler('pauseReplay', () => {
|
|
107
|
-
this.forceStop(this.mode !== MODE.ERROR);
|
|
108
|
-
}, this.featureName, this.ee);
|
|
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.
|
|
58
|
+
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
59
|
+
this.scheduler.runHarvest();
|
|
60
|
+
this.abort(ABORT_REASONS.RESET);
|
|
61
|
+
});
|
|
109
62
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
}, this.featureName, this.ee);
|
|
63
|
+
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
64
|
+
this.ee.on(SESSION_EVENTS.PAUSE, () => {
|
|
65
|
+
this.recorder?.stopRecording();
|
|
66
|
+
});
|
|
67
|
+
// 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.
|
|
68
|
+
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
69
|
+
if (!this.recorder) return;
|
|
70
|
+
// if the mode changed on a different tab, it needs to update this instance to match
|
|
120
71
|
const {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let [flagOn] = _ref;
|
|
133
|
-
this.entitled = flagOn;
|
|
134
|
-
if (!this.entitled && this.recorder?.recording) {
|
|
135
|
-
this.recorder.abort(ABORT_REASONS.ENTITLEMENTS);
|
|
136
|
-
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
137
|
-
}
|
|
138
|
-
this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
|
|
139
|
-
}).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
|
|
72
|
+
session
|
|
73
|
+
} = getRuntime(this.agentIdentifier);
|
|
74
|
+
this.mode = session.state.sessionReplayMode;
|
|
75
|
+
if (!this.initialized || this.mode === MODE.OFF) return;
|
|
76
|
+
this.recorder?.startRecording();
|
|
77
|
+
});
|
|
78
|
+
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
79
|
+
if (!this.recorder || !this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
80
|
+
if (this.mode !== MODE.OFF && data.sessionReplayMode === MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
|
|
81
|
+
this.mode = data.sessionReplay;
|
|
82
|
+
});
|
|
140
83
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
84
|
+
// Bespoke logic for blobs endpoint.
|
|
85
|
+
this.scheduler = new HarvestScheduler('browser/blobs', {
|
|
86
|
+
onFinished: this.onHarvestFinished.bind(this),
|
|
87
|
+
retryDelay: this.harvestTimeSeconds,
|
|
88
|
+
getPayload: this.prepareHarvest.bind(this),
|
|
89
|
+
raw: true
|
|
90
|
+
}, this);
|
|
91
|
+
registerHandler(SR_EVENT_EMITTER_TYPES.RECORD, () => {
|
|
92
|
+
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
93
|
+
if (this.blocked || !this.entitled) return;
|
|
94
|
+
// if it isnt already (fully) initialized... initialize it
|
|
95
|
+
if (!this.recorder) this.initializeRecording(false, true, true);
|
|
96
|
+
// its been initialized and imported the recorder but its not recording (mode === off || error)
|
|
97
|
+
else if (this.mode !== MODE.FULL) this.switchToFull();
|
|
98
|
+
// if it gets all the way to here, that means a full session is already recording... do nothing
|
|
99
|
+
}, this.featureName, this.ee);
|
|
100
|
+
registerHandler(SR_EVENT_EMITTER_TYPES.PAUSE, () => {
|
|
101
|
+
this.forceStop(this.mode !== MODE.ERROR);
|
|
102
|
+
}, this.featureName, this.ee);
|
|
103
|
+
|
|
104
|
+
// Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
|
|
105
|
+
// This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
|
|
106
|
+
registerHandler('errorAgg', e => {
|
|
107
|
+
this.errorNoticed = true;
|
|
108
|
+
if (this.recorder) this.recorder.currentBufferTarget.hasError = true;
|
|
109
|
+
// run once
|
|
110
|
+
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
111
|
+
this.switchToFull();
|
|
112
|
+
}
|
|
113
|
+
}, this.featureName, this.ee);
|
|
114
|
+
const {
|
|
115
|
+
error_sampling_rate,
|
|
116
|
+
sampling_rate,
|
|
117
|
+
autoStart,
|
|
118
|
+
block_selector,
|
|
119
|
+
mask_text_selector,
|
|
120
|
+
mask_all_inputs,
|
|
121
|
+
inline_stylesheet,
|
|
122
|
+
inline_images,
|
|
123
|
+
collect_fonts
|
|
124
|
+
} = getConfigurationValue(this.agentIdentifier, 'session_replay');
|
|
125
|
+
this.waitForFlags(['sr']).then(_ref => {
|
|
126
|
+
let [flagOn] = _ref;
|
|
127
|
+
this.entitled = flagOn;
|
|
128
|
+
if (!this.entitled && this.recorder?.recording) {
|
|
129
|
+
this.abort(ABORT_REASONS.ENTITLEMENTS);
|
|
130
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/EnabledNotEntitled/Detected'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.initializeRecording(Math.random() * 100 < error_sampling_rate, Math.random() * 100 < sampling_rate);
|
|
134
|
+
}).then(() => {
|
|
135
|
+
if (this.mode === MODE.OFF) args?.recorder?.stopRecording(); // stop any conservative preload recording launched by instrument
|
|
136
|
+
sharedChannel.onReplayReady(this.mode); // notify watchers that replay started with the mode
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/** Detect if the default configs have been altered and report a SM. This is useful to evaluate what the reasonable defaults are across a customer base over time */
|
|
140
|
+
if (!autoStart) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/AutoStart/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
141
|
+
if (collect_fonts === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/CollectFonts/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
142
|
+
if (inline_stylesheet !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineStylesheet/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
143
|
+
if (inline_images === true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/InlineImages/Modifed'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
144
|
+
if (mask_all_inputs !== true) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskAllInputs/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
145
|
+
if (block_selector !== '[data-nr-block]') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/BlockSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
146
|
+
if (mask_text_selector !== '*') handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/MaskTextSelector/Modified'], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
147
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/SamplingRate/Value', sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
148
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/ErrorSamplingRate/Value', error_sampling_rate], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
149
|
+
this.drain();
|
|
153
150
|
}
|
|
154
151
|
switchToFull() {
|
|
155
152
|
this.mode = MODE.FULL;
|
|
@@ -197,6 +194,11 @@ export class Aggregate extends AggregateBase {
|
|
|
197
194
|
return;
|
|
198
195
|
}
|
|
199
196
|
}
|
|
197
|
+
if (this.recorder?.getEvents().type === 'preloaded') {
|
|
198
|
+
this.prepUtils().then(() => {
|
|
199
|
+
this.scheduler.runHarvest();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
200
202
|
if (!this.recorder) {
|
|
201
203
|
try {
|
|
202
204
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
@@ -389,7 +391,7 @@ export class Aggregate extends AggregateBase {
|
|
|
389
391
|
});
|
|
390
392
|
this.recorder?.clearTimestamps?.();
|
|
391
393
|
this.ee.emit('REPLAY_ABORTED');
|
|
392
|
-
this.recorder?.clearBuffer?.();
|
|
394
|
+
while (this.recorder?.getEvents().events.length) this.recorder?.clearBuffer?.();
|
|
393
395
|
}
|
|
394
396
|
syncWithSessionManager() {
|
|
395
397
|
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { MODE } from '../../common/session/constants';
|
|
2
2
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
3
3
|
export const FEATURE_NAME = FEATURE_NAMES.sessionReplay;
|
|
4
|
+
export const SR_EVENT_EMITTER_TYPES = {
|
|
5
|
+
RECORD: 'recordReplay',
|
|
6
|
+
PAUSE: 'pauseReplay'
|
|
7
|
+
};
|
|
4
8
|
export const AVG_COMPRESSION = 0.12;
|
|
5
9
|
export const RRWEB_EVENT_TYPES = {
|
|
6
10
|
DomContentLoaded: 0,
|
|
@@ -9,23 +9,38 @@
|
|
|
9
9
|
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent until
|
|
10
10
|
* functionality is validated and a full user experience is curated.
|
|
11
11
|
*/
|
|
12
|
-
import { MODE } from '../../../common/session/constants';
|
|
12
|
+
import { DEFAULT_KEY, MODE, PREFIX } from '../../../common/session/constants';
|
|
13
13
|
import { InstrumentBase } from '../../utils/instrument-base';
|
|
14
14
|
import { FEATURE_NAME } from '../constants';
|
|
15
|
+
import { isPreloadAllowed } from '../shared/utils';
|
|
15
16
|
export class Instrument extends InstrumentBase {
|
|
16
17
|
static featureName = FEATURE_NAME;
|
|
17
18
|
constructor(agentIdentifier, aggregator) {
|
|
18
19
|
let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
19
20
|
super(agentIdentifier, aggregator, FEATURE_NAME, auto);
|
|
21
|
+
let session;
|
|
20
22
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
23
|
+
session = JSON.parse(localStorage.getItem("".concat(PREFIX, "_").concat(DEFAULT_KEY)));
|
|
24
|
+
} catch (err) {}
|
|
25
|
+
if (this.#canPreloadRecorder(session)) {
|
|
26
|
+
this.#startRecording(session?.sessionReplayMode);
|
|
27
|
+
} else {
|
|
28
|
+
this.importAggregator();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// At this point wherein session state exists already but we haven't init SessionEntity aka verify timers.
|
|
33
|
+
#canPreloadRecorder(session) {
|
|
34
|
+
if (!session) {
|
|
35
|
+
// this might be a new session if entity initializes: conservatively start recording if first-time config allows
|
|
36
|
+
// Note: users with SR enabled, as well as these other configs enabled by-default, will be penalized by the recorder overhead EVEN IF they don't actually have or get
|
|
37
|
+
// entitlement or sampling decision, or otherwise intentionally opted-in for the feature.
|
|
38
|
+
return isPreloadAllowed(this.agentIdentifier);
|
|
39
|
+
} else if (session.sessionReplayMode === MODE.FULL || session.sessionReplayMode === MODE.ERROR) {
|
|
40
|
+
return true; // existing sessions get to continue recording, regardless of this page's configs or if it has expired (conservatively)
|
|
41
|
+
} else {
|
|
42
|
+
// SR mode was OFF but may potentially be turned on if session resets and configs allows the new session to have replay...
|
|
43
|
+
return isPreloadAllowed(this.agentIdentifier);
|
|
29
44
|
}
|
|
30
45
|
}
|
|
31
46
|
async #startRecording(mode) {
|
|
@@ -37,6 +52,7 @@ export class Instrument extends InstrumentBase {
|
|
|
37
52
|
agentIdentifier: this.agentIdentifier
|
|
38
53
|
});
|
|
39
54
|
this.recorder.startRecording();
|
|
55
|
+
this.abortHandler = this.recorder.stopRecording;
|
|
40
56
|
this.importAggregator({
|
|
41
57
|
recorder: this.recorder
|
|
42
58
|
});
|
|
@@ -109,14 +109,15 @@ export class Recorder {
|
|
|
109
109
|
const incompletes = stylesheetEvaluator.evaluate();
|
|
110
110
|
/** Only stop ignoring data if already ignoring and a new valid snapshap is taking place (0 incompletes and we get a meta node for the snap) */
|
|
111
111
|
if (!incompletes && this.#fixing && event.type === RRWEB_EVENT_TYPES.Meta) this.#fixing = false;
|
|
112
|
-
if (incompletes) {
|
|
112
|
+
if (incompletes > 0) {
|
|
113
113
|
/** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
|
|
114
114
|
stylesheetEvaluator.fix().then(failedToFix => {
|
|
115
|
-
if (failedToFix) {
|
|
115
|
+
if (failedToFix > 0) {
|
|
116
116
|
this.currentBufferTarget.inlinedAllStylesheets = false;
|
|
117
117
|
this.shouldFix = false;
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
}
|
|
119
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Failed', failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
120
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ['SessionReplay/Payload/Missing-Inline-Css/Fixed', incompletes - failedToFix], undefined, FEATURE_NAMES.metrics, this.parent.ee);
|
|
120
121
|
this.takeFullSnapshot();
|
|
121
122
|
});
|
|
122
123
|
/** Only start ignoring data if got a faulty snapshot */
|
|
@@ -18,15 +18,15 @@ class StylesheetEvaluator {
|
|
|
18
18
|
let incompletes = 0;
|
|
19
19
|
if (isBrowserScope) {
|
|
20
20
|
for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.#evaluated.add(ss);
|
|
21
|
+
if (!this.#evaluated.has(document.styleSheets[i])) {
|
|
22
|
+
this.#evaluated.add(document.styleSheets[i]);
|
|
24
23
|
try {
|
|
25
24
|
// eslint-disable-next-line
|
|
26
|
-
const temp =
|
|
25
|
+
const temp = document.styleSheets[i].cssRules;
|
|
27
26
|
} catch (err) {
|
|
27
|
+
if (!document.styleSheets[i].href) return;
|
|
28
28
|
incompletes++;
|
|
29
|
-
this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]
|
|
29
|
+
this.#fetchProms.push(this.#fetchAndOverride(document.styleSheets[i]));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -53,9 +53,10 @@ class StylesheetEvaluator {
|
|
|
53
53
|
* @param {*} href - The asset href to fetch
|
|
54
54
|
* @returns {Promise}
|
|
55
55
|
*/
|
|
56
|
-
async #fetchAndOverride(target
|
|
56
|
+
async #fetchAndOverride(target) {
|
|
57
|
+
if (!target?.href) return;
|
|
57
58
|
try {
|
|
58
|
-
const stylesheetContents = await originals.FETCH.bind(window)(href);
|
|
59
|
+
const stylesheetContents = await originals.FETCH.bind(window)(target.href);
|
|
59
60
|
if (!stylesheetContents.ok) {
|
|
60
61
|
this.failedToFix++;
|
|
61
62
|
return;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getConfigurationValue, originals } from '../../../common/config/config';
|
|
2
|
+
import { isBrowserScope } from '../../../common/constants/runtime';
|
|
3
|
+
export const enableSessionTracking = agentId => isBrowserScope && getConfigurationValue(agentId, 'privacy.cookies_enabled') === true;
|
|
4
|
+
function hasReplayPrerequisite(agentId) {
|
|
5
|
+
return originals.MO &&
|
|
6
|
+
// Session Replay cannot work without Mutation Observer
|
|
7
|
+
enableSessionTracking &&
|
|
8
|
+
// requires session tracking to be running (hence "session" replay...)
|
|
9
|
+
getConfigurationValue(agentId, 'session_trace.enabled') === true; // Session Replay as of now is tightly coupled with Session Trace in the UI
|
|
10
|
+
}
|
|
11
|
+
export function isPreloadAllowed(agentId) {
|
|
12
|
+
return getConfigurationValue(agentId, 'session_replay.preload') === true && hasReplayPrerequisite(agentId);
|
|
13
|
+
}
|
|
14
|
+
export function canImportReplayAgg(agentId, sessionMgr) {
|
|
15
|
+
if (!hasReplayPrerequisite(agentId)) return false;
|
|
16
|
+
return !!sessionMgr?.isNew || !!sessionMgr?.state.sessionReplayMode; // Session Replay should only try to run if already running from a previous page, or at the beginning of a session
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
|
|
2
|
+
import { NODE_TYPE } from '../constants';
|
|
3
|
+
import { BelNode } from './bel-node';
|
|
4
|
+
export class AjaxNode extends BelNode {
|
|
5
|
+
constructor(agentIdentifier, ajaxEvent) {
|
|
6
|
+
super(agentIdentifier);
|
|
7
|
+
this.belType = NODE_TYPE.AJAX;
|
|
8
|
+
this.method = ajaxEvent.method;
|
|
9
|
+
this.status = ajaxEvent.status;
|
|
10
|
+
this.domain = ajaxEvent.domain;
|
|
11
|
+
this.path = ajaxEvent.path;
|
|
12
|
+
this.txSize = ajaxEvent.requestSize;
|
|
13
|
+
this.rxSize = ajaxEvent.responseSize;
|
|
14
|
+
this.requestedWith = ajaxEvent.type === 'fetch' ? 1 : ''; // 'xhr' and 'beacon' types get the empty string
|
|
15
|
+
this.spanId = ajaxEvent.spanId;
|
|
16
|
+
this.traceId = ajaxEvent.traceId;
|
|
17
|
+
this.spanTimestamp = ajaxEvent.spanTimestamp;
|
|
18
|
+
this.gql = ajaxEvent.gql;
|
|
19
|
+
this.start = ajaxEvent.startTime;
|
|
20
|
+
this.end = ajaxEvent.endTime;
|
|
21
|
+
}
|
|
22
|
+
serialize(parentStartTimestamp) {
|
|
23
|
+
const addString = getAddStringContext(this.agentIdentifier);
|
|
24
|
+
const nodeList = [];
|
|
25
|
+
|
|
26
|
+
// IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
|
|
27
|
+
const fields = [numeric(this.belType), 0,
|
|
28
|
+
// this will be overwritten below with number of attached nodes
|
|
29
|
+
numeric(this.start - parentStartTimestamp),
|
|
30
|
+
// start relative to parent start (if part of first node in payload) or first parent start
|
|
31
|
+
numeric(this.end - this.start),
|
|
32
|
+
// end is relative to start
|
|
33
|
+
numeric(this.callbackEnd), numeric(this.callbackDuration), addString(this.method), numeric(this.status), addString(this.domain), addString(this.path), numeric(this.txSize), numeric(this.rxSize), this.requestedWith, addString(this.nodeId), nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)];
|
|
34
|
+
let allAttachedNodes = [];
|
|
35
|
+
if (typeof this.gql === 'object') allAttachedNodes = addCustomAttributes(this.gql, addString);
|
|
36
|
+
this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
|
|
37
|
+
|
|
38
|
+
fields[1] = numeric(allAttachedNodes.length);
|
|
39
|
+
nodeList.push(fields);
|
|
40
|
+
if (allAttachedNodes.length) nodeList.push(allAttachedNodes.join(';'));
|
|
41
|
+
return nodeList.join(';');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { now } from '../../../common/timing/now';
|
|
2
|
+
let nodesSeen = 0;
|
|
3
|
+
export class BelNode {
|
|
4
|
+
belType;
|
|
5
|
+
/** List of other BelNode derivatives. Each children should be of a subclass that implements its own 'serialize' function. */
|
|
6
|
+
children = [];
|
|
7
|
+
start = now();
|
|
8
|
+
end;
|
|
9
|
+
callbackEnd = 0;
|
|
10
|
+
callbackDuration = 0;
|
|
11
|
+
nodeId = ++nodesSeen;
|
|
12
|
+
constructor(agentIdentifier) {
|
|
13
|
+
if (!agentIdentifier) throw new Error('Interaction is missing core attributes');
|
|
14
|
+
this.agentIdentifier = agentIdentifier;
|
|
15
|
+
}
|
|
16
|
+
addChild(child) {
|
|
17
|
+
this.children.push(child);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Virtual fn for stringifying an instance. */
|
|
21
|
+
serialize() {}
|
|
22
|
+
}
|