@newrelic/browser-agent 1.316.0-rc.6 → 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 (64) 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 +1 -1
  5. package/dist/cjs/common/constants/env.npm.js +1 -1
  6. package/dist/cjs/common/payloads/payloads.js +127 -0
  7. package/dist/cjs/common/serialize/bel-serializer.js +2 -1
  8. package/dist/cjs/common/util/data-size.js +2 -2
  9. package/dist/cjs/common/wrap/wrap-xhr.js +1 -1
  10. package/dist/cjs/features/ajax/aggregate/gql.js +40 -1
  11. package/dist/cjs/features/ajax/aggregate/index.js +46 -13
  12. package/dist/cjs/features/ajax/constants.js +6 -1
  13. package/dist/cjs/features/ajax/instrument/index.js +105 -17
  14. package/dist/cjs/features/jserrors/aggregate/format-stack-trace.js +2 -2
  15. package/dist/cjs/features/jserrors/aggregate/index.js +1 -1
  16. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +35 -4
  17. package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  18. package/dist/esm/common/config/init-types.js +1 -0
  19. package/dist/esm/common/config/init.js +3 -1
  20. package/dist/esm/common/constants/env.cdn.js +1 -1
  21. package/dist/esm/common/constants/env.npm.js +1 -1
  22. package/dist/esm/common/payloads/payloads.js +118 -0
  23. package/dist/esm/common/serialize/bel-serializer.js +2 -1
  24. package/dist/esm/common/util/data-size.js +2 -2
  25. package/dist/esm/common/wrap/wrap-xhr.js +1 -1
  26. package/dist/esm/features/ajax/aggregate/gql.js +39 -1
  27. package/dist/esm/features/ajax/aggregate/index.js +47 -14
  28. package/dist/esm/features/ajax/constants.js +5 -0
  29. package/dist/esm/features/ajax/instrument/index.js +106 -18
  30. package/dist/esm/features/jserrors/aggregate/format-stack-trace.js +2 -2
  31. package/dist/esm/features/jserrors/aggregate/index.js +1 -1
  32. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +35 -4
  33. package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/dist/types/common/config/init-types.d.ts +4 -0
  36. package/dist/types/common/config/init-types.d.ts.map +1 -1
  37. package/dist/types/common/config/init.d.ts.map +1 -1
  38. package/dist/types/common/payloads/payloads.d.ts +42 -0
  39. package/dist/types/common/payloads/payloads.d.ts.map +1 -0
  40. package/dist/types/common/serialize/bel-serializer.d.ts +1 -1
  41. package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
  42. package/dist/types/features/ajax/aggregate/gql.d.ts +8 -0
  43. package/dist/types/features/ajax/aggregate/gql.d.ts.map +1 -1
  44. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  45. package/dist/types/features/ajax/constants.d.ts +5 -0
  46. package/dist/types/features/ajax/constants.d.ts.map +1 -1
  47. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  48. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts +5 -0
  49. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  50. package/package.json +2 -2
  51. package/src/common/config/init-types.js +1 -0
  52. package/src/common/config/init.js +2 -1
  53. package/src/common/payloads/payloads.js +135 -0
  54. package/src/common/serialize/bel-serializer.js +2 -1
  55. package/src/common/util/data-size.js +2 -2
  56. package/src/common/wrap/wrap-xhr.js +1 -1
  57. package/src/features/ajax/aggregate/gql.js +42 -1
  58. package/src/features/ajax/aggregate/index.js +38 -13
  59. package/src/features/ajax/constants.js +5 -1
  60. package/src/features/ajax/instrument/index.js +114 -18
  61. package/src/features/jserrors/aggregate/format-stack-trace.js +3 -3
  62. package/src/features/jserrors/aggregate/index.js +1 -1
  63. package/src/features/soft_navigations/aggregate/ajax-node.js +25 -4
  64. package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -9,7 +9,7 @@ export function formatStackTrace(stackLines) {
9
9
  return truncateStackLines(stackLines).replace(stripNewlinesRegex, '');
10
10
  }
11
11
 
