@newrelic/browser-agent 1.254.0 → 1.255.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 (147) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/common/config/state/runtime.js +2 -1
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/context/shared-context.js +1 -1
  6. package/dist/cjs/common/harvest/harvest.js +1 -1
  7. package/dist/cjs/common/timing/time-keeper.js +28 -28
  8. package/dist/cjs/common/vitals/cumulative-layout-shift.js +12 -5
  9. package/dist/cjs/common/vitals/first-contentful-paint.js +10 -6
  10. package/dist/cjs/common/vitals/first-input-delay.js +12 -10
  11. package/dist/cjs/common/vitals/first-paint.js +1 -2
  12. package/dist/cjs/common/vitals/interaction-to-next-paint.js +11 -7
  13. package/dist/cjs/common/vitals/largest-contentful-paint.js +19 -17
  14. package/dist/cjs/common/vitals/long-task.js +0 -1
  15. package/dist/cjs/common/vitals/time-to-first-byte.js +11 -6
  16. package/dist/cjs/common/vitals/vital-metric.js +1 -4
  17. package/dist/cjs/common/window/nreum.js +1 -1
  18. package/dist/cjs/features/ajax/aggregate/index.js +3 -2
  19. package/dist/cjs/features/ajax/instrument/index.js +1 -1
  20. package/dist/cjs/features/jserrors/aggregate/index.js +3 -2
  21. package/dist/cjs/features/jserrors/instrument/index.js +1 -1
  22. package/dist/cjs/features/page_action/aggregate/index.js +3 -2
  23. package/dist/cjs/features/page_view_event/aggregate/index.js +11 -5
  24. package/dist/cjs/features/page_view_timing/aggregate/index.js +21 -7
  25. package/dist/cjs/features/page_view_timing/instrument/index.js +1 -1
  26. package/dist/cjs/features/session_replay/aggregate/index.js +14 -9
  27. package/dist/cjs/features/session_replay/shared/recorder-events.js +9 -1
  28. package/dist/cjs/features/session_replay/shared/recorder.js +56 -12
  29. package/dist/cjs/features/session_trace/aggregate/index.js +1 -1
  30. package/dist/cjs/features/session_trace/instrument/index.js +1 -1
  31. package/dist/cjs/features/soft_navigations/aggregate/index.js +2 -2
  32. package/dist/cjs/features/soft_navigations/instrument/index.js +1 -1
  33. package/dist/cjs/features/spa/aggregate/index.js +19 -10
  34. package/dist/cjs/features/spa/instrument/index.js +1 -1
  35. package/dist/cjs/features/utils/feature-base.js +0 -2
  36. package/dist/cjs/loaders/agent-base.js +0 -2
  37. package/dist/cjs/loaders/agent.js +1 -1
  38. package/dist/cjs/loaders/api/api.js +1 -1
  39. package/dist/cjs/loaders/micro-agent.js +4 -7
  40. package/dist/esm/common/config/state/runtime.js +2 -1
  41. package/dist/esm/common/constants/env.cdn.js +1 -1
  42. package/dist/esm/common/constants/env.npm.js +1 -1
  43. package/dist/esm/common/context/shared-context.js +1 -1
  44. package/dist/esm/common/harvest/harvest.js +1 -1
  45. package/dist/esm/common/timing/time-keeper.js +28 -28
  46. package/dist/esm/common/vitals/cumulative-layout-shift.js +11 -4
  47. package/dist/esm/common/vitals/first-contentful-paint.js +9 -5
  48. package/dist/esm/common/vitals/first-input-delay.js +11 -9
  49. package/dist/esm/common/vitals/first-paint.js +1 -2
  50. package/dist/esm/common/vitals/interaction-to-next-paint.js +10 -6
  51. package/dist/esm/common/vitals/largest-contentful-paint.js +18 -16
  52. package/dist/esm/common/vitals/long-task.js +0 -1
  53. package/dist/esm/common/vitals/time-to-first-byte.js +10 -5
  54. package/dist/esm/common/vitals/vital-metric.js +1 -4
  55. package/dist/esm/common/window/nreum.js +1 -1
  56. package/dist/esm/features/ajax/aggregate/index.js +3 -2
  57. package/dist/esm/features/ajax/instrument/index.js +1 -1
  58. package/dist/esm/features/jserrors/aggregate/index.js +3 -2
  59. package/dist/esm/features/jserrors/instrument/index.js +1 -1
  60. package/dist/esm/features/page_action/aggregate/index.js +3 -2
  61. package/dist/esm/features/page_view_event/aggregate/index.js +11 -5
  62. package/dist/esm/features/page_view_timing/aggregate/index.js +21 -7
  63. package/dist/esm/features/page_view_timing/instrument/index.js +1 -1
  64. package/dist/esm/features/session_replay/aggregate/index.js +14 -9
  65. package/dist/esm/features/session_replay/shared/recorder-events.js +9 -1
  66. package/dist/esm/features/session_replay/shared/recorder.js +56 -12
  67. package/dist/esm/features/session_trace/aggregate/index.js +1 -1
  68. package/dist/esm/features/session_trace/instrument/index.js +1 -1
  69. package/dist/esm/features/soft_navigations/aggregate/index.js +2 -2
  70. package/dist/esm/features/soft_navigations/instrument/index.js +1 -1
  71. package/dist/esm/features/spa/aggregate/index.js +19 -10
  72. package/dist/esm/features/spa/instrument/index.js +1 -1
  73. package/dist/esm/features/utils/feature-base.js +0 -2
  74. package/dist/esm/loaders/agent-base.js +0 -2
  75. package/dist/esm/loaders/agent.js +1 -1
  76. package/dist/esm/loaders/api/api.js +1 -1
  77. package/dist/esm/loaders/micro-agent.js +4 -7
  78. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  79. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  80. package/dist/types/common/timing/time-keeper.d.ts +7 -6
  81. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  82. package/dist/types/common/vitals/vital-metric.d.ts +1 -2
  83. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -1
  84. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  85. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  86. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  87. package/dist/types/features/jserrors/instrument/index.d.ts.map +1 -1
  88. package/dist/types/features/page_action/aggregate/index.d.ts.map +1 -1
  89. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  90. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  91. package/dist/types/features/page_view_timing/instrument/index.d.ts.map +1 -1
  92. package/dist/types/features/session_replay/aggregate/index.d.ts +4 -0
  93. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  94. package/dist/types/features/session_replay/shared/recorder-events.d.ts +8 -0
  95. package/dist/types/features/session_replay/shared/recorder-events.d.ts.map +1 -1
  96. package/dist/types/features/session_replay/shared/recorder.d.ts +17 -1
  97. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  98. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  99. package/dist/types/features/session_trace/instrument/index.d.ts.map +1 -1
  100. package/dist/types/features/soft_navigations/instrument/index.d.ts.map +1 -1
  101. package/dist/types/features/spa/aggregate/index.d.ts +0 -2
  102. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  103. package/dist/types/features/utils/feature-base.d.ts +0 -1
  104. package/dist/types/features/utils/feature-base.d.ts.map +1 -1
  105. package/dist/types/loaders/agent-base.d.ts +0 -2
  106. package/dist/types/loaders/agent-base.d.ts.map +1 -1
  107. package/dist/types/loaders/micro-agent.d.ts.map +1 -1
  108. package/package.json +1 -1
  109. package/src/common/config/state/runtime.js +2 -1
  110. package/src/common/context/__mocks__/shared-context.js +3 -0
  111. package/src/common/context/shared-context.js +1 -1
  112. package/src/common/harvest/harvest.js +1 -1
  113. package/src/common/timing/__mocks__/time-keeper.js +2 -0
  114. package/src/common/timing/time-keeper.js +30 -31
  115. package/src/common/vitals/cumulative-layout-shift.js +10 -4
  116. package/src/common/vitals/first-contentful-paint.js +9 -4
  117. package/src/common/vitals/first-input-delay.js +11 -6
  118. package/src/common/vitals/first-paint.js +1 -1
  119. package/src/common/vitals/interaction-to-next-paint.js +10 -3
  120. package/src/common/vitals/largest-contentful-paint.js +19 -15
  121. package/src/common/vitals/long-task.js +0 -1
  122. package/src/common/vitals/time-to-first-byte.js +5 -4
  123. package/src/common/vitals/vital-metric.js +2 -4
  124. package/src/common/window/nreum.js +1 -1
  125. package/src/features/ajax/aggregate/index.js +3 -2
  126. package/src/features/ajax/instrument/index.js +1 -1
  127. package/src/features/jserrors/aggregate/index.js +3 -2
  128. package/src/features/jserrors/instrument/index.js +1 -1
  129. package/src/features/page_action/aggregate/index.js +3 -2
  130. package/src/features/page_view_event/aggregate/index.js +13 -4
  131. package/src/features/page_view_timing/aggregate/index.js +16 -6
  132. package/src/features/page_view_timing/instrument/index.js +1 -1
  133. package/src/features/session_replay/aggregate/index.js +13 -9
  134. package/src/features/session_replay/shared/recorder-events.js +6 -1
  135. package/src/features/session_replay/shared/recorder.js +33 -9
  136. package/src/features/session_trace/aggregate/index.js +1 -1
  137. package/src/features/session_trace/instrument/index.js +1 -1
  138. package/src/features/soft_navigations/aggregate/index.js +2 -2
  139. package/src/features/soft_navigations/instrument/index.js +1 -1
  140. package/src/features/spa/aggregate/index.js +19 -8
  141. package/src/features/spa/instrument/index.js +1 -1
  142. package/src/features/utils/feature-base.js +0 -3
  143. package/src/loaders/agent-base.js +0 -2
  144. package/src/loaders/agent.js +1 -1
  145. package/src/loaders/api/api.js +1 -1
  146. package/src/loaders/micro-agent.js +4 -6
  147. package/src/common/vitals/__mocks__/web-vitals.js +0 -19
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.254.0";
9
+ export const VERSION = "1.255.0";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -14,7 +14,7 @@ export class SharedContext {
14
14
  if (Object.keys(model).includes(key)) this.sharedContext[key] = value;
15
15
  });
