@newrelic/browser-agent 1.241.0 → 1.243.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 +1465 -0
- package/dist/cjs/cdn/polyfills/lite.js +13 -1
- package/dist/cjs/cdn/polyfills/pro.js +17 -1
- package/dist/cjs/cdn/polyfills/spa.js +18 -1
- package/dist/cjs/common/config/state/init.js +32 -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/dom/query-selector.js +16 -0
- package/dist/cjs/common/session/session-entity.js +20 -2
- package/dist/cjs/common/wrap/wrap-function.js +1 -1
- package/dist/cjs/features/ajax/aggregate/index.js +1 -1
- package/dist/cjs/features/session_replay/aggregate/index.js +84 -50
- package/dist/cjs/features/utils/feature-base.js +1 -2
- package/dist/cjs/features/utils/instrument-base.js +1 -0
- package/dist/cjs/loaders/api/api.js +2 -2
- package/dist/cjs/loaders/api/apiAsync.js +0 -37
- package/dist/cjs/loaders/configure/configure.js +1 -1
- package/dist/cjs/loaders/configure/public-path.js +6 -3
- package/dist/esm/cdn/polyfills/lite.js +8 -1
- package/dist/esm/cdn/polyfills/pro.js +13 -2
- package/dist/esm/cdn/polyfills/spa.js +13 -1
- package/dist/esm/common/config/state/init.js +32 -5
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/dom/query-selector.js +9 -0
- package/dist/esm/common/session/session-entity.js +18 -1
- package/dist/esm/common/wrap/wrap-function.js +1 -1
- package/dist/esm/features/ajax/aggregate/index.js +1 -1
- package/dist/esm/features/session_replay/aggregate/index.js +83 -50
- package/dist/esm/features/utils/feature-base.js +1 -2
- package/dist/esm/features/utils/instrument-base.js +1 -0
- package/dist/esm/loaders/api/api.js +2 -2
- package/dist/esm/loaders/api/apiAsync.js +1 -36
- package/dist/esm/loaders/configure/configure.js +1 -1
- package/dist/esm/loaders/configure/public-path.js +6 -3
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/dom/query-selector.d.ts +2 -0
- package/dist/types/common/dom/query-selector.d.ts.map +1 -0
- package/dist/types/common/session/session-entity.d.ts +5 -0
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +11 -14
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/feature-base.d.ts.map +1 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
- package/dist/types/loaders/configure/public-path.d.ts +1 -1
- package/dist/types/loaders/configure/public-path.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/cdn/polyfills/lite.js +14 -1
- package/src/cdn/polyfills/pro.js +23 -2
- package/src/cdn/polyfills/spa.js +24 -1
- package/src/common/config/state/init.js +33 -4
- package/src/common/dom/query-selector.js +9 -0
- package/src/common/session/session-entity.js +20 -1
- package/src/common/wrap/wrap-function.js +1 -1
- package/src/features/ajax/aggregate/index.js +2 -2
- package/src/features/session_replay/aggregate/index.js +82 -34
- package/src/features/utils/feature-base.js +1 -2
- package/src/features/utils/instrument-base.js +1 -0
- package/src/loaders/api/api.js +1 -2
- package/src/loaders/api/apiAsync.js +1 -39
- package/src/loaders/configure/configure.js +1 -1
- package/src/loaders/configure/public-path.js +6 -3
- package/src/common/aggregate/aggregator.test.js +0 -107
- package/src/common/config/state/configurable.test.js +0 -73
- package/src/common/config/state/info.test.js +0 -31
- package/src/common/config/state/init.test.js +0 -28
- package/src/common/config/state/loader-config.test.js +0 -21
- package/src/common/config/state/runtime.test.js +0 -21
- package/src/common/constants/env.cdn.test.js +0 -7
- package/src/common/constants/env.npm.test.js +0 -7
- package/src/common/constants/env.test.js +0 -7
- package/src/common/constants/runtime.test.js +0 -176
- package/src/common/deny-list/deny-list.test.js +0 -104
- package/src/common/drain/drain.test.js +0 -74
- package/src/common/event-emitter/contextual-ee.component-test.js +0 -293
- package/src/common/event-emitter/handle.test.js +0 -56
- package/src/common/event-emitter/register-handler.test.js +0 -61
- package/src/common/harvest/harvest-scheduler.test.js +0 -492
- package/src/common/harvest/harvest.test.js +0 -813
- package/src/common/ids/id.test.js +0 -92
- package/src/common/ids/unique-id.test.js +0 -58
- package/src/common/session/session-entity.component-test.js +0 -346
- package/src/common/storage/local-storage.test.js +0 -17
- package/src/common/timer/interaction-timer.component-test.js +0 -212
- package/src/common/timer/timer.test.js +0 -99
- package/src/common/timing/nav-timing.test.js +0 -161
- package/src/common/url/canonicalize-url.test.js +0 -45
- package/src/common/url/clean-url.test.js +0 -25
- package/src/common/url/encode.test.js +0 -81
- package/src/common/url/location.test.js +0 -15
- package/src/common/url/parse-url.test.js +0 -110
- package/src/common/url/protocol.test.js +0 -17
- package/src/common/util/console.test.js +0 -34
- package/src/common/util/data-size.test.js +0 -56
- package/src/common/util/feature-flags.test.js +0 -94
- package/src/common/util/get-or-set.test.js +0 -58
- package/src/common/util/invoke.test.js +0 -65
- package/src/common/util/map-own.test.js +0 -52
- package/src/common/util/obfuscate.component-test.js +0 -173
- package/src/common/util/stringify.test.js +0 -49
- package/src/common/util/submit-data.test.js +0 -183
- package/src/common/util/traverse.test.js +0 -50
- package/src/common/vitals/cumulative-layout-shift.test.js +0 -71
- package/src/common/vitals/first-contentful-paint.test.js +0 -124
- package/src/common/vitals/first-input-delay.test.js +0 -88
- package/src/common/vitals/first-paint.test.js +0 -127
- package/src/common/vitals/interaction-to-next-paint.test.js +0 -74
- package/src/common/vitals/largest-contentful-paint.test.js +0 -94
- package/src/common/vitals/long-task.test.js +0 -122
- package/src/common/vitals/time-to-first-byte.test.js +0 -147
- package/src/common/vitals/vital-metric.test.js +0 -171
- package/src/common/wrap/wrap-promise.component-test.js +0 -110
- package/src/features/ajax/instrument/distributed-tracing.test.js +0 -375
- package/src/features/jserrors/aggregate/canonical-function-name.test.js +0 -13
- package/src/features/jserrors/aggregate/compute-stack-trace.test.js +0 -414
- package/src/features/jserrors/aggregate/format-stack-trace.test.js +0 -39
- package/src/features/jserrors/aggregate/string-hash-code.test.js +0 -12
- package/src/features/metrics/aggregate/framework-detection.test.js +0 -332
- package/src/features/page_view_timing/aggregate/index.component-test.js +0 -86
- package/src/features/session_replay/aggregate/index.component-test.js +0 -317
- package/src/features/spa/aggregate/interaction-node.test.js +0 -17
- package/src/features/utils/agent-session.test.js +0 -194
- package/src/features/utils/aggregate-base.test.js +0 -123
- package/src/features/utils/feature-base.test.js +0 -45
- package/src/features/utils/handler-cache.test.js +0 -72
- package/src/features/utils/instrument-base.test.js +0 -216
- package/src/features/utils/lazy-feature-loader.test.js +0 -37
- package/src/loaders/api/api.component-test.js +0 -45
- package/src/loaders/api/api.test.js +0 -85
- package/src/loaders/api/apiAsync.test.js +0 -17
|
@@ -10,19 +10,13 @@ var _contextualEe = require("../../common/event-emitter/contextual-ee");
|
|
|
10
10
|
var _handle = require("../../common/event-emitter/handle");
|
|
11
11
|
var _registerHandler = require("../../common/event-emitter/register-handler");
|
|
12
12
|
var _invoke = require("../../common/util/invoke");
|
|
13
|
-
var submitData = _interopRequireWildcard(require("../../common/util/submit-data"));
|
|
14
|
-
var _runtime = require("../../common/constants/runtime");
|
|
15
13
|
var _constants = require("../../features/metrics/constants");
|
|
16
|
-
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
17
|
-
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
18
14
|
function setAPI(agentIdentifier) {
|
|
19
15
|
var instanceEE = _contextualEe.ee.get(agentIdentifier);
|
|
20
|
-
var cycle = 0;
|
|
21
16
|
var api = {
|
|
22
17
|
finished: (0, _invoke.single)(finished),
|
|
23
18
|
setErrorHandler,
|
|
24
19
|
addToTrace,
|
|
25
|
-
inlineHit,
|
|
26
20
|
addRelease
|
|
27
21
|
};
|
|
28
22
|
|
|
@@ -58,37 +52,6 @@ function setAPI(agentIdentifier) {
|
|
|
58
52
|
};
|
|
59
53
|
(0, _handle.handle)('bstApi', [report], undefined, _features.FEATURE_NAMES.sessionTrace, instanceEE);
|
|
60
54
|
}
|
|
61
|
-
|
|
62
|
-
// NREUM.inlineHit(requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime)
|
|
63
|
-
//
|
|
64
|
-
// requestName - the 'web page' name or service name
|
|
65
|
-
// queueTime - the amount of time spent in the app tier queue
|
|
66
|
-
// appTime - the amount of time spent in the application code
|
|
67
|
-
// totalBackEndTime - the total roundtrip time of the remote service call
|
|
68
|
-
// domTime - the time spent processing the result of the service call (or user defined)
|
|
69
|
-
// frontEndTime - the time spent rendering the result of the service call (or user defined)
|
|
70
|
-
function inlineHit(t, requestName, queueTime, appTime, totalBackEndTime, domTime, frontEndTime) {
|
|
71
|
-
if (!_runtime.isBrowserScope) return;
|
|
72
|
-
requestName = window.encodeURIComponent(requestName);
|
|
73
|
-
cycle += 1;
|
|
74
|
-
const agentInfo = (0, _config.getInfo)(agentIdentifier);
|
|
75
|
-
if (!agentInfo.beacon) return;
|
|
76
|
-
const agentInit = (0, _config.getConfiguration)(agentIdentifier);
|
|
77
|
-
const scheme = agentInit.ssl === false ? 'http' : 'https';
|
|
78
|
-
const beacon = agentInit.proxy.beacon || agentInfo.beacon;
|
|
79
|
-
let url = "".concat(scheme, "://").concat(beacon, "/1/").concat(agentInfo.licenseKey);
|
|
80
|
-
url += '?a=' + agentInfo.applicationID + '&';
|
|
81
|
-
url += 't=' + requestName + '&';
|
|
82
|
-
url += 'qt=' + ~~queueTime + '&';
|
|
83
|
-
url += 'ap=' + ~~appTime + '&';
|
|
84
|
-
url += 'be=' + ~~totalBackEndTime + '&';
|
|
85
|
-
url += 'dc=' + ~~domTime + '&';
|
|
86
|
-
url += 'fe=' + ~~frontEndTime + '&';
|
|
87
|
-
url += 'c=' + cycle;
|
|
88
|
-
submitData.xhr({
|
|
89
|
-
url
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
55
|
function setErrorHandler(t, handler) {
|
|
93
56
|
(0, _config.getRuntime)(agentIdentifier).onerror = handler;
|
|
94
57
|
}
|
|
@@ -47,7 +47,7 @@ function configure(agentIdentifier) {
|
|
|
47
47
|
if (!alreadySetOnce) {
|
|
48
48
|
alreadySetOnce = true;
|
|
49
49
|
if (updatedInit.proxy.assets) {
|
|
50
|
-
(0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets
|
|
50
|
+
(0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets);
|
|
51
51
|
internalTrafficList.push(updatedInit.proxy.assets);
|
|
52
52
|
}
|
|
53
53
|
if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon);
|
|
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.redefinePublicPath = void 0;
|
|
7
7
|
// Set the default CDN or remote for fetching the assets; NPM shouldn't change this var.
|
|
8
8
|
|
|
9
|
-
const redefinePublicPath =
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const redefinePublicPath = urlString => {
|
|
10
|
+
const isOrigin = urlString.startsWith('http');
|
|
11
|
+
// Input is not expected to end in a slash, but webpack concats as-is, so one is inserted.
|
|
12
|
+
urlString += '/';
|
|
13
|
+
// If there's no existing HTTP scheme, the secure protocol is prepended by default.
|
|
14
|
+
__webpack_public_path__ = isOrigin ? urlString : 'https://' + urlString; // eslint-disable-line
|
|
12
15
|
};
|
|
13
16
|
exports.redefinePublicPath = redefinePublicPath;
|
|
@@ -4,4 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import '../polyfills.js';
|
|
7
|
-
import '
|
|
7
|
+
import { Agent } from '../../loaders/agent';
|
|
8
|
+
import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument';
|
|
9
|
+
import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument';
|
|
10
|
+
import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument';
|
|
11
|
+
new Agent({
|
|
12
|
+
features: [InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentMetrics],
|
|
13
|
+
loaderType: 'lite-polyfills'
|
|
14
|
+
});
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file Creates a version of the "
|
|
2
|
+
* @file Creates a version of the "PRO" agent loader with [core-js]{@link https://github.com/zloirock/core-js}
|
|
3
3
|
* polyfills for pre-ES6 browsers and IE 11.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import '../polyfills.js';
|
|
7
|
-
import '
|
|
7
|
+
import { Agent } from '../../loaders/agent';
|
|
8
|
+
import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument';
|
|
9
|
+
import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument';
|
|
10
|
+
import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument';
|
|
11
|
+
import { Instrument as InstrumentErrors } from '../../features/jserrors/instrument';
|
|
12
|
+
import { Instrument as InstrumentXhr } from '../../features/ajax/instrument';
|
|
13
|
+
import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument';
|
|
14
|
+
import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument';
|
|
15
|
+
new Agent({
|
|
16
|
+
features: [InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentXhr, InstrumentMetrics, InstrumentPageAction, InstrumentErrors],
|
|
17
|
+
loaderType: 'pro-polyfills'
|
|
18
|
+
});
|
|
@@ -4,4 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import '../polyfills.js';
|
|
7
|
-
import '
|
|
7
|
+
import { Agent } from '../../loaders/agent';
|
|
8
|
+
import { Instrument as InstrumentPageViewEvent } from '../../features/page_view_event/instrument';
|
|
9
|
+
import { Instrument as InstrumentPageViewTiming } from '../../features/page_view_timing/instrument';
|
|
10
|
+
import { Instrument as InstrumentMetrics } from '../../features/metrics/instrument';
|
|
11
|
+
import { Instrument as InstrumentErrors } from '../../features/jserrors/instrument';
|
|
12
|
+
import { Instrument as InstrumentXhr } from '../../features/ajax/instrument';
|
|
13
|
+
import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument';
|
|
14
|
+
import { Instrument as InstrumentSpa } from '../../features/spa/instrument';
|
|
15
|
+
import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument';
|
|
16
|
+
new Agent({
|
|
17
|
+
features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa],
|
|
18
|
+
loaderType: 'spa-polyfills'
|
|
19
|
+
});
|
|
@@ -1,13 +1,33 @@
|
|
|
1
|
+
import { isValidSelector } from '../../dom/query-selector';
|
|
1
2
|
import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../../session/constants';
|
|
3
|
+
import { warn } from '../../util/console';
|
|
2
4
|
import { gosNREUMInitializedAgents } from '../../window/nreum';
|
|
3
5
|
import { getModeledObject } from './configurable';
|
|
4
6
|
const model = () => {
|
|
5
7
|
const hiddenState = {
|
|
8
|
+
mask_selector: '*',
|
|
6
9
|
block_selector: '[data-nr-block]',
|
|
7
10
|
mask_input_options: {
|
|
8
|
-
|
|
11
|
+
color: false,
|
|
12
|
+
date: false,
|
|
13
|
+
'datetime-local': false,
|
|
14
|
+
email: false,
|
|
15
|
+
month: false,
|
|
16
|
+
number: false,
|
|
17
|
+
range: false,
|
|
18
|
+
search: false,
|
|
19
|
+
tel: false,
|
|
20
|
+
text: false,
|
|
21
|
+
time: false,
|
|
22
|
+
url: false,
|
|
23
|
+
week: false,
|
|
24
|
+
// unify textarea and select element with text input
|
|
25
|
+
textarea: false,
|
|
26
|
+
select: false,
|
|
27
|
+
password: true // This will be enforced to always be true in the setter
|
|
9
28
|
}
|
|
10
29
|
};
|
|
30
|
+
|
|
11
31
|
return {
|
|
12
32
|
proxy: {
|
|
13
33
|
assets: undefined,
|
|
@@ -83,8 +103,15 @@ const model = () => {
|
|
|
83
103
|
error_sampling_rate: 50,
|
|
84
104
|
// float from 0 - 100
|
|
85
105
|
// recording config settings
|
|
86
|
-
mask_text_selector: '*',
|
|
87
106
|
mask_all_inputs: true,
|
|
107
|
+
// this has a getter/setter to facilitate validation of the selectors
|
|
108
|
+
get mask_text_selector() {
|
|
109
|
+
return hiddenState.mask_selector;
|
|
110
|
+
},
|
|
111
|
+
set mask_text_selector(val) {
|
|
112
|
+
if (isValidSelector(val)) hiddenState.mask_selector = val + ',[data-nr-mask]';else if (val === null) hiddenState.mask_selector = val; // null is acceptable, which completely disables the behavior
|
|
113
|
+
else warn('An invalid session_replay.mask_selector was provided and will not be used', val);
|
|
114
|
+
},
|
|
88
115
|
// these properties only have getters because they are enforcable constants and should error if someone tries to override them
|
|
89
116
|
get block_class() {
|
|
90
117
|
return 'nr-block';
|
|
@@ -101,17 +128,17 @@ const model = () => {
|
|
|
101
128
|
return hiddenState.block_selector;
|
|
102
129
|
},
|
|
103
130
|
set block_selector(val) {
|
|
104
|
-
hiddenState.block_selector += ",".concat(val);
|
|
131
|
+
if (isValidSelector(val)) hiddenState.block_selector += ",".concat(val);else if (val !== '') warn('An invalid session_replay.block_selector was provided and will not be used', val);
|
|
105
132
|
},
|
|
106
133
|
// password: must always be present and true no matter what customer sets
|
|
107
134
|
get mask_input_options() {
|
|
108
135
|
return hiddenState.mask_input_options;
|
|
109
136
|
},
|
|
110
137
|
set mask_input_options(val) {
|
|
111
|
-
hiddenState.mask_input_options = {
|
|
138
|
+
if (val && typeof val === 'object') hiddenState.mask_input_options = {
|
|
112
139
|
...val,
|
|
113
140
|
password: true
|
|
114
|
-
};
|
|
141
|
+
};else warn('An invalid session_replay.mask_input_option was provided and will not be used', val);
|
|
115
142
|
}
|
|
116
143
|
},
|
|
117
144
|
spa: {
|
|
@@ -11,6 +11,7 @@ import { getModeledObject } from '../config/state/configurable';
|
|
|
11
11
|
import { handle } from '../event-emitter/handle';
|
|
12
12
|
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../features/metrics/constants';
|
|
13
13
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
14
|
+
import { windowAddEventListener } from '../event-listener/event-listener-opts';
|
|
14
15
|
export const MODE = {
|
|
15
16
|
OFF: 0,
|
|
16
17
|
FULL: 1,
|
|
@@ -24,13 +25,19 @@ const model = {
|
|
|
24
25
|
expiresAt: 0,
|
|
25
26
|
updatedAt: Date.now(),
|
|
26
27
|
sessionReplay: MODE.OFF,
|
|
28
|
+
sessionReplaySentFirstChunk: false,
|
|
27
29
|
sessionTraceMode: MODE.OFF,
|
|
28
30
|
custom: {}
|
|
29
31
|
};
|
|
30
32
|
export const SESSION_EVENTS = {
|
|
31
33
|
PAUSE: 'session-pause',
|
|
32
34
|
RESET: 'session-reset',
|
|
33
|
-
RESUME: 'session-resume'
|
|
35
|
+
RESUME: 'session-resume',
|
|
36
|
+
UPDATE: 'session-update'
|
|
37
|
+
};
|
|
38
|
+
export const SESSION_EVENT_TYPES = {
|
|
39
|
+
SAME_TAB: 'same-tab',
|
|
40
|
+
CROSS_TAB: 'cross-tab'
|
|
34
41
|
};
|
|
35
42
|
export class SessionEntity {
|
|
36
43
|
/**
|
|
@@ -57,6 +64,15 @@ export class SessionEntity {
|
|
|
57
64
|
this.ee = ee.get(agentIdentifier);
|
|
58
65
|
wrapEvents(this.ee);
|
|
59
66
|
this.setup(opts);
|
|
67
|
+
if (isBrowserScope) {
|
|
68
|
+
windowAddEventListener('storage', event => {
|
|
69
|
+
if (event.key === this.lookupKey) {
|
|
70
|
+
const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue;
|
|
71
|
+
this.sync(obj);
|
|
72
|
+
this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
77
|
setup(_ref) {
|
|
62
78
|
let {
|
|
@@ -190,6 +206,7 @@ export class SessionEntity {
|
|
|
190
206
|
//
|
|
191
207
|
// TODO - compression would need happen here if we decide to do it
|
|
192
208
|
this.storage.set(this.lookupKey, stringify(this.state));
|
|
209
|
+
this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state]);
|
|
193
210
|
return data;
|
|
194
211
|
} catch (e) {
|
|
195
212
|
// storage is inaccessible
|
|
@@ -210,5 +210,5 @@ function copy(from, to, emitter) {
|
|
|
210
210
|
* @returns {boolean} Whether the passed function is ineligible to be wrapped.
|
|
211
211
|
*/
|
|
212
212
|
function notWrappable(fn) {
|
|
213
|
-
return !(fn && fn
|
|
213
|
+
return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
|
|
214
214
|
}
|
|
@@ -76,7 +76,6 @@ export class Aggregate extends AggregateBase {
|
|
|
76
76
|
} else {
|
|
77
77
|
hash = stringify([params.status, params.host, params.pathname]);
|
|
78
78
|
}
|
|
79
|
-
handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee);
|
|
80
79
|
|
|
81
80
|
// store as metric
|
|
82
81
|
aggregator.store('xhr', hash, params, metrics);
|
|
@@ -90,6 +89,7 @@ export class Aggregate extends AggregateBase {
|
|
|
90
89
|
}
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
92
|
+
handle('bstXhrAgg', ['xhr', hash, params, metrics], undefined, FEATURE_NAMES.sessionTrace, ee);
|
|
93
93
|
var xhrContext = this;
|
|
94
94
|
var event = {
|
|
95
95
|
method: params.method,
|
|
@@ -15,16 +15,43 @@ import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
|
|
|
15
15
|
import { FEATURE_NAME } from '../constants';
|
|
16
16
|
import { stringify } from '../../../common/util/stringify';
|
|
17
17
|
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
|
|
18
|
-
import { SESSION_EVENTS, MODE } from '../../../common/session/session-entity';
|
|
18
|
+
import { SESSION_EVENTS, MODE, SESSION_EVENT_TYPES } from '../../../common/session/session-entity';
|
|
19
19
|
import { AggregateBase } from '../../utils/aggregate-base';
|
|
20
20
|
import { sharedChannel } from '../../../common/constants/shared-channel';
|
|
21
21
|
import { obj as encodeObj } from '../../../common/url/encode';
|
|
22
22
|
import { warn } from '../../../common/util/console';
|
|
23
23
|
import { globalScope } from '../../../common/constants/runtime';
|
|
24
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
24
25
|
|
|
25
26
|
// would be better to get this dynamically in some way
|
|
26
27
|
export const RRWEB_VERSION = '2.0.0-alpha.8';
|
|
27
28
|
export const AVG_COMPRESSION = 0.12;
|
|
29
|
+
export const RRWEB_EVENT_TYPES = {
|
|
30
|
+
DomContentLoaded: 0,
|
|
31
|
+
Load: 1,
|
|
32
|
+
FullSnapshot: 2,
|
|
33
|
+
IncrementalSnapshot: 3,
|
|
34
|
+
Meta: 4,
|
|
35
|
+
Custom: 5
|
|
36
|
+
};
|
|
37
|
+
const ABORT_REASONS = {
|
|
38
|
+
RESET: {
|
|
39
|
+
message: 'Session was reset',
|
|
40
|
+
sm: 'Reset'
|
|
41
|
+
},
|
|
42
|
+
IMPORT: {
|
|
43
|
+
message: 'Recorder failed to import',
|
|
44
|
+
sm: 'Import'
|
|
45
|
+
},
|
|
46
|
+
TOO_MANY: {
|
|
47
|
+
message: '429: Too Many Requests',
|
|
48
|
+
sm: 'Too-Many'
|
|
49
|
+
},
|
|
50
|
+
TOO_BIG: {
|
|
51
|
+
message: 'Payload was too large',
|
|
52
|
+
sm: 'Too-Big'
|
|
53
|
+
}
|
|
54
|
+
};
|
|
28
55
|
let recorder, gzipper, u8;
|
|
29
56
|
|
|
30
57
|
/** Vortex caps payload sizes at 1MB */
|
|
@@ -58,8 +85,6 @@ export class Aggregate extends AggregateBase {
|
|
|
58
85
|
/** can shut off efforts to compress the data */
|
|
59
86
|
this.shouldCompress = true;
|
|
60
87
|
|
|
61
|
-
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
62
|
-
this.isFirstChunk = false;
|
|
63
88
|
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
64
89
|
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
65
90
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
@@ -73,16 +98,7 @@ export class Aggregate extends AggregateBase {
|
|
|
73
98
|
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
74
99
|
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
75
100
|
*/
|
|
76
|
-
this.
|
|
77
|
-
event: {
|
|
78
|
-
first: undefined,
|
|
79
|
-
last: undefined
|
|
80
|
-
},
|
|
81
|
-
cycle: {
|
|
82
|
-
first: undefined,
|
|
83
|
-
last: undefined
|
|
84
|
-
}
|
|
85
|
-
};
|
|
101
|
+
this.cycleTimestamp = undefined;
|
|
86
102
|
|
|
87
103
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
88
104
|
this.payloadBytesEstimation = 0;
|
|
@@ -96,7 +112,7 @@ export class Aggregate extends AggregateBase {
|
|
|
96
112
|
if (shouldSetup) {
|
|
97
113
|
// 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.
|
|
98
114
|
this.ee.on(SESSION_EVENTS.RESET, () => {
|
|
99
|
-
this.abort();
|
|
115
|
+
this.abort(ABORT_REASONS.RESET);
|
|
100
116
|
});
|
|
101
117
|
|
|
102
118
|
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
@@ -105,9 +121,19 @@ export class Aggregate extends AggregateBase {
|
|
|
105
121
|
});
|
|
106
122
|
// 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.
|
|
107
123
|
this.ee.on(SESSION_EVENTS.RESUME, () => {
|
|
124
|
+
// if the mode changed on a different tab, it needs to update this instance to match
|
|
125
|
+
const {
|
|
126
|
+
session
|
|
127
|
+
} = getRuntime(this.agentIdentifier);
|
|
128
|
+
this.mode = session.state.sessionReplay;
|
|
108
129
|
if (!this.initialized || this.mode === MODE.OFF) return;
|
|
109
130
|
this.startRecording();
|
|
110
131
|
});
|
|
132
|
+
this.ee.on(SESSION_EVENTS.UPDATE, (type, data) => {
|
|
133
|
+
if (!this.initialized || this.blocked || type !== SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
134
|
+
if (this.mode !== MODE.OFF && data.sessionReplay === MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
|
|
135
|
+
this.mode = data.sessionReplay;
|
|
136
|
+
});
|
|
111
137
|
|
|
112
138
|
// Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
|
|
113
139
|
this.scheduler = new HarvestScheduler('browser/blobs', {
|
|
@@ -123,7 +149,7 @@ export class Aggregate extends AggregateBase {
|
|
|
123
149
|
this.hasError = true;
|
|
124
150
|
this.errorNoticed = true;
|
|
125
151
|
// run once
|
|
126
|
-
if (this.mode === MODE.ERROR) {
|
|
152
|
+
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
127
153
|
this.mode = MODE.FULL;
|
|
128
154
|
// if the error was noticed AFTER the recorder was already imported....
|
|
129
155
|
if (recorder && this.initialized) {
|
|
@@ -183,7 +209,7 @@ export class Aggregate extends AggregateBase {
|
|
|
183
209
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
184
210
|
recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
|
|
185
211
|
} catch (err) {
|
|
186
|
-
return this.abort();
|
|
212
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
187
213
|
}
|
|
188
214
|
|
|
189
215
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
@@ -206,7 +232,6 @@ export class Aggregate extends AggregateBase {
|
|
|
206
232
|
this.shouldCompress = false;
|
|
207
233
|
}
|
|
208
234
|
this.startRecording();
|
|
209
|
-
this.isFirstChunk = !!session.isNew;
|
|
210
235
|
this.syncWithSessionManager({
|
|
211
236
|
sessionReplay: this.mode
|
|
212
237
|
});
|
|
@@ -221,14 +246,39 @@ export class Aggregate extends AggregateBase {
|
|
|
221
246
|
this.scheduler.opts.gzip = false;
|
|
222
247
|
}
|
|
223
248
|
// TODO -- Gracefully handle the buffer for retries.
|
|
249
|
+
const {
|
|
250
|
+
session
|
|
251
|
+
} = getRuntime(this.agentIdentifier);
|
|
252
|
+
if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
253
|
+
sessionReplaySentFirstChunk: true
|
|
254
|
+
});
|
|
224
255
|
this.clearBuffer();
|
|
225
256
|
return [payload];
|
|
226
257
|
}
|
|
227
258
|
getHarvestContents() {
|
|
228
259
|
const agentRuntime = getRuntime(this.agentIdentifier);
|
|
229
260
|
const info = getInfo(this.agentIdentifier);
|
|
230
|
-
|
|
231
|
-
|
|
261
|
+
|
|
262
|
+
// do not let the last node be a meta node, since this NEEDS to precede a snapshot
|
|
263
|
+
// we will manually inject it later if we find a payload that is missing a meta node
|
|
264
|
+
const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
|
|
265
|
+
if (payloadEndsWithMeta) {
|
|
266
|
+
this.lastMeta = this.events[this.events.length - 1];
|
|
267
|
+
this.events = this.events.slice(0, this.events.length - 1);
|
|
268
|
+
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
272
|
+
// we will manually inject it if this happens
|
|
273
|
+
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
|
|
274
|
+
if (payloadStartsWithFullSnapshot) {
|
|
275
|
+
this.hasMeta = true;
|
|
276
|
+
this.events.unshift(this.lastMeta);
|
|
277
|
+
}
|
|
278
|
+
const firstEventTimestamp = this.events[0]?.timestamp;
|
|
279
|
+
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
|
|
280
|
+
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
|
|
281
|
+
const lastTimestamp = lastEventTimestamp || getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
|
|
232
282
|
return {
|
|
233
283
|
qs: {
|
|
234
284
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -242,12 +292,13 @@ export class Aggregate extends AggregateBase {
|
|
|
242
292
|
'replay.firstTimestamp': firstTimestamp,
|
|
243
293
|
'replay.lastTimestamp': lastTimestamp,
|
|
244
294
|
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
295
|
+
'replay.nodes': this.events.length,
|
|
245
296
|
agentVersion: agentRuntime.version,
|
|
246
297
|
session: agentRuntime.session.state.value,
|
|
247
298
|
hasMeta: this.hasMeta,
|
|
248
299
|
hasSnapshot: this.hasSnapshot,
|
|
249
300
|
hasError: this.hasError,
|
|
250
|
-
isFirstChunk:
|
|
301
|
+
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
251
302
|
decompressedBytes: this.payloadBytesEstimation,
|
|
252
303
|
'nr.rrweb.version': RRWEB_VERSION
|
|
253
304
|
}, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
|
|
@@ -259,7 +310,7 @@ export class Aggregate extends AggregateBase {
|
|
|
259
310
|
onHarvestFinished(result) {
|
|
260
311
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
261
312
|
if (result.status === 429) {
|
|
262
|
-
this.abort();
|
|
313
|
+
this.abort(ABORT_REASONS.TOO_MANY);
|
|
263
314
|
}
|
|
264
315
|
if (this.blocked) this.scheduler.stopTimer(true);
|
|
265
316
|
}
|
|
@@ -267,7 +318,6 @@ export class Aggregate extends AggregateBase {
|
|
|
267
318
|
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
268
319
|
clearBuffer() {
|
|
269
320
|
this.events = [];
|
|
270
|
-
this.isFirstChunk = false;
|
|
271
321
|
this.hasSnapshot = false;
|
|
272
322
|
this.hasMeta = false;
|
|
273
323
|
this.hasError = false;
|
|
@@ -279,7 +329,7 @@ export class Aggregate extends AggregateBase {
|
|
|
279
329
|
startRecording() {
|
|
280
330
|
if (!recorder) {
|
|
281
331
|
warn('Recording library was never imported');
|
|
282
|
-
return this.abort();
|
|
332
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
283
333
|
}
|
|
284
334
|
this.clearTimestamps();
|
|
285
335
|
// set the fallbacks as early as possible
|
|
@@ -315,7 +365,7 @@ export class Aggregate extends AggregateBase {
|
|
|
315
365
|
|
|
316
366
|
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
317
367
|
store(event, isCheckout) {
|
|
318
|
-
this.setTimestamps(
|
|
368
|
+
this.setTimestamps();
|
|
319
369
|
if (this.blocked) return;
|
|
320
370
|
const eventBytes = stringify(event).length;
|
|
321
371
|
/** The estimated size of the payload after compression */
|
|
@@ -323,7 +373,7 @@ export class Aggregate extends AggregateBase {
|
|
|
323
373
|
// Vortex will block payloads at a certain size, we might as well not send.
|
|
324
374
|
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
325
375
|
this.clearBuffer();
|
|
326
|
-
return this.abort();
|
|
376
|
+
return this.abort(ABORT_REASONS.TOO_BIG);
|
|
327
377
|
}
|
|
328
378
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
329
379
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
@@ -334,19 +384,13 @@ export class Aggregate extends AggregateBase {
|
|
|
334
384
|
}
|
|
335
385
|
|
|
336
386
|
// meta event
|
|
337
|
-
if (event.type ===
|
|
387
|
+
if (event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
338
388
|
this.hasMeta = true;
|
|
339
389
|
this.lastMeta = event;
|
|
340
390
|
}
|
|
341
391
|
// snapshot event
|
|
342
|
-
if (event.type ===
|
|
392
|
+
if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
343
393
|
this.hasSnapshot = true;
|
|
344
|
-
// small chance that the meta event got separated from its matching snapshot across payload harvests
|
|
345
|
-
// it needs to precede the snapshot, so shove it in first.
|
|
346
|
-
if (!this.hasMeta) {
|
|
347
|
-
this.events.push(this.lastMeta);
|
|
348
|
-
this.hasMeta = true;
|
|
349
|
-
}
|
|
350
394
|
}
|
|
351
395
|
this.events.push(event);
|
|
352
396
|
this.payloadBytesEstimation += eventBytes;
|
|
@@ -364,26 +408,12 @@ export class Aggregate extends AggregateBase {
|
|
|
364
408
|
if (!recorder) return;
|
|
365
409
|
recorder.takeFullSnapshot();
|
|
366
410
|
}
|
|
367
|
-
setTimestamps(
|
|
411
|
+
setTimestamps() {
|
|
368
412
|
// fallbacks if timestamps cannot be derived from rrweb events
|
|
369
|
-
this.
|
|
370
|
-
if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
|
|
371
|
-
// timestamps based on rrweb events
|
|
372
|
-
if (!event || !event.timestamp) return;
|
|
373
|
-
if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
|
|
374
|
-
this.timestamp.event.last = event.timestamp;
|
|
413
|
+
if (!this.cycleTimestamp) this.cycleTimestamp = getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
|
|
375
414
|
}
|
|
376
415
|
clearTimestamps() {
|
|
377
|
-
this.
|
|
378
|
-
event: {
|
|
379
|
-
first: undefined,
|
|
380
|
-
last: undefined
|
|
381
|
-
},
|
|
382
|
-
cycle: {
|
|
383
|
-
first: undefined,
|
|
384
|
-
last: undefined
|
|
385
|
-
}
|
|
386
|
-
};
|
|
416
|
+
this.cycleTimestamp = undefined;
|
|
387
417
|
}
|
|
388
418
|
|
|
389
419
|
/** Estimate the payload size */
|
|
@@ -395,6 +425,9 @@ export class Aggregate extends AggregateBase {
|
|
|
395
425
|
|
|
396
426
|
/** Abort the feature, once aborted it will not resume */
|
|
397
427
|
abort() {
|
|
428
|
+
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
429
|
+
warn("SR aborted -- ".concat(reason.message));
|
|
430
|
+
this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
|
|
398
431
|
this.blocked = true;
|
|
399
432
|
this.mode = MODE.OFF;
|
|
400
433
|
this.stopRecording();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getRuntime } from '../../common/config/config';
|
|
2
1
|
import { ee } from '../../common/event-emitter/contextual-ee';
|
|
3
2
|
export class FeatureBase {
|
|
4
3
|
constructor(agentIdentifier, aggregator, featureName) {
|
|
@@ -7,7 +6,7 @@ export class FeatureBase {
|
|
|
7
6
|
/** @type {Aggregator} */
|
|
8
7
|
this.aggregator = aggregator;
|
|
9
8
|
/** @type {ContextualEE} */
|
|
10
|
-
this.ee = ee.get(agentIdentifier
|
|
9
|
+
this.ee = ee.get(agentIdentifier);
|
|
11
10
|
/** @type {string} */
|
|
12
11
|
this.featureName = featureName;
|
|
13
12
|
/**
|
|
@@ -114,6 +114,7 @@ export class InstrumentBase extends FeatureBase {
|
|
|
114
114
|
warn("Downloading and initializing ".concat(this.featureName, " failed..."), e);
|
|
115
115
|
this.abortHandler?.(); // undo any important alterations made to the page
|
|
116
116
|
// not supported yet but nice to do: "abort" this agent's EE for this feature specifically
|
|
117
|
+
drain(this.agentIdentifier, this.featureName);
|
|
117
118
|
loadedSuccessfully(false);
|
|
118
119
|
}
|
|
119
120
|
};
|
|
@@ -17,7 +17,7 @@ export const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stor
|
|
|
17
17
|
|
|
18
18
|
export function setTopLevelCallers() {
|
|
19
19
|
const nr = gosCDN();
|
|
20
|
-
const funcs = ['setErrorHandler', 'finished', 'addToTrace', '
|
|
20
|
+
const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
|
|
21
21
|
funcs.forEach(f => {
|
|
22
22
|
nr[f] = function () {
|
|
23
23
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
@@ -44,7 +44,7 @@ export function setAPI(agentIdentifier, forceDrain) {
|
|
|
44
44
|
const apiInterface = {};
|
|
45
45
|
var instanceEE = ee.get(agentIdentifier);
|
|
46
46
|
var tracerEE = instanceEE.get('tracer');
|
|
47
|
-
var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', '
|
|
47
|
+
var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
|
|
48
48
|
var prefix = 'api-';
|
|
49
49
|
var spaPrefix = prefix + 'ixn-';
|
|
50
50
|
|