12
- // takes array of stack lines and returns string with top 50 and buttom 50 lines
12
+ // takes array of stack lines and returns string with top 50 and bottom 50 lines
13
13
  function truncateStackLines(stackLines) {
14
14
  var stackString;
15
15
  if (stackLines.length > 100) {
@@ -262,7 +262,7 @@ export class Aggregate extends AggregateBase {
262
262
  params.stack_trace = truncateSize(stackInfo.stackString);
263
263
  this.observedAt[bucketHash] = Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(time));
264
264
  } else {
265
- params.browser_stack_hash = stringHashCode(stackInfo.stackString);
265
+ params.browser_stack_hash = stringHashCode(truncateSize(stackInfo.stackString));
266
266
  }
267
267
  params.releaseIds = stringify(this.agentRef.runtime.releaseIds);
268
268
 
@@ -3,6 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { addCustomAttributes, getAddStringContext, nullable, numeric } from '../../../common/serialize/bel-serializer';
6
+ import { createStringAdders } from '../../../common/payloads/payloads';
6
7
  import { AJAX_ID } from '../../ajax/constants';
7
8
  import { NODE_TYPE } from '../constants';
8
9
  import { BelNode } from './bel-node';
@@ -24,6 +25,12 @@ export class AjaxNode extends BelNode {
24
25
  this.targetAttributes = ajaxEvent.targetAttributes;
25
26
  this[AJAX_ID] = ajaxEvent[AJAX_ID]; // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
26
27
 
28
+ // optional payload metadata attributes
29
+ this.requestBody = ajaxEvent.requestBody;
30
+ this.requestHeaders = ajaxEvent.requestHeaders;
31
+ this.requestQuery = ajaxEvent.requestQuery;
32
+ this.responseBody = ajaxEvent.responseBody;
33
+ this.responseHeaders = ajaxEvent.responseHeaders;
27
34
  this.start = ajaxEvent.startTime;
28
35
  this.end = ajaxEvent.endTime;
29
36
  if (ajaxContext?.latestLongtaskEnd) {
@@ -32,7 +39,10 @@ export class AjaxNode extends BelNode {
32
39
  } else this.callbackEnd = this.end; // if no long task was observed, callbackEnd is the same as end
33
40
  }
34
41
  serialize(parentStartTimestamp, agentRef, ajaxObfuscator) {
35
- const addString = getAddStringContext(ajaxObfuscator);
42
+ const {
43
+ addString,
44
+ addStringWithTruncation
45
+ } = createStringAdders(getAddStringContext, ajaxObfuscator);
36
46
  const nodeList = [];
37
47
 
38
48
  // IMPORTANT: The order in which addString is called matters and correlates to the order in which string shows up in the harvest payload. Do not re-order the following code.
@@ -47,11 +57,32 @@ export class AjaxNode extends BelNode {
47
57
  numeric(this.callbackDuration),
48
58
  // not relative
49
59
  addString(this.method), numeric(this.status), addString(this.domain), addString(this.path), numeric(this.txSize), numeric(this.rxSize), this.requestedWith, addString(this.nodeId), nullable(this.spanId, addString, true) + nullable(this.traceId, addString, true) + nullable(this.spanTimestamp, numeric)];
50
- let allAttachedNodes = addCustomAttributes({
51
- ...(this.gql || {}),
60
+ // Regular attributes: obfuscate only
61
+ const regularAttrs = addCustomAttributes({
62
+ [AJAX_ID]: this[AJAX_ID],
52
63
  ...(this.targetAttributes || {}),
53
- [AJAX_ID]: this[AJAX_ID]
64
+ ...(this.gql || {})
54
65
  }, addString);
66
+
67
+ // Payload attributes: obfuscate then truncate
68
+ const payloadAttrs = addCustomAttributes({
69
+ ...(this.requestBody ? {
70
+ requestBody: this.requestBody
71
+ } : {}),
72
+ ...(this.requestHeaders ? {
73
+ requestHeaders: this.requestHeaders
74
+ } : {}),
75
+ ...(this.requestQuery ? {
76
+ requestQuery: this.requestQuery
77
+ } : {}),
78
+ ...(this.responseBody ? {
79
+ responseBody: this.responseBody
80
+ } : {}),
81
+ ...(this.responseHeaders ? {
82
+ responseHeaders: this.responseHeaders
83
+ } : {})
84
+ }, addStringWithTruncation);
85
+ let allAttachedNodes = [...regularAttrs, ...payloadAttrs];
55
86
  this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
56
87
 
57
88
  fields[1] = numeric(allAttachedNodes.length);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { navTimingValues } from '../../../common/timing/nav-timing';
@@ -1 +1 @@
1
- {"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/events.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/session/session-key.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/short-circuit.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
1
+ {"root":["../src/index.js","../src/cdn/experimental.js","../src/cdn/lite.js","../src/cdn/pro.js","../src/cdn/spa.js","../src/common/aggregate/aggregator.js","../src/common/aggregate/event-aggregator.js","../src/common/config/configurable.js","../src/common/config/info.js","../src/common/config/init-types.js","../src/common/config/init.js","../src/common/config/loader-config.js","../src/common/config/runtime.js","../src/common/constants/agent-constants.js","../src/common/constants/env.cdn.js","../src/common/constants/env.js","../src/common/constants/env.npm.js","../src/common/constants/events.js","../src/common/constants/runtime.js","../src/common/constants/shared-channel.js","../src/common/deny-list/deny-list.js","../src/common/dispatch/global-event.js","../src/common/dom/iframe.js","../src/common/dom/query-selector.js","../src/common/dom/selector-path.js","../src/common/drain/drain.js","../src/common/event-emitter/contextual-ee.js","../src/common/event-emitter/event-context.js","../src/common/event-emitter/handle.js","../src/common/event-emitter/register-handler.js","../src/common/event-listener/event-listener-opts.js","../src/common/harvest/harvester.js","../src/common/harvest/types.js","../src/common/ids/bundle-id.js","../src/common/ids/id.js","../src/common/ids/unique-id.js","../src/common/payloads/payloads.js","../src/common/serialize/bel-serializer.js","../src/common/session/constants.js","../src/common/session/session-entity.js","../src/common/session/session-key.js","../src/common/storage/local-storage.js","../src/common/timer/interaction-timer.js","../src/common/timer/timer.js","../src/common/timing/nav-timing.js","../src/common/timing/now.js","../src/common/timing/time-keeper.js","../src/common/unload/eol.js","../src/common/url/canonicalize-url.js","../src/common/url/clean-url.js","../src/common/url/encode.js","../src/common/url/extract-url.js","../src/common/url/location.js","../src/common/url/parse-url.js","../src/common/url/protocol.js","../src/common/util/attribute-size.js","../src/common/util/browser-stack-matchers.js","../src/common/util/console.js","../src/common/util/data-size.js","../src/common/util/event-origin.js","../src/common/util/feature-flags.js","../src/common/util/get-or-set.js","../src/common/util/invoke.js","../src/common/util/monkey-patched.js","../src/common/util/obfuscate.js","../src/common/util/short-circuit.js","../src/common/util/stringify.js","../src/common/util/submit-data.js","../src/common/util/text.js","../src/common/util/traverse.js","../src/common/util/type-check.js","../src/common/util/webdriver-detection.js","../src/common/v2/script-correlation.js","../src/common/v2/script-tracker.js","../src/common/v2/utils.js","../src/common/vitals/constants.js","../src/common/vitals/cumulative-layout-shift.js","../src/common/vitals/first-contentful-paint.js","../src/common/vitals/first-paint.js","../src/common/vitals/interaction-to-next-paint.js","../src/common/vitals/largest-contentful-paint.js","../src/common/vitals/load-time.js","../src/common/vitals/time-to-first-byte.js","../src/common/vitals/vital-metric.js","../src/common/window/load.js","../src/common/window/nreum.js","../src/common/window/page-visibility.js","../src/common/wrap/wrap-events.js","../src/common/wrap/wrap-fetch.js","../src/common/wrap/wrap-function.js","../src/common/wrap/wrap-history.js","../src/common/wrap/wrap-logger.js","../src/common/wrap/wrap-websocket.js","../src/common/wrap/wrap-xhr.js","../src/features/ajax/constants.js","../src/features/ajax/index.js","../src/features/ajax/aggregate/gql.js","../src/features/ajax/aggregate/index.js","../src/features/ajax/instrument/distributed-tracing.js","../src/features/ajax/instrument/index.js","../src/features/ajax/instrument/response-size.js","../src/features/generic_events/constants.js","../src/features/generic_events/index.js","../src/features/generic_events/aggregate/index.js","../src/features/generic_events/aggregate/user-actions/aggregated-user-action.js","../src/features/generic_events/aggregate/user-actions/user-actions-aggregator.js","../src/features/generic_events/instrument/index.js","../src/features/jserrors/constants.js","../src/features/jserrors/index.js","../src/features/jserrors/aggregate/canonical-function-name.js","../src/features/jserrors/aggregate/cause-string.js","../src/features/jserrors/aggregate/compute-stack-trace.js","../src/features/jserrors/aggregate/format-stack-trace.js","../src/features/jserrors/aggregate/index.js","../src/features/jserrors/aggregate/internal-errors.js","../src/features/jserrors/aggregate/string-hash-code.js","../src/features/jserrors/instrument/index.js","../src/features/jserrors/shared/cast-error.js","../src/features/jserrors/shared/uncaught-error.js","../src/features/logging/constants.js","../src/features/logging/index.js","../src/features/logging/aggregate/index.js","../src/features/logging/instrument/index.js","../src/features/logging/shared/log.js","../src/features/logging/shared/utils.js","../src/features/metrics/constants.js","../src/features/metrics/index.js","../src/features/metrics/aggregate/framework-detection.js","../src/features/metrics/aggregate/harvest-metadata.js","../src/features/metrics/aggregate/index.js","../src/features/metrics/instrument/index.js","../src/features/page_action/constants.js","../src/features/page_action/index.js","../src/features/page_action/instrument/index.js","../src/features/page_view_event/constants.js","../src/features/page_view_event/index.js","../src/features/page_view_event/aggregate/index.js","../src/features/page_view_event/aggregate/initialized-features.js","../src/features/page_view_event/instrument/index.js","../src/features/page_view_timing/constants.js","../src/features/page_view_timing/index.js","../src/features/page_view_timing/aggregate/index.js","../src/features/page_view_timing/instrument/index.js","../src/features/session_replay/constants.js","../src/features/session_replay/index.js","../src/features/session_replay/aggregate/index.js","../src/features/session_replay/instrument/index.js","../src/features/session_replay/shared/recorder-events.js","../src/features/session_replay/shared/recorder.js","../src/features/session_replay/shared/stylesheet-evaluator.js","../src/features/session_replay/shared/utils.js","../src/features/session_trace/constants.js","../src/features/session_trace/index.js","../src/features/session_trace/aggregate/index.js","../src/features/session_trace/aggregate/trace/node.js","../src/features/session_trace/aggregate/trace/storage.js","../src/features/session_trace/aggregate/trace/utils.js","../src/features/session_trace/instrument/index.js","../src/features/soft_navigations/constants.js","../src/features/soft_navigations/index.js","../src/features/soft_navigations/aggregate/ajax-node.js","../src/features/soft_navigations/aggregate/bel-node.js","../src/features/soft_navigations/aggregate/index.js","../src/features/soft_navigations/aggregate/initial-page-load-interaction.js","../src/features/soft_navigations/aggregate/interaction.js","../src/features/soft_navigations/instrument/index.js","../src/features/utils/agent-session.js","../src/features/utils/aggregate-base.js","../src/features/utils/event-buffer.js","../src/features/utils/feature-base.js","../src/features/utils/feature-gates.js","../src/features/utils/instrument-base.js","../src/interfaces/registered-entity.js","../src/loaders/agent-base.js","../src/loaders/agent.js","../src/loaders/api-base.js","../src/loaders/browser-agent.js","../src/loaders/micro-agent-base.js","../src/loaders/micro-agent.js","../src/loaders/api/addPageAction.js","../src/loaders/api/addRelease.js","../src/loaders/api/addToTrace.js","../src/loaders/api/consent.js","../src/loaders/api/constants.js","../src/loaders/api/finished.js","../src/loaders/api/interaction-types.js","../src/loaders/api/interaction.js","../src/loaders/api/log.js","../src/loaders/api/measure.js","../src/loaders/api/noticeError.js","../src/loaders/api/pauseReplay.js","../src/loaders/api/recordCustomEvent.js","../src/loaders/api/recordReplay.js","../src/loaders/api/register-api-types.js","../src/loaders/api/register.js","../src/loaders/api/setApplicationVersion.js","../src/loaders/api/setCustomAttribute.js","../src/loaders/api/setErrorHandler.js","../src/loaders/api/setPageViewName.js","../src/loaders/api/setUserId.js","../src/loaders/api/sharedHandlers.js","../src/loaders/api/start.js","../src/loaders/api/topLevelCallers.js","../src/loaders/api/wrapLogger.js","../src/loaders/configure/configure.js","../src/loaders/configure/nonce.js","../src/loaders/configure/public-path.js","../src/loaders/features/enabled-features.js","../src/loaders/features/featureDependencies.js","../src/loaders/features/features.js"],"version":"5.9.3"}
@@ -18,6 +18,10 @@ export type Init = {
18
18
  * - 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
19
  */
20
20
  autoStart?: boolean | undefined;
21
+ /**
22
+ * - 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.
23
+ */
24
+ capture_payloads?: "none" | "failures" | "all" | undefined;
21
25
  } | undefined;
22
26
  api?: {
23
27
  register?: {
@@ -1 +1 @@
1
- {"version":3,"file":"init-types.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init-types.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAsCsB,MAAM,GAAG,MAAM;qBAAe,MAAM"}
1
+ {"version":3,"file":"init-types.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init-types.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAuCsB,MAAM,GAAG,MAAM;qBAAe,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init.js"],"names":[],"mappings":"AA+IO,0CAEN;mBAtIY,OAAO,cAAc,EAAE,IAAI"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/common/config/init.js"],"names":[],"mappings":"AAgJO,0CAEN;mBAtIY,OAAO,cAAc,EAAE,IAAI"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Determines whether payload data should be captured based on the capture mode setting,
3
+ * HTTP status code, and GraphQL error status.
4
+ * @param {string} captureMode - The capture mode setting ('none', 'all', or 'failures')
5
+ * @param {number} statusCode - The HTTP status code
6
+ * @param {boolean} hasGQLErrors - Whether the response contains GraphQL errors
7
+ * @returns {boolean} True if payload should be captured
8
+ */
9
+ export function canCapturePayload(captureMode: string, statusCode: number, hasGQLErrors: boolean): boolean;
10
+ /**
11
+ * Parses a query string into an object of key-value pairs.
12
+ * @param {string} search a query string starting with "?" or without (ex. new URL(...).search)
13
+ * @returns {Object|undefined} Parsed query parameters as key-value pairs. Returns undefined if no valid parameters are found.
14
+ */
15
+ export function parseQueryString(search: string): Object | undefined;
16
+ /**
17
+ * Determines if the given content type is likely to be human-readable (text-based).
18
+ * @param {Object} headers - The headers object containing content-type
19
+ * @param {*} data - The data to check
20
+ * @returns {boolean} True if the content type is human-readable (text-based)
21
+ */
22
+ export function isLikelyHumanReadable(headers: Object, data: any): boolean;
23
+ /**
24
+ * Truncates a string to ensure its UTF-8 byte length does not exceed 4092 bytes. If truncation is necessary,
25
+ * the string is cut off at a character boundary to avoid breaking multi-byte characters and " ..." is appended to indicate truncation.
26
+ * If not a string, it is first converted to a string using JSON.stringify.
27
+ * @param {*} data The data to truncate.
28
+ * @returns {string}
29
+ */
30
+ export function truncateAsString(data: any): string;
31
+ /**
32
+ * Creates string adder functions for BEL serialization with obfuscation and optional truncation.
33
+ * This ensures a single string table is used while providing separate handling for regular vs payload attributes.
34
+ * @param {Function} getAddStringContext - Function that creates a new string table context
35
+ * @param {Object} obfuscator - Optional obfuscator instance for string obfuscation
36
+ * @returns {{addString: Function, addStringWithTruncation: Function}} Object containing both string adder functions
37
+ */
38
+ export function createStringAdders(getAddStringContext: Function, obfuscator: Object): {
39
+ addString: Function;
40
+ addStringWithTruncation: Function;
41
+ };
42
+ //# sourceMappingURL=payloads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payloads.d.ts","sourceRoot":"","sources":["../../../../src/common/payloads/payloads.js"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH,+CALW,MAAM,cACN,MAAM,gBACN,OAAO,GACL,OAAO,CAQnB;AAED;;;;GAIG;AACH,yCAHW,MAAM,GACJ,MAAM,GAAC,SAAS,CAgB5B;AAED;;;;;GAKG;AACH,+CAJW,MAAM,QACN,GAAC,GACC,OAAO,CA2BnB;AAED;;;;;;GAMG;AACH,uCAHW,GAAC,GACC,MAAM,CA2BlB;AAED;;;;;;GAMG;AACH,8EAHW,MAAM,GACJ;IAAC,SAAS,WAAW;IAAC,uBAAuB,WAAU;CAAC,CAiBpE"}
@@ -1,5 +1,5 @@
1
1
  export function nullable(val: any, fn: any, comma: any): string;
2
2
  export function numeric(n: any, noDefault: any): string;
3
- export function getAddStringContext(obfuscator: any): (str: any) => string;
3
+ export function getAddStringContext(obfuscator: any, truncator: any): (str: any) => string;
4
4
  export function addCustomAttributes(attrs: any, addString: any): any[];
5
5
  //# sourceMappingURL=bel-serializer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bel-serializer.d.ts","sourceRoot":"","sources":["../../../../src/common/serialize/bel-serializer.js"],"names":[],"mappings":"AAUA,gEAIC;AAED,wDAKC;AAED,2EAgBC;AAED,uEAwCC"}
1
+ {"version":3,"file":"bel-serializer.d.ts","sourceRoot":"","sources":["../../../../src/common/serialize/bel-serializer.js"],"names":[],"mappings":"AAUA,gEAIC;AAED,wDAKC;AAED,2FAiBC;AAED,uEAwCC"}
@@ -12,6 +12,14 @@
12
12
  * @returns {GQLMetadata | undefined}
13
13
  */
14
14
  export function parseGQL({ body, query }?: object | string): GQLMetadata | undefined;
15
+ /**
16
+ * Checks if a response body contains GraphQL errors according to the GraphQL spec.
17
+ * A valid GraphQL error response contains an "errors" array with at least one error object.
18
+ * Supports both single and batched GraphQL responses.
19
+ * @param {string|object|array} [responseBody] The response body to check
20
+ * @returns {boolean} True if the response contains GraphQL errors
21
+ */
22
+ export function hasGQLErrors(responseBody?: string | object | array): boolean;
15
23
  export type GQLMetadata = {
16
24
  /**
17
25
  * Name of the operation
@@ -1 +1 @@
1
- {"version":3,"file":"gql.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/gql.js"],"names":[],"mappings":"AAMA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,2CAJW,MAAM,GAAC,MAAM,GAEX,WAAW,GAAG,SAAS,CAYnC;;;;;mBAtBa,MAAM;;;;mBACN,MAAM;;;;wBACN,MAAM"}
1
+ {"version":3,"file":"gql.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/gql.js"],"names":[],"mappings":"AAMA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,2CAJW,MAAM,GAAC,MAAM,GAEX,WAAW,GAAG,SAAS,CAYnC;AAkFD;;;;;;GAMG;AACH,4CAHW,MAAM,GAAC,MAAM,GAAC,KAAK,GACjB,OAAO,CAsBnB;;;;;mBAnIa,MAAM;;;;mBACN,MAAM;;;;wBACN,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAmBA;IACE,2BAAiC;IAEjC,2BA6BC;IAvBC,uBAA4D;IAyB9D,0GAwEC;IAED,+CAOC;IAED,iDAyDC;CACF;8BAxL6B,4BAA4B;2BAI/B,gCAAgC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/aggregate/index.js"],"names":[],"mappings":"AAoBA;IACE,2BAAiC;IAEjC,2BA6BC;IAvBC,uBAA4D;IAyB9D,0GAqFC;IAED,+CAOC;IAED,iDAoEC;CACF;8BAjN6B,4BAA4B;2BAK/B,gCAAgC"}
@@ -1,3 +1,8 @@
1
1
  export const FEATURE_NAME: string;
2
+ export namespace CAPTURE_PAYLOAD_SETTINGS {
3
+ let NONE: string;
4
+ let FAILURES: string;
5
+ let ALL: string;
6
+ }
2
7
  export const AJAX_ID: "ajaxRequest.id";
3
8
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/ajax/constants.js"],"names":[],"mappings":"AAMA,kCAA8C;AAE9C,sBAAuB,gBAAgB,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/features/ajax/constants.js"],"names":[],"mappings":"AAMA,kCAA8C;;;;;;AAM9C,sBAAuB,gBAAgB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA8BA;IACE,2BAAiC;IACjC,2BAkCC;IA/BC,OAA0B;IAE1B,8DAAkF;CA8BrF;AA6VD,qCAA8B;+BAjZC,6BAA6B;mBAFzC,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/ajax/instrument/index.js"],"names":[],"mappings":"AA+BA;IACE,2BAAiC;IACjC,2BAiCC;IA9BC,OAA0B;IAE1B,8DAAkF;CA6BrF;AA6bD,qCAA8B;+BAjfC,6BAA6B;mBAFzC,uBAAuB"}
@@ -13,6 +13,11 @@ export class AjaxNode extends BelNode {
13
13
  spanTimestamp: any;
14
14
  gql: any;
15
15
  targetAttributes: any;
16
+ requestBody: any;
17
+ requestHeaders: any;
18
+ requestQuery: any;
19
+ responseBody: any;
20
+ responseHeaders: any;
16
21
  callbackEnd: any;
17
22
  serialize(parentStartTimestamp: any, agentRef: any, ajaxObfuscator: any): string;
18
23
  "ajaxRequest.id": any;
@@ -1 +1 @@
1
- {"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AASA;IACE,8CAuBC;IArBC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IACxB,sBAAkD;IAMhD,iBAAoE;IAKxE,iFAkCC;IA5CC,sBAAkC;CA6CrC;wBA/DuB,YAAY"}
1
+ {"version":3,"file":"ajax-node.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/ajax-node.js"],"names":[],"mappings":"AAUA;IACE,8CA8BC;IA5BC,gBAA6B;IAC7B,YAA8B;IAC9B,YAA8B;IAC9B,YAA8B;IAC9B,UAA0B;IAC1B,YAAmC;IACnC,YAAoC;IACpC,+BAAwD;IACxD,YAA8B;IAC9B,aAAgC;IAChC,mBAA4C;IAC5C,SAAwB;IACxB,sBAAkD;IAIlD,iBAAwC;IACxC,oBAA8C;IAC9C,kBAA0C;IAC1C,kBAA0C;IAC1C,qBAAgD;IAK9C,iBAAoE;IAKxE,iFA+CC;IAhEC,sBAAkC;CAiErC;wBAnFuB,YAAY"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.316.0-rc.6",
3
+ "version": "1.317.0",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -284,4 +284,4 @@
284
284
  "README.md",
285
285
  "CHANGELOG.md"
286
286
  ]
287
- }
287
+ }
@@ -11,6 +11,7 @@
11
11
  * @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.
12
12
  * @property {boolean} [ajax.enabled] - Turn on/off the ajax feature (on by default).
13
13
  * @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.
14
+ * @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.
14
15
  * @property {Object} [api]
15
16
  * @property {Object} [api.register]
16
17
  * @property {boolean} [api.register.enabled] - If true, the agent will allow registered children to be sent to the server.
@@ -3,6 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { FEATURE_FLAGS } from '../../features/generic_events/constants'
6
+ import { CAPTURE_PAYLOAD_SETTINGS } from '../../features/ajax/constants'
6
7
  import { isValidSelector } from '../dom/query-selector'
7
8
  import { DEFAULT_EXPIRES_MS, DEFAULT_INACTIVE_MS } from '../session/constants'
8
9
  import { warn } from '../util/console'
@@ -47,7 +48,7 @@ const InitModelFn = () => {
47
48
  }
48
49
  }
49
50
  return {
50
- ajax: { deny_list: undefined, block_internal: true, enabled: true, autoStart: true },
51
+ ajax: { deny_list: undefined, block_internal: true, enabled: true, autoStart: true, capture_payloads: CAPTURE_PAYLOAD_SETTINGS.NONE },
51
52
  api: {
52
53
  register: {
53
54
  get enabled () { return hiddenState.feature_flags.includes(FEATURE_FLAGS.REGISTER) || hiddenState.experimental.register },
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { stringify } from '../util/stringify'
7
+ import { CAPTURE_PAYLOAD_SETTINGS } from '../../features/ajax/constants'
8
+
9
+ /**
10
+ * Determines whether payload data should be captured based on the capture mode setting,
11
+ * HTTP status code, and GraphQL error status.
12
+ * @param {string} captureMode - The capture mode setting ('none', 'all', or 'failures')
13
+ * @param {number} statusCode - The HTTP status code
14
+ * @param {boolean} hasGQLErrors - Whether the response contains GraphQL errors
15
+ * @returns {boolean} True if payload should be captured
16
+ */
17
+ export function canCapturePayload (captureMode, statusCode, hasGQLErrors) {
18
+ if (captureMode === CAPTURE_PAYLOAD_SETTINGS.ALL) return true
19
+ if (!captureMode || captureMode === CAPTURE_PAYLOAD_SETTINGS.NONE) return false
20
+
21
+ // Default "failures" mode
22
+ return statusCode === 0 || statusCode >= 400 || hasGQLErrors === true
23
+ }
24
+
25
+ /**
26
+ * Parses a query string into an object of key-value pairs.
27
+ * @param {string} search a query string starting with "?" or without (ex. new URL(...).search)
28
+ * @returns {Object|undefined} Parsed query parameters as key-value pairs. Returns undefined if no valid parameters are found.
29
+ */
30
+ export function parseQueryString (search) {
31
+ if (!search || search.length === 0) return
32
+
33
+ const queryParams = {}
34
+ try {
35
+ const searchParams = new URLSearchParams(search)
36
+ searchParams.forEach(function (value, key) {
37
+ queryParams[key] = value
38
+ })
39
+ } catch (e) {
40
+ // Fallback for environments without URLSearchParams
41
+ }
42
+
43
+ return queryParams
44
+ }
45
+
46
+ /**
47
+ * Determines if the given content type is likely to be human-readable (text-based).
48
+ * @param {Object} headers - The headers object containing content-type
49
+ * @param {*} data - The data to check
50
+ * @returns {boolean} True if the content type is human-readable (text-based)
51
+ */
52
+ export function isLikelyHumanReadable (headers, data) {
53
+ if (!headers) return typeof data === 'string'
54
+ var contentType = headers['content-type']
55
+ if (!contentType) return typeof data === 'string'
56
+ // Normalize to lowercase and extract the mime type (ignore charset, etc.)
57
+ var mimeType = contentType.toLowerCase().split(';')[0].trim()
58
+
59
+ // Check for text/* types
60
+ if (mimeType.indexOf('text/') === 0) return true
61
+
62
+ // Check for specific application/* types
63
+ var readableAppTypes = [
64
+ '/json',
65
+ '/xml',
66
+ '/xhtml+xml',
67
+ '/ld+json',
68
+ '/yaml',
69
+ '/x-www-form-urlencoded'
70
+ ]
71
+
72
+ for (var i = 0; i < readableAppTypes.length; i++) {
73
+ if (mimeType === 'application' + readableAppTypes[i]) return true
74
+ }
75
+
76
+ return false
77
+ }
78
+
79
+ /**
80
+ * Truncates a string to ensure its UTF-8 byte length does not exceed 4092 bytes. If truncation is necessary,
81
+ * the string is cut off at a character boundary to avoid breaking multi-byte characters and " ..." is appended to indicate truncation.
82
+ * If not a string, it is first converted to a string using JSON.stringify.
83
+ * @param {*} data The data to truncate.
84
+ * @returns {string}
85
+ */
86
+ export function truncateAsString (data) {
87
+ if (!data) return data
88
+ try {
89
+ if (typeof data !== 'string') data = stringify(data)
90
+ let bytes = 0
91
+ let i = 0
92
+ let needsEllipsis = false
93
+
94
+ while (i < data.length) {
95
+ const c = data.charCodeAt(i)
96
+ const charBytes = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0xD800 || c >= 0xE000 ? 3 : 4
97
+
98
+ if (bytes + charBytes > 4092) {
99
+ needsEllipsis = true
100
+ break
101
+ }
102
+
103
+ bytes += charBytes
104
+ i += charBytes === 4 ? 2 : 1
105
+ }
106
+
107
+ return data.slice(0, i) + (needsEllipsis ? ' ...' : '')
108
+ } catch (e) {
109
+ return data
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Creates string adder functions for BEL serialization with obfuscation and optional truncation.
115
+ * This ensures a single string table is used while providing separate handling for regular vs payload attributes.
116
+ * @param {Function} getAddStringContext - Function that creates a new string table context
117
+ * @param {Object} obfuscator - Optional obfuscator instance for string obfuscation
118
+ * @returns {{addString: Function, addStringWithTruncation: Function}} Object containing both string adder functions
119
+ */
120
+ export function createStringAdders (getAddStringContext, obfuscator) {
121
+ const addStringRaw = getAddStringContext()
122
+
123
+ const processString = (str, shouldTruncate) => {
124
+ if (typeof str === 'undefined' || str === '') return addStringRaw(str)
125
+ if (typeof str !== 'string') str = stringify(str)
126
+ const obfuscated = obfuscator?.obfuscateString(str) ?? str
127
+ const processed = shouldTruncate ? truncateAsString(obfuscated) : obfuscated
128
+ return addStringRaw(processed)
129
+ }
130
+
131
+ return {
132
+ addString: (str) => processString(str, false),
133
+ addStringWithTruncation: (str) => processString(str, true)
134
+ }
135
+ }
@@ -21,7 +21,7 @@ export function numeric (n, noDefault) {
21
21
  return (n === undefined || n === 0) ? '' : Math.floor(n).toString(36)
22
22
  }
23
23
 
24
- export function getAddStringContext (obfuscator) {
24
+ export function getAddStringContext (obfuscator, truncator) {
25
25
  let stringTableIdx = 0
26
26
  const stringTable = Object.prototype.hasOwnProperty.call(Object, 'create') ? Object.create(null) : {}
27
27
 
@@ -30,6 +30,7 @@ export function getAddStringContext (obfuscator) {
30
30
  function addString (str) {
31
31
  if (typeof str === 'undefined' || str === '') return ''
32
32
  str = obfuscator?.obfuscateString(String(str)) ?? String(str)
33
+ str = truncator?.(str) ?? str
33
34
  if (hasOwnProp.call(stringTable, str)) {
34
35
  return numeric(stringTable[str], true)
35
36
  } else {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
@@ -12,7 +12,7 @@ import { stringify } from './stringify'
12
12
  * @returns {(number|undefined)} - The size of the data or undefined if size cannot be determined.
13
13
  */
14
14
  export function dataSize (data) {
15
- if (typeof data === 'string' && data.length) return data.length
15
+ if (typeof data === 'string') return data.length
16
16
  if (typeof data !== 'object') return undefined
17
17
  // eslint-disable-next-line
18
18
  if (typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer && data.byteLength) return data.byteLength
@@ -17,7 +17,7 @@ import { warn } from '../util/console'
17
17
  import { findTargetsFromStackTrace } from '../v2/utils'
18
18
 
19
19
  const wrapped = {}
20
- const XHR_PROPS = ['open', 'send'] // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
20
+ const XHR_PROPS = ['open', 'send', 'setRequestHeader'] // these are the specific funcs being wrapped on all XMLHttpRequests(.prototype)
21
21
 
22
22
  /**
23
23
  * Wraps the native XMLHttpRequest (XHR) object to emit custom events to its readystatechange event and an assortment
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright 2020-2025 New Relic, Inc. All rights reserved.
2
+ * Copyright 2020-2026 New Relic, Inc. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { isPureObject } from '../../../common/util/type-check'
@@ -97,3 +97,44 @@ function parseGQLQueryString (gqlQueryString) {
97
97
  function validateGQLObject (obj) {
98
98
  return !(typeof obj !== 'object' || !obj.query || typeof obj.query !== 'string')
99
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) &&
108
+ response.errors.some(err =>
109
+ err && typeof err === 'object' && typeof err.message === 'string'
110
+ )
111
+ }
112
+
113
+ /**
114
+ * Checks if a response body contains GraphQL errors according to the GraphQL spec.
115
+ * A valid GraphQL error response contains an "errors" array with at least one error object.
116
+ * Supports both single and batched GraphQL responses.
117
+ * @param {string|object|array} [responseBody] The response body to check
118
+ * @returns {boolean} True if the response contains GraphQL errors
119
+ */
120
+ export function hasGQLErrors (responseBody) {
121
+ if (!responseBody) return false
122
+ try {
123
+ let parsed = responseBody
124
+
125
+ // Parse string to object if needed
126
+ if (typeof responseBody === 'string') {
127
+ parsed = JSON.parse(responseBody)
128
+ }
129
+
130
+ // Handle batched GraphQL responses (array of response objects)
131
+ if (Array.isArray(parsed)) {
132
+ return parsed.some(hasValidGQLErrors)
133
+ }
134
+
135
+ // Handle single GraphQL response
136
+ return hasValidGQLErrors(parsed)
137
+ } catch (err) {
138
+ return false
139
+ }
140
+ }