@atlaskit/react-ufo 3.14.3 → 3.14.4

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 (111) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cjs/create-payload/critical-metrics-payload/index.js +38 -0
  3. package/dist/cjs/create-payload/critical-metrics-payload/root-metrics.js +180 -0
  4. package/dist/cjs/create-payload/critical-metrics-payload/segment-metrics/create-segment-metrics.js +251 -0
  5. package/dist/cjs/create-payload/critical-metrics-payload/segment-metrics/get-is-root-segment.js +9 -0
  6. package/dist/cjs/create-payload/critical-metrics-payload/segment-metrics/get-segment-id.js +15 -0
  7. package/dist/cjs/create-payload/critical-metrics-payload/segment-metrics/get-segment-status.js +59 -0
  8. package/dist/cjs/create-payload/critical-metrics-payload/segment-metrics/has-segment-failed.js +17 -0
  9. package/dist/cjs/create-payload/critical-metrics-payload/segment-metrics/is-label-stack-under-segment.js +11 -0
  10. package/dist/cjs/create-payload/critical-metrics-payload/types.js +5 -0
  11. package/dist/cjs/create-payload/index.js +122 -214
  12. package/dist/cjs/create-payload/utils/find-matching-legacy-metric.js +15 -0
  13. package/dist/cjs/create-payload/utils/get-browser-metadata.js +87 -0
  14. package/dist/cjs/create-payload/utils/get-fmp.js +52 -0
  15. package/dist/cjs/create-payload/utils/get-navigation-metrics.js +66 -0
  16. package/dist/cjs/create-payload/utils/get-paint-metrics.js +124 -0
  17. package/dist/cjs/create-payload/utils/get-payload-size.js +17 -0
  18. package/dist/cjs/create-payload/utils/get-react-ufo-payload-version.js +3 -1
  19. package/dist/cjs/create-payload/utils/get-ssr-success.js +15 -0
  20. package/dist/cjs/create-payload/utils/get-ttai.js +14 -0
  21. package/dist/cjs/create-payload/utils/get-tti.js +38 -0
  22. package/dist/cjs/interaction-metrics/index.js +25 -0
  23. package/dist/es2019/create-payload/critical-metrics-payload/index.js +6 -0
  24. package/dist/es2019/create-payload/critical-metrics-payload/root-metrics.js +166 -0
  25. package/dist/es2019/create-payload/critical-metrics-payload/segment-metrics/create-segment-metrics.js +155 -0
  26. package/dist/es2019/create-payload/critical-metrics-payload/segment-metrics/get-is-root-segment.js +3 -0
  27. package/dist/es2019/create-payload/critical-metrics-payload/segment-metrics/get-segment-id.js +9 -0
  28. package/dist/es2019/create-payload/critical-metrics-payload/segment-metrics/get-segment-status.js +40 -0
  29. package/dist/es2019/create-payload/critical-metrics-payload/segment-metrics/has-segment-failed.js +10 -0
  30. package/dist/es2019/create-payload/critical-metrics-payload/segment-metrics/is-label-stack-under-segment.js +5 -0
  31. package/dist/es2019/create-payload/critical-metrics-payload/types.js +1 -0
  32. package/dist/es2019/create-payload/index.js +55 -151
  33. package/dist/es2019/create-payload/utils/find-matching-legacy-metric.js +7 -0
  34. package/dist/es2019/create-payload/utils/get-browser-metadata.js +79 -0
  35. package/dist/es2019/create-payload/utils/get-fmp.js +47 -0
  36. package/dist/es2019/create-payload/utils/get-navigation-metrics.js +59 -0
  37. package/dist/es2019/create-payload/utils/get-paint-metrics.js +78 -0
  38. package/dist/es2019/create-payload/utils/get-payload-size.js +11 -0
  39. package/dist/es2019/create-payload/utils/get-react-ufo-payload-version.js +2 -1
  40. package/dist/es2019/create-payload/utils/get-ssr-success.js +7 -0
  41. package/dist/es2019/create-payload/utils/get-ttai.js +9 -0
  42. package/dist/es2019/create-payload/utils/get-tti.js +35 -0
  43. package/dist/es2019/interaction-metrics/index.js +24 -0
  44. package/dist/esm/create-payload/critical-metrics-payload/index.js +31 -0
  45. package/dist/esm/create-payload/critical-metrics-payload/root-metrics.js +174 -0
  46. package/dist/esm/create-payload/critical-metrics-payload/segment-metrics/create-segment-metrics.js +244 -0
  47. package/dist/esm/create-payload/critical-metrics-payload/segment-metrics/get-is-root-segment.js +3 -0
  48. package/dist/esm/create-payload/critical-metrics-payload/segment-metrics/get-segment-id.js +9 -0
  49. package/dist/esm/create-payload/critical-metrics-payload/segment-metrics/get-segment-status.js +52 -0
  50. package/dist/esm/create-payload/critical-metrics-payload/segment-metrics/has-segment-failed.js +10 -0
  51. package/dist/esm/create-payload/critical-metrics-payload/segment-metrics/is-label-stack-under-segment.js +5 -0
  52. package/dist/esm/create-payload/critical-metrics-payload/types.js +1 -0
  53. package/dist/esm/create-payload/index.js +121 -210
  54. package/dist/esm/create-payload/utils/find-matching-legacy-metric.js +9 -0
  55. package/dist/esm/create-payload/utils/get-browser-metadata.js +79 -0
  56. package/dist/esm/create-payload/utils/get-fmp.js +47 -0
  57. package/dist/esm/create-payload/utils/get-navigation-metrics.js +59 -0
  58. package/dist/esm/create-payload/utils/get-paint-metrics.js +119 -0
  59. package/dist/esm/create-payload/utils/get-payload-size.js +11 -0
  60. package/dist/esm/create-payload/utils/get-react-ufo-payload-version.js +2 -1
  61. package/dist/esm/create-payload/utils/get-ssr-success.js +7 -0
  62. package/dist/esm/create-payload/utils/get-ttai.js +7 -0
  63. package/dist/esm/create-payload/utils/get-tti.js +33 -0
  64. package/dist/esm/interaction-metrics/index.js +24 -0
  65. package/dist/types/common/common/types.d.ts +1 -1
  66. package/dist/types/common/react-ufo-payload-schema.d.ts +23 -2
  67. package/dist/types/create-payload/critical-metrics-payload/index.d.ts +6 -0
  68. package/dist/types/create-payload/critical-metrics-payload/root-metrics.d.ts +7 -0
  69. package/dist/types/create-payload/critical-metrics-payload/segment-metrics/create-segment-metrics.d.ts +3 -0
  70. package/dist/types/create-payload/critical-metrics-payload/segment-metrics/get-is-root-segment.d.ts +2 -0
  71. package/dist/types/create-payload/critical-metrics-payload/segment-metrics/get-segment-id.d.ts +2 -0
  72. package/dist/types/create-payload/critical-metrics-payload/segment-metrics/get-segment-status.d.ts +7 -0
  73. package/dist/types/create-payload/critical-metrics-payload/segment-metrics/has-segment-failed.d.ts +2 -0
  74. package/dist/types/create-payload/critical-metrics-payload/segment-metrics/is-label-stack-under-segment.d.ts +2 -0
  75. package/dist/types/create-payload/critical-metrics-payload/types.d.ts +128 -0
  76. package/dist/types/create-payload/index.d.ts +339 -834
  77. package/dist/types/create-payload/utils/find-matching-legacy-metric.d.ts +5 -0
  78. package/dist/types/create-payload/utils/get-browser-metadata.d.ts +21 -0
  79. package/dist/types/create-payload/utils/get-fmp.d.ts +6 -0
  80. package/dist/types/create-payload/utils/get-navigation-metrics.d.ts +29 -0
  81. package/dist/types/create-payload/utils/get-paint-metrics.d.ts +13 -0
  82. package/dist/types/create-payload/utils/get-payload-size.d.ts +1 -0
  83. package/dist/types/create-payload/utils/get-react-ufo-payload-version.d.ts +2 -1
  84. package/dist/types/create-payload/utils/get-ssr-success.d.ts +2 -0
  85. package/dist/types/create-payload/utils/get-ttai.d.ts +2 -0
  86. package/dist/types/create-payload/utils/get-tti.d.ts +7 -0
  87. package/dist/types/interaction-metrics/index.d.ts +1 -0
  88. package/dist/types-ts4.5/common/common/types.d.ts +1 -1
  89. package/dist/types-ts4.5/common/react-ufo-payload-schema.d.ts +23 -2
  90. package/dist/types-ts4.5/create-payload/critical-metrics-payload/index.d.ts +6 -0
  91. package/dist/types-ts4.5/create-payload/critical-metrics-payload/root-metrics.d.ts +7 -0
  92. package/dist/types-ts4.5/create-payload/critical-metrics-payload/segment-metrics/create-segment-metrics.d.ts +3 -0
  93. package/dist/types-ts4.5/create-payload/critical-metrics-payload/segment-metrics/get-is-root-segment.d.ts +2 -0
  94. package/dist/types-ts4.5/create-payload/critical-metrics-payload/segment-metrics/get-segment-id.d.ts +2 -0
  95. package/dist/types-ts4.5/create-payload/critical-metrics-payload/segment-metrics/get-segment-status.d.ts +7 -0
  96. package/dist/types-ts4.5/create-payload/critical-metrics-payload/segment-metrics/has-segment-failed.d.ts +2 -0
  97. package/dist/types-ts4.5/create-payload/critical-metrics-payload/segment-metrics/is-label-stack-under-segment.d.ts +2 -0
  98. package/dist/types-ts4.5/create-payload/critical-metrics-payload/types.d.ts +130 -0
  99. package/dist/types-ts4.5/create-payload/index.d.ts +339 -834
  100. package/dist/types-ts4.5/create-payload/utils/find-matching-legacy-metric.d.ts +5 -0
  101. package/dist/types-ts4.5/create-payload/utils/get-browser-metadata.d.ts +21 -0
  102. package/dist/types-ts4.5/create-payload/utils/get-fmp.d.ts +6 -0
  103. package/dist/types-ts4.5/create-payload/utils/get-navigation-metrics.d.ts +29 -0
  104. package/dist/types-ts4.5/create-payload/utils/get-paint-metrics.d.ts +13 -0
  105. package/dist/types-ts4.5/create-payload/utils/get-payload-size.d.ts +1 -0
  106. package/dist/types-ts4.5/create-payload/utils/get-react-ufo-payload-version.d.ts +2 -1
  107. package/dist/types-ts4.5/create-payload/utils/get-ssr-success.d.ts +2 -0
  108. package/dist/types-ts4.5/create-payload/utils/get-ttai.d.ts +2 -0
  109. package/dist/types-ts4.5/create-payload/utils/get-tti.d.ts +7 -0
  110. package/dist/types-ts4.5/interaction-metrics/index.d.ts +1 -0
  111. package/package.json +5 -1
