@atlaskit/editor-synced-block-provider 6.6.5 → 6.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/clients/block-service/blockSubscription.js +2 -2
  3. package/dist/cjs/providers/block-service/blockServiceAPI.js +2 -2
  4. package/dist/cjs/providers/syncBlockProvider.js +2 -2
  5. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +26 -7
  6. package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +148 -25
  7. package/dist/cjs/utils/utils.js +49 -4
  8. package/dist/es2019/clients/block-service/blockSubscription.js +2 -2
  9. package/dist/es2019/providers/block-service/blockServiceAPI.js +2 -2
  10. package/dist/es2019/providers/syncBlockProvider.js +2 -2
  11. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +27 -8
  12. package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +109 -6
  13. package/dist/es2019/utils/utils.js +43 -1
  14. package/dist/esm/clients/block-service/blockSubscription.js +2 -2
  15. package/dist/esm/providers/block-service/blockServiceAPI.js +2 -2
  16. package/dist/esm/providers/syncBlockProvider.js +2 -2
  17. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +28 -9
  18. package/dist/esm/store-manager/syncBlockSubscriptionManager.js +148 -25
  19. package/dist/esm/utils/utils.js +48 -1
  20. package/dist/types/clients/block-service/blockSubscription.d.ts +2 -1
  21. package/dist/types/entry-points/providers-types.d.ts +1 -1
  22. package/dist/types/providers/block-service/blockServiceAPI.d.ts +1 -1
  23. package/dist/types/providers/syncBlockProvider.d.ts +1 -1
  24. package/dist/types/providers/types.d.ts +4 -2
  25. package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +1 -0
  26. package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +23 -0
  27. package/dist/types/utils/utils.d.ts +2 -1
  28. package/dist/types-ts4.5/clients/block-service/blockSubscription.d.ts +2 -1
  29. package/dist/types-ts4.5/entry-points/providers-types.d.ts +1 -1
  30. package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +1 -1
  31. package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +1 -1
  32. package/dist/types-ts4.5/providers/types.d.ts +4 -2
  33. package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +1 -0
  34. package/dist/types-ts4.5/store-manager/syncBlockSubscriptionManager.d.ts +23 -0
  35. package/dist/types-ts4.5/utils/utils.d.ts +2 -1
  36. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { logException } from '@atlaskit/editor-common/monitoring';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
4
5
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
5
6
  import { getSourceProductFromResourceIdSafe } from '../utils/utils';
@@ -21,6 +22,8 @@ export class SyncBlockSubscriptionManager {
21
22
  // causing the cache to be deleted prematurely. We delay deletion to allow
22
23
  // the new component to subscribe and cancel the pending deletion.
23
24
  _defineProperty(this, "pendingCacheDeletions", new Map());
25
+ _defineProperty(this, "retryAttempts", new Map());
26
+ _defineProperty(this, "pendingRetries", new Map());
24
27
  this.deps = deps;
25
28
  }
26
29
 
@@ -213,6 +216,7 @@ export class SyncBlockSubscriptionManager {
213
216
  if (!(dataProvider !== null && dataProvider !== void 0 && dataProvider.subscribeToBlockUpdates)) {
214
217
  return;
215
218
  }
219
+ const reconnectEnabled = fg('platform_synced_block_patch_12');
216
220
  const unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, syncBlockInstance => {
217
221
  this.handleGraphQLUpdate(syncBlockInstance);
218
222
  }, error => {
@@ -221,17 +225,105 @@ export class SyncBlockSubscriptionManager {
221
225
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
222
226
  });
223
227
  (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
224
- });
228
+ if (reconnectEnabled) {
229
+ this.handleSubscriptionTerminated(resourceId);
230
+ }
231
+ }, reconnectEnabled ? () => {
232
+ this.handleSubscriptionTerminated(resourceId);
233
+ } : undefined);
225
234
  if (unsubscribe) {
226
235
  this.graphqlSubscriptions.set(resourceId, unsubscribe);
227
236
  }
228
237
  }
