@newrelic/browser-agent 1.267.0 → 1.269.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 (104) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +2 -1
  3. package/dist/cjs/common/config/init.js +3 -0
  4. package/dist/cjs/common/constants/env.cdn.js +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/dom/iframe.js +10 -0
  7. package/dist/cjs/common/dom/selector-path.js +48 -0
  8. package/dist/cjs/common/event-listener/event-listener-opts.js +4 -26
  9. package/dist/cjs/common/timing/time-keeper.js +9 -0
  10. package/dist/cjs/common/util/stringify.js +1 -1
  11. package/dist/cjs/features/generic_events/aggregate/index.js +80 -9
  12. package/dist/cjs/features/generic_events/aggregate/user-actions/aggregated-user-action.js +39 -0
  13. package/dist/cjs/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +77 -0
  14. package/dist/cjs/features/generic_events/constants.js +6 -2
  15. package/dist/cjs/features/generic_events/instrument/index.js +12 -1
  16. package/dist/cjs/features/jserrors/aggregate/index.js +23 -3
  17. package/dist/cjs/features/jserrors/aggregate/internal-errors.js +42 -0
  18. package/dist/cjs/features/logging/aggregate/index.js +10 -2
  19. package/dist/cjs/features/metrics/aggregate/index.js +2 -1
  20. package/dist/cjs/features/page_view_event/aggregate/index.js +1 -1
  21. package/dist/cjs/features/session_replay/aggregate/index.js +5 -3
  22. package/dist/cjs/features/session_trace/aggregate/index.js +11 -9
  23. package/dist/cjs/features/spa/instrument/index.js +4 -0
  24. package/dist/cjs/loaders/agent-base.js +1 -0
  25. package/dist/cjs/loaders/micro-agent.js +1 -1
  26. package/dist/esm/common/config/init.js +3 -0
  27. package/dist/esm/common/constants/env.cdn.js +1 -1
  28. package/dist/esm/common/constants/env.npm.js +1 -1
  29. package/dist/esm/common/dom/iframe.js +4 -0
  30. package/dist/esm/common/dom/selector-path.js +41 -0
  31. package/dist/esm/common/event-listener/event-listener-opts.js +4 -27
  32. package/dist/esm/common/timing/time-keeper.js +9 -0
  33. package/dist/esm/common/util/stringify.js +1 -1
  34. package/dist/esm/features/generic_events/aggregate/index.js +82 -11
  35. package/dist/esm/features/generic_events/aggregate/user-actions/aggregated-user-action.js +32 -0
  36. package/dist/esm/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +70 -0
  37. package/dist/esm/features/generic_events/constants.js +5 -1
  38. package/dist/esm/features/generic_events/instrument/index.js +14 -3
  39. package/dist/esm/features/jserrors/aggregate/index.js +23 -3
  40. package/dist/esm/features/jserrors/aggregate/internal-errors.js +36 -0
  41. package/dist/esm/features/logging/aggregate/index.js +10 -2
  42. package/dist/esm/features/metrics/aggregate/index.js +2 -1
  43. package/dist/esm/features/page_view_event/aggregate/index.js +1 -1
  44. package/dist/esm/features/session_replay/aggregate/index.js +5 -3
  45. package/dist/esm/features/session_trace/aggregate/index.js +11 -9
  46. package/dist/esm/features/spa/instrument/index.js +4 -0
  47. package/dist/esm/loaders/agent-base.js +1 -0
  48. package/dist/esm/loaders/micro-agent.js +1 -1
  49. package/dist/types/common/config/init.d.ts.map +1 -1
  50. package/dist/types/common/dom/iframe.d.ts +2 -0
  51. package/dist/types/common/dom/iframe.d.ts.map +1 -0
  52. package/dist/types/common/dom/selector-path.d.ts +2 -0
  53. package/dist/types/common/dom/selector-path.d.ts.map +1 -0
  54. package/dist/types/common/event-listener/event-listener-opts.d.ts +2 -2
  55. package/dist/types/common/event-listener/event-listener-opts.d.ts.map +1 -1
  56. package/dist/types/common/timing/time-keeper.d.ts +6 -0
  57. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  58. package/dist/types/features/generic_events/aggregate/index.d.ts +16 -1
  59. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  60. package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts +22 -0
  61. package/dist/types/features/generic_events/aggregate/user-actions/aggregated-user-action.d.ts.map +1 -0
  62. package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts +12 -0
  63. package/dist/types/features/generic_events/aggregate/user-actions/user-actions-aggregator.d.ts.map +1 -0
  64. package/dist/types/features/generic_events/constants.d.ts +4 -0
  65. package/dist/types/features/generic_events/constants.d.ts.map +1 -1
  66. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  67. package/dist/types/features/jserrors/aggregate/index.d.ts +10 -1
  68. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  69. package/dist/types/features/jserrors/aggregate/internal-errors.d.ts +7 -0
  70. package/dist/types/features/jserrors/aggregate/internal-errors.d.ts.map +1 -0
  71. package/dist/types/features/logging/aggregate/index.d.ts +3 -0
  72. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  73. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  74. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  75. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  76. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  77. package/dist/types/features/spa/instrument/index.d.ts +3 -0
  78. package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
  79. package/dist/types/loaders/agent-base.d.ts +1 -0
  80. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  81. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/common/config/init.js +1 -0
  84. package/src/common/dom/iframe.js +4 -0
  85. package/src/common/dom/selector-path.js +45 -0
  86. package/src/common/event-listener/event-listener-opts.js +5 -30
  87. package/src/common/timing/__mocks__/time-keeper.js +1 -0
  88. package/src/common/timing/time-keeper.js +9 -0
  89. package/src/common/util/stringify.js +1 -1
  90. package/src/features/generic_events/aggregate/index.js +74 -14
  91. package/src/features/generic_events/aggregate/user-actions/aggregated-user-action.js +33 -0
  92. package/src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js +73 -0
  93. package/src/features/generic_events/constants.js +6 -0
  94. package/src/features/generic_events/instrument/index.js +19 -3
  95. package/src/features/jserrors/aggregate/index.js +21 -7
  96. package/src/features/jserrors/aggregate/internal-errors.js +33 -0
  97. package/src/features/logging/aggregate/index.js +9 -4
  98. package/src/features/metrics/aggregate/index.js +2 -1
  99. package/src/features/page_view_event/aggregate/index.js +1 -3
  100. package/src/features/session_replay/aggregate/index.js +5 -7
  101. package/src/features/session_trace/aggregate/index.js +12 -16
  102. package/src/features/spa/instrument/index.js +3 -0
  103. package/src/loaders/agent-base.js +1 -0
  104. package/src/loaders/micro-agent.js +2 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,34 @@
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.269.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.268.0...v1.269.0) (2024-10-16)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add instrumentation metadata to logging ([#1208](https://github.com/newrelic/newrelic-browser-agent/issues/1208)) ([6926474](https://github.com/newrelic/newrelic-browser-agent/commit/6926474fe2530475e10daab2bb6bad745bb547e1))
12
+ * Include logging feature in micro agent loader ([#1210](https://github.com/newrelic/newrelic-browser-agent/issues/1210)) ([1b24484](https://github.com/newrelic/newrelic-browser-agent/commit/1b2448498cf285f36530f2107cb0403401958b1f))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * Handle Session Replay Security Policy Errors ([#1215](https://github.com/newrelic/newrelic-browser-agent/issues/1215)) ([f14b0fe](https://github.com/newrelic/newrelic-browser-agent/commit/f14b0fec81d7d21b418b6be6c5d415bdd813eca9))
18
+ * Only ever allow session traces to capture page load timings once ([#1212](https://github.com/newrelic/newrelic-browser-agent/issues/1212)) ([d189686](https://github.com/newrelic/newrelic-browser-agent/commit/d1896869858eca3320144113e168f17f524f3119))
19
+
20
+ ## [1.268.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.267.0...v1.268.0) (2024-10-08)
21
+
22
+
23
+ ### Features
24
+
25
+ * Add UserAction to GenericEvents ([#1186](https://github.com/newrelic/newrelic-browser-agent/issues/1186)) ([1b0bb1d](https://github.com/newrelic/newrelic-browser-agent/commit/1b0bb1dd992674f77c41a73449b64b022cdfc83c))
26
+ * Aggregate UserActions ([#1195](https://github.com/newrelic/newrelic-browser-agent/issues/1195)) ([b5e4c6d](https://github.com/newrelic/newrelic-browser-agent/commit/b5e4c6dcdcfce3ab88132f2a1d82d4d02ab7640b))
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * Always return a string for custom stringify method ([#1207](https://github.com/newrelic/newrelic-browser-agent/issues/1207)) ([94a9a0b](https://github.com/newrelic/newrelic-browser-agent/commit/94a9a0b465e9ea34132317236d47ac9f62ee4051))
32
+ * Force generic events feature to clear buffer when unloading ([#1202](https://github.com/newrelic/newrelic-browser-agent/issues/1202)) ([d3fac07](https://github.com/newrelic/newrelic-browser-agent/commit/d3fac07379d18c98e9838152a31a482ff25fe5bf))
33
+
6
34
  ## [1.267.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.266.0...v1.267.0) (2024-09-23)
7
35
 
8
36
 
package/README.md CHANGED
@@ -238,7 +238,8 @@ A lot of new frameworks support the concept of server-side rendering the pages o
238
238
 
239
239
  ## Disclaimers
240
240
 
241
- The session replay library shipping as part of the browser agent is not turned on by default. For information on the use of this feature, see [Session Replay](#session-replay)
241
+ * The session replay feature is not turned on by default. For information on the use of this feature, see [Session Replay](#session-replay)
242
+ * As part of the improvement efforts around our SPA capabilities, the `createTracer` API has been [deprecated](https://docs.newrelic.com/eol/2024/04/eol-04-24-24-createtracer/). Please engage in removing usage of that library. If tracking task duration, we recommend utilizing the generic browser performance mark and measure APIs, which will gain native detection support from the agent in a future update.
242
243
 
243
244
  ## Support
244
245
 
@@ -80,6 +80,9 @@ const model = () => {
80
80
  page_action: {
81
81
  enabled: true
82
82
  },
83
+ user_actions: {
84
+ enabled: true
85
+ },
83
86
  page_view_event: {
84
87
  enabled: true,
85
88
  autoStart: true
@@ -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 = exports.VERSION = "1.267.0";
15
+ const VERSION = exports.VERSION = "1.269.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 = exports.VERSION = "1.267.0";
15
+ const VERSION = exports.VERSION = "1.269.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isIFrameWindow = isIFrameWindow;
7
+ function isIFrameWindow(windowObject) {
8
+ if (!windowObject) return false;
9
+ return windowObject.self !== windowObject.top;
10
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.generateSelectorPath = void 0;
7
+ /**
8
+ * Generates a CSS selector path for the given element, if possible
9
+ * @param {HTMLElement} elem
10
+ * @param {boolean} includeId
11
+ * @param {boolean} includeClass
12
+ * @returns {string|undefined}
13
+ */
14
+ const generateSelectorPath = elem => {
15
+ if (!elem) return;
16
+ const getNthOfTypeIndex = node => {
17
+ try {
18
+ let i = 1;
19
+ const {
20
+ tagName
21
+ } = node;
22
+ while (node.previousElementSibling) {
23
+ if (node.previousElementSibling.tagName === tagName) i++;
24
+ node = node.previousElementSibling;
25
+ }
26
+ return i;
27
+ } catch (err) {
28
+ // do nothing for now. An invalid child count will make the path selector not return a nth-of-type selector statement
29
+ }
30
+ };
31
+ let pathSelector = '';
32
+ let index = getNthOfTypeIndex(elem);
33
+ try {
34
+ while (elem?.tagName) {
35
+ const {
36
+ id,
37
+ localName
38
+ } = elem;
39
+ const selector = [localName, id ? "#".concat(id) : '', pathSelector ? ">".concat(pathSelector) : ''].join('');
40
+ pathSelector = selector;
41
+ elem = elem.parentNode;
42
+ }
43
+ } catch (err) {
44
+ // do nothing for now
45
+ }
46
+ return pathSelector ? index ? "".concat(pathSelector, ":nth-of-type(").concat(index, ")") : pathSelector : undefined;
47
+ };
48
+ exports.generateSelectorPath = generateSelectorPath;
@@ -6,34 +6,12 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.documentAddEventListener = documentAddEventListener;
7
7
  exports.eventListenerOpts = eventListenerOpts;
8
8
  exports.windowAddEventListener = windowAddEventListener;
9
- var _runtime = require("../constants/runtime");
10
- /*
11
- * See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#safely_detecting_option_support
12
- */
13
- let passiveSupported = false;
14
- let signalSupported = false;
15
- try {
16
- const options = {
17
- get passive() {
18
- // this function will be called when the browser attempts to access the passive property
19
- passiveSupported = true;
20
- return false;
21
- },
22
- get signal() {
23
- signalSupported = true;
24
- return false;
25
- }
26
- };
27
- _runtime.globalScope.addEventListener('test', null, options);
28
- _runtime.globalScope.removeEventListener('test', null, options);
29
- } catch (err) {}
30
9
  function eventListenerOpts(useCapture, abortSignal) {
31
- return passiveSupported || signalSupported ? {
32
- capture: !!useCapture,
33
- passive: passiveSupported,
34
- // passive defaults to false
10
+ return {
11
+ capture: useCapture,
12
+ passive: false,
35
13
  signal: abortSignal
36
- } : !!useCapture; // mainly just IE11 doesn't support third param options under EventTarget API
14
+ };
37
15
  }
38
16
 
39
17
  /** Do not use this within the worker context. */
@@ -107,6 +107,15 @@ class TimeKeeper {
107
107
  return timestamp - this.#localTimeDiff;
108
108
  }
109
109
 
110
+ /**
111
+ * Corrects relative timestamp to NR server time (epoch).
112
+ * @param {DOMHighResTimeStamp} relativeTime
113
+ * @returns {number}
114
+ */
115
+ correctRelativeTimestamp(relativeTime) {
116
+ return this.correctAbsoluteTimestamp(this.convertRelativeTimestamp(relativeTime));
117
+ }
118
+
110
119
  /** Process the session entity and use the info to set the main time calculations if present */
111
120
  processStoredDiff() {
112
121
  if (this.#ready) return; // Time diff has already been calculated
@@ -36,7 +36,7 @@ const getCircularReplacer = () => {
36
36
  */
37
37
  function stringify(val) {
38
38
  try {
39
- return JSON.stringify(val, getCircularReplacer());
39
+ return JSON.stringify(val, getCircularReplacer()) ?? '';
40
40
  } catch (e) {
41
41
  try {
42
42
  _contextualEe.ee.emit('internal-error', [e]);
@@ -21,6 +21,8 @@ var _constants2 = require("../../metrics/constants");
21
21
  var _eventBuffer = require("../../utils/event-buffer");
22
22
  var _traverse = require("../../../common/util/traverse");
23
23
  var _agentConstants = require("../../../common/constants/agent-constants");
24
+ var _userActionsAggregator = require("./user-actions/user-actions-aggregator");
25
+ var _iframe = require("../../../common/dom/iframe");
24
26
  /*
25
27
  * Copyright 2020 New Relic Corporation. All rights reserved.
26
28
  * SPDX-License-Identifier: Apache-2.0
@@ -31,8 +33,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
31
33
  static featureName = _constants.FEATURE_NAME;
32
34
  constructor(agentIdentifier, aggregator) {
33
35
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME);
36
+ const agentInit = (0, _init.getConfiguration)(this.agentIdentifier);
34
37
  this.eventsPerHarvest = 1000;
35
- this.harvestTimeSeconds = (0, _init.getConfigurationValue)(this.agentIdentifier, 'generic_events.harvestTimeSeconds');
38
+ this.harvestTimeSeconds = agentInit.generic_events.harvestTimeSeconds;
36
39
  this.referrerUrl = _runtime2.isBrowserScope && document.referrer ? (0, _cleanUrl.cleanURL)(document.referrer) : undefined;
37
40
  this.events = new _eventBuffer.EventBuffer();
38
41
  this.#agentRuntime = (0, _runtime.getRuntime)(this.agentIdentifier);
@@ -42,16 +45,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
42
45
  (0, _drain.deregisterDrain)(this.agentIdentifier, this.featureName);
43
46
  return;
44
47
  }
45
- if ((0, _init.getConfigurationValue)(this.agentIdentifier, 'page_action.enabled')) {
48
+ const preHarvestMethods = [];
49
+ if (agentInit.page_action.enabled) {
46
50
  (0, _registerHandler.registerHandler)('api-addPageAction', (timestamp, name, attributes) => {
47
51
  this.addEvent({
48
52
  ...attributes,
49
53
  eventType: 'PageAction',
50
- timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctAbsoluteTimestamp(this.#agentRuntime.timeKeeper.convertRelativeTimestamp(timestamp))),
54
+ timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(timestamp)),
51
55
  timeSinceLoad: timestamp / 1000,
52
56
  actionName: name,
53
57
  referrerUrl: this.referrerUrl,
54
- currentUrl: (0, _cleanUrl.cleanURL)('' + location),
55
58
  ...(_runtime2.isBrowserScope && {
56
59
  browserWidth: window.document.documentElement?.clientWidth,
57
60
  browserHeight: window.document.documentElement?.clientHeight
@@ -59,16 +62,83 @@ class Aggregate extends _aggregateBase.AggregateBase {
59
62
  });
60
63
  }, this.featureName, this.ee);
61
64
  }
65
+ if (_runtime2.isBrowserScope && agentInit.user_actions.enabled) {
66
+ this.userActionAggregator = new _userActionsAggregator.UserActionsAggregator();
67
+ this.addUserAction = aggregatedUserAction => {
68
+ try {
69
+ /** The aggregator process only returns an event when it is "done" aggregating -
70
+ * so we still need to validate that an event was given to this method before we try to add */
71
+ if (aggregatedUserAction?.event) {
72
+ const {
73
+ target,
74
+ timeStamp,
75
+ type
76
+ } = aggregatedUserAction.event;
77
+ this.addEvent({
78
+ eventType: 'UserAction',
79
+ timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(timeStamp)),
80
+ action: type,
81
+ actionCount: aggregatedUserAction.count,
82
+ actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
83
+ actionMs: aggregatedUserAction.relativeMs,
84
+ rageClick: aggregatedUserAction.rageClick,
85
+ target: aggregatedUserAction.selectorPath,
86
+ ...((0, _iframe.isIFrameWindow)(window) && {
87
+ iframe: true
88
+ }),
89
+ ...(target?.id && {
90
+ targetId: target.id
91
+ }),
92
+ ...(target?.tagName && {
93
+ targetTag: target.tagName
94
+ }),
95
+ ...(target?.type && {
96
+ targetType: target.type
97
+ }),
98
+ ...(target?.className && {
99
+ targetClass: target.className
100
+ })
101
+ });
102
+ }
103
+ } catch (e) {
104
+ // do nothing for now
105
+ }
106
+ };
107
+ (0, _registerHandler.registerHandler)('ua', evt => {
108
+ /** the processor will return the previously aggregated event if it has been completed by processing the current event */
109
+ this.addUserAction(this.userActionAggregator.process(evt));
110
+ }, this.featureName, this.ee);
111
+ preHarvestMethods.push((options = {}) => {
112
+ /** send whatever UserActions have been aggregated up to this point
113
+ * if we are in a final harvest. By accessing the aggregationEvent, the aggregation is then force-cleared */
114
+ if (options.isFinalHarvest) this.addUserAction(this.userActionAggregator.aggregationEvent);
115
+ });
116
+ }
62
117
  this.harvestScheduler = new _harvestScheduler.HarvestScheduler('ins', {
63
118
  onFinished: (...args) => this.onHarvestFinished(...args)
64
119
  }, this);
65
- this.harvestScheduler.harvest.on('ins', (...args) => this.onHarvestStarted(...args));
120
+ this.harvestScheduler.harvest.on('ins', (...args) => {
121
+ preHarvestMethods.forEach(fn => fn(...args));
122
+ return this.onHarvestStarted(...args);
123
+ });
66
124
  this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0);
67
125
  this.drain();
68
126
  });
69
127
  }
70
128
 
71
129
  // WARNING: Insights times are in seconds. EXCEPT timestamp, which is in ms.
130
+ /** Some keys are set by the query params or request headers sent with the harvest and override the body values, so check those before adding new standard body values...
131
+ * see harvest.js#baseQueryString for more info on the query params
132
+ * Notably:
133
+ * * name: set by the `t=` query param
134
+ * * appId: set by the `a=` query param
135
+ * * standalone: set by the `sa=` query param
136
+ * * session: set by the `s=` query param
137
+ * * sessionTraceId: set by the `ptid=` query param
138
+ * * userAgent*: set by the userAgent header
139
+ * @param {object=} obj the event object for storing in the event buffer
140
+ * @returns void
141
+ */
72
142
  addEvent(obj = {}) {
73
143
  if (!obj || !Object.keys(obj).length) return;
74
144
  if (!obj.eventType) {
@@ -81,9 +151,10 @@ class Aggregate extends _aggregateBase.AggregateBase {
81
151
  }
82
152
  const defaultEventAttributes = {
83
153
  /** should be overridden by the event-specific attributes, but just in case -- set it to now() */
84
- timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctAbsoluteTimestamp(this.#agentRuntime.timeKeeper.convertRelativeTimestamp((0, _now.now)()))),
85
- /** all generic events require a pageUrl */
86
- pageUrl: (0, _cleanUrl.cleanURL)((0, _runtime.getRuntime)(this.agentIdentifier).origin)
154
+ timestamp: Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp((0, _now.now)())),
155
+ /** all generic events require pageUrl(s) */
156
+ pageUrl: (0, _cleanUrl.cleanURL)('' + _runtime2.initialLocation),
157
+ currentUrl: (0, _cleanUrl.cleanURL)('' + location)
87
158
  };
88
159
  const eventAttributes = {
89
160
  /** Agent-level custom attributes */
@@ -111,7 +182,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
111
182
  ins: this.events.buffer
112
183
  }, this.obfuscator.obfuscateString.bind(this.obfuscator), 'string')
113
184
  };
114
- if (options.retry) this.events.hold();
185
+ if (options.retry) this.events.hold();else this.events.clear();
115
186
  return payload;
116
187
  }
117
188
  onHarvestFinished(result) {
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.AggregatedUserAction = void 0;
7
+ var _constants = require("../../constants");
8
+ class AggregatedUserAction {
9
+ constructor(evt, selectorPath) {
10
+ this.event = evt;
11
+ this.count = 1;
12
+ this.originMs = Math.floor(evt.timeStamp);
13
+ this.relativeMs = [0];
14
+ this.selectorPath = selectorPath;
15
+ this.rageClick = undefined;
16
+ }
17
+
18
+ /**
19
+ * Aggregates the count and maintains the relative MS array for matching events
20
+ * Will determine if a rage click was observed as part of the aggregation
21
+ * @param {Event} evt
22
+ * @returns {void}
23
+ */
24
+ aggregate(evt) {
25
+ this.count++;
26
+ this.relativeMs.push(Math.floor(evt.timeStamp - this.originMs));
27
+ if (this.isRageClick()) this.rageClick = true;
28
+ }
29
+
30
+ /**
31
+ * Determines if the current set of relative ms values constitutes a rage click
32
+ * @returns {boolean}
33
+ */
34
+ isRageClick() {
35
+ const len = this.relativeMs.length;
36
+ return this.event.type === 'click' && len >= _constants.RAGE_CLICK_THRESHOLD_EVENTS && this.relativeMs[len - 1] - this.relativeMs[len - _constants.RAGE_CLICK_THRESHOLD_EVENTS] < _constants.RAGE_CLICK_THRESHOLD_MS;
37
+ }
38
+ }
39
+ exports.AggregatedUserAction = AggregatedUserAction;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.UserActionsAggregator = void 0;
7
+ var _selectorPath = require("../../../../common/dom/selector-path");
8
+ var _constants = require("../../constants");
9
+ var _aggregatedUserAction = require("./aggregated-user-action");
10
+ class UserActionsAggregator {
11
+ /** @type {AggregatedUserAction=} */
12
+ #aggregationEvent = undefined;
13
+ #aggregationKey = '';
14
+ get aggregationEvent() {
15
+ // if this is accessed externally, we need to be done aggregating on it
16
+ // to prevent potential mutability and duplication issues, so the state is cleared upon returning.
17
+ // This value may need to be accessed during an unload harvest.
18
+ const finishedEvent = this.#aggregationEvent;
19
+ this.#aggregationKey = '';
20
+ this.#aggregationEvent = undefined;
21
+ return finishedEvent;
22
+ }
23
+
24
+ /**
25
+ * Process the event and determine if a new aggregation set should be made or if it should increment the current aggregation
26
+ * @param {Event} evt The event supplied by the addEventListener callback
27
+ * @returns {AggregatedUserAction|undefined} The previous aggregation set if it has been completed by processing the current event
28
+ */
29
+ process(evt) {
30
+ if (!evt) return;
31
+ const selectorPath = getSelectorPath(evt);
32
+ const aggregationKey = getAggregationKey(evt, selectorPath);
33
+ if (!!aggregationKey && aggregationKey === this.#aggregationKey) {
34
+ // an aggregation exists already, so lets just continue to increment
35
+ this.#aggregationEvent.aggregate(evt);
36
+ } else {
37
+ // return the prev existing one (if there is one)
38
+ const finishedEvent = this.#aggregationEvent;
39
+ // then set as this new event aggregation
40
+ this.#aggregationKey = aggregationKey;
41
+ this.#aggregationEvent = new _aggregatedUserAction.AggregatedUserAction(evt, selectorPath);
42
+ return finishedEvent;
43
+ }
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Generates a selector path for the event, starting with simple cases like window or document and getting more complex for dom-tree traversals as needed.
49
+ * Will return a random selector path value if no other path can be determined, to force the aggregator to skip aggregation for this event.
50
+ * @param {Event} evt
51
+ * @returns {string}
52
+ */
53
+ exports.UserActionsAggregator = UserActionsAggregator;
54
+ function getSelectorPath(evt) {
55
+ let selectorPath;
56
+ if (_constants.OBSERVED_WINDOW_EVENTS.includes(evt.type) || evt.target === window) selectorPath = 'window';else if (evt.target === document) selectorPath = 'document';
57
+ // if still no selectorPath, generate one from target tree that includes elem ids
58
+ else selectorPath = (0, _selectorPath.generateSelectorPath)(evt.target);
59
+ // if STILL no selectorPath, it will return undefined which will skip aggregation for this event
60
+ return selectorPath;
61
+ }
62
+
63
+ /**
64
+ * Returns an aggregation key based on the event type and the selector path of the event's target.
65
+ * Scrollend events are aggregated into one set, no matter what.
66
+ * @param {Event} evt
67
+ * @param {string} selectorPath
68
+ * @returns {string}
69
+ */
70
+ function getAggregationKey(evt, selectorPath) {
71
+ let aggregationKey = evt.type;
72
+ /** aggregate all scrollends into one set (if sequential), no matter what their target is
73
+ * the aggregation group's selector path with be reflected as the first one observed
74
+ * due to the way the aggregation logic works (by storing the initial value and aggregating it) */
75
+ if (evt.type !== 'scrollend') aggregationKey += '-' + selectorPath;
76
+ return aggregationKey;
77
+ }
@@ -3,8 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = void 0;
6
+ exports.RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_EVENTS = exports.OBSERVED_WINDOW_EVENTS = exports.OBSERVED_EVENTS = exports.MAX_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = exports.FEATURE_NAME = void 0;
7
7
  var _features = require("../../loaders/features/features");
8
8
  const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.genericEvents;
9
9
  const IDEAL_PAYLOAD_SIZE = exports.IDEAL_PAYLOAD_SIZE = 64000;
10
- const MAX_PAYLOAD_SIZE = exports.MAX_PAYLOAD_SIZE = 1000000;
10
+ const MAX_PAYLOAD_SIZE = exports.MAX_PAYLOAD_SIZE = 1000000;
11
+ const OBSERVED_EVENTS = exports.OBSERVED_EVENTS = ['auxclick', 'click', 'copy', 'keydown', 'paste', 'scrollend'];
12
+ const OBSERVED_WINDOW_EVENTS = exports.OBSERVED_WINDOW_EVENTS = ['focus', 'blur'];
13
+ const RAGE_CLICK_THRESHOLD_EVENTS = exports.RAGE_CLICK_THRESHOLD_EVENTS = 4;
14
+ const RAGE_CLICK_THRESHOLD_MS = exports.RAGE_CLICK_THRESHOLD_MS = 1000;
@@ -5,7 +5,10 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.Instrument = exports.GenericEvents = void 0;
7
7
  var _init = require("../../../common/config/init");
8
+ var _runtime = require("../../../common/constants/runtime");
8
9
  var _drain = require("../../../common/drain/drain");
10
+ var _handle = require("../../../common/event-emitter/handle");
11
+ var _eventListenerOpts = require("../../../common/event-listener/event-listener-opts");
9
12
  var _instrumentBase = require("../../utils/instrument-base");
10
13
  var _constants = require("../constants");
11
14
  /* Copyright 2020 New Relic Corporation. All rights reserved.
@@ -16,9 +19,17 @@ class Instrument extends _instrumentBase.InstrumentBase {
16
19
  static featureName = _constants.FEATURE_NAME;
17
20
  constructor(agentIdentifier, aggregator, auto = true) {
18
21
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME, auto);
19
- const genericEventSourceConfigs = [(0, _init.getConfigurationValue)(this.agentIdentifier, 'page_action.enabled')
22
+ const agentInit = (0, _init.getConfiguration)(this.agentIdentifier);
23
+ const genericEventSourceConfigs = [agentInit.page_action.enabled, agentInit.user_actions.enabled
20
24
  // other future generic event source configs to go here, like M&Ms, PageResouce, etc.
21
25
  ];
26
+ if (_runtime.isBrowserScope && agentInit.user_actions.enabled) {
27
+ _constants.OBSERVED_EVENTS.forEach(eventType => (0, _eventListenerOpts.windowAddEventListener)(eventType, evt => (0, _handle.handle)('ua', [evt], undefined, this.featureName, this.ee), true));
28
+ _constants.OBSERVED_WINDOW_EVENTS.forEach(eventType => (0, _eventListenerOpts.windowAddEventListener)(eventType, evt => (0, _handle.handle)('ua', [evt], undefined, this.featureName, this.ee))
29
+ // Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
30
+ );
31
+ }
32
+
22
33
  /** If any of the sources are active, import the aggregator. otherwise deregister */
23
34
  if (genericEventSourceConfigs.some(x => x)) this.importAggregator();else (0, _drain.deregisterDrain)(this.agentIdentifier, this.featureName);
24
35
  }
@@ -23,6 +23,8 @@ var _nreum = require("../../../common/window/nreum");
23
23
  var _drain = require("../../../common/drain/drain");
24
24
  var _now = require("../../../common/timing/now");
25
25
  var _traverse = require("../../../common/util/traverse");
26
+ var _internalErrors = require("./internal-errors");
27
+ var _constants2 = require("../../metrics/constants");
26
28
  /*
27
29
  * Copyright 2020 New Relic Corporation. All rights reserved.
28
30
  * SPDX-License-Identifier: Apache-2.0
@@ -130,6 +132,16 @@ class Aggregate extends _aggregateBase.AggregateBase {
130
132
  }
131
133
  return canonicalStackString;
132
134
  }
135
+
136
+ /**
137
+ *
138
+ * @param {Error|UncaughtError} err The error instance to be processed
139
+ * @param {number} time the relative ms (to origin) timestamp of occurence
140
+ * @param {boolean=} internal if the error was "caught" and deemed "internal" before reporting to the jserrors feature
141
+ * @param {object=} customAttributes any custom attributes to be included in the error payload
142
+ * @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
143
+ * @returns
144
+ */
133
145
  storeError(err, time, internal, customAttributes, hasReplay) {
134
146
  if (!err) return;
135
147
  // are we in an interaction
@@ -146,6 +158,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
146
158
  // Again as with previous usage, all falsey values would include the error.
147
159
  }
148
160
  var stackInfo = (0, _computeStackTrace.computeStackTrace)(err);
161
+ const {
162
+ shouldSwallow,
163
+ reason
164
+ } = (0, _internalErrors.evaluateInternalError)(stackInfo, internal);
165
+ if (shouldSwallow) {
166
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Internal/Error/' + reason], undefined, _features.FEATURE_NAMES.metrics, this.ee);
167
+ return;
168
+ }
149
169
  var canonicalStackString = this.buildCanonicalStackString(stackInfo);
150
170
  const params = {
151
171
  stackHash: (0, _stringHashCode.stringHashCode)(canonicalStackString),
@@ -167,7 +187,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
167
187
  if (!this.stackReported[bucketHash]) {
168
188
  this.stackReported[bucketHash] = true;
169
189
  params.stack_trace = (0, _formatStackTrace.truncateSize)(stackInfo.stackString);
170
- this.observedAt[bucketHash] = Math.floor(agentRuntime.timeKeeper.correctAbsoluteTimestamp(agentRuntime.timeKeeper.convertRelativeTimestamp(time)));
190
+ this.observedAt[bucketHash] = Math.floor(agentRuntime.timeKeeper.correctRelativeTimestamp(time));
171
191
  } else {
172
192
  params.browser_stack_hash = (0, _stringHashCode.stringHashCode)(stackInfo.stackString);
173
193
  }
@@ -183,8 +203,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
183
203
  this.pageviewReported[bucketHash] = true;
184
204
  }
185
205
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
186
- params.timestamp = Math.floor(agentRuntime.timeKeeper.correctAbsoluteTimestamp(agentRuntime.timeKeeper.convertRelativeTimestamp(time)));
187
- var type = internal ? 'ierr' : 'err';
206
+ params.timestamp = Math.floor(agentRuntime.timeKeeper.correctRelativeTimestamp(time));
207
+ var type = 'err';
188
208
  var newMetrics = {
189
209
  time
190
210
  };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.evaluateInternalError = evaluateInternalError;
7
+ const REASON_RRWEB = 'Rrweb';
8
+ const REASON_SECURITY_POLICY = 'Security-Policy';
9
+ /**
10
+ * This function is responsible for determining if an error should be swallowed or not.
11
+ * @param {Object} stackInfo - The error stack information.
12
+ * @returns {boolean} - Whether the error should be swallowed or not.
13
+ */
14
+ function evaluateInternalError(stackInfo, internal) {
15
+ const output = {
16
+ shouldSwallow: internal || false,
17
+ reason: 'Other'
18
+ };
19
+ const leadingFrame = stackInfo.frames?.[0];
20
+ /** If we cant otherwise determine from the frames and message, the default of internal + reason will be the fallback */
21
+ if (!leadingFrame || typeof stackInfo?.message !== 'string') return output;
22
+
23
+ // check if the error happened in expected modules or if messages match known patterns
24
+ const isNrRecorder = leadingFrame?.url?.match(/nr-(.*)-recorder.min.js/);
25
+ const isRrweb = leadingFrame?.url?.match(/rrweb/);
26
+ const isMaybeNrRecorder = leadingFrame?.url?.match(/recorder/);
27
+ const isSecurityPolicyAPIError = stackInfo.message.toLowerCase().match(/an attempt was made to break through the security policy of the user agent/);
28
+
29
+ // check if modules and patterns above fit known swallow cases
30
+ if (!!isNrRecorder || !!isRrweb) {
31
+ /** We know -for sure- that the error came from our recorder module or rrweb directly if these are true, so swallow it */
32
+ output.shouldSwallow = true;
33
+ output.reason = REASON_RRWEB;
34
+ if (isSecurityPolicyAPIError) output.reason += '-' + REASON_SECURITY_POLICY;
35
+ } else if (!!isMaybeNrRecorder && isSecurityPolicyAPIError) {
36
+ /** We -suspect- that the error came from NR, so if it matches the exact case we know about, swallow it */
37
+ output.shouldSwallow = true;
38
+ output.reason = REASON_RRWEB + '-' + REASON_SECURITY_POLICY;
39
+ }
40
+ // other swallow conditions could also be added here
41
+ return output;
42
+ }
@@ -66,7 +66,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
66
66
  return;
67
67
  }
68
68
  if (typeof message !== 'string' || !message) return (0, _console.warn)(32);
69
- const log = new _log.Log(Math.floor(this.#agentRuntime.timeKeeper.correctAbsoluteTimestamp(this.#agentRuntime.timeKeeper.convertRelativeTimestamp(timestamp))), message, attributes, level);
69
+ const log = new _log.Log(Math.floor(this.#agentRuntime.timeKeeper.correctRelativeTimestamp(timestamp)), message, attributes, level);
70
70
  const logBytes = log.message.length + (0, _stringify.stringify)(log.attributes).length + log.level.length + 10; // timestamp == 10 chars
71
71
 
72
72
  if (!this.bufferedLogs.canMerge(logBytes)) {
@@ -84,6 +84,12 @@ class Aggregate extends _aggregateBase.AggregateBase {
84
84
  }
85
85
  prepareHarvest(options = {}) {
86
86
  if (this.blocked || !this.bufferedLogs.hasData) return;
87
+ /** These attributes are evaluated and dropped at ingest processing time and do not get stored on NRDB */
88
+ const unbilledAttributes = {
89
+ 'instrumentation.provider': 'browser',
90
+ 'instrumentation.version': this.#agentRuntime.version,
91
+ 'instrumentation.name': this.#agentRuntime.loaderType
92
+ };
87
93
  /** see https://source.datanerd.us/agents/rum-specs/blob/main/browser/Log for logging spec */
88
94
  const payload = {
89
95
  qs: {
@@ -107,7 +113,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
107
113
  // Application ID from info object,
108
114
  standalone: Boolean(this.#agentInfo.sa),
109
115
  // copy paste (true) vs APM (false)
110
- agentVersion: this.#agentRuntime.version // browser agent version
116
+ agentVersion: this.#agentRuntime.version,
117
+ // browser agent version,
118
+ ...unbilledAttributes
111
119
  }
112
120
  },
113
121
  /** logs section contains individual unique log entries */