16
16
  } catch (err) {
17
- warn('An error occured while setting SharedContext', err);
17
+ warn('An error occurred while setting SharedContext', err);
18
18
  }
19
19
  }
20
20
  }
@@ -9,7 +9,6 @@ import * as submitData from '../util/submit-data';
9
9
  import { getLocation } from '../url/location';
10
10
  import { getInfo, getConfigurationValue, getRuntime, getConfiguration } from '../config/config';
11
11
  import { cleanURL } from '../url/clean-url';
12
- import { now } from '../timing/now';
13
12
  import { eventListenerOpts } from '../event-listener/event-listener-opts';
14
13
  import { Obfuscator } from '../util/obfuscate';
15
14
  import { applyFnToProps } from '../util/traverse';
@@ -17,6 +16,7 @@ import { SharedContext } from '../context/shared-context';
17
16
  import { VERSION } from "../constants/env.npm";
18
17
  import { isWorkerScope, isIE } from '../constants/runtime';
19
18
  import { warn } from '../util/console';
19
+ import { now } from '../timing/now';
20
20
  const warnings = {};
21
21
 
22
22
  /**
@@ -1,6 +1,4 @@
1
- import { gosNREUM } from '../window/nreum';
2
1
  import { globalScope } from '../constants/runtime';
3
- import { getRuntime } from '../config/config';
4
2
 
5
3
  /**
6
4
  * Class used to adjust the timestamp of harvested data to New Relic server time. This
@@ -8,7 +6,11 @@ import { getRuntime } from '../config/config';
8
6
  * to the harvested data event offset time.
9
7
  */
