@newrelic/browser-agent 1.316.0 → 1.317.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 (140) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/common/config/init-types.js +1 -0
  3. package/dist/cjs/common/config/init.js +6 -4
  4. package/dist/cjs/common/constants/env.cdn.js +2 -2
  5. package/dist/cjs/common/constants/env.npm.js +2 -2
  6. package/dist/cjs/common/constants/events.js +26 -0
  7. package/dist/cjs/common/harvest/harvester.js +12 -4
  8. package/dist/cjs/common/payloads/payloads.js +127 -0
  9. package/dist/cjs/common/serialize/bel-serializer.js +4 -3
  10. package/dist/cjs/common/util/data-size.js +2 -2
  11. package/dist/cjs/common/util/obfuscate.js +147 -3
  12. package/dist/cjs/common/wrap/wrap-websocket.js +2 -1
  13. package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
  14. package/dist/cjs/features/ajax/aggregate/gql.js +40 -1
  15. package/dist/cjs/features/ajax/aggregate/index.js +51 -13
  16. package/dist/cjs/features/ajax/constants.js +6 -1
  17. package/dist/cjs/features/ajax/instrument/index.js +105 -17
  18. package/dist/cjs/features/generic_events/aggregate/index.js +15 -10
  19. package/dist/cjs/features/generic_events/constants.js +3 -2
  20. package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +2 -2
  21. package/dist/cjs/features/jserrors/aggregate/index.js +12 -3
  22. package/dist/cjs/features/logging/aggregate/index.js +7 -3
  23. package/dist/cjs/features/metrics/aggregate/index.js +5 -1
  24. package/dist/cjs/features/page_view_event/aggregate/index.js +7 -3
  25. package/dist/cjs/features/page_view_timing/aggregate/index.js +6 -1
  26. package/dist/cjs/features/session_replay/aggregate/index.js +7 -1
  27. package/dist/cjs/features/session_trace/aggregate/index.js +6 -2
  28. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +36 -5
  29. package/dist/cjs/features/soft_navigations/aggregate/index.js +7 -1
  30. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  31. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +6 -4
  32. package/dist/cjs/features/utils/aggregate-base.js +2 -3
  33. package/dist/esm/common/config/init-types.js +1 -0
  34. package/dist/esm/common/config/init.js +3 -1
  35. package/dist/esm/common/constants/env.cdn.js +2 -2
  36. package/dist/esm/common/constants/env.npm.js +2 -2
  37. package/dist/esm/common/constants/events.js +20 -0
  38. package/dist/esm/common/harvest/harvester.js +12 -4
  39. package/dist/esm/common/payloads/payloads.js +118 -0
  40. package/dist/esm/common/serialize/bel-serializer.js +4 -3
  41. package/dist/esm/common/util/data-size.js +2 -2
  42. package/dist/esm/common/util/obfuscate.js +147 -3
  43. package/dist/esm/common/wrap/wrap-websocket.js +2 -1
  44. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  45. package/dist/esm/features/ajax/aggregate/gql.js +39 -1
  46. package/dist/esm/features/ajax/aggregate/index.js +52 -14
  47. package/dist/esm/features/ajax/constants.js +5 -0
  48. package/dist/esm/features/ajax/instrument/index.js +106 -18
  49. package/dist/esm/features/generic_events/aggregate/index.js +15 -10
  50. package/dist/esm/features/generic_events/constants.js +3 -2
  51. package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +2 -2
  52. package/dist/esm/features/jserrors/aggregate/index.js +12 -3
  53. package/dist/esm/features/logging/aggregate/index.js +7 -3
  54. package/dist/esm/features/metrics/aggregate/index.js +5 -1
  55. package/dist/esm/features/page_view_event/aggregate/index.js +7 -3
  56. package/dist/esm/features/page_view_timing/aggregate/index.js +6 -1
  57. package/dist/esm/features/session_replay/aggregate/index.js +7 -1
  58. package/dist/esm/features/session_trace/aggregate/index.js +6 -2
  59. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +36 -5
  60. package/dist/esm/features/soft_navigations/aggregate/index.js +7 -1
  61. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  62. package/dist/esm/features/soft_navigations/aggregate/interaction.js +6 -4
  63. package/dist/esm/features/utils/aggregate-base.js +2 -3
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/types/common/config/init-types.d.ts +4 -0
  66. package/dist/types/common/config/init-types.d.ts.map +1 -1
  67. package/dist/types/common/config/init.d.ts.map +1 -1
  68. package/dist/types/common/constants/events.d.ts +16 -0
  69. package/dist/types/common/constants/events.d.ts.map +1 -0
  70. package/dist/types/common/harvest/harvester.d.ts +4 -1
  71. package/dist/types/common/harvest/harvester.d.ts.map +1 -1
  72. package/dist/types/common/payloads/payloads.d.ts +42 -0
  73. package/dist/types/common/payloads/payloads.d.ts.map +1 -0
  74. package/dist/types/common/serialize/bel-serializer.d.ts +1 -1
  75. package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
  76. package/dist/types/common/util/obfuscate.d.ts +23 -2
  77. package/dist/types/common/util/obfuscate.d.ts.map +1 -1
  78. package/dist/types/common/wrap/wrap-websocket.d.ts.map +1 -1
  79. package/dist/types/features/ajax/aggregate/gql.d.ts +8 -0
  80. package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -1
  81. package/dist/types/features/ajax/aggregate/index.d.ts +2 -0
  82. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  83. package/dist/types/features/ajax/constants.d.ts +5 -0
  84. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  85. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  86. package/dist/types/features/generic_events/aggregate/index.d.ts +3 -1
  87. package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
  88. package/dist/types/features/generic_events/constants.d.ts.map +1 -1
  89. package/dist/types/features/jserrors/aggregate/index.d.ts +8 -1
  90. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  91. package/dist/types/features/logging/aggregate/index.d.ts +3 -1
  92. package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
  93. package/dist/types/features/metrics/aggregate/index.d.ts +2 -0
  94. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  95. package/dist/types/features/page_view_event/aggregate/index.d.ts +2 -0
  96. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  97. package/dist/types/features/page_view_timing/aggregate/index.d.ts +2 -0
  98. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  99. package/dist/types/features/session_replay/aggregate/index.d.ts +2 -0
  100. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  101. package/dist/types/features/session_trace/aggregate/index.d.ts +2 -0
  102. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  103. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +6 -1
  104. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  105. package/dist/types/features/soft_navigations/aggregate/index.d.ts +3 -0
  106. package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
  107. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -1
  108. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  109. package/dist/types/features/utils/aggregate-base.d.ts +0 -1
  110. package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
  111. package/package.json +2 -2
  112. package/src/common/config/init-types.js +1 -0
  113. package/src/common/config/init.js +2 -1
  114. package/src/common/constants/events.js +20 -0
  115. package/src/common/harvest/harvester.js +11 -4
  116. package/src/common/payloads/payloads.js +135 -0
  117. package/src/common/serialize/bel-serializer.js +4 -3
  118. package/src/common/util/data-size.js +2 -2
  119. package/src/common/util/obfuscate.js +154 -3
  120. package/src/common/wrap/wrap-websocket.js +2 -1
  121. package/src/common/wrap/wrap-xhr.js +1 -1
  122. package/src/features/ajax/aggregate/gql.js +42 -1
  123. package/src/features/ajax/aggregate/index.js +43 -13
  124. package/src/features/ajax/constants.js +5 -1
  125. package/src/features/ajax/instrument/index.js +114 -18
  126. package/src/features/generic_events/aggregate/index.js +14 -9
  127. package/src/features/generic_events/constants.js +3 -2
  128. package/src/features/jserrors/aggregate/format-stack-trace.js +3 -3
  129. package/src/features/jserrors/aggregate/index.js +12 -3
  130. package/src/features/logging/aggregate/index.js +7 -6
  131. package/src/features/metrics/aggregate/index.js +5 -1
  132. package/src/features/page_view_event/aggregate/index.js +6 -2
  133. package/src/features/page_view_timing/aggregate/index.js +6 -1
  134. package/src/features/session_replay/aggregate/index.js +7 -1
  135. package/src/features/session_trace/aggregate/index.js +6 -2
  136. package/src/features/soft_navigations/aggregate/ajax-node.js +26 -5
  137. package/src/features/soft_navigations/aggregate/index.js +7 -1
  138. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  139. package/src/features/soft_navigations/aggregate/interaction.js +6 -4
  140. package/src/features/utils/aggregate-base.js +1 -3
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
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.317.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.316.0...v1.317.0) (2026-06-18)
7
+
8
+
9
+ ### Features
10
+
11
+ * Detect and report AJAX payloads ([#1651](https://github.com/newrelic/newrelic-browser-agent/issues/1651)) ([08394db](https://github.com/newrelic/newrelic-browser-agent/commit/08394db8a5a9f2b6fd627e9eee76022977cb433c))
12
+ * Enhance obfuscation to support event filter ([#1782](https://github.com/newrelic/newrelic-browser-agent/issues/1782)) ([c97aaf9](https://github.com/newrelic/newrelic-browser-agent/commit/c97aaf9773cfb0b62a810eb07b37e6f5b96d5ab5))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * Calculate browser_stack_hash from truncated stack trace ([#1789](https://github.com/newrelic/newrelic-browser-agent/issues/1789)) ([f7df627](https://github.com/newrelic/newrelic-browser-agent/commit/f7df627b14e3e5e8fa4a8d94433ea76e9a9902bb))
18
+ * Get responseBodySize from body if content-length is missing ([#1793](https://github.com/newrelic/newrelic-browser-agent/issues/1793)) ([58663ec](https://github.com/newrelic/newrelic-browser-agent/commit/58663ec19dc99ec94e34c17a5dfaa20a07038dba))
19
+ * Patch handling of inlining images for Session Replay ([#1765](https://github.com/newrelic/newrelic-browser-agent/issues/1765)) ([b330f8e](https://github.com/newrelic/newrelic-browser-agent/commit/b330f8e3e35f2fda076967092bcb70e792de6eeb))
20
+
6
21
  ## [1.316.0](https://github.com/newrelic/newrelic-browser-agent/compare/v1.315.0...v1.316.0) (2026-06-01)
7
22
 
8
23
 
@@ -16,6 +16,7 @@ exports.default = void 0;
16
16
  * @property {boolean} [ajax.block_internal] - If true, agent requests going to harvest endpoint are treated as on deny list. In other words, agent will not self-report AJAX.
17
17
  * @property {boolean} [ajax.enabled] - Turn on/off the ajax feature (on by default).
18
18
  * @property {boolean} [ajax.autoStart] - If true, the agent will automatically start the ajax feature. Otherwise, it will be in a deferred state until the `start` API method is called.
19
+ * @property {('none'|'failures'|'all')} [ajax.capture_payloads] - Controls when AJAX request/response payloads are captured. 'none' = never capture, 'failures' = capture only on errors (4xx, 5xx, network errors, GraphQL errors), 'all' = always capture.
19
20
  * @property {Object} [api]
20
21
  * @property {Object} [api.register]
21
22
  * @property {boolean} [api.register.enabled] - If true, the agent will allow registered children to be sent to the server.
@@ -5,8 +5,9 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.mergeInit = void 0;
7
7
  var _constants = require("../../features/generic_events/constants");
8
+ var _constants2 = require("../../features/ajax/constants");
8
9
  var _querySelector = require("../dom/query-selector");
9
- var _constants2 = require("../session/constants");
10
+ var _constants3 = require("../session/constants");
10
11
  var _console = require("../util/console");
11
12
  var _configurable = require("./configurable");
12
13
  /**
@@ -57,7 +58,8 @@ const InitModelFn = () => {
57
58
  deny_list: undefined,
58
59
  block_internal: true,
59
60
  enabled: true,
60
- autoStart: true
61
+ autoStart: true,
62
+ capture_payloads: _constants2.CAPTURE_PAYLOAD_SETTINGS.NONE
61
63
  },
62
64
  api: {
63
65
  register: {
@@ -141,8 +143,8 @@ const InitModelFn = () => {
141
143
  beacon: undefined
142
144
  },
143
145
  session: {
144
- expiresMs: _constants2.DEFAULT_EXPIRES_MS,
145
- inactiveMs: _constants2.DEFAULT_INACTIVE_MS
146
+ expiresMs: _constants3.DEFAULT_EXPIRES_MS,
147
+ inactiveMs: _constants3.DEFAULT_INACTIVE_MS
146
148
  },
147
149
  session_replay: {
148
150
  autoStart: true,
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.316.0";
20
+ const VERSION = exports.VERSION = "1.317.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -31,4 +31,4 @@ const BUILD_ENV = exports.BUILD_ENV = "CDN";
31
31
  const DIST_METHOD = exports.DIST_METHOD = 'CDN';
32
32
  const RRWEB_PACKAGE_NAME = exports.RRWEB_PACKAGE_NAME = '@newrelic/rrweb';
33
33
  // Babel will inline this with the rrweb (fork) version on CDN builds.
34
- const RRWEB_VERSION = exports.RRWEB_VERSION = "1.1.0";
34
+ const RRWEB_VERSION = exports.RRWEB_VERSION = "^1.1.2";
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.316.0";
20
+ const VERSION = exports.VERSION = "1.317.0";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -32,4 +32,4 @@ const BUILD_ENV = exports.BUILD_ENV = 'NPM';
32
32
  const DIST_METHOD = exports.DIST_METHOD = 'NPM';
33
33
  const RRWEB_PACKAGE_NAME = exports.RRWEB_PACKAGE_NAME = '@newrelic/rrweb';
34
34
  // Babel will inline this with the rrweb (fork) version on NPM dist esm/cjs builds.
35
- const RRWEB_VERSION = exports.RRWEB_VERSION = "1.1.0";
35
+ const RRWEB_VERSION = exports.RRWEB_VERSION = "^1.1.2";
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.EVENT_TYPES = void 0;
7
+ /**
8
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
9
+ * SPDX-License-Identifier: Apache-2.0
10
+ */
11
+
12
+ const EVENT_TYPES = exports.EVENT_TYPES = {
13
+ AJAX: 'AjaxRequest',
14
+ PA: 'PageAction',
15
+ UA: 'UserAction',
16
+ BP: 'BrowserPerformance',
17
+ WS: 'WebSocket',
18
+ SPV: 'SecurityPolicyViolation',
19
+ JSE: 'JavaScriptError',
20
+ LOG: 'Log',
21
+ PVE: 'PageView',
22
+ PVT: 'PageViewTiming',
23
+ SR: 'SessionReplay',
24
+ ST: 'SessionTrace',
25
+ BI: 'BrowserInteraction'
26
+ };
@@ -19,6 +19,7 @@ var _console = require("../util/console");
19
19
  var _stringify = require("../util/stringify");
20
20
  var _submitData = require("../util/submit-data");
21
21
  var _globalEvent = require("../dispatch/global-event");
22
+ var _obfuscate = require("../util/obfuscate");
22
23
  /**
23
24
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
24
25
  * SPDX-License-Identifier: Apache-2.0
@@ -33,6 +34,10 @@ class Harvester {
33
34
  initializedAggregates = [];
34
35
  constructor(agentRef) {
35
36
  this.agentRef = agentRef;
37
+
38
+ // Create obfuscator for harvest metadata (referrer URL, etc.)
39
+ // No event type specified - applies to all harvests regardless of feature
40
+ this.obfuscator = new _obfuscate.Obfuscator(agentRef);
36
41
  (0, _eol.subscribeToEOL)(() => {
37
42
  // do one last harvest round or check
38
43
  this.initializedAggregates.forEach(aggregateInst => {
@@ -79,6 +84,7 @@ class Harvester {
79
84
  payload: output.payload,
80
85
  localOpts,
81
86
  submitMethod,
87
+ harvesterObfuscator: this.obfuscator,
82
88
  cbFinished,
83
89
  raw: aggregateInst.harvestOpts.raw,
84
90
  featureName: aggregateInst.featureName,
@@ -127,7 +133,8 @@ function send(agentRef, {
127
133
  cbFinished,
128
134
  raw,
129
135
  featureName,
130
- endpointVersion = 1
136
+ endpointVersion = 1,
137
+ harvesterObfuscator
131
138
  }) {
132
139
  if (!agentRef.info.errorBeacon) return false;
133
140
  let {
@@ -144,7 +151,7 @@ function send(agentRef, {
144
151
  const protocol = agentRef.init.ssl === false ? 'http' : 'https';
145
152
  const perceivedBeacon = agentRef.init.proxy.beacon || agentRef.info.errorBeacon;
146
153
  const url = raw ? "".concat(protocol, "://").concat(perceivedBeacon, "/").concat(endpoint) : "".concat(protocol, "://").concat(perceivedBeacon).concat(endpoint !== _features.RUM ? '/' + endpoint : '', "/").concat(endpointVersion, "/").concat(agentRef.info.licenseKey);
147
- const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint) : '';
154
+ const baseParams = !raw ? baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) : '';
148
155
  let payloadParams = (0, _encode.obj)(qs, agentRef.runtime.maxBytes);
149
156
  if (baseParams === '' && payloadParams.startsWith('&')) {
150
157
  payloadParams = payloadParams.substring(1);
@@ -292,8 +299,9 @@ function cleanPayload(payload = {}) {
292
299
  }
293
300
 
294
301
  // The stuff that gets sent every time.
295
- function baseQueryString(agentRef, qs, endpoint) {
296
- const ref = agentRef.runtime.obfuscator.obfuscateString((0, _cleanUrl.cleanURL)('' + _runtime.globalScope.location));
302
+ function baseQueryString(agentRef, qs, endpoint, harvesterObfuscator) {
303
+ const cleanedURL = (0, _cleanUrl.cleanURL)('' + _runtime.globalScope.location);
304
+ const ref = harvesterObfuscator?.obfuscateString(cleanedURL) ?? cleanedURL;
297
305
  const session = agentRef.runtime.session;
298
306
  const hr = !!session?.state.sessionReplaySentFirstChunk && session?.state.sessionReplayMode === 1 && endpoint !== _features.JSERRORS;
299
307
  const ht = !!session?.state.traceHarvestStarted && session?.state.sessionTraceMode === 1 && ![_features.LOGS, _features.BLOBS].includes(endpoint);
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.canCapturePayload = canCapturePayload;
7
+ exports.createStringAdders = createStringAdders;
8
+ exports.isLikelyHumanReadable = isLikelyHumanReadable;
9
+ exports.parseQueryString = parseQueryString;
10
+ exports.truncateAsString = truncateAsString;
11
+ var _stringify = require("../util/stringify");
12
+ var _constants = require("../../features/ajax/constants");
13
+ /**
14
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
15
+ * SPDX-License-Identifier: Apache-2.0
16
+ */
17
+
18
+ /**
19
+ * Determines whether payload data should be captured based on the capture mode setting,
20
+ * HTTP status code, and GraphQL error status.
21
+ * @param {string} captureMode - The capture mode setting ('none', 'all', or 'failures')
22
+ * @param {number} statusCode - The HTTP status code
23
+ * @param {boolean} hasGQLErrors - Whether the response contains GraphQL errors
24
+ * @returns {boolean} True if payload should be captured
25
+ */
26
+ function canCapturePayload(captureMode, statusCode, hasGQLErrors) {
27
+ if (captureMode === _constants.CAPTURE_PAYLOAD_SETTINGS.ALL) return true;
28
+ if (!captureMode || captureMode === _constants.CAPTURE_PAYLOAD_SETTINGS.NONE) return false;
29
+
30
+ // Default "failures" mode
31
+ return statusCode === 0 || statusCode >= 400 || hasGQLErrors === true;
32
+ }
33
+
34
+ /**
35
+ * Parses a query string into an object of key-value pairs.
36
+ * @param {string} search a query string starting with "?" or without (ex. new URL(...).search)
37
+ * @returns {Object|undefined} Parsed query parameters as key-value pairs. Returns undefined if no valid parameters are found.
38
+ */
39
+ function parseQueryString(search) {
40
+ if (!search || search.length === 0) return;
41
+ const queryParams = {};
42
+ try {
43
+ const searchParams = new URLSearchParams(search);
44
+ searchParams.forEach(function (value, key) {
45
+ queryParams[key] = value;
46
+ });
47
+ } catch (e) {
48
+ // Fallback for environments without URLSearchParams
49
+ }
50
+ return queryParams;
51
+ }
52
+
53
+ /**
54
+ * Determines if the given content type is likely to be human-readable (text-based).
55
+ * @param {Object} headers - The headers object containing content-type
56
+ * @param {*} data - The data to check
57
+ * @returns {boolean} True if the content type is human-readable (text-based)
58
+ */
59
+ function isLikelyHumanReadable(headers, data) {
60
+ if (!headers) return typeof data === 'string';
61
+ var contentType = headers['content-type'];
62
+ if (!contentType) return typeof data === 'string';
63
+ // Normalize to lowercase and extract the mime type (ignore charset, etc.)
64
+ var mimeType = contentType.toLowerCase().split(';')[0].trim();
65
+
66
+ // Check for text/* types
67
+ if (mimeType.indexOf('text/') === 0) return true;
68
+
69
+ // Check for specific application/* types
70
+ var readableAppTypes = ['/json', '/xml', '/xhtml+xml', '/ld+json', '/yaml', '/x-www-form-urlencoded'];
71
+ for (var i = 0; i < readableAppTypes.length; i++) {
72
+ if (mimeType === 'application' + readableAppTypes[i]) return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ /**
78
+ * Truncates a string to ensure its UTF-8 byte length does not exceed 4092 bytes. If truncation is necessary,
79
+ * the string is cut off at a character boundary to avoid breaking multi-byte characters and " ..." is appended to indicate truncation.
80
+ * If not a string, it is first converted to a string using JSON.stringify.
81
+ * @param {*} data The data to truncate.
82
+ * @returns {string}
83
+ */
84
+ function truncateAsString(data) {
85
+ if (!data) return data;
86
+ try {
87
+ if (typeof data !== 'string') data = (0, _stringify.stringify)(data);
88
+ let bytes = 0;
89
+ let i = 0;
90
+ let needsEllipsis = false;
91
+ while (i < data.length) {
92
+ const c = data.charCodeAt(i);
93
+ const charBytes = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0xD800 || c >= 0xE000 ? 3 : 4;
94
+ if (bytes + charBytes > 4092) {
95
+ needsEllipsis = true;
96
+ break;
97
+ }
98
+ bytes += charBytes;
99
+ i += charBytes === 4 ? 2 : 1;
100
+ }
101
+ return data.slice(0, i) + (needsEllipsis ? ' ...' : '');
102
+ } catch (e) {
103
+ return data;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Creates string adder functions for BEL serialization with obfuscation and optional truncation.
109
+ * This ensures a single string table is used while providing separate handling for regular vs payload attributes.
110
+ * @param {Function} getAddStringContext - Function that creates a new string table context
111
+ * @param {Object} obfuscator - Optional obfuscator instance for string obfuscation
112
+ * @returns {{addString: Function, addStringWithTruncation: Function}} Object containing both string adder functions
113
+ */
114
+ function createStringAdders(getAddStringContext, obfuscator) {
115
+ const addStringRaw = getAddStringContext();
116
+ const processString = (str, shouldTruncate) => {
117
+ if (typeof str === 'undefined' || str === '') return addStringRaw(str);
118
+ if (typeof str !== 'string') str = (0, _stringify.stringify)(str);
119
+ const obfuscated = obfuscator?.obfuscateString(str) ?? str;
120
+ const processed = shouldTruncate ? truncateAsString(obfuscated) : obfuscated;
121
+ return addStringRaw(processed);
122
+ };
123
+ return {
124
+ addString: str => processString(str, false),
125
+ addStringWithTruncation: str => processString(str, true)
126
+ };
127
+ }
@@ -9,7 +9,7 @@ exports.nullable = nullable;
9
9
  exports.numeric = numeric;
10
10
  var _stringify = require("../util/stringify");
11
11
  /**
12
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
12
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
13
13
  * SPDX-License-Identifier: Apache-2.0
14
14
  */
15
15
 
@@ -24,13 +24,14 @@ function numeric(n, noDefault) {
24
24
  }
25
25
  return n === undefined || n === 0 ? '' : Math.floor(n).toString(36);
26
26
  }
27
- function getAddStringContext(obfuscator) {
27
+ function getAddStringContext(obfuscator, truncator) {
28
28
  let stringTableIdx = 0;
29
29
  const stringTable = Object.prototype.hasOwnProperty.call(Object, 'create') ? Object.create(null) : {};
30
30
  return addString;
31
31
  function addString(str) {
32
32
  if (typeof str === 'undefined' || str === '') return '';
33
- str = obfuscator.obfuscateString(String(str));
33
+ str = obfuscator?.obfuscateString(String(str)) ?? String(str);
34
+ str = truncator?.(str) ?? str;
34
35
  if (hasOwnProp.call(stringTable, str)) {
35
36
  return numeric(stringTable[str], true);
36
37
  } else {
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.dataSize = dataSize;
7
7
  var _stringify = require("./stringify");
8
8
  /**
9
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
9
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
10
10
  * SPDX-License-Identifier: Apache-2.0
11
11
  */
12
12
 
@@ -17,7 +17,7 @@ var _stringify = require("./stringify");
17
17
  * @returns {(number|undefined)} - The size of the data or undefined if size cannot be determined.
18
18
  */
19
19
  function dataSize(data) {
20
- if (typeof data === 'string' && data.length) return data.length;
20
+ if (typeof data === 'string') return data.length;
21
21
  if (typeof data !== 'object') return undefined;
22
22
  // eslint-disable-next-line
23
23
  if (typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer && data.byteLength) return data.byteLength;
@@ -7,7 +7,7 @@ exports.Obfuscator = void 0;
7
7
  var _protocol = require("../url/protocol");
8
8
  var _console = require("./console");
9
9
  /**
10
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
10
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
11
11
  * SPDX-License-Identifier: Apache-2.0
12
12
  */
13
13
 
@@ -16,6 +16,7 @@ var _console = require("./console");
16
16
  * @typedef {object} ObfuscationRule
17
17
  * @property {string|RegExp} regex The regular expression to match against in the payload
18
18
  * @property {string} [replacement] The string to replace the matched regex with
19
+ * @property {string[]} [eventFilter] An optional list of event types to which this rule should be applied. If not provided, or an empty array, the rule will be applied to all events.
19
20
  */
20
21
 
21
22
  /**
@@ -30,14 +31,33 @@ var _console = require("./console");
30
31
  */
31
32
 
32
33
  class Obfuscator {
33
- constructor(agentRef) {
34
+ /**
35
+ * @param {Object} agentRef - Reference to the agent instance
36
+ * @param {string} [eventType] - Optional event type this obfuscator instance handles.
37
+ * If provided, only rules matching this event type (or rules with no eventFilter) will be applied.
38
+ */
39
+ constructor(agentRef, eventType) {
34
40
  this.agentRef = agentRef;
41
+ this.eventType = eventType;
35
42
  this.warnedRegexMissing = false;
36
43
  this.warnedInvalidRegex = false;
37
44
  this.warnedInvalidReplacement = false;
38
45
  }
39
46
  get obfuscateConfigRules() {
40
- return this.agentRef.init.obfuscate || [];
47
+ const allRules = this.agentRef.init.obfuscate || [];
48
+
49
+ // If this instance has no specific event type, return all rules
50
+ if (!this.eventType) return allRules;
51
+
52
+ // Filter rules to only those that apply to this instance's event type
53
+ return allRules.filter(rule => {
54
+ // If rule has no eventFilter, it applies to all events
55
+ if (!this.#hasValidEventFilter(rule)) {
56
+ return true;
57
+ }
58
+ // Otherwise, check if this instance's event type matches the rule's filter
59
+ return rule.eventFilter.includes(this.eventType);
60
+ });
41
61
  }
42
62
 
43
63
  /**
@@ -63,6 +83,130 @@ class Obfuscator {
63
83
  }, input);
64
84
  }
65
85
 
86
+ /**
87
+ * Traverses an object and obfuscates all string properties.
88
+ * This instance will only apply rules that match its configured event type (if any).
89
+ * For features with mixed event types in their payloads (like generic_events), this will
90
+ * traverse the object and check each object's eventType property against the rules.
91
+ * @param {Object|Array} obj - The object or array to traverse
92
+ * @returns {Object|Array} The modified object
93
+ */
94
+ traverseAndObfuscateEvents(obj) {
95
+ if (!obj || typeof obj !== 'object') return obj;
96
+
97
+ // If this instance was configured with a specific event type, obfuscate everything
98
+ // (rules are already filtered in obfuscateConfigRules getter)
99
+ if (this.eventType) {
100
+ this.#applyFnToAllStrings(obj, this.obfuscateString.bind(this));
101
+ return obj;
102
+ }
103
+
104
+ // For generic obfuscators (no specific event types), check individual eventType properties
105
+ // This path is for features like generic_events that handle multiple event types
106
+ const eventTypesToObfuscate = new Set();
107
+ const globalRules = [];
108
+ const eventSpecificRules = [];
109
+ this.obfuscateConfigRules.forEach(rule => {
110
+ if (this.#hasValidEventFilter(rule)) {
111
+ eventSpecificRules.push(rule);
112
+ rule.eventFilter.forEach(eventType => eventTypesToObfuscate.add(eventType));
113
+ } else {
114
+ globalRules.push(rule);
115
+ }
116
+ });
117
+
118
+ // Optimization: if ALL rules are global (no event-specific rules exist),
119
+ // we can just apply all rules to everything without checking eventType properties
120
+ if (eventSpecificRules.length === 0) {
121
+ this.#applyFnToAllStrings(obj, this.obfuscateString.bind(this));
122
+ return obj;
123
+ }
124
+
125
+ // We have a mix of global and event-specific rules:
126
+ // - Apply event-specific rules only to matching eventTypes
127
+ // - Apply global rules to ALL event types
128
+ this.#applyFnWithEventTypeFilter(obj, this.obfuscateString.bind(this), Array.from(eventTypesToObfuscate), globalRules, null);
129
+ return obj;
130
+ }
131
+
132
+ /**
133
+ * Recursively applies a function to all string properties in an object.
134
+ * @param {Object|Array} obj - The object or array to traverse
135
+ * @param {Function} fn - The function to apply to string properties
136
+ * @private
137
+ */
138
+ #applyFnToAllStrings(obj, fn) {
139
+ if (!obj || typeof obj !== 'object') return;
140
+ Object.keys(obj).forEach(property => {
141
+ const value = obj[property];
142
+ if (typeof value === 'object' && value !== null) {
143
+ this.#applyFnToAllStrings(value, fn);
144
+ } else if (typeof value === 'string') {
145
+ obj[property] = fn(value);
146
+ }
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Recursively applies a function to string properties based on eventType filtering.
152
+ * @param {Object|Array} obj - The object or array to traverse
153
+ * @param {Function} fn - The function to apply to string properties (applies all rules)
154
+ * @param {string[]} eventTypes - Array of event types to apply event-specific obfuscation
155
+ * @param {Array} globalRules - Rules without eventFilter that apply to all events
156
+ * @param {boolean|null} shouldObfuscate - Track obfuscation state: null = not determined yet, true = obfuscate with all rules, false = obfuscate with global rules only
157
+ * @private
158
+ */
159
+ #applyFnWithEventTypeFilter(obj, fn, eventTypes, globalRules, shouldObfuscate) {
160
+ if (!obj || typeof obj !== 'object') return;
161
+
162
+ // Determine the obfuscation state for this object
163
+ let currentShouldObfuscate = shouldObfuscate;
164
+
165
+ // Check if this object has an eventType property
166
+ if ('eventType' in obj && typeof obj.eventType === 'string') {
167
+ currentShouldObfuscate = eventTypes.includes(obj.eventType);
168
+ }
169
+
170
+ // Process all properties
171
+ Object.keys(obj).forEach(property => {
172
+ const value = obj[property];
173
+ if (typeof value === 'object' && value !== null) {
174
+ // Recursively traverse objects/arrays, passing the current obfuscation state
175
+ this.#applyFnWithEventTypeFilter(value, fn, eventTypes, globalRules, currentShouldObfuscate);
176
+ } else if (typeof value === 'string') {
177
+ if (currentShouldObfuscate === true) {
178
+ // Apply all rules (both global and event-specific)
179
+ obj[property] = fn(value);
180
+ } else if (currentShouldObfuscate === false && globalRules.length > 0) {
181
+ // Apply only global rules
182
+ obj[property] = this.#applyRulesToString(value, globalRules);
183
+ }
184
+ // If currentShouldObfuscate is null, we haven't found an eventType yet, so don't obfuscate
185
+ }
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Applies specific obfuscation rules to a string.
191
+ * @param {string} input - The string to obfuscate
192
+ * @param {Array} rules - The rules to apply
193
+ * @returns {string} The obfuscated string
194
+ * @private
195
+ */
196
+ #applyRulesToString(input, rules) {
197
+ if (typeof input !== 'string' || input.trim().length === 0) return input;
198
+ const validatedRules = rules.map(rule => this.validateObfuscationRule(rule));
199
+ return validatedRules.filter(ruleValidation => ruleValidation.isValid).reduce((input, ruleValidation) => {
200
+ const {
201
+ rule
202
+ } = ruleValidation;
203
+ return input.replace(rule.regex, rule.replacement || '*');
204
+ }, input);
205
+ }
206
+ #hasValidEventFilter(rule) {
207
+ return Array.isArray(rule?.eventFilter) && rule.eventFilter.length > 0;
208
+ }
209
+
66
210
  /**
67
211
  * Validates an obfuscation rule and provides errors if any are found.
68
212
  * @param {ObfuscationRule} rule The rule to validate
@@ -10,6 +10,7 @@ var _now = require("../timing/now");
10
10
  var _cleanUrl = require("../url/clean-url");
11
11
  var _nreum = require("../window/nreum");
12
12
  var _pageVisibility = require("../window/page-visibility");
13
+ var _events = require("../constants/events");
13
14
  /**
14
15
  * Copyright 2020-2026 New Relic, Inc. All rights reserved.
15
16
  * SPDX-License-Identifier: Apache-2.0
@@ -40,7 +41,7 @@ function wrapWebSocket(sharedEE) {
40
41
  });
41
42
  });
42
43
  class WrappedWebSocket extends WebSocket {
43
- static name = 'WebSocket';
44
+ static name = _events.EVENT_TYPES.WS;
44
45
  static toString() {
45
46
  // fake native WebSocket when static class is stringified
46
47
  return 'function WebSocket() { [native code] }';
@@ -23,7 +23,7 @@ var _utils = require("../v2/utils");
23
23
  */
24
24
 
25
25
  const wrapped = {};
26
- const XHR_PROPS = ['open', 'send']; // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
26
+ const XHR_PROPS = ['open', 'send', 'setRequestHeader']; // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
27
27
 
28
28
  /**
29
29
  * Wraps the native XMLHttpRequest (XHR) object to emit custom events to its readystatechange event and an assortment
@@ -3,10 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.hasGQLErrors = hasGQLErrors;
6
7
  exports.parseGQL = parseGQL;
7
8
  var _typeCheck = require("../../../common/util/type-check");
8
9
  /**
9
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
10
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
10
11
  * SPDX-License-Identifier: Apache-2.0
11
12
  */
12
13
 
@@ -95,4 +96,42 @@ function parseGQLQueryString(gqlQueryString) {
95
96
  }
96
97
  function validateGQLObject(obj) {
97
98
  return !(typeof obj !== 'object' || !obj.query || typeof obj.query !== 'string');
99
+ }
100
+
101
+ /**
102
+ * Checks if a response object has valid GraphQL errors.
103
+ * @param {object} response - A single GraphQL response object
104
+ * @returns {boolean} True if the response has valid errors
105
+ */
106
+ function hasValidGQLErrors(response) {
107
+ return Array.isArray(response?.errors) && response.errors.some(err => err && typeof err === 'object' && typeof err.message === 'string');
108
+ }
109
+
110
+ /**
111
+ * Checks if a response body contains GraphQL errors according to the GraphQL spec.
112
+ * A valid GraphQL error response contains an "errors" array with at least one error object.
113
+ * Supports both single and batched GraphQL responses.
114
+ * @param {string|object|array} [responseBody] The response body to check
115
+ * @returns {boolean} True if the response contains GraphQL errors
116
+ */
117
+ function hasGQLErrors(responseBody) {
118
+ if (!responseBody) return false;
119
+ try {
120
+ let parsed = responseBody;
121
+
122
+ // Parse string to object if needed
123
+ if (typeof responseBody === 'string') {
124
+ parsed = JSON.parse(responseBody);
125
+ }
126
+
127
+ // Handle batched GraphQL responses (array of response objects)
128
+ if (Array.isArray(parsed)) {
129
+ return parsed.some(hasValidGQLErrors);
130
+ }
131
+
132
+ // Handle single GraphQL response
133
+ return hasValidGQLErrors(parsed);
134
+ } catch (err) {
135
+ return false;
136
+ }
98
137
  }