@atlaskit/media-card 79.12.0 → 79.13.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 (38) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/card/card.js +1 -1
  3. package/dist/cjs/card/externalImageCard.js +17 -12
  4. package/dist/cjs/card/fileCard.js +34 -12
  5. package/dist/cjs/card/media-card-analytics-error-boundary.js +1 -1
  6. package/dist/cjs/card/ui/titleBox/failedTitleBox.js +0 -2
  7. package/dist/cjs/inline/loader.js +1 -1
  8. package/dist/cjs/inline/mediaInlineAnalyticsErrorBoundary.js +0 -2
  9. package/dist/cjs/utils/globalScope/globalScope.js +2 -1
  10. package/dist/cjs/utils/mediaPerformanceObserver/durationMetrics.js +1 -0
  11. package/dist/cjs/utils/ufoExperiences.js +454 -68
  12. package/dist/es2019/card/card.js +1 -1
  13. package/dist/es2019/card/externalImageCard.js +16 -13
  14. package/dist/es2019/card/fileCard.js +33 -13
  15. package/dist/es2019/card/media-card-analytics-error-boundary.js +1 -1
  16. package/dist/es2019/card/ui/titleBox/failedTitleBox.js +0 -2
  17. package/dist/es2019/inline/loader.js +1 -1
  18. package/dist/es2019/inline/mediaInlineAnalyticsErrorBoundary.js +0 -2
  19. package/dist/es2019/utils/globalScope/globalScope.js +1 -0
  20. package/dist/es2019/utils/mediaPerformanceObserver/durationMetrics.js +1 -0
  21. package/dist/es2019/utils/ufoExperiences.js +449 -72
  22. package/dist/esm/card/card.js +1 -1
  23. package/dist/esm/card/externalImageCard.js +18 -13
  24. package/dist/esm/card/fileCard.js +35 -13
  25. package/dist/esm/card/media-card-analytics-error-boundary.js +1 -1
  26. package/dist/esm/card/ui/titleBox/failedTitleBox.js +0 -2
  27. package/dist/esm/inline/loader.js +1 -1
  28. package/dist/esm/inline/mediaInlineAnalyticsErrorBoundary.js +0 -2
  29. package/dist/esm/utils/globalScope/globalScope.js +1 -0
  30. package/dist/esm/utils/mediaPerformanceObserver/durationMetrics.js +1 -0
  31. package/dist/esm/utils/ufoExperiences.js +454 -68
  32. package/dist/types/utils/globalScope/globalScope.d.ts +2 -0
  33. package/dist/types/utils/mediaPerformanceObserver/durationMetrics.d.ts +1 -0
  34. package/dist/types/utils/ufoExperiences.d.ts +88 -5
  35. package/dist/types-ts4.5/utils/globalScope/globalScope.d.ts +2 -0
  36. package/dist/types-ts4.5/utils/mediaPerformanceObserver/durationMetrics.d.ts +1 -0
  37. package/dist/types-ts4.5/utils/ufoExperiences.d.ts +88 -5
  38. package/package.json +6 -6
@@ -4,19 +4,402 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.startUfoExperience = exports.shouldPerformanceBeSampled = exports.completeUfoExperience = exports.abortUfoExperience = void 0;
7
+ exports.useMediaCardUfoExperience = exports.startUfoExperience = exports.shouldPerformanceBeSampled = exports.completeUfoExperience = exports.abortUfoExperience = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _react = require("react");
9
11
  var _ufo = require("@atlaskit/ufo");
10
12
  var _mediaCommon = require("@atlaskit/media-common");
11
13
  var _uuidValidate = _interopRequireDefault(require("uuid-validate"));
12
14
  var _analytics = require("./analytics");
13
15
  var _errors = require("../errors");
14
16
  var _mediaClient = require("@atlaskit/media-client");
17
+ var _interactionMetrics = require("@atlaskit/react-ufo/interaction-metrics");
18
+ var _globalScope = require("./globalScope/globalScope");
15
19
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
16
20
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
17
21
  var packageName = "@atlaskit/media-card";
18
- var packageVersion = "0.0.0-development";
22
+ var packageVersion = "79.13.0";
19
23
  var SAMPLE_RATE = 0.05;
