@atlaskit/media-file-preview 0.16.2 → 0.16.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/media-file-preview
2
2
 
3
+ ## 0.16.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`be94d5f05b8ae`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/be94d5f05b8ae) -
8
+ Fix media image blob cache to only evict blobs when they are no longer referenced
9
+ - Updated dependencies
10
+
11
+ ## 0.16.3
12
+
13
+ ### Patch Changes
14
+
15
+ - [`fa50dabb4be8f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fa50dabb4be8f) -
16
+ Retrieve clientId synchronously during Media SSR blob URL construction.
17
+ - Updated dependencies
18
+
3
19
  ## 0.16.2
4
20
 
5
21
  ### Patch Changes
@@ -34,6 +34,14 @@ var CardPreviewCacheImpl = exports.CardPreviewCacheImpl = /*#__PURE__*/(0, _crea
34
34
  (0, _defineProperty2.default)(this, "clear", function () {
35
35
  _this.previewCache.clear();
36
36
  });
37
+ (0, _defineProperty2.default)(this, "acquire", function (id, mode) {
38
+ var cacheKey = getCacheKey(id, mode);
39
+ _this.previewCache.acquire(cacheKey);
40
+ });
41
+ (0, _defineProperty2.default)(this, "release", function (id, mode) {
42
+ var cacheKey = getCacheKey(id, mode);
43
+ _this.previewCache.release(cacheKey);
44
+ });
37
45
  this.previewCache = previewCache;
38
46
  });
39
47
  var mediaFilePreviewCache = exports.mediaFilePreviewCache = new CardPreviewCacheImpl((0, _objectURLCache.createObjectURLCache)());
@@ -6,9 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.isSSRPreview = exports.isSSRDataPreview = exports.isSSRClientPreview = exports.isRemotePreview = exports.isLocalPreview = exports.getSSRPreview = exports.getAndCacheRemotePreview = exports.getAndCacheLocalPreview = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
10
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
11
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
12
  var _mediaClient = require("@atlaskit/media-client");
13
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
14
  var _errors = require("../errors");
13
15
  var _cache = require("./cache");
14
16
  var _helpers = require("./helpers");
@@ -41,15 +43,41 @@ var getDataUri = function getDataUri(mediaClient, id, params, mediaBlobUrlAttrs)
41
43
  var rawDataURI = mediaClient.getImageUrlSync(id, params);
42
44
  return mediaBlobUrlAttrs ? (0, _mediaClient.addFileAttrsToUrl)(rawDataURI, mediaBlobUrlAttrs) : rawDataURI;
43
45
  };
46
+
47
+ /**
48
+ * Merges a clientId into mediaBlobUrlAttrs for cross-client copy support.
49
+ * Returns the original attrs unchanged if clientId is not available or the feature flag is off.
50
+ */
51
+ var mergeClientIdIntoAttrs = function mergeClientIdIntoAttrs(clientId, id, mediaBlobUrlAttrs, collectionName) {
52
+ if (!clientId) {
53
+ return mediaBlobUrlAttrs;
54
+ }
55
+ if (mediaBlobUrlAttrs) {
56
+ return _objectSpread(_objectSpread({}, mediaBlobUrlAttrs), {}, {
57
+ clientId: clientId
58
+ });
59
+ }
60
+
61
+ // Construct minimal attrs when none provided
62
+ return {
63
+ id: id,
64
+ clientId: clientId,
65
+ contextId: collectionName || '',
66
+ collection: collectionName
67
+ };
68
+ };
44
69
  var getSSRPreview = exports.getSSRPreview = function getSSRPreview(ssr, mediaClient, id, params, mediaBlobUrlAttrs) {
45
70
  try {
46
- var dataURI = getDataUri(mediaClient, id, params, mediaBlobUrlAttrs);
71
+ // Synchronously extract clientId from initialAuth and merge into blob URL attrs
72
+ var clientId = (0, _platformFeatureFlags.fg)('platform_media_cross_client_copy_with_auth') ? mediaClient.getClientIdSync() : undefined;
73
+ var attrsWithClientId = mergeClientIdIntoAttrs(clientId, id, mediaBlobUrlAttrs, params.collection);
74
+ var dataURI = getDataUri(mediaClient, id, params, attrsWithClientId);
47
75
  var srcSet = "".concat(dataURI, " 1x");
48
76
  if (params.width) {
49
77
  var doubleDataURI = getDataUri(mediaClient, id, _objectSpread(_objectSpread({}, params), {}, {
50
78
  width: params.width * 2,
51
79
  height: params.height && params.height * 2
52
- }), mediaBlobUrlAttrs);
80
+ }), attrsWithClientId);
53
81
  // We want to embed some meta context into dataURI for Copy/Paste to work.
54
82
  srcSet += ", ".concat(doubleDataURI, " 2x");
55
83
  }
@@ -83,49 +111,100 @@ var isSSRPreview = exports.isSSRPreview = function isSSRPreview(preview) {
83
111
  var ssrClientSources = ['ssr-client', 'ssr-server', 'ssr-data'];
84
112
  return ssrClientSources.includes(preview.source);
85
113
  };
86
- var getAndCacheRemotePreview = exports.getAndCacheRemotePreview = /*#__PURE__*/function () {
87
- var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(mediaClient, id, dimensions, params, mediaBlobUrlAttrs, traceContext) {
88
- var remotePreview;
114
+
115
+ /**
116
+ * Resolves clientId (sync first, async fallback) and enriches mediaBlobUrlAttrs
117
+ * with it for cross-client copy support.
118
+ */
119
+ var enrichAttrsWithClientId = /*#__PURE__*/function () {
120
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(mediaClient, id, mediaBlobUrlAttrs, collectionName) {
121
+ var clientId;
89
122
  return _regenerator.default.wrap(function _callee$(_context) {
90
123
  while (1) switch (_context.prev = _context.next) {
91
124
  case 0:
92
- _context.next = 2;
93
- return (0, _helpers.getRemotePreview)(mediaClient, id, params, traceContext);
125
+ if ((0, _platformFeatureFlags.fg)('platform_media_cross_client_copy_with_auth')) {
126
+ _context.next = 2;
127
+ break;
128
+ }
129
+ return _context.abrupt("return", mediaBlobUrlAttrs);
94
130
  case 2:
95
- remotePreview = _context.sent;
96
- return _context.abrupt("return", extendAndCachePreview(id, params.mode, _objectSpread(_objectSpread({}, remotePreview), {}, {
97
- dimensions: dimensions
98
- }), mediaBlobUrlAttrs));
99
- case 4:
131
+ // Try sync first, then async fallback
132
+ clientId = mediaClient.getClientIdSync();
133
+ if (clientId) {
134
+ _context.next = 12;
135
+ break;
136
+ }
137
+ _context.prev = 4;
138
+ _context.next = 7;
139
+ return mediaClient.getClientId(collectionName);
140
+ case 7:
141
+ clientId = _context.sent;
142
+ _context.next = 12;
143
+ break;
144
+ case 10:
145
+ _context.prev = 10;
146
+ _context.t0 = _context["catch"](4);
147
+ case 12:
148
+ return _context.abrupt("return", mergeClientIdIntoAttrs(clientId, id, mediaBlobUrlAttrs, collectionName));
149
+ case 13:
100
150
  case "end":
101
151
  return _context.stop();
102
152
  }
103
- }, _callee);
153
+ }, _callee, null, [[4, 10]]);
104
154
  }));
105
- return function getAndCacheRemotePreview(_x, _x2, _x3, _x4, _x5, _x6) {
155
+ return function enrichAttrsWithClientId(_x, _x2, _x3, _x4) {
106
156
  return _ref.apply(this, arguments);
107
157
  };
108
158
  }();
109
- var getAndCacheLocalPreview = exports.getAndCacheLocalPreview = /*#__PURE__*/function () {
110
- var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(id, filePreview, dimensions, mode, mediaBlobUrlAttrs) {
111
- var localPreview;
159
+ var getAndCacheRemotePreview = exports.getAndCacheRemotePreview = /*#__PURE__*/function () {
160
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(mediaClient, id, dimensions, params, mediaBlobUrlAttrs, traceContext) {
161
+ var _yield$Promise$all, _yield$Promise$all2, remotePreview, enrichedAttrs;
112
162
  return _regenerator.default.wrap(function _callee2$(_context2) {
113
163
  while (1) switch (_context2.prev = _context2.next) {
114
164
  case 0:
115
165
  _context2.next = 2;
116
- return (0, _helpers.getLocalPreview)(filePreview);
166
+ return Promise.all([(0, _helpers.getRemotePreview)(mediaClient, id, params, traceContext), enrichAttrsWithClientId(mediaClient, id, mediaBlobUrlAttrs, params.collection)]);
117
167
  case 2:
118
- localPreview = _context2.sent;
119
- return _context2.abrupt("return", extendAndCachePreview(id, mode, _objectSpread(_objectSpread({}, localPreview), {}, {
168
+ _yield$Promise$all = _context2.sent;
169
+ _yield$Promise$all2 = (0, _slicedToArray2.default)(_yield$Promise$all, 2);
170
+ remotePreview = _yield$Promise$all2[0];
171
+ enrichedAttrs = _yield$Promise$all2[1];
172
+ return _context2.abrupt("return", extendAndCachePreview(id, params.mode, _objectSpread(_objectSpread({}, remotePreview), {}, {
120
173
  dimensions: dimensions
121
- }), mediaBlobUrlAttrs));
122
- case 4:
174
+ }), enrichedAttrs));
175
+ case 7:
123
176
  case "end":
124
177
  return _context2.stop();
125
178
  }
126
179
  }, _callee2);
127
180
  }));
128
- return function getAndCacheLocalPreview(_x7, _x8, _x9, _x0, _x1) {
181
+ return function getAndCacheRemotePreview(_x5, _x6, _x7, _x8, _x9, _x0) {
129
182
  return _ref2.apply(this, arguments);
130
183
  };
184
+ }();
185
+ var getAndCacheLocalPreview = exports.getAndCacheLocalPreview = /*#__PURE__*/function () {
186
+ var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(mediaClient, id, filePreview, dimensions, mode, mediaBlobUrlAttrs, collectionName) {
187
+ var _yield$Promise$all3, _yield$Promise$all4, localPreview, enrichedAttrs;
188
+ return _regenerator.default.wrap(function _callee3$(_context3) {
189
+ while (1) switch (_context3.prev = _context3.next) {
190
+ case 0:
191
+ _context3.next = 2;
192
+ return Promise.all([(0, _helpers.getLocalPreview)(filePreview), enrichAttrsWithClientId(mediaClient, id, mediaBlobUrlAttrs, collectionName)]);
193
+ case 2:
194
+ _yield$Promise$all3 = _context3.sent;
195
+ _yield$Promise$all4 = (0, _slicedToArray2.default)(_yield$Promise$all3, 2);
196
+ localPreview = _yield$Promise$all4[0];
197
+ enrichedAttrs = _yield$Promise$all4[1];
198
+ return _context3.abrupt("return", extendAndCachePreview(id, mode, _objectSpread(_objectSpread({}, localPreview), {}, {
199
+ dimensions: dimensions
200
+ }), enrichedAttrs));
201
+ case 7:
202
+ case "end":
203
+ return _context3.stop();
204
+ }
205
+ }, _callee3);
206
+ }));
207
+ return function getAndCacheLocalPreview(_x1, _x10, _x11, _x12, _x13, _x14, _x15) {
208
+ return _ref3.apply(this, arguments);
209
+ };
131
210
  }();
@@ -5,52 +5,103 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.createObjectURLCache = exports.PREVIEW_CACHE_LRU_SIZE = exports.ObjectURLCache = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
8
9
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
10
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
11
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
11
12
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
12
13
  var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));
13
14
  var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
14
- var _eventemitter = require("eventemitter2");
15
15
  var _lru_map = require("lru_map");
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
16
17
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
17
18
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
18
19
  function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
19
20
  var PREVIEW_CACHE_LRU_SIZE = exports.PREVIEW_CACHE_LRU_SIZE = 50;
20
- var ExtendedLRUCache = /*#__PURE__*/function (_LRUMap) {
21
- function ExtendedLRUCache(limit) {
21
+ /**
22
+ * LRU cache that checks whether the oldest entry should be evicted
23
+ * this means that the cache can grow past the softLimit if `shouldEvict` returns false.
24
+ *
25
+ * When `platform_media_safe_blob_url_eviction` is ON the oldest
26
+ * entry is kept if `shouldEvict` returns false.
27
+ * When the flag is OFF the oldest entry is always evicted.
28
+ */
29
+ var EvictionLRUCache = /*#__PURE__*/function (_LRUMap) {
30
+ function EvictionLRUCache(softLimit, options) {
22
31
  var _this;
23
- (0, _classCallCheck2.default)(this, ExtendedLRUCache);
24
- _this = _callSuper(this, ExtendedLRUCache, [limit]);
25
- _this.eventEmitter = new _eventemitter.EventEmitter2();
32
+ (0, _classCallCheck2.default)(this, EvictionLRUCache);
33
+ _this = _callSuper(this, EvictionLRUCache, [softLimit]);
34
+ _this.shouldEvict = options === null || options === void 0 ? void 0 : options.shouldEvict;
35
+ _this.onEvict = options === null || options === void 0 ? void 0 : options.onEvict;
26
36
  return _this;
27
37
  }
28
- (0, _inherits2.default)(ExtendedLRUCache, _LRUMap);
29
- return (0, _createClass2.default)(ExtendedLRUCache, [{
38
+ (0, _inherits2.default)(EvictionLRUCache, _LRUMap);
39
+ return (0, _createClass2.default)(EvictionLRUCache, [{
30
40
  key: "shift",
31
41
  value: function shift() {
32
- var entry = _superPropGet(ExtendedLRUCache, "shift", this, 3)([]);
33
- this.eventEmitter.emit('shift', entry);
42
+ if ((0, _platformFeatureFlags.fg)('platform_media_safe_blob_url_eviction')) {
43
+ var oldest = this.oldest;
44
+ if (oldest && this.shouldEvict && !this.shouldEvict([oldest.key, oldest.value])) {
45
+ return undefined;
46
+ }
47
+ }
48
+ var entry = _superPropGet(EvictionLRUCache, "shift", this, 3)([]);
49
+ if (entry && this.onEvict) {
50
+ this.onEvict(entry);
51
+ }
34
52
  return entry;
35
53
  }
36
- }, {
37
- key: "on",
38
- value: function on(event, callback) {
39
- this.eventEmitter.on(event, callback);
40
- }
41
54
  }]);
42
55
  }(_lru_map.LRUMap);
43
56
  var ObjectURLCache = exports.ObjectURLCache = /*#__PURE__*/function () {
44
57
  function ObjectURLCache(size) {
58
+ var _this2 = this;
45
59
  (0, _classCallCheck2.default)(this, ObjectURLCache);
46
- this.cache = new ExtendedLRUCache(size);
47
- this.cache.on('shift', function (entry) {
48
- if (entry && entry[1].dataURI) {
49
- URL.revokeObjectURL(entry[1].dataURI);
60
+ (0, _defineProperty2.default)(this, "activeRefs", new Map());
61
+ this.cache = new EvictionLRUCache(size, {
62
+ shouldEvict: function shouldEvict(entry) {
63
+ return !_this2.isInUse(entry[0]);
64
+ },
65
+ onEvict: function onEvict(entry) {
66
+ if (entry[1].dataURI) {
67
+ var dataURI = (0, _platformFeatureFlags.fg)('platform_media_safe_blob_url_eviction') ? entry[1].dataURI.split('#')[0] : entry[1].dataURI;
68
+ if (dataURI) {
69
+ URL.revokeObjectURL(dataURI);
70
+ }
71
+ }
50
72
  }
51
73
  });
52
74
  }
75
+
76
+ /**
77
+ * Marks a cache key as actively in use by a consumer.
78
+ * Multiple consumers can acquire the same key; eviction is
79
+ * blocked until all consumers have released it.
80
+ */
53
81
  return (0, _createClass2.default)(ObjectURLCache, [{
82
+ key: "acquire",
83
+ value: function acquire(key) {
84
+ var _this$activeRefs$get;
85
+ this.activeRefs.set(key, ((_this$activeRefs$get = this.activeRefs.get(key)) !== null && _this$activeRefs$get !== void 0 ? _this$activeRefs$get : 0) + 1);
86
+ }
87
+ }, {
88
+ key: "release",
89
+ value: function release(key) {
90
+ var _this$activeRefs$get2;
91
+ var count = (_this$activeRefs$get2 = this.activeRefs.get(key)) !== null && _this$activeRefs$get2 !== void 0 ? _this$activeRefs$get2 : 0;
92
+ if (count <= 1) {
93
+ this.activeRefs.delete(key);
94
+ } else {
95
+ this.activeRefs.set(key, count - 1);
96
+ }
97
+ }
98
+ }, {
99
+ key: "isInUse",
100
+ value: function isInUse(key) {
101
+ var _this$activeRefs$get3;
102
+ return ((_this$activeRefs$get3 = this.activeRefs.get(key)) !== null && _this$activeRefs$get3 !== void 0 ? _this$activeRefs$get3 : 0) > 0;
103
+ }
104
+ }, {
54
105
  key: "has",
55
106
  value: function has(key) {
56
107
  return !!this.cache.find(key);
@@ -69,7 +120,9 @@ var ObjectURLCache = exports.ObjectURLCache = /*#__PURE__*/function () {
69
120
  key: "remove",
70
121
  value: function remove(key) {
71
122
  var removed = this.cache.delete(key);
72
- removed && URL.revokeObjectURL(removed.dataURI);
123
+ this.activeRefs.delete(key);
124
+ var dataURI = (0, _platformFeatureFlags.fg)('platform_media_safe_blob_url_eviction') ? removed === null || removed === void 0 ? void 0 : removed.dataURI.split('#')[0] : removed === null || removed === void 0 ? void 0 : removed.dataURI;
125
+ dataURI && URL.revokeObjectURL(dataURI);
73
126
  }
74
127
  }, {
75
128
  key: "clear",
@@ -53,54 +53,20 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
53
53
  setStatus = _useState2[1];
54
54
  var _useState3 = (0, _react.useState)(),
55
55
  _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
56
- clientId = _useState4[0],
57
- setClientId = _useState4[1];
58
-
59
- // Fetch clientId on mount for cross-client copy
60
- (0, _react.useEffect)(function () {
61
- if ((0, _platformFeatureFlags.fg)('platform_media_cross_client_copy_with_auth')) {
62
- mediaClient.getClientId(identifier.collectionName).then(setClientId).catch(function () {
63
- // ClientId is optional, silently fail
64
- });
65
- }
66
- }, [mediaClient, identifier.collectionName]);
67
-
68
- // Merge clientId into mediaBlobUrlAttrs for embedding in blob URLs
69
- // If mediaBlobUrlAttrs is not provided, construct minimal attrs from identifier
70
- var mediaBlobUrlAttrsWithClientId = (0, _react.useMemo)(function () {
71
- if (!(0, _platformFeatureFlags.fg)('platform_media_cross_client_copy_with_auth') || !clientId) {
72
- return mediaBlobUrlAttrs;
73
- }
74
- if (mediaBlobUrlAttrs) {
75
- return _objectSpread(_objectSpread({}, mediaBlobUrlAttrs), {}, {
76
- clientId: clientId
77
- });
78
- }
79
-
80
- // Construct minimal attrs when none provided (e.g., MediaImage)
81
- return {
82
- id: identifier.id,
83
- clientId: clientId,
84
- contextId: identifier.collectionName || '',
85
- collection: identifier.collectionName
86
- };
87
- }, [mediaBlobUrlAttrs, clientId, identifier.id, identifier.collectionName]);
56
+ error = _useState4[0],
57
+ setError = _useState4[1];
88
58
  var _useState5 = (0, _react.useState)(),
89
59
  _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
90
- error = _useState6[0],
91
- setError = _useState6[1];
92
- var _useState7 = (0, _react.useState)(),
60
+ nonCriticalError = _useState6[0],
61
+ setNonCriticalError = _useState6[1];
62
+ var _useState7 = (0, _react.useState)(false),
93
63
  _useState8 = (0, _slicedToArray2.default)(_useState7, 2),
94
- nonCriticalError = _useState8[0],
95
- setNonCriticalError = _useState8[1];
96
- var _useState9 = (0, _react.useState)(false),
64
+ isBannedLocalPreview = _useState8[0],
65
+ setIsBannedLocalPreview = _useState8[1];
66
+ var _useState9 = (0, _react.useState)('not-resolved'),
97
67
  _useState0 = (0, _slicedToArray2.default)(_useState9, 2),
98
- isBannedLocalPreview = _useState0[0],
99
- setIsBannedLocalPreview = _useState0[1];
100
- var _useState1 = (0, _react.useState)('not-resolved'),
101
- _useState10 = (0, _slicedToArray2.default)(_useState1, 2),
102
- upfrontPreviewStatus = _useState10[0],
103
- setUpfrontPreviewStatus = _useState10[1];
68
+ upfrontPreviewStatus = _useState0[0],
69
+ setUpfrontPreviewStatus = _useState0[1];
104
70
  var ssrReliabilityRef = (0, _react.useRef)({
105
71
  server: {
106
72
  status: 'unknown'
@@ -110,10 +76,10 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
110
76
  }
111
77
  });
112
78
  var ufoContext = (0, _interactionContext.useInteractionContext)();
113
- var _useState11 = (0, _react.useState)(false),
114
- _useState12 = (0, _slicedToArray2.default)(_useState11, 2),
115
- isLoading = _useState12[0],
116
- setIsLoading = _useState12[1];
79
+ var _useState1 = (0, _react.useState)(false),
80
+ _useState10 = (0, _slicedToArray2.default)(_useState1, 2),
81
+ isLoading = _useState10[0],
82
+ setIsLoading = _useState10[1];
117
83
  (0, _react.useLayoutEffect)(function () {
118
84
  if (isLoading && (0, _platformFeatureFlags.fg)('platform_close_image_blindspot_2')) {
119
85
  return ufoContext === null || ufoContext === void 0 ? void 0 : ufoContext.hold('img-loading');
@@ -156,7 +122,7 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
156
122
  // where no SSR occurred, so we should skip SSR preview generation entirely.
157
123
  if (ssr === 'server' || ssrData) {
158
124
  try {
159
- return (0, _getPreview.getSSRPreview)(ssr, mediaClient, identifier.id, imageURLParams, mediaBlobUrlAttrsWithClientId);
125
+ return (0, _getPreview.getSSRPreview)(ssr, mediaClient, identifier.id, imageURLParams, mediaBlobUrlAttrs);
160
126
  } catch (e) {
161
127
  ssrReliabilityRef.current = _objectSpread(_objectSpread({}, ssrReliabilityRef.current), {}, (0, _defineProperty2.default)({}, ssr, _objectSpread({
162
128
  status: 'fail'
@@ -178,10 +144,10 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
178
144
  }
179
145
  }
180
146
  };
181
- var _useState13 = (0, _react.useState)(previewInitializer),
182
- _useState14 = (0, _slicedToArray2.default)(_useState13, 2),
183
- preview = _useState14[0],
184
- setPreview = _useState14[1];
147
+ var _useState11 = (0, _react.useState)(previewInitializer),
148
+ _useState12 = (0, _slicedToArray2.default)(_useState11, 2),
149
+ preview = _useState12[0],
150
+ setPreview = _useState12[1];
185
151
 
186
152
  //----------------------------------------------------------------
187
153
  // FILE STATE
@@ -248,29 +214,11 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
248
214
  }
249
215
  }, [preview, identifier, resizeMode]);
250
216
 
251
- //----------------------------------------------------------------
252
- // Update preview with clientId when it becomes available
253
- //----------------------------------------------------------------
254
- var previewUpdatedWithClientIdRef = (0, _react.useRef)(null);
255
- (0, _react.useEffect)(function () {
256
- // Only update if we have a preview, clientId is available, URL doesn't already have clientId, and feature flag is enabled.
257
- // Also skip if we've already updated this preview (prevents re-render loops)
258
- if (preview && clientId && mediaBlobUrlAttrsWithClientId && !preview.dataURI.includes('clientId=') && previewUpdatedWithClientIdRef.current !== identifier.id && (0, _platformFeatureFlags.fg)('platform_media_cross_client_copy_with_auth')) {
259
- // Mark this preview as updated
260
- previewUpdatedWithClientIdRef.current = identifier.id;
261
- var baseUrl = preview.dataURI.split('#')[0]; // Remove any existing hash
262
- var updatedDataURI = (0, _mediaClient.addFileAttrsToUrl)(baseUrl, mediaBlobUrlAttrsWithClientId);
263
- setPreview(_objectSpread(_objectSpread({}, preview), {}, {
264
- dataURI: updatedDataURI
265
- }));
266
- }
267
- }, [clientId, mediaBlobUrlAttrsWithClientId, preview, identifier.id]);
268
-
269
217
  //----------------------------------------------------------------
270
218
  // Preview Fetch Helper
271
219
  //----------------------------------------------------------------
272
220
  var getAndCacheRemotePreviewRef = (0, _helpers.useCurrentValueRef)(function () {
273
- return (0, _getPreview.getAndCacheRemotePreview)(mediaClient, identifier.id, requestDimensions || {}, imageURLParams, mediaBlobUrlAttrsWithClientId, traceContext);
221
+ return (0, _getPreview.getAndCacheRemotePreview)(mediaClient, identifier.id, requestDimensions || {}, imageURLParams, mediaBlobUrlAttrs, traceContext);
274
222
  });
275
223
 
276
224
  //----------------------------------------------------------------
@@ -305,7 +253,7 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
305
253
  // Cache, Local & Remote Preview
306
254
  //----------------------------------------------------------------
307
255
 
308
- var mediaBlobUrlAttrsRef = (0, _helpers.useCurrentValueRef)(mediaBlobUrlAttrsWithClientId);
256
+ var mediaBlobUrlAttrsRef = (0, _helpers.useCurrentValueRef)(mediaBlobUrlAttrs);
309
257
  (0, _react.useEffect)(function () {
310
258
  var cachedPreview = _getPreview.mediaFilePreviewCache.get(identifier.id, resizeMode);
311
259
 
@@ -319,7 +267,7 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
319
267
  // For example, SVGs are mime type NOT supported by browser but media type supported by Media Card (image)
320
268
  // Then, local Preview NOT available
321
269
  setIsLoading(true);
322
- (0, _getPreview.getAndCacheLocalPreview)(identifier.id, localBinary, requestDimensions || {}, resizeMode, mediaBlobUrlAttrsRef.current).then(setPreview).catch(function (e) {
270
+ (0, _getPreview.getAndCacheLocalPreview)(mediaClient, identifier.id, localBinary, requestDimensions || {}, resizeMode, mediaBlobUrlAttrsRef.current, identifier.collectionName).then(setPreview).catch(function (e) {
323
271
  setIsBannedLocalPreview(true);
324
272
  // CXP-2723 TODO: We might have to wrap this error in MediaCardError
325
273
  setNonCriticalError(e);
@@ -350,7 +298,19 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
350
298
  setIsLoading(false);
351
299
  });
352
300
  }
353
- }, [error, nonCriticalError, getAndCacheRemotePreviewRef, identifier.id, resizeMode, isBannedLocalPreview, mediaBlobUrlAttrsRef, preview, requestDimensions, skipRemote, isBackendPreviewReady, localBinary, mediaType, mimeType, upfrontPreviewStatus, dimensions]);
301
+ }, [error, nonCriticalError, getAndCacheRemotePreviewRef, identifier.id, identifier.collectionName, resizeMode, isBannedLocalPreview, mediaBlobUrlAttrsRef, mediaClient, preview, requestDimensions, skipRemote, isBackendPreviewReady, localBinary, mediaType, mimeType, upfrontPreviewStatus, dimensions]);
302
+
303
+ //----------------------------------------------------------------
304
+ // Cache ref tracking — prevent blob URL eviction while mounted
305
+ //----------------------------------------------------------------
306
+ (0, _react.useEffect)(function () {
307
+ if (preview) {
308
+ _getPreview.mediaFilePreviewCache.acquire(identifier.id, resizeMode);
309
+ return function () {
310
+ _getPreview.mediaFilePreviewCache.release(identifier.id, resizeMode);
311
+ };
312
+ }
313
+ }, [preview, identifier.id, resizeMode]);
354
314
 
355
315
  //----------------------------------------------------------------
356
316
  // RETURN
@@ -467,7 +427,6 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
467
427
  onImageError: onImageError,
468
428
  onImageLoad: onImageLoad,
469
429
  getSsrScriptProps: getSsrScriptProps,
470
- copyNodeRef: copyNodeRef,
471
- clientId: clientId
430
+ copyNodeRef: copyNodeRef
472
431
  };
473
432
  };
@@ -25,6 +25,14 @@ export class CardPreviewCacheImpl {
25
25
  _defineProperty(this, "clear", () => {
26
26
  this.previewCache.clear();
27
27
  });
28
+ _defineProperty(this, "acquire", (id, mode) => {
29
+ const cacheKey = getCacheKey(id, mode);
30
+ this.previewCache.acquire(cacheKey);
31
+ });
32
+ _defineProperty(this, "release", (id, mode) => {
33
+ const cacheKey = getCacheKey(id, mode);
34
+ this.previewCache.release(cacheKey);
35
+ });
28
36
  this.previewCache = previewCache;
29
37
  }
30
38
  }
@@ -1,4 +1,5 @@
1
1
  import { addFileAttrsToUrl } from '@atlaskit/media-client';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
2
3
  import { SsrPreviewError } from '../errors';
3
4
  import { mediaFilePreviewCache } from './cache';
4
5
  import { getLocalPreview, getRemotePreview } from './helpers';
@@ -31,16 +32,43 @@ const getDataUri = (mediaClient, id, params, mediaBlobUrlAttrs) => {
31
32
  const rawDataURI = mediaClient.getImageUrlSync(id, params);
32
33
  return mediaBlobUrlAttrs ? addFileAttrsToUrl(rawDataURI, mediaBlobUrlAttrs) : rawDataURI;
33
34
  };
35
+
36
+ /**
37
+ * Merges a clientId into mediaBlobUrlAttrs for cross-client copy support.
38
+ * Returns the original attrs unchanged if clientId is not available or the feature flag is off.
39
+ */
40
+ const mergeClientIdIntoAttrs = (clientId, id, mediaBlobUrlAttrs, collectionName) => {
41
+ if (!clientId) {
42
+ return mediaBlobUrlAttrs;
43
+ }
44
+ if (mediaBlobUrlAttrs) {
45
+ return {
46
+ ...mediaBlobUrlAttrs,
47
+ clientId
48
+ };
49
+ }
50
+
51
+ // Construct minimal attrs when none provided
52
+ return {
53
+ id,
54
+ clientId,
55
+ contextId: collectionName || '',
56
+ collection: collectionName
57
+ };
58
+ };
34
59
  export const getSSRPreview = (ssr, mediaClient, id, params, mediaBlobUrlAttrs) => {
35
60
  try {
36
- const dataURI = getDataUri(mediaClient, id, params, mediaBlobUrlAttrs);
61
+ // Synchronously extract clientId from initialAuth and merge into blob URL attrs
62
+ const clientId = fg('platform_media_cross_client_copy_with_auth') ? mediaClient.getClientIdSync() : undefined;
63
+ const attrsWithClientId = mergeClientIdIntoAttrs(clientId, id, mediaBlobUrlAttrs, params.collection);
64
+ const dataURI = getDataUri(mediaClient, id, params, attrsWithClientId);
37
65
  let srcSet = `${dataURI} 1x`;
38
66
  if (params.width) {
39
67
  const doubleDataURI = getDataUri(mediaClient, id, {
40
68
  ...params,
41
69
  width: params.width * 2,
42
70
  height: params.height && params.height * 2
43
- }, mediaBlobUrlAttrs);
71
+ }, attrsWithClientId);
44
72
  // We want to embed some meta context into dataURI for Copy/Paste to work.
45
73
  srcSet += `, ${doubleDataURI} 2x`;
46
74
  }
@@ -70,17 +98,38 @@ export const isSSRPreview = preview => {
70
98
  const ssrClientSources = ['ssr-client', 'ssr-server', 'ssr-data'];
71
99
  return ssrClientSources.includes(preview.source);
72
100
  };
101
+
102
+ /**
103
+ * Resolves clientId (sync first, async fallback) and enriches mediaBlobUrlAttrs
104
+ * with it for cross-client copy support.
105
+ */
106
+ const enrichAttrsWithClientId = async (mediaClient, id, mediaBlobUrlAttrs, collectionName) => {
107
+ if (!fg('platform_media_cross_client_copy_with_auth')) {
108
+ return mediaBlobUrlAttrs;
109
+ }
110
+
111
+ // Try sync first, then async fallback
112
+ let clientId = mediaClient.getClientIdSync();
113
+ if (!clientId) {
114
+ try {
115
+ clientId = await mediaClient.getClientId(collectionName);
116
+ } catch {
117
+ // clientId is optional, silently fail
118
+ }
119
+ }
120
+ return mergeClientIdIntoAttrs(clientId, id, mediaBlobUrlAttrs, collectionName);
121
+ };
73
122
  export const getAndCacheRemotePreview = async (mediaClient, id, dimensions, params, mediaBlobUrlAttrs, traceContext) => {
74
- const remotePreview = await getRemotePreview(mediaClient, id, params, traceContext);
123
+ const [remotePreview, enrichedAttrs] = await Promise.all([getRemotePreview(mediaClient, id, params, traceContext), enrichAttrsWithClientId(mediaClient, id, mediaBlobUrlAttrs, params.collection)]);
75
124
  return extendAndCachePreview(id, params.mode, {
76
125
  ...remotePreview,
77
126
  dimensions
78
- }, mediaBlobUrlAttrs);
127
+ }, enrichedAttrs);
79
128
  };
80
- export const getAndCacheLocalPreview = async (id, filePreview, dimensions, mode, mediaBlobUrlAttrs) => {
81
- const localPreview = await getLocalPreview(filePreview);
129
+ export const getAndCacheLocalPreview = async (mediaClient, id, filePreview, dimensions, mode, mediaBlobUrlAttrs, collectionName) => {
130
+ const [localPreview, enrichedAttrs] = await Promise.all([getLocalPreview(filePreview), enrichAttrsWithClientId(mediaClient, id, mediaBlobUrlAttrs, collectionName)]);
82
131
  return extendAndCachePreview(id, mode, {
83
132
  ...localPreview,
84
133
  dimensions
85
- }, mediaBlobUrlAttrs);
134
+ }, enrichedAttrs);
86
135
  };