238
+
239
+ /**
240
+ * Handles subscription termination (complete or error) by cleaning up the
241
+ * stale entry and scheduling a reconnection with exponential backoff.
242
+ */
243
+ handleSubscriptionTerminated(resourceId) {
244
+ // Remove the stale subscription entry so setupSubscription won't early-return
245
+ this.graphqlSubscriptions.delete(resourceId);
246
+
247
+ // Guard against duplicate calls (graphql-ws can fire both error and complete)
248
+ if (this.pendingRetries.has(resourceId)) {
249
+ return;
250
+ }
251
+
252
+ // Only reconnect if there are still active subscribers for this resource
253
+ if (this.subscriptions.has(resourceId) && this.shouldUseRealTime()) {
254
+ this.scheduleReconnection(resourceId);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Schedules a reconnection attempt with exponential backoff.
260
+ * Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
261
+ * e.g. 1s, 2s, 4s, 8s, 16s
262
+ */
263
+ scheduleReconnection(resourceId) {
264
+ var _this$retryAttempts$g;
265
+ const attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
266
+ if (attempts >= SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS) {
267
+ var _this$deps$getFireAna3;
268
+ const errorMessage = `Subscription reconnection failed after ${attempts} attempts`;
269
+ logException(new Error(errorMessage), {
270
+ location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
271
+ });
272
+ (_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
273
+ return;
274
+ }
275
+ const delay = SyncBlockSubscriptionManager.INITIAL_RETRY_DELAY_MS * Math.pow(SyncBlockSubscriptionManager.RETRY_BACKOFF_MULTIPLIER, attempts);
276
+ const timer = setTimeout(() => {
277
+ this.pendingRetries.delete(resourceId);
278
+
279
+ // Only re-subscribe if still relevant
280
+ if (this.subscriptions.has(resourceId) && this.shouldUseRealTime()) {
281
+ this.setupSubscription(resourceId);
282
+
283
+ // Only increment if the subscription was actually established
284
+ // (setupSubscription may be a no-op if another code path already re-established it)
285
+ if (this.graphqlSubscriptions.has(resourceId)) {
286
+ this.retryAttempts.set(resourceId, attempts + 1);
287
+ }
288
+ } else {
289
+ // Conditions no longer met — clean up stale retry state
290
+ this.retryAttempts.delete(resourceId);
291
+ }
292
+ }, delay);
293
+ this.pendingRetries.set(resourceId, timer);
294
+ }
295
+
296
+ /**
297
+ * Resets the retry counter for a resource. Called when a successful
298
+ * update is received, indicating the subscription is healthy.
299
+ */
300
+ resetRetryCount(resourceId) {
301
+ this.retryAttempts.delete(resourceId);
302
+ }
303
+ cancelPendingRetry(resourceId) {
304
+ const timer = this.pendingRetries.get(resourceId);
305
+ if (timer) {
306
+ clearTimeout(timer);
307
+ this.pendingRetries.delete(resourceId);
308
+ }
309
+ }
310
+ cancelAllPendingRetries() {
311
+ for (const timer of this.pendingRetries.values()) {
312
+ clearTimeout(timer);
313
+ }
314
+ this.pendingRetries.clear();
315
+ this.retryAttempts.clear();
316
+ }
229
317
  cleanupSubscription(resourceId) {
230
318
  const unsubscribe = this.graphqlSubscriptions.get(resourceId);
231
319
  if (unsubscribe) {
232
320
  unsubscribe();
233
321
  this.graphqlSubscriptions.delete(resourceId);
234
322
  }
323
+ if (fg('platform_synced_block_patch_12')) {
324
+ this.cancelPendingRetry(resourceId);
325
+ this.retryAttempts.delete(resourceId);
326
+ }
235
327
  }
236
328
  setupSubscriptionsForAllBlocks() {
237
329
  for (const resourceId of this.subscriptions.keys()) {
@@ -243,6 +335,9 @@ export class SyncBlockSubscriptionManager {
243
335
  unsubscribe();
244
336
  }
245
337
  this.graphqlSubscriptions.clear();
338
+ if (fg('platform_synced_block_patch_12')) {
339
+ this.cancelAllPendingRetries();
340
+ }
246
341
  }
247
342
  destroy() {
248
343
  this.cleanupAll();
@@ -268,17 +363,25 @@ export class SyncBlockSubscriptionManager {
268
363
  const resolved = existing ? resolveSyncBlockInstance(existing, syncBlockInstance) : syncBlockInstance;
269
364
  this.deps.updateCache(resolved);
270
365
  if (!syncBlockInstance.error) {
366
+ // Successful data delivery means the subscription is healthy — reset retry counter
367
+ if (fg('platform_synced_block_patch_12')) {
368
+ this.resetRetryCount(syncBlockInstance.resourceId);
369
+ }
271
370
  const callbacks = this.subscriptions.get(syncBlockInstance.resourceId);
272
371
  const localIds = callbacks ? Object.keys(callbacks) : [];
273
372
  localIds.forEach(localId => {
274
- var _this$deps$getFireAna3, _syncBlockInstance$da, _syncBlockInstance$da2;
275
- (_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchSuccessPayload(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
373
+ var _this$deps$getFireAna4, _syncBlockInstance$da, _syncBlockInstance$da2;
374
+ (_this$deps$getFireAna4 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna4 === void 0 ? void 0 : _this$deps$getFireAna4(fetchSuccessPayload(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
276
375
  });
277
376
  this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
278
377
  } else {
279
- var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna4, _syncBlockInstance$da3, _syncBlockInstance$da4;
378
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna5, _syncBlockInstance$da3, _syncBlockInstance$da4;
280
379
  const errorMessage = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type);
281
- (_this$deps$getFireAna4 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna4 === void 0 ? void 0 : _this$deps$getFireAna4(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
380
+ (_this$deps$getFireAna5 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna5 === void 0 ? void 0 : _this$deps$getFireAna5(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
282
381
  }
283
382
  }
284
- }
383
+ }
384
+ // Reconnection with exponential backoff
385
+ _defineProperty(SyncBlockSubscriptionManager, "INITIAL_RETRY_DELAY_MS", 1000);
386
+ _defineProperty(SyncBlockSubscriptionManager, "RETRY_BACKOFF_MULTIPLIER", 2);
387
+ _defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
@@ -1,9 +1,51 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
2
 
3
+ import { fg } from '@atlaskit/platform-feature-flags';
4
+ export const stripAnnotationMarksFromJSONContent = content => {
5
+ var _strippedContent3;
6
+ let strippedContent;
7
+ content.forEach((contentNode, index) => {
8
+ var _contentNode$marks;
9
+ if (!contentNode) {
10
+ var _strippedContent;
11
+ (_strippedContent = strippedContent) === null || _strippedContent === void 0 ? void 0 : _strippedContent.push(contentNode);
12
+ return;
13
+ }
14
+ const hasAnnotationMark = (_contentNode$marks = contentNode.marks) === null || _contentNode$marks === void 0 ? void 0 : _contentNode$marks.some(mark => mark.type === 'annotation');
15
+ const childContent = contentNode.content ? stripAnnotationMarksFromJSONContent(contentNode.content) : undefined;
16
+ const hasContentChanged = childContent !== undefined && childContent !== contentNode.content;
17
+ if (!hasAnnotationMark && !hasContentChanged) {
18
+ var _strippedContent2;
19
+ (_strippedContent2 = strippedContent) === null || _strippedContent2 === void 0 ? void 0 : _strippedContent2.push(contentNode);
20
+ return;
21
+ }
22
+ if (!strippedContent) {
23
+ strippedContent = content.slice(0, index);
24
+ }
25
+ const strippedNode = {
26
+ ...contentNode
27
+ };
28
+ if (hasAnnotationMark) {
29
+ var _contentNode$marks2;
30
+ const marks = (_contentNode$marks2 = contentNode.marks) === null || _contentNode$marks2 === void 0 ? void 0 : _contentNode$marks2.filter(mark => mark.type !== 'annotation');
31
+ if (marks && marks.length > 0) {
32
+ strippedNode.marks = marks;
33
+ } else {
34
+ delete strippedNode.marks;
35
+ }
36
+ }
37
+ if (hasContentChanged && childContent) {
38
+ strippedNode.content = childContent;
39
+ }
40
+ strippedContent.push(strippedNode);
41
+ });
42
+ return (_strippedContent3 = strippedContent) !== null && _strippedContent3 !== void 0 ? _strippedContent3 : content;
43
+ };
3
44
  export const convertSyncBlockPMNodeToSyncBlockData = node => {
45
+ const content = node.content.toJSON();
4
46
  return {
5
47
  blockInstanceId: node.attrs.localId,
6
- content: node.content.toJSON(),
48
+ content: fg('platform_synced_block_patch_12') ? stripAnnotationMarksFromJSONContent(content) : content,
7
49
  resourceId: node.attrs.resourceId
8
50
  };
9
51
  };
@@ -167,7 +167,7 @@ var parseSubscriptionPayload = function parseSubscriptionPayload(payload) {
167
167
  * @param onError - Optional callback function invoked on subscription errors
168
168
  * @returns Unsubscribe function to close the subscription
169
169
  */
170
- export var subscribeToBlockUpdates = function subscribeToBlockUpdates(blockAri, onData, onError) {
170
+ export var subscribeToBlockUpdates = function subscribeToBlockUpdates(blockAri, onData, onError, onComplete) {
171
171
  var client = getBlockServiceClient();
172
172
  if (!client) {
173
173
  // Return a no-op unsubscribe if client is not available (e.g., SSR)
@@ -203,7 +203,7 @@ export var subscribeToBlockUpdates = function subscribeToBlockUpdates(blockAri,
203
203
  onError === null || onError === void 0 || onError(new Error(extractGraphQLWSErrorMessage(error)));
204
204
  }),
205
205
  complete: function complete() {
206
- // Subscription completed
206
+ onComplete === null || onComplete === void 0 || onComplete();
207
207
  }
208
208
  });
209
209
  return unsubscribe;
@@ -786,7 +786,7 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
786
786
  )
787
787
  }, {
788
788
  key: "subscribeToBlockUpdates",
789
- value: function subscribeToBlockUpdates(resourceId, onUpdate, onError) {
789
+ value: function subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete) {
790
790
  var blockAri = generateBlockAriFromReference({
791
791
  cloudId: this.cloudId,
792
792
  resourceId: resourceId
@@ -820,7 +820,7 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
820
820
  resourceId: parsedData.resourceId
821
821
  };
822
822
  onUpdate(syncBlockInstance);
823
- }, onError);
823
+ }, onError, onComplete);
824
824
  }).catch(function (err) {
825
825
  if (cancelled) {
826
826
  return;
@@ -462,9 +462,9 @@ export var SyncedBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
462
462
  */
463
463
  }, {
464
464
  key: "subscribeToBlockUpdates",
465
- value: function subscribeToBlockUpdates(resourceId, onUpdate, onError) {
465
+ value: function subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete) {
466
466
  if (this.fetchProvider.subscribeToBlockUpdates) {
467
- return this.fetchProvider.subscribeToBlockUpdates(resourceId, onUpdate, onError);
467
+ return this.fetchProvider.subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete);
468
468
  }
469
469
  return undefined;
470
470
  }
@@ -6,16 +6,17 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
6
6
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
7
7
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
8
8
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
9
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
9
10
  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; }
10
11
  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; }
11
- import _regeneratorRuntime from "@babel/runtime/regenerator";
12
12
  import isEqual from 'lodash/isEqual';
13
13
  import { logException } from '@atlaskit/editor-common/monitoring';
14
+ import { fg } from '@atlaskit/platform-feature-flags';
14
15
  import { SyncBlockError } from '../common/types';
15
16
  import { fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
16
17
  import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
17
18
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
18
- import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
19
+ import { createSyncBlockNode, getSourceProductFromResourceIdSafe, stripAnnotationMarksFromJSONContent } from '../utils/utils';
19
20
  import { SyncBlockBatchFetcher } from './syncBlockBatchFetcher';
20
21
  import { syncBlockInMemorySessionCache } from './syncBlockInMemorySessionCache';
21
22
  import { SyncBlockProviderFactoryManager } from './syncBlockProviderFactoryManager';
@@ -203,10 +204,27 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
203
204
  var syncBlockNode = createSyncBlockNode('', resourceId);
204
205
  var providerData = (_this$dataProvider2 = this.dataProvider) === null || _this$dataProvider2 === void 0 || (_this$dataProvider2 = _this$dataProvider2.getNodeDataFromCache(syncBlockNode)) === null || _this$dataProvider2 === void 0 ? void 0 : _this$dataProvider2.data;
205
206
  if (providerData) {
206
- return providerData;
207
+ return this.stripAnnotationMarksFromReferenceData(providerData);
207
208
  }
208
209
  return this.getFromSessionCache(resourceId);
209
210
  }
211
+ }, {
212
+ key: "stripAnnotationMarksFromReferenceData",
213
+ value: function stripAnnotationMarksFromReferenceData(syncBlock) {
214
+ var _syncBlock$data;
215
+ if (!fg('platform_synced_block_patch_12') || !((_syncBlock$data = syncBlock.data) !== null && _syncBlock$data !== void 0 && _syncBlock$data.content)) {
216
+ return syncBlock;
217
+ }
218
+ var content = stripAnnotationMarksFromJSONContent(syncBlock.data.content);
219
+ if (content === syncBlock.data.content) {
220
+ return syncBlock;
221
+ }
222
+ return _objectSpread(_objectSpread({}, syncBlock), {}, {
223
+ data: _objectSpread(_objectSpread({}, syncBlock.data), {}, {
224
+ content: content
225
+ })
226
+ });
227
+ }
210
228
  }, {
211
229
  key: "updateSessionCache",
212
230
  value: function updateSessionCache(resourceId) {
@@ -223,7 +241,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
223
241
  if (!raw) {
224
242
  return undefined;
225
243
  }
226
- return JSON.parse(raw);
244
+ return this.stripAnnotationMarksFromReferenceData(JSON.parse(raw));
227
245
  } catch (error) {
228
246
  logException(error, {
229
247
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager/getFromSessionCache'
@@ -594,14 +612,15 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
594
612
  }, {
595
613
  key: "updateCache",
596
614
  value: function updateCache(syncBlock) {
597
- var resourceId = syncBlock.resourceId;
615
+ var sanitizedSyncBlock = this.stripAnnotationMarksFromReferenceData(syncBlock);
616
+ var resourceId = sanitizedSyncBlock.resourceId;
598
617
  if (resourceId) {
599
618
  var _this$dataProvider3;
600
- (_this$dataProvider3 = this.dataProvider) === null || _this$dataProvider3 === void 0 || _this$dataProvider3.updateCache(_defineProperty({}, resourceId, syncBlock), {
619
+ (_this$dataProvider3 = this.dataProvider) === null || _this$dataProvider3 === void 0 || _this$dataProvider3.updateCache(_defineProperty({}, resourceId, sanitizedSyncBlock), {
601
620
  strategy: 'merge',
602
621
  source: 'network'
603
622
  });
604
- this._subscriptionManager.notifySubscriptionCallbacks(resourceId, syncBlock);
623
+ this._subscriptionManager.notifySubscriptionCallbacks(resourceId, sanitizedSyncBlock);
605
624
  this.updateSessionCache(resourceId);
606
625
  }
607
626
  }
@@ -678,12 +697,12 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
678
697
  }, {
679
698
  key: "getSyncBlockURL",
680
699
  value: function getSyncBlockURL(resourceId) {
681
- var _syncBlock$data;
700
+ var _syncBlock$data2;
682
701
  var syncBlock = this.getFromCache(resourceId);
683
702
  if (!syncBlock) {
684
703
  return undefined;
685
704
  }
686
- return (_syncBlock$data = syncBlock.data) === null || _syncBlock$data === void 0 ? void 0 : _syncBlock$data.sourceURL;
705
+ return (_syncBlock$data2 = syncBlock.data) === null || _syncBlock$data2 === void 0 ? void 0 : _syncBlock$data2.sourceURL;
687
706
  }
688
707
  }, {
689
708
  key: "getProviderFactory",
@@ -7,6 +7,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
7
7
  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; }
8
8
  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; }
9
9
  import { logException } from '@atlaskit/editor-common/monitoring';
10
+ import { fg } from '@atlaskit/platform-feature-flags';
10
11
  import { fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
11
12
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
12
13
  import { getSourceProductFromResourceIdSafe } from '../utils/utils';
@@ -29,6 +30,8 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
29
30
  // causing the cache to be deleted prematurely. We delay deletion to allow
30
31
  // the new component to subscribe and cancel the pending deletion.
31
32
  _defineProperty(this, "pendingCacheDeletions", new Map());
33
+ _defineProperty(this, "retryAttempts", new Map());
34
+ _defineProperty(this, "pendingRetries", new Map());
32
35
  this.deps = deps;
33
36
  }
34
37
 
@@ -243,6 +246,7 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
243
246
  if (!(dataProvider !== null && dataProvider !== void 0 && dataProvider.subscribeToBlockUpdates)) {
244
247
  return;
245
248
  }
249
+ var reconnectEnabled = fg('platform_synced_block_patch_12');
246
250
  var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
247
251
  _this5.handleGraphQLUpdate(syncBlockInstance);
248
252
  }, function (error) {
@@ -251,11 +255,115 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
251
255
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
252
256
  });
253
257
  (_this5$deps$getFireAn = _this5.deps.getFireAnalyticsEvent()) === null || _this5$deps$getFireAn === void 0 || _this5$deps$getFireAn(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
254
- });
258
+ if (reconnectEnabled) {
259
+ _this5.handleSubscriptionTerminated(resourceId);
260
+ }
261
+ }, reconnectEnabled ? function () {
262
+ _this5.handleSubscriptionTerminated(resourceId);
263
+ } : undefined);
255
264
  if (unsubscribe) {
256
265
  this.graphqlSubscriptions.set(resourceId, unsubscribe);
257
266
  }
258
267
  }
268
+
269
+ /**
270
+ * Handles subscription termination (complete or error) by cleaning up the
271
+ * stale entry and scheduling a reconnection with exponential backoff.
272
+ */
273
+ }, {
274
+ key: "handleSubscriptionTerminated",
275
+ value: function handleSubscriptionTerminated(resourceId) {
276
+ // Remove the stale subscription entry so setupSubscription won't early-return
277
+ this.graphqlSubscriptions.delete(resourceId);
278
+
279
+ // Guard against duplicate calls (graphql-ws can fire both error and complete)
280
+ if (this.pendingRetries.has(resourceId)) {
281
+ return;
282
+ }
283
+
284
+ // Only reconnect if there are still active subscribers for this resource
285
+ if (this.subscriptions.has(resourceId) && this.shouldUseRealTime()) {
286
+ this.scheduleReconnection(resourceId);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Schedules a reconnection attempt with exponential backoff.
292
+ * Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
293
+ * e.g. 1s, 2s, 4s, 8s, 16s
294
+ */
295
+ }, {
296
+ key: "scheduleReconnection",
297
+ value: function scheduleReconnection(resourceId) {
298
+ var _this$retryAttempts$g,
299
+ _this6 = this;
300
+ var attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
301
+ if (attempts >= SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS) {
302
+ var _this$deps$getFireAna;
303
+ var errorMessage = "Subscription reconnection failed after ".concat(attempts, " attempts");
304
+ logException(new Error(errorMessage), {
305
+ location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
306
+ });
307
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
308
+ return;
309
+ }
310
+ var delay = SyncBlockSubscriptionManager.INITIAL_RETRY_DELAY_MS * Math.pow(SyncBlockSubscriptionManager.RETRY_BACKOFF_MULTIPLIER, attempts);
311
+ var timer = setTimeout(function () {
312
+ _this6.pendingRetries.delete(resourceId);
313
+
314
+ // Only re-subscribe if still relevant
315
+ if (_this6.subscriptions.has(resourceId) && _this6.shouldUseRealTime()) {
316
+ _this6.setupSubscription(resourceId);
317
+
318
+ // Only increment if the subscription was actually established
319
+ // (setupSubscription may be a no-op if another code path already re-established it)
320
+ if (_this6.graphqlSubscriptions.has(resourceId)) {
321
+ _this6.retryAttempts.set(resourceId, attempts + 1);
322
+ }
323
+ } else {
324
+ // Conditions no longer met — clean up stale retry state
325
+ _this6.retryAttempts.delete(resourceId);
326
+ }
327
+ }, delay);
328
+ this.pendingRetries.set(resourceId, timer);
329
+ }
330
+
331
+ /**
332
+ * Resets the retry counter for a resource. Called when a successful
333
+ * update is received, indicating the subscription is healthy.
334
+ */
335
+ }, {
336
+ key: "resetRetryCount",
337
+ value: function resetRetryCount(resourceId) {
338
+ this.retryAttempts.delete(resourceId);
339
+ }
340
+ }, {
341
+ key: "cancelPendingRetry",
342
+ value: function cancelPendingRetry(resourceId) {
343
+ var timer = this.pendingRetries.get(resourceId);
344
+ if (timer) {
345
+ clearTimeout(timer);
346
+ this.pendingRetries.delete(resourceId);
347
+ }
348
+ }
349
+ }, {
350
+ key: "cancelAllPendingRetries",
351
+ value: function cancelAllPendingRetries() {
352
+ var _iterator = _createForOfIteratorHelper(this.pendingRetries.values()),
353
+ _step;
354
+ try {
355
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
356
+ var timer = _step.value;
357
+ clearTimeout(timer);
358
+ }
359
+ } catch (err) {
360
+ _iterator.e(err);
361
+ } finally {
362
+ _iterator.f();
363
+ }
364
+ this.pendingRetries.clear();
365
+ this.retryAttempts.clear();
366
+ }
259
367
  }, {
260
368
  key: "cleanupSubscription",
261
369
  value: function cleanupSubscription(resourceId) {
@@ -264,39 +372,46 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
264
372
  unsubscribe();
265
373
  this.graphqlSubscriptions.delete(resourceId);
266
374
  }
375
+ if (fg('platform_synced_block_patch_12')) {
376
+ this.cancelPendingRetry(resourceId);
377
+ this.retryAttempts.delete(resourceId);
378
+ }
267
379
  }
268
380
  }, {
269
381
  key: "setupSubscriptionsForAllBlocks",
270
382
  value: function setupSubscriptionsForAllBlocks() {
271
- var _iterator = _createForOfIteratorHelper(this.subscriptions.keys()),
272
- _step;
383
+ var _iterator2 = _createForOfIteratorHelper(this.subscriptions.keys()),
384
+ _step2;
273
385
  try {
274
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
275
- var resourceId = _step.value;
386
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
387
+ var resourceId = _step2.value;
276
388
  this.setupSubscription(resourceId);
277
389
  }
278
390
  } catch (err) {
279
- _iterator.e(err);
391
+ _iterator2.e(err);
280
392
  } finally {
281
- _iterator.f();
393
+ _iterator2.f();
282
394
  }
283
395
  }
284
396
  }, {
285
397
  key: "cleanupAll",
286
398
  value: function cleanupAll() {
287
- var _iterator2 = _createForOfIteratorHelper(this.graphqlSubscriptions.values()),
288
- _step2;
399
+ var _iterator3 = _createForOfIteratorHelper(this.graphqlSubscriptions.values()),
400
+ _step3;
289
401
  try {
290
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
291
- var unsubscribe = _step2.value;
402
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
403
+ var unsubscribe = _step3.value;
292
404
  unsubscribe();
293
405
  }
294
406
  } catch (err) {
295
- _iterator2.e(err);
407
+ _iterator3.e(err);
296
408
  } finally {
297
- _iterator2.f();
409
+ _iterator3.f();
298
410
  }
299
411
  this.graphqlSubscriptions.clear();
412
+ if (fg('platform_synced_block_patch_12')) {
413
+ this.cancelAllPendingRetries();
414
+ }
300
415
  }
301
416
  }, {
302
417
  key: "destroy",
@@ -308,17 +423,17 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
308
423
  this.useRealTimeSubscriptions = false;
309
424
 
310
425
  // Clear any pending cache deletions
311
- var _iterator3 = _createForOfIteratorHelper(this.pendingCacheDeletions.values()),
312
- _step3;
426
+ var _iterator4 = _createForOfIteratorHelper(this.pendingCacheDeletions.values()),
427
+ _step4;
313
428
  try {
314
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
315
- var timeout = _step3.value;
429
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
430
+ var timeout = _step4.value;
316
431
  clearTimeout(timeout);
317
432
  }
318
433
  } catch (err) {
319
- _iterator3.e(err);
434
+ _iterator4.e(err);
320
435
  } finally {
321
- _iterator3.f();
436
+ _iterator4.f();
322
437
  }
323
438
  this.pendingCacheDeletions.clear();
324
439
  }
