@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +10 -2
  3. package/dist/cjs/common/config/state/info.js +2 -1
  4. package/dist/cjs/common/config/state/init.js +2 -1
  5. package/dist/cjs/common/config/state/loader-config.js +2 -1
  6. package/dist/cjs/common/config/state/runtime.js +2 -1
  7. package/dist/cjs/common/constants/env.cdn.js +1 -1
  8. package/dist/cjs/common/constants/env.npm.js +1 -1
  9. package/dist/cjs/common/constants/runtime.js +3 -1
  10. package/dist/cjs/common/dispatch/global-event.js +19 -0
  11. package/dist/cjs/common/session/session-entity.js +14 -9
  12. package/dist/cjs/common/util/feature-flags.js +17 -12
  13. package/dist/cjs/common/window/load.js +1 -0
  14. package/dist/cjs/common/window/nreum.js +18 -17
  15. package/dist/cjs/features/metrics/aggregate/index.js +4 -2
  16. package/dist/cjs/features/session_replay/aggregate/index.js +56 -16
  17. package/dist/cjs/features/session_trace/aggregate/index.js +32 -18
  18. package/dist/cjs/features/spa/aggregate/index.js +2 -1
  19. package/dist/cjs/features/utils/aggregate-base.js +3 -1
  20. package/dist/cjs/loaders/agent-base.js +19 -0
  21. package/dist/cjs/loaders/agent.js +6 -4
  22. package/dist/cjs/loaders/api/api.js +10 -1
  23. package/dist/cjs/loaders/configure/configure.js +14 -13
  24. package/dist/cjs/loaders/configure/nonce.js +13 -0
  25. package/dist/cjs/loaders/configure/nonce.npm.js +2 -0
  26. package/dist/cjs/loaders/micro-agent.js +5 -4
  27. package/dist/esm/common/config/state/info.js +3 -2
  28. package/dist/esm/common/config/state/init.js +3 -2
  29. package/dist/esm/common/config/state/loader-config.js +3 -2
  30. package/dist/esm/common/config/state/runtime.js +3 -2
  31. package/dist/esm/common/constants/env.cdn.js +1 -1
  32. package/dist/esm/common/constants/env.npm.js +1 -1
  33. package/dist/esm/common/constants/runtime.js +1 -0
  34. package/dist/esm/common/dispatch/global-event.js +13 -0
  35. package/dist/esm/common/session/session-entity.js +14 -9
  36. package/dist/esm/common/util/feature-flags.js +17 -12
  37. package/dist/esm/common/window/load.js +1 -1
  38. package/dist/esm/common/window/nreum.js +16 -16
  39. package/dist/esm/features/metrics/aggregate/index.js +4 -2
  40. package/dist/esm/features/session_replay/aggregate/index.js +56 -16
  41. package/dist/esm/features/session_trace/aggregate/index.js +32 -18
  42. package/dist/esm/features/spa/aggregate/index.js +2 -1
  43. package/dist/esm/features/utils/aggregate-base.js +3 -1
  44. package/dist/esm/loaders/agent-base.js +19 -0
  45. package/dist/esm/loaders/agent.js +7 -5
  46. package/dist/esm/loaders/api/api.js +10 -1
  47. package/dist/esm/loaders/configure/configure.js +15 -14
  48. package/dist/esm/loaders/configure/nonce.js +11 -0
  49. package/dist/esm/loaders/configure/nonce.npm.js +1 -0
  50. package/dist/esm/loaders/micro-agent.js +6 -5
  51. package/dist/types/common/config/state/info.d.ts.map +1 -1
  52. package/dist/types/common/config/state/init.d.ts.map +1 -1
  53. package/dist/types/common/config/state/loader-config.d.ts.map +1 -1
  54. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  55. package/dist/types/common/constants/runtime.d.ts +1 -0
  56. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  57. package/dist/types/common/dispatch/global-event.d.ts +2 -0
  58. package/dist/types/common/dispatch/global-event.d.ts.map +1 -0
  59. package/dist/types/common/session/session-entity.d.ts +1 -0
  60. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  61. package/dist/types/common/util/feature-flags.d.ts.map +1 -1
  62. package/dist/types/common/window/load.d.ts +1 -0
  63. package/dist/types/common/window/load.d.ts.map +1 -1
  64. package/dist/types/common/window/nreum.d.ts +7 -1
  65. package/dist/types/common/window/nreum.d.ts.map +1 -1
  66. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  67. package/dist/types/features/session_replay/aggregate/index.d.ts +11 -1
  68. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  70. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  71. package/dist/types/loaders/agent-base.d.ts +13 -0
  72. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  73. package/dist/types/loaders/agent.d.ts.map +1 -1
  74. package/dist/types/loaders/api/api.d.ts +2 -0
  75. package/dist/types/loaders/api/api.d.ts.map +1 -1
  76. package/dist/types/loaders/configure/configure.d.ts +4 -11
  77. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  78. package/dist/types/loaders/configure/nonce.d.ts +1 -0
  79. package/dist/types/loaders/configure/nonce.d.ts.map +1 -0
  80. package/dist/types/loaders/configure/nonce.npm.d.ts +1 -0
  81. package/dist/types/loaders/configure/nonce.npm.d.ts.map +1 -0
  82. package/dist/types/loaders/micro-agent.d.ts +2 -2
  83. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  84. package/package.json +2 -1
  85. package/src/common/config/state/info.js +3 -2
  86. package/src/common/config/state/init.js +3 -2
  87. package/src/common/config/state/loader-config.js +3 -2
  88. package/src/common/config/state/runtime.js +3 -2
  89. package/src/common/constants/runtime.js +2 -0
  90. package/src/common/dispatch/global-event.js +12 -0
  91. package/src/common/session/session-entity.js +13 -9
  92. package/src/common/util/feature-flags.js +15 -12
  93. package/src/common/window/__mocks__/nreum.js +2 -1
  94. package/src/common/window/load.js +1 -1
  95. package/src/common/window/nreum.js +15 -18
  96. package/src/features/metrics/aggregate/index.js +5 -1
  97. package/src/features/session_replay/aggregate/index.js +63 -20
  98. package/src/features/session_trace/aggregate/index.js +31 -14
  99. package/src/features/spa/aggregate/index.js +2 -1
  100. package/src/features/utils/aggregate-base.js +1 -1
  101. package/src/loaders/agent-base.js +19 -0
  102. package/src/loaders/agent.js +5 -5
  103. package/src/loaders/api/api.js +12 -1
  104. package/src/loaders/configure/configure.js +17 -15
  105. package/src/loaders/configure/nonce.js +12 -0
  106. package/src/loaders/configure/nonce.npm.js +1 -0
  107. 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
