@newrelic/browser-agent 1.274.0 → 1.276.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 (64) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cjs/common/config/init.js +44 -8
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/harvest/harvest.js +21 -0
  6. package/dist/cjs/common/util/submit-data.js +42 -5
  7. package/dist/cjs/common/wrap/wrap-events.js +1 -1
  8. package/dist/cjs/common/wrap/wrap-logger.js +5 -1
  9. package/dist/cjs/common/wrap/wrap-xhr.js +1 -0
  10. package/dist/cjs/features/ajax/instrument/index.js +1 -1
  11. package/dist/cjs/features/generic_events/aggregate/index.js +77 -14
  12. package/dist/cjs/features/generic_events/constants.js +7 -2
  13. package/dist/cjs/features/generic_events/instrument/index.js +20 -8
  14. package/dist/cjs/features/jserrors/aggregate/index.js +3 -2
  15. package/dist/cjs/features/jserrors/aggregate/internal-errors.js +2 -2
  16. package/dist/cjs/features/jserrors/instrument/index.js +2 -2
  17. package/dist/cjs/features/metrics/aggregate/index.js +1 -35
  18. package/dist/esm/common/config/init.js +39 -3
  19. package/dist/esm/common/constants/env.cdn.js +1 -1
  20. package/dist/esm/common/constants/env.npm.js +1 -1
  21. package/dist/esm/common/harvest/harvest.js +21 -0
  22. package/dist/esm/common/util/submit-data.js +41 -5
  23. package/dist/esm/common/wrap/wrap-events.js +1 -1
  24. package/dist/esm/common/wrap/wrap-logger.js +5 -2
  25. package/dist/esm/common/wrap/wrap-xhr.js +1 -0
  26. package/dist/esm/features/ajax/instrument/index.js +1 -1
  27. package/dist/esm/features/generic_events/aggregate/index.js +78 -15
  28. package/dist/esm/features/generic_events/constants.js +6 -1
  29. package/dist/esm/features/generic_events/instrument/index.js +21 -9
  30. package/dist/esm/features/jserrors/aggregate/index.js +3 -2
  31. package/dist/esm/features/jserrors/aggregate/internal-errors.js +2 -2
  32. package/dist/esm/features/jserrors/instrument/index.js +2 -2
  33. package/dist/esm/features/metrics/aggregate/index.js +1 -35
  34. package/dist/types/common/config/init.d.ts.map +1 -1
  35. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  36. package/dist/types/common/util/submit-data.d.ts +18 -1
  37. package/dist/types/common/util/submit-data.d.ts.map +1 -1
  38. package/dist/types/common/wrap/wrap-logger.d.ts.map +1 -1
  39. package/dist/types/common/wrap/wrap-xhr.d.ts.map +1 -1
  40. package/dist/types/features/generic_events/aggregate/index.d.ts +1 -1
  41. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  42. package/dist/types/features/generic_events/constants.d.ts +5 -0
  43. package/dist/types/features/generic_events/instrument/index.d.ts.map +1 -1
  44. package/dist/types/features/jserrors/aggregate/index.d.ts +2 -1
  45. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  46. package/dist/types/features/jserrors/aggregate/internal-errors.d.ts +1 -1
  47. package/dist/types/features/jserrors/aggregate/internal-errors.d.ts.map +1 -1
  48. package/dist/types/features/metrics/aggregate/index.d.ts +0 -1
  49. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  50. package/package.json +13 -1
  51. package/src/common/config/init.js +21 -3
  52. package/src/common/harvest/harvest.js +19 -0
  53. package/src/common/util/submit-data.js +37 -4
  54. package/src/common/wrap/wrap-events.js +1 -1
  55. package/src/common/wrap/wrap-logger.js +7 -2
  56. package/src/common/wrap/wrap-xhr.js +2 -0
  57. package/src/features/ajax/instrument/index.js +1 -1
  58. package/src/features/generic_events/aggregate/index.js +79 -16
  59. package/src/features/generic_events/constants.js +6 -0
  60. package/src/features/generic_events/instrument/index.js +21 -10
  61. package/src/features/jserrors/aggregate/index.js +3 -2
  62. package/src/features/jserrors/aggregate/internal-errors.js +2 -2
  63. package/src/features/jserrors/instrument/index.js +2 -2
  64. package/src/features/metrics/aggregate/index.js +1 -33
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAwBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,2BAkCC;IA/BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,qBAAwB;IA6B1B,oDAEC;IAED;;;MAcC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED;;;;;;;;OAQG;IACH,gBAPW,KAAK,GAAC,aAAa,QACnB,MAAM,aACN,OAAO,YAAC,qBACR,MAAM,YAAC,cACP,OAAO,YAAC,QAqGlB;IA4BD,yDA6BC;IAED,qFAOC;;CACF;wBAlQY,OAAO,0BAA0B,EAAE,SAAS;8BAP3B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/index.js"],"names":[],"mappings":"AAwBA;;GAEG;AAEH;IACE,2BAAiC;IACjC,2BAkCC;IA/BC,kBAAuB;IACvB,eAAoB;IACpB,qBAA0B;IAC1B,2BAAgC;IAChC,qBAAwB;IA6B1B,oDAEC;IAED;;;MAcC;IAED;;;;;;OAMG;IACH,qCAHW,SAAS,GACP,MAAM,CAgBlB;IAED;;;;;;;;;OASG;IACH,gBARW,KAAK,GAAC,aAAa,QACnB,MAAM,aACN,OAAO,YAAC,qBACR,MAAM,YAAC,cACP,OAAO,YAAC,kBACR,MAAM,YAAC,QAqGjB;IA4BD,yDA6BC;IAED,qFAOC;;CACF;wBAnQY,OAAO,0BAA0B,EAAE,SAAS;8BAP3B,4BAA4B"}
@@ -3,5 +3,5 @@
3
3
  * @param {Object} stackInfo - The error stack information.
