@newrelic/browser-agent 1.320.0 → 1.320.1

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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/common/constants/env.cdn.js +1 -1
  3. package/dist/cjs/common/constants/env.npm.js +1 -1
  4. package/dist/cjs/common/payloads/payloads.js +3 -2
  5. package/dist/cjs/common/serialize/bel-serializer.js +17 -4
  6. package/dist/cjs/common/v2/mfe-vitals.js +34 -11
  7. package/dist/cjs/features/ajax/aggregate/index.js +19 -8
  8. package/dist/cjs/features/page_view_timing/aggregate/index.js +28 -13
  9. package/dist/cjs/features/soft_navigations/aggregate/ajax-node.js +16 -4
  10. package/dist/cjs/features/soft_navigations/aggregate/interaction.js +10 -3
  11. package/dist/esm/common/constants/env.cdn.js +1 -1
  12. package/dist/esm/common/constants/env.npm.js +1 -1
  13. package/dist/esm/common/payloads/payloads.js +3 -2
  14. package/dist/esm/common/serialize/bel-serializer.js +17 -4
  15. package/dist/esm/common/v2/mfe-vitals.js +34 -11
  16. package/dist/esm/features/ajax/aggregate/index.js +19 -8
  17. package/dist/esm/features/page_view_timing/aggregate/index.js +28 -13
  18. package/dist/esm/features/soft_navigations/aggregate/ajax-node.js +16 -4
  19. package/dist/esm/features/soft_navigations/aggregate/interaction.js +10 -3
  20. package/dist/types/common/payloads/payloads.d.ts +3 -2
  21. package/dist/types/common/payloads/payloads.d.ts.map +1 -1
  22. package/dist/types/common/serialize/bel-serializer.d.ts +9 -1
  23. package/dist/types/common/serialize/bel-serializer.d.ts.map +1 -1
  24. package/dist/types/common/v2/mfe-vitals.d.ts.map +1 -1
  25. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  26. package/dist/types/features/page_view_timing/aggregate/index.d.ts +0 -1
  27. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  28. package/dist/types/features/soft_navigations/aggregate/ajax-node.d.ts.map +1 -1
  29. package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/common/payloads/payloads.js +3 -2
  32. package/src/common/serialize/bel-serializer.js +13 -4
  33. package/src/common/v2/mfe-vitals.js +39 -12
  34. package/src/features/ajax/aggregate/index.js +12 -9
  35. package/src/features/page_view_timing/aggregate/index.js +19 -14
  36. package/src/features/soft_navigations/aggregate/ajax-node.js +10 -7
  37. package/src/features/soft_navigations/aggregate/interaction.js +6 -5