24
+
25
+ /**
26
+ * Determines if performance events should be sampled for this instance.
27
+ * Approximately 5% of instances will be sampled.
28
+ */
29
+ var shouldPerformanceBeSampled = exports.shouldPerformanceBeSampled = function shouldPerformanceBeSampled() {
30
+ return Math.random() < SAMPLE_RATE;
31
+ };
32
+
33
+ /**
34
+ * Gets the UFO interaction start time.
35
+ * For page_load: returns 0 (relative to page navigation)
36
+ * For SPA transitions: returns performance.now() when transition started
37
+ */
38
+ var getInteractionStartTime = function getInteractionStartTime() {
39
+ var _interaction$start;
40
+ var interaction = (0, _interactionMetrics.getActiveInteraction)();
41
+ return (_interaction$start = interaction === null || interaction === void 0 ? void 0 : interaction.start) !== null && _interaction$start !== void 0 ? _interaction$start : 0;
42
+ };
43
+
44
+ /**
45
+ * Finds a performance resource timing entry by matching a full URI.
46
+ * Searches from the end (most recent) to find the latest matching entry.
47
+ */
48
+ var findPerformanceEntryByName = function findPerformanceEntryByName(name) {
49
+ var _getMediaGlobalScope$;
50
+ if (typeof performance === 'undefined' || !performance.getEntriesByType) {
51
+ return undefined;
52
+ }
53
+
54
+ // For data URIs (base64), there won't be a performance entry
55
+ if (name.startsWith('data:')) {
56
+ return undefined;
57
+ }
58
+ var entries = performance.getEntriesByType('resource');
59
+ var ssrPerformanceEntries = (_getMediaGlobalScope$ = (0, _globalScope.getMediaGlobalScope)().performanceEntries) !== null && _getMediaGlobalScope$ !== void 0 ? _getMediaGlobalScope$ : [];
60
+ return [].concat((0, _toConsumableArray2.default)(ssrPerformanceEntries), (0, _toConsumableArray2.default)(entries)).find(function (entry) {
61
+ return name.includes(entry.name);
62
+ });
63
+ };
64
+
65
+ /**
66
+ * Creates timing configuration for the UFO experience based on the performance entry.
67
+ * These timings will be calculated by UFO using the marks we add.
68
+ */
69
+ var createTimingsConfig = function createTimingsConfig(prefix) {
70
+ return [{
71
+ key: "".concat(prefix, ":resourceTiming"),
72
+ startMark: "".concat(prefix, ":resourceTiming:start"),
73
+ endMark: "".concat(prefix, ":resourceTiming:end")
74
+ }, {
75
+ key: "".concat(prefix, ":dnsLookup"),
76
+ startMark: "".concat(prefix, ":dnsLookup:start"),
77
+ endMark: "".concat(prefix, ":dnsLookup:end")
78
+ }, {
79
+ key: "".concat(prefix, ":tcpHandshake"),
80
+ startMark: "".concat(prefix, ":tcpHandshake:start"),
81
+ endMark: "".concat(prefix, ":tcpHandshake:end")
82
+ }, {
83
+ key: "".concat(prefix, ":tlsNegotiation"),
84
+ startMark: "".concat(prefix, ":tlsNegotiation:start"),
85
+ endMark: "".concat(prefix, ":tlsNegotiation:end")
86
+ }, {
87
+ key: "".concat(prefix, ":ttfb"),
88
+ startMark: "".concat(prefix, ":ttfb:start"),
89
+ endMark: "".concat(prefix, ":ttfb:end")
90
+ }, {
91
+ key: "".concat(prefix, ":contentDownload"),
92
+ startMark: "".concat(prefix, ":contentDownload:start"),
93
+ endMark: "".concat(prefix, ":contentDownload:end")
94
+ }, {
95
+ key: "".concat(prefix, ":redirect"),
96
+ startMark: "".concat(prefix, ":redirect:start"),
97
+ endMark: "".concat(prefix, ":redirect:end")
98
+ }, {
99
+ key: "".concat(prefix, ":fetchTime"),
100
+ startMark: "".concat(prefix, ":fetchTime:start"),
101
+ endMark: "".concat(prefix, ":fetchTime:end")
102
+ }];
103
+ };
104
+
105
+ /**
106
+ * Creates resource timing metadata from a performance entry.
107
+ * Only includes non-timing information (sizes, cache status, protocol, CDN info).
108
+ * Timing durations are captured via marks which feed into the timings config.
109
+ */
110
+ var createResourceTimingMetadata = function createResourceTimingMetadata(entry) {
111
+ var _entry$serverTiming$s, _entry$serverTiming, _entry$serverTiming2, _entry$serverTiming3;
112
+ return {
113
+ // Size information
114
+ resourceTransferSize: entry.transferSize,
115
+ resourceDecodedBodySize: entry.decodedBodySize,
116
+ // Request info
117
+ resourceInitiatorType: entry.initiatorType,
118
+ resourceNextHopProtocol: entry.nextHopProtocol,
119
+ // Cache status
120
+ resourceBrowserCacheHit: entry.transferSize === 0,
121
+ // Server timing (CDN metrics)
122
+ resourceCdnCacheHit: (_entry$serverTiming$s = (_entry$serverTiming = entry.serverTiming) === null || _entry$serverTiming === void 0 ? void 0 : _entry$serverTiming.some(function (_ref) {
123
+ var name = _ref.name;
124
+ return name === 'cdn-cache-hit';
125
+ })) !== null && _entry$serverTiming$s !== void 0 ? _entry$serverTiming$s : false,
126
+ resourceCdnDownstreamFBL: (_entry$serverTiming2 = entry.serverTiming) === null || _entry$serverTiming2 === void 0 || (_entry$serverTiming2 = _entry$serverTiming2.find(function (_ref2) {
127
+ var name = _ref2.name;
128
+ return name === 'cdn-downstream-fbl';
129
+ })) === null || _entry$serverTiming2 === void 0 ? void 0 : _entry$serverTiming2.duration,
130
+ resourceCdnUpstreamFBL: (_entry$serverTiming3 = entry.serverTiming) === null || _entry$serverTiming3 === void 0 || (_entry$serverTiming3 = _entry$serverTiming3.find(function (_ref3) {
131
+ var name = _ref3.name;
132
+ return name === 'cdn-upstream-fbl';
133
+ })) === null || _entry$serverTiming3 === void 0 ? void 0 : _entry$serverTiming3.duration
134
+ };
135
+ };
136
+
137
+ /**
138
+ * Adds timing marks from a performance resource timing entry to the UFO experience.
139
+ */
140
+ var addResourceTimingMarks = function addResourceTimingMarks(experience, entry, interactionStartTime, prefix) {
141
+ var addMark = function addMark(name, start, end) {
142
+ var relativeStart = start - interactionStartTime;
143
+ var relativeEnd = end - interactionStartTime;
144
+ if (relativeEnd > relativeStart && relativeStart >= 0) {
145
+ experience.mark("".concat(prefix, ":").concat(name, ":start"), relativeStart);
146
+ experience.mark("".concat(prefix, ":").concat(name, ":end"), relativeEnd);
147
+ }
148
+ };
149
+
150
+ // Overall resource timing
151
+ addMark('resourceTiming', entry.startTime, entry.responseEnd);
152
+
153
+ // DNS lookup
154
+ addMark('dnsLookup', entry.domainLookupStart, entry.domainLookupEnd);
155
+
156
+ // TCP handshake
157
+ addMark('tcpHandshake', entry.connectStart, entry.connectEnd);
158
+
159
+ // TLS negotiation (only for HTTPS)
160
+ if (entry.secureConnectionStart > 0) {
161
+ addMark('tlsNegotiation', entry.secureConnectionStart, entry.requestStart);
162
+ }
163
+
164
+ // Request to first byte (TTFB)
165
+ addMark('ttfb', entry.requestStart, entry.responseStart);
166
+
167
+ // Content download
168
+ addMark('contentDownload', entry.responseStart, entry.responseEnd);
169
+
170
+ // Redirect time (if any)
171
+ if (entry.redirectStart > 0 && entry.redirectEnd > 0) {
172
+ addMark('redirect', entry.redirectStart, entry.redirectEnd);
173
+ }
174
+
175
+ // Total fetch time (without redirect) - from fetchStart to responseEnd
176
+ addMark('fetchTime', entry.fetchStart, entry.responseEnd);
177
+ };
178
+ var sanitiseFileAttributes = function sanitiseFileAttributes(fileAttributes) {
179
+ var sanitisedFileId = 'INVALID_FILE_ID';
180
+ if (fileAttributes.fileId === 'external-image' || (0, _uuidValidate.default)(fileAttributes.fileId)) {
181
+ sanitisedFileId = fileAttributes.fileId;
182
+ }
183
+ return _objectSpread(_objectSpread({}, fileAttributes), {}, {
184
+ fileId: sanitisedFileId
185
+ });
186
+ };
187
+ var getBasePayloadAttributes = function getBasePayloadAttributes() {
188
+ return {
189
+ packageName: packageName,
190
+ packageVersion: packageVersion,
191
+ mediaEnvironment: (0, _mediaClient.getMediaEnvironment)(),
192
+ mediaRegion: (0, _mediaClient.getMediaRegion)()
193
+ };
194
+ };
195
+ /**
196
+ * Creates a new UFO experience instance with the given configuration.
197
+ */
198
+ var createExperience = function createExperience(instanceId) {
199
+ var timings = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
200
+ return new _ufo.UFOExperience('media-card-render', {
201
+ platform: {
202
+ component: 'media-card'
203
+ },
204
+ type: _ufo.ExperienceTypes.Experience,
205
+ performanceType: _ufo.ExperiencePerformanceTypes.InlineResult,
206
+ featureFlags: (0, _mediaCommon.getFeatureFlagKeysAllProducts)(),
207
+ timings: timings
208
+ }, instanceId);
209
+ };
210
+
211
+ /**
212
+ * Hook to create a UFO experience tied to a media card component lifecycle.
213
+ *
214
+ * This creates a unique UFOExperience instance per component, allowing:
215
+ * - Unique timing config per instance
216
+ * - Direct control over experience lifecycle
217
+ * - Proper cleanup on unmount
218
+ *
219
+ * @example
220
+ * ```tsx
221
+ * const ufoExperience = useMediaCardUfoExperience({
222
+ * instanceId: internalOccurrenceKey,
223
+ * enabled: shouldSendPerformanceEvent,
224
+ * });
225
+ *
226
+ * // On card visible
227
+ * ufoExperience.start();
228
+ *
229
+ * // On card complete/error
230
+ * ufoExperience.complete(status, fileAttributes, fileStateFlags, ssrReliability, error, ssrPreviewInfo);
231
+ * ```
232
+ */
233
+ var useMediaCardUfoExperience = exports.useMediaCardUfoExperience = function useMediaCardUfoExperience(_ref4) {
234
+ var instanceId = _ref4.instanceId,
235
+ enabled = _ref4.enabled;
236
+ // Store the start time when start() is called - experience creation is deferred to complete()
237
+ var startTimeRef = (0, _react.useRef)(undefined);
238
+ var hasStartedRef = (0, _react.useRef)(false);
239
+ // Store the experience so abort() can use it after complete() has run
240
+ var experienceRef = (0, _react.useRef)(null);
241
+
242
+ // Reset refs when instanceId changes (new card instance)
243
+ (0, _react.useEffect)(function () {
244
+ return function () {
245
+ // Note: Don't clear experienceRef here as abort() might still need it
246
+ // The component's cleanup calls abort() which handles final state
247
+ hasStartedRef.current = false;
248
+ startTimeRef.current = undefined;
249
+ };
250
+ }, [instanceId]);
251
+ var start = (0, _react.useCallback)(function (options) {
252
+ if (!enabled) {
253
+ return;
254
+ }
255
+ // Store the start time - experience will be created in complete()
256
+ // This allows us to have the correct timings config when creating the experience
257
+ hasStartedRef.current = true;
258
+ startTimeRef.current = options !== null && options !== void 0 && options.useInteractionTime ? getInteractionStartTime() : performance.now();
259
+ }, [enabled]);
260
+ var complete = (0, _react.useCallback)(function (status, fileAttributes, fileStateFlags, ssrReliability) {
261
+ var error = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : new _errors.MediaCardError('missing-error-data');
262
+ var ssrPreviewInfo = arguments.length > 5 ? arguments[5] : undefined;
263
+ // Only complete for terminal statuses - ignore intermediate statuses like 'loading-preview'
264
+ if (!['complete', 'error', 'failed-processing'].includes(status)) {
265
+ return;
266
+ }
267
+ if (!enabled || !hasStartedRef.current) {
268
+ return;
269
+ }
270
+ var interactionStartTime = getInteractionStartTime();
271
+
272
+ // Determine timings config based on SSR status
273
+ var timingsConfig = [];
274
+ var resourceTimingEntry;
275
+ if (ssrPreviewInfo !== null && ssrPreviewInfo !== void 0 && ssrPreviewInfo.wasSSRSuccessful && ssrPreviewInfo.dataUri) {
276
+ var _ssrPreviewInfo$srcse;
277
+ resourceTimingEntry = findPerformanceEntryByName((_ssrPreviewInfo$srcse = ssrPreviewInfo.srcset) !== null && _ssrPreviewInfo$srcse !== void 0 ? _ssrPreviewInfo$srcse : ssrPreviewInfo.dataUri);
278
+ if (resourceTimingEntry) {
279
+ timingsConfig = createTimingsConfig('ssr');
280
+ }
281
+ }
282
+
283
+ // Create the experience with the correct timings config
284
+ var experience = createExperience(instanceId, timingsConfig);
285
+ experienceRef.current = experience;
286
+ experience.start(startTimeRef.current);
287
+
288
+ // Add timing marks and metadata based on strategy
289
+ if (ssrPreviewInfo !== null && ssrPreviewInfo !== void 0 && ssrPreviewInfo.wasSSRSuccessful && ssrPreviewInfo.dataUri) {
290
+ if (resourceTimingEntry) {
291
+ addResourceTimingMarks(experience, resourceTimingEntry, interactionStartTime, 'ssr');
292
+ experience.addMetadata(_objectSpread(_objectSpread({}, createResourceTimingMetadata(resourceTimingEntry)), {}, {
293
+ timingStrategy: 'ssr-resource-timing'
294
+ }));
295
+ } else {
296
+ experience.addMetadata({
297
+ timingStrategy: 'ssr-no-entry-found'
298
+ });
299
+ }
300
+ } else if (ssrPreviewInfo !== null && ssrPreviewInfo !== void 0 && ssrPreviewInfo.wasSSRAttempted) {
301
+ // Strategy 2: SSR was attempted but failed - use interaction start time
302
+ experience.addMetadata({
303
+ timingStrategy: 'ssr-failed',
304
+ interactionStartTime: interactionStartTime
305
+ });
306
+ experience.mark('interactionStart', 0);
307
+ } else {
308
+ // Strategy 3: No SSR - CSR mount-based behavior
309
+ experience.addMetadata({
310
+ timingStrategy: 'csr-mount-based'
311
+ });
312
+ }
313
+
314
+ // Complete the experience with appropriate state
315
+ var sanitisedFileAttributes = sanitiseFileAttributes(fileAttributes);
316
+ switch (status) {
317
+ case 'complete':
318
+ experience.success({
319
+ metadata: _objectSpread({
320
+ fileAttributes: sanitisedFileAttributes,
321
+ ssrReliability: ssrReliability,
322
+ fileStateFlags: fileStateFlags
323
+ }, getBasePayloadAttributes())
324
+ });
325
+ break;
326
+ case 'failed-processing':
327
+ experience.failure({
328
+ metadata: _objectSpread({
329
+ fileAttributes: sanitisedFileAttributes,
330
+ ssrReliability: ssrReliability,
331
+ fileStateFlags: fileStateFlags,
332
+ failReason: 'failed-processing'
333
+ }, getBasePayloadAttributes())
334
+ });
335
+ break;
336
+ case 'error':
337
+ experience.failure({
338
+ metadata: _objectSpread(_objectSpread({
339
+ fileAttributes: sanitisedFileAttributes,
340
+ ssrReliability: ssrReliability,
341
+ fileStateFlags: fileStateFlags
342
+ }, (0, _analytics.extractErrorInfo)(error)), {}, {
343
+ request: (0, _analytics.getRenderErrorRequestMetadata)(error)
344
+ }, getBasePayloadAttributes())
345
+ });
346
+ break;
347
+ }
348
+
349
+ // Reset state after completion
350
+ hasStartedRef.current = false;
351
+ startTimeRef.current = undefined;
352
+ }, [enabled, instanceId]);
353
+ var abort = (0, _react.useCallback)(function (properties) {
354
+ if (!enabled) {
355
+ return;
356
+ }
357
+
358
+ // Use existing experience if available (created by complete()),
359
+ // otherwise create new one if experience was started but not completed
360
+ var experience = experienceRef.current;
361
+ if (!experience) {
362
+ if (!hasStartedRef.current) {
363
+ return;
364
+ }
365
+ experience = createExperience(instanceId);
366
+ experienceRef.current = experience;
367
+ experience.start(startTimeRef.current);
368
+ }
369
+ var metadata = _objectSpread({}, getBasePayloadAttributes());
370
+ if (properties !== null && properties !== void 0 && properties.fileAttributes) {
371
+ metadata.fileAttributes = sanitiseFileAttributes(properties.fileAttributes);
372
+ }
373
+ if (properties !== null && properties !== void 0 && properties.fileStateFlags) {
374
+ metadata.fileStateFlags = properties.fileStateFlags;
375
+ }
376
+ if (properties !== null && properties !== void 0 && properties.ssrReliability) {
377
+ metadata.ssrReliability = properties.ssrReliability;
378
+ }
379
+
380
+ // UFO will ignore abort if experience is already in final state
381
+ experience.abort({
382
+ metadata: metadata
383
+ });
384
+
385
+ // Reset state after abort
386
+ hasStartedRef.current = false;
387
+ startTimeRef.current = undefined;
388
+ }, [enabled, instanceId]);
389
+ return (0, _react.useMemo)(function () {
390
+ return {
391
+ start: start,
392
+ complete: complete,
393
+ abort: abort
394
+ };
395
+ }, [start, complete, abort]);
396
+ };
397
+
398
+ // ============================================================================
399
+ // Legacy exports for backwards compatibility
400
+ // These will be removed once all consumers are migrated to the hook
401
+ // ============================================================================
402
+
20
403
  var concurrentExperience;