- Object.assign(this, (0, _configure.configure)(this.agentIdentifier, options, options.loaderType || 'agent'));
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]?.[NR_FEATURES_REF_NAME]; // GC mem used internally by features
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
- function configure(agentIdentifier) {
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.setTopLevelCallers)();
58
- const api = (0, _api.setAPI)(agentIdentifier, forceDrain);
59
- (0, _nreum.gosNREUMInitializedAgents)(agentIdentifier, api, 'api');
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
+ })();
@@ -0,0 +1,2 @@
1
+ // We don't support setting automating the nonce attribute in the npm package
2
+ "use strict";
@@ -38,12 +38,14 @@ class MicroAgent extends _agentBase.AgentBase {
38
38
  agentIdentifier: this.agentIdentifier
39
39
  });
40
40
  this.features = {};
41
- Object.assign(this, (0, _configure.configure)(this.agentIdentifier, {
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
- (0, _nreum.gosNREUMInitializedAgents)(this.agentIdentifier, this.features, 'features');
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, gosNREUMInitializedAgents } from '../../window/nreum';
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
- gosNREUMInitializedAgents(id, _cache[id], 'info');
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 { gosNREUMInitializedAgents } from '../../window/nreum';
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
- gosNREUMInitializedAgents(id, _cache[id], 'config');
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 { gosNREUMInitializedAgents } from '../../window/nreum';
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
- gosNREUMInitializedAgents(id, _cache[id], 'loader_config');
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 { gosNREUMInitializedAgents } from '../../window/nreum';
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
- gosNREUMInitializedAgents(id, _cache[id], 'runtime');
34
+ const agentInst = getNREUMInitializedAgent(id);
35
+ if (agentInst) agentInst.runtime = _cache[id];
35
36
  }
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.246.1";
9
+ export const VERSION = "1.247.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.246.1";
9
+ export const VERSION = "1.247.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -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', this);
108
- this.collectSM('duration', this);
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', this);
125
- this.collectSM('duration', this);
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', this);
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', this);
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
- const startingTimestamp = data.expiresAt - data.expiresMs;
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 (!sentIds.has(agentIdentifier)) {
22
- Object.entries(flags).forEach(_ref => {
23
- let [flag, num] = _ref;
24
- if (bucketMap[flag]) {
25
- bucketMap[flag].forEach(feat => {
26
- if (!num) handle('block-' + flag, [], undefined, feat, sharedEE);else handle('feat-' + flag, [], undefined, feat, sharedEE);
27
- handle('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE); // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
28
- });
29
- } 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 --
30
- activatedFeatures[flag] = Boolean(num);
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 gosNREUMInitializedAgents(id, obj, target) {
58
+ export function setNREUMInitializedAgent(id, newAgentInstance) {
59
59
  let nr = gosNREUM();
60
- const externallySupplied = nr.initializedAgents || {};
61
- const curr = externallySupplied[id] || {};
62
- if (!Object.keys(curr).length) {
63
- curr.initializedAt = {
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
- return nr;
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.mode = MODE.FULL;
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
- return this.initializeRecording(flagOn, Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < getConfigurationValue(this.agentIdentifier, 'session_replay.sampling_rate'));
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(entitlements, errorSample, fullSample) {
208
+ async initializeRecording(errorSample, fullSample, ignoreSession) {
188
209
  this.initialized = true;
189
- if (!entitlements) return;
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 || getRuntime(this.agentIdentifier).offset + globalScope.performance.now();
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
- 'nr.rrweb.version': RRWEB_VERSION
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] : {};