@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
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
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.247.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.246.1...v1.247.0) (2023-11-14)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add basic support for deferring the browser agent loader script ([#800](https://github.com/newrelic/newrelic-browser-agent/issues/800)) ([92d864c](https://github.com/newrelic/newrelic-browser-agent/commit/92d864cb12a3076fd9b623fcd411d2dc9190110c))
12
+ * Add relative timestamps to Session Replay payloads ([#810](https://github.com/newrelic/newrelic-browser-agent/issues/810)) ([e4d1c70](https://github.com/newrelic/newrelic-browser-agent/commit/e4d1c701228e011d7c6f9d84cdc107044c69ce79))
13
+ * Add session durationMs to Session Replay payloads ([#792](https://github.com/newrelic/newrelic-browser-agent/issues/792)) ([3dfc4d4](https://github.com/newrelic/newrelic-browser-agent/commit/3dfc4d43fa978eeec47ebf432f8741562d0dd864))
14
+ * Enable SRI and nonce attributes for async chunks ([#805](https://github.com/newrelic/newrelic-browser-agent/issues/805)) ([fd9c3f3](https://github.com/newrelic/newrelic-browser-agent/commit/fd9c3f388f17353796ac2ebf18814353ca819dcf))
15
+ * Expose library versions used to capture session replay data ([#809](https://github.com/newrelic/newrelic-browser-agent/issues/809)) ([bc275ee](https://github.com/newrelic/newrelic-browser-agent/commit/bc275ee20242a5208358a0a77ac75e2b7cbd11c4))
16
+ * Session Replay API ([#803](https://github.com/newrelic/newrelic-browser-agent/issues/803)) ([12eb453](https://github.com/newrelic/newrelic-browser-agent/commit/12eb4530cfb5eb1e0a94d858485be0df40582c21))
17
+
6
18
  ## [1.246.1](https://github.com/newrelic/newrelic-browser-agent/compare/v1.246.0...v1.246.1) (2023-10-31)
7
19
 
8
20
 
package/README.md CHANGED
@@ -39,7 +39,7 @@ Before instrumenting your app using the NPM package, a Browser App should be con
39
39
 
40
40
  ## Instantiating the agent
41
41
 
42
- For best results, import and instantiate the `BrowserAgent` class as close to the top of the `head` element of your app's HTML output as possible. The specific location and method will vary based on your application's architecture or framework.
42
+ For best results, import and instantiate the `BrowserAgent` class as close to the top of the `head` element of your app's HTML output as possible. The specific location and method will vary based on your application's architecture or framework. See [Library Support](#library-support) for more information.
43
43
 
44
44
  Populate the `options` parameter using configuration values found in the the *Copy/Paste JavaScript* box in your browser app's *Application settings* page in New Relic One.
45
45
 
@@ -229,6 +229,14 @@ import { Agent } from '@newrelic/browser-agent/loaders/agent'
229
229
  import { Metrics } from '@newrelic/browser-agent/src/features/metrics'
230
230
  ```
231
231
 
232
+ ## Library Support
233
+
234
+ The browser agent is written to be agnostic to any JavaScript library or framework. The agent exposes a number of [API methods](https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/using-browser-apis/) that can be incorporated into libraries and frameworks. For example, export or make available the initialized agent and create a new error boundary in your react application that calls `browserAgent.noticeError()`.
235
+
236
+ ### Server-Side Rendering
237
+
238
+ A lot of new frameworks support the concept of server-side rendering the pages of an application to improve SEO and the performance of the user experience. The browser agent must be imported and instantiated within a browser context and will not work if executed on a server or in the server context of a server-side rendered application. These frameworks typically provide support for defining code that should only be run on the client. Check your frameworks documentation for more information.
239
+
232
240
  ## Disclaimers
233
241
 
234
242
  The session replay library shipping with this version of the browser agent is in *limited preview* and is not turned on by default. To use the feature, users will need to be part of the limited preview customer group and configure their browser application settings in the UI.
@@ -259,6 +267,6 @@ To all contributors, we thank you! Without your contribution, this project would
259
267
 
260
268
  ## License
261
269
 
262
- The Browser agent is licensed under the [Apache 2.0](http://apache.org/licenses/LICENSE-2.0.txt) License.
270
+ The Browser agent is licensed under the [Apache 2.0](https://apache.org/licenses/LICENSE-2.0.txt) License.
263
271
 
264
272
  The Browser agent also uses source code from third-party libraries. Full details on which libraries are used and the terms under which they are licensed can be found in the [third-party notices document](THIRD_PARTY_NOTICES.md).
@@ -46,5 +46,6 @@ function getInfo(id) {
46
46
  function setInfo(id, obj) {
47
47
  if (!id) throw new Error('All info objects require an agent identifier!');
48
48
  _cache[id] = (0, _configurable.getModeledObject)(obj, model);
49
- (0, _nreum.gosNREUMInitializedAgents)(id, _cache[id], 'info');
49
+ const agentInst = (0, _nreum.getNREUMInitializedAgent)(id);
50
+ if (agentInst) agentInst.info = _cache[id];
50
51
  }
@@ -173,7 +173,8 @@ function getConfiguration(id) {
173
173
  function setConfiguration(id, obj) {
174
174
  if (!id) throw new Error(missingAgentIdError);
175
175
  _cache[id] = (0, _configurable.getModeledObject)(obj, model());
176
- (0, _nreum.gosNREUMInitializedAgents)(id, _cache[id], 'config');
176
+ const agentInst = (0, _nreum.getNREUMInitializedAgent)(id);
177
+ if (agentInst) agentInst.init = _cache[id];
177
178
  }
178
179
  function getConfigurationValue(id, path) {
179
180
  if (!id) throw new Error(missingAgentIdError);
@@ -24,5 +24,6 @@ function getLoaderConfig(id) {
24
24
  function setLoaderConfig(id, obj) {
25
25
  if (!id) throw new Error('All loader-config objects require an agent identifier!');
26
26
  _cache[id] = (0, _configurable.getModeledObject)(obj, model);
27
- (0, _nreum.gosNREUMInitializedAgents)(id, _cache[id], 'loader_config');
27
+ const agentInst = (0, _nreum.getNREUMInitializedAgent)(id);
28
+ if (agentInst) agentInst.loader_config = _cache[id];
28
29
  }
@@ -38,5 +38,6 @@ function getRuntime(id) {
38
38
  function setRuntime(id, obj) {
39
39
  if (!id) throw new Error('All runtime objects require an agent identifier!');
40
40
  _cache[id] = (0, _configurable.getModeledObject)(obj, model);
41
- (0, _nreum.gosNREUMInitializedAgents)(id, _cache[id], 'runtime');
41
+ const agentInst = (0, _nreum.getNREUMInitializedAgent)(id);
42
+ if (agentInst) agentInst.runtime = _cache[id];
42
43
  }
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.246.1";
15
+ const VERSION = "1.247.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.DIST_METHOD = exports.BUILD_EN
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.246.1";
15
+ const VERSION = "1.247.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.supportsSendBeacon = exports.offset = exports.isiOS = exports.isWorkerScope = exports.isIE = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
6
+ exports.supportsSendBeacon = exports.offset = exports.loadedAsDeferredBrowserScript = exports.isiOS = exports.isWorkerScope = exports.isIE = exports.isBrowserScope = exports.initiallyHidden = exports.initialLocation = exports.iOSBelow16 = exports.globalScope = exports.ffVersion = void 0;
7
7
  /**
8
8
  * @file Contains constants about the environment the agent is running
9
9
  * within. These values are derived at the time the agent is first loaded.
@@ -24,6 +24,8 @@ const isWorkerScope = typeof WorkerGlobalScope !== 'undefined' && (typeof self !
24
24
  exports.isWorkerScope = isWorkerScope;
25
25
  const globalScope = isBrowserScope ? window : typeof WorkerGlobalScope !== 'undefined' && (typeof self !== 'undefined' && self instanceof WorkerGlobalScope && self || typeof globalThis !== 'undefined' && globalThis instanceof WorkerGlobalScope && globalThis);
26
26
  exports.globalScope = globalScope;
27
+ const loadedAsDeferredBrowserScript = globalScope?.document?.readyState === 'complete';
28
+ exports.loadedAsDeferredBrowserScript = loadedAsDeferredBrowserScript;
27
29
  const initiallyHidden = Boolean(globalScope?.document?.visibilityState === 'hidden');
28
30
  exports.initiallyHidden = initiallyHidden;
29
31
  const initialLocation = '' + globalScope?.location;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.dispatchGlobalEvent = dispatchGlobalEvent;
7
+ var _runtime = require("../constants/runtime");
8
+ const GLOBAL_EVENT_NAMESPACE = 'newrelic';
9
+ function dispatchGlobalEvent() {
10
+ let detail = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
11
+ try {
12
+ _runtime.globalScope.dispatchEvent(new CustomEvent(GLOBAL_EVENT_NAMESPACE, {
13
+ detail
14
+ }));
15
+ } catch (err) {
16
+ // something happened... dispatchEvent or CustomEvent might not be supported
17
+ // decide what to do about it here
18
+ }
19
+ }
@@ -113,8 +113,8 @@ class SessionEntity {
113
113
  this.expiresTimer = new _timer.Timer({
114
114
  // When the inactive timer ends, collect a SM and reset the session
115
115
  onEnd: () => {
116
- this.collectSM('expired', this);
117
- this.collectSM('duration', this);
116
+ this.collectSM('expired');
117
+ this.collectSM('duration');
118
118
  this.reset();
119
119
  }
120
120
  }, this.state.expiresAt - Date.now());
@@ -130,8 +130,8 @@ class SessionEntity {
130
130
  this.inactiveTimer = new _interactionTimer.InteractionTimer({
131
131
  // When the inactive timer ends, collect a SM and reset the session
132
132
  onEnd: () => {
133
- this.collectSM('inactive', this);
134
- this.collectSM('duration', this);
133
+ this.collectSM('inactive');
134
+ this.collectSM('duration');
135
135
  this.reset();
136
136
  },
137
137
  // When the inactive timer refreshes, it will update the storage values with an update timestamp
@@ -181,14 +181,14 @@ class SessionEntity {
181
181
  if (this.isInvalid(obj)) return {};
182
182
  // if the session expires, collect a SM count before resetting
183
183
  if (this.isExpired(obj.expiresAt)) {
184
- this.collectSM('expired', this);
184
+ this.collectSM('expired');
185
185
  this.collectSM('duration', obj, true);
186
186
  return this.reset();
187
187
  }
188
188
  // if "inactive" timer is expired at "read" time -- esp. initial read -- reset
189
189
  // collect a SM count before resetting
190
190
  if (this.isExpired(obj.inactiveAt)) {
191
- this.collectSM('inactive', this);
191
+ this.collectSM('inactive');
192
192
  this.collectSM('duration', obj, true);
193
193
  return this.reset();
194
194
  }
@@ -279,15 +279,20 @@ class SessionEntity {
279
279
  collectSM(type, data, useUpdatedAt) {
280
280
  let value, tag;
281
281
  if (type === 'duration') {
282
- const startingTimestamp = data.expiresAt - data.expiresMs;
283
- const endingTimestamp = useUpdatedAt ? data.updatedAt : Date.now();
284
- value = endingTimestamp - startingTimestamp;
282
+ value = this.getDuration(data, useUpdatedAt);
285
283
  tag = 'Session/Duration/Ms';
286
284
  }
287
285
  if (type === 'expired') tag = 'Session/Expired/Seen';
288
286
  if (type === 'inactive') tag = 'Session/Inactive/Seen';
289
287
  if (tag) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, [tag, value], undefined, _features.FEATURE_NAMES.metrics, this.ee);
290
288
  }
289
+ getDuration() {
290
+ let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state;
291
+ let useUpdatedAt = arguments.length > 1 ? arguments[1] : undefined;
292
+ const startingTimestamp = data.expiresAt - this.expiresMs;
293
+ const endingTimestamp = !useUpdatedAt ? data.updatedAt : Date.now();
294
+ return endingTimestamp - startingTimestamp;
295
+ }
291
296
 
292
297
  /**
293
298
  * @param {number} futureMs - The number of ms to use to generate a future timestamp
@@ -8,6 +8,7 @@ exports.activatedFeatures = void 0;
8
8
  var _contextualEe = require("../event-emitter/contextual-ee");
9
9
  var _handle = require("../event-emitter/handle");
10
10
  var _features = require("../../loaders/features/features");
11
+ var _globalEvent = require("../dispatch/global-event");
11
12
  /*
12
13
  * Copyright 2020 New Relic Corporation. All rights reserved.
13
14
  * SPDX-License-Identifier: Apache-2.0
@@ -26,18 +27,17 @@ const sentIds = new Set();
26
27
  function activateFeatures(flags, agentIdentifier) {
27
28
  const sharedEE = _contextualEe.ee.get(agentIdentifier);
28
29
  if (!(flags && typeof flags === 'object')) return;
29
- if (!sentIds.has(agentIdentifier)) {
30
- Object.entries(flags).forEach(_ref => {
31
- let [flag, num] = _ref;
32
- if (bucketMap[flag]) {
33
- bucketMap[flag].forEach(feat => {
34
- if (!num) (0, _handle.handle)('block-' + flag, [], undefined, feat, sharedEE);else (0, _handle.handle)('feat-' + flag, [], undefined, feat, sharedEE);
35
- (0, _handle.handle)('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE); // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
36
- });
37
- } else if (num) (0, _handle.handle)('feat-' + flag, [], undefined, undefined, sharedEE); // not sure what other flags are overlooked, but there's a test for ones not in the map --
38
- activatedFeatures[flag] = Boolean(num);
39
- });
40
- }
30
+ if (sentIds.has(agentIdentifier)) return;
31
+ Object.entries(flags).forEach(_ref => {
32
+ let [flag, num] = _ref;
33
+ if (bucketMap[flag]) {
34
+ bucketMap[flag].forEach(feat => {
35
+ if (!num) (0, _handle.handle)('block-' + flag, [], undefined, feat, sharedEE);else (0, _handle.handle)('feat-' + flag, [], undefined, feat, sharedEE);
36
+ (0, _handle.handle)('rumresp-' + flag, [Boolean(num)], undefined, feat, sharedEE); // this is a duplicate of feat-/block- but makes awaiting for 1 event easier than 2
37
+ });
38
+ } else if (num) (0, _handle.handle)('feat-' + flag, [], undefined, undefined, sharedEE); // not sure what other flags are overlooked, but there's a test for ones not in the map --
39
+ activatedFeatures[flag] = Boolean(num);
40
+ });
41
41
 
42
42
  // 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".
43
43
  // Hence, those features will not be hanging forever if their flags aren't included in the response.
@@ -48,6 +48,11 @@ function activateFeatures(flags, agentIdentifier) {
48
48
  }
49
49
  });
50
50
  sentIds.add(agentIdentifier);
51
+
52
+ // let any window level subscribers know that the agent is running
53
+ (0, _globalEvent.dispatchGlobalEvent)({
54
+ loaded: true
55
+ });
51
56
  }
52
57
  const activatedFeatures = {};
53
58
  exports.activatedFeatures = activatedFeatures;
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.checkState = checkState;
6
7
  exports.onDOMContentLoaded = onDOMContentLoaded;
7
8
  exports.onWindowLoad = onWindowLoad;
8
9
  var _eventListenerOpts = require("../event-listener/event-listener-opts");
@@ -6,13 +6,14 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.NREUMinitialized = NREUMinitialized;
7
7
  exports.addToNREUM = addToNREUM;
8
8
  exports.defaults = void 0;
9
+ exports.getNREUMInitializedAgent = getNREUMInitializedAgent;
9
10
  exports.gosCDN = gosCDN;
10
11
  exports.gosNREUM = gosNREUM;
11
12
  exports.gosNREUMInfo = gosNREUMInfo;
12
13
  exports.gosNREUMInit = gosNREUMInit;
13
- exports.gosNREUMInitializedAgents = gosNREUMInitializedAgents;
14
14
  exports.gosNREUMLoaderConfig = gosNREUMLoaderConfig;
15
15
  exports.gosNREUMOriginals = gosNREUMOriginals;
16
+ exports.setNREUMInitializedAgent = setNREUMInitializedAgent;
16
17
  var _now = require("../timing/now");
17
18
  var _runtime = require("../constants/runtime");
18
19
  const defaults = {
@@ -71,24 +72,24 @@ function gosNREUMOriginals() {
71
72
  }
72
73
  return nr;
73
74
  }
74
- function gosNREUMInitializedAgents(id, obj, target) {
75
+ function setNREUMInitializedAgent(id, newAgentInstance) {
75
76
  let nr = gosNREUM();
76
- const externallySupplied = nr.initializedAgents || {};
77
- const curr = externallySupplied[id] || {};
78
- if (!Object.keys(curr).length) {
79
- curr.initializedAt = {
80
- ms: (0, _now.now)(),
81
- date: new Date()
82
- };
83
- }
84
- nr.initializedAgents = {
85
- ...externallySupplied,
86
- [id]: {
87
- ...curr,
88
- [target]: obj
89
- }
77
+ nr.initializedAgents ??= {};
78
+ newAgentInstance.initializedAt = {
79
+ ms: (0, _now.now)(),
80
+ date: new Date()
90
81
  };
91
- return nr;
82
+ nr.initializedAgents[id] = newAgentInstance;
83
+ }
84
+
85
+ /**
86
+ * Get the agent instance under the associated identifier on the global newrelic object.
87
+ * @see setNREUMInitializedAgents
88
+ * @returns Existing agent instance under newrelic.initializedAgent[id], or undefined if it does not exist.
89
+ */
90
+ function getNREUMInitializedAgent(id) {
91
+ let nr = gosNREUM();
92
+ return nr.initializedAgents?.[id];
92
93
  }
93
94
  function addToNREUM(fnName, fn) {
94
95
  let nr = gosNREUM();
@@ -69,10 +69,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
69
69
  } = (0, _config.getRuntime)(this.agentIdentifier);
70
70
  if (loaderType) this.storeSupportabilityMetrics("Generic/LoaderType/".concat(loaderType, "/Detected"));
71
71
  if (distMethod) this.storeSupportabilityMetrics("Generic/DistMethod/".concat(distMethod, "/Detected"));
72
-
73
- // frameworks on page
74
72
  if (_runtime.isBrowserScope) {
75
73
  this.storeSupportabilityMetrics('Generic/Runtime/Browser/Detected');
74
+ const nonce = document?.currentScript?.nonce;
75
+ if (nonce && nonce !== '') {
76
+ this.storeSupportabilityMetrics('Generic/Runtime/Nonce/Detected');
77
+ }
76
78
 
77
79
  // These SMs are used by the AppExp team
78
80
  (0, _load.onDOMContentLoaded)(() => {
@@ -19,6 +19,7 @@ var _constants2 = require("../../metrics/constants");
19
19
  var _handle = require("../../../common/event-emitter/handle");
20
20
  var _features = require("../../../loaders/features/features");
21
21
  var _env = require("../../../common/constants/env.npm");
22
+ var _now = require("../../../common/timing/now");
22
23
  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); }
23
24
  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; } /*
24
25
  * Copyright 2023 New Relic Corporation. All rights reserved.
@@ -120,6 +121,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
120
121
 
121
122
  /** 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 */
122
123
  this.lastMeta = undefined;
124
+
125
+ /** set by BCS response */
126
+ this.entitled = false;
123
127
  const shouldSetup = (0, _config.getConfigurationValue)(agentIdentifier, 'privacy.cookies_enabled') === true && (0, _config.getConfigurationValue)(agentIdentifier, 'session_trace.enabled') === true;
124
128
 
125
129
  /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
@@ -157,6 +161,18 @@ class Aggregate extends _aggregateBase.AggregateBase {
157
161
  getPayload: this.prepareHarvest.bind(this),
158
162
  raw: true
159
163
  }, this);
164
+ (0, _registerHandler.registerHandler)('recordReplay', () => {
165
+ // if it has aborted or BCS returned bad entitlements, do not allow
166
+ if (this.blocked || !this.entitled) return;
167
+ // if it isnt already (fully) initialized... initialize it
168
+ if (!recorder) this.initializeRecording(false, true, true);
169
+ // its been initialized and imported the recorder but its not recording (mode === off || error)
170
+ else if (this.mode !== _sessionEntity.MODE.FULL) this.switchToFull();
171
+ // if it gets all the way to here, that means a full session is already recording... do nothing
172
+ }, this.featureName, this.ee);
173
+ (0, _registerHandler.registerHandler)('pauseReplay', () => {
174
+ this.forceStop(this.mode !== _sessionEntity.MODE.ERROR);
175
+ }, this.featureName, this.ee);
160
176
 
161
177
  // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
162
178
  // This was to ensure that all errors, including those on the page before load and those handled with "noticeError" are accounted for. Needs evalulation
@@ -165,37 +181,42 @@ class Aggregate extends _aggregateBase.AggregateBase {
165
181
  this.errorNoticed = true;
166
182
  // run once
167
183
  if (this.mode === _sessionEntity.MODE.ERROR && _runtime.globalScope?.document.visibilityState === 'visible') {
168
- this.mode = _sessionEntity.MODE.FULL;
169
- // if the error was noticed AFTER the recorder was already imported....
170
- if (recorder && this.initialized) {
171
- this.stopRecording();
172
- this.startRecording();
173
- this.scheduler.startTimer(this.harvestTimeSeconds);
174
- this.syncWithSessionManager({
175
- sessionReplayMode: this.mode
176
- });
177
- }
184
+ this.switchToFull();
178
185
  }
179
186
  }, this.featureName, this.ee);
180
187
  this.waitForFlags(['sr']).then(_ref => {
181
188
  let [flagOn] = _ref;
182
- return this.initializeRecording(flagOn, Math.random() * 100 < (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay.sampling_rate'));
189
+ this.entitled = flagOn;
190
+ this.initializeRecording(Math.random() * 100 < (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay.error_sampling_rate'), Math.random() * 100 < (0, _config.getConfigurationValue)(this.agentIdentifier, 'session_replay.sampling_rate'));
183
191
  }).then(() => _sharedChannel.sharedChannel.onReplayReady(this.mode)); // notify watchers that replay started with the mode
184
192
 
185
193
  this.drain();
186
194
  }
187
195
  }
196
+ switchToFull() {
197
+ this.mode = _sessionEntity.MODE.FULL;
198
+ // if the error was noticed AFTER the recorder was already imported....
199
+ if (recorder && this.initialized) {
200
+ this.stopRecording();
201
+ this.startRecording();
202
+ this.scheduler.startTimer(this.harvestTimeSeconds);
203
+ this.syncWithSessionManager({
204
+ sessionReplayMode: this.mode
205
+ });
206
+ }
207
+ }
188
208
 
189
209
  /**
190
210
  * Evaluate entitlements and sampling before starting feature mechanics, importing and configuring recording library, and setting storage state
191
211
  * @param {boolean} entitlements - the true/false state of the "sr" flag from RUM response
192
212
  * @param {boolean} errorSample - the true/false state of the error sampling decision
193
213
  * @param {boolean} fullSample - the true/false state of the full sampling decision
214
+ * @param {boolean} ignoreSession - whether to force the method to ignore the session state and use just the sample flags
194
215
  * @returns {void}
195
216
  */
196
- async initializeRecording(entitlements, errorSample, fullSample) {
217
+ async initializeRecording(errorSample, fullSample, ignoreSession) {
197
218
  this.initialized = true;
198
- if (!entitlements) return;
219
+ if (!this.entitled || this.recording) return;
199
220
  const {
200
221
  session
201
222
  } = (0, _config.getRuntime)(this.agentIdentifier);
@@ -205,7 +226,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
205
226
  // we are not actively recording SR... DO NOT import or run the recording library
206
227
  // session replay samples can only be decided on the first load of a session
207
228
  // session replays can continue if already in progress
208
- if (!session.isNew) {
229
+ if (!session.isNew && !ignoreSession) {
209
230
  // inherit the mode of the existing session
210
231
  this.mode = session.state.sessionReplayMode;
211
232
  } else {
@@ -296,10 +317,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
296
317
  this.events = this.events.slice(0, this.events.length - 1);
297
318
  this.hasMeta = !!this.events.find(x => x.type === RRWEB_EVENT_TYPES.Meta);
298
319
  }
320
+ const agentOffset = (0, _config.getRuntime)(this.agentIdentifier).offset;
321
+ const relativeNow = (0, _now.now)();
299
322
  const firstEventTimestamp = this.events[0]?.timestamp; // from rrweb node
300
323
  const lastEventTimestamp = this.events[this.events.length - 1]?.timestamp; // from rrweb node
301
324
  const firstTimestamp = firstEventTimestamp || this.cycleTimestamp;
302
- const lastTimestamp = lastEventTimestamp || (0, _config.getRuntime)(this.agentIdentifier).offset + _runtime.globalScope.performance.now();
325
+ const lastTimestamp = lastEventTimestamp || agentOffset + relativeNow;
303
326
  return {
304
327
  qs: {
305
328
  browser_monitoring_key: info.licenseKey,
@@ -311,17 +334,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
311
334
  content_encoding: 'gzip'
312
335
  }),
313
336
  'replay.firstTimestamp': firstTimestamp,
337
+ 'replay.firstTimestampOffset': firstTimestamp - agentOffset,
314
338
  'replay.lastTimestamp': lastTimestamp,
315
339
  'replay.durationMs': lastTimestamp - firstTimestamp,
316
340
  'replay.nodes': this.events.length,
341
+ 'session.durationMs': agentRuntime.session.getDuration(),
317
342
  agentVersion: agentRuntime.version,
318
343
  session: agentRuntime.session.state.value,
344
+ rst: relativeNow,
319
345
  hasMeta: this.hasMeta,
320
346
  hasSnapshot: this.hasSnapshot,
321
347
  hasError: this.hasError,
322
348
  isFirstChunk: agentRuntime.session.state.sessionReplaySentFirstChunk === false,
323
349
  decompressedBytes: this.payloadBytesEstimation,
324
- 'nr.rrweb.version': _env.RRWEB_VERSION
350
+ 'rrweb.version': _env.RRWEB_VERSION
325
351
  }, MAX_PAYLOAD_SIZE - this.payloadBytesEstimation).substring(1) // remove the leading '&'
326
352
  },
327
353
 
@@ -448,6 +474,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
448
474
  return this.estimateCompression(this.payloadBytesEstimation + newBytes) + 1000;
449
475
  }
450
476
 
477
+ /**
478
+ * Forces the agent into OFF mode so that changing tabs or navigating
479
+ * does not restart the recording. This is used when the customer calls
480
+ * the stopRecording API.
481
+ */
482
+ forceStop(forceHarvest) {
483
+ if (forceHarvest) this.scheduler.runHarvest();
484
+ this.mode = _sessionEntity.MODE.OFF;
485
+ this.stopRecording();
486
+ this.syncWithSessionManager({
487
+ sessionReplayMode: this.mode
488
+ });
489
+ }
490
+
451
491
  /** Abort the feature, once aborted it will not resume */
452
492
  abort() {
453
493
  let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -89,30 +89,44 @@ class Aggregate extends _aggregateBase.AggregateBase {
89
89
  break;
90
90
  }
91
91
  };
92
+ let seenAnError = false;
93
+ let mostRecentModeKnown;
94
+ this.ee.on(_sessionEntity.SESSION_EVENTS.UPDATE, (eventType, sessionState) => {
95
+ // this will only have an effect if ST is NOT already in full mode
96
+ if (sessionState.sessionReplayMode === _sessionEntity.MODE.FULL) switchToFull();
97
+ });
98
+
99
+ /**
100
+ * The goal of switchToFull is to take external input to trigger a change from off or error to full.
101
+ * It will have no effect if already running in full mode.
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
+ */
104
+ const switchToFull = () => {
105
+ if (mostRecentModeKnown !== _sessionEntity.MODE.FULL) {
106
+ const prevMode = mostRecentModeKnown;
107
+ mostRecentModeKnown = _sessionEntity.MODE.FULL;
108
+ sessionEntity.write({
109
+ sessionTraceMode: mostRecentModeKnown
110
+ });
111
+ this.isStandalone = false;
112
+ if (prevMode === _sessionEntity.MODE.ERROR && this.#scheduler) {
113
+ this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
114
+ this.#scheduler.runHarvest({
115
+ needResponse: true
116
+ });
117
+ } else {
118
+ controlTraceOp(_sessionEntity.MODE.FULL);
119
+ }
120
+ }
121
+ };
92
122
  if (!sessionEntity) {
93
123
  // Since session manager isn't around, do the old Trace behavior of waiting for RUM response to decide feature activation.
94
124
  this.isStandalone = true;
95
125
  (0, _registerHandler.registerHandler)('rumresp-stn', on => controlTraceOp(on), this.featureName, this.ee);
96
126
  } else {
97
- let seenAnError = false;
98
- let mostRecentModeKnown;
99
127
  (0, _registerHandler.registerHandler)('errorAgg', () => {
100
- // Switch to full capture mode on next harvest on first exception thrown only. Only done once so that sessionTraceMode isn't constantly overwritten after decision block.
101
- if (!seenAnError) {
102
- seenAnError = true;
103
- /* If this cb executes before Trace has started, then no further action needed. But if...
104
- - startTracing already ran under ERROR mode, then it will NOT have kicked off the harvest-scheduler so that needs to be done & switch mode.
105
- - startTracing never ran because mode is OFF or Replay aborted or Traced turned off elsewhere OR trace already in FULL, then this should do nothing. */
106
- if (sessionEntity.state.sessionTraceMode === _sessionEntity.MODE.ERROR && this.#scheduler) {
107
- sessionEntity.write({
108
- sessionTraceMode: mostRecentModeKnown = _sessionEntity.MODE.FULL
109
- });
110
- this.trimSTNs(ERROR_MODE_SECONDS_WINDOW); // up until now, Trace would've been just buffering nodes up to max, which needs to be trimmed to last X seconds
111
- this.#scheduler.runHarvest({
112
- needResponse: true
113
- });
114
- }
115
- }
128
+ seenAnError = true;
129
+ switchToFull();
116
130
  }, this.featureName, this.ee);
117
131
  const stopTracePerm = () => {
118
132
  if (sessionEntity.state.sessionTraceMode !== _sessionEntity.MODE.OFF) sessionEntity.write({
@@ -22,6 +22,7 @@ var _aggregateBase = require("../../utils/aggregate-base");
22
22
  var _firstContentfulPaint = require("../../../common/vitals/first-contentful-paint");
23
23
  var _firstPaint = require("../../../common/vitals/first-paint");
24
24
  var _bundleId = require("../../../common/ids/bundle-id");
25
+ var _runtime = require("../../../common/constants/runtime");
25
26
  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); }
26
27
  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; }
27
28
  /*
@@ -186,7 +187,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
186
187
  var ev = args[0];
187
188
  var evName = ev.type;
188
189
  var eventNode = ev["__nrNode:".concat(_bundleId.bundleId)];
189
- if (!state.pageLoaded && evName === 'load' && eventSource === window) {
190
+ if (!state.pageLoaded && (evName === 'load' && eventSource === window || _runtime.loadedAsDeferredBrowserScript)) {
190
191
  state.pageLoaded = true;
191
192
  // set to null so prevNode is set correctly
192
193
  this.prevNode = state.currentNode = null;
@@ -49,7 +49,9 @@ class AggregateBase extends _featureBase.FeatureBase {
49
49
  } catch (err) {
50
50
  // do nothing
51
51
  }
52
- (0, _configure.configure)(this.agentIdentifier, {
52
+ (0, _configure.configure)({
53
+ agentIdentifier: this.agentIdentifier
54
+ }, {
53
55
  ...(0, _nreum.gosCDN)(),
54
56
  info: {
55
57
  ...(0, _nreum.gosCDN)().info,
@@ -106,5 +106,24 @@ class AgentBase {
106
106
  start(featureNames) {
107
107
  (0, _console.warn)('Call to agent api addRelease failed. The agent is not currently initialized.');
108
108
  }
109
+
110
+ /**
111
+ * Forces a replay to record. If a replay is already actively recording, this call will be ignored.
112
+ * If a recording has not been started, a new one will be created. If a recording has been started, but is currently not recording, it will resume recording.
113
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
114
+ */
115
+ recordReplay() {
116
+ (0, _console.warn)('Call to agent api recordReplay failed. The agent is not currently initialized.');
117
+ }
118
+
119
+ /**
120
+ * Forces an active replay to pause recording. If a replay is already actively recording, this call will cause the recording to pause.
121
+ * If a recording is not currently recording, this call will be ignored. This API will pause both manual and automatic replays that are in progress.
122
+ * The only way to resume recording after manually pausing a replay is to manually record again using the recordReplay() API.
123
+ * {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordReplay/}
124
+ */
125
+ pauseReplay() {
126
+ (0, _console.warn)('Call to agent api pauseReplay failed. The agent is not currently initialized.');
127
+ }
109
128
  }
110
129
  exports.AgentBase = AgentBase;