4
4
  * @returns {boolean} - Whether the error should be swallowed or not.
5
5
  */
6
- export function evaluateInternalError(stackInfo: Object, internal: any): boolean;
6
+ export function evaluateInternalError(stackInfo: Object, internal: any, reason: any): boolean;
7
7
  //# sourceMappingURL=internal-errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal-errors.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/internal-errors.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,iDAHW,MAAM,kBACJ,OAAO,CA2BnB"}
1
+ {"version":3,"file":"internal-errors.d.ts","sourceRoot":"","sources":["../../../../../src/features/jserrors/aggregate/internal-errors.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,iDAHW,MAAM,+BACJ,OAAO,CA2BnB"}
@@ -6,7 +6,6 @@ export class Aggregate extends AggregateBase {
6
6
  singleChecks(): void;
7
7
  eachSessionChecks(): void;
8
8
  unload(): void;
9
- resourcesSent: boolean | undefined;
10
9
  }
11
10
  import { AggregateBase } from '../../utils/aggregate-base';
12
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IACjC,2BAuBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqEC;IAED,0BAOC;IAED,eAkCC;IA/BG,mCAAyB;CAgC9B;8BAlK6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/metrics/aggregate/index.js"],"names":[],"mappings":"AAcA;IACE,2BAAiC;IACjC,2BAuBC;IAED,wDAKC;IAED,iDAKC;IAED,qBAqEC;IAED,0BAOC;IAED,eAEC;CACF;8BAlI6B,4BAA4B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.274.0",
3
+ "version": "1.276.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -154,6 +154,17 @@
154
154
  "default": "./dist/esm/features/spa/instrument/index.js"
155
155
  }
156
156
  },
157
+ "devEngines": {
158
+ "runtime": {
159
+ "name": "node",
160
+ "onFail": "error",
161
+ "version": "22.11.0"
162
+ },
163
+ "packageManager": {
164
+ "name": "npm",
165
+ "onFail": "error"
166
+ }
167
+ },
157
168
  "engines": {
158
169
  "node": ">=12.17.0 < 13.0.0 || >=13.7.0"
159
170
  },
@@ -177,6 +188,7 @@
177
188
  "cdn:watch": "jung -r ./src -F '.*\\.test\\.js' --run -- npm run cdn:build:local",
178
189
  "test-server": "node ./tools/wdio/bin/server",
179
190
  "lt:update-browsers": "node ./tools/browsers-lists/lt-update-supported.mjs",
191
+ "lt:upload-webview-assets": "node ./tools/lambda-test/upload-webview-assets.mjs",
180
192
  "tools:test-builds": "npm --prefix ./tools/test-builds run build-all",
