@newrelic/browser-agent 1.246.1 → 1.247.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 +12 -0
- package/README.md +10 -2
- package/dist/cjs/common/config/state/info.js +2 -1
- package/dist/cjs/common/config/state/init.js +2 -1
- package/dist/cjs/common/config/state/loader-config.js +2 -1
- package/dist/cjs/common/config/state/runtime.js +2 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/constants/runtime.js +3 -1
- package/dist/cjs/common/dispatch/global-event.js +19 -0
- package/dist/cjs/common/session/session-entity.js +14 -9
- package/dist/cjs/common/util/feature-flags.js +17 -12
- package/dist/cjs/common/window/load.js +1 -0
- package/dist/cjs/common/window/nreum.js +18 -17
- package/dist/cjs/features/metrics/aggregate/index.js +4 -2
- package/dist/cjs/features/session_replay/aggregate/index.js +56 -16
- package/dist/cjs/features/session_trace/aggregate/index.js +32 -18
- package/dist/cjs/features/spa/aggregate/index.js +2 -1
- package/dist/cjs/features/utils/aggregate-base.js +3 -1
- package/dist/cjs/loaders/agent-base.js +19 -0
- package/dist/cjs/loaders/agent.js +6 -4
- package/dist/cjs/loaders/api/api.js +10 -1
- package/dist/cjs/loaders/configure/configure.js +14 -13
- package/dist/cjs/loaders/configure/nonce.js +13 -0
- package/dist/cjs/loaders/configure/nonce.npm.js +2 -0
- package/dist/cjs/loaders/micro-agent.js +5 -4
- package/dist/esm/common/config/state/info.js +3 -2
- package/dist/esm/common/config/state/init.js +3 -2
- package/dist/esm/common/config/state/loader-config.js +3 -2
- package/dist/esm/common/config/state/runtime.js +3 -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/constants/runtime.js +1 -0
- package/dist/esm/common/dispatch/global-event.js +13 -0
- package/dist/esm/common/session/session-entity.js +14 -9
- package/dist/esm/common/util/feature-flags.js +17 -12
- package/dist/esm/common/window/load.js +1 -1
- package/dist/esm/common/window/nreum.js +16 -16
- package/dist/esm/features/metrics/aggregate/index.js +4 -2
- package/dist/esm/features/session_replay/aggregate/index.js +56 -16
- package/dist/esm/features/session_trace/aggregate/index.js +32 -18
- package/dist/esm/features/spa/aggregate/index.js +2 -1
- package/dist/esm/features/utils/aggregate-base.js +3 -1
- package/dist/esm/loaders/agent-base.js +19 -0
- package/dist/esm/loaders/agent.js +7 -5
- package/dist/esm/loaders/api/api.js +10 -1
- package/dist/esm/loaders/configure/configure.js +15 -14
- package/dist/esm/loaders/configure/nonce.js +11 -0
- package/dist/esm/loaders/configure/nonce.npm.js +1 -0
- package/dist/esm/loaders/micro-agent.js +6 -5
- package/dist/types/common/config/state/info.d.ts.map +1 -1
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/config/state/loader-config.d.ts.map +1 -1
- package/dist/types/common/config/state/runtime.d.ts.map +1 -1
- package/dist/types/common/constants/runtime.d.ts +1 -0
- package/dist/types/common/constants/runtime.d.ts.map +1 -1
- package/dist/types/common/dispatch/global-event.d.ts +2 -0
- package/dist/types/common/dispatch/global-event.d.ts.map +1 -0
- package/dist/types/common/session/session-entity.d.ts +1 -0
- package/dist/types/common/session/session-entity.d.ts.map +1 -1
- package/dist/types/common/util/feature-flags.d.ts.map +1 -1
- package/dist/types/common/window/load.d.ts +1 -0
- package/dist/types/common/window/load.d.ts.map +1 -1
- package/dist/types/common/window/nreum.d.ts +7 -1
- package/dist/types/common/window/nreum.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +11 -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 +13 -0
- package/dist/types/loaders/agent-base.d.ts.map +1 -1
- package/dist/types/loaders/agent.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts +2 -0
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts +4 -11
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/dist/types/loaders/configure/nonce.d.ts +1 -0
- package/dist/types/loaders/configure/nonce.d.ts.map +1 -0
- package/dist/types/loaders/configure/nonce.npm.d.ts +1 -0
- package/dist/types/loaders/configure/nonce.npm.d.ts.map +1 -0
- package/dist/types/loaders/micro-agent.d.ts +2 -2
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/common/config/state/info.js +3 -2
- package/src/common/config/state/init.js +3 -2
- package/src/common/config/state/loader-config.js +3 -2
- package/src/common/config/state/runtime.js +3 -2
- package/src/common/constants/runtime.js +2 -0
- package/src/common/dispatch/global-event.js +12 -0
- package/src/common/session/session-entity.js +13 -9
- package/src/common/util/feature-flags.js +15 -12
- package/src/common/window/__mocks__/nreum.js +2 -1
- package/src/common/window/load.js +1 -1
- package/src/common/window/nreum.js +15 -18
- package/src/features/metrics/aggregate/index.js +5 -1
- package/src/features/session_replay/aggregate/index.js +63 -20
- package/src/features/session_trace/aggregate/index.js +31 -14
- package/src/features/spa/aggregate/index.js +2 -1
- package/src/features/utils/aggregate-base.js +1 -1
- package/src/loaders/agent-base.js +19 -0
- package/src/loaders/agent.js +5 -5
- package/src/loaders/api/api.js +12 -1
- package/src/loaders/configure/configure.js +17 -15
- package/src/loaders/configure/nonce.js +12 -0
- package/src/loaders/configure/nonce.npm.js +1 -0
- package/src/loaders/micro-agent.js +5 -4
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.Agent = void 0;
|
|
7
7
|
require("./configure/public-path.npm");
|
|
8
|
+
require("./configure/nonce.npm");
|
|
8
9
|
var _agentBase = require("./agent-base");
|
|
9
10
|
var _enabledFeatures = require("./features/enabled-features");
|
|
10
11
|
var _configure = require("./configure/configure");
|
|
@@ -49,12 +50,15 @@ class Agent extends _agentBase.AgentBase {
|
|
|
49
50
|
agentIdentifier: this.agentIdentifier
|
|
50
51
|
});
|
|
51
52
|
this.features = {};
|
|
53
|
+
(0, _nreum.setNREUMInitializedAgent)(agentIdentifier, this); // append this agent onto the global NREUM.initializedAgents
|
|
54
|
+
|
|
52
55
|
this.desiredFeatures = new Set(options.features || []); // expected to be a list of static Instrument/InstrumentBase classes, see "spa.js" for example
|
|
53
56
|
// For Now... ALL agents must make the rum call whether the page_view_event feature was enabled or not.
|
|
54
57
|
// NR1 creates an index on the rum call, and if not seen for a few days, will remove the browser app!
|
|
55
58
|
// Future work is being planned to evaluate removing this behavior from the backend, but for now we must ensure this call is made
|
|
56
59
|
this.desiredFeatures.add(_instrument.Instrument);
|
|
57
|
-
|
|
60
|
+
(0, _configure.configure)(this, options, options.loaderType || 'agent'); // add api, exposed, and other config properties
|
|
61
|
+
|
|
58
62
|
this.run();
|
|
59
63
|
}
|
|
60
64
|
get config() {
|
|
@@ -66,7 +70,6 @@ class Agent extends _agentBase.AgentBase {
|
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
run() {
|
|
69
|
-
const NR_FEATURES_REF_NAME = 'features';
|
|
70
73
|
// Attempt to initialize all the requested features (sequentially in prio order & synchronously), with any failure aborting the whole process.
|
|
71
74
|
try {
|
|
72
75
|
const enabledFeatures = (0, _enabledFeatures.getEnabledFeatures)(this.agentIdentifier);
|
|
@@ -81,7 +84,6 @@ class Agent extends _agentBase.AgentBase {
|
|
|
81
84
|
this.features[InstrumentCtor.featureName] = new InstrumentCtor(this.agentIdentifier, this.sharedAggregator);
|
|
82
85
|
}
|
|
83
86
|
});
|
|
84
|
-
(0, _nreum.gosNREUMInitializedAgents)(this.agentIdentifier, this.features, NR_FEATURES_REF_NAME);
|
|
85
87
|
} catch (err) {
|
|
86
88
|
(0, _console.warn)('Failed to initialize all enabled instrument classes (agent aborted) -', err);
|
|
87
89
|
for (const featName in this.features) {
|
|
@@ -90,7 +92,7 @@ class Agent extends _agentBase.AgentBase {
|
|
|
90
92
|
}
|
|
91
93
|
const newrelic = (0, _nreum.gosNREUM)();
|
|
92
94
|
delete newrelic.initializedAgents[this.agentIdentifier]?.api; // prevent further calls to agent-specific APIs (see "configure.js")
|
|
93
|
-
delete newrelic.initializedAgents[this.agentIdentifier]?.
|
|
95
|
+
delete newrelic.initializedAgents[this.agentIdentifier]?.features; // GC mem used internally by features
|
|
94
96
|
delete this.sharedAggregator;
|
|
95
97
|
// Keep the initialized agent object with its configs for troubleshooting purposes.
|
|
96
98
|
newrelic.ee?.abort(); // set flag and clear global backlog
|
|
@@ -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', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start'];
|
|
29
|
+
const funcs = ['setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute', 'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start', 'recordReplay', 'pauseReplay'];
|
|
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++) {
|
|
@@ -147,6 +147,14 @@ function setAPI(agentIdentifier, forceDrain) {
|
|
|
147
147
|
(0, _console.warn)('An unexpected issue occurred', err);
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
|
+
apiInterface.recordReplay = function () {
|
|
151
|
+
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/recordReplay/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
|
|
152
|
+
(0, _handle.handle)('recordReplay', [], undefined, _features.FEATURE_NAMES.sessionReplay, instanceEE);
|
|
153
|
+
};
|
|
154
|
+
apiInterface.pauseReplay = function () {
|
|
155
|
+
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/pauseReplay/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
|
|
156
|
+
(0, _handle.handle)('pauseReplay', [], undefined, _features.FEATURE_NAMES.sessionReplay, instanceEE);
|
|
157
|
+
};
|
|
150
158
|
apiInterface.interaction = function () {
|
|
151
159
|
return new InteractionHandle().get();
|
|
152
160
|
};
|
|
@@ -156,6 +164,7 @@ function setAPI(agentIdentifier, forceDrain) {
|
|
|
156
164
|
var contextStore = {};
|
|
157
165
|
var ixn = this;
|
|
158
166
|
var hasCb = typeof cb === 'function';
|
|
167
|
+
(0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, ['API/createTracer/called'], undefined, _features.FEATURE_NAMES.metrics, instanceEE);
|
|
159
168
|
(0, _handle.handle)(spaPrefix + 'tracer', [(0, _now.now)(), name, contextStore], ixn, _features.FEATURE_NAMES.spa, instanceEE);
|
|
160
169
|
return function () {
|
|
161
170
|
tracerEE.emit((hasCb ? '' : 'no-') + 'fn-start', [(0, _now.now)(), ixn, hasCb], contextStore);
|
|
@@ -12,7 +12,10 @@ var _runtime = require("../../common/constants/runtime");
|
|
|
12
12
|
var _publicPath = require("./public-path");
|
|
13
13
|
let alreadySetOnce = false; // the configure() function can run multiple times in agent lifecycle
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Sets or re-sets the agent's configuration values from global settings. This also attach those as properties to the agent instance.
|
|
17
|
+
*/
|
|
18
|
+
function configure(agent) {
|
|
16
19
|
let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
17
20
|
let loaderType = arguments.length > 2 ? arguments[2] : undefined;
|
|
18
21
|
let forceDrain = arguments.length > 3 ? arguments[3] : undefined;
|
|
@@ -33,31 +36,29 @@ function configure(agentIdentifier) {
|
|
|
33
36
|
// eslint-disable-next-line camelcase
|
|
34
37
|
loader_config = nr.loader_config;
|
|
35
38
|
}
|
|
36
|
-
(0, _config.setConfiguration)(agentIdentifier, init || {});
|
|
39
|
+
(0, _config.setConfiguration)(agent.agentIdentifier, init || {});
|
|
37
40
|
// eslint-disable-next-line camelcase
|
|
38
|
-
(0, _config.setLoaderConfig)(agentIdentifier, loader_config || {});
|
|
41
|
+
(0, _config.setLoaderConfig)(agent.agentIdentifier, loader_config || {});
|
|
39
42
|
info.jsAttributes ??= {};
|
|
40
43
|
if (_runtime.isWorkerScope) {
|
|
41
44
|
// add a default attr to all worker payloads
|
|
42
45
|
info.jsAttributes.isWorker = true;
|
|
43
46
|
}
|
|
44
|
-
(0, _config.setInfo)(agentIdentifier, info);
|
|
45
|
-
const updatedInit = (0, _config.getConfiguration)(agentIdentifier);
|
|
47
|
+
(0, _config.setInfo)(agent.agentIdentifier, info);
|
|
48
|
+
const updatedInit = (0, _config.getConfiguration)(agent.agentIdentifier);
|
|
46
49
|
const internalTrafficList = [info.beacon, info.errorBeacon];
|
|
47
50
|
if (!alreadySetOnce) {
|
|
48
|
-
alreadySetOnce = true;
|
|
49
51
|
if (updatedInit.proxy.assets) {
|
|
50
52
|
(0, _publicPath.redefinePublicPath)(updatedInit.proxy.assets);
|
|
51
53
|
internalTrafficList.push(updatedInit.proxy.assets);
|
|
52
54
|
}
|
|
53
55
|
if (updatedInit.proxy.beacon) internalTrafficList.push(updatedInit.proxy.beacon);
|
|
56
|
+
(0, _api.setTopLevelCallers)(); // no need to set global APIs on newrelic obj more than once
|
|
57
|
+
(0, _nreum.addToNREUM)('activatedFeatures', _featureFlags.activatedFeatures);
|
|
54
58
|
}
|
|
55
59
|
runtime.denyList = [...(updatedInit.ajax.deny_list || []), ...(updatedInit.ajax.block_internal ? internalTrafficList : [])];
|
|
56
|
-
(0, _config.setRuntime)(agentIdentifier, runtime);
|
|
57
|
-
(0, _api.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
(0, _nreum.gosNREUMInitializedAgents)(agentIdentifier, exposed, 'exposed');
|
|
61
|
-
(0, _nreum.addToNREUM)('activatedFeatures', _featureFlags.activatedFeatures);
|
|
62
|
-
return api;
|
|
60
|
+
(0, _config.setRuntime)(agent.agentIdentifier, runtime);
|
|
61
|
+
if (agent.api === undefined) agent.api = (0, _api.setAPI)(agent.agentIdentifier, forceDrain);
|
|
62
|
+
if (agent.exposed === undefined) agent.exposed = exposed;
|
|
63
|
+
alreadySetOnce = true;
|
|
63
64
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/* global __webpack_require__ */
|
|
4
|
+
|
|
5
|
+
__webpack_require__.nc = (() => {
|
|
6
|
+
try {
|
|
7
|
+
return document?.currentScript?.nonce;
|
|
8
|
+
} catch (ex) {
|
|
9
|
+
// Swallow error and proceed like nonce is not defined
|
|
10
|
+
// This will happen when the agent is loaded in a worker scope
|
|
11
|
+
}
|
|
12
|
+
return '';
|
|
13
|
+
})();
|
|
@@ -38,12 +38,14 @@ class MicroAgent extends _agentBase.AgentBase {
|
|
|
38
38
|
agentIdentifier: this.agentIdentifier
|
|
39
39
|
});
|
|
40
40
|
this.features = {};
|
|
41
|
-
|
|
41
|
+
(0, _nreum.setNREUMInitializedAgent)(agentIdentifier, this);
|
|
42
|
+
(0, _configure.configure)(this, {
|
|
42
43
|
...options,
|
|
43
44
|
runtime: {
|
|
44
45
|
isolatedBacklog: true
|
|
45
46
|
}
|
|
46
|
-
}, options.loaderType || 'micro-agent')
|
|
47
|
+
}, options.loaderType || 'micro-agent');
|
|
48
|
+
Object.assign(this, this.api); // the APIs should be available at the class level for micro-agent
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* Starts a set of agent features if not running in "autoStart" mode
|
|
@@ -98,8 +100,7 @@ class MicroAgent extends _agentBase.AgentBase {
|
|
|
98
100
|
}
|
|
99
101
|
});
|
|
100
102
|
});
|
|
101
|
-
|
|
102
|
-
return this;
|
|
103
|
+
return true;
|
|
103
104
|
} catch (err) {
|
|
104
105
|
(0, _console.warn)('Failed to initialize instrument classes.', err);
|
|
105
106
|
return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaults as nrDefaults,
|
|
1
|
+
import { defaults as nrDefaults, getNREUMInitializedAgent } from '../../window/nreum';
|
|
2
2
|
import { getModeledObject } from './configurable';
|
|
3
3
|
const model = {
|
|
4
4
|
// preset defaults
|
|
@@ -38,5 +38,6 @@ export function getInfo(id) {
|
|
|
38
38
|
export function setInfo(id, obj) {
|
|
39
39
|
if (!id) throw new Error('All info objects require an agent identifier!');
|
|
40
40
|
_cache[id] = getModeledObject(obj, model);
|
|
41
|
-
|
|
41
|
+
const agentInst = getNREUMInitializedAgent(id);
|
|
42
|
+
if (agentInst) agentInst.info = _cache[id];
|
|
42
43
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isValidSelector } from '../../dom/query-selector';
|
|
2
2
|
import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../../session/constants';
|
|
3
3
|
import { warn } from '../../util/console';
|
|
4
|
-
import {
|
|
4
|
+
import { getNREUMInitializedAgent } from '../../window/nreum';
|
|
5
5
|
import { getModeledObject } from './configurable';
|
|
6
6
|
const model = () => {
|
|
7
7
|
const hiddenState = {
|
|
@@ -165,7 +165,8 @@ export function getConfiguration(id) {
|
|
|
165
165
|
export function setConfiguration(id, obj) {
|
|
166
166
|
if (!id) throw new Error(missingAgentIdError);
|
|
167
167
|
_cache[id] = getModeledObject(obj, model());
|
|
168
|
-
|
|
168
|
+
const agentInst = getNREUMInitializedAgent(id);
|
|
169
|
+
if (agentInst) agentInst.init = _cache[id];
|
|
169
170
|
}
|
|
170
171
|
export function getConfigurationValue(id, path) {
|
|
171
172
|
if (!id) throw new Error(missingAgentIdError);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getNREUMInitializedAgent } from '../../window/nreum';
|
|
2
2
|
import { getModeledObject } from './configurable';
|
|
3
3
|
const model = {
|
|
4
4
|
accountID: undefined,
|
|
@@ -17,5 +17,6 @@ export function getLoaderConfig(id) {
|
|
|
17
17
|
export function setLoaderConfig(id, obj) {
|
|
18
18
|
if (!id) throw new Error('All loader-config objects require an agent identifier!');
|
|
19
19
|
_cache[id] = getModeledObject(obj, model);
|
|
20
|
-
|
|
20
|
+
const agentInst = getNREUMInitializedAgent(id);
|
|
21
|
+
if (agentInst) agentInst.loader_config = _cache[id];
|
|
21
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getModeledObject } from './configurable';
|
|
2
|
-
import {
|
|
2
|
+
import { getNREUMInitializedAgent } from '../../window/nreum';
|
|
3
3
|
import { globalScope } from '../../constants/runtime';
|
|
4
4
|
import { BUILD_ENV, DIST_METHOD, VERSION } from "../../constants/env.npm";
|
|
5
5
|
const model = {
|
|
@@ -31,5 +31,6 @@ export function getRuntime(id) {
|
|
|
31
31
|
export function setRuntime(id, obj) {
|
|
32
32
|
if (!id) throw new Error('All runtime objects require an agent identifier!');
|
|
33
33
|
_cache[id] = getModeledObject(obj, model);
|
|
34
|
-
|
|
34
|
+
const agentInst = getNREUMInitializedAgent(id);
|
|
35
|
+
if (agentInst) agentInst.runtime = _cache[id];
|
|
35
36
|
}
|
|
@@ -15,6 +15,7 @@ export const isBrowserScope = typeof window !== 'undefined' && !!window.document
|
|
|
15
15
|
*/
|
|
16
16
|
export const isWorkerScope = typeof WorkerGlobalScope !== 'undefined' && (typeof self !== 'undefined' && self instanceof WorkerGlobalScope && self.navigator instanceof WorkerNavigator || typeof globalThis !== 'undefined' && globalThis instanceof WorkerGlobalScope && globalThis.navigator instanceof WorkerNavigator);
|
|
17
17
|
export const globalScope = isBrowserScope ? window : typeof WorkerGlobalScope !== 'undefined' && (typeof self !== 'undefined' && self instanceof WorkerGlobalScope && self || typeof globalThis !== 'undefined' && globalThis instanceof WorkerGlobalScope && globalThis);
|
|
18
|
+
export const loadedAsDeferredBrowserScript = globalScope?.document?.readyState === 'complete';
|
|
18
19
|
export const initiallyHidden = Boolean(globalScope?.document?.visibilityState === 'hidden');
|
|
19
20
|
export const initialLocation = '' + globalScope?.location;
|
|
20
21
|
export const isiOS = /iPad|iPhone|iPod/.test(globalScope.navigator?.userAgent);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { globalScope } from '../constants/runtime';
|
|
2
|
+
const GLOBAL_EVENT_NAMESPACE = 'newrelic';
|
|
3
|
+
export function dispatchGlobalEvent() {
|
|
4
|
+
let detail = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
5
|
+
try {
|
|
6
|
+
globalScope.dispatchEvent(new CustomEvent(GLOBAL_EVENT_NAMESPACE, {
|
|
7
|
+
detail
|
|
8
|
+
}));
|
|
9
|
+
} catch (err) {
|
|
10
|
+
// something happened... dispatchEvent or CustomEvent might not be supported
|
|
11
|
+
// decide what to do about it here
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -104,8 +104,8 @@ export class SessionEntity {
|
|
|
104
104
|
this.expiresTimer = new Timer({
|
|
105
105
|
// When the inactive timer ends, collect a SM and reset the session
|
|
106
106
|
onEnd: () => {
|
|
107
|
-
this.collectSM('expired'
|
|
108
|
-
this.collectSM('duration'
|
|
107
|
+
this.collectSM('expired');
|
|
108
|
+
this.collectSM('duration');
|
|
109
109
|
this.reset();
|
|
110
110
|
}
|
|
111
111
|
}, this.state.expiresAt - Date.now());
|
|
@@ -121,8 +121,8 @@ export class SessionEntity {
|
|
|
121
121
|
this.inactiveTimer = new InteractionTimer({
|
|
122
122
|
// When the inactive timer ends, collect a SM and reset the session
|
|
123
123
|
onEnd: () => {
|
|
124
|
-
this.collectSM('inactive'
|
|
125
|
-
this.collectSM('duration'
|
|
124
|
+
this.collectSM('inactive');
|
|
125
|
+
this.collectSM('duration');
|
|
126
126
|
this.reset();
|
|
127
127
|
},
|
|
128
128
|
// When the inactive timer refreshes, it will update the storage values with an update timestamp
|
|
@@ -172,14 +172,14 @@ export class SessionEntity {
|
|
|
172
172
|
if (this.isInvalid(obj)) return {};
|
|
173
173
|
// if the session expires, collect a SM count before resetting
|
|
174
174
|
if (this.isExpired(obj.expiresAt)) {
|
|
175
|
-
this.collectSM('expired'
|
|
175
|
+
this.collectSM('expired');
|
|
176
176
|
this.collectSM('duration', obj, true);
|
|
177
177
|
return this.reset();
|
|
178
178
|
}
|
|
179
179
|
// if "inactive" timer is expired at "read" time -- esp. initial read -- reset
|
|
180
180
|
// collect a SM count before resetting
|
|
181
181
|
if (this.isExpired(obj.inactiveAt)) {
|
|
182
|
-
this.collectSM('inactive'
|
|
182
|
+
this.collectSM('inactive');
|
|
183
183
|
this.collectSM('duration', obj, true);
|
|
184
184
|
return this.reset();
|
|
185
185
|
}
|
|
@@ -270,15 +270,20 @@ export class SessionEntity {
|
|
|
270
270
|
collectSM(type, data, useUpdatedAt) {
|
|
271
271
|
let value, tag;
|
|
272
272
|
if (type === 'duration') {
|
|
273
|
-
|
|
274
|
-
const endingTimestamp = useUpdatedAt ? data.updatedAt : Date.now();
|
|
275
|
-
value = endingTimestamp - startingTimestamp;
|
|
273
|
+
value = this.getDuration(data, useUpdatedAt);
|
|
276
274
|
tag = 'Session/Duration/Ms';
|
|
277
275
|
}
|
|
278
276
|
if (type === 'expired') tag = 'Session/Expired/Seen';
|
|
279
277
|
if (type === 'inactive') tag = 'Session/Inactive/Seen';
|
|
280
278
|
if (tag) handle(SUPPORTABILITY_METRIC_CHANNEL, [tag, value], undefined, FEATURE_NAMES.metrics, this.ee);
|
|
281
279
|
}
|
|
280
|
+
getDuration() {
|
|
281
|
+
let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state;
|
|
282
|
+
let useUpdatedAt = arguments.length > 1 ? arguments[1] : undefined;
|
|
283
|
+
const startingTimestamp = data.expiresAt - this.expiresMs;
|
|
284
|
+
const endingTimestamp = !useUpdatedAt ? data.updatedAt : Date.now();
|
|
285
|
+
return endingTimestamp - startingTimestamp;
|
|
286
|
+
}
|
|
282
287
|
|
|
283
288
|
/**
|
|
284
289
|
* @param {number} futureMs - The number of ms to use to generate a future timestamp
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { ee } from '../event-emitter/contextual-ee';
|
|
6
6
|
import { handle } from '../event-emitter/handle';
|
|
7
7
|
import { FEATURE_NAMES } from '../../loaders/features/features';
|
|
8
|
+
import { dispatchGlobalEvent } from '../dispatch/global-event';
|
|
8
9
|
const bucketMap = {
|
|
9
10
|
stn: [FEATURE_NAMES.sessionTrace],
|
|
10
11
|
err: [FEATURE_NAMES.jserrors, FEATURE_NAMES.metrics],
|
|
@@ -18,18 +19,17 @@ const sentIds = new Set();
|
|
|
18
19
|
export function activateFeatures(flags, agentIdentifier) {
|
|
19
20
|
const sharedEE = ee.get(agentIdentifier);
|
|
20
21
|
if (!(flags && typeof flags === 'object')) return;
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
22
|
+
if (sentIds.has(agentIdentifier)) return;
|
|
23
|
+
Object.entries(flags).forEach(_ref => {
|
|
24
|
+
let [flag, num] = _ref;
|
|
25
|
+
if (bucketMap[flag]) {
|
|
26
|
+
bucketMap[flag].forEach(feat => {
|
|
27
|
+
if (!num) handle('block-' + flag, [], undefined, feat, sharedEE);else handle('feat-' + flag, [], undefined, feat, sharedEE);
|
|
28
|
+
handle('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE); // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
|
|
29
|
+
});
|
|
30
|
+
} else if (num) handle('feat-' + flag, [], undefined, undefined, sharedEE); // not sure what other flags are overlooked, but there's a test for ones not in the map --
|
|
31
|
+
activatedFeatures[flag] = Boolean(num);
|
|
32
|
+
});
|
|
33
33
|
|
|
34
34
|
// Let the features waiting on their respective flags know that RUM response was received and that any missing flags are interpreted as bad entitlement / "off".
|
|
35
35
|
// Hence, those features will not be hanging forever if their flags aren't included in the response.
|
|
@@ -40,5 +40,10 @@ export function activateFeatures(flags, agentIdentifier) {
|
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
sentIds.add(agentIdentifier);
|
|
43
|
+
|
|
44
|
+
// let any window level subscribers know that the agent is running
|
|
45
|
+
dispatchGlobalEvent({
|
|
46
|
+
loaded: true
|
|
47
|
+
});
|
|
43
48
|
}
|
|
44
49
|
export const activatedFeatures = {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { windowAddEventListener, documentAddEventListener } from '../event-listener/event-listener-opts';
|
|
2
|
-
function checkState() {
|
|
2
|
+
export function checkState() {
|
|
3
3
|
return typeof document === 'undefined' || document.readyState === 'complete';
|
|
4
4
|
}
|
|
5
5
|
export function onWindowLoad(cb, useCapture) {
|
|
@@ -55,24 +55,24 @@ export function gosNREUMOriginals() {
|
|
|
55
55
|
}
|
|
56
56
|
return nr;
|
|
57
57
|
}
|
|
58
|
-
export function
|
|
58
|
+
export function setNREUMInitializedAgent(id, newAgentInstance) {
|
|
59
59
|
let nr = gosNREUM();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
ms: now(),
|
|
65
|
-
date: new Date()
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
nr.initializedAgents = {
|
|
69
|
-
...externallySupplied,
|
|
70
|
-
[id]: {
|
|
71
|
-
...curr,
|
|
72
|
-
[target]: obj
|
|
73
|
-
}
|
|
60
|
+
nr.initializedAgents ??= {};
|
|
61
|
+
newAgentInstance.initializedAt = {
|
|
62
|
+
ms: now(),
|
|
63
|
+
date: new Date()
|
|
74
64
|
};
|
|
75
|
-
|
|
65
|
+
nr.initializedAgents[id] = newAgentInstance;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the agent instance under the associated identifier on the global newrelic object.
|
|
70
|
+
* @see setNREUMInitializedAgents
|
|
71
|
+
* @returns Existing agent instance under newrelic.initializedAgent[id], or undefined if it does not exist.
|
|
72
|
+
*/
|
|
73
|
+
export function getNREUMInitializedAgent(id) {
|
|
74
|
+
let nr = gosNREUM();
|
|
75
|
+
return nr.initializedAgents?.[id];
|
|
76
76
|
}
|
|
77
77
|
export function addToNREUM(fnName, fn) {
|
|
78
78
|
let nr = gosNREUM();
|
|
@@ -63,10 +63,12 @@ export class Aggregate extends AggregateBase {
|
|
|
63
63
|
} = getRuntime(this.agentIdentifier);
|
|
64
64
|
if (loaderType) this.storeSupportabilityMetrics("Generic/LoaderType/".concat(loaderType, "/Detected"));
|
|
65
65
|
if (distMethod) this.storeSupportabilityMetrics("Generic/DistMethod/".concat(distMethod, "/Detected"));
|
|
66
|
-
|
|
67
|
-
// frameworks on page
|
|
68
66
|
if (isBrowserScope) {
|
|
69
67
|
this.storeSupportabilityMetrics('Generic/Runtime/Browser/Detected');
|
|
68
|
+
const nonce = document?.currentScript?.nonce;
|
|
69
|
+
if (nonce && nonce !== '') {
|
|
70
|
+
this.storeSupportabilityMetrics('Generic/Runtime/Nonce/Detected');
|
|
71
|
+
}
|
|
70
72
|
|
|
71
73
|
// These SMs are used by the AppExp team
|
|
72
74
|
onDOMContentLoaded(() => {
|
|
@@ -25,6 +25,7 @@ import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
|
|
|
25
25
|
import { handle } from '../../../common/event-emitter/handle';
|
|
26
26
|
import { FEATURE_NAMES } from '../../../loaders/features/features';
|
|
27
27
|
import { RRWEB_VERSION } from "../../../common/constants/env.npm";
|
|
28
|
+
import { now } from '../../../common/timing/now';
|
|
28
29
|
export const AVG_COMPRESSION = 0.12;
|
|
29
30
|
export const RRWEB_EVENT_TYPES = {
|
|
30
31
|
DomContentLoaded: 0,
|
|
@@ -111,6 +112,9 @@ export class Aggregate extends AggregateBase {
|
|
|
111
112
|
|
|
112
113
|
/** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
|
|
113
114
|
this.lastMeta = undefined;
|
|
115
|
+
|
|
116
|
+
/** set by BCS response */
|
|
117
|
+
this.entitled = false;
|
|
114
118
|
const shouldSetup = getConfigurationValue(agentIdentifier, 'privacy.cookies_enabled') === true && getConfigurationValue(agentIdentifier, 'session_trace.enabled') === true;
|
|
115
119
|
|
|
116
120
|
/** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
|
|
@@ -148,6 +152,18 @@ export class Aggregate extends AggregateBase {
|
|
|
148
152
|
getPayload: this.prepareHarvest.bind(this),
|
|
149
153
|
raw: true
|
|
150
154
|
}, this);
|
|
155
|
+
registerHandler('recordReplay', () => {
|
|
156
|
+
// if it has aborted or BCS returned bad entitlements, do not allow
|
|
157
|
+
if (this.blocked || !this.entitled) return;
|
|
158
|
+
// if it isnt already (fully) initialized... initialize it
|
|
159
|
+
if (!recorder) this.initializeRecording(false, true, true);
|
|
160
|
+
// its been initialized and imported the recorder but its not recording (mode === off || error)
|
|
161
|
+
else if (this.mode !== MODE.FULL) this.switchToFull();
|
|
162
|
+
// if it gets all the way to here, that means a full session is already recording... do nothing
|
|
163
|
+
}, this.featureName, this.ee);
|
|
164
|
+
registerHandler('pauseReplay', () => {
|
|
165
|
+
this.forceStop(this.mode !== MODE.ERROR);
|
|
166
|
+
}, this.featureName, this.ee);
|
|
151
167
|
|
|
152
168
|
// Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
|
|
153
169
|
// This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
|
|
@@ -156,37 +172,42 @@ export class Aggregate extends AggregateBase {
|
|
|
156
172
|
this.errorNoticed = true;
|
|
157
173
|
// run once
|
|
158
174
|
if (this.mode === MODE.ERROR && globalScope?.document.visibilityState === 'visible') {
|
|
159
|
-
this.
|
|
160
|
-
// if the error was noticed AFTER the recorder was already imported....
|
|
161
|
-
if (recorder && this.initialized) {
|
|
162
|
-
this.stopRecording();
|
|
163
|
-
this.startRecording();
|
|
164
|
-
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
165
|
-
this.syncWithSessionManager({
|
|
166
|
-
sessionReplayMode: this.mode
|
|
167
|
-
});
|
|
168
|
-
}
|
|
175
|
+
this.switchToFull();
|
|
169
176
|
}
|
|
170
177
|
}, this.featureName, this.ee);
|
|
171
178
|
this.waitForFlags(['sr']).then(_ref => {
|
|
172
179
|
let [flagOn] = _ref;
|
|
173
|
-
|
|
180
|
+
this.entitled = flagOn;
|
|
181
|
+
this.initializeRecording(Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate'));
|
|
174
182
|
}).then(() => sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
|
|
175
183
|
|
|
176
184
|
this.drain();
|
|
177
185
|
}
|
|
178
186
|
}
|
|
187
|
+
switchToFull() {
|
|
188
|
+
this.mode = MODE.FULL;
|
|
189
|
+
// if the error was noticed AFTER the recorder was already imported....
|
|
190
|
+
if (recorder && this.initialized) {
|
|
191
|
+
this.stopRecording();
|
|
192
|
+
this.startRecording();
|
|
193
|
+
this.scheduler.startTimer(this.harvestTimeSeconds);
|
|
194
|
+
this.syncWithSessionManager({
|
|
195
|
+
sessionReplayMode: this.mode
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
179
199
|
|
|
180
200
|
/**
|
|
181
201
|
* Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
|
|
182
202
|
* @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
|
|
183
203
|
* @param {boolean} errorSample - the true/false state of the error sampling decision
|
|
184
204
|
* @param {boolean} fullSample - the true/false state of the full sampling decision
|
|
205
|
+
* @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
|
|
185
206
|
* @returns {void}
|
|
186
207
|
*/
|
|
187
|
-
async initializeRecording(
|
|
208
|
+
async initializeRecording(errorSample, fullSample, ignoreSession) {
|
|
188
209
|
this.initialized = true;
|
|
189
|
-
if (!
|
|
210
|
+
if (!this.entitled || this.recording) return;
|
|
190
211
|
const {
|
|
191
212
|
session
|
|
192
213
|
} = getRuntime(this.agentIdentifier);
|
|
@@ -196,7 +217,7 @@ export class Aggregate extends AggregateBase {
|
|
|
196
217
|
// we are not actively recording SR... DO NOT import or run the recording library
|
|
197
218
|
// session replay samples can only be decided on the first load of a session
|
|
198
219
|
// session replays can continue if already in progress
|
|
199
|
-
if (!session.isNew) {
|
|
220
|
+
if (!session.isNew && !ignoreSession) {
|
|
200
221
|
// inherit the mode of the existing session
|
|
201
222
|
this.mode = session.state.sessionReplayMode;
|
|
202
223
|
} else {
|
|
@@ -287,10 +308,12 @@ export class Aggregate extends AggregateBase {
|
|
|
287
308
|
this.events = this.events.slice(0, this.events.length - 1);
|
|
288
309
|
this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
|
|
289
310
|
}
|
|
311
|
+
const agentOffset = getRuntime(this.agentIdentifier).offset;
|
|
312
|
+
const relativeNow = now();
|
|
290
313
|
const firstEventTimestamp = this.events[0]?.timestamp; // from rrweb node
|
|
291
314
|
const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp; // from rrweb node
|
|
292
315
|
const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
|
|
293
|
-
const lastTimestamp = lastEventTimestamp ||
|
|
316
|
+
const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
|
|
294
317
|
return {
|
|
295
318
|
qs: {
|
|
296
319
|
browser_monitoring_key: info.licenseKey,
|
|
@@ -302,17 +325,20 @@ export class Aggregate extends AggregateBase {
|
|
|
302
325
|
content_encoding: 'gzip'
|
|
303
326
|
}),
|
|
304
327
|
'replay.firstTimestamp': firstTimestamp,
|
|
328
|
+
'replay.firstTimestampOffset': firstTimestamp - agentOffset,
|
|
305
329
|
'replay.lastTimestamp': lastTimestamp,
|
|
306
330
|
'replay.durationMs': lastTimestamp - firstTimestamp,
|
|
307
331
|
'replay.nodes': this.events.length,
|
|
332
|
+
'session.durationMs': agentRuntime.session.getDuration(),
|
|
308
333
|
agentVersion: agentRuntime.version,
|
|
309
334
|
session: agentRuntime.session.state.value,
|
|
335
|
+
rst: relativeNow,
|
|
310
336
|
hasMeta: this.hasMeta,
|
|
311
337
|
hasSnapshot: this.hasSnapshot,
|
|
312
338
|
hasError: this.hasError,
|
|
313
339
|
isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
|
|
314
340
|
decompressedBytes: this.payloadBytesEstimation,
|
|
315
|
-
'
|
|
341
|
+
'rrweb.version': RRWEB_VERSION
|
|
316
342
|
}, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
|
|
317
343
|
},
|
|
318
344
|
|
|
@@ -439,6 +465,20 @@ export class Aggregate extends AggregateBase {
|
|
|
439
465
|
return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000;
|
|
440
466
|
}
|
|
441
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Forces the agent into OFF mode so that changing tabs or navigating
|
|
470
|
+
* does not restart the recording. This is used when the customer calls
|
|
471
|
+
* the stopRecording API.
|
|
472
|
+
*/
|
|
473
|
+
forceStop(forceHarvest) {
|
|
474
|
+
if (forceHarvest) this.scheduler.runHarvest();
|
|
475
|
+
this.mode = MODE.OFF;
|
|
476
|
+
this.stopRecording();
|
|
477
|
+
this.syncWithSessionManager({
|
|
478
|
+
sessionReplayMode: this.mode
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
442
482
|
/** Abort the feature, once aborted it will not resume */
|
|
443
483
|
abort() {
|
|
444
484
|
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|