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