@newrelic/browser-agent 1.239.1 → 1.241.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 (178) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/cdn/pro.js +3 -2
  3. package/dist/cjs/cdn/spa.js +4 -3
  4. package/dist/cjs/common/config/state/init.js +25 -17
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/constants/runtime.js +9 -5
  8. package/dist/cjs/common/harvest/harvest.js +5 -3
  9. package/dist/cjs/common/vitals/constants.js +17 -0
  10. package/dist/cjs/common/vitals/cumulative-layout-shift.js +27 -0
  11. package/dist/cjs/common/vitals/first-contentful-paint.js +49 -0
  12. package/dist/cjs/common/vitals/first-input-delay.js +32 -0
  13. package/dist/{esm/features/page_view_timing → cjs/common/vitals}/first-paint.js +19 -17
  14. package/dist/cjs/common/vitals/interaction-to-next-paint.js +29 -0
  15. package/dist/cjs/common/vitals/largest-contentful-paint.js +41 -0
  16. package/dist/cjs/common/vitals/long-task.js +64 -0
  17. package/dist/cjs/common/vitals/time-to-first-byte.js +36 -0
  18. package/dist/cjs/common/vitals/vital-metric.js +71 -0
  19. package/dist/cjs/features/ajax/aggregate/index.js +4 -1
  20. package/dist/cjs/features/metrics/aggregate/index.js +17 -7
  21. package/dist/cjs/features/page_view_event/aggregate/index.js +18 -40
  22. package/dist/cjs/features/page_view_event/constants.js +2 -8
  23. package/dist/cjs/features/page_view_event/instrument/index.js +0 -22
  24. package/dist/cjs/features/page_view_timing/aggregate/index.js +36 -147
  25. package/dist/cjs/features/page_view_timing/instrument/index.js +0 -3
  26. package/dist/cjs/features/session_replay/aggregate/index.js +81 -35
  27. package/dist/cjs/features/session_trace/aggregate/index.js +13 -1
  28. package/dist/cjs/features/spa/aggregate/index.js +4 -3
  29. package/dist/cjs/loaders/agent.js +3 -0
  30. package/dist/cjs/loaders/api/api.js +2 -0
  31. package/dist/cjs/loaders/api/apiAsync.js +4 -2
  32. package/dist/cjs/loaders/browser-agent.js +2 -1
  33. package/dist/cjs/loaders/configure/configure.js +13 -1
  34. package/dist/cjs/loaders/configure/public-path.js +13 -0
  35. package/dist/cjs/loaders/configure/public-path.npm.js +10 -0
  36. package/dist/esm/cdn/pro.js +2 -1
  37. package/dist/esm/cdn/spa.js +2 -1
  38. package/dist/esm/common/config/state/init.js +25 -17
  39. package/dist/esm/common/constants/env.cdn.js +1 -1
  40. package/dist/esm/common/constants/env.npm.js +1 -1
  41. package/dist/esm/common/constants/runtime.js +5 -3
  42. package/dist/esm/common/harvest/harvest.js +6 -4
  43. package/dist/esm/common/vitals/constants.js +10 -0
  44. package/dist/esm/common/vitals/cumulative-layout-shift.js +20 -0
  45. package/dist/esm/common/vitals/first-contentful-paint.js +41 -0
  46. package/dist/esm/common/vitals/first-input-delay.js +25 -0
  47. package/dist/{cjs/features/page_view_timing → esm/common/vitals}/first-paint.js +12 -24
  48. package/dist/esm/common/vitals/interaction-to-next-paint.js +22 -0
  49. package/dist/esm/common/vitals/largest-contentful-paint.js +34 -0
  50. package/dist/esm/common/vitals/long-task.js +57 -0
  51. package/dist/esm/common/vitals/time-to-first-byte.js +29 -0
  52. package/dist/esm/common/vitals/vital-metric.js +64 -0
  53. package/dist/esm/features/ajax/aggregate/index.js +4 -1
  54. package/dist/esm/features/metrics/aggregate/index.js +18 -8
  55. package/dist/esm/features/page_view_event/aggregate/index.js +20 -42
  56. package/dist/esm/features/page_view_event/constants.js +1 -4
  57. package/dist/esm/features/page_view_event/instrument/index.js +0 -22
  58. package/dist/esm/features/page_view_timing/aggregate/index.js +37 -148
  59. package/dist/esm/features/page_view_timing/instrument/index.js +0 -3
  60. package/dist/esm/features/session_replay/aggregate/index.js +81 -35
  61. package/dist/esm/features/session_trace/aggregate/index.js +13 -1
  62. package/dist/esm/features/spa/aggregate/index.js +4 -3
  63. package/dist/esm/loaders/agent.js +2 -0
  64. package/dist/esm/loaders/api/api.js +2 -0
  65. package/dist/esm/loaders/api/apiAsync.js +5 -3
  66. package/dist/esm/loaders/browser-agent.js +2 -1
  67. package/dist/esm/loaders/configure/configure.js +13 -1
  68. package/dist/esm/loaders/configure/public-path.js +6 -0
  69. package/dist/esm/loaders/configure/public-path.npm.js +3 -0
  70. package/dist/types/common/config/state/init.d.ts.map +1 -1
  71. package/dist/types/common/constants/runtime.d.ts +3 -1
  72. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  73. package/dist/types/common/harvest/harvest.d.ts +0 -1
  74. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  75. package/dist/types/common/vitals/constants.d.ts +11 -0
  76. package/dist/types/common/vitals/constants.d.ts.map +1 -0
  77. package/dist/types/common/vitals/cumulative-layout-shift.d.ts +3 -0
  78. package/dist/types/common/vitals/cumulative-layout-shift.d.ts.map +1 -0
  79. package/dist/types/common/vitals/first-contentful-paint.d.ts +3 -0
  80. package/dist/types/common/vitals/first-contentful-paint.d.ts.map +1 -0
  81. package/dist/types/common/vitals/first-input-delay.d.ts +3 -0
  82. package/dist/types/common/vitals/first-input-delay.d.ts.map +1 -0
  83. package/dist/types/common/vitals/first-paint.d.ts +3 -0
  84. package/dist/types/common/vitals/first-paint.d.ts.map +1 -0
  85. package/dist/types/common/vitals/interaction-to-next-paint.d.ts +3 -0
  86. package/dist/types/common/vitals/interaction-to-next-paint.d.ts.map +1 -0
  87. package/dist/types/common/vitals/largest-contentful-paint.d.ts +3 -0
  88. package/dist/types/common/vitals/largest-contentful-paint.d.ts.map +1 -0
  89. package/dist/types/common/vitals/long-task.d.ts +3 -0
  90. package/dist/types/common/vitals/long-task.d.ts.map +1 -0
  91. package/dist/types/common/vitals/time-to-first-byte.d.ts +3 -0
  92. package/dist/types/common/vitals/time-to-first-byte.d.ts.map +1 -0
  93. package/dist/types/common/vitals/vital-metric.d.ts +18 -0
  94. package/dist/types/common/vitals/vital-metric.d.ts.map +1 -0
  95. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  96. package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
  97. package/dist/types/features/page_view_event/aggregate/index.d.ts +3 -2
  98. package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
  99. package/dist/types/features/page_view_event/constants.d.ts +0 -3
  100. package/dist/types/features/page_view_event/constants.d.ts.map +1 -1
  101. package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
  102. package/dist/types/features/page_view_timing/aggregate/index.d.ts +1 -3
  103. package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
  104. package/dist/types/features/page_view_timing/instrument/index.d.ts.map +1 -1
  105. package/dist/types/features/session_replay/aggregate/index.d.ts +16 -4
  106. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  107. package/dist/types/features/session_trace/aggregate/index.d.ts +9 -1
  108. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  109. package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
  110. package/dist/types/loaders/agent.d.ts.map +1 -1
  111. package/dist/types/loaders/api/api.d.ts.map +1 -1
  112. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  113. package/dist/types/loaders/browser-agent.d.ts.map +1 -1
  114. package/dist/types/loaders/configure/configure.d.ts.map +1 -1
  115. package/dist/types/loaders/configure/public-path.d.ts +2 -0
  116. package/dist/types/loaders/configure/public-path.d.ts.map +1 -0
  117. package/dist/types/loaders/configure/public-path.npm.d.ts +2 -0
  118. package/dist/types/loaders/configure/public-path.npm.d.ts.map +1 -0
  119. package/package.json +2 -3
  120. package/src/cdn/pro.js +2 -0
  121. package/src/cdn/spa.js +2 -0
  122. package/src/common/config/state/init.js +21 -17
  123. package/src/common/constants/runtime.js +7 -3
  124. package/src/common/constants/runtime.test.js +8 -0
  125. package/src/common/harvest/harvest-scheduler.test.js +2 -2
  126. package/src/common/harvest/harvest.js +6 -4
  127. package/src/common/harvest/harvest.test.js +17 -0
  128. package/src/common/vitals/__mocks__/web-vitals.js +19 -0
  129. package/src/common/vitals/constants.js +10 -0
  130. package/src/common/vitals/cumulative-layout-shift.js +13 -0
  131. package/src/common/vitals/cumulative-layout-shift.test.js +71 -0
  132. package/src/common/vitals/first-contentful-paint.js +31 -0
  133. package/src/common/vitals/first-contentful-paint.test.js +124 -0
  134. package/src/common/vitals/first-input-delay.js +20 -0
  135. package/src/common/vitals/first-input-delay.test.js +88 -0
  136. package/src/{features/page_view_timing → common/vitals}/first-paint.js +11 -17
  137. package/src/common/vitals/first-paint.test.js +127 -0
  138. package/src/common/vitals/interaction-to-next-paint.js +13 -0
  139. package/src/common/vitals/interaction-to-next-paint.test.js +74 -0
  140. package/src/common/vitals/largest-contentful-paint.js +29 -0
  141. package/src/common/vitals/largest-contentful-paint.test.js +94 -0
  142. package/src/common/vitals/long-task.js +52 -0
  143. package/src/common/vitals/long-task.test.js +122 -0
  144. package/src/common/vitals/time-to-first-byte.js +21 -0
  145. package/src/common/vitals/time-to-first-byte.test.js +147 -0
  146. package/src/common/vitals/vital-metric.js +60 -0
  147. package/src/common/vitals/vital-metric.test.js +171 -0
  148. package/src/features/ajax/aggregate/index.js +5 -1
  149. package/src/features/metrics/aggregate/index.js +11 -4
  150. package/src/features/page_view_event/aggregate/index.js +20 -43
  151. package/src/features/page_view_event/constants.js +0 -3
  152. package/src/features/page_view_event/instrument/index.js +0 -21
  153. package/src/features/page_view_timing/aggregate/index.component-test.js +86 -0
  154. package/src/features/page_view_timing/aggregate/index.js +31 -108
  155. package/src/features/page_view_timing/instrument/index.js +0 -3
  156. package/src/features/session_replay/aggregate/index.component-test.js +10 -10
  157. package/src/features/session_replay/aggregate/index.js +62 -29
  158. package/src/features/session_trace/aggregate/index.js +15 -1
  159. package/src/features/spa/aggregate/index.js +4 -3
  160. package/src/loaders/agent.js +2 -0
  161. package/src/loaders/api/api.js +2 -0
  162. package/src/loaders/api/apiAsync.js +5 -4
  163. package/src/loaders/browser-agent.js +3 -1
  164. package/src/loaders/configure/configure.js +15 -7
  165. package/src/loaders/configure/public-path.js +6 -0
  166. package/src/loaders/configure/public-path.npm.js +4 -0
  167. package/dist/cjs/common/metrics/paint-metrics.js +0 -13
  168. package/dist/cjs/features/page_view_timing/long-tasks.js +0 -75
  169. package/dist/esm/common/metrics/paint-metrics.js +0 -6
  170. package/dist/esm/features/page_view_timing/long-tasks.js +0 -69
  171. package/dist/types/common/metrics/paint-metrics.d.ts +0 -2
  172. package/dist/types/common/metrics/paint-metrics.d.ts.map +0 -1
  173. package/dist/types/features/page_view_timing/first-paint.d.ts +0 -2
  174. package/dist/types/features/page_view_timing/first-paint.d.ts.map +0 -1
  175. package/dist/types/features/page_view_timing/long-tasks.d.ts +0 -2
  176. package/dist/types/features/page_view_timing/long-tasks.d.ts.map +0 -1
  177. package/src/common/metrics/paint-metrics.js +0 -6
  178. package/src/features/page_view_timing/long-tasks.js +0 -60
