@atlaskit/editor-card-provider 6.4.8 → 6.4.9

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,14 @@
1
1
  # @atlaskit/editor-card-provider
2
2
 
3
+ ## 6.4.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [`50a2e34ed2f69`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/50a2e34ed2f69) -
8
+ Optimize SmartCardLocalCacheClient: in-memory caching, async writes, singleton pattern, and write
9
+ batching.
10
+ - Updated dependencies
11
+
3
12
  ## 6.4.8
4
13
 
5
14
  ### Patch Changes
@@ -141,7 +141,7 @@ var EditorCardProvider = exports.EditorCardProvider = /*#__PURE__*/function (_re
141
141
  var _this;
142
142
  (0, _classCallCheck2.default)(this, EditorCardProvider);
143
143
  _this = _callSuper(this, EditorCardProvider);
144
- (0, _defineProperty2.default)(_this, "smartCardLocalCacheClient", new _smartCardLocalCacheClient.SmartCardLocalCacheClient());
144
+ (0, _defineProperty2.default)(_this, "smartCardLocalCacheClient", _smartCardLocalCacheClient.SmartCardLocalCacheClient.getInstance());
145
145
  _this.name = 'editorCardProvider';
146
146
  _this.baseUrl = baseUrlOverride || (0, _linkingCommon.getBaseUrl)(envKey);
147
147
  _this.resolverUrl = (0, _linkingCommon.getResolverUrl)(envKey, baseUrlOverride);
@@ -5,82 +5,131 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.SmartCardLocalCacheClient = void 0;
8
- var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
9
8
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _storageClient = require("@atlaskit/frontend-utilities/storage-client");
12
12
  var SMART_CARD_CACHE_CLIENT_KEY = 'smart-card-url-data';
13
13
  var SMART_CARD_CACHE_KEY = 'response-cache';
14
14
  var SMART_CARD_CACHE_DEFAULT_MAX_ITEMS = 100;
15
- var SmartCardLocalCacheClient = exports.SmartCardLocalCacheClient = /*#__PURE__*/(0, _createClass2.default)(function SmartCardLocalCacheClient() {
16
- var _this = this;
17
- var maxItems = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SMART_CARD_CACHE_DEFAULT_MAX_ITEMS;
18
- (0, _classCallCheck2.default)(this, SmartCardLocalCacheClient);
19
- (0, _defineProperty2.default)(this, "storageClient", new _storageClient.StorageClient(SMART_CARD_CACHE_CLIENT_KEY, {
20
- storageEngine: 'sessionStorage'
21
- }));
22
- /**
23
- * Retrieves the entire cache object from storage
24
- * @returns The entire cache object
25
- */
26
- (0, _defineProperty2.default)(this, "getCache", function () {
27
- var stored = _this.storageClient.getItem(SMART_CARD_CACHE_KEY, {
28
- useExpiredItem: true
15
+ /**
16
+ * Singleton cache client for smart link responses. Uses sessionStorage for per-tab isolation (no cross-tab conflicts).
17
+ * If switching to localStorage for cross-tab sharing, add a 'storage' event listener in the constructor to sync
18
+ * memoryCache when other tabs modify the cache, preventing race conditions from concurrent writes.
19
+ */
20
+ var SmartCardLocalCacheClient = exports.SmartCardLocalCacheClient = /*#__PURE__*/function () {
21
+ function SmartCardLocalCacheClient() {
22
+ var _this = this;
23
+ var maxItems = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SMART_CARD_CACHE_DEFAULT_MAX_ITEMS;
24
+ (0, _classCallCheck2.default)(this, SmartCardLocalCacheClient);
25
+ (0, _defineProperty2.default)(this, "storageClient", new _storageClient.StorageClient(SMART_CARD_CACHE_CLIENT_KEY, {
26
+ storageEngine: 'sessionStorage'
27
+ }));
28
+ (0, _defineProperty2.default)(this, "pendingWrite", false);
29
+ /**
30
+ * Retrieves the entire cache object from memory
31
+ * @returns The entire cache object
32
+ */
33
+ (0, _defineProperty2.default)(this, "getCache", function () {
34
+ return _this.memoryCache;
29
35
  });
30
- var persistentCache = stored ? JSON.parse(stored) : {};
31
- return persistentCache;
32
- });
33
- /**
34
- * Caches the SmartLinkResponse against the provided url in storage
35
- * @param url The url (key) to store the response against
36
- * @param response The SmartLinkResponse to be cached in storage
37
- */
38
- (0, _defineProperty2.default)(this, "setItem", function (url, response) {
39
- var newCache = _this.getCache();
36
+ /**
37
+ * Caches the SmartLinkResponse against the provided url in storage
38
+ * @param url The url (key) to store the response against
39
+ * @param response The SmartLinkResponse to be cached in storage
40
+ */
41
+ (0, _defineProperty2.default)(this, "setItem", function (url, response) {
42
+ // Add or update the item in the cache with current timestamp
43
+ _this.memoryCache[url] = {
44
+ data: response,
45
+ timestamp: Date.now()
46
+ };
40
47
 
41
- // Add or update the item in the cache with current timestamp
42
- newCache[url] = {
43
- data: response,
44
- timestamp: Date.now()
45
- };
48
+ // Apply FIFO behavior if cache exceeds maxItems
49
+ var cacheKeys = Object.keys(_this.memoryCache);
50
+ if (cacheKeys.length > _this.maxItems) {
51
+ // Sort keys by timestamp (oldest first) and remove the oldest items
52
+ var sortedKeys = cacheKeys.sort(function (a, b) {
53
+ return _this.memoryCache[a].timestamp - _this.memoryCache[b].timestamp;
54
+ });
55
+ var itemsToRemove = cacheKeys.length - _this.maxItems;
56
+ var keysToRemove = sortedKeys.slice(0, itemsToRemove);
57
+ keysToRemove.forEach(function (key) {
58
+ delete _this.memoryCache[key];
59
+ });
60
+ }
61
+
62
+ // Write through to storage asynchronously (non-blocking)
63
+ // Debounced to batch multiple rapid calls into a single write
64
+ if (!_this.pendingWrite) {
65
+ _this.pendingWrite = true;
66
+ queueMicrotask(function () {
67
+ try {
68
+ _this.storageClient.setItemWithExpiry(SMART_CARD_CACHE_KEY, JSON.stringify(_this.memoryCache));
69
+ } catch (_) {
70
+ // Ignore storage write errors (e.g. quota exceeded) to prevent blocking future writes
71
+ // Cache will still function in-memory for the session, but won't persist
72
+ } finally {
73
+ _this.pendingWrite = false;
74
+ }
75
+ });
76
+ }
77
+ });
78
+ /**
79
+ * Retrieves the cached SmartLinkResponse for the provided url from storage (if it exists)
80
+ * @param url The url (key) to retrieve the cached response for
81
+ * @returns The cached SmartLinkResponse if it exists, else undefined
82
+ */
83
+ (0, _defineProperty2.default)(this, "getItem", function (url) {
84
+ var _this$memoryCache;
85
+ if (!url) {
86
+ return;
87
+ }
88
+ var response = (_this$memoryCache = _this.memoryCache) === null || _this$memoryCache === void 0 || (_this$memoryCache = _this$memoryCache[url]) === null || _this$memoryCache === void 0 ? void 0 : _this$memoryCache.data;
89
+ return response;
90
+ });
91
+ /**
92
+ * Checks if a cached entry exists for the provided url in storage
93
+ * @param url The url (key) to check for existence in the cache
94
+ * @returns True if a cached entry exists, else false
95
+ */
96
+ (0, _defineProperty2.default)(this, "isUrlInCache", function (url) {
97
+ return url in _this.memoryCache;
98
+ });
99
+ this.maxItems = maxItems;
100
+ // Load cache from storage into memory on instantiation
101
+ var stored = this.storageClient.getItem(SMART_CARD_CACHE_KEY, {
102
+ useExpiredItem: true
103
+ });
104
+ this.memoryCache = stored ? JSON.parse(stored) : {};
105
+ }
46
106
 
47
- // Apply FIFO behavior if cache exceeds maxItems
48
- var cacheKeys = Object.keys(newCache);
49
- if (cacheKeys.length > _this.maxItems) {
50
- // Sort keys by timestamp (oldest first) and remove the oldest items
51
- var sortedKeys = cacheKeys.sort(function (a, b) {
52
- return newCache[a].timestamp - newCache[b].timestamp;
53
- });
54
- var itemsToRemove = cacheKeys.length - _this.maxItems;
55
- var keysToRemove = sortedKeys.slice(0, itemsToRemove);
56
- keysToRemove.forEach(function (key) {
57
- delete newCache[key];
58
- });
59
- }
60
- _this.storageClient.setItemWithExpiry(SMART_CARD_CACHE_KEY, JSON.stringify(newCache));
61
- });
62
107
  /**
63
- * Retrieves the cached SmartLinkResponse for the provided url from storage (if it exists)
64
- * @param url The url (key) to retrieve the cached response for
65
- * @returns The cached SmartLinkResponse if it exists, else undefined
108
+ * Gets the singleton instance of SmartCardLocalCacheClient.
109
+ * This ensures all callers share the same in-memory cache and prevents
110
+ * race conditions when multiple instances write to the same storage key.
111
+ * @param maxItems Maximum number of items to store in the cache (only used on first instantiation)
112
+ * @returns The singleton instance
66
113
  */
67
- (0, _defineProperty2.default)(this, "getItem", function (url) {
68
- var _persistentCache$url;
69
- if (!url) {
70
- return;
114
+ return (0, _createClass2.default)(SmartCardLocalCacheClient, null, [{
115
+ key: "getInstance",
116
+ value: function getInstance() {
117
+ var maxItems = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SMART_CARD_CACHE_DEFAULT_MAX_ITEMS;
118
+ if (!SmartCardLocalCacheClient.instance) {
119
+ SmartCardLocalCacheClient.instance = new SmartCardLocalCacheClient(maxItems);
120
+ }
121
+ return SmartCardLocalCacheClient.instance;
71
122
  }
72
- var persistentCache = _this.getCache();
73
- var response = persistentCache === null || persistentCache === void 0 || (_persistentCache$url = persistentCache[url]) === null || _persistentCache$url === void 0 ? void 0 : _persistentCache$url.data;
74
- return response;
75
- });
76
- /**
77
- * Checks if a cached entry exists for the provided url in storage
78
- * @param url The url (key) to check for existence in the cache
79
- * @returns True if a cached entry exists, else false
80
- */
81
- (0, _defineProperty2.default)(this, "isUrlInCache", function (url) {
82
- var persistentCache = _this.getCache();
83
- return persistentCache ? url in persistentCache : false;
84
- });
85
- this.maxItems = maxItems;
86
- });
123
+
124
+ /**
125
+ * Resets the singleton instance. Only intended for testing purposes.
126
+ * @internal
127
+ */
128
+ }, {
129
+ key: "resetInstance",
130
+ value: function resetInstance() {
131
+ SmartCardLocalCacheClient.instance = null;
132
+ }
133
+ }]);
134
+ }();
135
+ (0, _defineProperty2.default)(SmartCardLocalCacheClient, "instance", null);
@@ -72,7 +72,7 @@ export const isJiraWorkItem = url => /\/browse\/((?:\w+)-(?:\d+))/i.test(url);
72
72
  export class EditorCardProvider extends NodeDataProvider {
73
73
  constructor(envKey, baseUrlOverride, product, onResolve, customCardClient) {
74
74
  super();
75
- _defineProperty(this, "smartCardLocalCacheClient", new SmartCardLocalCacheClient());
75
+ _defineProperty(this, "smartCardLocalCacheClient", SmartCardLocalCacheClient.getInstance());
76
76
  this.name = 'editorCardProvider';
77
77
  this.baseUrl = baseUrlOverride || getBaseUrl(envKey);
78
78
  this.resolverUrl = getResolverUrl(envKey, baseUrlOverride);
@@ -3,21 +3,23 @@ import { StorageClient } from '@atlaskit/frontend-utilities/storage-client';
3
3
  const SMART_CARD_CACHE_CLIENT_KEY = 'smart-card-url-data';
4
4
  const SMART_CARD_CACHE_KEY = 'response-cache';
5
5
  const SMART_CARD_CACHE_DEFAULT_MAX_ITEMS = 100;
6
+ /**
7
+ * Singleton cache client for smart link responses. Uses sessionStorage for per-tab isolation (no cross-tab conflicts).
8
+ * If switching to localStorage for cross-tab sharing, add a 'storage' event listener in the constructor to sync
9
+ * memoryCache when other tabs modify the cache, preventing race conditions from concurrent writes.
10
+ */
6
11
  export class SmartCardLocalCacheClient {
7
12
  constructor(maxItems = SMART_CARD_CACHE_DEFAULT_MAX_ITEMS) {
8
13
  _defineProperty(this, "storageClient", new StorageClient(SMART_CARD_CACHE_CLIENT_KEY, {
9
14
  storageEngine: 'sessionStorage'
10
15
  }));
16
+ _defineProperty(this, "pendingWrite", false);
11
17
  /**
12
- * Retrieves the entire cache object from storage
18
+ * Retrieves the entire cache object from memory
13
19
  * @returns The entire cache object
14
20
  */
15
21
  _defineProperty(this, "getCache", () => {
16
- const stored = this.storageClient.getItem(SMART_CARD_CACHE_KEY, {
17
- useExpiredItem: true
18
- });
19
- const persistentCache = stored ? JSON.parse(stored) : {};
20
- return persistentCache;
22
+ return this.memoryCache;
21
23
  });
22
24
  /**
23
25
  * Caches the SmartLinkResponse against the provided url in storage
@@ -25,26 +27,39 @@ export class SmartCardLocalCacheClient {
25
27
  * @param response The SmartLinkResponse to be cached in storage
26
28
  */
27
29
  _defineProperty(this, "setItem", (url, response) => {
28
- const newCache = this.getCache();
29
-
30
30
  // Add or update the item in the cache with current timestamp
31
- newCache[url] = {
31
+ this.memoryCache[url] = {
32
32
  data: response,
33
33
  timestamp: Date.now()
34
34
  };
35
35
 
36
36
  // Apply FIFO behavior if cache exceeds maxItems
37
- const cacheKeys = Object.keys(newCache);
37
+ const cacheKeys = Object.keys(this.memoryCache);
38
38
  if (cacheKeys.length > this.maxItems) {
39
39
  // Sort keys by timestamp (oldest first) and remove the oldest items
40
- const sortedKeys = cacheKeys.sort((a, b) => newCache[a].timestamp - newCache[b].timestamp);
40
+ const sortedKeys = cacheKeys.sort((a, b) => this.memoryCache[a].timestamp - this.memoryCache[b].timestamp);
41
41
  const itemsToRemove = cacheKeys.length - this.maxItems;
42
42
  const keysToRemove = sortedKeys.slice(0, itemsToRemove);
43
43
  keysToRemove.forEach(key => {
44
- delete newCache[key];
44
+ delete this.memoryCache[key];
45
+ });
46
+ }
47
+
48
+ // Write through to storage asynchronously (non-blocking)
49
+ // Debounced to batch multiple rapid calls into a single write
50
+ if (!this.pendingWrite) {
51
+ this.pendingWrite = true;
52
+ queueMicrotask(() => {
53
+ try {
54
+ this.storageClient.setItemWithExpiry(SMART_CARD_CACHE_KEY, JSON.stringify(this.memoryCache));
55
+ } catch (_) {
56
+ // Ignore storage write errors (e.g. quota exceeded) to prevent blocking future writes
57
+ // Cache will still function in-memory for the session, but won't persist
58
+ } finally {
59
+ this.pendingWrite = false;
60
+ }
45
61
  });
46
62
  }
47
- this.storageClient.setItemWithExpiry(SMART_CARD_CACHE_KEY, JSON.stringify(newCache));
48
63
  });
49
64
  /**
50
65
  * Retrieves the cached SmartLinkResponse for the provided url from storage (if it exists)
@@ -52,12 +67,11 @@ export class SmartCardLocalCacheClient {
52
67
  * @returns The cached SmartLinkResponse if it exists, else undefined
53
68
  */
54
69
  _defineProperty(this, "getItem", url => {
55
- var _persistentCache$url;
70
+ var _this$memoryCache, _this$memoryCache$url;
56
71
  if (!url) {
57
72
  return;
58
73
  }
59
- const persistentCache = this.getCache();
60
- const response = persistentCache === null || persistentCache === void 0 ? void 0 : (_persistentCache$url = persistentCache[url]) === null || _persistentCache$url === void 0 ? void 0 : _persistentCache$url.data;
74
+ const response = (_this$memoryCache = this.memoryCache) === null || _this$memoryCache === void 0 ? void 0 : (_this$memoryCache$url = _this$memoryCache[url]) === null || _this$memoryCache$url === void 0 ? void 0 : _this$memoryCache$url.data;
61
75
  return response;
62
76
  });
63
77
  /**
@@ -66,9 +80,36 @@ export class SmartCardLocalCacheClient {
66
80
  * @returns True if a cached entry exists, else false
67
81
  */
68
82
  _defineProperty(this, "isUrlInCache", url => {
69
- const persistentCache = this.getCache();
70
- return persistentCache ? url in persistentCache : false;
83
+ return url in this.memoryCache;
71
84
  });
72
85
  this.maxItems = maxItems;
86
+ // Load cache from storage into memory on instantiation
87
+ const stored = this.storageClient.getItem(SMART_CARD_CACHE_KEY, {
88
+ useExpiredItem: true
89
+ });
90
+ this.memoryCache = stored ? JSON.parse(stored) : {};
91
+ }
92
+
93
+ /**
94
+ * Gets the singleton instance of SmartCardLocalCacheClient.
95
+ * This ensures all callers share the same in-memory cache and prevents
96
+ * race conditions when multiple instances write to the same storage key.
97
+ * @param maxItems Maximum number of items to store in the cache (only used on first instantiation)
98
+ * @returns The singleton instance
99
+ */
100
+ static getInstance(maxItems = SMART_CARD_CACHE_DEFAULT_MAX_ITEMS) {
101
+ if (!SmartCardLocalCacheClient.instance) {
102
+ SmartCardLocalCacheClient.instance = new SmartCardLocalCacheClient(maxItems);
103
+ }
104
+ return SmartCardLocalCacheClient.instance;
105
+ }
106
+
107
+ /**
108
+ * Resets the singleton instance. Only intended for testing purposes.
109
+ * @internal
110
+ */
111
+ static resetInstance() {
112
+ SmartCardLocalCacheClient.instance = null;
73
113
  }
74
- }
114
+ }
115
+ _defineProperty(SmartCardLocalCacheClient, "instance", null);
@@ -136,7 +136,7 @@ export var EditorCardProvider = /*#__PURE__*/function (_ref) {
136
136
  var _this;
137
137
  _classCallCheck(this, EditorCardProvider);
138
138
  _this = _callSuper(this, EditorCardProvider);
139
- _defineProperty(_this, "smartCardLocalCacheClient", new SmartCardLocalCacheClient());
139
+ _defineProperty(_this, "smartCardLocalCacheClient", SmartCardLocalCacheClient.getInstance());
140
140
  _this.name = 'editorCardProvider';
141
141
  _this.baseUrl = baseUrlOverride || getBaseUrl(envKey);
142
142
  _this.resolverUrl = getResolverUrl(envKey, baseUrlOverride);
@@ -1,79 +1,128 @@
1
- import _createClass from "@babel/runtime/helpers/createClass";
2
1
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
3
3
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  import { StorageClient } from '@atlaskit/frontend-utilities/storage-client';
5
5
  var SMART_CARD_CACHE_CLIENT_KEY = 'smart-card-url-data';
6
6
  var SMART_CARD_CACHE_KEY = 'response-cache';
7
7
  var SMART_CARD_CACHE_DEFAULT_MAX_ITEMS = 100;
8
- export var SmartCardLocalCacheClient = /*#__PURE__*/_createClass(function SmartCardLocalCacheClient() {
9
- var _this = this;
10
- var maxItems = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SMART_CARD_CACHE_DEFAULT_MAX_ITEMS;
11
- _classCallCheck(this, SmartCardLocalCacheClient);
12
- _defineProperty(this, "storageClient", new StorageClient(SMART_CARD_CACHE_CLIENT_KEY, {
13
- storageEngine: 'sessionStorage'
14
- }));
15
- /**
16
- * Retrieves the entire cache object from storage
17
- * @returns The entire cache object
18
- */
19
- _defineProperty(this, "getCache", function () {
20
- var stored = _this.storageClient.getItem(SMART_CARD_CACHE_KEY, {
21
- useExpiredItem: true
8
+ /**
9
+ * Singleton cache client for smart link responses. Uses sessionStorage for per-tab isolation (no cross-tab conflicts).
10
+ * If switching to localStorage for cross-tab sharing, add a 'storage' event listener in the constructor to sync
11
+ * memoryCache when other tabs modify the cache, preventing race conditions from concurrent writes.
12
+ */
13
+ export var SmartCardLocalCacheClient = /*#__PURE__*/function () {
14
+ function SmartCardLocalCacheClient() {
15
+ var _this = this;
16
+ var maxItems = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SMART_CARD_CACHE_DEFAULT_MAX_ITEMS;
17
+ _classCallCheck(this, SmartCardLocalCacheClient);
18
+ _defineProperty(this, "storageClient", new StorageClient(SMART_CARD_CACHE_CLIENT_KEY, {
19
+ storageEngine: 'sessionStorage'
20
+ }));
21
+ _defineProperty(this, "pendingWrite", false);
22
+ /**
23
+ * Retrieves the entire cache object from memory
24
+ * @returns The entire cache object
25
+ */
26
+ _defineProperty(this, "getCache", function () {
27
+ return _this.memoryCache;
22
28
  });
23
- var persistentCache = stored ? JSON.parse(stored) : {};
24
- return persistentCache;
25
- });
26
- /**
27
- * Caches the SmartLinkResponse against the provided url in storage
28
- * @param url The url (key) to store the response against
29
- * @param response The SmartLinkResponse to be cached in storage
30
- */
31
- _defineProperty(this, "setItem", function (url, response) {
32
- var newCache = _this.getCache();
29
+ /**
30
+ * Caches the SmartLinkResponse against the provided url in storage
31
+ * @param url The url (key) to store the response against
32
+ * @param response The SmartLinkResponse to be cached in storage
33
+ */
34
+ _defineProperty(this, "setItem", function (url, response) {
35
+ // Add or update the item in the cache with current timestamp
36
+ _this.memoryCache[url] = {
37
+ data: response,
38
+ timestamp: Date.now()
39
+ };
33
40
 
34
- // Add or update the item in the cache with current timestamp
35
- newCache[url] = {
36
- data: response,
37
- timestamp: Date.now()
38
- };
41
+ // Apply FIFO behavior if cache exceeds maxItems
42
+ var cacheKeys = Object.keys(_this.memoryCache);
43
+ if (cacheKeys.length > _this.maxItems) {
44
+ // Sort keys by timestamp (oldest first) and remove the oldest items
45
+ var sortedKeys = cacheKeys.sort(function (a, b) {
46
+ return _this.memoryCache[a].timestamp - _this.memoryCache[b].timestamp;
47
+ });
48
+ var itemsToRemove = cacheKeys.length - _this.maxItems;
49
+ var keysToRemove = sortedKeys.slice(0, itemsToRemove);
50
+ keysToRemove.forEach(function (key) {
51
+ delete _this.memoryCache[key];
52
+ });
53
+ }
54
+
55
+ // Write through to storage asynchronously (non-blocking)
56
+ // Debounced to batch multiple rapid calls into a single write
57
+ if (!_this.pendingWrite) {
58
+ _this.pendingWrite = true;
59
+ queueMicrotask(function () {
60
+ try {
61
+ _this.storageClient.setItemWithExpiry(SMART_CARD_CACHE_KEY, JSON.stringify(_this.memoryCache));
62
+ } catch (_) {
63
+ // Ignore storage write errors (e.g. quota exceeded) to prevent blocking future writes
64
+ // Cache will still function in-memory for the session, but won't persist
65
+ } finally {
66
+ _this.pendingWrite = false;
67
+ }
68
+ });
69
+ }
70
+ });
71
+ /**
72
+ * Retrieves the cached SmartLinkResponse for the provided url from storage (if it exists)
73
+ * @param url The url (key) to retrieve the cached response for
74
+ * @returns The cached SmartLinkResponse if it exists, else undefined
75
+ */
76
+ _defineProperty(this, "getItem", function (url) {
77
+ var _this$memoryCache;
78
+ if (!url) {
79
+ return;
80
+ }
81
+ var response = (_this$memoryCache = _this.memoryCache) === null || _this$memoryCache === void 0 || (_this$memoryCache = _this$memoryCache[url]) === null || _this$memoryCache === void 0 ? void 0 : _this$memoryCache.data;
82
+ return response;
83
+ });
84
+ /**
85
+ * Checks if a cached entry exists for the provided url in storage
86
+ * @param url The url (key) to check for existence in the cache
87
+ * @returns True if a cached entry exists, else false
88
+ */
89
+ _defineProperty(this, "isUrlInCache", function (url) {
90
+ return url in _this.memoryCache;
91
+ });
92
+ this.maxItems = maxItems;
93
+ // Load cache from storage into memory on instantiation
94
+ var stored = this.storageClient.getItem(SMART_CARD_CACHE_KEY, {
95
+ useExpiredItem: true
96
+ });
97
+ this.memoryCache = stored ? JSON.parse(stored) : {};
98
+ }
39
99
 
40
- // Apply FIFO behavior if cache exceeds maxItems
41
- var cacheKeys = Object.keys(newCache);
42
- if (cacheKeys.length > _this.maxItems) {
43
- // Sort keys by timestamp (oldest first) and remove the oldest items
44
- var sortedKeys = cacheKeys.sort(function (a, b) {
45
- return newCache[a].timestamp - newCache[b].timestamp;
46
- });
47
- var itemsToRemove = cacheKeys.length - _this.maxItems;
48
- var keysToRemove = sortedKeys.slice(0, itemsToRemove);
49
- keysToRemove.forEach(function (key) {
50
- delete newCache[key];
51
- });
52
- }
53
- _this.storageClient.setItemWithExpiry(SMART_CARD_CACHE_KEY, JSON.stringify(newCache));
54
- });
55
100
  /**
56
- * Retrieves the cached SmartLinkResponse for the provided url from storage (if it exists)
57
- * @param url The url (key) to retrieve the cached response for
58
- * @returns The cached SmartLinkResponse if it exists, else undefined
101
+ * Gets the singleton instance of SmartCardLocalCacheClient.
102
+ * This ensures all callers share the same in-memory cache and prevents
103
+ * race conditions when multiple instances write to the same storage key.
104
+ * @param maxItems Maximum number of items to store in the cache (only used on first instantiation)
105
+ * @returns The singleton instance
59
106
  */
60
- _defineProperty(this, "getItem", function (url) {
61
- var _persistentCache$url;
62
- if (!url) {
63
- return;
107
+ return _createClass(SmartCardLocalCacheClient, null, [{
108
+ key: "getInstance",
109
+ value: function getInstance() {
110
+ var maxItems = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : SMART_CARD_CACHE_DEFAULT_MAX_ITEMS;
111
+ if (!SmartCardLocalCacheClient.instance) {
112
+ SmartCardLocalCacheClient.instance = new SmartCardLocalCacheClient(maxItems);
113
+ }
114
+ return SmartCardLocalCacheClient.instance;
64
115
  }
65
- var persistentCache = _this.getCache();
66
- var response = persistentCache === null || persistentCache === void 0 || (_persistentCache$url = persistentCache[url]) === null || _persistentCache$url === void 0 ? void 0 : _persistentCache$url.data;
67
- return response;
68
- });
69
- /**
70
- * Checks if a cached entry exists for the provided url in storage
71
- * @param url The url (key) to check for existence in the cache
72
- * @returns True if a cached entry exists, else false
73
- */
74
- _defineProperty(this, "isUrlInCache", function (url) {
75
- var persistentCache = _this.getCache();
76
- return persistentCache ? url in persistentCache : false;
77
- });
78
- this.maxItems = maxItems;
79
- });
116
+
117
+ /**
118
+ * Resets the singleton instance. Only intended for testing purposes.
119
+ * @internal
120
+ */
121
+ }, {
122
+ key: "resetInstance",
123
+ value: function resetInstance() {
124
+ SmartCardLocalCacheClient.instance = null;
125
+ }
126
+ }]);
127
+ }();
128
+ _defineProperty(SmartCardLocalCacheClient, "instance", null);
@@ -3,12 +3,33 @@ type CacheEntry = {
3
3
  data: SmartLinkResponse;
4
4
  timestamp: number;
5
5
  };
6
+ /**
7
+ * Singleton cache client for smart link responses. Uses sessionStorage for per-tab isolation (no cross-tab conflicts).
8
+ * If switching to localStorage for cross-tab sharing, add a 'storage' event listener in the constructor to sync
9
+ * memoryCache when other tabs modify the cache, preventing race conditions from concurrent writes.
10
+ */
6
11
  export declare class SmartCardLocalCacheClient {
12
+ private static instance;
7
13
  private readonly storageClient;
8
14
  private maxItems;
9
- constructor(maxItems?: number);
15
+ private memoryCache;
16
+ private pendingWrite;
17
+ private constructor();
10
18
  /**
11
- * Retrieves the entire cache object from storage
19
+ * Gets the singleton instance of SmartCardLocalCacheClient.
20
+ * This ensures all callers share the same in-memory cache and prevents
21
+ * race conditions when multiple instances write to the same storage key.
22
+ * @param maxItems Maximum number of items to store in the cache (only used on first instantiation)
23
+ * @returns The singleton instance
24
+ */
25
+ static getInstance(maxItems?: number): SmartCardLocalCacheClient;
26
+ /**
27
+ * Resets the singleton instance. Only intended for testing purposes.
28
+ * @internal
29
+ */
30
+ static resetInstance(): void;
31
+ /**
32
+ * Retrieves the entire cache object from memory
12
33
  * @returns The entire cache object
13
34
  */
14
35
  getCache: () => Record<string, CacheEntry>;
@@ -3,12 +3,33 @@ type CacheEntry = {
3
3
  data: SmartLinkResponse;
4
4
  timestamp: number;
5
5
  };
6
+ /**
7
+ * Singleton cache client for smart link responses. Uses sessionStorage for per-tab isolation (no cross-tab conflicts).
8
+ * If switching to localStorage for cross-tab sharing, add a 'storage' event listener in the constructor to sync
9
+ * memoryCache when other tabs modify the cache, preventing race conditions from concurrent writes.
10
+ */
6
11
  export declare class SmartCardLocalCacheClient {
12
+ private static instance;
7
13
  private readonly storageClient;
8
14
  private maxItems;
9
- constructor(maxItems?: number);
15
+ private memoryCache;
16
+ private pendingWrite;
17
+ private constructor();
10
18
  /**
11
- * Retrieves the entire cache object from storage
19
+ * Gets the singleton instance of SmartCardLocalCacheClient.
20
+ * This ensures all callers share the same in-memory cache and prevents
21
+ * race conditions when multiple instances write to the same storage key.
22
+ * @param maxItems Maximum number of items to store in the cache (only used on first instantiation)
23
+ * @returns The singleton instance
24
+ */
25
+ static getInstance(maxItems?: number): SmartCardLocalCacheClient;
26
+ /**
27
+ * Resets the singleton instance. Only intended for testing purposes.
28
+ * @internal
29
+ */
30
+ static resetInstance(): void;
31
+ /**
32
+ * Retrieves the entire cache object from memory
12
33
  * @returns The entire cache object
13
34
  */
14
35
  getCache: () => Record<string, CacheEntry>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-card-provider",
3
- "version": "6.4.8",
3
+ "version": "6.4.9",
4
4
  "description": "Contains the EditorCardProvider, for determining smart card behaviour in the editor.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -39,7 +39,7 @@
39
39
  "@atlaskit/linking-types": "^14.2.0",
40
40
  "@atlaskit/node-data-provider": "^8.3.0",
41
41
  "@atlaskit/platform-feature-flags": "^1.1.0",
42
- "@atlaskit/tmp-editor-statsig": "^29.0.0",
42
+ "@atlaskit/tmp-editor-statsig": "^29.2.0",
43
43
  "@babel/runtime": "^7.0.0",
44
44
  "dataloader": "^2.1.0"
45
45
  },