@newrelic/browser-agent 1.242.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/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- 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 -54
- package/dist/cjs/features/utils/feature-base.js +1 -2
- 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/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- 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 -54
- package/dist/esm/features/utils/feature-base.js +1 -2
- 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/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/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/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 +81 -37
- package/src/features/utils/feature-base.js +1 -2
- 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 -68
- 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/dom/query-selector.test.js +0 -24
- 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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.SessionEntity = exports.SESSION_EVENTS = exports.MODE = void 0;
|
|
6
|
+
exports.SessionEntity = exports.SESSION_EVENT_TYPES = exports.SESSION_EVENTS = exports.MODE = void 0;
|
|
7
7
|
var _uniqueId = require("../ids/unique-id");
|
|
8
8
|
var _console = require("../util/console");
|
|
9
9
|
var _stringify = require("../util/stringify");
|
|
@@ -17,6 +17,7 @@ var _configurable = require("../config/state/configurable");
|
|
|
17
17
|
var _handle = require("../event-emitter/handle");
|
|
18
18
|
var _constants2 = require("../../features/metrics/constants");
|
|
19
19
|
var _features = require("../../loaders/features/features");
|
|
20
|
+
var _eventListenerOpts = require("../event-listener/event-listener-opts");
|
|
20
21
|
const MODE = {
|
|
21
22
|
OFF: 0,
|
|
22
23
|
FULL: 1,
|
|
@@ -31,15 +32,22 @@ const model = {
|
|
|
31
32
|
expiresAt: 0,
|
|
32
33
|
updatedAt: Date.now(),
|
|
33
34
|
sessionReplay: MODE.OFF,
|
|
35
|
+
sessionReplaySentFirstChunk: false,
|
|
34
36
|
sessionTraceMode: MODE.OFF,
|
|
35
37
|
custom: {}
|
|
36
38
|
};
|
|
37
39
|
const SESSION_EVENTS = {
|
|
38
40
|
PAUSE: 'session-pause',
|
|
39
41
|
RESET: 'session-reset',
|
|
40
|
-
RESUME: 'session-resume'
|
|
42
|
+
RESUME: 'session-resume',
|
|
43
|
+
UPDATE: 'session-update'
|
|
41
44
|
};
|
|
42
45
|
exports.SESSION_EVENTS = SESSION_EVENTS;
|
|
46
|
+
const SESSION_EVENT_TYPES = {
|
|
47
|
+
SAME_TAB: 'same-tab',
|
|
48
|
+
CROSS_TAB: 'cross-tab'
|
|
49
|
+
};
|
|
50
|
+
exports.SESSION_EVENT_TYPES = SESSION_EVENT_TYPES;
|
|
43
51
|
class SessionEntity {
|
|
44
52
|
/**
|
|
45
53
|
* Create a self-managing Session Entity. This entity is scoped to the agent identifier which triggered it, allowing for multiple simultaneous session objects to exist.
|
|
@@ -65,6 +73,15 @@ class SessionEntity {
|
|
|
65
73
|
this.ee = _contextualEe.ee.get(agentIdentifier);
|
|
66
74
|
(0, _wrap.wrapEvents)(this.ee);
|
|
67
75
|
this.setup(opts);
|
|
76
|
+
if (_runtime.isBrowserScope) {
|
|
77
|
+
(0, _eventListenerOpts.windowAddEventListener)('storage', event => {
|
|
78
|
+
if (event.key === this.lookupKey) {
|
|
79
|
+
const obj = typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue;
|
|
80
|
+
this.sync(obj);
|
|
81
|
+
this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.CROSS_TAB, this.state]);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
68
85
|
}
|
|
69
86
|
setup(_ref) {
|
|
70
87
|
let {
|
|
@@ -198,6 +215,7 @@ class SessionEntity {
|
|
|
198
215
|
//
|
|
199
216
|
// TODO - compression would need happen here if we decide to do it
|
|
200
217
|
this.storage.set(this.lookupKey, (0, _stringify.stringify)(this.state));
|
|
218
|
+
this.ee.emit(SESSION_EVENTS.UPDATE, [SESSION_EVENT_TYPES.SAME_TAB, this.state]);
|
|
201
219
|
return data;
|
|
202
220
|
} catch (e) {
|
|
203
221
|
// storage is inaccessible
|
|
@@ -218,5 +218,5 @@ function copy(from, to, emitter) {
|
|
|
218
218
|
* @returns {boolean} Whether the passed function is ineligible to be wrapped.
|
|
219
219
|
*/
|
|
220
220
|
function notWrappable(fn) {
|
|
221
|
-
return !(fn && fn
|
|
221
|
+
return !(fn && typeof fn === 'function' && fn.apply && !fn[flag]);
|
|
222
222
|
}
|
|
@@ -83,7 +83,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
83
83
|
} else {
|
|
84
84
|
hash = (0, _stringify.stringify)([params.status, params.host, params.pathname]);
|
|
85
85
|
}
|
|
86
|
-
(0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
|
|
87
86
|
|
|
88
87
|
// store as metric
|
|
89
88
|
aggregator.store('xhr', hash, params, metrics);
|
|
@@ -97,6 +96,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
97
96
|
}
|
|
98
97
|
return;
|
|
99
98
|
}
|
|
99
|
+
(0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
|
|
100
100
|
var xhrContext = this;
|
|
101
101
|
var event = {
|
|
102
102
|
method: params.method,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.RRWEB_VERSION = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.Aggregate = exports.AVG_COMPRESSION = void 0;
|
|
6
|
+
exports.RRWEB_VERSION = exports.RRWEB_EVENT_TYPES = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.Aggregate = exports.AVG_COMPRESSION = void 0;
|
|
7
7
|
var _registerHandler = require("../../../common/event-emitter/register-handler");
|
|
8
8
|
var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
|
|
9
9
|
var _constants = require("../constants");
|
|
@@ -16,7 +16,6 @@ var _encode = require("../../../common/url/encode");
|
|
|
16
16
|
var _console = require("../../../common/util/console");
|
|
17
17
|
var _runtime = require("../../../common/constants/runtime");
|
|
18
18
|
var _constants2 = require("../../metrics/constants");
|
|
19
|
-
var _features = require("../../../loaders/features/features");
|
|
20
19
|
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); }
|
|
21
20
|
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; } /*
|
|
22
21
|
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
@@ -33,6 +32,33 @@ const RRWEB_VERSION = '2.0.0-alpha.8';
|
|
|
33
32
|
exports.RRWEB_VERSION = RRWEB_VERSION;
|
|
34
33
|
const AVG_COMPRESSION = 0.12;
|
|
35
34
|
exports.AVG_COMPRESSION = AVG_COMPRESSION;
|
|
35
|
+
const RRWEB_EVENT_TYPES = {
|
|
36
|
+
DomContentLoaded: 0,
|
|
37
|
+
Load: 1,
|
|
38
|
+
FullSnapshot: 2,
|
|
39
|
+
IncrementalSnapshot: 3,
|
|
40
|
+
Meta: 4,
|
|
41
|
+
Custom: 5
|
|
42
|
+
};
|
|
43
|
+
exports.RRWEB_EVENT_TYPES = RRWEB_EVENT_TYPES;
|
|
44
|
+
const ABORT_REASONS = {
|
|
45
|
+
RESET: {
|
|
46
|
+
message: 'Session was reset',
|
|
47
|
+
sm: 'Reset'
|
|
48
|
+
},
|
|
49
|
+
IMPORT: {
|
|
50
|
+
message: 'Recorder failed to import',
|
|
51
|
+
sm: 'Import'
|
|
52
|
+
},
|
|
53
|
+
TOO_MANY: {
|
|
54
|
+
message: '429: Too Many Requests',
|
|
55
|
+
sm: 'Too-Many'
|
|
56
|
+
},
|
|
57
|
+
TOO_BIG: {
|
|
58
|
+
message: 'Payload was too large',
|
|
59
|
+
sm: 'Too-Big'
|
|
60
|
+
}
|
|
61
|
+
};
|
|
36
62
|
let recorder, gzipper, u8;
|
|
37
63
|
|
|
38
64
|
/** Vortex caps payload sizes at 1MB */
|
|
@@ -68,8 +94,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
68
94
|
/** can shut off efforts to compress the data */
|
|
69
95
|
this.shouldCompress = true;
|
|
70
96
|
|
|
71
|
-
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
72
|
-
this.isFirstChunk = false;
|
|
73
97
|
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
74
98
|
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
75
99
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
@@ -83,16 +107,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
83
107
|
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
84
108
|
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
85
109
|
*/
|
|
86
|
-
this.
|
|
87
|
-
event: {
|
|
88
|
-
first: undefined,
|
|
89
|
-
last: undefined
|
|
90
|
-
},
|
|
91
|
-
cycle: {
|
|
92
|
-
first: undefined,
|
|
93
|
-
last: undefined
|
|
94
|
-
}
|
|
95
|
-
};
|
|
110
|
+
this.cycleTimestamp = undefined;
|
|
96
111
|
|
|
97
112
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
98
113
|
this.payloadBytesEstimation = 0;
|
|
@@ -106,7 +121,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
106
121
|
if (shouldSetup) {
|
|
107
122
|
// 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.
|
|
108
123
|
this.ee.on(_sessionEntity.SESSION_EVENTS.RESET, () => {
|
|
109
|
-
this.abort(
|
|
124
|
+
this.abort(ABORT_REASONS.RESET);
|
|
110
125
|
});
|
|
111
126
|
|
|
112
127
|
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
@@ -115,9 +130,19 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
115
130
|
});
|
|
116
131
|
// 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.
|
|
117
132
|
this.ee.on(_sessionEntity.SESSION_EVENTS.RESUME, () => {
|
|
133
|
+
// if the mode changed on a different tab, it needs to update this instance to match
|
|
134
|
+
const {
|
|
135
|
+
session
|
|
136
|
+
} = (0, _config.getRuntime)(this.agentIdentifier);
|
|
137
|
+
this.mode = session.state.sessionReplay;
|
|
118
138
|
if (!this.initialized || this.mode === _sessionEntity.MODE.OFF) return;
|
|
119
139
|
this.startRecording();
|
|
120
140
|
});
|
|
141
|
+
this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (type, data) => {
|
|
142
|
+
if (!this.initialized || this.blocked || type !== _sessionEntity.SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
143
|
+
if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplay === _sessionEntity.MODE.OFF) this.abort('Session Entity was set to OFF on another tab');
|
|
144
|
+
this.mode = data.sessionReplay;
|
|
145
|
+
});
|
|
121
146
|
|
|
122
147
|
// Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
|
|
123
148
|
this.scheduler = new _harvestScheduler.HarvestScheduler('browser/blobs', {
|
|
@@ -133,7 +158,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
133
158
|
this.hasError = true;
|
|
134
159
|
this.errorNoticed = true;
|
|
135
160
|
// run once
|
|
136
|
-
if (this.mode === _sessionEntity.MODE.ERROR) {
|
|
161
|
+
if (this.mode === _sessionEntity.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
|
|
137
162
|
this.mode = _sessionEntity.MODE.FULL;
|
|
138
163
|
// if the error was noticed AFTER the recorder was already imported....
|
|
139
164
|
if (recorder && this.initialized) {
|
|
@@ -193,7 +218,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
193
218
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
194
219
|
recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
|
|
195
220
|
} catch (err) {
|
|
196
|
-
return this.abort(
|
|
221
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
197
222
|
}
|
|
198
223
|
|
|
199
224
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
@@ -216,7 +241,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
216
241
|
this.shouldCompress = false;
|
|
217
242
|
}
|
|
218
243
|
this.startRecording();
|
|
219
|
-
this.isFirstChunk = !!session.isNew;
|
|
220
244
|
this.syncWithSessionManager({
|
|
221
245
|
sessionReplay: this.mode
|
|
222
246
|
});
|
|
@@ -231,14 +255,39 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
231
255
|
this.scheduler.opts.gzip = false;
|
|
232
256
|
}
|
|
233
257
|
// TODO -- Gracefully handle the buffer for retries.
|
|
258
|
+
const {
|
|
259
|
+
session
|
|
260
|
+
} = (0, _config.getRuntime)(this.agentIdentifier);
|
|
261
|
+
if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
262
|
+
sessionReplaySentFirstChunk: true
|
|
263
|
+
});
|
|
234
264
|
this.clearBuffer();
|
|
235
265
|
return [payload];
|
|
236
266
|
}
|
|
237
267
|
getHarvestContents() {
|
|
238
268
|
const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
|
|
239
269
|
const info = (0, _config.getInfo)(this.agentIdentifier);
|
|
240
|
-
|
|
241
|
-
|
|
270
|
+
|
|
271
|
+
// do not let the last node be a meta node, since this NEEDS to precede a snapshot
|
|
272
|
+
// we will manually inject it later if we find a payload that is missing a meta node
|
|
273
|
+
const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
|
|
274
|
+
if (payloadEndsWithMeta) {
|
|
275
|
+
this.lastMeta = this.events[this.events.length - 1];
|
|
276
|
+
this.events = this.events.slice(0, this.events.length - 1);
|
|
277
|
+
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
281
|
+
// we will manually inject it if this happens
|
|
282
|
+
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
|
|
283
|
+
if (payloadStartsWithFullSnapshot) {
|
|
284
|
+
this.hasMeta = true;
|
|
285
|
+
this.events.unshift(this.lastMeta);
|
|
286
|
+
}
|
|
287
|
+
const firstEventTimestamp = this.events[0]?.timestamp;
|
|
288
|
+
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
|
|
289
|
+
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
|
|
290
|
+
const lastTimestamp = lastEventTimestamp || (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
|
|
242
291
|
return {
|
|
243
292
|
qs: {
|
|
244
293
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -252,12 +301,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
252
301
|
'replay.firstTimestamp': firstTimestamp,
|
|
253
302
|
'replay.lastTimestamp': lastTimestamp,
|
|
254
303
|
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
304
|
+
'replay.nodes': this.events.length,
|
|
255
305
|
agentVersion: agentRuntime.version,
|
|
256
306
|
session: agentRuntime.session.state.value,
|
|
257
307
|
hasMeta: this.hasMeta,
|
|
258
308
|
hasSnapshot: this.hasSnapshot,
|
|
259
309
|
hasError: this.hasError,
|
|
260
|
-
isFirstChunk:
|
|
310
|
+
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
261
311
|
decompressedBytes: this.payloadBytesEstimation,
|
|
262
312
|
'nr.rrweb.version': RRWEB_VERSION
|
|
263
313
|
}, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
|
|
@@ -269,7 +319,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
269
319
|
onHarvestFinished(result) {
|
|
270
320
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
271
321
|
if (result.status === 429) {
|
|
272
|
-
this.abort(
|
|
322
|
+
this.abort(ABORT_REASONS.TOO_MANY);
|
|
273
323
|
}
|
|
274
324
|
if (this.blocked) this.scheduler.stopTimer(true);
|
|
275
325
|
}
|
|
@@ -277,7 +327,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
277
327
|
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
278
328
|
clearBuffer() {
|
|
279
329
|
this.events = [];
|
|
280
|
-
this.isFirstChunk = false;
|
|
281
330
|
this.hasSnapshot = false;
|
|
282
331
|
this.hasMeta = false;
|
|
283
332
|
this.hasError = false;
|
|
@@ -289,7 +338,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
289
338
|
startRecording() {
|
|
290
339
|
if (!recorder) {
|
|
291
340
|
(0, _console.warn)('Recording library was never imported');
|
|
292
|
-
return this.abort(
|
|
341
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
293
342
|
}
|
|
294
343
|
this.clearTimestamps();
|
|
295
344
|
// set the fallbacks as early as possible
|
|
@@ -325,7 +374,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
325
374
|
|
|
326
375
|
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
327
376
|
store(event, isCheckout) {
|
|
328
|
-
this.setTimestamps(
|
|
377
|
+
this.setTimestamps();
|
|
329
378
|
if (this.blocked) return;
|
|
330
379
|
const eventBytes = (0, _stringify.stringify)(event).length;
|
|
331
380
|
/** The estimated size of the payload after compression */
|
|
@@ -333,8 +382,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
333
382
|
// Vortex will block payloads at a certain size, we might as well not send.
|
|
334
383
|
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
335
384
|
this.clearBuffer();
|
|
336
|
-
this.
|
|
337
|
-
return this.abort('Payload too big');
|
|
385
|
+
return this.abort(ABORT_REASONS.TOO_BIG);
|
|
338
386
|
}
|
|
339
387
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
340
388
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
@@ -345,19 +393,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
345
393
|
}
|
|
346
394
|
|
|
347
395
|
// meta event
|
|
348
|
-
if (event.type ===
|
|
396
|
+
if (event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
349
397
|
this.hasMeta = true;
|
|
350
398
|
this.lastMeta = event;
|
|
351
399
|
}
|
|
352
400
|
// snapshot event
|
|
353
|
-
if (event.type ===
|
|
401
|
+
if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
354
402
|
this.hasSnapshot = true;
|
|
355
|
-
// small chance that the meta event got separated from its matching snapshot across payload harvests
|
|
356
|
-
// it needs to precede the snapshot, so shove it in first.
|
|
357
|
-
if (!this.hasMeta) {
|
|
358
|
-
this.events.push(this.lastMeta);
|
|
359
|
-
this.hasMeta = true;
|
|
360
|
-
}
|
|
361
403
|
}
|
|
362
404
|
this.events.push(event);
|
|
363
405
|
this.payloadBytesEstimation += eventBytes;
|
|
@@ -375,26 +417,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
375
417
|
if (!recorder) return;
|
|
376
418
|
recorder.takeFullSnapshot();
|
|
377
419
|
}
|
|
378
|
-
setTimestamps(
|
|
420
|
+
setTimestamps() {
|
|
379
421
|
// fallbacks if timestamps cannot be derived from rrweb events
|
|
380
|
-
this.
|
|
381
|
-
if (!this.timestamp.cycle.first) this.timestamp.cycle.first = this.timestamp.cycle.last;
|
|
382
|
-
// timestamps based on rrweb events
|
|
383
|
-
if (!event || !event.timestamp) return;
|
|
384
|
-
if (!this.timestamp.event.first) this.timestamp.event.first = event.timestamp;
|
|
385
|
-
this.timestamp.event.last = event.timestamp;
|
|
422
|
+
if (!this.cycleTimestamp) this.cycleTimestamp = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
|
|
386
423
|
}
|
|
387
424
|
clearTimestamps() {
|
|
388
|
-
this.
|
|
389
|
-
event: {
|
|
390
|
-
first: undefined,
|
|
391
|
-
last: undefined
|
|
392
|
-
},
|
|
393
|
-
cycle: {
|
|
394
|
-
first: undefined,
|
|
395
|
-
last: undefined
|
|
396
|
-
}
|
|
397
|
-
};
|
|
425
|
+
this.cycleTimestamp = undefined;
|
|
398
426
|
}
|
|
399
427
|
|
|
400
428
|
/** Estimate the payload size */
|
|
@@ -405,8 +433,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
405
433
|
}
|
|
406
434
|
|
|
407
435
|
/** Abort the feature, once aborted it will not resume */
|
|
408
|
-
abort(
|
|
409
|
-
|
|
436
|
+
abort() {
|
|
437
|
+
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
438
|
+
(0, _console.warn)("SR aborted -- ".concat(reason.message));
|
|
439
|
+
this.ee.emit(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(ABORT_REASONS[reason.sm])]);
|
|
410
440
|
this.blocked = true;
|
|
411
441
|
this.mode = _sessionEntity.MODE.OFF;
|
|
412
442
|
this.stopRecording();
|
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.FeatureBase = void 0;
|
|
7
|
-
var _config = require("../../common/config/config");
|
|
8
7
|
var _contextualEe = require("../../common/event-emitter/contextual-ee");
|
|
9
8
|
class FeatureBase {
|
|
10
9
|
constructor(agentIdentifier, aggregator, featureName) {
|
|
@@ -13,7 +12,7 @@ class FeatureBase {
|
|
|
13
12
|
/** @type {Aggregator} */
|
|
14
13
|
this.aggregator = aggregator;
|
|
15
14
|
/** @type {ContextualEE} */
|
|
16
|
-
this.ee = _contextualEe.ee.get(agentIdentifier
|
|
15
|
+
this.ee = _contextualEe.ee.get(agentIdentifier);
|
|
17
16
|
/** @type {string} */
|
|
18
17
|
this.featureName = featureName;
|
|
19
18
|
/**
|
|
@@ -26,7 +26,7 @@ const CUSTOM_ATTR_GROUP = 'CUSTOM/'; // the subgroup items should be stored unde
|
|
|
26
26
|
exports.CUSTOM_ATTR_GROUP = CUSTOM_ATTR_GROUP;
|
|
27
27
|
function setTopLevelCallers() {
|
|
28
28
|
const nr = (0, _nreum.gosCDN)();
|
|
29
|
-
const funcs = ['setErrorHandler', 'finished', 'addToTrace', '
|
|
29
|
+
const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
|
|
30
30
|
funcs.forEach(f => {
|
|
31
31
|
nr[f] = function () {
|
|
32
32
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
@@ -53,7 +53,7 @@ function setAPI(agentIdentifier, forceDrain) {
|
|
|
53
53
|
const apiInterface = {};
|
|
54
54
|
var instanceEE = _contextualEe.ee.get(agentIdentifier);
|
|
55
55
|
var tracerEE = instanceEE.get('tracer');
|
|
56
|
-
var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', '
|
|
56
|
+
var asyncApiFns = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease'];
|
|
57
57
|
var prefix = 'api-';
|
|
58
58
|
var spaPrefix = prefix + 'ixn-';
|
|
59
59
|
|
|
@@ -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;
|
|
@@ -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,
|