@@ -0,0 +1,155 @@
1
+ import { getConfig } from '../../../config';
2
+ import getBrowserMetadata from '../../utils/get-browser-metadata';
3
+ import getPageVisibilityUpToTTAI from '../../utils/get-page-visibility-up-to-ttai';
4
+ import { LATEST_REACT_UFO_PAYLOAD_VERSION } from '../../utils/get-react-ufo-payload-version';
5
+ import getIsRootSegment from './get-is-root-segment';
6
+ import getSegmentId from './get-segment-id';
7
+ import getSegmentStatus from './get-segment-status';
8
+ export async function createSegmentMetricsPayloads(interactionId, interaction) {
9
+ var _interaction$cohortin;
10
+ const config = getConfig();
11
+ if (!config) {
12
+ throw Error('UFO Configuration not provided');
13
+ }
14
+
15
+ // Get browser metadata (using compact nested format)
16
+ const browserMetadata = getBrowserMetadata();
17
+
18
+ // Process cohorting custom data
19
+ const cohortingCustomData = (_interaction$cohortin = interaction.cohortingCustomData) !== null && _interaction$cohortin !== void 0 && _interaction$cohortin.size ? Object.fromEntries(interaction.cohortingCustomData) : undefined;
20
+ const pageVisibilityAtTTAI = getPageVisibilityUpToTTAI(interaction);
21
+ const {
22
+ knownSegments,
23
+ reactProfilerTimings,
24
+ rate,
25
+ routeName,
26
+ previousInteractionName,
27
+ isPreviousInteractionAborted,
28
+ abortedByInteractionName
29
+ } = interaction;
30
+
31
+ // Group segments by name and select the first segment for each name
32
+ const segmentsByName = new Map();
33
+ for (const segment of knownSegments) {
34
+ const segmentId = getSegmentId(segment.labelStack);
35
+
36
+ // skip if no segmentId
37
+ if (!segmentId) {
38
+ continue;
39
+ }
40
+ const name = segment.labelStack[segment.labelStack.length - 1].name;
41
+ const isRootSegment = getIsRootSegment(segment.labelStack);
42
+ const segmentProfilerTimings = reactProfilerTimings.filter(timing => {
43
+ const timingSegmentId = getSegmentId(timing.labelStack);
44
+ // check if labelStack matches exactly
45
+ return timingSegmentId === segmentId;
46
+ }).sort((a, b) => {
47
+ return a.startTime - b.startTime;
48
+ });
49
+ const firstMountTiming = segmentProfilerTimings.find(timing => {
50
+ return timing.type === 'mount';
51
+ });
52
+ if (!firstMountTiming) {
53
+ continue;
54
+ }
55
+ const firstMountTime = isRootSegment ? interaction.start : firstMountTiming.startTime;
56
+
57
+ // Check if we already have a segment with this name
58
+ const existingEntry = segmentsByName.get(name);
59
+ if (!existingEntry || firstMountTime < existingEntry.firstMountTime) {
60
+ // Either first time seeing this name, or this segment mounted earlier
61
+ segmentsByName.set(name, {
62
+ segment,
63
+ firstMountTime
64
+ });
65
+ }
66
+ }
67
+
68
+ // Create payloads only for the selected segments (first one per name)
69
+ const payloads = [];
70
+ for (const {
71
+ segment
72
+ } of segmentsByName.values()) {
73
+ var _window$location;
74
+ const segmentId = getSegmentId(segment.labelStack);
75
+ const name = segment.labelStack[segment.labelStack.length - 1].name;
76
+ const isRootSegment = getIsRootSegment(segment.labelStack);
77
+ const segmentProfilerTimings = reactProfilerTimings.filter(timing => {
78
+ const timingSegmentId = getSegmentId(timing.labelStack);
79
+ // check if labelStack matches exactly
80
+ return timingSegmentId === segmentId;
81
+ }).sort((a, b) => {
82
+ return a.startTime - b.startTime;
83
+ });
84
+ const firstMountTiming = segmentProfilerTimings.find(timing => {
85
+ return timing.type === 'mount';
86
+ });
87
+
88
+ // We already checked this exists in the grouping phase
89
+ if (!firstMountTiming) {
90
+ continue;
91
+ }
92
+ const lastTiming = segmentProfilerTimings[segmentProfilerTimings.length - 1];
93
+ const startTime = isRootSegment ? interaction.start : firstMountTiming.startTime;
94
+ const endTime = lastTiming.commitTime;
95
+ const ttai = Math.round(endTime - startTime);
96
+ const {
97
+ status,
98
+ abortReason: segmentAbortReason
99
+ } = getSegmentStatus(interaction, segment);
100
+ const properties = {
101
+ // Basic metadata
102
+ 'event:hostname': ((_window$location = window.location) === null || _window$location === void 0 ? void 0 : _window$location.hostname) || 'unknown',
103
+ 'event:product': config.product,
104
+ 'event:schema': '1.0.0',
105
+ 'event:region': config.region || 'unknown',
106
+ 'event:source': {
107
+ name: 'react-ufo/web',
108
+ version: LATEST_REACT_UFO_PAYLOAD_VERSION
109
+ },
110
+ 'experience:key': 'custom.ufo.critical-metrics',
111
+ 'experience:name': name,
112
+ // Browser metadata (compact nested format)
113
+ browser: browserMetadata.browser,
114
+ device: browserMetadata.device,
115
+ network: browserMetadata.network,
116
+ time: browserMetadata.time,
117
+ metrics: {
118
+ ttai,
119
+ tti: ttai
120
+ },
121
+ interactionId,
122
+ type: 'page_segment_load',
123
+ rate,
124
+ routeName: routeName !== null && routeName !== void 0 ? routeName : undefined,
125
+ // Performance timings
126
+ start: Math.round(startTime),
127
+ end: Math.round(endTime),
128
+ // Status and outcome
129
+ status,
130
+ abortReason: segmentAbortReason,
131
+ previousInteractionName,
132
+ isPreviousInteractionAborted,
133
+ abortedByInteractionName,
134
+ pageVisibilityAtTTAI,
135
+ // Basic error count (not detailed error count)
136
+ errorCount: interaction.errors.length,
137
+ // Cohorting custom data
138
+ ...(Object.keys(cohortingCustomData || {}).length > 0 && {
139
+ cohortingCustomData
140
+ })
141
+ };
142
+ const payload = {
143
+ actionSubject: 'experience',
144
+ action: 'measured',
145
+ eventType: 'operational',
146
+ source: 'measured',
147
+ tags: ['observability'],
148
+ attributes: {
149
+ properties: properties
150
+ }
151
+ };
152
+ payloads.push(payload);
153
+ }
154
+ return payloads;
155
+ }
@@ -0,0 +1,3 @@
1
+ export default function getIsRootSegment(labelStack) {
2
+ return labelStack.length === 1;
3
+ }
@@ -0,0 +1,9 @@
1
+ export default function getSegmentId(labelStack) {
2
+ for (let i = labelStack.length - 1; i >= 0; i--) {
3
+ const label = labelStack[i];
4
+ if ('segmentId' in label) {
5
+ return label.segmentId;
6
+ }
7
+ }
8
+ return undefined;
9
+ }
@@ -0,0 +1,40 @@
1
+ import getInteractionStatus from '../../utils/get-interaction-status';
2
+ import getSegmentId from './get-segment-id';
3
+ import hasSegmentFailed from './has-segment-failed';
4
+ import isLabelStackUnderSegment from './is-label-stack-under-segment';
5
+ export default function getSegmentStatus(interaction, segment) {
6
+ const segmentId = getSegmentId(segment.labelStack);
7
+
8
+ // Get the root interaction status info
9
+ const rootInteractionStatus = getInteractionStatus(interaction);
10
+ const rootStatus = rootInteractionStatus.originalInteractionStatus;
11
+ const rootAbortReason = interaction.abortReason;
12
+ const isInteractionsAbortedByNewInteraction = rootStatus === 'ABORTED' && rootAbortReason === 'new_interaction';
13
+ const isInteractionsAbortedByTransition = rootStatus === 'ABORTED' && rootAbortReason === 'transition';
14
+ let status = 'SUCCEEDED';
15
+ let abortReason;
16
+
17
+ // Check if this specific segment has failed
18
+ if (segmentId && hasSegmentFailed(interaction.errors, segmentId)) {
19
+ status = 'FAILED';
20
+ } else if (isInteractionsAbortedByNewInteraction) {
21
+ status = 'ABORTED';
22
+ abortReason = 'new_interaction';
23
+ } else if (isInteractionsAbortedByTransition) {
24
+ status = 'ABORTED';
25
+ abortReason = 'transition';
26
+ } else if (segmentId) {
27
+ // Check for active holds that are under this segment
28
+ for (const activeHold of interaction.holdActive.values()) {
29
+ if (isLabelStackUnderSegment(activeHold.labelStack, segmentId)) {
30
+ status = 'ABORTED';
31
+ abortReason = 'timeout';
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ return {
37
+ status,
38
+ abortReason
39
+ };
40
+ }
@@ -0,0 +1,10 @@
1
+ import getSegmentId from './get-segment-id';
2
+ export default function hasSegmentFailed(errors, segmentId) {
3
+ return errors.some(error => {
4
+ if (!error.labelStack) {
5
+ return false;
6
+ }
7
+ const errorSegmentId = getSegmentId(error.labelStack);
8
+ return errorSegmentId === segmentId;
9
+ });
10
+ }
@@ -0,0 +1,5 @@
1
+ export default function isLabelStackUnderSegment(labelStack, segmentId) {
2
+ return labelStack.some(label => {
3
+ return 'segmentId' in label && label.segmentId === segmentId;
4
+ });
5
+ }
@@ -1,5 +1,8 @@
1
- import Bowser from 'bowser-ultralight';
1
+ import { getDocument } from '@atlaskit/browser-apis';
2
2
  import { fg } from '@atlaskit/platform-feature-flags';
3
+
4
+ // Import common utilities
5
+
3
6
  import { getLighthouseMetrics } from '../additional-payload';
4
7
  import { CHRReporter } from '../assets';
5
8
  import * as bundleEvalTiming from '../bundle-eval-timing';
@@ -17,10 +20,17 @@ import { filterResourceTimings } from '../resource-timing/common/utils/resource-
17
20
  import { roundEpsilon } from '../round-number';
18
21
  import * as ssr from '../ssr';
19
22
  import { buildSegmentTree, labelStackStartWith, optimizeLabelStack, sanitizeUfoName, stringifyLabelStackFully } from './common/utils';
23
+ import { createCriticalMetricsPayloads } from './critical-metrics-payload';
24
+ import { getBrowserMetadataToLegacyFormat } from './utils/get-browser-metadata';
20
25
  import getInteractionStatus from './utils/get-interaction-status';
26
+ import { getNavigationMetricsToLegacyFormat } from './utils/get-navigation-metrics';
21
27
  import getPageVisibilityUpToTTAI from './utils/get-page-visibility-up-to-ttai';
28
+ import { getPaintMetricsToLegacyFormat } from './utils/get-paint-metrics';
29
+ import getPayloadSize from './utils/get-payload-size';
22
30
  import { getReactUFOPayloadVersion } from './utils/get-react-ufo-payload-version';
23
31
  import getSSRDoneTimeValue from './utils/get-ssr-done-time-value';
32
+ import getSSRSuccessUtil from './utils/get-ssr-success';
33
+ import getTTAI from './utils/get-ttai';
24
34
  import getVCMetrics from './utils/get-vc-metrics';
25
35
  function getUfoNameOverride(interaction) {
26
36
  const {
@@ -131,119 +141,12 @@ function getResourceTimings(start, end) {
131
141
  function getBundleEvalTimings(start) {
132
142
  return bundleEvalTiming.getBundleEvalTimings(start);
133
143
  }
134
- function getSSRSuccess(type) {
135
- return type === 'page_load' ? ssr.getSSRSuccess() : undefined;
136
- }
137
144
  function getSSRPhaseSuccess(type) {
138
145
  return type === 'page_load' ? ssr.getSSRPhaseSuccess() : undefined;
139
146
  }
140
147
  function getSSRFeatureFlags(type) {
141
148
  return type === 'page_load' ? ssr.getSSRFeatureFlags() : undefined;
142
149
  }
143
- const getLCP = end => {
144
- return new Promise(resolve => {
145
- let observer;
146
- const timeout = setTimeout(() => {
147
- var _observer;
148
- (_observer = observer) === null || _observer === void 0 ? void 0 : _observer.disconnect();
149
- resolve(null);
150
- }, 200);
151
- const performanceObserverCallback = list => {
152
- const entries = Array.from(list.getEntries());
153
- const lastEntry = entries.reduce((agg, entry) => {
154
- // Use the latest LCP candidate before TTAI
155
- if (entry.startTime <= end && (agg === null || agg.startTime < entry.startTime)) {
156
- return entry;
157
- }
158
- return agg;
159
- }, null);
160
- clearTimeout(timeout);
161
- if (!lastEntry || lastEntry === null) {
162
- resolve(null);
163
- } else {
164
- resolve(lastEntry.startTime);
165
- }
166
- };
167
- observer = new PerformanceObserver(performanceObserverCallback);
168
- observer.observe({
169
- type: 'largest-contentful-paint',
170
- buffered: true
171
- });
172
- });
173
- };
174
- async function getPaintMetrics(type, end) {
175
- if (type !== 'page_load') {
176
- return {};
177
- }
178
- const metrics = {};
179
- performance.getEntriesByType('paint').forEach(entry => {
180
- if (entry.name === 'first-paint') {
181
- metrics['metric:fp'] = Math.round(entry.startTime);
182
- }
183
- if (entry.name === 'first-contentful-paint') {
184
- metrics['metric:fcp'] = Math.round(entry.startTime);
185
- }
186
- });
187
- const lcp = await getLCP(end);
188
- if (lcp) {
189
- metrics['metric:lcp'] = Math.round(lcp);
190
- }
191
- return metrics;
192
- }
193
- function getTTAI(interaction) {
194
- const {
195
- start,
196
- end
197
- } = interaction;
198
- const pageVisibilityUpToTTAI = getPageVisibilityUpToTTAI(interaction);
199
- return !interaction.abortReason && pageVisibilityUpToTTAI === 'visible' ? Math.round(end - start) : undefined;
200
- }
201
- function getNavigationMetrics(type) {
202
- if (type !== 'page_load') {
203
- return {};
204
- }
205
- const entries = performance.getEntriesByType('navigation');
206
- if (entries.length === 0) {
207
- return {};
208
- }
209
- const navigation = entries[0];
210
- const metrics = {
211
- // From https://www.w3.org/TR/resource-timing/
212
- redirectStart: Math.round(navigation.redirectStart),
213
- redirectEnd: Math.round(navigation.redirectEnd),
214
- fetchStart: Math.round(navigation.fetchStart),
215
- domainLookupStart: Math.round(navigation.domainLookupStart),
216
- domainLookupEnd: Math.round(navigation.domainLookupEnd),
217
- connectStart: Math.round(navigation.connectStart),
218
- connectEnd: Math.round(navigation.connectEnd),
219
- secureConnectionStart: Math.round(navigation.secureConnectionStart),
220
- requestStart: Math.round(navigation.requestStart),
221
- responseStart: Math.round(navigation.responseStart),
222
- responseEnd: Math.round(navigation.responseEnd),
223
- encodedBodySize: Math.round(navigation.encodedBodySize),
224
- decodedBodySize: Math.round(navigation.decodedBodySize),
225
- transferSize: Math.round(navigation.transferSize),
226
- // From https://www.w3.org/TR/navigation-timing-2/
227
- redirectCount: navigation.redirectCount,
228
- type: navigation.type,
229
- unloadEventEnd: Math.round(navigation.unloadEventEnd),
230
- unloadEventStart: Math.round(navigation.unloadEventStart),
231
- workerStart: Math.round(navigation.workerStart),
232
- nextHopProtocol: navigation.nextHopProtocol
233
-
234
- // The following properties are ignored because they provided limited value on a modern stack (e.g. the content
235
- // is usually rendered and interactive before the dom is fully parsed, dont't play well with streamed content...)
236
- // * domComplete
237
- // * domContentLoadedEventEnd
238
- // * domContentLoadedEventStart
239
- // * domInteractive
240
- // * loadEventEnd
241
- // * loadEventStart
242
- };
243
- return {
244
- 'metrics:navigation': metrics
245
- };
246
- }
247
150
  function getPPSMetrics(interaction) {
248
151
  var _interaction$apdex, _interaction$apdex$;
249
152
  const {
@@ -277,7 +180,7 @@ function getPPSMetrics(interaction) {
277
180
  function getSSRProperties(type) {
278
181
  const ssrPhases = getSSRPhaseSuccess(type);
279
182
  return {
280
- 'ssr:success': getSSRSuccess(type),
183
+ 'ssr:success': getSSRSuccessUtil(type),
281
184
  'ssr:featureFlags': getSSRFeatureFlags(type),
282
185
  ...((ssrPhases === null || ssrPhases === void 0 ? void 0 : ssrPhases.earlyFlush) != null ? {
283
186
  'ssr:earlyflush:success': ssrPhases.earlyFlush
@@ -314,33 +217,6 @@ function getAssetsMetrics(interaction, SSRDoneTime) {
314
217
  return {};
315
218
  }
316
219
  }
317
- function getBrowserMetadata() {
318
- const data = {};
319
- const now = new Date();
320
- data['event:localHour'] = now.getHours(); // returns the hours for this date according to local time
321
- data['event:localDayOfWeek'] = now.getDay(); // Sunday - Saturday : 0 - 6
322
- data['event:localTimezoneOffset'] = now.getTimezoneOffset(); // A number representing the difference, in minutes, between the date as evaluated in the UTC time zone and as evaluated in the local time zone.
323
-
324
- if (navigator.userAgent != null) {
325
- const browser = Bowser.getParser(navigator.userAgent);
326
- data['event:browser:name'] = browser.getBrowserName();
327
- data['event:browser:version'] = browser.getBrowserVersion();
328
- }
329
- if (navigator.hardwareConcurrency != null) {
330
- data['event:cpus'] = navigator.hardwareConcurrency;
331
- }
332
- if (navigator.deviceMemory != null) {
333
- data['event:memory'] = navigator.deviceMemory;
334
- }
335
-
336
- // eslint-disable-next-line compat/compat
337
- if (navigator.connection != null) {
338
- data['event:network:effectiveType'] = navigator.connection.effectiveType;
339
- data['event:network:rtt'] = navigator.connection.rtt;
340
- data['event:network:downlink'] = navigator.connection.downlink;
341
- }
342
- return data;
343
- }
344
220
  function getTracingContextData(interaction) {
345
221
  const {
346
222
  trace,
@@ -362,6 +238,7 @@ function getTracingContextData(interaction) {
362
238
  function optimizeCustomData(interaction) {
363
239
  const {
364
240
  customData,
241
+ cohortingCustomData,
365
242
  legacyMetrics
366
243
  } = interaction;
367
244
  const customDataMap = customData.reduce((result, {
@@ -377,6 +254,17 @@ function optimizeCustomData(interaction) {
377
254
  });
378
255
  return result;
379
256
  }, new Map());
257
+
258
+ // Merge cohorting custom data into the same map
259
+ if (cohortingCustomData && cohortingCustomData.size > 0) {
260
+ var _interaction$labelSta, _customDataMap$get$da, _customDataMap$get, _interaction$labelSta2;
261
+ const label = stringifyLabelStackFully((_interaction$labelSta = interaction.labelStack) !== null && _interaction$labelSta !== void 0 ? _interaction$labelSta : []);
262
+ const value = (_customDataMap$get$da = (_customDataMap$get = customDataMap.get(label)) === null || _customDataMap$get === void 0 ? void 0 : _customDataMap$get.data) !== null && _customDataMap$get$da !== void 0 ? _customDataMap$get$da : {};
263
+ customDataMap.set(label, {
264
+ labelStack: optimizeLabelStack((_interaction$labelSta2 = interaction.labelStack) !== null && _interaction$labelSta2 !== void 0 ? _interaction$labelSta2 : [], getReactUFOPayloadVersion(interaction.type)),
265
+ data: Object.assign(value, Object.fromEntries(cohortingCustomData))
266
+ });
267
+ }
380
268
  if (legacyMetrics) {
381
269
  const legacyMetricsFiltered = legacyMetrics.filter(item => item.type === 'PAGE_LOAD').reduce((result, currentValue) => {
382
270
  for (const [key, value] of Object.entries(currentValue.custom || {})) {
@@ -633,14 +521,15 @@ function getBm3TrackerTimings(interaction) {
633
521
  legacyMetrics
634
522
  };
635
523
  }
636
- function getPayloadSize(payload) {
637
- return Math.round(new TextEncoder().encode(JSON.stringify(payload)).length / 1024);
638
- }
639
524
  function getStylesheetMetrics() {
640
525
  try {
641
- const stylesheets = Array.from(document.styleSheets);
526
+ const doc = getDocument();
527
+ if (!doc) {
528
+ return {};
529
+ }
530
+ const stylesheets = Array.from(doc.styleSheets);
642
531
  const stylesheetCount = stylesheets.length;
643
- const cssrules = Array.from(document.styleSheets).reduce((acc, item) => {
532
+ const cssrules = Array.from(doc.styleSheets).reduce((acc, item) => {
644
533
  // Other domain stylesheets throw a SecurityError
645
534
  try {
646
535
  return acc + item.cssRules.length;
@@ -648,9 +537,9 @@ function getStylesheetMetrics() {
648
537
  return acc;
649
538
  }
650
539
  }, 0);
651
- const styleElements = document.querySelectorAll('style').length;
652
- const styleProps = document.querySelectorAll('[style]');
653
- const styleDeclarations = Array.from(document.querySelectorAll('[style]')).reduce((acc, item) => {
540
+ const styleElements = doc.querySelectorAll('style').length;
541
+ const styleProps = doc.querySelectorAll('[style]');
542
+ const styleDeclarations = Array.from(doc.querySelectorAll('[style]')).reduce((acc, item) => {
654
543
  try {
655
544
  if ('style' in item) {
656
545
  return acc + item.style.length;
@@ -680,7 +569,7 @@ function getErrorCounts(interaction) {
680
569
  'ufo:errors:count': interaction.errors.length
681
570
  };
682
571
  }
683
- async function createInteractionMetricsPayload(interaction, interactionId, experimental) {
572
+ async function createInteractionMetricsPayload(interaction, interactionId, experimental, criticalPayloadCount, vcMetrics) {
684
573
  var _window$location, _config$additionalPay;
685
574
  const interactionPayloadStart = performance.now();
686
575
  const config = getConfig();
@@ -779,7 +668,7 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
779
668
  }
780
669
  const newUFOName = sanitizeUfoName(ufoName);
781
670
  const resourceTimings = getResourceTimings(start, end);
782
- const [vcMetrics, experimentalMetrics, paintMetrics] = await Promise.all([getVCMetrics(interaction), experimental ? getExperimentalVCMetrics(interaction) : Promise.resolve(undefined), getPaintMetrics(type, end)]);
671
+ const [finalVCMetrics, experimentalMetrics, paintMetrics] = await Promise.all([vcMetrics || (await getVCMetrics(interaction)), experimental ? getExperimentalVCMetrics(interaction) : Promise.resolve(undefined), getPaintMetricsToLegacyFormat(type, end)]);
783
672
  const payload = {
784
673
  actionSubject: 'experience',
785
674
  action: 'measured',
@@ -806,14 +695,18 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
806
695
  ...(fg('platform_ufo_report_memory_usage') ? {
807
696
  'event:memory:usage': createMemoryStateReport(interaction.start, interaction.end)
808
697
  } : {}),
698
+ ...(criticalPayloadCount !== undefined ? {
699
+ 'ufo:multipayload': true,
700
+ 'ufo:criticalPayloadCount': criticalPayloadCount
701
+ } : {}),
809
702
  // root
810
- ...getBrowserMetadata(),
703
+ ...getBrowserMetadataToLegacyFormat(),
811
704
  ...getSSRProperties(type),
812
705
  ...getAssetsMetrics(interaction, pageLoadInteractionMetrics === null || pageLoadInteractionMetrics === void 0 ? void 0 : pageLoadInteractionMetrics.SSRDoneTime),
813
706
  ...getPPSMetrics(interaction),
814
707
  ...paintMetrics,
815
- ...getNavigationMetrics(type),
816
- ...vcMetrics,
708
+ ...getNavigationMetricsToLegacyFormat(type),
709
+ ...finalVCMetrics,
817
710
  ...experimentalMetrics,
818
711
  ...((_config$additionalPay = config.additionalPayloadData) === null || _config$additionalPay === void 0 ? void 0 : _config$additionalPay.call(config, interaction)),
819
712
  ...getTracingContextData(interaction),
@@ -884,8 +777,19 @@ export async function createPayloads(interactionId, interaction) {
884
777
  ...interaction,
885
778
  ufoName: ufoNameOverride
886
779
  };
887
- const interactionMetricsPayload = await createInteractionMetricsPayload(modifiedInteraction, interactionId);
888
- return [interactionMetricsPayload];
780
+ const payloads = [];
781
+ const isCriticalMetricsEnabled = fg('platform_ufo_critical_metrics_payload');
782
+
783
+ // Calculate VC metrics once to avoid duplicate expensive calculations
784
+ const vcMetrics = await getVCMetrics(interaction);
785
+
786
+ // typeof Promise<CriticalMetricsPayload[]>
787
+ const criticalMetricsPayloads = isCriticalMetricsEnabled ? await createCriticalMetricsPayloads(interactionId, interaction, vcMetrics) : [];
788
+ payloads.push(...criticalMetricsPayloads);
789
+ const criticalPayloadCount = isCriticalMetricsEnabled ? criticalMetricsPayloads.length : undefined;
790
+ const interactionMetricsPayload = await createInteractionMetricsPayload(modifiedInteraction, interactionId, undefined, criticalPayloadCount, vcMetrics);
791
+ payloads.push(interactionMetricsPayload);
792
+ return payloads.filter(Boolean);
889
793
  }
890
794
  export async function createExperimentalMetricsPayload(interactionId, interaction) {
891
795
  const config = getConfig();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Find matching legacy metric by experience name and type
3
+ */
4
+ export function findMatchingLegacyMetric(interaction, experienceName) {
5
+ var _interaction$legacyMe;
6
+ return (_interaction$legacyMe = interaction.legacyMetrics) === null || _interaction$legacyMe === void 0 ? void 0 : _interaction$legacyMe.find(metric => (metric.key === experienceName || metric.config.reactUFOName === experienceName) && metric.type === 'PAGE_LOAD');
7
+ }
@@ -0,0 +1,79 @@
1
+ import Bowser from 'bowser-ultralight';
2
+ export default function getBrowserMetadata() {
3
+ const data = {
4
+ time: {
5
+ localHour: 0,
6
+ localDayOfWeek: 0,
7
+ localTimezoneOffset: 0
8
+ }
9
+ };
10
+ const now = new Date();
11
+ data.time.localHour = now.getHours(); // returns the hours for this date according to local time
12
+ data.time.localDayOfWeek = now.getDay(); // Sunday - Saturday : 0 - 6
13
+ data.time.localTimezoneOffset = now.getTimezoneOffset(); // A number representing the difference, in minutes, between the date as evaluated in the UTC time zone and as evaluated in the local time zone.
14
+
15
+ if (typeof navigator !== 'undefined' && navigator.userAgent != null) {
16
+ const browser = Bowser.getParser(navigator.userAgent);
17
+ data.browser = {
18
+ name: browser.getBrowserName(),
19
+ version: browser.getBrowserVersion()
20
+ };
21
+ }
22
+ if (typeof navigator !== 'undefined' && navigator.hardwareConcurrency != null) {
23
+ if (!data.device) {
24
+ data.device = {};
25
+ }
26
+ data.device.cpus = navigator.hardwareConcurrency;
27
+ }
28
+ if (typeof navigator !== 'undefined' && navigator.deviceMemory != null) {
29
+ if (!data.device) {
30
+ data.device = {};
31
+ }
32
+ data.device.memory = navigator.deviceMemory;
33
+ }
34
+
35
+ // eslint-disable-next-line compat/compat
36
+ if (typeof navigator !== 'undefined' && navigator.connection != null) {
37
+ data.network = {
38
+ effectiveType: navigator.connection.effectiveType,
39
+ rtt: navigator.connection.rtt,
40
+ downlink: navigator.connection.downlink
41
+ };
42
+ }
43
+ return data;
44
+ }
45
+
46
+ // Helper function to get browser metadata in legacy colon format for backward compatibility
47
+ export function getBrowserMetadataToLegacyFormat() {
48
+ const metadata = getBrowserMetadata();
49
+ const legacyFormat = {};
50
+
51
+ // Time data
52
+ legacyFormat['event:localHour'] = metadata.time.localHour;
53
+ legacyFormat['event:localDayOfWeek'] = metadata.time.localDayOfWeek;
54
+ legacyFormat['event:localTimezoneOffset'] = metadata.time.localTimezoneOffset;
55
+
56
+ // Browser data
57
+ if (metadata.browser) {
58
+ legacyFormat['event:browser:name'] = metadata.browser.name;
59
+ legacyFormat['event:browser:version'] = metadata.browser.version;
60
+ }
61
+
62
+ // Device data
63
+ if (metadata.device) {
64
+ if (metadata.device.cpus !== undefined) {
65
+ legacyFormat['event:cpus'] = metadata.device.cpus;
66
+ }
67
+ if (metadata.device.memory !== undefined) {
68
+ legacyFormat['event:memory'] = metadata.device.memory;
69
+ }
70
+ }
71
+
72
+ // Network data
73
+ if (metadata.network) {
74
+ legacyFormat['event:network:effectiveType'] = metadata.network.effectiveType;
75
+ legacyFormat['event:network:rtt'] = metadata.network.rtt;
76
+ legacyFormat['event:network:downlink'] = metadata.network.downlink;
77
+ }
78
+ return legacyFormat;
79
+ }
@@ -0,0 +1,47 @@
1
+ import { getConfig } from '../../config';
2
+ import { findMatchingLegacyMetric } from './find-matching-legacy-metric';
3
+
4
+ /**
5
+ * Calculate FMP (First Meaningful Paint) based on interaction type and configuration
6
+ * FMP is calculated based on legacy metrics or marks depending on interaction type and configuration
7
+ */
8
+ export function getFMP(interaction, experienceName) {
9
+ var _config$ssr, _config$ssr$getSSRDon, _config$ssr2;
10
+ const {
11
+ start,
12
+ type,
13
+ marks
14
+ } = interaction;
15
+ const config = getConfig();
16
+ const ssrDoneTime = config === null || config === void 0 ? void 0 : (_config$ssr = config.ssr) === null || _config$ssr === void 0 ? void 0 : (_config$ssr$getSSRDon = _config$ssr.getSSRDoneTime) === null || _config$ssr$getSSRDon === void 0 ? void 0 : _config$ssr$getSSRDon.call(_config$ssr);
17
+ const isBM3ConfigSSRDoneAsFmp = interaction.metaData.__legacy__bm3ConfigSSRDoneAsFmp;
18
+ const isUFOConfigSSRDoneAsFmp = interaction.metaData.__legacy__bm3ConfigSSRDoneAsFmp || !!(config !== null && config !== void 0 && (_config$ssr2 = config.ssr) !== null && _config$ssr2 !== void 0 && _config$ssr2.getSSRDoneTime);
19
+
20
+ // Find matching legacy metric
21
+ const matchingLegacyMetric = findMatchingLegacyMetric(interaction, experienceName);
22
+ let fmp;
23
+ if (type === 'page_load' || type === 'transition') {
24
+ if (interaction.legacyMetrics && matchingLegacyMetric) {
25
+ // Check if legacy metric has FMP
26
+ const legacyFmp = matchingLegacyMetric.fmp; // BM3Event doesn't have fmp in types, but it might exist
27
+ if (legacyFmp) {
28
+ fmp = Math.round(legacyFmp - start);
29
+ }
30
+ // If no FMP in legacy metric, return undefined (don't calculate fallback)
31
+ }
32
+ }
33
+ if (type === 'page_load' && fmp === undefined) {
34
+ if (isBM3ConfigSSRDoneAsFmp || isUFOConfigSSRDoneAsFmp) {
35
+ var _marks$find;
36
+ const foundMark = marks === null || marks === void 0 ? void 0 : (_marks$find = marks.find(mark => mark.name === 'fmp')) === null || _marks$find === void 0 ? void 0 : _marks$find.time;
37
+ if (foundMark) {
38
+ fmp = Math.round(foundMark - start);
39
+ } else if (ssrDoneTime) {
40
+ fmp = Math.round(ssrDoneTime - start);
41
+ }
42
+ // If no FMP mark and no SSR done time, fmp remains undefined
43
+ }
44
+ // If not using SSR config, fmp remains undefined for page_load without legacy metrics
45
+ }
46
+ return fmp;
47
+ }