@newrelic/browser-agent 1.270.3 → 1.272.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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.272.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.271.0...v1.272.0) (2024-11-07)
7
+
8
+
9
+ ### Features
10
+
11
+ * Marks and measures ([#1189](https://github.com/newrelic/newrelic-browser-agent/issues/1189)) ([1a58409](https://github.com/newrelic/newrelic-browser-agent/commit/1a58409cda8b7b264e58fe284041540941dccd1c))
12
+
13
+ ## [1.271.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.270.3...v1.271.0) (2024-11-01)
14
+
15
+
16
+ ### Features
17
+
18
+ * Ignore unhandled promise rejections that lack a valid reason ([#1233](https://github.com/newrelic/newrelic-browser-agent/issues/1233)) ([25a1fff](https://github.com/newrelic/newrelic-browser-agent/commit/25a1fffb91fa5936766a7bc89d735b3018aa62a2))
19
+
6
20
  ## [1.270.3](https://github.com/newrelic/newrelic-browser-agent/compare/v1.270.2...v1.270.3) (2024-10-31)
7
21
 
8
22
 
@@ -80,9 +80,6 @@ const model = () => {
80
80
  page_action: {
81
81
  enabled: true
82
82
  },
83
- user_actions: {
84
- enabled: true
85
- },
86
83
  page_view_event: {
87
84
  enabled: true,
88
85
  autoStart: true
@@ -92,6 +89,10 @@ const model = () => {
92
89
  harvestTimeSeconds: 30,
93
90
  autoStart: true
94
91
  },
92
+ performance: {
93
+ capture_marks: false,
94
+ capture_measures: false // false by default through experimental phase, but flipped to true once GA'd
95
+ },
95
96
  privacy: {
96
97
  cookies_enabled: true
97
98
  },
@@ -175,7 +176,10 @@ const model = () => {
175
176
  harvestTimeSeconds: 10,
176
177
  autoStart: true
177
178
  },
178
- ssl: undefined
179
+ ssl: undefined,
180
+ user_actions: {
181
+ enabled: true
182
+ }
179
183
  };
180
184
  };
181
185
  const _cache = {};
@@ -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.270.3";
15
+ const VERSION = exports.VERSION = "1.272.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.270.3";
15
+ const VERSION = exports.VERSION = "1.272.0";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -107,6 +107,45 @@ class Aggregate extends _aggregateBase.AggregateBase {
107
107
  if (options.isFinalHarvest) this.addUserAction(this.userActionAggregator.aggregationEvent);
108
108
  });
109
109
  }
110
+
111
+ /**
112
+ * is it worth complicating the agent and skipping the POs for single repeating queries? maybe,
113
+ * but right now it was less desirable simply because it is a nice benefit of populating the event buffer
114
+ * immediately as events happen for payload evaluation purposes and that becomes a little more chaotic
115
+ * with an arbitrary query method. note: eventTypes: [...types] does not support the 'buffered' flag so we have
116
+ * to create up to two PO's here.
117
+ */
118
+ const performanceTypesToCapture = [...(agentRef.init.performance.capture_marks ? ['mark'] : []), ...(agentRef.init.performance.capture_measures ? ['measure'] : [])];
119
+ if (performanceTypesToCapture.length) {
120
+ try {
121
+ performanceTypesToCapture.forEach(type => {
122
+ if (PerformanceObserver.supportedEntryTypes.includes(type)) {
123
+ const observer = new PerformanceObserver(list => {
124
+ list.getEntries().forEach(entry => {
125
+ try {
126
+ this.addEvent({
127
+ eventType: 'BrowserPerformance',
128
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
129
+ entryName: entry.name,
130
+ entryDuration: entry.duration,
131
+ entryType: type,
132
+ ...(entry.detail && {
133
+ entryDetail: entry.detail
134
+ })
135
+ });
136
+ } catch (err) {}
137
+ });
138
+ });
139
+ observer.observe({
140
+ buffered: true,
141
+ type
142
+ });
143
+ }
144
+ });
145
+ } catch (err) {
146
+ // Something failed in our set up, likely the browser does not support PO's... do nothing
147
+ }
148
+ }
110
149
  this.harvestScheduler = new _harvestScheduler.HarvestScheduler('ins', {
111
150
  onFinished: (...args) => this.onHarvestFinished(...args)
112
151
  }, this);
@@ -17,7 +17,7 @@ class Instrument extends _instrumentBase.InstrumentBase {
17
17
  static featureName = _constants.FEATURE_NAME;
18
18
  constructor(agentRef, auto = true) {
19
19
  super(agentRef, _constants.FEATURE_NAME, auto);
20
- const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.user_actions.enabled
20
+ const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.user_actions.enabled
21
21
  // other future generic event source configs to go here, like M&Ms, PageResouce, etc.
22
22
  ];
23
23
  if (_runtime.isBrowserScope && agentRef.init.user_actions.enabled) {
@@ -32,8 +32,15 @@ function castError(error) {
32
32
  * @returns {Error} An Error object with the message as the casted reason
33
33
  */
34
34
  function castPromiseRejectionEvent(promiseRejectionEvent) {
35
- let prefix = 'Unhandled Promise Rejection';
36
- if (canTrustError(promiseRejectionEvent?.reason)) {
35
+ const prefix = 'Unhandled Promise Rejection';
36
+
37
+ /**
38
+ * If the casted return value is falsy like this, it will get dropped and not produce an error event for harvest.
39
+ * We drop promise rejections that could not form a valid error stack or message deriving from the .reason attribute
40
+ * -- such as a manually invoked rejection without an argument -- since they lack reproduction value and create confusion.
41
+ * */
42
+ if (!promiseRejectionEvent?.reason) return;
43
+ if (canTrustError(promiseRejectionEvent.reason)) {
37
44
  try {
38
45
  promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message;
39
46
  return castError(promiseRejectionEvent.reason);
@@ -41,7 +48,6 @@ function castPromiseRejectionEvent(promiseRejectionEvent) {
41
48
  return castError(promiseRejectionEvent.reason);
42
49
  }
43
50
  }
44
- if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix);
45
51
  const error = castError(promiseRejectionEvent.reason);
46
52
  error.message = prefix + ': ' + error?.message;
47
53
  return error;
@@ -72,9 +72,6 @@ const model = () => {
72
72
  page_action: {
73
73
  enabled: true
74
74
  },
75
- user_actions: {
76
- enabled: true
77
- },
78
75
  page_view_event: {
79
76
  enabled: true,
80
77
  autoStart: true
@@ -84,6 +81,10 @@ const model = () => {
84
81
  harvestTimeSeconds: 30,
85
82
  autoStart: true
86
83
  },
84
+ performance: {
85
+ capture_marks: false,
86
+ capture_measures: false // false by default through experimental phase, but flipped to true once GA'd
87
+ },
87
88
  privacy: {
88
89
  cookies_enabled: true
89
90
  },
@@ -167,7 +168,10 @@ const model = () => {
167
168
  harvestTimeSeconds: 10,
168
169
  autoStart: true
169
170
  },
170
- ssl: undefined
171
+ ssl: undefined,
172
+ user_actions: {
173
+ enabled: true
174
+ }
171
175
  };
172
176
  };
173
177
  const _cache = {};
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.270.3";
9
+ export const VERSION = "1.272.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.270.3";
9
+ export const VERSION = "1.272.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -100,6 +100,45 @@ export class Aggregate extends AggregateBase {
100
100
  if (options.isFinalHarvest) this.addUserAction(this.userActionAggregator.aggregationEvent);
101
101
  });
102
102
  }
103
+
104
+ /**
105
+ * is it worth complicating the agent and skipping the POs for single repeating queries? maybe,
106
+ * but right now it was less desirable simply because it is a nice benefit of populating the event buffer
107
+ * immediately as events happen for payload evaluation purposes and that becomes a little more chaotic
108
+ * with an arbitrary query method. note: eventTypes: [...types] does not support the 'buffered' flag so we have
109
+ * to create up to two PO's here.
110
+ */
111
+ const performanceTypesToCapture = [...(agentRef.init.performance.capture_marks ? ['mark'] : []), ...(agentRef.init.performance.capture_measures ? ['measure'] : [])];
112
+ if (performanceTypesToCapture.length) {
113
+ try {
114
+ performanceTypesToCapture.forEach(type => {
115
+ if (PerformanceObserver.supportedEntryTypes.includes(type)) {
116
+ const observer = new PerformanceObserver(list => {
117
+ list.getEntries().forEach(entry => {
118
+ try {
119
+ this.addEvent({
120
+ eventType: 'BrowserPerformance',
121
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
122
+ entryName: entry.name,
123
+ entryDuration: entry.duration,
124
+ entryType: type,
125
+ ...(entry.detail && {
126
+ entryDetail: entry.detail
127
+ })
128
+ });
129
+ } catch (err) {}
130
+ });
131
+ });
132
+ observer.observe({
133
+ buffered: true,
134
+ type
135
+ });
136
+ }
137
+ });
138
+ } catch (err) {
139
+ // Something failed in our set up, likely the browser does not support PO's... do nothing
140
+ }
141
+ }
103
142
  this.harvestScheduler = new HarvestScheduler('ins', {
104
143
  onFinished: (...args) => this.onHarvestFinished(...args)
105
144
  }, this);
@@ -11,7 +11,7 @@ export class Instrument extends InstrumentBase {
11
11
  static featureName = FEATURE_NAME;
12
12
  constructor(agentRef, auto = true) {
13
13
  super(agentRef, FEATURE_NAME, auto);
14
- const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.user_actions.enabled
14
+ const genericEventSourceConfigs = [agentRef.init.page_action.enabled, agentRef.init.performance.capture_marks, agentRef.init.performance.capture_measures, agentRef.init.user_actions.enabled
15
15
  // other future generic event source configs to go here, like M&Ms, PageResouce, etc.
16
16
  ];
17
17
  if (isBrowserScope && agentRef.init.user_actions.enabled) {
@@ -25,8 +25,15 @@ export function castError(error) {
25
25
  * @returns {Error} An Error object with the message as the casted reason
26
26
  */
27
27
  export function castPromiseRejectionEvent(promiseRejectionEvent) {
28
- let prefix = 'Unhandled Promise Rejection';
29
- if (canTrustError(promiseRejectionEvent?.reason)) {
28
+ const prefix = 'Unhandled Promise Rejection';
29
+
30
+ /**
31
+ * If the casted return value is falsy like this, it will get dropped and not produce an error event for harvest.
32
+ * We drop promise rejections that could not form a valid error stack or message deriving from the .reason attribute
33
+ * -- such as a manually invoked rejection without an argument -- since they lack reproduction value and create confusion.
34
+ * */
35
+ if (!promiseRejectionEvent?.reason) return;
36
+ if (canTrustError(promiseRejectionEvent.reason)) {
30
37
  try {
31
38
  promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message;
32
39
  return castError(promiseRejectionEvent.reason);
@@ -34,7 +41,6 @@ export function castPromiseRejectionEvent(promiseRejectionEvent) {
34
41
  return castError(promiseRejectionEvent.reason);
35
42
  }
36
43
  }
37
- if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix);
38
44
  const error = castError(promiseRejectionEvent.reason);
39
45
  error.message = prefix + ': ' + error?.message;
40
46
  return error;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init.js"],"names":[],"mappings":"AAkHA,+CAIC;AAED,0DAKC;AAED,+DAYC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init.js"],"names":[],"mappings":"AAsHA,+CAIC;AAED,0DAKC;AAED,+DAYC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IACjC,2BAsFC;IAnFC,yBAA4B;IAC5B,wBAAyE;IAEzE,gCAAkG;IAClG,oBAA+B;IA6B3B,4CAAuD;IAEvD,mDAyBC;IAcH,mCAAuH;IAY3H;;;;;;;;;;;OAWG;IACH,eAHW,MAAM,YAAC,QAmCjB;IAED;;;;;;kBAkBC;IAED,qCAGC;IAED,yBAMC;CACF;8BApL6B,4BAA4B;4BAK9B,0BAA0B;sCAGhB,wCAAwC;iCAZ7C,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IACjC,2BAyHC;IAtHC,yBAA4B;IAC5B,wBAAyE;IAEzE,gCAAkG;IAClG,oBAA+B;IA6B3B,4CAAuD;IAEvD,mDAyBC;IAiDH,mCAAuH;IAY3H;;;;;;;;;;;OAWG;IACH,eAHW,MAAM,YAAC,QAmCjB;IAED;;;;;;kBAkBC;IAED,qCAGC;IAED,yBAMC;CACF;8BAvN6B,4BAA4B;4BAK9B,0BAA0B;sCAGhB,wCAAwC;iCAZ7C,2CAA2C"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/instrument/index.js"],"names":[],"mappings":"AAUA;IACE,2BAAiC;IACjC,2CAqBC;CACF;AAED,8CAAuC;+BA7BR,6BAA6B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/generic_events/instrument/index.js"],"names":[],"mappings":"AAUA;IACE,2BAAiC;IACjC,2CAuBC;CACF;AAED,8CAAuC;+BA/BR,6BAA6B"}
@@ -1 +1 @@
1
- {"version":3,"file":"cast-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/shared/cast-error.js"],"names":[],"mappings":"AAEA;;;;;KAKK;AACL,iCAHa,GAAG,GACD,KAAK,GAAC,aAAa,CAmBjC;AAED;;;;KAIK;AACL,uEAFe,KAAK,CAmBnB;AAED;;;;KAIK;AACL,2CAHa,UAAU,GACR,KAAK,GAAC,aAAa,CAUjC;8BAhE6B,kBAAkB"}
1
+ {"version":3,"file":"cast-error.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/shared/cast-error.js"],"names":[],"mappings":"AAEA;;;;;KAKK;AACL,iCAHa,GAAG,GACD,KAAK,GAAC,aAAa,CAmBjC;AAED;;;;KAIK;AACL,uEAFe,KAAK,CAwBnB;AAED;;;;KAIK;AACL,2CAHa,UAAU,GACR,KAAK,GAAC,aAAa,CAUjC;8BArE6B,kBAAkB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.270.3",
3
+ "version": "1.272.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -201,7 +201,8 @@
201
201
  "devDependencies": {
202
202
  "@babel/cli": "^7.23.4",
203
203
  "@babel/core": "^7.23.7",
204
- "@babel/eslint-parser": "^7.23.3",
204
+ "@babel/eslint-parser": "^7.25.9",
205
+ "@babel/plugin-syntax-import-attributes": "^7.26.0",
205
206
  "@babel/plugin-transform-template-literals": "^7.24.7",
206
207
  "@babel/preset-env": "^7.23.8",
207
208
  "@babel/register": "^7.23.7",
@@ -48,9 +48,12 @@ const model = () => {
48
48
  metrics: { enabled: true, autoStart: true },
49
49
  obfuscate: undefined,
50
50
  page_action: { enabled: true },
51
- user_actions: { enabled: true },
52
51
  page_view_event: { enabled: true, autoStart: true },
53
52
  page_view_timing: { enabled: true, harvestTimeSeconds: 30, autoStart: true },
53
+ performance: {
54
+ capture_marks: false,
55
+ capture_measures: false // false by default through experimental phase, but flipped to true once GA'd
56
+ },
54
57
  privacy: { cookies_enabled: true }, // *cli - per discussion, default should be true
55
58
  proxy: {
56
59
  assets: undefined, // if this value is set, it will be used to overwrite the webpack asset path used to fetch assets
@@ -105,7 +108,8 @@ const model = () => {
105
108
  session_trace: { enabled: true, harvestTimeSeconds: 10, autoStart: true },
106
109
  soft_navigations: { enabled: true, harvestTimeSeconds: 10, autoStart: true },
107
110
  spa: { enabled: true, harvestTimeSeconds: 10, autoStart: true },
108
- ssl: undefined
111
+ ssl: undefined,
112
+ user_actions: { enabled: true }
109
113
  }
110
114
  }
111
115
 
@@ -97,6 +97,41 @@ export class Aggregate extends AggregateBase {
97
97
  })
98
98
  }
99
99
 
100
+ /**
101
+ * is it worth complicating the agent and skipping the POs for single repeating queries? maybe,
102
+ * but right now it was less desirable simply because it is a nice benefit of populating the event buffer
103
+ * immediately as events happen for payload evaluation purposes and that becomes a little more chaotic
104
+ * with an arbitrary query method. note: eventTypes: [...types] does not support the 'buffered' flag so we have
105
+ * to create up to two PO's here.
106
+ */
107
+ const performanceTypesToCapture = [...(agentRef.init.performance.capture_marks ? ['mark'] : []), ...(agentRef.init.performance.capture_measures ? ['measure'] : [])]
108
+ if (performanceTypesToCapture.length) {
109
+ try {
110
+ performanceTypesToCapture.forEach(type => {
111
+ if (PerformanceObserver.supportedEntryTypes.includes(type)) {
112
+ const observer = new PerformanceObserver((list) => {
113
+ list.getEntries().forEach(entry => {
114
+ try {
115
+ this.addEvent({
116
+ eventType: 'BrowserPerformance',
117
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
118
+ entryName: entry.name,
119
+ entryDuration: entry.duration,
120
+ entryType: type,
121
+ ...(entry.detail && { entryDetail: entry.detail })
122
+ })
123
+ } catch (err) {
124
+ }
125
+ })
126
+ })
127
+ observer.observe({ buffered: true, type })
128
+ }
129
+ })
130
+ } catch (err) {
131
+ // Something failed in our set up, likely the browser does not support PO's... do nothing
132
+ }
133
+ }
134
+
100
135
  this.harvestScheduler = new HarvestScheduler('ins', { onFinished: (...args) => this.onHarvestFinished(...args) }, this)
101
136
  this.harvestScheduler.harvest.on('ins', (...args) => {
102
137
  preHarvestMethods.forEach(fn => fn(...args))
@@ -14,6 +14,8 @@ export class Instrument extends InstrumentBase {
14
14
  super(agentRef, FEATURE_NAME, auto)
15
15
  const genericEventSourceConfigs = [
16
16
  agentRef.init.page_action.enabled,
17
+ agentRef.init.performance.capture_marks,
18
+ agentRef.init.performance.capture_measures,
17
19
  agentRef.init.user_actions.enabled
18
20
  // other future generic event source configs to go here, like M&Ms, PageResouce, etc.
19
21
  ]
@@ -31,9 +31,16 @@ export function castError (error) {
31
31
  * @returns {Error} An Error object with the message as the casted reason
32
32
  */
33
33
  export function castPromiseRejectionEvent (promiseRejectionEvent) {
34
- let prefix = 'Unhandled Promise Rejection'
34
+ const prefix = 'Unhandled Promise Rejection'
35
35
 
36
- if (canTrustError(promiseRejectionEvent?.reason)) {
36
+ /**
37
+ * If the casted return value is falsy like this, it will get dropped and not produce an error event for harvest.
38
+ * We drop promise rejections that could not form a valid error stack or message deriving from the .reason attribute
39
+ * -- such as a manually invoked rejection without an argument -- since they lack reproduction value and create confusion.
40
+ * */
41
+ if (!promiseRejectionEvent?.reason) return
42
+
43
+ if (canTrustError(promiseRejectionEvent.reason)) {
37
44
  try {
38
45
  promiseRejectionEvent.reason.message = prefix + ': ' + promiseRejectionEvent.reason.message
39
46
  return castError(promiseRejectionEvent.reason)
@@ -42,8 +49,6 @@ export function castPromiseRejectionEvent (promiseRejectionEvent) {
42
49
  }
43
50
  }
44
51
 
45
- if (typeof promiseRejectionEvent.reason === 'undefined') return castError(prefix)
46
-
47
52
  const error = castError(promiseRejectionEvent.reason)
48
53
  error.message = prefix + ': ' + error?.message
49
54
  return error