@newrelic/browser-agent 1.247.0 → 1.249.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 +26 -0
- package/dist/cjs/common/config/state/init.js +2 -2
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/ids/unique-id.js +4 -3
- package/dist/cjs/features/session_replay/aggregate/index.js +15 -5
- package/dist/cjs/features/session_trace/aggregate/index.js +1 -0
- package/dist/cjs/features/spa/aggregate/index.js +5 -0
- package/dist/cjs/loaders/agent-base.js +18 -13
- package/dist/esm/common/config/state/init.js +2 -2
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/ids/unique-id.js +4 -3
- package/dist/esm/features/session_replay/aggregate/index.js +14 -4
- package/dist/esm/features/session_trace/aggregate/index.js +1 -0
- package/dist/esm/features/spa/aggregate/index.js +5 -0
- package/dist/esm/loaders/agent-base.js +18 -13
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/ids/unique-id.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/loaders/agent-base.d.ts +2 -1
- package/dist/types/loaders/agent-base.d.ts.map +1 -1
- package/package.json +1 -5
- package/src/common/config/state/init.js +5 -3
- package/src/common/ids/unique-id.js +4 -3
- package/src/features/session_replay/aggregate/index.js +12 -4
- package/src/features/session_trace/aggregate/index.js +2 -0
- package/src/features/spa/aggregate/index.js +8 -0
- package/src/loaders/agent-base.js +18 -13
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.249.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.248.0...v1.249.0) (2023-12-14)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Allow empty string for Session Replay masking value ([#831](https://github.com/newrelic/newrelic-browser-agent/issues/831)) ([34f837f](https://github.com/newrelic/newrelic-browser-agent/commit/34f837f65ab31a67b821f125e20e80d39d7790a9))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* Fix API Warning Messages ([#830](https://github.com/newrelic/newrelic-browser-agent/issues/830)) ([2b13a0f](https://github.com/newrelic/newrelic-browser-agent/commit/2b13a0fdfad529dc1cfff43506e28473498ce8a1))
|
|
17
|
+
* loader missing sub-resource integrity hashes ([#837](https://github.com/newrelic/newrelic-browser-agent/issues/837)) ([a9b6f2e](https://github.com/newrelic/newrelic-browser-agent/commit/a9b6f2e578b1684dd50f8eb491251c03eca88a12))
|
|
18
|
+
* traceids not random when using `webcrypto` ([#825](https://github.com/newrelic/newrelic-browser-agent/issues/825)) ([e264acf](https://github.com/newrelic/newrelic-browser-agent/commit/e264acfbff2cacc93fae88daea70be3c1e006f90))
|
|
19
|
+
|
|
20
|
+
## [1.248.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.247.0...v1.248.0) (2023-11-16)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* Report enduser.id with Session Replay ([#815](https://github.com/newrelic/newrelic-browser-agent/issues/815)) ([8f5446d](https://github.com/newrelic/newrelic-browser-agent/commit/8f5446d1f7679f6a5ea0ba90eb082d1d4deb0d93))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* Fix issue with errors forcefully triggering session traces ([#819](https://github.com/newrelic/newrelic-browser-agent/issues/819)) ([3872c35](https://github.com/newrelic/newrelic-browser-agent/commit/3872c35a173f76644b663df5ca0474971451b7cf))
|
|
31
|
+
|
|
6
32
|
## [1.247.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.246.1...v1.247.0) (2023-11-14)
|
|
7
33
|
|
|
8
34
|
|
|
@@ -11,6 +11,7 @@ var _constants = require("../../session/constants");
|
|
|
11
11
|
var _console = require("../../util/console");
|
|
12
12
|
var _nreum = require("../../window/nreum");
|
|
13
13
|
var _configurable = require("./configurable");
|
|
14
|
+
const nrMask = '[data-nr-mask]';
|
|
14
15
|
const model = () => {
|
|
15
16
|
const hiddenState = {
|
|
16
17
|
mask_selector: '*',
|
|
@@ -124,8 +125,7 @@ const model = () => {
|
|
|
124
125
|
return hiddenState.mask_selector;
|
|
125
126
|
},
|
|
126
127
|
set mask_text_selector(val) {
|
|
127
|
-
if ((0, _querySelector.isValidSelector)(val)) hiddenState.mask_selector = val
|
|
128
|
-
else (0, _console.warn)('An invalid session_replay.mask_selector was provided and will not be used', val);
|
|
128
|
+
if ((0, _querySelector.isValidSelector)(val)) hiddenState.mask_selector = "".concat(val, ",").concat(nrMask);else if (val === '' || val === null) hiddenState.mask_selector = nrMask;else (0, _console.warn)('An invalid session_replay.mask_selector was provided. \'*\' will be used.', val);
|
|
129
129
|
},
|
|
130
130
|
// these properties only have getters because they are enforcable constants and should error if someone tries to override them
|
|
131
131
|
get block_class() {
|
|
@@ -50,12 +50,13 @@ function generateUuid() {
|
|
|
50
50
|
let randomValueTable;
|
|
51
51
|
let randomValueIndex = 0;
|
|
52
52
|
if (crypto && crypto.getRandomValues) {
|
|
53
|
+
// For a UUID, we only need 30 characters since two characters are pre-defined
|
|
53
54
|
// eslint-disable-next-line
|
|
54
|
-
randomValueTable = crypto.getRandomValues(new Uint8Array(
|
|
55
|
+
randomValueTable = crypto.getRandomValues(new Uint8Array(30));
|
|
55
56
|
}
|
|
56
57
|
return uuidv4Template.split('').map(templateInput => {
|
|
57
58
|
if (templateInput === 'x') {
|
|
58
|
-
return getRandomValue(randomValueTable, ++
|
|
59
|
+
return getRandomValue(randomValueTable, randomValueIndex++).toString(16);
|
|
59
60
|
} else if (templateInput === 'y') {
|
|
60
61
|
// this is the uuid variant per spec (8, 9, a, b)
|
|
61
62
|
// % 4, then shift to get values 8-11
|
|
@@ -78,7 +79,7 @@ function generateRandomHexString(length) {
|
|
|
78
79
|
let randomValueIndex = 0;
|
|
79
80
|
if (crypto && crypto.getRandomValues) {
|
|
80
81
|
// eslint-disable-next-line
|
|
81
|
-
randomValueTable = crypto.getRandomValues(new Uint8Array(
|
|
82
|
+
randomValueTable = crypto.getRandomValues(new Uint8Array(length));
|
|
82
83
|
}
|
|
83
84
|
const chars = [];
|
|
84
85
|
for (var i = 0; i < length; i++) {
|
|
@@ -71,8 +71,10 @@ const MAX_PAYLOAD_SIZE = 1000000;
|
|
|
71
71
|
/** Unloading caps around 64kb */
|
|
72
72
|
exports.MAX_PAYLOAD_SIZE = MAX_PAYLOAD_SIZE;
|
|
73
73
|
const IDEAL_PAYLOAD_SIZE = 64000;
|
|
74
|
-
/**
|
|
74
|
+
/** Reserved room for query param attrs */
|
|
75
75
|
exports.IDEAL_PAYLOAD_SIZE = IDEAL_PAYLOAD_SIZE;
|
|
76
|
+
const QUERY_PARAM_PADDING = 5000;
|
|
77
|
+
/** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
|
|
76
78
|
const CHECKOUT_MS = {
|
|
77
79
|
[_sessionEntity.MODE.ERROR]: 15000,
|
|
78
80
|
[_sessionEntity.MODE.FULL]: 300000,
|
|
@@ -298,6 +300,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
298
300
|
getHarvestContents() {
|
|
299
301
|
const agentRuntime = (0, _config.getRuntime)(this.agentIdentifier);
|
|
300
302
|
const info = (0, _config.getInfo)(this.agentIdentifier);
|
|
303
|
+
const endUserId = info.jsAttributes?.['enduser.id'];
|
|
301
304
|
if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events];
|
|
302
305
|
|
|
303
306
|
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
@@ -330,6 +333,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
330
333
|
app_id: info.applicationID,
|
|
331
334
|
protocol_version: '0',
|
|
332
335
|
attributes: (0, _encode.obj)({
|
|
336
|
+
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
337
|
+
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
333
338
|
...(this.shouldCompress && {
|
|
334
339
|
content_encoding: 'gzip'
|
|
335
340
|
}),
|
|
@@ -347,8 +352,13 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
347
352
|
hasError: this.hasError,
|
|
348
353
|
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
349
354
|
decompressedBytes: this.payloadBytesEstimation,
|
|
350
|
-
'rrweb.version': _env.RRWEB_VERSION
|
|
351
|
-
|
|
355
|
+
'rrweb.version': _env.RRWEB_VERSION,
|
|
356
|
+
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
357
|
+
...(endUserId && {
|
|
358
|
+
'enduser.id': endUserId
|
|
359
|
+
})
|
|
360
|
+
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
361
|
+
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
352
362
|
},
|
|
353
363
|
|
|
354
364
|
body: this.events
|
|
@@ -470,8 +480,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
470
480
|
/** Estimate the payload size */
|
|
471
481
|
getPayloadSize() {
|
|
472
482
|
let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
473
|
-
// the
|
|
474
|
-
return this.estimateCompression(this.payloadBytesEstimation + newBytes) +
|
|
483
|
+
// the query param padding constant gives us some padding for the other metadata to be safely injected
|
|
484
|
+
return this.estimateCompression(this.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING;
|
|
475
485
|
}
|
|
476
486
|
|
|
477
487
|
/**
|
|
@@ -102,6 +102,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
102
102
|
* "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
|
|
103
103
|
*/
|
|
104
104
|
const switchToFull = () => {
|
|
105
|
+
if (this.agentRuntime?.session?.state?.sessionReplayMode !== _sessionEntity.MODE.FULL) return;
|
|
105
106
|
if (mostRecentModeKnown !== _sessionEntity.MODE.FULL) {
|
|
106
107
|
const prevMode = mostRecentModeKnown;
|
|
107
108
|
mostRecentModeKnown = _sessionEntity.MODE.FULL;
|
|
@@ -23,6 +23,8 @@ var _firstContentfulPaint = require("../../../common/vitals/first-contentful-pai
|
|
|
23
23
|
var _firstPaint = require("../../../common/vitals/first-paint");
|
|
24
24
|
var _bundleId = require("../../../common/ids/bundle-id");
|
|
25
25
|
var _runtime = require("../../../common/constants/runtime");
|
|
26
|
+
var _handle = require("../../../common/event-emitter/handle");
|
|
27
|
+
var _constants2 = require("../../metrics/constants");
|
|
26
28
|
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); }
|
|
27
29
|
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; }
|
|
28
30
|
/*
|
|
@@ -672,6 +674,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
|
|
|
672
674
|
}
|
|
673
675
|
baseEE.emit('interactionSaved', [interaction]);
|
|
674
676
|
state.interactionsToHarvest.push(interaction);
|
|
677
|
+
let smCategory = 'RouteChange';
|
|
678
|
+
if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad';else if (interaction.root?.attrs?.trigger === 'api') smCategory = 'Custom';
|
|
679
|
+
(0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ["Spa/Interaction/".concat(smCategory, "/Duration/Ms"), Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, _features.FEATURE_NAMES.metrics, baseEE);
|
|
675
680
|
scheduler.scheduleHarvest(0);
|
|
676
681
|
}
|
|
677
682
|
function isEnabled() {
|
|
@@ -8,6 +8,11 @@ var _console = require("../common/util/console");
|
|
|
8
8
|
/* eslint-disable n/handle-callback-err */
|
|
9
9
|
|
|
10
10
|
class AgentBase {
|
|
11
|
+
/** Generates a generic warning message with the api name injected */
|
|
12
|
+
#warnMessage(api) {
|
|
13
|
+
return "Call to agent api ".concat(api, " failed. The agent is not currently initialized.");
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
/**
|
|
12
17
|
* Reports a browser PageAction event along with a name and optional attributes.
|
|
13
18
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/addpageaction/}
|
|
@@ -15,7 +20,7 @@ class AgentBase {
|
|
|
15
20
|
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}. The key is reported as its own PageAction attribute with the specified values.
|
|
16
21
|
*/
|
|
17
22
|
addPageAction(name, attributes) {
|
|
18
|
-
(0, _console.warn)('
|
|
23
|
+
(0, _console.warn)(this.#warnMessage('addPageAction'));
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
/**
|
|
@@ -25,7 +30,7 @@ class AgentBase {
|
|
|
25
30
|
* @param {string} [host] Default is http://custom.transaction. Typically set host to your site's domain URI.
|
|
26
31
|
*/
|
|
27
32
|
setPageViewName(name, host) {
|
|
28
|
-
(0, _console.warn)('
|
|
33
|
+
(0, _console.warn)(this.#warnMessage('setPageViewName'));
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
@@ -36,7 +41,7 @@ class AgentBase {
|
|
|
36
41
|
* @param {boolean} [persist] Default false. f set to true, the name-value pair will also be set into the browser's storage API. Then on the following instrumented pages that load within the same session, the pair will be re-applied as a custom attribute.
|
|
37
42
|
*/
|
|
38
43
|
setCustomAttribute(name, value, persist) {
|
|
39
|
-
(0, _console.warn)('
|
|
44
|
+
(0, _console.warn)(this.#warnMessage('setCustomAttribute'));
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
/**
|
|
@@ -46,7 +51,7 @@ class AgentBase {
|
|
|
46
51
|
* @param {object} [customAttributes] An object containing name/value pairs representing custom attributes.
|
|
47
52
|
*/
|
|
48
53
|
noticeError(error, customAttributes) {
|
|
49
|
-
(0, _console.warn)('
|
|
54
|
+
(0, _console.warn)(this.#warnMessage('noticeError'));
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/**
|
|
@@ -55,7 +60,7 @@ class AgentBase {
|
|
|
55
60
|
* @param {string|null} value A string identifier for the end-user, useful for tying all browser events to specific users. The value parameter does not have to be unique. If IDs should be unique, the caller is responsible for that validation. Passing a null value unsets any existing user ID.
|
|
56
61
|
*/
|
|
57
62
|
setUserId(value) {
|
|
58
|
-
(0, _console.warn)('
|
|
63
|
+
(0, _console.warn)(this.#warnMessage('setUserId'));
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
/**
|
|
@@ -67,7 +72,7 @@ class AgentBase {
|
|
|
67
72
|
* have to be unique. Passing a null value unsets any existing value.
|
|
68
73
|
*/
|
|
69
74
|
setApplicationVersion(value) {
|
|
70
|
-
(0, _console.warn)('
|
|
75
|
+
(0, _console.warn)(this.#warnMessage('setApplicationVersion'));
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
/**
|
|
@@ -76,16 +81,16 @@ class AgentBase {
|
|
|
76
81
|
* @param {(error: Error|string) => boolean | { group: string }} callback When an error occurs, the callback is called with the error object as a parameter. The callback will be called with each error, so it is not specific to one error.
|
|
77
82
|
*/
|
|
78
83
|
setErrorHandler(callback) {
|
|
79
|
-
(0, _console.warn)('
|
|
84
|
+
(0, _console.warn)(this.#warnMessage('setErrorHandler'));
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
/**
|
|
83
|
-
* Records an additional time point as "finished" in a session trace
|
|
88
|
+
* Records an additional time point as "finished" in a session trace and adds a page action.
|
|
84
89
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/finished/}
|
|
85
90
|
* @param {number} [timeStamp] Defaults to the current time of the call. If used, this marks the time that the page is "finished" according to your own criteria.
|
|
86
91
|
*/
|
|
87
92
|
finished(timeStamp) {
|
|
88
|
-
(0, _console.warn)('
|
|
93
|
+
(0, _console.warn)(this.#warnMessage('finished'));
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/**
|
|
@@ -95,7 +100,7 @@ class AgentBase {
|
|
|
95
100
|
* @param {string} id The ID or version of this release; for example, a version number, build number from your CI environment, GitHub SHA, GUID, or a hash of the contents.
|
|
96
101
|
*/
|
|
97
102
|
addRelease(name, id) {
|
|
98
|
-
(0, _console.warn)('
|
|
103
|
+
(0, _console.warn)(this.#warnMessage('addRelease'));
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
/**
|
|
@@ -104,7 +109,7 @@ class AgentBase {
|
|
|
104
109
|
* @param {string|string[]} [featureNames] The name(s) of the features to start. If no name(s) are passed, all features will be started
|
|
105
110
|
*/
|
|
106
111
|
start(featureNames) {
|
|
107
|
-
(0, _console.warn)('
|
|
112
|
+
(0, _console.warn)(this.#warnMessage('start'));
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
/**
|
|
@@ -113,7 +118,7 @@ class AgentBase {
|
|
|
113
118
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
114
119
|
*/
|
|
115
120
|
recordReplay() {
|
|
116
|
-
(0, _console.warn)('
|
|
121
|
+
(0, _console.warn)(this.#warnMessage('recordReplay'));
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
/**
|
|
@@ -123,7 +128,7 @@ class AgentBase {
|
|
|
123
128
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
124
129
|
*/
|
|
125
130
|
pauseReplay() {
|
|
126
|
-
(0, _console.warn)('
|
|
131
|
+
(0, _console.warn)(this.#warnMessage('pauseReplay'));
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
exports.AgentBase = AgentBase;
|
|
@@ -3,6 +3,7 @@ import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../../session/constants
|
|
|
3
3
|
import { warn } from '../../util/console';
|
|
4
4
|
import { getNREUMInitializedAgent } from '../../window/nreum';
|
|
5
5
|
import { getModeledObject } from './configurable';
|
|
6
|
+
const nrMask = '[data-nr-mask]';
|
|
6
7
|
const model = () => {
|
|
7
8
|
const hiddenState = {
|
|
8
9
|
mask_selector: '*',
|
|
@@ -116,8 +117,7 @@ const model = () => {
|
|
|
116
117
|
return hiddenState.mask_selector;
|
|
117
118
|
},
|
|
118
119
|
set mask_text_selector(val) {
|
|
119
|
-
if (isValidSelector(val)) hiddenState.mask_selector = val
|
|
120
|
-
else warn('An invalid session_replay.mask_selector was provided and will not be used', val);
|
|
120
|
+
if (isValidSelector(val)) hiddenState.mask_selector = "".concat(val, ",").concat(nrMask);else if (val === '' || val === null) hiddenState.mask_selector = nrMask;else warn('An invalid session_replay.mask_selector was provided. \'*\' will be used.', val);
|
|
121
121
|
},
|
|
122
122
|
// these properties only have getters because they are enforcable constants and should error if someone tries to override them
|
|
123
123
|
get block_class() {
|
|
@@ -41,12 +41,13 @@ export function generateUuid() {
|
|
|
41
41
|
let randomValueTable;
|
|
42
42
|
let randomValueIndex = 0;
|
|
43
43
|
if (crypto && crypto.getRandomValues) {
|
|
44
|
+
// For a UUID, we only need 30 characters since two characters are pre-defined
|
|
44
45
|
// eslint-disable-next-line
|
|
45
|
-
randomValueTable = crypto.getRandomValues(new Uint8Array(
|
|
46
|
+
randomValueTable = crypto.getRandomValues(new Uint8Array(30));
|
|
46
47
|
}
|
|
47
48
|
return uuidv4Template.split('').map(templateInput => {
|
|
48
49
|
if (templateInput === 'x') {
|
|
49
|
-
return getRandomValue(randomValueTable, ++
|
|
50
|
+
return getRandomValue(randomValueTable, randomValueIndex++).toString(16);
|
|
50
51
|
} else if (templateInput === 'y') {
|
|
51
52
|
// this is the uuid variant per spec (8, 9, a, b)
|
|
52
53
|
// % 4, then shift to get values 8-11
|
|
@@ -69,7 +70,7 @@ export function generateRandomHexString(length) {
|
|
|
69
70
|
let randomValueIndex = 0;
|
|
70
71
|
if (crypto && crypto.getRandomValues) {
|
|
71
72
|
// eslint-disable-next-line
|
|
72
|
-
randomValueTable = crypto.getRandomValues(new Uint8Array(
|
|
73
|
+
randomValueTable = crypto.getRandomValues(new Uint8Array(length));
|
|
73
74
|
}
|
|
74
75
|
const chars = [];
|
|
75
76
|
for (var i = 0; i < length; i++) {
|
|
@@ -63,6 +63,8 @@ let recorder, gzipper, u8;
|
|
|
63
63
|
export const MAX_PAYLOAD_SIZE = 1000000;
|
|
64
64
|
/** Unloading caps around 64kb */
|
|
65
65
|
export const IDEAL_PAYLOAD_SIZE = 64000;
|
|
66
|
+
/** Reserved room for query param attrs */
|
|
67
|
+
const QUERY_PARAM_PADDING = 5000;
|
|
66
68
|
/** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
|
|
67
69
|
const CHECKOUT_MS = {
|
|
68
70
|
[MODE.ERROR]: 15000,
|
|
@@ -289,6 +291,7 @@ export class Aggregate extends AggregateBase {
|
|
|
289
291
|
getHarvestContents() {
|
|
290
292
|
const agentRuntime = getRuntime(this.agentIdentifier);
|
|
291
293
|
const info = getInfo(this.agentIdentifier);
|
|
294
|
+
const endUserId = info.jsAttributes?.['enduser.id'];
|
|
292
295
|
if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events];
|
|
293
296
|
|
|
294
297
|
// do not let the first node be a full snapshot node, since this NEEDS to be preceded by a meta node
|
|
@@ -321,6 +324,8 @@ export class Aggregate extends AggregateBase {
|
|
|
321
324
|
app_id: info.applicationID,
|
|
322
325
|
protocol_version: '0',
|
|
323
326
|
attributes: encodeObj({
|
|
327
|
+
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
328
|
+
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
324
329
|
...(this.shouldCompress && {
|
|
325
330
|
content_encoding: 'gzip'
|
|
326
331
|
}),
|
|
@@ -338,8 +343,13 @@ export class Aggregate extends AggregateBase {
|
|
|
338
343
|
hasError: this.hasError,
|
|
339
344
|
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
340
345
|
decompressedBytes: this.payloadBytesEstimation,
|
|
341
|
-
'rrweb.version': RRWEB_VERSION
|
|
342
|
-
|
|
346
|
+
'rrweb.version': RRWEB_VERSION,
|
|
347
|
+
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
348
|
+
...(endUserId && {
|
|
349
|
+
'enduser.id': endUserId
|
|
350
|
+
})
|
|
351
|
+
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
352
|
+
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
343
353
|
},
|
|
344
354
|
|
|
345
355
|
body: this.events
|
|
@@ -461,8 +471,8 @@ export class Aggregate extends AggregateBase {
|
|
|
461
471
|
/** Estimate the payload size */
|
|
462
472
|
getPayloadSize() {
|
|
463
473
|
let newBytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
464
|
-
// the
|
|
465
|
-
return this.estimateCompression(this.payloadBytesEstimation + newBytes) +
|
|
474
|
+
// the query param padding constant gives us some padding for the other metadata to be safely injected
|
|
475
|
+
return this.estimateCompression(this.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING;
|
|
466
476
|
}
|
|
467
477
|
|
|
468
478
|
/**
|
|
@@ -95,6 +95,7 @@ export class Aggregate extends AggregateBase {
|
|
|
95
95
|
* "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
|
|
96
96
|
*/
|
|
97
97
|
const switchToFull = () => {
|
|
98
|
+
if (this.agentRuntime?.session?.state?.sessionReplayMode !== MODE.FULL) return;
|
|
98
99
|
if (mostRecentModeKnown !== MODE.FULL) {
|
|
99
100
|
const prevMode = mostRecentModeKnown;
|
|
100
101
|
mostRecentModeKnown = MODE.FULL;
|
|
@@ -22,6 +22,8 @@ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-pa
|
|
|
22
22
|
import { firstPaint } from '../../../common/vitals/first-paint';
|
|
23
23
|
import { bundleId } from '../../../common/ids/bundle-id';
|
|
24
24
|
import { loadedAsDeferredBrowserScript } from '../../../common/constants/runtime';
|
|
25
|
+
import { handle } from '../../../common/event-emitter/handle';
|
|
26
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
25
27
|
const {
|
|
26
28
|
FEATURE_NAME,
|
|
27
29
|
INTERACTION_EVENTS,
|
|
@@ -663,6 +665,9 @@ export class Aggregate extends AggregateBase {
|
|
|
663
665
|
}
|
|
664
666
|
baseEE.emit('interactionSaved', [interaction]);
|
|
665
667
|
state.interactionsToHarvest.push(interaction);
|
|
668
|
+
let smCategory = 'RouteChange';
|
|
669
|
+
if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad';else if (interaction.root?.attrs?.trigger === 'api') smCategory = 'Custom';
|
|
670
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, ["Spa/Interaction/".concat(smCategory, "/Duration/Ms"), Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, FEATURE_NAMES.metrics, baseEE);
|
|
666
671
|
scheduler.scheduleHarvest(0);
|
|
667
672
|
}
|
|
668
673
|
function isEnabled() {
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { warn } from '../common/util/console';
|
|
4
4
|
export class AgentBase {
|
|
5
|
+
/** Generates a generic warning message with the api name injected */
|
|
6
|
+
#warnMessage(api) {
|
|
7
|
+
return "Call to agent api ".concat(api, " failed. The agent is not currently initialized.");
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Reports a browser PageAction event along with a name and optional attributes.
|
|
7
12
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/addpageaction/}
|
|
@@ -9,7 +14,7 @@ export class AgentBase {
|
|
|
9
14
|
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}. The key is reported as its own PageAction attribute with the specified values.
|
|
10
15
|
*/
|
|
11
16
|
addPageAction(name, attributes) {
|
|
12
|
-
warn('
|
|
17
|
+
warn(this.#warnMessage('addPageAction'));
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -19,7 +24,7 @@ export class AgentBase {
|
|
|
19
24
|
* @param {string} [host] Default is http://custom.transaction. Typically set host to your site's domain URI.
|
|
20
25
|
*/
|
|
21
26
|
setPageViewName(name, host) {
|
|
22
|
-
warn('
|
|
27
|
+
warn(this.#warnMessage('setPageViewName'));
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
/**
|
|
@@ -30,7 +35,7 @@ export class AgentBase {
|
|
|
30
35
|
* @param {boolean} [persist] Default false. f set to true, the name-value pair will also be set into the browser's storage API. Then on the following instrumented pages that load within the same session, the pair will be re-applied as a custom attribute.
|
|
31
36
|
*/
|
|
32
37
|
setCustomAttribute(name, value, persist) {
|
|
33
|
-
warn('
|
|
38
|
+
warn(this.#warnMessage('setCustomAttribute'));
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -40,7 +45,7 @@ export class AgentBase {
|
|
|
40
45
|
* @param {object} [customAttributes] An object containing name/value pairs representing custom attributes.
|
|
41
46
|
*/
|
|
42
47
|
noticeError(error, customAttributes) {
|
|
43
|
-
warn('
|
|
48
|
+
warn(this.#warnMessage('noticeError'));
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
/**
|
|
@@ -49,7 +54,7 @@ export class AgentBase {
|
|
|
49
54
|
* @param {string|null} value A string identifier for the end-user, useful for tying all browser events to specific users. The value parameter does not have to be unique. If IDs should be unique, the caller is responsible for that validation. Passing a null value unsets any existing user ID.
|
|
50
55
|
*/
|
|
51
56
|
setUserId(value) {
|
|
52
|
-
warn('
|
|
57
|
+
warn(this.#warnMessage('setUserId'));
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -61,7 +66,7 @@ export class AgentBase {
|
|
|
61
66
|
* have to be unique. Passing a null value unsets any existing value.
|
|
62
67
|
*/
|
|
63
68
|
setApplicationVersion(value) {
|
|
64
|
-
warn('
|
|
69
|
+
warn(this.#warnMessage('setApplicationVersion'));
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
@@ -70,16 +75,16 @@ export class AgentBase {
|
|
|
70
75
|
* @param {(error: Error|string) => boolean | { group: string }} callback When an error occurs, the callback is called with the error object as a parameter. The callback will be called with each error, so it is not specific to one error.
|
|
71
76
|
*/
|
|
72
77
|
setErrorHandler(callback) {
|
|
73
|
-
warn('
|
|
78
|
+
warn(this.#warnMessage('setErrorHandler'));
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
|
-
* Records an additional time point as "finished" in a session trace
|
|
82
|
+
* Records an additional time point as "finished" in a session trace and adds a page action.
|
|
78
83
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/finished/}
|
|
79
84
|
* @param {number} [timeStamp] Defaults to the current time of the call. If used, this marks the time that the page is "finished" according to your own criteria.
|
|
80
85
|
*/
|
|
81
86
|
finished(timeStamp) {
|
|
82
|
-
warn('
|
|
87
|
+
warn(this.#warnMessage('finished'));
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
/**
|
|
@@ -89,7 +94,7 @@ export class AgentBase {
|
|
|
89
94
|
* @param {string} id The ID or version of this release; for example, a version number, build number from your CI environment, GitHub SHA, GUID, or a hash of the contents.
|
|
90
95
|
*/
|
|
91
96
|
addRelease(name, id) {
|
|
92
|
-
warn('
|
|
97
|
+
warn(this.#warnMessage('addRelease'));
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
/**
|
|
@@ -98,7 +103,7 @@ export class AgentBase {
|
|
|
98
103
|
* @param {string|string[]} [featureNames] The name(s) of the features to start. If no name(s) are passed, all features will be started
|
|
99
104
|
*/
|
|
100
105
|
start(featureNames) {
|
|
101
|
-
warn('
|
|
106
|
+
warn(this.#warnMessage('start'));
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
/**
|
|
@@ -107,7 +112,7 @@ export class AgentBase {
|
|
|
107
112
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
108
113
|
*/
|
|
109
114
|
recordReplay() {
|
|
110
|
-
warn('
|
|
115
|
+
warn(this.#warnMessage('recordReplay'));
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
/**
|
|
@@ -117,6 +122,6 @@ export class AgentBase {
|
|
|
117
122
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
118
123
|
*/
|
|
119
124
|
pauseReplay() {
|
|
120
|
-
warn('
|
|
125
|
+
warn(this.#warnMessage('pauseReplay'));
|
|
121
126
|
}
|
|
122
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../src/common/config/state/init.js"],"names":[],"mappings":"AA6GA,+CAIC;AAED,0DAKC;AAED,+DAYC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unique-id.d.ts","sourceRoot":"","sources":["../../../../src/common/ids/unique-id.js"],"names":[],"mappings":"AAkCA;;;;GAIG;AACH,gCAFa,MAAM,
|
|
1
|
+
{"version":3,"file":"unique-id.d.ts","sourceRoot":"","sources":["../../../../src/common/ids/unique-id.js"],"names":[],"mappings":"AAkCA;;;;GAIG;AACH,gCAFa,MAAM,CAwBlB;AAED;;;;;GAKG;AACH,sDAFa,MAAM,CAiBlB;AAED;;;;;GAKG;AACH,kCAFa,MAAM,CAIlB;AAED;;;;;GAKG;AACH,mCAFa,MAAM,CAIlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA6BA,mCAAmC;;;;;;;;;AAoCnC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AA6BA,mCAAmC;;;;;;;;;AAoCnC,uCAAuC;AACvC,uCAAuC;AACvC,iCAAiC;AACjC,uCAAuC;AAMvC;IACE,2BAAiC;IACjC,mDAuHC;IArHC,iHAAiH;IACjH,cAAgB;IAChB,mFAAmF;IACnF,wBAA0B;IAC1B,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,mEAAmE;IACnE,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAGpB,iEAAiE;IACjE,mBAAsB;IACtB,gDAAgD;IAChD,wBAA0B;IAE1B;;;MAGE;IACF,qBAAwB;IACxB,4IAA4I;IAC5I,iBAAoB;IACpB,+HAA+H;IAC/H,kBAAqB;IAErB;;OAEG;IACH,oBAA+B;IAE/B,qGAAqG;IACrG,+BAA+B;IAE/B,kIAAkI;IAClI,cAAyB;IAEzB,0BAA0B;IAC1B,kBAAqB;IAOrB,uIAAuI;IACvI,0BAAyE;IA0BvE,wCAKQ;IAuCZ,qBAWC;IAED;;;;;;;OAOG;IACH,iCALW,OAAO,cACP,OAAO,iBACP,OAAO,GACL,IAAI,CAuDhB;IAED;;;;;;;;;oBAkBC;IAED;;;;;;;;;MAgEC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBASC;IAED,qDAAqD;IACrD,uBA4BC;IAED,yHAAyH;IACzH,yCAsCC;IAED,0HAA0H;IAC1H,yBAGC;IAED,sBAGC;IAED,wBAEC;IAED,gCAAgC;IAChC,uCAGC;IAED;;;;OAIG;IACH,mCAKC;IAED,yDAAyD;IACzD,yBASC;IAED;;;SAGK;IACL,oCAGC;IAED,yCAGC;CACF;8BA5e6B,4BAA4B;iCALzB,2CAA2C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_trace/aggregate/index.js"],"names":[],"mappings":"AAiCA;IACE,2BAAiC;IAGjC,iEA4IC;IA1IC,kBAA+C;IAK/C,sBAAiD;IACjD,yBAAc;IACd,sBAAe;IACf,8BAAkB;IAClB,iCAAqB;IACrB,wBAA0G;IAC1G,wBAA4G;IAC5G;;4EAEwE;IACxE,kCAAyB;IAGzB,0CAAsC;IA0HxC,sEAcC;IA6CD,oDAOC;IAGD,oCAwBC;IAGD,uEAkBC;IAED,oDAKC;IAED,wBAwBC;IAED,uCAuBC;IAGD,gDASC;IAID,qCAkBC;IAGD,qEAUC;IAGD,mEAUC;IAGD,yBAeC;IAED;;;;OAIG;IACH,2BAHW,MAAM,GACJ,MAAM,CAsBlB;IAGD;;;;;;;YAkCM;0GAC8F;;YAE9F;0FAC8E;;YAE9E,4IAA4I;;;;;;MAMjJ;IAED,mEA6BC;;CACF;8BA5gB6B,4BAA4B;6BAH7B,2BAA2B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/spa/aggregate/index.js"],"names":[],"mappings":"AA+BA;IACE,2BAAiC;IACjC,mDAssBC;IAnsBC;;;;;;;;;;;;;;;;;MAkBC;IAED,uBAAsC;CAgrBzC;8BArtB6B,4BAA4B;2BAJ/B,cAAc"}
|
|
@@ -52,7 +52,7 @@ export class AgentBase {
|
|
|
52
52
|
group: string;
|
|
53
53
|
}): void;
|
|
54
54
|
/**
|
|
55
|
-
* Records an additional time point as "finished" in a session trace
|
|
55
|
+
* Records an additional time point as "finished" in a session trace and adds a page action.
|
|
56
56
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/finished/}
|
|
57
57
|
* @param {number} [timeStamp] Defaults to the current time of the call. If used, this marks the time that the page is "finished" according to your own criteria.
|
|
58
58
|
*/
|
|
@@ -83,5 +83,6 @@ export class AgentBase {
|
|
|
83
83
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
84
84
|
*/
|
|
85
85
|
pauseReplay(): void;
|
|
86
|
+
#private;
|
|
86
87
|
}
|
|
87
88
|
//# sourceMappingURL=agent-base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/agent-base.js"],"names":[],"mappings":"AAIA;
|
|
1
|
+
{"version":3,"file":"agent-base.d.ts","sourceRoot":"","sources":["../../../src/loaders/agent-base.js"],"names":[],"mappings":"AAIA;IAME;;;;;OAKG;IACH,oBAHW,MAAM,yCAKhB;IAED;;;;;OAKG;IACH,sBAHW,MAAM,mCAKhB;IAED;;;;;;OAMG;IACH,yBAJW,MAAM,SACN,MAAM,GAAC,MAAM,GAAC,IAAI,uCAK5B;IAED;;;;;OAKG;IACH,mBAHW,KAAK,GAAC,MAAM,+CAKtB;IAED;;;;OAIG;IACH,iBAFW,MAAM,GAAC,IAAI,QAIrB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,IAAI,QAMrB;IAED;;;;OAIG;IACH,kCAFmB,KAAK,GAAC,MAAM,KAAK,OAAO,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,QAI9D;IAED;;;;OAIG;IACH,+CAEC;IAED;;;;;OAKG;IACH,iBAHW,MAAM,MACN,MAAM,QAIhB;IAED;;;;OAIG;IACH,0DAEC;IAED;;;;OAIG;IACH,qBAEC;IAED;;;;;OAKG;IACH,oBAEC;;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/browser-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.249.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
|
|
6
6
|
"description": "New Relic Browser Agent",
|
|
@@ -139,14 +139,11 @@
|
|
|
139
139
|
"cdn:build": "npm run cdn:build:prod",
|
|
140
140
|
"cdn:build:local": "npm run cdn:webpack",
|
|
141
141
|
"cdn:build:prod": "npm run cdn:webpack -- --env mode=prod",
|
|
142
|
-
"postcdn:build:prod": "npm run cdn:clone",
|
|
143
142
|
"cdn:build:dev": "npm run cdn:webpack -- --env mode=dev",
|
|
144
143
|
"cdn:build:experiment": "npm run cdn:webpack -- --env mode=experiment",
|
|
145
144
|
"cdn:webpack": "npx webpack --progress --config ./tools/webpack/index.mjs",
|
|
146
|
-
"postcdn:webpack": "npm run cdn:cleanup",
|
|
147
145
|
"cdn:watch": "jung -r ./src -F '.*\\.test\\.js' --run -- npm run cdn:build:local",
|
|
148
146
|
"cdn:cleanup": "node ./tools/webpack/scripts/cleanup.mjs",
|
|
149
|
-
"cdn:clone": "node ./tools/webpack/scripts/clone.mjs",
|
|
150
147
|
"test-server": "node ./tools/wdio/bin/server",
|
|
151
148
|
"sauce:connect": "node ./tools/saucelabs/bin.mjs",
|
|
152
149
|
"sauce:get-browsers": "node ./tools/browsers-lists/sauce-browsers.mjs",
|
|
@@ -251,7 +248,6 @@
|
|
|
251
248
|
"webpack-bundle-analyzer": "^4.7.0",
|
|
252
249
|
"webpack-cli": "^4.10.0",
|
|
253
250
|
"webpack-stream": "^7.0.0",
|
|
254
|
-
"webpack-subresource-integrity": "^5.1.0",
|
|
255
251
|
"yargs": "^17.6.2"
|
|
256
252
|
},
|
|
257
253
|
"files": [
|
|
@@ -4,6 +4,8 @@ import { warn } from '../../util/console'
|
|
|
4
4
|
import { getNREUMInitializedAgent } from '../../window/nreum'
|
|
5
5
|
import { getModeledObject } from './configurable'
|
|
6
6
|
|
|
7
|
+
const nrMask = '[data-nr-mask]'
|
|
8
|
+
|
|
7
9
|
const model = () => {
|
|
8
10
|
const hiddenState = {
|
|
9
11
|
mask_selector: '*',
|
|
@@ -72,9 +74,9 @@ const model = () => {
|
|
|
72
74
|
// this has a getter/setter to facilitate validation of the selectors
|
|
73
75
|
get mask_text_selector () { return hiddenState.mask_selector },
|
|
74
76
|
set mask_text_selector (val) {
|
|
75
|
-
if (isValidSelector(val)) hiddenState.mask_selector = val
|
|
76
|
-
else if (val === null) hiddenState.mask_selector =
|
|
77
|
-
else warn('An invalid session_replay.mask_selector was provided
|
|
77
|
+
if (isValidSelector(val)) hiddenState.mask_selector = `${val},${nrMask}`
|
|
78
|
+
else if (val === '' || val === null) hiddenState.mask_selector = nrMask
|
|
79
|
+
else warn('An invalid session_replay.mask_selector was provided. \'*\' will be used.', val)
|
|
78
80
|
},
|
|
79
81
|
// these properties only have getters because they are enforcable constants and should error if someone tries to override them
|
|
80
82
|
get block_class () { return 'nr-block' },
|
|
@@ -43,13 +43,14 @@ export function generateUuid () {
|
|
|
43
43
|
let randomValueTable
|
|
44
44
|
let randomValueIndex = 0
|
|
45
45
|
if (crypto && crypto.getRandomValues) {
|
|
46
|
+
// For a UUID, we only need 30 characters since two characters are pre-defined
|
|
46
47
|
// eslint-disable-next-line
|
|
47
|
-
randomValueTable = crypto.getRandomValues(new Uint8Array(
|
|
48
|
+
randomValueTable = crypto.getRandomValues(new Uint8Array(30))
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
return uuidv4Template.split('').map(templateInput => {
|
|
51
52
|
if (templateInput === 'x') {
|
|
52
|
-
return getRandomValue(randomValueTable, ++
|
|
53
|
+
return getRandomValue(randomValueTable, randomValueIndex++).toString(16)
|
|
53
54
|
} else if (templateInput === 'y') {
|
|
54
55
|
// this is the uuid variant per spec (8, 9, a, b)
|
|
55
56
|
// % 4, then shift to get values 8-11
|
|
@@ -73,7 +74,7 @@ export function generateRandomHexString (length) {
|
|
|
73
74
|
let randomValueIndex = 0
|
|
74
75
|
if (crypto && crypto.getRandomValues) {
|
|
75
76
|
// eslint-disable-next-line
|
|
76
|
-
randomValueTable = crypto.getRandomValues(new Uint8Array(
|
|
77
|
+
randomValueTable = crypto.getRandomValues(new Uint8Array(length))
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
const chars = []
|
|
@@ -67,6 +67,8 @@ let recorder, gzipper, u8
|
|
|
67
67
|
export const MAX_PAYLOAD_SIZE = 1000000
|
|
68
68
|
/** Unloading caps around 64kb */
|
|
69
69
|
export const IDEAL_PAYLOAD_SIZE = 64000
|
|
70
|
+
/** Reserved room for query param attrs */
|
|
71
|
+
const QUERY_PARAM_PADDING = 5000
|
|
70
72
|
/** Interval between forcing new full snapshots -- 15 seconds in error mode (x2), 5 minutes in full mode */
|
|
71
73
|
const CHECKOUT_MS = { [MODE.ERROR]: 15000, [MODE.FULL]: 300000, [MODE.OFF]: 0 }
|
|
72
74
|
|
|
@@ -292,6 +294,7 @@ export class Aggregate extends AggregateBase {
|
|
|
292
294
|
getHarvestContents () {
|
|
293
295
|
const agentRuntime = getRuntime(this.agentIdentifier)
|
|
294
296
|
const info = getInfo(this.agentIdentifier)
|
|
297
|
+
const endUserId = info.jsAttributes?.['enduser.id']
|
|
295
298
|
|
|
296
299
|
if (this.backloggedEvents.length) this.events = [...this.backloggedEvents, ...this.events]
|
|
297
300
|
|
|
@@ -327,6 +330,8 @@ export class Aggregate extends AggregateBase {
|
|
|
327
330
|
app_id: info.applicationID,
|
|
328
331
|
protocol_version: '0',
|
|
329
332
|
attributes: encodeObj({
|
|
333
|
+
// this section of attributes must be controllable and stay below the query param padding limit -- see QUERY_PARAM_PADDING
|
|
334
|
+
// if not, data could be lost to truncation at time of sending, potentially breaking parsing / API behavior in NR1
|
|
330
335
|
...(this.shouldCompress && { content_encoding: 'gzip' }),
|
|
331
336
|
'replay.firstTimestamp': firstTimestamp,
|
|
332
337
|
'replay.firstTimestampOffset': firstTimestamp - agentOffset,
|
|
@@ -342,8 +347,11 @@ export class Aggregate extends AggregateBase {
|
|
|
342
347
|
hasError: this.hasError,
|
|
343
348
|
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
344
349
|
decompressedBytes: this.payloadBytesEstimation,
|
|
345
|
-
'rrweb.version': RRWEB_VERSION
|
|
346
|
-
|
|
350
|
+
'rrweb.version': RRWEB_VERSION,
|
|
351
|
+
// customer-defined data should go last so that if it exceeds the query param padding limit it will be truncated instead of important attrs
|
|
352
|
+
...(endUserId && { 'enduser.id': endUserId })
|
|
353
|
+
// The Query Param is being arbitrarily limited in length here. It is also applied when estimating the size of the payload in getPayloadSize()
|
|
354
|
+
}, QUERY_PARAM_PADDING).substring(1) // remove the leading '&'
|
|
347
355
|
},
|
|
348
356
|
body: this.events
|
|
349
357
|
}
|
|
@@ -459,8 +467,8 @@ export class Aggregate extends AggregateBase {
|
|
|
459
467
|
|
|
460
468
|
/** Estimate the payload size */
|
|
461
469
|
getPayloadSize (newBytes = 0) {
|
|
462
|
-
// the
|
|
463
|
-
return this.estimateCompression(this.payloadBytesEstimation + newBytes) +
|
|
470
|
+
// the query param padding constant gives us some padding for the other metadata to be safely injected
|
|
471
|
+
return this.estimateCompression(this.payloadBytesEstimation + newBytes) + QUERY_PARAM_PADDING
|
|
464
472
|
}
|
|
465
473
|
|
|
466
474
|
/**
|
|
@@ -89,6 +89,8 @@ export class Aggregate extends AggregateBase {
|
|
|
89
89
|
* "external" input in this case means errors thrown on the page or session replay itself being triggered to run in full mode by the API, which updates the session entity.
|
|
90
90
|
*/
|
|
91
91
|
const switchToFull = () => {
|
|
92
|
+
if (this.agentRuntime?.session?.state?.sessionReplayMode !== MODE.FULL) return
|
|
93
|
+
|
|
92
94
|
if (mostRecentModeKnown !== MODE.FULL) {
|
|
93
95
|
const prevMode = mostRecentModeKnown
|
|
94
96
|
mostRecentModeKnown = MODE.FULL
|
|
@@ -22,6 +22,8 @@ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-pa
|
|
|
22
22
|
import { firstPaint } from '../../../common/vitals/first-paint'
|
|
23
23
|
import { bundleId } from '../../../common/ids/bundle-id'
|
|
24
24
|
import { loadedAsDeferredBrowserScript } from '../../../common/constants/runtime'
|
|
25
|
+
import { handle } from '../../../common/event-emitter/handle'
|
|
26
|
+
import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
|
|
25
27
|
|
|
26
28
|
const {
|
|
27
29
|
FEATURE_NAME, INTERACTION_EVENTS, MAX_TIMER_BUDGET, FN_START, FN_END, CB_START, INTERACTION_API, REMAINING,
|
|
@@ -724,6 +726,12 @@ export class Aggregate extends AggregateBase {
|
|
|
724
726
|
}
|
|
725
727
|
baseEE.emit('interactionSaved', [interaction])
|
|
726
728
|
state.interactionsToHarvest.push(interaction)
|
|
729
|
+
|
|
730
|
+
let smCategory = 'RouteChange'
|
|
731
|
+
if (interaction.root?.attrs?.trigger === 'initialPageLoad') smCategory = 'InitialPageLoad'
|
|
732
|
+
else if (interaction.root?.attrs?.trigger === 'api') smCategory = 'Custom'
|
|
733
|
+
handle(SUPPORTABILITY_METRIC_CHANNEL, [`Spa/Interaction/${smCategory}/Duration/Ms`, Math.max((interaction.root?.end || 0) - (interaction.root?.start || 0), 0)], undefined, FEATURE_NAMES.metrics, baseEE)
|
|
734
|
+
|
|
727
735
|
scheduler.scheduleHarvest(0)
|
|
728
736
|
}
|
|
729
737
|
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
import { warn } from '../common/util/console'
|
|
4
4
|
|
|
5
5
|
export class AgentBase {
|
|
6
|
+
/** Generates a generic warning message with the api name injected */
|
|
7
|
+
#warnMessage (api) {
|
|
8
|
+
return `Call to agent api ${api} failed. The agent is not currently initialized.`
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Reports a browser PageAction event along with a name and optional attributes.
|
|
8
13
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/addpageaction/}
|
|
@@ -10,7 +15,7 @@ export class AgentBase {
|
|
|
10
15
|
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}. The key is reported as its own PageAction attribute with the specified values.
|
|
11
16
|
*/
|
|
12
17
|
addPageAction (name, attributes) {
|
|
13
|
-
warn('
|
|
18
|
+
warn(this.#warnMessage('addPageAction'))
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -20,7 +25,7 @@ export class AgentBase {
|
|
|
20
25
|
* @param {string} [host] Default is http://custom.transaction. Typically set host to your site's domain URI.
|
|
21
26
|
*/
|
|
22
27
|
setPageViewName (name, host) {
|
|
23
|
-
warn('
|
|
28
|
+
warn(this.#warnMessage('setPageViewName'))
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
/**
|
|
@@ -31,7 +36,7 @@ export class AgentBase {
|
|
|
31
36
|
* @param {boolean} [persist] Default false. f set to true, the name-value pair will also be set into the browser's storage API. Then on the following instrumented pages that load within the same session, the pair will be re-applied as a custom attribute.
|
|
32
37
|
*/
|
|
33
38
|
setCustomAttribute (name, value, persist) {
|
|
34
|
-
warn('
|
|
39
|
+
warn(this.#warnMessage('setCustomAttribute'))
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
@@ -41,7 +46,7 @@ export class AgentBase {
|
|
|
41
46
|
* @param {object} [customAttributes] An object containing name/value pairs representing custom attributes.
|
|
42
47
|
*/
|
|
43
48
|
noticeError (error, customAttributes) {
|
|
44
|
-
warn('
|
|
49
|
+
warn(this.#warnMessage('noticeError'))
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
@@ -50,7 +55,7 @@ export class AgentBase {
|
|
|
50
55
|
* @param {string|null} value A string identifier for the end-user, useful for tying all browser events to specific users. The value parameter does not have to be unique. If IDs should be unique, the caller is responsible for that validation. Passing a null value unsets any existing user ID.
|
|
51
56
|
*/
|
|
52
57
|
setUserId (value) {
|
|
53
|
-
warn('
|
|
58
|
+
warn(this.#warnMessage('setUserId'))
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
@@ -62,7 +67,7 @@ export class AgentBase {
|
|
|
62
67
|
* have to be unique. Passing a null value unsets any existing value.
|
|
63
68
|
*/
|
|
64
69
|
setApplicationVersion (value) {
|
|
65
|
-
warn('
|
|
70
|
+
warn(this.#warnMessage('setApplicationVersion'))
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
/**
|
|
@@ -71,16 +76,16 @@ export class AgentBase {
|
|
|
71
76
|
* @param {(error: Error|string) => boolean | { group: string }} callback When an error occurs, the callback is called with the error object as a parameter. The callback will be called with each error, so it is not specific to one error.
|
|
72
77
|
*/
|
|
73
78
|
setErrorHandler (callback) {
|
|
74
|
-
warn('
|
|
79
|
+
warn(this.#warnMessage('setErrorHandler'))
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
/**
|
|
78
|
-
* Records an additional time point as "finished" in a session trace
|
|
83
|
+
* Records an additional time point as "finished" in a session trace and adds a page action.
|
|
79
84
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/finished/}
|
|
80
85
|
* @param {number} [timeStamp] Defaults to the current time of the call. If used, this marks the time that the page is "finished" according to your own criteria.
|
|
81
86
|
*/
|
|
82
87
|
finished (timeStamp) {
|
|
83
|
-
warn('
|
|
88
|
+
warn(this.#warnMessage('finished'))
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
/**
|
|
@@ -90,7 +95,7 @@ export class AgentBase {
|
|
|
90
95
|
* @param {string} id The ID or version of this release; for example, a version number, build number from your CI environment, GitHub SHA, GUID, or a hash of the contents.
|
|
91
96
|
*/
|
|
92
97
|
addRelease (name, id) {
|
|
93
|
-
warn('
|
|
98
|
+
warn(this.#warnMessage('addRelease'))
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
/**
|
|
@@ -99,7 +104,7 @@ export class AgentBase {
|
|
|
99
104
|
* @param {string|string[]} [featureNames] The name(s) of the features to start. If no name(s) are passed, all features will be started
|
|
100
105
|
*/
|
|
101
106
|
start (featureNames) {
|
|
102
|
-
warn('
|
|
107
|
+
warn(this.#warnMessage('start'))
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
/**
|
|
@@ -108,7 +113,7 @@ export class AgentBase {
|
|
|
108
113
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
109
114
|
*/
|
|
110
115
|
recordReplay () {
|
|
111
|
-
warn('
|
|
116
|
+
warn(this.#warnMessage('recordReplay'))
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
/**
|
|
@@ -118,6 +123,6 @@ export class AgentBase {
|
|
|
118
123
|
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
|
|
119
124
|
*/
|
|
120
125
|
pauseReplay () {
|
|
121
|
-
warn('
|
|
126
|
+
warn(this.#warnMessage('pauseReplay'))
|
|
122
127
|
}
|
|
123
128
|
}
|