@@ -0,0 +1,41 @@
1
+ import { onFCP } from 'web-vitals';
2
+ // eslint-disable-next-line camelcase
3
+ import { iOSBelow16, initiallyHidden, isBrowserScope } from '../constants/runtime';
4
+ import { VITAL_NAMES } from './constants';
5
+ import { VitalMetric } from './vital-metric';
6
+ export const firstContentfulPaint = new VitalMetric(VITAL_NAMES.FIRST_CONTENTFUL_PAINT);
7
+
8
+ /* First 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. */
9
+ if (isBrowserScope) {
10
+ // eslint-disable-next-line camelcase
11
+ if (iOSBelow16) {
12
+ try {
13
+ if (!initiallyHidden) {
14
+ // see ios-version.js for detail on this following bug case; tldr: buffered flag doesn't work but getEntriesByType does
15
+ const paintEntries = performance.getEntriesByType('paint');
16
+ paintEntries.forEach(entry => {
17
+ if (entry.name === 'first-contentful-paint') {
18
+ firstContentfulPaint.update({
19
+ value: Math.floor(entry.startTime),
20
+ entries: paintEntries
21
+ });
22
+ }
23
+ });
24
+ }
25
+ } catch (e) {
26
+ // ignore
27
+ }
28
+ } else {
29
+ onFCP(_ref => {
30
+ let {
31
+ value,
32
+ entries
33
+ } = _ref;
34
+ if (initiallyHidden || firstContentfulPaint.isValid) return;
35
+ firstContentfulPaint.update({
36
+ value,
37
+ entries
38
+ });
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,25 @@
1
+ import { onFID } from 'web-vitals';
2
+ import { VitalMetric } from './vital-metric';
3
+ import { VITAL_NAMES } from './constants';
4
+ import { initiallyHidden, isBrowserScope } from '../constants/runtime';
5
+ export const firstInputDelay = new VitalMetric(VITAL_NAMES.FIRST_INPUT_DELAY);
6
+ if (isBrowserScope) {
7
+ onFID(_ref => {
8
+ let {
9
+ value,
10
+ entries
11
+ } = _ref;
12
+ if (initiallyHidden || firstInputDelay.isValid || entries.length === 0) return;
13
+
14
+ // 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
+ firstInputDelay.update({
16
+ value: entries[0].startTime,
17
+ entries,
18
+ attrs: {
19
+ type: entries[0].name,
20
+ fid: Math.round(value)
21
+ },
22
+ shouldAddConnectionAttributes: true
23
+ });
24
+ });
25
+ }
@@ -1,33 +1,24 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.onFirstPaint = void 0;
7
- /**
8
- * Calls the `onReport` function when the 'first-paint' PerformancePaintTiming entry is observed.
9
- * The argument supplied is an object similar to the Metric type used by web-vitals library.
10
- *
11
- * @param {Function} onReport - callback that accepts a `metric` object as the single parameter
12
- */
13
- const onFirstPaint = onReport => {
1
+ import { initiallyHidden, isBrowserScope } from '../constants/runtime';
2
+ import { VITAL_NAMES } from './constants';
3
+ import { VitalMetric } from './vital-metric';
4
+ export const firstPaint = new VitalMetric(VITAL_NAMES.FIRST_PAINT);
5
+ if (isBrowserScope) {
14
6
  const handleEntries = entries => {
15
7
  entries.forEach(entry => {
16
- if (entry.name === 'first-paint') {
8
+ if (entry.name === 'first-paint' && !firstPaint.isValid) {
17
9
  observer.disconnect();
18
10
 
19
11
  /* Initial hidden state and pre-rendering not yet considered for first paint. See web-vitals onFCP for example. */
20
- const metric = {
21
- name: 'FP',
22
- value: entry.startTime
23
- };
24
- onReport(metric);
12
+ firstPaint.update({
13
+ value: entry.startTime,
14
+ entries
15
+ });
25
16
  }
26
17
  });
27
18
  };
28
19
  let observer;
29
20
  try {
30
- if (PerformanceObserver.supportedEntryTypes.includes('paint')) {
21
+ if (PerformanceObserver.supportedEntryTypes.includes('paint') && !initiallyHidden) {
31
22
  observer = new PerformanceObserver(list => {
32
23
  // Delay by a microtask to workaround a bug in Safari where the
33
24
  // callback is invoked immediately, rather than in a separate task.
@@ -44,7 +35,4 @@ const onFirstPaint = onReport => {
44
35
  } catch (e) {
45
36
  // Do nothing.
46
37
  }
47
-
48
- /* BFCache restore not yet considered for first paint. See web-vitals onFCP for example. */
49
- };
50
- exports.onFirstPaint = onFirstPaint;
38
+ }
@@ -0,0 +1,22 @@
1
+ import { onINP } from 'web-vitals';
2
+ import { VitalMetric } from './vital-metric';
3
+ import { VITAL_NAMES } from './constants';
4
+ import { isBrowserScope } from '../constants/runtime';
5
+ export const interactionToNextPaint = new VitalMetric(VITAL_NAMES.INTERACTION_TO_NEXT_PAINT);
6
+ if (isBrowserScope) {
7
+ /* Interaction-to-Next-Paint */
8
+ onINP(_ref => {
9
+ let {
10
+ value,
11
+ entries,
12
+ id
13
+ } = _ref;
14
+ interactionToNextPaint.update({
15
+ value,
16
+ entries,
17
+ attrs: {
18
+ metricId: id
19
+ }
20
+ });
21
+ });
22
+ }
@@ -0,0 +1,34 @@
1
+ import { onLCP } from 'web-vitals';
2
+ import { VitalMetric } from './vital-metric';
3
+ import { VITAL_NAMES } from './constants';
4
+ import { initiallyHidden, isBrowserScope } from '../constants/runtime';
5
+ import { cleanURL } from '../url/clean-url';
6
+ export const largestContentfulPaint = new VitalMetric(VITAL_NAMES.LARGEST_CONTENTFUL_PAINT);
7
+ if (isBrowserScope) {
8
+ onLCP(_ref => {
9
+ let {
10
+ value,
11
+ entries
12
+ } = _ref;
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
+ 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...
16
+ largestContentfulPaint.update({
17
+ 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
+ }),
31
+ shouldAddConnectionAttributes: true
32
+ });
33
+ });
34
+ }
@@ -0,0 +1,57 @@
1
+ import { isBrowserScope } from '../constants/runtime';
2
+ import { subscribeToEOL } from '../unload/eol';
3
+ import { VITAL_NAMES } from './constants';
4
+ import { VitalMetric } from './vital-metric';
5
+ export const longTask = new VitalMetric(VITAL_NAMES.LONG_TASK);
6
+ if (isBrowserScope) {
7
+ const handleEntries = entries => {
8
+ entries.forEach(entry => {
9
+ longTask.update({
10
+ value: entry.duration,
11
+ entries: [entry],
12
+ attrs: {
13
+ ltFrame: entry.name,
14
+ // MDN: the browsing context or frame that can be attributed to the long task
15
+ ltStart: entry.startTime,
16
+ // MDN: a double representing the time (millisec) when the task started
17
+ ltCtr: entry.attribution[0].containerType,
18
+ // MDN: type of frame container: 'iframe', 'embed', or 'object' ... but this can also be 'window',
19
+ ...(entry.attribution[0].containerType !== 'window' && {
20
+ ltCtrSrc: entry.attribution[0].containerSrc,
21
+ // MDN: container's 'src' attribute
22
+ ltCtrId: entry.attribution[0].containerId,
23
+ // MDN: container's 'id' attribute
24
+ ltCtrName: entry.attribution[0].containerName // MDN: container's 'name' attribute
25
+ })
26
+ }
27
+ });
28
+ });
29
+ };
30
+
31
+ let observer;
32
+ try {
33
+ if (PerformanceObserver.supportedEntryTypes.includes('longtask')) {
34
+ observer = new PerformanceObserver(list => {
35
+ // Delay by a microtask to workaround a bug in Safari where the
36
+ // callback is invoked immediately, rather than in a separate task.
37
+ // See: https://github.com/GoogleChrome/web-vitals/issues/277
38
+ Promise.resolve().then(() => {
39
+ handleEntries(list.getEntries());
40
+ });
41
+ });
42
+ observer.observe({
43
+ type: 'longtask',
44
+ buffered: true
45
+ });
46
+ }
47
+ } catch (e) {
48
+ // Do nothing.
49
+ }
50
+ if (observer) {
51
+ subscribeToEOL(() => {
52
+ handleEntries(observer.takeRecords());
53
+ }, true); // this bool is a temp arg under staged BFCache work that runs the func under the new page session logic -- tb removed w/ the feature flag later
54
+
55
+ /* No work needed on BFCache restore for long task. */
56
+ }
57
+ }
@@ -0,0 +1,29 @@
1
+ import { globalScope, isBrowserScope, isiOS, offset } from '../constants/runtime';
2
+ import { VITAL_NAMES } from './constants';
3
+ import { VitalMetric } from './vital-metric';
4
+ import { onTTFB } from 'web-vitals';
5
+ export const timeToFirstByte = new VitalMetric(VITAL_NAMES.TIME_TO_FIRST_BYTE);
6
+ if (isBrowserScope && typeof PerformanceNavigationTiming !== 'undefined' && !isiOS) {
7
+ onTTFB(_ref => {
8
+ let {
9
+ value,
10
+ entries
11
+ } = _ref;
12
+ if (!timeToFirstByte.isValid) timeToFirstByte.update({
13
+ value,
14
+ entries
15
+ });
16
+ });
17
+ } else {
18
+ if (!timeToFirstByte.isValid) {
19
+ const entry = {};
20
+ // convert real timestamps to relative timestamps to match web-vitals behavior
21
+ for (let key in globalScope?.performance?.timing || {}) entry[key] = Math.max(globalScope?.performance?.timing[key] - offset, 0);
22
+
23
+ // ttfb is equiv to document's responseStart property in timing API --> https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/responseStart
24
+ timeToFirstByte.update({
25
+ value: entry.responseStart,
26
+ entries: [entry]
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,64 @@
1
+ export class VitalMetric {
2
+ #subscribers = new Set();
3
+ history = [];
4
+ constructor(name, roundingMethod) {
5
+ this.name = name;
6
+ this.attrs = {};
7
+ this.roundingMethod = typeof roundingMethod === 'function' ? roundingMethod : Math.floor;
8
+ }
9
+ update(_ref) {
10
+ let {
11
+ value,
12
+ entries = [],
13
+ attrs = {},
14
+ shouldAddConnectionAttributes = false
15
+ } = _ref;
16
+ if (value < 0) return;
17
+ const state = {
18
+ value: this.roundingMethod(value),
19
+ name: this.name,
20
+ entries,
21
+ attrs
22
+ };
23
+ if (shouldAddConnectionAttributes) addConnectionAttributes(state.attrs);
24
+ this.history.push(state);
25
+ this.#subscribers.forEach(cb => {
26
+ try {
27
+ cb(this.current);
28
+ } catch (e) {
29
+ // ignore errors
30
+ }
31
+ });
32
+ }
33
+ get current() {
34
+ return this.history[this.history.length - 1] || {
35
+ value: undefined,
36
+ name: this.name,
37
+ entries: [],
38
+ attrs: {}
39
+ };
40
+ }
41
+ get isValid() {
42
+ return this.current.value >= 0;
43
+ }
44
+ subscribe(callback) {
45
+ let buffered = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
46
+ if (typeof callback !== 'function') return;
47
+ this.#subscribers.add(callback);
48
+ // emit full history on subscription ("buffered" behavior)
49
+ if (this.isValid && !!buffered) this.history.forEach(state => {
50
+ callback(state);
51
+ });
52
+ return () => {
53
+ this.#subscribers.delete(callback);
54
+ };
55
+ }
56
+ }
57
+ function addConnectionAttributes(obj) {
58
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; // to date, both window & worker shares the same support for connection
59
+ if (!connection) return;
60
+ if (connection.type) obj['net-type'] = connection.type;
61
+ if (connection.effectiveType) obj['net-etype'] = connection.effectiveType;
62
+ if (connection.rtt) obj['net-rtt'] = connection.rtt;
63
+ if (connection.downlink) obj['net-dlink'] = connection.downlink;
64
+ }
@@ -64,6 +64,8 @@ export class Aggregate extends AggregateBase {
64
64
  scheduler.startTimer(harvestTimeSeconds);
65
65
  });
66
66
  this.drain();
67
+ const beacon = getInfo(agentIdentifier).errorBeacon;
68
+ const proxyBeacon = agentInit.proxy.beacon;
67
69
  function storeXhr(params, metrics, startTime, endTime, type) {
68
70
  metrics.time = startTime;
69
71
 
@@ -80,7 +82,8 @@ export class Aggregate extends AggregateBase {
80
82
  aggregator.store('xhr', hash, params, metrics);
81
83
  if (!allAjaxIsEnabled) return;
82
84
  if (!shouldCollectEvent(params)) {
83
- if (params.hostname === getInfo(agentIdentifier).errorBeacon) {
85
+ if (params.hostname === beacon || proxyBeacon && params.hostname === proxyBeacon) {
86
+ // This doesn't make a distinction if the same-domain request is going to a different port or path...
84
87
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/Agent'], undefined, FEATURE_NAMES.metrics, ee);
85
88
  } else {
86
89
  handle(SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/App'], undefined, FEATURE_NAMES.metrics, ee);
@@ -1,4 +1,4 @@
1
- import { getRuntime } from '../../../common/config/config';
1
+ import { getRuntime, getConfiguration } from '../../../common/config/config';
2
2
  import { registerHandler } from '../../../common/event-emitter/register-handler';
3
3
  import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler';
4
4
  import { FEATURE_NAME, SUPPORTABILITY_METRIC, CUSTOM_METRIC, SUPPORTABILITY_METRIC_CHANNEL, CUSTOM_METRIC_CHANNEL } from '../constants';
@@ -27,13 +27,16 @@ export class Aggregate extends AggregateBase {
27
27
  this.singleChecks(); // checks that are run only one time, at script load
28
28
  this.eachSessionChecks(); // the start of every time user engages with page
29
29
 
30
- // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
31
- scheduler = new HarvestScheduler('jserrors', {
32
- onUnload: () => this.unload()
33
- }, this);
34
- scheduler.harvest.on('jserrors', () => ({
35
- body: this.aggregator.take(['cm', 'sm'])
36
- }));
30
+ this.ee.on("drain-".concat(this.featureName), () => {
31
+ // *cli, Mar 23 - Per NR-94597, this feature should only harvest ONCE at the (potential) EoL time of the page.
32
+ scheduler = new HarvestScheduler('jserrors', {
33
+ onUnload: () => this.unload()
34
+ }, this);
35
+ scheduler.harvest.on('jserrors', () => ({
36
+ body: this.aggregator.take(['cm', 'sm'])
37
+ }));
38
+ }); // this is needed to ensure EoL is "on" and sent
39
+
37
40
  this.drain();
38
41
  }
39
42
  storeSupportabilityMetrics(name, value) {
@@ -87,6 +90,13 @@ export class Aggregate extends AggregateBase {
87
90
  const rules = getRules(this.agentIdentifier);
88
91
  if (rules.length > 0) this.storeSupportabilityMetrics('Generic/Obfuscate/Detected');
89
92
  if (rules.length > 0 && !validateRules(rules)) this.storeSupportabilityMetrics('Generic/Obfuscate/Invalid');
93
+
94
+ // Check if proxy for either chunks or beacon is being used
95
+ const {
96
+ proxy
97
+ } = getConfiguration(this.agentIdentifier);
98
+ if (proxy.assets) this.storeSupportabilityMetrics('Config/AssetsUrl/Changed');
99
+ if (proxy.beacon) this.storeSupportabilityMetrics('Config/BeaconUrl/Changed');
90
100
  }
91
101
  eachSessionChecks() {
92
102
  if (!isBrowserScope) return;
@@ -1,53 +1,42 @@
1
- import { handle } from '../../../common/event-emitter/handle';
2
- import { FEATURE_NAMES } from '../../../loaders/features/features';
3
- import { isiOS, globalScope, isBrowserScope } from '../../../common/constants/runtime';
4
- import { onTTFB } from 'web-vitals';
1
+ import { globalScope, isBrowserScope } from '../../../common/constants/runtime';
5
2
  import { addPT, addPN } from '../../../common/timing/nav-timing';
6
3
  import { stringify } from '../../../common/util/stringify';
7
- import { paintMetrics } from '../../../common/metrics/paint-metrics';
8
- import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config';
4
+ import { getInfo, getRuntime } from '../../../common/config/config';
9
5
  import { Harvest } from '../../../common/harvest/harvest';
10
6
  import * as CONSTANTS from '../constants';
11
7
  import { getActivatedFeaturesFlags } from './initialized-features';
12
8
  import { activateFeatures } from '../../../common/util/feature-flags';
13
9
  import { warn } from '../../../common/util/console';
14
10
  import { AggregateBase } from '../../utils/aggregate-base';
11
+ import { firstContentfulPaint } from '../../../common/vitals/first-contentful-paint';
12
+ import { firstPaint } from '../../../common/vitals/first-paint';
13
+ import { timeToFirstByte } from '../../../common/vitals/time-to-first-byte';
15
14
  export class Aggregate extends AggregateBase {
16
15
  static featureName = CONSTANTS.FEATURE_NAME;
17
16
  constructor(agentIdentifier, aggregator) {
18
17
  super(agentIdentifier, aggregator, CONSTANTS.FEATURE_NAME);
19
- if (typeof PerformanceNavigationTiming !== 'undefined' && !isiOS) {
20
- this.alreadySent = false; // we don't support timings on BFCache restores
21
- const agentRuntime = getRuntime(agentIdentifier); // we'll store timing values on the runtime obj to be read by the aggregate module
18
+ this.timeToFirstByte = 0;
19
+ this.firstByteToWindowLoad = 0; // our "frontend" duration
20
+ this.firstByteToDomContent = 0; // our "dom processing" duration
22
21
 
23
- /* Time To First Byte
24
- This listener must record these values *before* PVE's aggregate sends RUM. */
25
- onTTFB(_ref => {
22
+ if (isBrowserScope) {
23
+ timeToFirstByte.subscribe(_ref => {
26
24
  let {
27
25
  value,
28
26
  entries
29
27
  } = _ref;
30
- if (this.alreadySent) return;
31
- this.alreadySent = true;
32
- agentRuntime[CONSTANTS.TTFB] = Math.round(value); // this is our "backend" duration; web-vitals will ensure it's lower bounded at 0
33
-
34
- // Similar to what vitals does for ttfb, we have to factor in activation-start when calculating relative timings:
35
28
  const navEntry = entries[0];
36
- const respOrActivStart = Math.max(navEntry.responseStart, navEntry.activationStart || 0);
37
- agentRuntime[CONSTANTS.FBTWL] = Math.max(Math.round(navEntry.loadEventEnd - respOrActivStart), 0); // our "frontend" duration
38
- handle('timing', ['load', Math.round(navEntry.loadEventEnd)], undefined, FEATURE_NAMES.pageViewTiming, this.ee);
39
- agentRuntime[CONSTANTS.FBTDC] = Math.max(Math.round(navEntry.domContentLoadedEventEnd - respOrActivStart), 0); // our "dom processing" duration
29
+ this.timeToFirstByte = Math.max(value, this.timeToFirstByte);
30
+ this.firstByteToWindowLoad = Math.max(Math.round(navEntry.loadEventEnd - this.timeToFirstByte), this.firstByteToWindowLoad); // our "frontend" duration
31
+ this.firstByteToDomContent = Math.max(Math.round(navEntry.domContentLoadedEventEnd - this.timeToFirstByte), this.firstByteToDomContent); // our "dom processing" duration
40
32
 
41
33
  this.sendRum();
42
34
  });
43
35
  } else {
44
- this.sendRum(); // timings either already in runtime from instrument or is meant to get 0'd.
36
+ // worker agent build does not get TTFB values, use default 0 values
37
+ this.sendRum();
45
38
  }
46
39
  }
47
-
48
- getScheme() {
49
- return getConfigurationValue(this.agentIdentifier, 'ssl') === false ? 'http' : 'https';
50
- }
51
40
  sendRum() {
52
41
  const info = getInfo(this.agentIdentifier);
53
42
  const agentRuntime = getRuntime(this.agentIdentifier);
@@ -64,13 +53,13 @@ export class Aggregate extends AggregateBase {
64
53
  // Following PR #428, which demands that all agents send RUM call, these need to be sent even outside of the main window context where PerformanceTiming
65
54
  // or PerformanceNavigationTiming do not exists. Hence, they'll be filled in by 0s instead in, for example, worker threads that still init the PVE module.
66
55
  this.aggregator.store('measures', 'be', {
67
- value: isBrowserScope ? agentRuntime[CONSTANTS.TTFB] : 0
56
+ value: this.timeToFirstByte
68
57
  });
69
58
  this.aggregator.store('measures', 'fe', {
70
- value: isBrowserScope ? agentRuntime[CONSTANTS.FBTWL] : 0
59
+ value: this.firstByteToWindowLoad
71
60
  });
72
61
  this.aggregator.store('measures', 'dc', {
73
- value: isBrowserScope ? agentRuntime[CONSTANTS.FBTDC] : 0
62
+ value: this.firstByteToDomContent
74
63
  });
75
64
  const queryParameters = {
76
65
  tt: info.ttGuid,
@@ -111,19 +100,8 @@ export class Aggregate extends AggregateBase {
111
100
  queryParameters.perf = stringify(perf);
112
101
  }
113
102
  }
114
- try {
115
- // PVTiming sends these too, albeit using web-vitals and slightly different; it's unknown why they're duplicated, but PVT should be the truth
116
- var entries = globalScope.performance.getEntriesByType('paint');
117
- entries.forEach(function (entry) {
118
- if (!entry.startTime || entry.startTime <= 0) return;
119
- if (entry.name === 'first-paint') {
120
- queryParameters.fp = String(Math.floor(entry.startTime));
121
- } else if (entry.name === 'first-contentful-paint') {
122
- queryParameters.fcp = String(Math.floor(entry.startTime));
123
- }
124
- paintMetrics[entry.name] = Math.floor(entry.startTime); // this is consumed by Spa module
125
- });
126
- } catch (e) {}
103
+ queryParameters.fp = firstPaint.current.value;
104
+ queryParameters.fcp = firstContentfulPaint.current.value;
127
105
  harvester.send({
128
106
  endpoint: 'rum',
129
107
  payload: {
@@ -1,5 +1,2 @@
1
1
  import { FEATURE_NAMES } from '../../loaders/features/features';
2
- export const FEATURE_NAME = FEATURE_NAMES.pageViewEvent;
3
- export const TTFB = 'firstbyte';
4
- export const FBTDC = 'domcontent';
5
- export const FBTWL = 'windowload';
2
+ export const FEATURE_NAME = FEATURE_NAMES.pageViewEvent;
@@ -1,32 +1,10 @@
1
- import { handle } from '../../../common/event-emitter/handle';
2
- import { isiOS } from '../../../common/constants/runtime';
3
1
  import { InstrumentBase } from '../../utils/instrument-base';
4
2
  import * as CONSTANTS from '../constants';
5
- import { FEATURE_NAMES } from '../../../loaders/features/features';
6
- import { getRuntime } from '../../../common/config/config';
7
- import { onDOMContentLoaded, onWindowLoad } from '../../../common/window/load';
8
- import { now } from '../../../common/timing/now';
9
3
  export class Instrument extends InstrumentBase {
10
4
  static featureName = CONSTANTS.FEATURE_NAME;
11
5
  constructor(agentIdentifier, aggregator) {
12
6
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
13
7
  super(agentIdentifier, aggregator, CONSTANTS.FEATURE_NAME, auto);
14
- if ((typeof PerformanceNavigationTiming === 'undefined' || isiOS) && typeof PerformanceTiming !== 'undefined') {
15
- // For majority browser versions in which PNT exists, we can get load timings later from the nav entry (in the aggregate portion). At minimum, PT should exist for main window.
16
- // *cli Mar'23 - iOS 15.2 & 15.4 testing in Sauce still fails with onTTFB. Hence, all iOS will fallback to this for now. Unknown if this is similar in nature to iOSBelow16 bug.
17
- const agentRuntime = getRuntime(agentIdentifier);
18
- agentRuntime[CONSTANTS.TTFB] = Math.max(Date.now() - agentRuntime.offset, 0);
19
- onDOMContentLoaded(() => {
20
- agentRuntime[CONSTANTS.FBTDC] = Math.max(now() - agentRuntime[CONSTANTS.TTFB], 0);
21
- });
22
- onWindowLoad(() => {
23
- const timeNow = now();
24
- agentRuntime[CONSTANTS.FBTWL] = Math.max(timeNow - agentRuntime[CONSTANTS.TTFB], 0);
25
- handle('timing', ['load', timeNow], undefined, FEATURE_NAMES.pageViewTiming, this.ee);
26
- });
27
- }
28
- // Else, inference: inside worker or some other env where these events are irrelevant. They'll get filled in with 0s in RUM call.
29
-
30
8
  this.importAggregator();
31
9
  }
32
10
  }