@newrelic/browser-agent 1.242.0 → 1.243.1
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 +1472 -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 +89 -53
- 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 +88 -53
- 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 +86 -36
- 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,6 +16,7 @@ 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 _handle = require("../../../common/event-emitter/handle");
|
|
19
20
|
var _features = require("../../../loaders/features/features");
|
|
20
21
|
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
22
|
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; } /*
|
|
@@ -33,6 +34,37 @@ const RRWEB_VERSION = '2.0.0-alpha.8';
|
|
|
33
34
|
exports.RRWEB_VERSION = RRWEB_VERSION;
|
|
34
35
|
const AVG_COMPRESSION = 0.12;
|
|
35
36
|
exports.AVG_COMPRESSION = AVG_COMPRESSION;
|
|
37
|
+
const RRWEB_EVENT_TYPES = {
|
|
38
|
+
DomContentLoaded: 0,
|
|
39
|
+
Load: 1,
|
|
40
|
+
FullSnapshot: 2,
|
|
41
|
+
IncrementalSnapshot: 3,
|
|
42
|
+
Meta: 4,
|
|
43
|
+
Custom: 5
|
|
44
|
+
};
|
|
45
|
+
exports.RRWEB_EVENT_TYPES = RRWEB_EVENT_TYPES;
|
|
46
|
+
const ABORT_REASONS = {
|
|
47
|
+
RESET: {
|
|
48
|
+
message: 'Session was reset',
|
|
49
|
+
sm: 'Reset'
|
|
50
|
+
},
|
|
51
|
+
IMPORT: {
|
|
52
|
+
message: 'Recorder failed to import',
|
|
53
|
+
sm: 'Import'
|
|
54
|
+
},
|
|
55
|
+
TOO_MANY: {
|
|
56
|
+
message: '429: Too Many Requests',
|
|
57
|
+
sm: 'Too-Many'
|
|
58
|
+
},
|
|
59
|
+
TOO_BIG: {
|
|
60
|
+
message: 'Payload was too large',
|
|
61
|
+
sm: 'Too-Big'
|
|
62
|
+
},
|
|
63
|
+
CROSS_TAB: {
|
|
64
|
+
message: 'Session Entity was set to OFF on another tab',
|
|
65
|
+
sm: 'Cross-Tab'
|
|
66
|
+
}
|
|
67
|
+
};
|
|
36
68
|
let recorder, gzipper, u8;
|
|
37
69
|
|
|
38
70
|
/** Vortex caps payload sizes at 1MB */
|
|
@@ -68,8 +100,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
68
100
|
/** can shut off efforts to compress the data */
|
|
69
101
|
this.shouldCompress = true;
|
|
70
102
|
|
|
71
|
-
/** Payload metadata -- Should indicate that the payload being sent is the first of a session */
|
|
72
|
-
this.isFirstChunk = false;
|
|
73
103
|
/** Payload metadata -- Should indicate that the payload being sent has a full DOM snapshot. This can happen
|
|
74
104
|
* -- When the recording library begins recording, it starts by taking a DOM snapshot
|
|
75
105
|
* -- When visibility changes from "hidden" -> "visible", it must capture a full snapshot for the replay to work correctly across tabs
|
|
@@ -83,16 +113,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
83
113
|
/** Payload metadata -- Should indicate when a replay blob started recording. Resets each time a harvest occurs.
|
|
84
114
|
* cycle timestamps are used as fallbacks if event timestamps cannot be used
|
|
85
115
|
*/
|
|
86
|
-
this.
|
|
87
|
-
event: {
|
|
88
|
-
first: undefined,
|
|
89
|
-
last: undefined
|
|
90
|
-
},
|
|
91
|
-
cycle: {
|
|
92
|
-
first: undefined,
|
|
93
|
-
last: undefined
|
|
94
|
-
}
|
|
95
|
-
};
|
|
116
|
+
this.cycleTimestamp = undefined;
|
|
96
117
|
|
|
97
118
|
/** A value which increments with every new mutation node reported. Resets after a harvest is sent */
|
|
98
119
|
this.payloadBytesEstimation = 0;
|
|
@@ -106,7 +127,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
106
127
|
if (shouldSetup) {
|
|
107
128
|
// 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
129
|
this.ee.on(_sessionEntity.SESSION_EVENTS.RESET, () => {
|
|
109
|
-
this.abort(
|
|
130
|
+
this.abort(ABORT_REASONS.RESET);
|
|
110
131
|
});
|
|
111
132
|
|
|
112
133
|
// The SessionEntity class can emit a message indicating the session was paused (visibility change). This feature must stop recording if that occurs.
|
|
@@ -115,9 +136,19 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
115
136
|
});
|
|
116
137
|
// 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
138
|
this.ee.on(_sessionEntity.SESSION_EVENTS.RESUME, () => {
|
|
139
|
+
// if the mode changed on a different tab, it needs to update this instance to match
|
|
140
|
+
const {
|
|
141
|
+
session
|
|
142
|
+
} = (0, _config.getRuntime)(this.agentIdentifier);
|
|
143
|
+
this.mode = session.state.sessionReplay;
|
|
118
144
|
if (!this.initialized || this.mode === _sessionEntity.MODE.OFF) return;
|
|
119
145
|
this.startRecording();
|
|
120
146
|
});
|
|
147
|
+
this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (type, data) => {
|
|
148
|
+
if (!this.initialized || this.blocked || type !== _sessionEntity.SESSION_EVENT_TYPES.CROSS_TAB) return;
|
|
149
|
+
if (this.mode !== _sessionEntity.MODE.OFF && data.sessionReplay === _sessionEntity.MODE.OFF) this.abort(ABORT_REASONS.CROSS_TAB);
|
|
150
|
+
this.mode = data.sessionReplay;
|
|
151
|
+
});
|
|
121
152
|
|
|
122
153
|
// Bespoke logic for new endpoint. This will change as downstream dependencies become solidified.
|
|
123
154
|
this.scheduler = new _harvestScheduler.HarvestScheduler('browser/blobs', {
|
|
@@ -133,7 +164,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
133
164
|
this.hasError = true;
|
|
134
165
|
this.errorNoticed = true;
|
|
135
166
|
// run once
|
|
136
|
-
if (this.mode === _sessionEntity.MODE.ERROR) {
|
|
167
|
+
if (this.mode === _sessionEntity.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
|
|
137
168
|
this.mode = _sessionEntity.MODE.FULL;
|
|
138
169
|
// if the error was noticed AFTER the recorder was already imported....
|
|
139
170
|
if (recorder && this.initialized) {
|
|
@@ -193,7 +224,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
193
224
|
// Do not change the webpackChunkName or it will break the webpack nrba-chunking plugin
|
|
194
225
|
recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
|
|
195
226
|
} catch (err) {
|
|
196
|
-
return this.abort(
|
|
227
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
197
228
|
}
|
|
198
229
|
|
|
199
230
|
// FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
|
|
@@ -216,7 +247,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
216
247
|
this.shouldCompress = false;
|
|
217
248
|
}
|
|
218
249
|
this.startRecording();
|
|
219
|
-
this.isFirstChunk = !!session.isNew;
|
|
220
250
|
this.syncWithSessionManager({
|
|
221
251
|
sessionReplay: this.mode
|
|
222
252
|
});
|
|
@@ -231,14 +261,39 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
231
261
|
this.scheduler.opts.gzip = false;
|
|
232
262
|
}
|
|
233
263
|
// TODO -- Gracefully handle the buffer for retries.
|
|
264
|
+
const {
|
|
265
|
+
session
|
|
266
|
+
} = (0, _config.getRuntime)(this.agentIdentifier);
|
|
267
|
+
if (!session.state.sessionReplaySentFirstChunk) this.syncWithSessionManager({
|
|
268
|
+
sessionReplaySentFirstChunk: true
|
|
269
|
+
});
|
|
234
270
|
this.clearBuffer();
|
|
235
271
|
return [payload];
|
|
236
272
|
}
|
|
237
273
|
getHarvestContents() {
|
|
238
274
|
const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
|
|
239
275
|
const info = (0, _config.getInfo)(this.agentIdentifier);
|
|
240
|
-
|
|
241
|
-
|
|
276
|
+
|
|
277
|
+
// do not let the last node be a meta node, since this NEEDS to precede a snapshot
|
|
278
|
+
// we will manually inject it later if we find a payload that is missing a meta node
|
|
279
|
+
const payloadEndsWithMeta = this.events[this.events.length - 1]?.type === RRWEB_EVENT_TYPES.Meta;
|
|
280
|
+
if (payloadEndsWithMeta) {
|
|
281
|
+
this.lastMeta = this.events[this.events.length - 1];
|
|
282
|
+
this.events = this.events.slice(0, this.events.length - 1);
|
|
283
|
+
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
287
|
+
// we will manually inject it if this happens
|
|
288
|
+
const payloadStartsWithFullSnapshot = this.events[0]?.type === RRWEB_EVENT_TYPES.FullSnapshot;
|
|
289
|
+
if (payloadStartsWithFullSnapshot) {
|
|
290
|
+
this.hasMeta = true;
|
|
291
|
+
this.events.unshift(this.lastMeta);
|
|
292
|
+
}
|
|
293
|
+
const firstEventTimestamp = this.events[0]?.timestamp;
|
|
294
|
+
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp;
|
|
295
|
+
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
|
|
296
|
+
const lastTimestamp = lastEventTimestamp || (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
|
|
242
297
|
return {
|
|
243
298
|
qs: {
|
|
244
299
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -252,12 +307,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
252
307
|
'replay.firstTimestamp': firstTimestamp,
|
|
253
308
|
'replay.lastTimestamp': lastTimestamp,
|
|
254
309
|
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
310
|
+
'replay.nodes': this.events.length,
|
|
255
311
|
agentVersion: agentRuntime.version,
|
|
256
312
|
session: agentRuntime.session.state.value,
|
|
257
313
|
hasMeta: this.hasMeta,
|
|
258
314
|
hasSnapshot: this.hasSnapshot,
|
|
259
315
|
hasError: this.hasError,
|
|
260
|
-
isFirstChunk:
|
|
316
|
+
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
261
317
|
decompressedBytes: this.payloadBytesEstimation,
|
|
262
318
|
'nr.rrweb.version': RRWEB_VERSION
|
|
263
319
|
}, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
|
|
@@ -269,7 +325,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
269
325
|
onHarvestFinished(result) {
|
|
270
326
|
// The mutual decision for now is to stop recording and clear buffers if ingest is experiencing 429 rate limiting
|
|
271
327
|
if (result.status === 429) {
|
|
272
|
-
this.abort(
|
|
328
|
+
this.abort(ABORT_REASONS.TOO_MANY);
|
|
273
329
|
}
|
|
274
330
|
if (this.blocked) this.scheduler.stopTimer(true);
|
|
275
331
|
}
|
|
@@ -277,7 +333,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
277
333
|
/** Clears the buffer (this.events), and resets all payload metadata properties */
|
|
278
334
|
clearBuffer() {
|
|
279
335
|
this.events = [];
|
|
280
|
-
this.isFirstChunk = false;
|
|
281
336
|
this.hasSnapshot = false;
|
|
282
337
|
this.hasMeta = false;
|
|
283
338
|
this.hasError = false;
|
|
@@ -289,7 +344,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
289
344
|
startRecording() {
|
|
290
345
|
if (!recorder) {
|
|
291
346
|
(0, _console.warn)('Recording library was never imported');
|
|
292
|
-
return this.abort(
|
|
347
|
+
return this.abort(ABORT_REASONS.IMPORT);
|
|
293
348
|
}
|
|
294
349
|
this.clearTimestamps();
|
|
295
350
|
// set the fallbacks as early as possible
|
|
@@ -325,7 +380,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
325
380
|
|
|
326
381
|
/** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
|
|
327
382
|
store(event, isCheckout) {
|
|
328
|
-
this.setTimestamps(
|
|
383
|
+
this.setTimestamps();
|
|
329
384
|
if (this.blocked) return;
|
|
330
385
|
const eventBytes = (0, _stringify.stringify)(event).length;
|
|
331
386
|
/** The estimated size of the payload after compression */
|
|
@@ -333,8 +388,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
333
388
|
// Vortex will block payloads at a certain size, we might as well not send.
|
|
334
389
|
if (payloadSize > MAX_PAYLOAD_SIZE) {
|
|
335
390
|
this.clearBuffer();
|
|
336
|
-
this.
|
|
337
|
-
return this.abort('Payload too big');
|
|
391
|
+
return this.abort(ABORT_REASONS.TOO_BIG);
|
|
338
392
|
}
|
|
339
393
|
// Checkout events are flags by the recording lib that indicate a fullsnapshot was taken every n ms. These are important
|
|
340
394
|
// to help reconstruct the replay later and must be included. While waiting and buffering for errors to come through,
|
|
@@ -345,19 +399,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
345
399
|
}
|
|
346
400
|
|
|
347
401
|
// meta event
|
|
348
|
-
if (event.type ===
|
|
402
|
+
if (event.type === RRWEB_EVENT_TYPES.Meta) {
|
|
349
403
|
this.hasMeta = true;
|
|
350
404
|
this.lastMeta = event;
|
|
351
405
|
}
|
|
352
406
|
// snapshot event
|
|
353
|
-
if (event.type ===
|
|
407
|
+
if (event.type === RRWEB_EVENT_TYPES.FullSnapshot) {
|
|
354
408
|
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
409
|
}
|
|
362
410
|
this.events.push(event);
|
|
363
411
|
this.payloadBytesEstimation += eventBytes;
|
|
@@ -375,26 +423,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
375
423
|
if (!recorder) return;
|
|
376
424
|
recorder.takeFullSnapshot();
|
|
377
425
|
}
|
|
378
|
-
setTimestamps(
|
|
426
|
+
setTimestamps() {
|
|
379
427
|
// 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;
|
|
428
|
+
if (!this.cycleTimestamp) this.cycleTimestamp = (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
|
|
386
429
|
}
|
|
387
430
|
clearTimestamps() {
|
|
388
|
-
this.
|
|
389
|
-
event: {
|
|
390
|
-
first: undefined,
|
|
391
|
-
last: undefined
|
|
392
|
-
},
|
|
393
|
-
cycle: {
|
|
394
|
-
first: undefined,
|
|
395
|
-
last: undefined
|
|
396
|
-
}
|
|
397
|
-
};
|
|
431
|
+
this.cycleTimestamp = undefined;
|
|
398
432
|
}
|
|
399
433
|
|
|
400
434
|
/** Estimate the payload size */
|
|
@@ -405,8 +439,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
405
439
|
}
|
|
406
440
|
|
|
407
441
|
/** Abort the feature, once aborted it will not resume */
|
|
408
|
-
abort(
|
|
409
|
-
|
|
442
|
+
abort() {
|
|
443
|
+
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
444
|
+
(0, _console.warn)("SR aborted -- ".concat(reason.message));
|
|
445
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["SessionReplay/Abort/".concat(reason.sm)], undefined, _features.FEATURE_NAMES.metrics, this.ee);
|
|
410
446
|
this.blocked = true;
|
|
411
447
|
this.mode = _sessionEntity.MODE.OFF;
|
|
412
448
|
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,
|