21
404
  var getExperience = function getExperience(id) {
22
405
  if (!concurrentExperience) {
@@ -32,89 +415,92 @@ var getExperience = function getExperience(id) {
32
415
  }
33
416
  return concurrentExperience.getInstance(id);
34
417
  };
35
- var shouldPerformanceBeSampled = exports.shouldPerformanceBeSampled = function shouldPerformanceBeSampled() {
36
- return (
37
- // We generate about 100M events UFOv1 events, we want to reduce this to about 5M as we can get the same info from there
38
- // Math.random() generates a random floating-point number between 0 (inclusive) and 1 (exclusive).
39
- // The condition Math.random() < SAMPLE_RATE (0.05) will be true approximately 5% of the time.
40
- Math.random() < SAMPLE_RATE
41
- );
42
- };
43
- var startUfoExperience = exports.startUfoExperience = function startUfoExperience(id) {
44
- getExperience(id).start();
45
- };
46
- var sanitiseFileAttributes = function sanitiseFileAttributes(fileAttributes) {
47
- /*
48
- Allow external image mediaItemType as fileId
49
- See ExternalImageIdentifier interface on platform/packages/media/media-client/src/identifier.ts
50
- */
51
- var sanitisedFileId = 'INVALID_FILE_ID';
52
- if (fileAttributes.fileId === 'external-image' || (0, _uuidValidate.default)(fileAttributes.fileId)) {
53
- sanitisedFileId = fileAttributes.fileId;
54
- }
55
- return _objectSpread(_objectSpread({}, fileAttributes), {}, {
56
- fileId: sanitisedFileId
57
- });
418
+ var startUfoExperience = exports.startUfoExperience = function startUfoExperience(id, startTime) {
419
+ getExperience(id).start(startTime);
58
420
  };
59
421
  var completeUfoExperience = exports.completeUfoExperience = function completeUfoExperience(id, status, fileAttributes, fileStateFlags, ssrReliability) {
60
422
  var error = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : new _errors.MediaCardError('missing-error-data');
423
+ var ssrPreviewInfo = arguments.length > 6 ? arguments[6] : undefined;
424
+ // Only complete for terminal statuses - ignore intermediate statuses like 'loading-preview'
425
+ if (!['complete', 'error', 'failed-processing'].includes(status)) {
426
+ return;
427
+ }
428
+ var experience = getExperience(id);
429
+ var interactionStartTime = getInteractionStartTime();
430
+
431
+ // Determine timing strategy based on SSR status
432
+ if (ssrPreviewInfo !== null && ssrPreviewInfo !== void 0 && ssrPreviewInfo.wasSSRSuccessful && ssrPreviewInfo.dataUri) {
433
+ var _ssrPreviewInfo$srcse2;
434
+ var entry = findPerformanceEntryByName((_ssrPreviewInfo$srcse2 = ssrPreviewInfo.srcset) !== null && _ssrPreviewInfo$srcse2 !== void 0 ? _ssrPreviewInfo$srcse2 : ssrPreviewInfo.dataUri);
435
+ if (entry) {
436
+ addResourceTimingMarks(experience, entry, interactionStartTime, 'ssr');
437
+ experience.addMetadata(_objectSpread(_objectSpread({}, createResourceTimingMetadata(entry)), {}, {
438
+ timingStrategy: 'ssr-resource-timing'
439
+ }));
440
+ } else {
441
+ experience.addMetadata({
442
+ timingStrategy: 'ssr-no-entry-found'
443
+ });
444
+ }
445
+ } else if (ssrPreviewInfo !== null && ssrPreviewInfo !== void 0 && ssrPreviewInfo.wasSSRAttempted) {
446
+ experience.addMetadata({
447
+ timingStrategy: 'ssr-failed',
448
+ interactionStartTime: interactionStartTime
449
+ });
450
+ experience.mark('interactionStart', 0);
451
+ } else {
452
+ experience.addMetadata({
453
+ timingStrategy: 'csr-mount-based'
454
+ });
455
+ }
456
+ var sanitisedFileAttributes = sanitiseFileAttributes(fileAttributes);
61
457
  switch (status) {
62
458
  case 'complete':
63
- succeedUfoExperience(id, {
64
- fileAttributes: fileAttributes,
65
- ssrReliability: ssrReliability,
66
- fileStateFlags: fileStateFlags
459
+ experience.success({
460
+ metadata: _objectSpread({
461
+ fileAttributes: sanitisedFileAttributes,
462
+ ssrReliability: ssrReliability,
463
+ fileStateFlags: fileStateFlags
464
+ }, getBasePayloadAttributes())
67
465
  });
68
466
  break;
69
467
  case 'failed-processing':
70
- failUfoExperience(id, {
71
- fileAttributes: fileAttributes,
72
- failReason: 'failed-processing',
73
- ssrReliability: ssrReliability,
74
- fileStateFlags: fileStateFlags
468
+ experience.failure({
469
+ metadata: _objectSpread({
470
+ fileAttributes: sanitisedFileAttributes,
471
+ ssrReliability: ssrReliability,
472
+ fileStateFlags: fileStateFlags,
473
+ failReason: 'failed-processing'
474
+ }, getBasePayloadAttributes())
75
475
  });
76
476
  break;
77
477
  case 'error':
78
- failUfoExperience(id, _objectSpread(_objectSpread({
79
- fileAttributes: fileAttributes
80
- }, (0, _analytics.extractErrorInfo)(error)), {}, {
81
- request: (0, _analytics.getRenderErrorRequestMetadata)(error),
82
- ssrReliability: ssrReliability,
83
- fileStateFlags: fileStateFlags
84
- }));
478
+ experience.failure({
479
+ metadata: _objectSpread(_objectSpread({
480
+ fileAttributes: sanitisedFileAttributes,
481
+ ssrReliability: ssrReliability,
482
+ fileStateFlags: fileStateFlags
483
+ }, (0, _analytics.extractErrorInfo)(error)), {}, {
484
+ request: (0, _analytics.getRenderErrorRequestMetadata)(error)
485
+ }, getBasePayloadAttributes())
486
+ });
85
487
  break;
86
488
  }
87
489
  };
88
- var getBasePayloadAttributes = function getBasePayloadAttributes() {
89
- return {
90
- packageName: packageName,
91
- packageVersion: packageVersion,
92
- mediaEnvironment: (0, _mediaClient.getMediaEnvironment)(),
93
- mediaRegion: (0, _mediaClient.getMediaRegion)()
94
- };
95
- };
96
- var succeedUfoExperience = function succeedUfoExperience(id, properties) {
490
+ var abortUfoExperience = exports.abortUfoExperience = function abortUfoExperience(id, properties) {
491
+ var metadata = _objectSpread({}, getBasePayloadAttributes());
97
492
  if (properties !== null && properties !== void 0 && properties.fileAttributes) {
98
- properties.fileAttributes = sanitiseFileAttributes(properties.fileAttributes);
493
+ metadata.fileAttributes = sanitiseFileAttributes(properties.fileAttributes);
99
494
  }
100
- getExperience(id).success({
101
- metadata: _objectSpread(_objectSpread({}, properties), getBasePayloadAttributes())
102
- });
103
- };
104
- var failUfoExperience = function failUfoExperience(id, properties) {
105
- if (properties !== null && properties !== void 0 && properties.fileAttributes) {
106
- properties.fileAttributes = sanitiseFileAttributes(properties.fileAttributes);
495
+ if (properties !== null && properties !== void 0 && properties.fileStateFlags) {
496
+ metadata.fileStateFlags = properties.fileStateFlags;
107
497
  }
108
- getExperience(id).failure({
109
- metadata: _objectSpread(_objectSpread({}, properties), getBasePayloadAttributes())
110
- });
111
- };
112
- var abortUfoExperience = exports.abortUfoExperience = function abortUfoExperience(id, properties) {
113
- // UFO won't abort if it's already in a final state (succeeded, failed, aborted, etc)
114
- if (properties !== null && properties !== void 0 && properties.fileAttributes) {
115
- properties.fileAttributes = sanitiseFileAttributes(properties.fileAttributes);
498
+ if (properties !== null && properties !== void 0 && properties.ssrReliability) {
499
+ metadata.ssrReliability = properties.ssrReliability;
116
500
  }
117
501
  getExperience(id).abort({
118
- metadata: _objectSpread(_objectSpread({}, properties), getBasePayloadAttributes())
502
+ metadata: metadata
119
503
  });
120
- };
504
+ };
505
+
506
+ // Suppress unused type warnings - these are used for type documentation
@@ -11,7 +11,7 @@ import { useAnalyticsEvents } from '@atlaskit/analytics-next';
11
11
  import { fg } from '@atlaskit/platform-feature-flags';
12
12
  import UFOLabel from '@atlaskit/react-ufo/label';
13
13
  const packageName = "@atlaskit/media-card";
14
- const packageVersion = "0.0.0-development";
14
+ const packageVersion = "79.13.0";
15
15
  export const CardBase = ({
16
16
  identifier,
17
17
  ...otherProps
@@ -7,7 +7,7 @@ import ReactDOM from 'react-dom';
7
7
  import { ImageLoadError } from '../errors';
8
8
  import { generateUniqueId } from '../utils/generateUniqueId';
9
9
  import { getMediaCardCursor } from '../utils/getMediaCardCursor';
10
- import { abortUfoExperience, completeUfoExperience, startUfoExperience, shouldPerformanceBeSampled } from '../utils/ufoExperiences';
10
+ import { shouldPerformanceBeSampled, useMediaCardUfoExperience } from '../utils/ufoExperiences';
11
11
  import { useCurrentValueRef } from '../utils/useCurrentValueRef';
12
12
  import { getDefaultCardDimensions } from '../utils/cardDimensions';
13
13
  import { usePrevious } from '../utils/usePrevious';
@@ -52,10 +52,15 @@ export const ExternalImageCard = ({
52
52
  wasStatusUploading: false,
53
53
  wasStatusProcessing: false
54
54
  });
55
+ const shouldSendPerformanceEvent = useMemo(() => shouldPerformanceBeSampled(), []);
56
+
57
+ // UFO experience hook - creates a unique experience instance per card
58
+ const ufoExperience = useMediaCardUfoExperience({
59
+ instanceId: internalOccurrenceKey,
60
+ enabled: shouldSendPerformanceEvent
61
+ });
55
62
  const startUfoExperienceRef = useCurrentValueRef(() => {
56
- if (shouldSendPerformanceEventRef.current) {
57
- startUfoExperience(internalOccurrenceKey);
58
- }
63
+ ufoExperience.start();
59
64
  });
60
65
  const [status, setStatus] = useState('loading-preview');
61
66
  const cardPreview = useMemo(() => ({
@@ -85,7 +90,6 @@ export const ExternalImageCard = ({
85
90
  const [previewDidRender, setPreviewDidRender] = useState(false);
86
91
  const [error, setError] = useState();
87
92
  const [mediaViewerSelectedItem, setMediaViewerSelectedItem] = useState(null);
88
- const shouldSendPerformanceEventRef = useRef(shouldPerformanceBeSampled());
89
93
 
90
94
  //----------------------------------------------------------------//
91
95
  //---------------------- Analytics ------------------------------//
@@ -101,17 +105,16 @@ export const ExternalImageCard = ({
101
105
  }
102
106
  };
103
107
  createAnalyticsEvent && fireOperationalEvent(createAnalyticsEvent, status, fileAttributes, performanceAttributes, ssrReliability, error, traceContext, undefined);
104
- shouldSendPerformanceEventRef.current && completeUfoExperience(internalOccurrenceKey, status, fileAttributes, fileStateFlagsRef.current, ssrReliability, error);
108
+ // External images don't use SSR - no ssrPreviewInfo passed
109
+ ufoExperience.complete(status, fileAttributes, fileStateFlagsRef.current, ssrReliability, error, undefined);
105
110
  });
106
111
  const fireAbortedEventRef = useCurrentValueRef(() => {
107
112
  // UFO won't abort if it's already in a final state (succeeded, failed, aborted, etc)
108
- if (shouldSendPerformanceEventRef.current) {
109
- abortUfoExperience(internalOccurrenceKey, {
110
- fileAttributes,
111
- fileStateFlags: fileStateFlagsRef === null || fileStateFlagsRef === void 0 ? void 0 : fileStateFlagsRef.current,
112
- ssrReliability: ssrReliability
113
- });
114
- }
113
+ ufoExperience.abort({
114
+ fileAttributes,
115
+ fileStateFlags: fileStateFlagsRef === null || fileStateFlagsRef === void 0 ? void 0 : fileStateFlagsRef.current,
116
+ ssrReliability: ssrReliability
117
+ });
115
118
  });
116
119
 
117
120
  //----------------------------------------------------------------//