10
8
  export class TimeKeeper {
11
- #agent;
9
+ /**
10
+ * Represents the browser origin time.
11
+ * @type {number}
12
+ */
13
+ #originTime;
12
14
 
13
15
  /**
14
16
  * Represents the browser origin time corrected to NR server time.
@@ -22,56 +24,54 @@ export class TimeKeeper {
22
24
  * @type {number}
23
25
  */
24
26
  #localTimeDiff;
25
- constructor(agent) {
26
- this.#agent = agent;
27
+
28
+ /**
29
+ * Represents whether the timekeeper is in a state that it can accurately convert
30
+ * timestamps.
31
+ * @type {number}
32
+ */
33
+ #ready = false;
34
+ constructor() {
35
+ this.#originTime = globalScope.performance.timeOrigin || globalScope.performance.timing.navigationStart;
36
+ }
37
+ get ready() {
38
+ return this.#ready;
27
39
  }
28
- static getTimeKeeperByAgentIdentifier(agentIdentifier) {
29
- const nr = gosNREUM();
30
- return Object.keys(nr?.initializedAgents || {}).indexOf(agentIdentifier) > -1 ? nr.initializedAgents[agentIdentifier].timeKeeper : undefined;
40
+ get originTime() {
41
+ return this.#originTime;
31
42
  }
32
- get correctedPageOriginTime() {
43
+ get correctedOriginTime() {
33
44
  return this.#correctedOriginTime;
34
45
  }
35
46
 
36
47
  /**
37
48
  * Process a rum request to calculate NR server time.
38
49
  * @param rumRequest {XMLHttpRequest} The xhr for the rum request
39
- * @param rumRequestUrl {string} The full url of the rum request
50
+ * @param startTime {number} The start time of the RUM request
51
+ * @param endTime {number} The end time of the RUM request
40
52
  */
41
- processRumRequest(rumRequest, rumRequestUrl) {
53
+ processRumRequest(rumRequest, startTime, endTime) {
42
54
  const responseDateHeader = rumRequest.getResponseHeader('Date');
43
55
  if (!responseDateHeader) {
44
56
  throw new Error('Missing date header on rum response.');
45
57
  }
46
- const resourceEntries = globalScope.performance.getEntriesByName(rumRequestUrl, 'resource');
47
- if (!Array.isArray(resourceEntries) || resourceEntries.length === 0) {
48
- throw new Error('Missing rum request performance entry.');
49
- }
50
- let medianRumOffset = 0;
51
- let serverOffset = 0;
52
- if (typeof resourceEntries[0].responseStart === 'number' && resourceEntries[0].responseStart !== 0) {
53
- // Cors is enabled and we can make a more accurate calculation of NR server time
54
- medianRumOffset = (resourceEntries[0].responseStart - resourceEntries[0].requestStart) / 2;
55
- serverOffset = Math.floor(resourceEntries[0].requestStart + medianRumOffset);
56
- } else {
57
- // Cors is disabled or erred, we need to use a less accurate calculation
58
- medianRumOffset = (resourceEntries[0].responseEnd - resourceEntries[0].fetchStart) / 2;
59
- serverOffset = Math.floor(resourceEntries[0].fetchStart + medianRumOffset);
60
- }
58
+ const medianRumOffset = (endTime - startTime) / 2;
59
+ const serverOffset = Math.floor(startTime + medianRumOffset);
61
60
 
62
61
  // Corrected page origin time
63
62
  this.#correctedOriginTime = Math.floor(Date.parse(responseDateHeader) - serverOffset);
64
- this.#localTimeDiff = getRuntime(this.#agent.agentIdentifier).offset - this.#correctedOriginTime;
63
+ this.#localTimeDiff = this.#originTime - this.#correctedOriginTime;
65
64
  if (Number.isNaN(this.#correctedOriginTime)) {
66
65
  throw new Error('Date header invalid format.');
67
66
  }
67
+ this.#ready = true;
68
68
  }
69
69
 
70
70
  /**
71
71
  * Converts a page origin relative time to an absolute timestamp
72
72
  * corrected to NR server time.
73
73
  * @param relativeTime {number} The relative time of the event in milliseconds
74
- * @returns {number} The correct timestamp as a unix/epoch timestamp value
74
+ * @returns {number} Corrected unix/epoch timestamp
75
75
  */
76
76
  convertRelativeTimestamp(relativeTime) {
77
77
  return this.#correctedOriginTime + relativeTime;
@@ -1,4 +1,4 @@
1
- import { onCLS } from 'web-vitals';
1
+ import { onCLS } from 'web-vitals/attribution';
2
2
  import { VITAL_NAMES } from './constants';
3
3
  import { VitalMetric } from './vital-metric';
4
4
  import { isBrowserScope } from '../constants/runtime';
@@ -7,12 +7,19 @@ if (isBrowserScope) {
7
7
  onCLS(_ref => {
8
8
  let {
9
9
  value,
10
- entries
10
+ attribution,
11
+ id
11
12
  } = _ref;
12
- if (cumulativeLayoutShift.roundingMethod(value) === cumulativeLayoutShift.current.value) return;
13
+ const attrs = {
14
+ metricId: id,
15
+ largestShiftTarget: attribution.largestShiftTarget,
16
+ largestShiftTime: attribution.largestShiftTime,
17
+ largestShiftValue: attribution.largestShiftValue,
18
+ loadState: attribution.loadState
19
+ };
13
20
  cumulativeLayoutShift.update({
14
21
  value,
15
- entries
22
+ attrs
16
23
  });
17
24
  }, {
18
25
  reportAllChanges: true
@@ -1,4 +1,4 @@
1
- import { onFCP } from 'web-vitals';
1
+ import { onFCP } from 'web-vitals/attribution';
2
2
  // eslint-disable-next-line camelcase
3
3
  import { iOSBelow16, initiallyHidden, isBrowserScope } from '../constants/runtime';
4
4
  import { VITAL_NAMES } from './constants';
@@ -16,8 +16,7 @@ if (isBrowserScope) {
16
16
  paintEntries.forEach(entry => {
17
17
  if (entry.name === 'first-contentful-paint') {
18
18
  firstContentfulPaint.update({
19
- value: Math.floor(entry.startTime),
20
- entries: paintEntries
19
+ value: Math.floor(entry.startTime)
21
20
  });
22
21
  }
23
22
  });
@@ -29,12 +28,17 @@ if (isBrowserScope) {
29
28
  onFCP(_ref => {
30
29
  let {
31
30
  value,
32
- entries
31
+ attribution
33
32
  } = _ref;
34
33
  if (initiallyHidden || firstContentfulPaint.isValid) return;
34
+ const attrs = {
35
+ timeToFirstByte: attribution.timeToFirstByte,
36
+ firstByteToFCP: attribution.firstByteToFCP,
37
+ loadState: attribution.loadState
38
+ };
35
39
  firstContentfulPaint.update({
36
40
  value,
37
- entries
41
+ attrs
38
42
  });
39
43
  });
40
44
  }
@@ -1,4 +1,4 @@
1
- import { onFID } from 'web-vitals';
1
+ import { onFID } from 'web-vitals/attribution';
2
2
  import { VitalMetric } from './vital-metric';
3
3
  import { VITAL_NAMES } from './constants';
4
4
  import { initiallyHidden, isBrowserScope } from '../constants/runtime';
@@ -7,18 +7,20 @@ if (isBrowserScope) {
7
7
  onFID(_ref => {
8
8
  let {
9
9
  value,
10
- entries
10
+ attribution
11
11
  } = _ref;
12
- if (initiallyHidden || firstInputDelay.isValid || entries.length === 0) return;
12
+ if (initiallyHidden || firstInputDelay.isValid) return;
13
+ const attrs = {
14
+ type: attribution.eventType,
15
+ fid: Math.round(value),
16
+ eventTarget: attribution.eventTarget,
17
+ loadState: attribution.loadState
18
+ };
13
19
 
14
20
  // CWV will only report one (THE) first-input entry to us; fid isn't reported if there are no user interactions occurs before the *first* page hiding.
15
21
  firstInputDelay.update({
16
- value: entries[0].startTime,
17
- entries,
18
- attrs: {
19
- type: entries[0].name,
20
- fid: Math.round(value)
21
- }
22
+ value: attribution.eventTime,
23
+ attrs
22
24
  });
23
25
  });
24
26
  }
@@ -10,8 +10,7 @@ if (isBrowserScope) {
10
10
 
11
11
  /* Initial hidden state and pre-rendering not yet considered for first paint. See web-vitals onFCP for example. */
12
12
  firstPaint.update({
13
- value: entry.startTime,
14
- entries
13
+ value: entry.startTime
15
14
  });
16
15
  }
17
16
  });
@@ -1,4 +1,4 @@
1
- import { onINP } from 'web-vitals';
1
+ import { onINP } from 'web-vitals/attribution';
2
2
  import { VitalMetric } from './vital-metric';
3
3
  import { VITAL_NAMES } from './constants';
4
4
  import { isBrowserScope } from '../constants/runtime';
@@ -8,15 +8,19 @@ if (isBrowserScope) {
8
8
  onINP(_ref => {
9
9
  let {
10
10
  value,
11
- entries,
11
+ attribution,
12
12
  id
13
13
  } = _ref;
14
+ const attrs = {
15
+ metricId: id,
16
+ eventTarget: attribution.eventTarget,
17
+ eventType: attribution.eventType,
18
+ eventTime: attribution.eventTime,
19
+ loadState: attribution.loadState
20
+ };
14
21
  interactionToNextPaint.update({
15
22
  value,
16
- entries,
17
- attrs: {
18
- metricId: id
19
- }
23
+ attrs
20
24
  });
21
25
  });
22
26
  }
@@ -1,4 +1,4 @@
1
- import { onLCP } from 'web-vitals';
1
+ import { onLCP } from 'web-vitals/attribution';
2
2
  import { VitalMetric } from './vital-metric';
3
3
  import { VITAL_NAMES } from './constants';
4
4
  import { initiallyHidden, isBrowserScope } from '../constants/runtime';
@@ -8,26 +8,28 @@ if (isBrowserScope) {
8
8
  onLCP(_ref => {
9
9
  let {
10
10
  value,
11
- entries
11
+ attribution
12
12
  } = _ref;
13
13
  /* Largest Contentful Paint - As of WV v3, it still imperfectly tries to detect document vis state asap and isn't supposed to report if page starts hidden. */
14
14
  if (initiallyHidden || largestContentfulPaint.isValid) return;
15
- const lcpEntry = entries[entries.length - 1]; // this looks weird if we only expect one, but this is how cwv-attribution gets it so to be sure...
15
+ let attrs;
16
+ const lcpEntry = attribution.lcpEntry;
17
+ if (lcpEntry) {
18
+ attrs = {
19
+ size: lcpEntry.size,
20
+ eid: lcpEntry.id,
21
+ element: attribution.element,
22
+ timeToFirstByte: attribution.timeToFirstByte,
23
+ resourceLoadDelay: attribution.resourceLoadDelay,
24
+ resourceLoadTime: attribution.resourceLoadTime,
25
+ elementRenderDelay: attribution.elementRenderDelay
26
+ };
27
+ if (attribution.url) attrs.elUrl = cleanURL(attribution.url);
28
+ if (lcpEntry.element?.tagName) attrs.elTag = lcpEntry.element.tagName;
29
+ }
16
30
  largestContentfulPaint.update({
17
31
  value,
18
- entries,
19
- ...(entries.length > 0 && {
20
- attrs: {
21
- size: lcpEntry.size,
22
- eid: lcpEntry.id,
23
- ...(!!lcpEntry.url && {
24
- elUrl: cleanURL(lcpEntry.url)
25
- }),
26
- ...(!!lcpEntry.element?.tagName && {
27
- elTag: lcpEntry.element.tagName
28
- })
29
- }
30
- })
32
+ attrs
31
33
  });
32
34
  });
33
35
  }
@@ -8,7 +8,6 @@ if (isBrowserScope) {
8
8
  entries.forEach(entry => {
9
9
  longTask.update({
10
10
  value: entry.duration,
11
- entries: [entry],
12
11
  attrs: {
13
12
  ltFrame: entry.name,
14
13
  // MDN: the browsing context or frame that can be attributed to the long task
@@ -1,17 +1,20 @@
1
1
  import { globalScope, isBrowserScope, isiOS, offset } from '../constants/runtime';
2
2
  import { VITAL_NAMES } from './constants';
3
3
  import { VitalMetric } from './vital-metric';
4
- import { onTTFB } from 'web-vitals';
4
+ import { onTTFB } from 'web-vitals/attribution';
5
5
  export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
6
6
  if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS) {
7
7
  onTTFB(_ref => {
8
8
  let {
9
9
  value,
10
- entries
10
+ attribution
11
11
  } = _ref;
12
- if (!timeToFirstByte.isValid) timeToFirstByte.update({
12
+ if (timeToFirstByte.isValid) return;
13
+ timeToFirstByte.update({
13
14
  value,
14
- entries
15
+ attrs: {
16
+ navigationEntry: attribution.navigationEntry
17
+ }
15
18
  });
16
19
  });
17
20
  } else {
@@ -23,7 +26,9 @@ if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isi
23
26
  // ttfb is equiv to document's responseStart property in timing API --> https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/responseStart
24
27
  timeToFirstByte.update({
25
28
  value: entry.responseStart,
26
- entries: [entry]
29
+ attrs: {
30
+ navigationEntry: entry
31
+ }
27
32
  });
28
33
  }
29
34
  }
@@ -9,20 +9,18 @@ export class VitalMetric {
9
9
  update(_ref) {
10
10
  let {
11
11
  value,
12
- entries = [],
13
12
  attrs = {}
14
13
  } = _ref;
15
14
  if (value < 0) return;
16
15
  const state = {
17
16
  value: this.roundingMethod(value),
18
17
  name: this.name,
19
- entries,
20
18
  attrs
21
19
  };
22
20
  this.history.push(state);
23
21
  this.#subscribers.forEach(cb => {
24
22
  try {
25
- cb(this.current);
23
+ cb(state);
26
24
  } catch (e) {
27
25
  // ignore errors
28
26
  }
@@ -32,7 +30,6 @@ export class VitalMetric {
32
30
  return this.history[this.history.length - 1] || {
33
31
  value: undefined,
34
32
  name: this.name,
35
- entries: [],
36
33
  attrs: {}
37
34
  };
38
35
  }
@@ -1,5 +1,5 @@
1
- import { now } from '../timing/now';
2
1
  import { globalScope } from '../constants/runtime';
2
+ import { now } from '../timing/now';
3
3
  export const defaults = {
4
4
  beacon: 'bam.nr-data.net',
5
5
  errorBeacon: 'bam.nr-data.net'
@@ -29,7 +29,8 @@ export class Aggregate extends AggregateBase {
29
29
  scheduler.startTimer(harvestTimeSeconds);
30
30
  this.drain();
31
31
  });
32
- const denyList = getRuntime(agentIdentifier).denyList;
32
+ const agentRuntime = getRuntime(agentIdentifier);
33
+ const denyList = agentRuntime.denyList;
33
34
  setDenyList(denyList);
34
35
  let ajaxEvents = [];
35
36
  let spaAjaxEvents = {};
@@ -115,7 +116,7 @@ export class Aggregate extends AggregateBase {
115
116
  if (xhrContext.dt) {
116
117
  event.spanId = xhrContext.dt.spanId;
117
118
  event.traceId = xhrContext.dt.traceId;
118
- event.spanTimestamp = xhrContext.dt.timestamp;
119
+ event.spanTimestamp = agentRuntime.timeKeeper.correctAbsoluteTimestamp(xhrContext.dt.timestamp);
119
120
  }
120
121
 
121
122
  // parsed from the AJAX body, looking for operationName param & parsing query for operationType
@@ -8,7 +8,6 @@ import { id } from '../../../common/ids/id';
8
8
  import { ffVersion, globalScope, isBrowserScope } from '../../../common/constants/runtime';
9
9
  import { dataSize } from '../../../common/util/data-size';
10
10
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
11
- import { now } from '../../../common/timing/now';
12
11
  import { wrapFetch, wrapXhr } from '../../../common/wrap';
13
12
  import { parseUrl } from '../../../common/url/parse-url';
14
13
  import { DT } from './distributed-tracing';
@@ -17,6 +16,7 @@ import { InstrumentBase } from '../../utils/instrument-base';
17
16
  import { FEATURE_NAME } from '../constants';
18
17
  import { FEATURE_NAMES } from '../../../loaders/features/features';
19
18
  import { SUPPORTABILITY_METRIC } from '../../metrics/constants';
19
+ import { now } from '../../../common/timing/now';
20
20
  var handlers = ['load', 'error', 'abort', 'timeout'];
21
21
  var handlersLen = handlers.length;
22
22
  var origRequest = originals.REQ;
@@ -13,13 +13,13 @@ import { stringify } from '../../../common/util/stringify';
13
13
  import { handle } from '../../../common/event-emitter/handle';
14
14
  import { mapOwn } from '../../../common/util/map-own';
15
15
  import { getInfo, getConfigurationValue, getRuntime } from '../../../common/config/config';
16
- import { now } from '../../../common/timing/now';
17
16
  import { globalScope } from '../../../common/constants/runtime';
18
17
  import { FEATURE_NAME } from '../constants';
19
18
  import { FEATURE_NAMES } from '../../../loaders/features/features';
20
19
  import { AggregateBase } from '../../utils/aggregate-base';
21
20
  import { getNREUMInitializedAgent } from '../../../common/window/nreum';
22
21
  import { deregisterDrain } from '../../../common/drain/drain';
22
+ import { now } from '../../../common/timing/now';
23
23
 
24
24
  /**
25
25
  * @typedef {import('./compute-stack-trace.js').StackInfo} StackInfo
@@ -167,7 +167,7 @@ export class Aggregate extends AggregateBase {
167
167
  if (!this.stackReported[bucketHash]) {
168
168
  this.stackReported[bucketHash] = true;
169
169
  params.stack_trace = truncateSize(stackInfo.stackString);
170
- this.observedAt[bucketHash] = agentRuntime.offset + time;
170
+ this.observedAt[bucketHash] = agentRuntime.timeKeeper.convertRelativeTimestamp(time);
171
171
  } else {
172
172
  params.browser_stack_hash = stringHashCode(stackInfo.stackString);
173
173
  }
@@ -184,6 +184,7 @@ export class Aggregate extends AggregateBase {
184
184
  }
185
185
  if (agentRuntime?.session?.state?.sessionReplayMode) params.hasReplay = true;
186
186
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
187
+ params.timestamp = this.observedAt[bucketHash];
187
188
  var type = internal ? 'ierr' : 'err';
188
189
  var newMetrics = {
189
190
  time
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { handle } from '../../../common/event-emitter/handle';
7
- import { now } from '../../../common/timing/now';
8
7
  import { InstrumentBase } from '../../utils/instrument-base';
9
8
  import { FEATURE_NAME } from '../constants';
10
9
  import { FEATURE_NAMES } from '../../../loaders/features/features';
@@ -12,6 +11,7 @@ import { globalScope } from '../../../common/constants/runtime';
12
11
  import { eventListenerOpts } from '../../../common/event-listener/event-listener-opts';
13
12
  import { stringify } from '../../../common/util/stringify';
14
13
  import { UncaughtError } from './uncaught-error';
14
+ import { now } from '../../../common/timing/now';
15
15
  export class Instrument extends InstrumentBase {
16
16
  static featureName = FEATURE_NAME;
17
17
  #seenErrors = new Set();
@@ -89,14 +89,15 @@ export class Aggregate extends AggregateBase {
89
89
  width = window.document.documentElement.clientWidth;
90
90
  height = window.document.documentElement.clientHeight;
91
91
  }
92
+ const agentRuntime = getRuntime(this.agentIdentifier);
92
93
  var defaults = {
93
- timestamp: t + getRuntime(this.agentIdentifier).offset,
94
+ timestamp: agentRuntime.timeKeeper.convertRelativeTimestamp(t),
94
95
  timeSinceLoad: t / 1000,
95
96
  browserWidth: width,
96
97
  browserHeight: height,
97
98
  referrerUrl: this.referrerUrl,
98
99
  currentUrl: cleanURL('' + location),
99
- pageUrl: cleanURL(getRuntime(this.agentIdentifier).origin),
100
+ pageUrl: cleanURL(agentRuntime.origin),
100
101
  eventType: 'PageAction'
101
102
  };
102
103
  mapOwn(defaults, set);
@@ -15,6 +15,8 @@ import { drain } from '../../../common/drain/drain';
15
15
  import { FEATURE_NAMES } from '../../../loaders/features/features';
16
16
  import { handle } from '../../../common/event-emitter/handle';
17
17
  import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants';
18
+ import { now } from '../../../common/timing/now';
19
+ import { TimeKeeper } from '../../../common/timing/time-keeper';
18
20
  export class Aggregate extends AggregateBase {
19
21
  static featureName = CONSTANTS.FEATURE_NAME;
20
22
  constructor(agentIdentifier, aggregator) {
@@ -27,9 +29,9 @@ export class Aggregate extends AggregateBase {
27
29
  timeToFirstByte.subscribe(_ref => {
28
30
  let {
29
31
  value,
30
- entries
32
+ attrs
31
33
  } = _ref;
32
- const navEntry = entries[0];
34
+ const navEntry = attrs.navigationEntry;
33
35
  this.timeToFirstByte = Math.max(value, this.timeToFirstByte);
34
36
  this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad); // our "frontend" duration
35
37
  this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent); // our "dom processing" duration
@@ -108,6 +110,7 @@ export class Aggregate extends AggregateBase {
108
110
  }
109
111
  queryParameters.fp = firstPaint.current.value;
110
112
  queryParameters.fcp = firstContentfulPaint.current.value;
113
+ const rumStartTime = now();
111
114
  harvester.send({
112
115
  endpoint: 'rum',
113
116
  payload: {
@@ -122,16 +125,19 @@ export class Aggregate extends AggregateBase {
122
125
  let {
123
126
  status,
124
127
  responseText,
125
- xhr,
126
- fullUrl
128
+ xhr
127
129
  } = _ref3;
130
+ const rumEndTime = now();
128
131
  if (status >= 400 || status === 0) {
129
132
  // Adding retry logic for the rum call will be a separate change
130
133
  this.ee.abort();
131
134
  return;
132
135
  }
133
136
  try {
134
- this.timeKeeper.processRumRequest(xhr, fullUrl);
137
+ const timeKeeper = new TimeKeeper();
138
+ timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
139
+ if (!timeKeeper.ready) throw new Error('TimeKeeper not ready');
140
+ agentRuntime.timeKeeper = timeKeeper;
135
141
  } catch (error) {
136
142
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['PVE/NRTime/Calculation/Failed'], undefined, FEATURE_NAMES.metrics, this.ee);
137
143
  drain(this.agentIdentifier, FEATURE_NAMES.metrics, true);
@@ -20,6 +20,8 @@ import { interactionToNextPaint } from '../../../common/vitals/interaction-to-ne
20
20
  import { largestContentfulPaint } from '../../../common/vitals/largest-contentful-paint';
21
21
  import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
22
22
  import { longTask } from '../../../common/vitals/long-task';
23
+ import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
24
+ import { VITAL_NAMES } from '../../../common/vitals/constants';
23
25
  export class Aggregate extends AggregateBase {
24
26
  static featureName = FEATURE_NAME;
25
27
  #handleVitalMetric = _ref => {
@@ -38,15 +40,13 @@ export class Aggregate extends AggregateBase {
38
40
  this.timingsSent = [];
39
41
  this.curSessEndRecorded = false;
40
42
  if (getConfigurationValue(this.agentIdentifier, 'page_view_timing.long_task') === true) longTask.subscribe(this.#handleVitalMetric);
41
-
42
- /* It's important that CWV api, like "onLCP", is called before this scheduler is initialized. The reason is because they listen to the same
43
- on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
44
-
45
43
  registerHandler('docHidden', msTimestamp => this.endCurrentSession(msTimestamp), this.featureName, this.ee);
46
44
  registerHandler('winPagehide', msTimestamp => this.recordPageUnload(msTimestamp), this.featureName, this.ee);
47
45
  const initialHarvestSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.initialHarvestSeconds') || 10;
48
46
  const harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'page_view_timing.harvestTimeSeconds') || 30;
49
47
  this.waitForFlags([]).then(() => {
48
+ /* It's important that CWV api, like "onLCP", is called before the **scheduler** is initialized. The reason is because they listen to the same
49
+ on vis change or pagehide events, and we'd want ex. onLCP to record the timing (win the race) before we try to send "final harvest". */
50
50
  firstPaint.subscribe(this.#handleVitalMetric);
51
51
  firstContentfulPaint.subscribe(this.#handleVitalMetric);
52
52
  firstInputDelay.subscribe(this.#handleVitalMetric);
@@ -54,10 +54,23 @@ export class Aggregate extends AggregateBase {
54
54
  interactionToNextPaint.subscribe(this.#handleVitalMetric);
55
55
  timeToFirstByte.subscribe(_ref2 => {
56
56
  let {
57
- entries
57
+ attrs
58
58
  } = _ref2;
59
- this.addTiming('load', Math.round(entries[0].loadEventEnd));
59
+ this.addTiming('load', Math.round(attrs.navigationEntry.loadEventEnd));
60
60
  });
61
+ subscribeToVisibilityChange(() => {
62
+ /* Downstream, the event consumer interprets all timing node value as ms-unit and converts it to seconds via division by 1000. CLS is unitless so this normally is a problem.
63
+ bel.6 schema also doesn't support decimal values, of which cls within [0,1). However, the two nicely cancels out, and we can multiply cls by 1000 to both negate the division
64
+ and send an integer > 1. We effectively lose some precision down to 3 decimal places for this workaround. E.g. (real) 0.749132... -> 749.132...-> 749 -> 0.749 (final) */
65
+ const {
66
+ name,
67
+ value,
68
+ attrs
69
+ } = cumulativeLayoutShift.current;
70
+ if (value === undefined) return;
71
+ this.addTiming(name, value * 1000, attrs);
72
+ }, true); // CLS node should only reports on vis change rather than on every change
73
+
61
74
  const scheduler = new HarvestScheduler('events', {
62
75
  onFinished: function () {
63
76
  return _this.onHarvestFinished(...arguments);
@@ -107,8 +120,9 @@ export class Aggregate extends AggregateBase {
107
120
  Issue: Because NR 'pageHide' was only sent once with what is considered the "final" CLS value, in the case that 'pageHide' fires before 'load' happens, we incorrectly a final CLS of 0 for that page.
108
121
  Mitigation: We've set initial CLS to null so that it's omitted from timings like 'pageHide' in that edge case. It should only be included if onCLS callback was executed at least once.
109
122
  Future: onCLS value changes should be reported directly & CLS separated into its own timing node so it's not beholden to 'pageHide' firing. It'd also be possible to report the real final CLS.
123
+ *cli Mar'24 update: CLS now emitted as its own timing node in addition to as-property under other nodes. The 'cls' property is unnecessary for cls nodes.
110
124
  */
111
- if (cumulativeLayoutShift.current.value >= 0) {
125
+ if (name !== VITAL_NAMES.CUMULATIVE_LAYOUT_SHIFT && cumulativeLayoutShift.current.value >= 0) {
112
126
  attrs.cls = cumulativeLayoutShift.current.value;
113
127
  }
114
128
  this.timings.push({
@@ -5,10 +5,10 @@
5
5
  import { handle } from '../../../common/event-emitter/handle';
6
6
  import { subscribeToVisibilityChange } from '../../../common/window/page-visibility';
7
7
  import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts';
8
- import { now } from '../../../common/timing/now';
9
8
  import { InstrumentBase } from '../../utils/instrument-base';
10
9
  import { FEATURE_NAME } from '../constants';
11
10
  import { isBrowserScope } from '../../../common/constants/runtime';
11
+ import { now } from '../../../common/timing/now';
12
12
  export class Instrument extends InstrumentBase {
13
13
  static featureName = FEATURE_NAME;
14
14
  constructor(agentIdentifier, aggregator) {