@@ -145,6 +145,7 @@ export class Aggregate extends AggregateBase {
145
145
  if (!eventBuffer.length) return;
146
146
  const {
147
147
  addString,
148
+ addStringRaw,
148
149
  addStringWithTruncation
149
150
  } = createStringAdders(getAddStringContext, this.obfuscator);
150
151
  let payload = 'bel.7;';
@@ -158,11 +159,11 @@ export class Aggregate extends AggregateBase {
158
159
  // callbackEnd
159
160
  numeric(0),
160
161
  // no callbackDuration for non-SPA events
161
- addString(event.method), numeric(event.status), addString(event.domain), addString(event.path), numeric(event.requestSize), numeric(event.responseSize), event.type === 'fetch' ? 1 : '', addString(0),
162
+ addStringRaw(event.method), numeric(event.status), addString(event.domain), addString(event.path), numeric(event.requestSize), numeric(event.responseSize), event.type === 'fetch' ? 1 : '', addStringRaw(0),
162
163
  // nodeId
163
- nullable(event.spanId, addString, true) +
164
+ nullable(event.spanId, addStringRaw, true) +
164
165
  // guid
165
- nullable(event.traceId, addString, true) +
166
+ nullable(event.traceId, addStringRaw, true) +
166
167
  // traceId
167
168
  nullable(event.spanTimestamp, numeric, false) // timestamp
168
169
  ];
@@ -172,14 +173,21 @@ export class Aggregate extends AggregateBase {
172
173
  const jsAttributes = this.agentRef.info.jsAttributes;
173
174
 
174
175
  // Regular attributes: obfuscate only
175
- const regularAttrs = addCustomAttributes({
176
+ let regularAttrs = addCustomAttributes({
176
177
  ...(jsAttributes || {}),
177
- [AJAX_ID]: event[AJAX_ID],
178
- // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
179
178
  ...(event.targetAttributes || {}),
180
179
  // used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
181
180
  ...(event.gql || {})
182
- }, addString);
181
+ }, {
182
+ addKey: addStringRaw,
183
+ addVal: addString
184
+ });
185
+ regularAttrs.push(...addCustomAttributes({
186
+ [AJAX_ID]: event[AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
187
+ }, {
188
+ addKey: addStringRaw,
189
+ addVal: addStringRaw
190
+ }));
183
191
 
184
192
  // Payload attributes: obfuscate then truncate
185
193
  const payloadAttrs = addCustomAttributes({
@@ -198,7 +206,10 @@ export class Aggregate extends AggregateBase {
198
206
  ...(event.responseHeaders ? {
199
207
  responseHeaders: event.responseHeaders
200
208
  } : {})
201
- }, addStringWithTruncation);
209
+ }, {
210
+ addKey: addStringRaw,
211
+ addVal: addStringWithTruncation
212
+ });
202
213
  const attrParts = [...regularAttrs, ...payloadAttrs];
203
214
  fields.unshift(numeric(attrParts.length));
204
215
  insert += fields.join(',');
@@ -23,6 +23,7 @@ import { loadTime } from '../../../common/vitals/load-time';
23
23
  import { webdriverDetected } from '../../../common/util/webdriver-detection';
24
24
  import { cleanURL } from '../../../common/url/clean-url';
25
25
  import { EVENT_TYPES } from '../../../common/constants/events';
26
+ import { createStringAdders } from '../../../common/payloads/payloads';
26
27
  export class Aggregate extends AggregateBase {
27
28
  static featureName = FEATURE_NAME;
28
29
  #handleVitalMetric = ({
@@ -98,6 +99,7 @@ export class Aggregate extends AggregateBase {
98
99
  if (name !== VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT && cumulativeLayoutShift.current.value >= 0) {
99
100
  attrs.cls = cumulativeLayoutShift.current.value;
100
101
  }
102
+ attrs.webdriverDetected = webdriverDetected;
101
103
  const timing = {
102
104
  name,
103
105
  value,
@@ -127,15 +129,9 @@ export class Aggregate extends AggregateBase {
127
129
  loadState: document.readyState
128
130
  });
129
131
  }
130
- appendGlobalCustomAttributes(timing) {
131
- var timingAttributes = timing.attrs || {};
132
- var reservedAttributes = ['size', 'eid', 'cls', 'type', 'fid', 'elTag', 'elUrl', 'net-type', 'net-etype', 'net-rtt', 'net-dlink'];
133
- Object.entries(this.agentRef.info.jsAttributes || {}).forEach(([key, val]) => {
134
- if (reservedAttributes.indexOf(key) < 0) {
135
- timingAttributes[key] = val;
136
- }
137
- });
138
- timingAttributes.webdriverDetected = webdriverDetected;
132
+ #getGlobalCustomAttributes() {
133
+ const reservedAttributes = ['size', 'eid', 'cls', 'type', 'fid', 'elTag', 'elUrl', 'net-type', 'net-etype', 'net-rtt', 'net-dlink', 'webdriverDetected'];
134
+ return Object.fromEntries(Object.entries(this.agentRef.info.jsAttributes || {}).filter(([key]) => !reservedAttributes.includes(key)));
139
135
  }
140
136
  preHarvestChecks() {
141
137
  this.checkForFirstInteraction();
@@ -145,15 +141,34 @@ export class Aggregate extends AggregateBase {
145
141
  // serialize array of timing data
146
142
  serializer(eventBuffer) {
147
143
  if (!eventBuffer?.length) return '';
148
- var addString = getAddStringContext(this.obfuscator);
144
+ const {
145
+ addString,
146
+ addStringRaw
147
+ } = createStringAdders(getAddStringContext, this.obfuscator);
148
+ const keepOrigValue = {
149
+ addKey: addStringRaw,
150
+ addVal: addStringRaw
151
+ };
152
+ const obfuscateValue = {
153
+ addKey: addStringRaw,
154
+ addVal: addString
155
+ };
149
156
  var payload = 'bel.6;';
150
157
  for (var i = 0; i < eventBuffer.length; i++) {
151
158
  var timing = eventBuffer[i];
152
159
  payload += 'e,';
153
- payload += addString(timing.name) + ',';
160
+ payload += addStringRaw(timing.name) + ',';
154
161
  payload += nullable(timing.value, numeric, false) + ',';
155
- this.appendGlobalCustomAttributes(timing);
156
- var attrParts = addCustomAttributes(timing.attrs, addString);
162
+ const userAttrs = this.#getGlobalCustomAttributes();
163
+ const {
164
+ pageUrl,
165
+ ...systemReservedAttrs
166
+ } = timing.attrs || {};
167
+ var attrParts = addCustomAttributes(systemReservedAttrs, keepOrigValue);
168
+ attrParts.push(...addCustomAttributes(userAttrs, obfuscateValue));
169
+ if (pageUrl) attrParts.push(...addCustomAttributes({
170
+ pageUrl
171
+ }, obfuscateValue));
157
172
  if (attrParts && attrParts.length > 0) {
158
173
  payload += numeric(attrParts.length) + ';' + attrParts.join(';');
159
174
  }
@@ -41,6 +41,7 @@ export class AjaxNode extends BelNode {
41
41
  serialize(parentStartTimestamp, agentRef, ajaxObfuscator) {
42
42
  const {
43
43
  addString,
44
+ addStringRaw,
44
45
  addStringWithTruncation
45
46
  } = createStringAdders(getAddStringContext, ajaxObfuscator);
46
47
  const nodeList = [];
@@ -56,13 +57,21 @@ export class AjaxNode extends BelNode {
56
57
  // callbackEnd is relative to end
57
58
  numeric(this.callbackDuration),
58
59
  // not relative
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)];
60
+ addStringRaw(this.method), numeric(this.status), addString(this.domain), addString(this.path), numeric(this.txSize), numeric(this.rxSize), this.requestedWith, addStringRaw(this.nodeId), nullable(this.spanId, addStringRaw, true) + nullable(this.traceId, addStringRaw, true) + nullable(this.spanTimestamp, numeric)];
60
61
  // Regular attributes: obfuscate only
61
62
  const regularAttrs = addCustomAttributes({
62
- [AJAX_ID]: this[AJAX_ID],
63
63
  ...(this.targetAttributes || {}),
64
64
  ...(this.gql || {})
65
- }, addString);
65
+ }, {
66
+ addKey: addStringRaw,
67
+ addVal: addString
68
+ });
69
+ regularAttrs.push(...addCustomAttributes({
70
+ [AJAX_ID]: this[AJAX_ID]
71
+ }, {
72
+ addKey: addStringRaw,
73
+ addVal: addStringRaw
74
+ }));
66
75
 
67
76
  // Payload attributes: obfuscate then truncate
68
77
  const payloadAttrs = addCustomAttributes({
@@ -81,7 +90,10 @@ export class AjaxNode extends BelNode {
81
90
  ...(this.responseHeaders ? {
82
91
  responseHeaders: this.responseHeaders
83
92
  } : {})
84
- }, addStringWithTruncation);
93
+ }, {
94
+ addKey: addStringRaw,
95
+ addVal: addStringWithTruncation
96
+ });
85
97
  let allAttachedNodes = [...regularAttrs, ...payloadAttrs];
86
98
  this.children.forEach(node => allAttachedNodes.push(node.serialize())); // no children is expected under ajax nodes at this time
87
99
 
@@ -9,6 +9,7 @@ import { now } from '../../../common/timing/now';
9
9
  import { cleanURL } from '../../../common/url/clean-url';
10
10
  import { NODE_TYPE, INTERACTION_STATUS, INTERACTION_TYPE, API_TRIGGER_NAME, IPL_TRIGGER_NAME, NO_LONG_TASK_WINDOW } from '../constants';
11
11
  import { BelNode } from './bel-node';
12
+ import { createStringAdders } from '../../../common/payloads/payloads';
12
13
 
13
14
  /**
14
15
  * link https://github.com/newrelic/nr-querypack/blob/main/schemas/bel/7.qpschema
@@ -127,7 +128,10 @@ export class Interaction extends BelNode {
127
128
  */
128
129
  serialize(firstStartTimeOfPayload, agentRef, interactionObfuscator, ajaxObfuscator) {
129
130
  const isFirstIxnOfPayload = firstStartTimeOfPayload === undefined;
130
- const addString = getAddStringContext(interactionObfuscator);
131
+ const {
132
+ addString,
133
+ addStringRaw
134
+ } = createStringAdders(getAddStringContext, interactionObfuscator);
131
135
  const nodeList = [];
132
136
  let ixnType;
133
137
  if (this.trigger === IPL_TRIGGER_NAME) ixnType = INTERACTION_TYPE.INITIAL_PAGE_LOAD;else if (this.newURL !== this.oldURL) ixnType = INTERACTION_TYPE.ROUTE_CHANGE;else ixnType = INTERACTION_TYPE.UNSPECIFIED;
@@ -143,12 +147,15 @@ export class Interaction extends BelNode {
143
147
  // callbackEnd -- relative to start; not used by BrowserInteraction events so these are always 0
144
148
  numeric(0),
145
149
  // not relative; always 0 for BrowserInteraction
146
- addString(this.trigger), addString(cleanURL(this.initialPageURL, true)), addString(cleanURL(this.oldURL, true)), addString(cleanURL(this.newURL, true)), addString(this.customName), ixnType, nullable(this.queueTime, numeric, true) + nullable(this.appTime, numeric, true) + nullable(this.oldRoute, addString, true) + nullable(this.newRoute, addString, true) + addString(this.id), addString(this.nodeId), nullable(this.firstPaint, numeric, true) + nullable(this.firstContentfulPaint, numeric)];
150
+ addStringRaw(this.trigger), addString(cleanURL(this.initialPageURL, true)), addString(cleanURL(this.oldURL, true)), addString(cleanURL(this.newURL, true)), addString(this.customName), ixnType, nullable(this.queueTime, numeric, true) + nullable(this.appTime, numeric, true) + nullable(this.oldRoute, addString, true) + nullable(this.newRoute, addString, true) + addStringRaw(this.id), addStringRaw(this.nodeId), nullable(this.firstPaint, numeric, true) + nullable(this.firstContentfulPaint, numeric)];
147
151
  const customAttributes = {
148
152
  ...agentRef.info.jsAttributes,
149
153
  ...this.customAttributes
150
154
  }; // attrs specific to this interaction should have precedence over the general custom attrs
151
- const allAttachedNodes = addCustomAttributes(customAttributes || {}, addString); // start with all custom attributes
155
+ const allAttachedNodes = addCustomAttributes(customAttributes || {}, {
156
+ addKey: addStringRaw,
157
+ addVal: addString
158
+ }); // start with all custom attributes
152
159
  if (agentRef.info.atts) allAttachedNodes.push('a,' + addString(agentRef.info.atts)); // add apm provided attributes
153
160
  /* Querypack encoder+decoder quirkiness:
154
161
  - If first ixn node of payload is being processed, its children's start time must be offset by this node's start. (firstStartTime should be undefined.)
@@ -40,14 +40,15 @@ export function isLikelyHumanReadable(headers: Object, data: any): boolean;
40
40
  */
41
41
  export function truncateAsString(data: any): string;
42
42
  /**
43
- * Creates string adder functions for BEL serialization with obfuscation and optional truncation.
43
+ * Creates string adder functions for BEL serialization with obfuscation and optional truncation, as well as raw string.
44
44
  * This ensures a single string table is used while providing separate handling for regular vs payload attributes.
45
45
  * @param {Function} getAddStringContext - Function that creates a new string table context
46
46
  * @param {Object} obfuscator - Optional obfuscator instance for string obfuscation
47
- * @returns {{addString: Function, addStringWithTruncation: Function}} Object containing both string adder functions
47
+ * @returns {{addString: Function, addStringRaw: Function, addStringWithTruncation: Function}} Object containing various string adder functions
48
48
  */
49
49
  export function createStringAdders(getAddStringContext: Function, obfuscator: Object): {
50
50
  addString: Function;
51
+ addStringRaw: Function;
51
52
  addStringWithTruncation: Function;
52
53
  };
53
54
  //# sourceMappingURL=payloads.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"payloads.d.ts","sourceRoot":"","sources":["../../../../src/common/payloads/payloads.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,+CAVW,MAAM,+EAEd;IAAsB,UAAU,EAAxB,MAAM;IACS,YAAY,EAA3B,OAAO;IACO,WAAW,EAAzB,MAAM;IACQ,eAAe,EAA7B,MAAM;IACS,eAAe;CACtC,YAAQ,MAAM,EAAE,GACN,OAAO,CAqBnB;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
+ {"version":3,"file":"payloads.d.ts","sourceRoot":"","sources":["../../../../src/common/payloads/payloads.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,+CAVW,MAAM,+EAEd;IAAsB,UAAU,EAAxB,MAAM;IACS,YAAY,EAA3B,OAAO;IACO,WAAW,EAAzB,MAAM;IACQ,eAAe,EAA7B,MAAM;IACS,eAAe;CACtC,YAAQ,MAAM,EAAE,GACN,OAAO,CAqBnB;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,YAAY,WAAW;IAAC,uBAAuB,WAAU;CAAC,CAkB5F"}
@@ -1,5 +1,13 @@
1
1
  export function nullable(val: any, fn: any, comma: any): string;
2
2
  export function numeric(n: any, noDefault: any): string;
3
3
  export function getAddStringContext(obfuscator: any, truncator: any): (str: any, obfuscate?: boolean) => string;
4
- export function addCustomAttributes(attrs: any, addString: any): any[];
4
+ /**
5
+ * Attributes are serialized according to their types, using the provided serializers. Only the first 64 attributes will be added to the payload.
6
+ * @param attrs
7
+ * @param serializers
8
+ * @param {function} serializers.addKey -function for serializing attribute keys
9
+ * @param {function} serializers.addVal - function for serializing attribute values
10
+ * @returns {*[]}
11
+ */
12
+ export function addCustomAttributes(attrs: any, serializers: any): any[];
5
13
  //# 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,gHAiBC;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,gHAiBC;AAED;;;;;;;GAOG;AACH,mEAFa,GAAC,EAAE,CA2Cf"}
@@ -1 +1 @@
1
- {"version":3,"file":"mfe-vitals.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/mfe-vitals.js"],"names":[],"mappings":"AAqHA;;;;;GAKG;AACH,uCAJW,iBAAiB,WACjB,kBAAkB,GAChB,iBAAiB,CAuM7B;iCAtTY,OAAO,sCAAsC,EAAE,kBAAkB;gCACjE,OAAO,sCAAsC,EAAE,iBAAiB;gCAChE,OAAO,sCAAsC,EAAE,iBAAiB"}
1
+ {"version":3,"file":"mfe-vitals.d.ts","sourceRoot":"","sources":["../../../../src/common/v2/mfe-vitals.js"],"names":[],"mappings":"AAqHA;;;;;GAKG;AACH,uCAJW,iBAAiB,WACjB,kBAAkB,GAChB,iBAAiB,CAkO7B;iCAjVY,OAAO,sCAAsC,EAAE,kBAAkB;gCACjE,OAAO,sCAAsC,EAAE,iBAAiB;gCAChE,OAAO,sCAAsC,EAAE,iBAAiB"}
@@ -1 +1 @@
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,0GA2FC;IAED,+CAOC;IAED,iDAoEC;CACF;8BAvN6B,4BAA4B;2BAK/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,0GA2FC;IAED,+CAOC;IAED,iDAuEC;CACF;8BA1N6B,4BAA4B;2BAK/B,gCAAgC"}
@@ -19,7 +19,6 @@ export class Aggregate extends AggregateBase {
19
19
  * @returns {void}
20
20
  */
21
21
  checkForFirstInteraction(): void;
22
- appendGlobalCustomAttributes(timing: any): void;
23
22
  preHarvestChecks(): boolean;
24
23
  serializer(eventBuffer: any): string;
25
24
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AA0BA;IACE,2BAAiC;IAMjC,2BA+BC;IA7BC,4BAA+B;IAC/B,0BAA6B;IAK7B,uBAA2D;IAyB7D;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;;;MA+BC;IAED;;;OAGG;IACH,4BAFa,IAAI,CAahB;IAED,gDAWC;IAED,4BAGC;IAGD,qCAwBC;;CACF;8BAjK6B,4BAA4B;2BAC/B,gCAAgC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_timing/aggregate/index.js"],"names":[],"mappings":"AA2BA;IACE,2BAAiC;IAMjC,2BA+BC;IA7BC,4BAA+B;IAC/B,0BAA6B;IAK7B,uBAA2D;IAyB7D;;;OAGG;IACH,6BAFW,MAAM,QAOhB;IAED;;;;MAiCC;IAED;;;OAGG;IACH,4BAFa,IAAI,CAahB;IAWD,4BAGC;IAGD,qCA8BC;;CACF;8BAtK6B,4BAA4B;2BAC/B,gCAAgC"}
@@ -1 +1 @@
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"}
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,iFAkDC;IAnEC,sBAAkC;CAoErC;wBAtFuB,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AAYA;;IAEI;AACJ;IAoBE,0FAaC;IAhCD,WAAmB;IACnB,uBAAgC;IAChC,gBAAU;IACV,qBAAqB;IACrB,oBAAoB;IACpB,eAAS;IACT,aAAO;IACP,cAAQ;IACR,+EAA+E;IAC/E,eAA8B;IAC9B,qBAAgB;IAChB,yBAAoB;IACpB,sBAAoB;IACpB,6BAA2B;IAC3B,cAAW;IACX,kBAAa;IACb,uBAAiB;IACjB,wBAAkB;IAIhB,gBAAoC;IACpC,aAAsB;IAEtB,cAAiC;IACjC,wCAGE;IACF,mBAAyC;IAAxB,qBAAwB;IAEzC,YAAsE;IAAxD,YAAwD;IAGxE,iDAKC;IAED,gCAGC;IAED,oCAUC;IAED,8BAIC;IAED,6DAeC;IAmBD;;;;;;OAMG;IACH,0BAHW,mBAAmB,WAM7B;IAGD,uBAAoB;IACpB,iCAA8B;IAC9B,sBAAmB;IAEnB;;;;;;;OAOG;IACH,qDALW,KAAK,yBACL,UAAU,kBACV,UAAU,UAgDpB;;CACF;wBA/KuB,YAAY"}
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../../src/features/soft_navigations/aggregate/interaction.js"],"names":[],"mappings":"AAaA;;IAEI;AACJ;IAoBE,0FAaC;IAhCD,WAAmB;IACnB,uBAAgC;IAChC,gBAAU;IACV,qBAAqB;IACrB,oBAAoB;IACpB,eAAS;IACT,aAAO;IACP,cAAQ;IACR,+EAA+E;IAC/E,eAA8B;IAC9B,qBAAgB;IAChB,yBAAoB;IACpB,sBAAoB;IACpB,6BAA2B;IAC3B,cAAW;IACX,kBAAa;IACb,uBAAiB;IACjB,wBAAkB;IAIhB,gBAAoC;IACpC,aAAsB;IAEtB,cAAiC;IACjC,wCAGE;IACF,mBAAyC;IAAxB,qBAAwB;IAEzC,YAAsE;IAAxD,YAAwD;IAGxE,iDAKC;IAED,gCAGC;IAED,oCAUC;IAED,8BAIC;IAED,6DAeC;IAmBD;;;;;;OAMG;IACH,0BAHW,mBAAmB,WAM7B;IAGD,uBAAoB;IACpB,iCAA8B;IAC9B,sBAAmB;IAEnB;;;;;;;OAOG;IACH,qDALW,KAAK,yBACL,UAAU,kBACV,UAAU,UAgDpB;;CACF;wBAhLuB,YAAY"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.320.0",
3
+ "version": "1.320.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -129,11 +129,11 @@ export function truncateAsString (data) {
129
129
  }
130
130
 
131
131
  /**
132
- * Creates string adder functions for BEL serialization with obfuscation and optional truncation.
132
+ * Creates string adder functions for BEL serialization with obfuscation and optional truncation, as well as raw string.
133
133
  * This ensures a single string table is used while providing separate handling for regular vs payload attributes.
134
134
  * @param {Function} getAddStringContext - Function that creates a new string table context
135
135
  * @param {Object} obfuscator - Optional obfuscator instance for string obfuscation
136
- * @returns {{addString: Function, addStringWithTruncation: Function}} Object containing both string adder functions
136
+ * @returns {{addString: Function, addStringRaw: Function, addStringWithTruncation: Function}} Object containing various string adder functions
137
137
  */
138
138
  export function createStringAdders (getAddStringContext, obfuscator) {
139
139
  const addStringRaw = getAddStringContext()
@@ -148,6 +148,7 @@ export function createStringAdders (getAddStringContext, obfuscator) {
148
148
 
149
149
  return {
150
150
  addString: (str) => processString(str, false),
151
+ addStringRaw: (str) => addStringRaw(str),
151
152
  addStringWithTruncation: (str) => processString(str, true)
152
153
  }
153
154
  }
@@ -40,7 +40,16 @@ export function getAddStringContext (obfuscator, truncator) {
40
40
  }
41
41
  }
42
42
 
43
- export function addCustomAttributes (attrs, addString) {
43
+ /**
44
+ * Attributes are serialized according to their types, using the provided serializers. Only the first 64 attributes will be added to the payload.
45
+ * @param attrs
46
+ * @param serializers
47
+ * @param {function} serializers.addKey -function for serializing attribute keys
48
+ * @param {function} serializers.addVal - function for serializing attribute values
49
+ * @returns {*[]}
50
+ */
51
+ export function addCustomAttributes (attrs, serializers) {
52
+ const { addKey, addVal } = serializers
44
53
  var attrParts = []
45
54
 
46
55
  Object.entries(attrs || {}).forEach(([key, val]) => {
@@ -48,13 +57,13 @@ export function addCustomAttributes (attrs, addString) {
48
57
  var type = 5
49
58
  var serializedValue
50
59
  // add key to string table first
51
- key = addString(key, false)
60
+ key = addKey(key)
52
61
 
53
62
  switch (typeof val) {
54
63
  case 'object':
55
64
  if (val) {
56
65
  // serialize objects to strings
57
- serializedValue = addString(stringify(val))
66
+ serializedValue = addVal(stringify(val))
58
67
  } else {
59
68
  // null attribute type
60
69
  type = 9
@@ -73,7 +82,7 @@ export function addCustomAttributes (attrs, addString) {
73
82
  type = 9
74
83
  break
75
84
  default:
76
- serializedValue = addString(val)
85
+ serializedValue = addVal(val)
77
86
  }
78
87
 
79
88
  attrParts.push([type, key + (serializedValue ? ',' + serializedValue : '')])
@@ -124,6 +124,9 @@ const observePerformance = (observers, config, onEntry) => {
124
124
  export function trackMFEVitals (target, timings) {
125
125
  let fcpObservedAt
126
126
  let lcpObservedAt
127
+ // Once disconnect() runs, no further vitals data should be written - even from an observer callback that was
128
+ // already in flight when disconnect() was called.
129
+ let disconnected = false
127
130
 
128
131
  const getTimeRelativeToScriptStart = (capturedAt) => {
129
132
  if (capturedAt == null) return capturedAt
@@ -132,12 +135,21 @@ export function trackMFEVitals (target, timings) {
132
135
 
133
136
  const vitals = {
134
137
  fcp: {
135
- get value () { return getTimeRelativeToScriptStart(fcpObservedAt) },
136
- set value (v) { fcpObservedAt = v }
138
+ // A clock freeze/tab suspend between script registration and first paint can make this value huge once the
139
+ // tab resumes. Rather than report a multi-hour "FCP", drop it so bad data doesn't reach the backend.
140
+ get value () {
141
+ const relative = getTimeRelativeToScriptStart(fcpObservedAt)
142
+ return relative > CORRELATION_STALE_THRESHOLD_MS ? undefined : relative
143
+ },
144
+ set value (v) { if (!disconnected) fcpObservedAt = v }
137
145
  },
138
146
  lcp: {
139
- get value () { return getTimeRelativeToScriptStart(lcpObservedAt) },
140
- set value (v) { lcpObservedAt = v }
147
+ // LCP can never occur before FCP, so if FCP was dropped as invalid, LCP is derived from the same untrustworthy clock and must be dropped too.
148
+ get value () {
149
+ if (vitals.fcp.value === undefined) return undefined
150
+ return getTimeRelativeToScriptStart(lcpObservedAt)
151
+ },
152
+ set value (v) { if (!disconnected) lcpObservedAt = v }
141
153
  },
142
154
  cls: {
143
155
  value: undefined
@@ -159,15 +171,22 @@ export function trackMFEVitals (target, timings) {
159
171
  }, CORRELATION_STALE_THRESHOLD_MS)
160
172
 
161
173
  const populateVitalMinimums = () => {
162
- fcpObservedAt ??= now()
174
+ if (disconnected) return
175
+ if (fcpObservedAt == null) {
176
+ const capturedAt = now()
177
+ // If the very first observed render already exceeds the window, the clock is untrustworthy for this
178
+ // MFE (e.g. tab was frozen/suspended between registration and paint) - shut everything down instead
179
+ // of seeding vitals with data derived from it.
180
+ if (getTimeRelativeToScriptStart(capturedAt) > CORRELATION_STALE_THRESHOLD_MS) {
181
+ vitals.disconnect()
182
+ return
183
+ }
184
+ fcpObservedAt = capturedAt
185
+ }
163
186
  lcpObservedAt ??= now()
164
187
  vitals.cls.value ??= 0
165
188
  }
166
189
 
167
- // if the MFE has already rendered something on the page before we could set up listeners, just populate vital minimums immediately
168
- const existingRoots = globalScope.document?.querySelectorAll(`[data-nr-mfe-id="${escapeSelectorValue(target.id)}"]`)
169
- if (Array.from(existingRoots || []).some(x => isMatch(x.dataset, target))) populateVitalMinimums()
170
-
171
190
  // Track FCP - first contentful paint
172
191
  const fcpObs = observeMutations(target, (_, obs) => {
173
192
  // An observed "FCP" means _something_ rendered, so at minimum we can populate all the baseline values for the vitals
@@ -183,12 +202,13 @@ export function trackMFEVitals (target, timings) {
183
202
 
184
203
  try {
185
204
  resizeObs = new globalScope.ResizeObserver((entries) => {
205
+ if (disconnected) return
186
206
  entries.forEach((entry) => {
187
207
  try {
188
208
  const size = entry.contentRect.width * entry.contentRect.height
189
209
  if (size > largestSize) {
190
210
  largestSize = size
191
- lcpObservedAt = now()
211
+ vitals.lcp.value = now()
192
212
  }
193
213
  resizeObs.unobserve(entry.target)
194
214
  } catch (e) {
@@ -245,7 +265,7 @@ export function trackMFEVitals (target, timings) {
245
265
  // Track CLS - cumulative layout shift
246
266
  // Initialize CLS to 0 if browser supports it
247
267
  observePerformance(observers, { type: 'layout-shift', buffered: true }, (entry) => {
248
- if (entry.hadRecentInput) return
268
+ if (disconnected || entry.hadRecentInput) return
249
269
  ;(entry.sources || []).some(source => {
250
270
  if (isInMFE(source.node, target)) {
251
271
  // an observed "CLS" means _something_ rendered for the MFE, so at minimum we can make sure all the baseline values are populated for the vitals
@@ -259,7 +279,7 @@ export function trackMFEVitals (target, timings) {
259
279
 
260
280
  // Track INP - interaction to next paint
261
281
  observePerformance(observers, { type: 'event', buffered: true, durationThreshold: 40 }, (entry) => {
262
- if (!entry.interactionId || !isInMFE(entry.target, target)) return
282
+ if (disconnected || !entry.interactionId || !isInMFE(entry.target, target)) return
263
283
  if (vitals.inp.value === undefined || entry.duration > vitals.inp.value) {
264
284
  // an observed "INP" means _something_ rendered for the MFE, so at minimum we can make sure all the baseline values are populated for the vitals
265
285
  populateVitalMinimums()
@@ -276,6 +296,7 @@ export function trackMFEVitals (target, timings) {
276
296
 
277
297
  // Disconnect all observers
278
298
  vitals.disconnect = () => {
299
+ disconnected = true
279
300
  // Disconnect all observers
280
301
  observers.forEach(obs => {
281
302
  try {
@@ -317,5 +338,11 @@ export function trackMFEVitals (target, timings) {
317
338
  globalScope.addEventListener(type, vitals.disconnect, { once: true, passive: true })
318
339
  })
319
340
 
341
+ // If the MFE has already rendered something on the page before we could set up listeners, just populate vital
342
+ // minimums immediately. Done last, once `observers` is fully populated and `vitals.disconnect` has its real
343
+ // implementation, so a stale-clock detection here can actually tear everything down instead of no-op'ing.
344
+ const existingRoots = globalScope.document?.querySelectorAll(`[data-nr-mfe-id="${escapeSelectorValue(target.id)}"]`)
345
+ if (Array.from(existingRoots || []).some(x => isMatch(x.dataset, target))) populateVitalMinimums()
346
+
320
347
  return vitals
321
348
  }
@@ -157,7 +157,7 @@ export class Aggregate extends AggregateBase {
157
157
  serializer (eventBuffer) {
158
158
  if (!eventBuffer.length) return
159
159
 
160
- const { addString, addStringWithTruncation } = createStringAdders(getAddStringContext, this.obfuscator)
160
+ const { addString, addStringRaw, addStringWithTruncation } = createStringAdders(getAddStringContext, this.obfuscator)
161
161
 
162
162
  let payload = 'bel.7;'
163
163
 
@@ -174,16 +174,16 @@ export class Aggregate extends AggregateBase {
174
174
  numeric(event.endTime - event.startTime),
175
175
  numeric(0), // callbackEnd
176
176
  numeric(0), // no callbackDuration for non-SPA events
177
- addString(event.method),
177
+ addStringRaw(event.method),
178
178
  numeric(event.status),
179
179
  addString(event.domain),
180
180
  addString(event.path),
181
181
  numeric(event.requestSize),
182
182
  numeric(event.responseSize),
183
183
  event.type === 'fetch' ? 1 : '',
184
- addString(0), // nodeId
185
- nullable(event.spanId, addString, true) + // guid
186
- nullable(event.traceId, addString, true) + // traceId
184
+ addStringRaw(0), // nodeId
185
+ nullable(event.spanId, addStringRaw, true) + // guid
186
+ nullable(event.traceId, addStringRaw, true) + // traceId
187
187
  nullable(event.spanTimestamp, numeric, false) // timestamp
188
188
  ]
189
189
 
@@ -193,12 +193,15 @@ export class Aggregate extends AggregateBase {
193
193
  const jsAttributes = this.agentRef.info.jsAttributes
194
194
 
195
195
  // Regular attributes: obfuscate only
196
- const regularAttrs = addCustomAttributes({
196
+ let regularAttrs = addCustomAttributes({
197
197
  ...(jsAttributes || {}),
198
- [AJAX_ID]: event[AJAX_ID], // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
199
198
  ...(event.targetAttributes || {}), // used to supply the version 2 attributes, either MFE target or duplication attributes for the main agent app
200
199
  ...(event.gql || {})
201
- }, addString)
200
+ }, { addKey: addStringRaw, addVal: addString })
201
+
202
+ regularAttrs.push(...addCustomAttributes({
203
+ [AJAX_ID]: event[AJAX_ID] // all AjaxRequest events should have a unique identifier to allow for easier grouping and analysis in the UI
204
+ }, { addKey: addStringRaw, addVal: addStringRaw }))
202
205
 
203
206
  // Payload attributes: obfuscate then truncate
204
207
  const payloadAttrs = addCustomAttributes({
@@ -207,7 +210,7 @@ export class Aggregate extends AggregateBase {
207
210
  ...(event.requestQuery ? { requestQuery: event.requestQuery } : {}),
208
211
  ...(event.responseBody ? { responseBody: event.responseBody } : {}),
209
212
  ...(event.responseHeaders ? { responseHeaders: event.responseHeaders } : {})
210
- }, addStringWithTruncation)
213
+ }, { addKey: addStringRaw, addVal: addStringWithTruncation })
211
214
 
212
215
  const attrParts = [...regularAttrs, ...payloadAttrs]
213
216
  fields.unshift(numeric(attrParts.length))
@@ -23,6 +23,7 @@ import { loadTime } from '../../../common/vitals/load-time'
23
23
  import { webdriverDetected } from '../../../common/util/webdriver-detection'
24
24
  import { cleanURL } from '../../../common/url/clean-url'
25
25
  import { EVENT_TYPES } from '../../../common/constants/events'
26
+ import { createStringAdders } from '../../../common/payloads/payloads'
26
27
 
27
28
  export class Aggregate extends AggregateBase {
28
29
  static featureName = FEATURE_NAME
@@ -93,6 +94,8 @@ export class Aggregate extends AggregateBase {
93
94
  attrs.cls = cumulativeLayoutShift.current.value
94
95
  }
95
96
 
97
+ attrs.webdriverDetected = webdriverDetected
98
+
96
99
  const timing = {
97
100
  name,
98
101
  value,
@@ -125,17 +128,13 @@ export class Aggregate extends AggregateBase {
125
128
  })
126
129
  }
127
130
 
128
- appendGlobalCustomAttributes (timing) {
129
- var timingAttributes = timing.attrs || {}
131
+ #getGlobalCustomAttributes () {
132
+ const reservedAttributes = ['size', 'eid', 'cls', 'type', 'fid', 'elTag', 'elUrl', 'net-type',
133
+ 'net-etype', 'net-rtt', 'net-dlink', 'webdriverDetected']
130
134
 
131
- var reservedAttributes = ['size', 'eid', 'cls', 'type', 'fid', 'elTag', 'elUrl', 'net-type',
132
- 'net-etype', 'net-rtt', 'net-dlink']
133
- Object.entries(this.agentRef.info.jsAttributes || {}).forEach(([key, val]) => {
134
- if (reservedAttributes.indexOf(key) < 0) {
135
- timingAttributes[key] = val
136
- }
137
- })
138
- timingAttributes.webdriverDetected = webdriverDetected
135
+ return Object.fromEntries(
136
+ Object.entries(this.agentRef.info.jsAttributes || {}).filter(([key]) => !reservedAttributes.includes(key))
137
+ )
139
138
  }
140
139
 
141
140
  preHarvestChecks () {
@@ -146,7 +145,9 @@ export class Aggregate extends AggregateBase {
146
145
  // serialize array of timing data
147
146
  serializer (eventBuffer) {
148
147
  if (!eventBuffer?.length) return ''
149
- var addString = getAddStringContext(this.obfuscator)
148
+ const { addString, addStringRaw } = createStringAdders(getAddStringContext, this.obfuscator)
149
+ const keepOrigValue = { addKey: addStringRaw, addVal: addStringRaw }
150
+ const obfuscateValue = { addKey: addStringRaw, addVal: addString }
150
151
 
151
152
  var payload = 'bel.6;'
152
153
 
@@ -154,12 +155,16 @@ export class Aggregate extends AggregateBase {
154
155
  var timing = eventBuffer[i]
155
156
 
156
157
  payload += 'e,'
157
- payload += addString(timing.name) + ','
158
+ payload += addStringRaw(timing.name) + ','
158
159
  payload += nullable(timing.value, numeric, false) + ','
159
160
 
160
- this.appendGlobalCustomAttributes(timing)
161
+ const userAttrs = this.#getGlobalCustomAttributes()
162
+
163
+ const { pageUrl, ...systemReservedAttrs } = timing.attrs || {}
164
+ var attrParts = addCustomAttributes(systemReservedAttrs, keepOrigValue)
165
+ attrParts.push(...addCustomAttributes(userAttrs, obfuscateValue))
166
+ if (pageUrl) attrParts.push(...addCustomAttributes({ pageUrl }, obfuscateValue))
161
167
 
162
- var attrParts = addCustomAttributes(timing.attrs, addString)
163
168
  if (attrParts && attrParts.length > 0) {
164
169
  payload += numeric(attrParts.length) + ';' + attrParts.join(';')
165
170
  }