181
193
  "third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps",
182
194
  "prepare": "husky install",
@@ -1,3 +1,4 @@
1
+ import { FEATURE_FLAGS } from '../../features/generic_events/constants'
1
2
  import { LOG_LEVELS } from '../../features/logging/constants'
2
3
  import { isValidSelector } from '../dom/query-selector'
3
4
  import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../session/constants'
@@ -9,6 +10,12 @@ const nrMask = '[data-nr-mask]'
9
10
 
10
11
  const model = () => {
11
12
  const hiddenState = {
13
+ feature_flags: [],
14
+ experimental: {
15
+ marks: false,
16
+ measures: false,
17
+ resources: false
18
+ },
12
19
  mask_selector: '*',
13
20
  block_selector: '[data-nr-block]',
14
21
  mask_input_options: {
@@ -40,7 +47,8 @@ const model = () => {
40
47
  cors_use_tracecontext_headers: undefined,
41
48
  allowed_origins: undefined
42
49
  },
43
- feature_flags: [],
50
+ get feature_flags () { return hiddenState.feature_flags },
51
+ set feature_flags (val) { hiddenState.feature_flags = val },
44
52
  generic_events: { enabled: true, harvestTimeSeconds: 30, autoStart: true },
45
53
  harvest: { tooManyRequestsDelay: 60 },
46
54
  jserrors: { enabled: true, harvestTimeSeconds: 10, autoStart: true },
@@ -51,8 +59,18 @@ const model = () => {
51
59
  page_view_event: { enabled: true, autoStart: true },
52
60
  page_view_timing: { enabled: true, harvestTimeSeconds: 30, autoStart: true },
53
61
  performance: {
54
- capture_marks: false,
55
- capture_measures: false // false by default through experimental phase, but flipped to true once GA'd
62
+ get capture_marks () { return hiddenState.feature_flags.includes(FEATURE_FLAGS.MARKS) || hiddenState.experimental.marks },
63
+ set capture_marks (val) { hiddenState.experimental.marks = val },
64
+ get capture_measures () { return hiddenState.feature_flags.includes(FEATURE_FLAGS.MEASURES) || hiddenState.experimental.measures },
65
+ set capture_measures (val) { hiddenState.experimental.measures = val },
66
+ resources: {
67
+ // whether to run this subfeature or not in the generic_events feature. false by default through experimental phase, but flipped to true once GA'd
68
+ get enabled () { return hiddenState.feature_flags.includes(FEATURE_FLAGS.RESOURCES) || hiddenState.experimental.resources },
69
+ set enabled (val) { hiddenState.experimental.resources = val },
70
+ asset_types: [], // MDN types to collect, empty array will collect all types
71
+ first_party_domains: [], // when included, will decorate the resource as first party if matching
72
+ ignore_newrelic: true // ignore capturing internal agent scripts and harvest calls
73
+ }
56
74
  },
57
75
  privacy: { cookies_enabled: true }, // *cli - per discussion, default should be true
58
76
  proxy: {
@@ -144,6 +144,25 @@ export class Harvest extends SharedContext {
144
144
  }
145
145
  cbFinished(cbResult)
146
146
  }, eventListenerOpts(false))
147
+ } else if (!opts.unload && cbFinished && submitMethod === submitData.xhrFetch) {
148
+ const harvestScope = this
149
+ result.then(async function (response) {
150
+ const status = response.status
151
+ const cbResult = { sent: true, status, fullUrl, fetchResponse: response }
152
+
153
+ if (response.status === 429) {
154
+ cbResult.retry = true
155
+ cbResult.delay = harvestScope.tooManyRequestsDelay
156
+ } else if (status === 408 || status === 500 || status === 503) {
157
+ cbResult.retry = true
158
+ }
159
+
160
+ if (opts.needResponse) {
161
+ cbResult.responseText = await response.text()
162
+ }
163
+
164
+ cbFinished(cbResult)
165
+ })
147
166
  }
148
167
 
149
168
  return result
@@ -17,11 +17,44 @@ import { isBrowserScope } from '../constants/runtime'
17
17
  * a final harvest within the agent.
18
18
  */
19
19
  export function getSubmitMethod ({ isFinalHarvest = false } = {}) {
20
- return isFinalHarvest && isBrowserScope
20
+ if (isFinalHarvest && isBrowserScope) {
21
21
  // Use sendBeacon for final harvest
22
- ? beacon
23
- // If not final harvest, or not browserScope, always use xhr post
24
- : xhr
22
+ return beacon
23
+ }
24
+
25
+ // If not final harvest, or not browserScope, use XHR post if available
26
+ if (typeof XMLHttpRequest !== 'undefined') {
27
+ return xhr
28
+ }
29
+
30
+ // Fallback for web workers where XMLHttpRequest is not available
31
+ return xhrFetch
32
+ }
33
+
34
+ /**
35
+ *
36
+ * @param url
37
+ * @param body
38
+ * @param method
39
+ * @param headers
40
+ * @returns {Promise<Response>}
41
+ */
42
+ export function xhrFetch ({
43
+ url,
44
+ body = null,
45
+ method = 'POST',
46
+ headers = [{
47
+ key: 'content-type',
48
+ value: 'text/plain'
49
+ }]
50
+ }) {
51
+ const objHeaders = {}
52
+
53
+ for (const header of headers) {
54
+ objHeaders[header.key] = header.value
55
+ }
56
+
57
+ return fetch(url, { headers: objHeaders, method, body, credentials: 'include' })
25
58
  }
26
59
 
27
60
  /**
@@ -37,8 +37,8 @@ export function wrapEvents (sharedEE) {
37
37
  // Guard against instrumenting environments w/o necessary features
38
38
  if ('getPrototypeOf' in Object) {
39
39
  if (isBrowserScope) findEventListenerProtoAndCb(document, wrapNode)
40
+ if (XHR) findEventListenerProtoAndCb(XHR.prototype, wrapNode)
40
41
  findEventListenerProtoAndCb(globalScope, wrapNode)
41
- findEventListenerProtoAndCb(XHR.prototype, wrapNode)
42
42
  }
43
43
 
44
44
  ee.on(ADD_EVENT_LISTENER + '-start', function (args, target) {
@@ -10,7 +10,9 @@
10
10
  import { ee as baseEE, contextId } from '../event-emitter/contextual-ee'
11
11
  import { EventContext } from '../event-emitter/event-context'
12
12
  import { warn } from '../util/console'
13
- import { createWrapperWithEmitter as wfn } from './wrap-function'
13
+ import { flag, createWrapperWithEmitter as wfn } from './wrap-function'
14
+
15
+ const contextMap = new Map()
14
16
 
15
17
  /**
16
18
  * Wraps a supplied function and adds emitter events under the `-wrap-logger-` prefix
@@ -33,8 +35,11 @@ export function wrapLogger(sharedEE, parent, loggerFn, context) {
33
35
  ctx.level = context.level
34
36
  ctx.customAttributes = context.customAttributes
35
37
 
38
+ const contextLookupKey = parent[loggerFn]?.[flag] || parent[loggerFn]
39
+ contextMap.set(contextLookupKey, ctx)
40
+
36
41
  /** observe calls to <loggerFn> and emit events prefixed with `wrap-logger-` */
37
- wrapFn.inPlace(parent, [loggerFn], 'wrap-logger-', ctx)
42
+ wrapFn.inPlace(parent, [loggerFn], 'wrap-logger-', () => contextMap.get(contextLookupKey))
38
43
 
39
44
  return ee
40
45
  }
@@ -28,6 +28,8 @@ export function wrapXhr (sharedEE) {
28
28
  var baseEE = sharedEE || contextualEE
29
29
  const ee = scopedEE(baseEE)
30
30
 
31
+ if (typeof globalScope.XMLHttpRequest === 'undefined') return ee
32
+
31
33
  // Notice if our wrapping never ran yet, the falsy NaN will not early return; but if it has,
32
34
  // then we increment the count to track # of feats using this at runtime.
33
35
  if (wrapped[ee.debugId]++) return ee
@@ -374,7 +374,7 @@ function subscribeToEvents (agentRef, ee, handler, dt) {
374
374
  if (hasUndefinedHostname(params)) return // don't bother with XHR of url with no hostname
375
375
 
376
376
  metrics.duration = now() - this.startTime
377
- if (!this.loadCazptureCalled && xhr.readyState === 4) {
377
+ if (!this.loadCaptureCalled && xhr.readyState === 4) {
378
378
  captureXhrData(this, xhr)
379
379
  } else if (params.status == null) {
380
380
  params.status = 0
@@ -6,17 +6,17 @@ import { stringify } from '../../../common/util/stringify'
6
6
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
7
7
  import { cleanURL } from '../../../common/url/clean-url'
8
8
  import { FEATURE_NAME } from '../constants'
9
- import { initialLocation, isBrowserScope } from '../../../common/constants/runtime'
9
+ import { globalScope, initialLocation, isBrowserScope } from '../../../common/constants/runtime'
10
10
  import { AggregateBase } from '../../utils/aggregate-base'
11
11
  import { warn } from '../../../common/util/console'
12
12
  import { now } from '../../../common/timing/now'
13
13
  import { registerHandler } from '../../../common/event-emitter/register-handler'
14
14
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
15
15
  import { applyFnToProps } from '../../../common/util/traverse'
16
- import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
17
16
  import { FEATURE_TO_ENDPOINT } from '../../../loaders/features/features'
18
17
  import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
19
18
  import { isIFrameWindow } from '../../../common/dom/iframe'
19
+ import { handle } from '../../../common/event-emitter/handle'
20
20
 
21
21
  export class Aggregate extends AggregateBase {
22
22
  static featureName = FEATURE_NAME
@@ -35,6 +35,8 @@ export class Aggregate extends AggregateBase {
35
35
  return
36
36
  }
37
37
 
38
+ this.trackSupportabilityMetrics()
39
+
38
40
  if (agentRef.init.page_action.enabled) {
39
41
  registerHandler('api-addPageAction', (timestamp, name, attributes) => {
40
42
  this.addEvent({
@@ -72,11 +74,20 @@ export class Aggregate extends AggregateBase {
72
74
  rageClick: aggregatedUserAction.rageClick,
73
75
  target: aggregatedUserAction.selectorPath,
74
76
  ...(isIFrameWindow(window) && { iframe: true }),
75
- ...(target?.id && { targetId: target.id }),
76
- ...(target?.tagName && { targetTag: target.tagName }),
77
- ...(target?.type && { targetType: target.type }),
78
- ...(target?.className && { targetClass: target.className })
77
+ ...(canTrustTargetAttribute('id') && { targetId: target.id }),
78
+ ...(canTrustTargetAttribute('tagName') && { targetTag: target.tagName }),
79
+ ...(canTrustTargetAttribute('type') && { targetType: target.type }),
80
+ ...(canTrustTargetAttribute('className') && { targetClass: target.className })
79
81
  })
82
+
83
+ /**
84
+ * Only trust attributes that exist on HTML element targets, which excludes the window and the document targets
85
+ * @param {string} attribute The attribute to check for on the target element
86
+ * @returns {boolean} Whether the target element has the attribute and can be trusted
87
+ */
88
+ function canTrustTargetAttribute (attribute) {
89
+ return !!(aggregatedUserAction.selectorPath !== 'window' && aggregatedUserAction.selectorPath !== 'document' && target instanceof HTMLElement && target?.[attribute])
90
+ }
80
91
  }
81
92
  } catch (e) {
82
93
  // do nothing for now
@@ -104,10 +115,11 @@ export class Aggregate extends AggregateBase {
104
115
  const observer = new PerformanceObserver((list) => {
105
116
  list.getEntries().forEach(entry => {
106
117
  try {
118
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/' + type + '/Seen'])
107
119
  this.addEvent({
108
120
  eventType: 'BrowserPerformance',
109
121
  timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
110
- entryName: entry.name,
122
+ entryName: cleanURL(entry.name),
111
123
  entryDuration: entry.duration,
112
124
  entryType: type,
113
125
  ...(entry.detail && { entryDetail: entry.detail })
@@ -124,6 +136,47 @@ export class Aggregate extends AggregateBase {
124
136
  }
125
137
  }
126
138
 
139
+ if (isBrowserScope && agentRef.init.performance.resources.enabled) {
140
+ registerHandler('browserPerformance.resource', (entry) => {
141
+ try {
142
+ // convert the entry to a plain object and separate the name and duration from the object
143
+ // you need to do this to be able to spread it into the addEvent call later, and name and duration
144
+ // would be duplicative of entryName and entryDuration and are protected keys in NR1
145
+ const { name, duration, ...entryObject } = entry.toJSON()
146
+
147
+ let firstParty = false
148
+ try {
149
+ const entryDomain = new URL(name).hostname
150
+ const isNr = (entryDomain.includes('newrelic.com') || entryDomain.includes('nr-data.net') || entryDomain.includes('nr-local.net'))
151
+ /** decide if we should ignore nr-specific assets */
152
+ if (this.agentRef.init.performance.resources.ignore_newrelic && isNr) return
153
+ /** decide if we should ignore the asset type (empty means allow everything, which is the default) */
154
+ if (this.agentRef.init.performance.resources.asset_types.length && !this.agentRef.init.performance.resources.asset_types.includes(entryObject.initiatorType)) return
155
+ /** decide if the entryDomain is a first party domain */
156
+ firstParty = entryDomain === globalScope?.location.hostname || agentRef.init.performance.resources.first_party_domains.includes(entryDomain)
157
+ if (firstParty) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/FirstPartyResource/Seen'])
158
+ if (isNr) handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/NrResource/Seen'])
159
+ } catch (err) {
160
+ // couldnt parse the URL, so firstParty will just default to false
161
+ }
162
+
163
+ handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/Resource/Seen'])
164
+ const event = {
165
+ ...entryObject,
166
+ eventType: 'BrowserPerformance',
167
+ timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entryObject.startTime)),
168
+ entryName: name,
169
+ entryDuration: duration,
170
+ firstParty
171
+ }
172
+
173
+ this.addEvent(event)
174
+ } catch (err) {
175
+ this.ee.emit('internal-error', [err, 'GenericEvents-Resource'])
176
+ }
177
+ }, this.featureName, this.ee)
178
+ }
179
+
127
180
  this.harvestScheduler = new HarvestScheduler(FEATURE_TO_ENDPOINT[this.featureName], {
128
181
  onFinished: (result) => this.postHarvestCleanup(result.sent && result.retry),
129
182
  onUnload: () => addUserAction?.(this.userActionAggregator.aggregationEvent)
@@ -177,9 +230,16 @@ export class Aggregate extends AggregateBase {
177
230
  ...obj
178
231
  }
179
232
 
180
- this.events.add(eventAttributes)
181
-
182
- this.checkEventLimits()
233
+ const addedEvent = this.events.add(eventAttributes)
234
+ if (!addedEvent && !this.events.isEmpty()) {
235
+ /** could not add the event because it pushed the buffer over the limit
236
+ * so we harvest early, and try to add it again now that the buffer is cleared
237
+ * if it fails again, we do nothing
238
+ */
239
+ this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['GenericEvents/Harvest/Max/Seen'])
240
+ this.harvestScheduler.runHarvest()
241
+ this.events.add(eventAttributes)
242
+ }
183
243
  }
184
244
 
185
245
  serializer (eventBuffer) {
@@ -190,11 +250,14 @@ export class Aggregate extends AggregateBase {
190
250
  return { ua: this.agentRef.info.userAttributes, at: this.agentRef.info.atts }
191
251
  }
192
252
 
193
- checkEventLimits () {
194
- // check if we've reached any harvest limits...
195
- if (this.events.byteSize() > IDEAL_PAYLOAD_SIZE) {
196
- this.ee.emit(SUPPORTABILITY_METRIC_CHANNEL, ['GenericEvents/Harvest/Max/Seen'])
197
- this.harvestScheduler.runHarvest()
198
- }
253
+ trackSupportabilityMetrics () {
254
+ /** track usage SMs to improve these experimental features */
255
+ const configPerfTag = 'Config/Performance/'
256
+ if (this.agentRef.init.performance.capture_marks) handle(SUPPORTABILITY_METRIC_CHANNEL, [configPerfTag + 'CaptureMarks/Enabled'])
257
+ if (this.agentRef.init.performance.capture_measures) handle(SUPPORTABILITY_METRIC_CHANNEL, [configPerfTag + 'CaptureMeasures/Enabled'])
258
+ if (this.agentRef.init.performance.resources.enabled) handle(SUPPORTABILITY_METRIC_CHANNEL, [configPerfTag + 'Resources/Enabled'])
259
+ if (this.agentRef.init.performance.resources.asset_types?.length !== 0) handle(SUPPORTABILITY_METRIC_CHANNEL, [configPerfTag + 'Resources/AssetTypes/Changed'])
260
+ if (this.agentRef.init.performance.resources.first_party_domains?.length !== 0) handle(SUPPORTABILITY_METRIC_CHANNEL, [configPerfTag + 'Resources/FirstPartyDomains/Changed'])
261
+ if (this.agentRef.init.performance.resources.ignore_newrelic === false) handle(SUPPORTABILITY_METRIC_CHANNEL, [configPerfTag + 'Resources/IgnoreNewrelic/Changed'])
199
262
  }
200
263
  }
@@ -9,3 +9,9 @@ export const OBSERVED_WINDOW_EVENTS = ['focus', 'blur']
9
9
 
10
10
  export const RAGE_CLICK_THRESHOLD_EVENTS = 4
11
11
  export const RAGE_CLICK_THRESHOLD_MS = 1000
12
+
13
+ export const FEATURE_FLAGS = {
14
+ MARKS: 'experimental.marks',
15
+ MEASURES: 'experimental.measures',
16
+ RESOURCES: 'experimental.resources'
17
+ }
@@ -2,7 +2,7 @@
2
2
  * SPDX-License-Identifier: Apache-2.0
3
3
  */
4
4
 
5
- import { isBrowserScope } from '../../../common/constants/runtime'
5
+ import { globalScope, isBrowserScope } from '../../../common/constants/runtime'
6
6
  import { handle } from '../../../common/event-emitter/handle'
7
7
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
8
8
  import { InstrumentBase } from '../../utils/instrument-base'
@@ -12,22 +12,33 @@ export class Instrument extends InstrumentBase {
12
12
  static featureName = FEATURE_NAME
13
13
  constructor (agentRef, auto = true) {
14
14
  super(agentRef, FEATURE_NAME, auto)
15
+ /** config values that gate whether the generic events aggregator should be imported at all */
15
16
  const genericEventSourceConfigs = [
16
17
  agentRef.init.page_action.enabled,
17
18
  agentRef.init.performance.capture_marks,
18
19
  agentRef.init.performance.capture_measures,
19
- agentRef.init.user_actions.enabled
20
- // other future generic event source configs to go here, like M&Ms, PageResouce, etc.
20
+ agentRef.init.user_actions.enabled,
21
+ agentRef.init.performance.resources.enabled
21
22
  ]
22
23
 
23
- if (isBrowserScope && agentRef.init.user_actions.enabled) {
24
- OBSERVED_EVENTS.forEach(eventType =>
25
- windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee), true)
26
- )
27
- OBSERVED_WINDOW_EVENTS.forEach(eventType =>
28
- windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee))
24
+ if (isBrowserScope) {
25
+ if (agentRef.init.user_actions.enabled) {
26
+ OBSERVED_EVENTS.forEach(eventType =>
27
+ windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee), true)
28
+ )
29
+ OBSERVED_WINDOW_EVENTS.forEach(eventType =>
30
+ windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee))
29
31
  // 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
- )
32
+ )
33
+ }
34
+ if (agentRef.init.performance.resources.enabled && globalScope.PerformanceObserver?.supportedEntryTypes.includes('resource')) {
35
+ const observer = new PerformanceObserver((list) => {
36
+ list.getEntries().forEach(entry => {
37
+ handle('browserPerformance.resource', [entry], undefined, this.featureName, this.ee)
38
+ })
39
+ })
40
+ observer.observe({ type: 'resource', buffered: true })
41
+ }
31
42
  }
32
43
 
33
44
  /** If any of the sources are active, import the aggregator. otherwise deregister */
@@ -114,9 +114,10 @@ export class Aggregate extends AggregateBase {
114
114
  * @param {boolean=} internal if the error was "caught" and deemed "internal" before reporting to the jserrors feature
115
115
  * @param {object=} customAttributes any custom attributes to be included in the error payload
116
116
  * @param {boolean=} hasReplay a flag indicating if the error occurred during a replay session
117
+ * @param {string=} swallowReason a string indicating pre-defined reason if swallowing the error. Mainly used by the internal error SMs.
117
118
  * @returns
118
119
  */
119
- storeError (err, time, internal, customAttributes, hasReplay) {
120
+ storeError (err, time, internal, customAttributes, hasReplay, swallowReason) {
120
121
  if (!err) return
121
122
  // are we in an interaction
122
123
  time = time || now()
@@ -134,7 +135,7 @@ export class Aggregate extends AggregateBase {
134
135
 
135
136
  var stackInfo = computeStackTrace(err)
136
137
 
137
- const { shouldSwallow, reason } = evaluateInternalError(stackInfo, internal)
138
+ const { shouldSwallow, reason } = evaluateInternalError(stackInfo, internal, swallowReason)
138
139
  if (shouldSwallow) {
139
140
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Internal/Error/' + reason], undefined, FEATURE_NAMES.metrics, this.ee)
140
141
  return
@@ -5,8 +5,8 @@ const REASON_SECURITY_POLICY = 'Security-Policy'
5
5
  * @param {Object} stackInfo - The error stack information.
6
6
  * @returns {boolean} - Whether the error should be swallowed or not.
7
7
  */
8
- export function evaluateInternalError (stackInfo, internal) {
9
- const output = { shouldSwallow: internal || false, reason: 'Other' }
8
+ export function evaluateInternalError (stackInfo, internal, reason) {
9
+ const output = { shouldSwallow: internal || false, reason: reason || 'Other' }
10
10
  const leadingFrame = stackInfo.frames?.[0]
11
11
  /** If we cant otherwise determine from the frames and message, the default of internal + reason will be the fallback */
12
12
  if (!leadingFrame || typeof stackInfo?.message !== 'string') return output
@@ -24,9 +24,9 @@ export class Instrument extends InstrumentBase {
24
24
  this.removeOnAbort = new AbortController()
25
25
  } catch (e) {}
26
26
 
27
- this.ee.on('internal-error', (error) => {
27
+ this.ee.on('internal-error', (error, reason) => {
28
28
  if (!this.abortHandler) return
29
- handle('ierr', [castError(error), now(), true, {}, this.#replayRunning], undefined, this.featureName, this.ee)
29
+ handle('ierr', [castError(error), now(), true, {}, this.#replayRunning, reason], undefined, this.featureName, this.ee)
30
30
  })
31
31
 
32
32
  this.ee.on(SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, (isRunning) => {
@@ -134,38 +134,6 @@ export class Aggregate extends AggregateBase {
134
134
  }
135
135
 
136
136
  unload () {
137
- try {
138
- if (this.resourcesSent) return
139
- this.resourcesSent = true // make sure this only gets sent once
140
-
141
- // Capture SMs around network resources using the performance API to assess
142
- // work to split this out from the ST nodes
143
- // differentiate between internal+external and ajax+non-ajax
144
- const ajaxResources = ['beacon', 'fetch', 'xmlhttprequest']
145
- const internalUrls = ['nr-data.net', 'newrelic.com', 'nr-local.net', 'localhost']
146
- function isInternal (x) { return internalUrls.some(y => x.name.indexOf(y) >= 0) }
147
- function isAjax (x) { return ajaxResources.includes(x.initiatorType) }
148
- const allResources = performance?.getEntriesByType('resource') || []
149
- allResources.forEach((entry) => {
150
- if (isInternal(entry)) {
151
- if (isAjax(entry)) this.storeSupportabilityMetrics('Generic/Resources/Ajax/Internal')
152
- else this.storeSupportabilityMetrics('Generic/Resources/Non-Ajax/Internal')
153
- } else {
154
- if (isAjax(entry)) this.storeSupportabilityMetrics('Generic/Resources/Ajax/External')
155
- else this.storeSupportabilityMetrics('Generic/Resources/Non-Ajax/External')
156
- }
157
- })
158
-
159
- // Capture SMs for performance markers and measures to assess the usage and possible inclusion of this
160
- // data in the agent for use in NR
161
- if (typeof performance !== 'undefined') {
162
- const markers = performance.getEntriesByType('mark')
163
- const measures = performance.getEntriesByType('measure')
164
- if (markers.length) this.storeSupportabilityMetrics('Generic/Performance/Mark/Seen', markers.length)
165
- if (measures.length) this.storeSupportabilityMetrics('Generic/Performance/Measure/Seen', measures.length)
166
- }
167
- } catch (e) {
168
- // do nothing
169
- }
137
+ // do nothing for now, marks and measures and resources stats are now being captured by the ge feature
170
138
  }
171
139
  }