@@ -330,7 +445,7 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
330
445
  }, {
331
446
  key: "handleGraphQLUpdate",
332
447
  value: function handleGraphQLUpdate(syncBlockInstance) {
333
- var _this6 = this;
448
+ var _this7 = this;
334
449
  if (!syncBlockInstance.resourceId) {
335
450
  return;
336
451
  }
@@ -338,18 +453,26 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
338
453
  var resolved = existing ? resolveSyncBlockInstance(existing, syncBlockInstance) : syncBlockInstance;
339
454
  this.deps.updateCache(resolved);
340
455
  if (!syncBlockInstance.error) {
456
+ // Successful data delivery means the subscription is healthy — reset retry counter
457
+ if (fg('platform_synced_block_patch_12')) {
458
+ this.resetRetryCount(syncBlockInstance.resourceId);
459
+ }
341
460
  var callbacks = this.subscriptions.get(syncBlockInstance.resourceId);
342
461
  var localIds = callbacks ? Object.keys(callbacks) : [];
343
462
  localIds.forEach(function (localId) {
344
- var _this6$deps$getFireAn, _syncBlockInstance$da, _syncBlockInstance$da2;
345
- (_this6$deps$getFireAn = _this6.deps.getFireAnalyticsEvent()) === null || _this6$deps$getFireAn === void 0 || _this6$deps$getFireAn(fetchSuccessPayload(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
463
+ var _this7$deps$getFireAn, _syncBlockInstance$da, _syncBlockInstance$da2;
464
+ (_this7$deps$getFireAn = _this7.deps.getFireAnalyticsEvent()) === null || _this7$deps$getFireAn === void 0 || _this7$deps$getFireAn(fetchSuccessPayload(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
346
465
  });
347
466
  this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
348
467
  } else {
349
- var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna, _syncBlockInstance$da3, _syncBlockInstance$da4;
468
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna2, _syncBlockInstance$da3, _syncBlockInstance$da4;
350
469
  var errorMessage = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type);
351
- (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
470
+ (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
352
471
  }
353
472
  }
354
473
  }]);
355
- }();
474
+ }();
475
+ // Reconnection with exponential backoff
476
+ _defineProperty(SyncBlockSubscriptionManager, "INITIAL_RETRY_DELAY_MS", 1000);
477
+ _defineProperty(SyncBlockSubscriptionManager, "RETRY_BACKOFF_MULTIPLIER", 2);
478
+ _defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);