@atlaskit/editor-synced-block-provider 6.6.6 → 6.6.8
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 +14 -0
- package/dist/cjs/clients/block-service/blockSubscription.js +2 -2
- package/dist/cjs/providers/block-service/blockServiceAPI.js +2 -2
- package/dist/cjs/providers/syncBlockProvider.js +2 -2
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +4 -0
- package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +148 -25
- package/dist/es2019/clients/block-service/blockSubscription.js +2 -2
- package/dist/es2019/providers/block-service/blockServiceAPI.js +2 -2
- package/dist/es2019/providers/syncBlockProvider.js +2 -2
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +4 -0
- package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +109 -6
- package/dist/esm/clients/block-service/blockSubscription.js +2 -2
- package/dist/esm/providers/block-service/blockServiceAPI.js +2 -2
- package/dist/esm/providers/syncBlockProvider.js +2 -2
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +4 -0
- package/dist/esm/store-manager/syncBlockSubscriptionManager.js +148 -25
- package/dist/types/clients/block-service/blockSubscription.d.ts +2 -1
- package/dist/types/entry-points/providers-types.d.ts +1 -1
- package/dist/types/providers/block-service/blockServiceAPI.d.ts +1 -1
- package/dist/types/providers/syncBlockProvider.d.ts +1 -1
- package/dist/types/providers/types.d.ts +4 -2
- package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +23 -0
- package/dist/types-ts4.5/clients/block-service/blockSubscription.d.ts +2 -1
- package/dist/types-ts4.5/entry-points/providers-types.d.ts +1 -1
- package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +1 -1
- package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +1 -1
- package/dist/types-ts4.5/providers/types.d.ts +4 -2
- package/dist/types-ts4.5/store-manager/syncBlockSubscriptionManager.d.ts +23 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @atlaskit/editor-synced-block-provider
|
|
2
2
|
|
|
3
|
+
## 6.6.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 6.6.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`bfba4158ff047`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/bfba4158ff047) -
|
|
14
|
+
Fix subscription reconnection for synced blocks when WebSocket or Relay subscriptions complete or
|
|
15
|
+
error silently
|
|
16
|
+
|
|
3
17
|
## 6.6.6
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -174,7 +174,7 @@ var parseSubscriptionPayload = function parseSubscriptionPayload(payload) {
|
|
|
174
174
|
* @param onError - Optional callback function invoked on subscription errors
|
|
175
175
|
* @returns Unsubscribe function to close the subscription
|
|
176
176
|
*/
|
|
177
|
-
var subscribeToBlockUpdates = exports.subscribeToBlockUpdates = function subscribeToBlockUpdates(blockAri, onData, onError) {
|
|
177
|
+
var subscribeToBlockUpdates = exports.subscribeToBlockUpdates = function subscribeToBlockUpdates(blockAri, onData, onError, onComplete) {
|
|
178
178
|
var client = getBlockServiceClient();
|
|
179
179
|
if (!client) {
|
|
180
180
|
// Return a no-op unsubscribe if client is not available (e.g., SSR)
|
|
@@ -210,7 +210,7 @@ var subscribeToBlockUpdates = exports.subscribeToBlockUpdates = function subscri
|
|
|
210
210
|
onError === null || onError === void 0 || onError(new Error(extractGraphQLWSErrorMessage(error)));
|
|
211
211
|
}),
|
|
212
212
|
complete: function complete() {
|
|
213
|
-
|
|
213
|
+
onComplete === null || onComplete === void 0 || onComplete();
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
216
|
return unsubscribe;
|
|
@@ -793,7 +793,7 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
|
|
|
793
793
|
)
|
|
794
794
|
}, {
|
|
795
795
|
key: "subscribeToBlockUpdates",
|
|
796
|
-
value: function subscribeToBlockUpdates(resourceId, onUpdate, onError) {
|
|
796
|
+
value: function subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete) {
|
|
797
797
|
var blockAri = (0, _ari.generateBlockAriFromReference)({
|
|
798
798
|
cloudId: this.cloudId,
|
|
799
799
|
resourceId: resourceId
|
|
@@ -829,7 +829,7 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
|
|
|
829
829
|
resourceId: parsedData.resourceId
|
|
830
830
|
};
|
|
831
831
|
onUpdate(syncBlockInstance);
|
|
832
|
-
}, onError);
|
|
832
|
+
}, onError, onComplete);
|
|
833
833
|
}).catch(function (err) {
|
|
834
834
|
if (cancelled) {
|
|
835
835
|
return;
|
|
@@ -469,9 +469,9 @@ var SyncedBlockProvider = exports.SyncedBlockProvider = /*#__PURE__*/function (_
|
|
|
469
469
|
*/
|
|
470
470
|
}, {
|
|
471
471
|
key: "subscribeToBlockUpdates",
|
|
472
|
-
value: function subscribeToBlockUpdates(resourceId, onUpdate, onError) {
|
|
472
|
+
value: function subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete) {
|
|
473
473
|
if (this.fetchProvider.subscribeToBlockUpdates) {
|
|
474
|
-
return this.fetchProvider.subscribeToBlockUpdates(resourceId, onUpdate, onError);
|
|
474
|
+
return this.fetchProvider.subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete);
|
|
475
475
|
}
|
|
476
476
|
return undefined;
|
|
477
477
|
}
|
|
@@ -211,6 +211,8 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
211
211
|
var syncBlockNode = (0, _utils.createSyncBlockNode)('', resourceId);
|
|
212
212
|
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;
|
|
213
213
|
if (providerData) {
|
|
214
|
+
// Initial provider cache data can come from SSR/prefetch and bypass updateCache(),
|
|
215
|
+
// so strip annotations here before references render existing synced block payloads.
|
|
214
216
|
return this.stripAnnotationMarksFromReferenceData(providerData);
|
|
215
217
|
}
|
|
216
218
|
return this.getFromSessionCache(resourceId);
|
|
@@ -248,6 +250,8 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
248
250
|
if (!raw) {
|
|
249
251
|
return undefined;
|
|
250
252
|
}
|
|
253
|
+
// Session cache entries written before this sanitizer existed may still include
|
|
254
|
+
// source annotation marks, so keep this read-time safety net for legacy data.
|
|
251
255
|
return this.stripAnnotationMarksFromReferenceData(JSON.parse(raw));
|
|
252
256
|
} catch (error) {
|
|
253
257
|
(0, _monitoring.logException)(error, {
|
|
@@ -9,6 +9,7 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
|
|
|
9
9
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
11
|
var _monitoring = require("@atlaskit/editor-common/monitoring");
|
|
12
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
12
13
|
var _errorHandling = require("../utils/errorHandling");
|
|
13
14
|
var _resolveSyncBlockInstance = require("../utils/resolveSyncBlockInstance");
|
|
14
15
|
var _utils = require("../utils/utils");
|
|
@@ -36,6 +37,8 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
36
37
|
// causing the cache to be deleted prematurely. We delay deletion to allow
|
|
37
38
|
// the new component to subscribe and cancel the pending deletion.
|
|
38
39
|
(0, _defineProperty2.default)(this, "pendingCacheDeletions", new Map());
|
|
40
|
+
(0, _defineProperty2.default)(this, "retryAttempts", new Map());
|
|
41
|
+
(0, _defineProperty2.default)(this, "pendingRetries", new Map());
|
|
39
42
|
this.deps = deps;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -250,6 +253,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
250
253
|
if (!(dataProvider !== null && dataProvider !== void 0 && dataProvider.subscribeToBlockUpdates)) {
|
|
251
254
|
return;
|
|
252
255
|
}
|
|
256
|
+
var reconnectEnabled = (0, _platformFeatureFlags.fg)('platform_synced_block_patch_12');
|
|
253
257
|
var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
|
|
254
258
|
_this5.handleGraphQLUpdate(syncBlockInstance);
|
|
255
259
|
}, function (error) {
|
|
@@ -258,11 +262,115 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
258
262
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
|
|
259
263
|
});
|
|
260
264
|
(_this5$deps$getFireAn = _this5.deps.getFireAnalyticsEvent()) === null || _this5$deps$getFireAn === void 0 || _this5$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
|
|
261
|
-
|
|
265
|
+
if (reconnectEnabled) {
|
|
266
|
+
_this5.handleSubscriptionTerminated(resourceId);
|
|
267
|
+
}
|
|
268
|
+
}, reconnectEnabled ? function () {
|
|
269
|
+
_this5.handleSubscriptionTerminated(resourceId);
|
|
270
|
+
} : undefined);
|
|
262
271
|
if (unsubscribe) {
|
|
263
272
|
this.graphqlSubscriptions.set(resourceId, unsubscribe);
|
|
264
273
|
}
|
|
265
274
|
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Handles subscription termination (complete or error) by cleaning up the
|
|
278
|
+
* stale entry and scheduling a reconnection with exponential backoff.
|
|
279
|
+
*/
|
|
280
|
+
}, {
|
|
281
|
+
key: "handleSubscriptionTerminated",
|
|
282
|
+
value: function handleSubscriptionTerminated(resourceId) {
|
|
283
|
+
// Remove the stale subscription entry so setupSubscription won't early-return
|
|
284
|
+
this.graphqlSubscriptions.delete(resourceId);
|
|
285
|
+
|
|
286
|
+
// Guard against duplicate calls (graphql-ws can fire both error and complete)
|
|
287
|
+
if (this.pendingRetries.has(resourceId)) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Only reconnect if there are still active subscribers for this resource
|
|
292
|
+
if (this.subscriptions.has(resourceId) && this.shouldUseRealTime()) {
|
|
293
|
+
this.scheduleReconnection(resourceId);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Schedules a reconnection attempt with exponential backoff.
|
|
299
|
+
* Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
|
|
300
|
+
* e.g. 1s, 2s, 4s, 8s, 16s
|
|
301
|
+
*/
|
|
302
|
+
}, {
|
|
303
|
+
key: "scheduleReconnection",
|
|
304
|
+
value: function scheduleReconnection(resourceId) {
|
|
305
|
+
var _this$retryAttempts$g,
|
|
306
|
+
_this6 = this;
|
|
307
|
+
var attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
|
|
308
|
+
if (attempts >= SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS) {
|
|
309
|
+
var _this$deps$getFireAna;
|
|
310
|
+
var errorMessage = "Subscription reconnection failed after ".concat(attempts, " attempts");
|
|
311
|
+
(0, _monitoring.logException)(new Error(errorMessage), {
|
|
312
|
+
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
|
|
313
|
+
});
|
|
314
|
+
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(errorMessage, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
var delay = SyncBlockSubscriptionManager.INITIAL_RETRY_DELAY_MS * Math.pow(SyncBlockSubscriptionManager.RETRY_BACKOFF_MULTIPLIER, attempts);
|
|
318
|
+
var timer = setTimeout(function () {
|
|
319
|
+
_this6.pendingRetries.delete(resourceId);
|
|
320
|
+
|
|
321
|
+
// Only re-subscribe if still relevant
|
|
322
|
+
if (_this6.subscriptions.has(resourceId) && _this6.shouldUseRealTime()) {
|
|
323
|
+
_this6.setupSubscription(resourceId);
|
|
324
|
+
|
|
325
|
+
// Only increment if the subscription was actually established
|
|
326
|
+
// (setupSubscription may be a no-op if another code path already re-established it)
|
|
327
|
+
if (_this6.graphqlSubscriptions.has(resourceId)) {
|
|
328
|
+
_this6.retryAttempts.set(resourceId, attempts + 1);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
// Conditions no longer met — clean up stale retry state
|
|
332
|
+
_this6.retryAttempts.delete(resourceId);
|
|
333
|
+
}
|
|
334
|
+
}, delay);
|
|
335
|
+
this.pendingRetries.set(resourceId, timer);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Resets the retry counter for a resource. Called when a successful
|
|
340
|
+
* update is received, indicating the subscription is healthy.
|
|
341
|
+
*/
|
|
342
|
+
}, {
|
|
343
|
+
key: "resetRetryCount",
|
|
344
|
+
value: function resetRetryCount(resourceId) {
|
|
345
|
+
this.retryAttempts.delete(resourceId);
|
|
346
|
+
}
|
|
347
|
+
}, {
|
|
348
|
+
key: "cancelPendingRetry",
|
|
349
|
+
value: function cancelPendingRetry(resourceId) {
|
|
350
|
+
var timer = this.pendingRetries.get(resourceId);
|
|
351
|
+
if (timer) {
|
|
352
|
+
clearTimeout(timer);
|
|
353
|
+
this.pendingRetries.delete(resourceId);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}, {
|
|
357
|
+
key: "cancelAllPendingRetries",
|
|
358
|
+
value: function cancelAllPendingRetries() {
|
|
359
|
+
var _iterator = _createForOfIteratorHelper(this.pendingRetries.values()),
|
|
360
|
+
_step;
|
|
361
|
+
try {
|
|
362
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
363
|
+
var timer = _step.value;
|
|
364
|
+
clearTimeout(timer);
|
|
365
|
+
}
|
|
366
|
+
} catch (err) {
|
|
367
|
+
_iterator.e(err);
|
|
368
|
+
} finally {
|
|
369
|
+
_iterator.f();
|
|
370
|
+
}
|
|
371
|
+
this.pendingRetries.clear();
|
|
372
|
+
this.retryAttempts.clear();
|
|
373
|
+
}
|
|
266
374
|
}, {
|
|
267
375
|
key: "cleanupSubscription",
|
|
268
376
|
value: function cleanupSubscription(resourceId) {
|
|
@@ -271,39 +379,46 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
271
379
|
unsubscribe();
|
|
272
380
|
this.graphqlSubscriptions.delete(resourceId);
|
|
273
381
|
}
|
|
382
|
+
if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_12')) {
|
|
383
|
+
this.cancelPendingRetry(resourceId);
|
|
384
|
+
this.retryAttempts.delete(resourceId);
|
|
385
|
+
}
|
|
274
386
|
}
|
|
275
387
|
}, {
|
|
276
388
|
key: "setupSubscriptionsForAllBlocks",
|
|
277
389
|
value: function setupSubscriptionsForAllBlocks() {
|
|
278
|
-
var
|
|
279
|
-
|
|
390
|
+
var _iterator2 = _createForOfIteratorHelper(this.subscriptions.keys()),
|
|
391
|
+
_step2;
|
|
280
392
|
try {
|
|
281
|
-
for (
|
|
282
|
-
var resourceId =
|
|
393
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
394
|
+
var resourceId = _step2.value;
|
|
283
395
|
this.setupSubscription(resourceId);
|
|
284
396
|
}
|
|
285
397
|
} catch (err) {
|
|
286
|
-
|
|
398
|
+
_iterator2.e(err);
|
|
287
399
|
} finally {
|
|
288
|
-
|
|
400
|
+
_iterator2.f();
|
|
289
401
|
}
|
|
290
402
|
}
|
|
291
403
|
}, {
|
|
292
404
|
key: "cleanupAll",
|
|
293
405
|
value: function cleanupAll() {
|
|
294
|
-
var
|
|
295
|
-
|
|
406
|
+
var _iterator3 = _createForOfIteratorHelper(this.graphqlSubscriptions.values()),
|
|
407
|
+
_step3;
|
|
296
408
|
try {
|
|
297
|
-
for (
|
|
298
|
-
var unsubscribe =
|
|
409
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
410
|
+
var unsubscribe = _step3.value;
|
|
299
411
|
unsubscribe();
|
|
300
412
|
}
|
|
301
413
|
} catch (err) {
|
|
302
|
-
|
|
414
|
+
_iterator3.e(err);
|
|
303
415
|
} finally {
|
|
304
|
-
|
|
416
|
+
_iterator3.f();
|
|
305
417
|
}
|
|
306
418
|
this.graphqlSubscriptions.clear();
|
|
419
|
+
if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_12')) {
|
|
420
|
+
this.cancelAllPendingRetries();
|
|
421
|
+
}
|
|
307
422
|
}
|
|
308
423
|
}, {
|
|
309
424
|
key: "destroy",
|
|
@@ -315,17 +430,17 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
315
430
|
this.useRealTimeSubscriptions = false;
|
|
316
431
|
|
|
317
432
|
// Clear any pending cache deletions
|
|
318
|
-
var
|
|
319
|
-
|
|
433
|
+
var _iterator4 = _createForOfIteratorHelper(this.pendingCacheDeletions.values()),
|
|
434
|
+
_step4;
|
|
320
435
|
try {
|
|
321
|
-
for (
|
|
322
|
-
var timeout =
|
|
436
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
437
|
+
var timeout = _step4.value;
|
|
323
438
|
clearTimeout(timeout);
|
|
324
439
|
}
|
|
325
440
|
} catch (err) {
|
|
326
|
-
|
|
441
|
+
_iterator4.e(err);
|
|
327
442
|
} finally {
|
|
328
|
-
|
|
443
|
+
_iterator4.f();
|
|
329
444
|
}
|
|
330
445
|
this.pendingCacheDeletions.clear();
|
|
331
446
|
}
|
|
@@ -337,7 +452,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
337
452
|
}, {
|
|
338
453
|
key: "handleGraphQLUpdate",
|
|
339
454
|
value: function handleGraphQLUpdate(syncBlockInstance) {
|
|
340
|
-
var
|
|
455
|
+
var _this7 = this;
|
|
341
456
|
if (!syncBlockInstance.resourceId) {
|
|
342
457
|
return;
|
|
343
458
|
}
|
|
@@ -345,18 +460,26 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
345
460
|
var resolved = existing ? (0, _resolveSyncBlockInstance.resolveSyncBlockInstance)(existing, syncBlockInstance) : syncBlockInstance;
|
|
346
461
|
this.deps.updateCache(resolved);
|
|
347
462
|
if (!syncBlockInstance.error) {
|
|
463
|
+
// Successful data delivery means the subscription is healthy — reset retry counter
|
|
464
|
+
if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_12')) {
|
|
465
|
+
this.resetRetryCount(syncBlockInstance.resourceId);
|
|
466
|
+
}
|
|
348
467
|
var callbacks = this.subscriptions.get(syncBlockInstance.resourceId);
|
|
349
468
|
var localIds = callbacks ? Object.keys(callbacks) : [];
|
|
350
469
|
localIds.forEach(function (localId) {
|
|
351
|
-
var
|
|
352
|
-
(
|
|
470
|
+
var _this7$deps$getFireAn, _syncBlockInstance$da, _syncBlockInstance$da2;
|
|
471
|
+
(_this7$deps$getFireAn = _this7.deps.getFireAnalyticsEvent()) === null || _this7$deps$getFireAn === void 0 || _this7$deps$getFireAn((0, _errorHandling.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 : (0, _utils.getSourceProductFromResourceIdSafe)(syncBlockInstance.resourceId)));
|
|
353
472
|
});
|
|
354
473
|
this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
|
|
355
474
|
} else {
|
|
356
|
-
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$
|
|
475
|
+
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna2, _syncBlockInstance$da3, _syncBlockInstance$da4;
|
|
357
476
|
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);
|
|
358
|
-
(_this$deps$
|
|
477
|
+
(_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2((0, _errorHandling.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 : (0, _utils.getSourceProductFromResourceIdSafe)(syncBlockInstance.resourceId)));
|
|
359
478
|
}
|
|
360
479
|
}
|
|
361
480
|
}]);
|
|
362
|
-
}();
|
|
481
|
+
}();
|
|
482
|
+
// Reconnection with exponential backoff
|
|
483
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "INITIAL_RETRY_DELAY_MS", 1000);
|
|
484
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "RETRY_BACKOFF_MULTIPLIER", 2);
|
|
485
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
@@ -181,7 +181,7 @@ const parseSubscriptionPayload = payload => {
|
|
|
181
181
|
* @param onError - Optional callback function invoked on subscription errors
|
|
182
182
|
* @returns Unsubscribe function to close the subscription
|
|
183
183
|
*/
|
|
184
|
-
export const subscribeToBlockUpdates = (blockAri, onData, onError) => {
|
|
184
|
+
export const subscribeToBlockUpdates = (blockAri, onData, onError, onComplete) => {
|
|
185
185
|
const client = getBlockServiceClient();
|
|
186
186
|
if (!client) {
|
|
187
187
|
// Return a no-op unsubscribe if client is not available (e.g., SSR)
|
|
@@ -209,7 +209,7 @@ export const subscribeToBlockUpdates = (blockAri, onData, onError) => {
|
|
|
209
209
|
onError === null || onError === void 0 ? void 0 : onError(new Error(extractGraphQLWSErrorMessage(error)));
|
|
210
210
|
},
|
|
211
211
|
complete: () => {
|
|
212
|
-
|
|
212
|
+
onComplete === null || onComplete === void 0 ? void 0 : onComplete();
|
|
213
213
|
}
|
|
214
214
|
});
|
|
215
215
|
return unsubscribe;
|
|
@@ -533,7 +533,7 @@ class BlockServiceADFFetchProvider {
|
|
|
533
533
|
* @param onError - Optional callback function invoked on subscription errors
|
|
534
534
|
* @returns Unsubscribe function to stop receiving updates
|
|
535
535
|
*/
|
|
536
|
-
subscribeToBlockUpdates(resourceId, onUpdate, onError) {
|
|
536
|
+
subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete) {
|
|
537
537
|
const blockAri = generateBlockAriFromReference({
|
|
538
538
|
cloudId: this.cloudId,
|
|
539
539
|
resourceId
|
|
@@ -568,7 +568,7 @@ class BlockServiceADFFetchProvider {
|
|
|
568
568
|
resourceId: parsedData.resourceId
|
|
569
569
|
};
|
|
570
570
|
onUpdate(syncBlockInstance);
|
|
571
|
-
}, onError);
|
|
571
|
+
}, onError, onComplete);
|
|
572
572
|
}).catch(err => {
|
|
573
573
|
if (cancelled) {
|
|
574
574
|
return;
|
|
@@ -313,9 +313,9 @@ export class SyncedBlockProvider extends SyncBlockDataProviderInterface {
|
|
|
313
313
|
* @param onError - Optional callback function invoked on subscription errors
|
|
314
314
|
* @returns Unsubscribe function to stop receiving updates, or undefined if not supported
|
|
315
315
|
*/
|
|
316
|
-
subscribeToBlockUpdates(resourceId, onUpdate, onError) {
|
|
316
|
+
subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete) {
|
|
317
317
|
if (this.fetchProvider.subscribeToBlockUpdates) {
|
|
318
|
-
return this.fetchProvider.subscribeToBlockUpdates(resourceId, onUpdate, onError);
|
|
318
|
+
return this.fetchProvider.subscribeToBlockUpdates(resourceId, onUpdate, onError, onComplete);
|
|
319
319
|
}
|
|
320
320
|
return undefined;
|
|
321
321
|
}
|
|
@@ -142,6 +142,8 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
142
142
|
const syncBlockNode = createSyncBlockNode('', resourceId);
|
|
143
143
|
const providerData = (_this$dataProvider2 = this.dataProvider) === null || _this$dataProvider2 === void 0 ? void 0 : (_this$dataProvider2$g = _this$dataProvider2.getNodeDataFromCache(syncBlockNode)) === null || _this$dataProvider2$g === void 0 ? void 0 : _this$dataProvider2$g.data;
|
|
144
144
|
if (providerData) {
|
|
145
|
+
// Initial provider cache data can come from SSR/prefetch and bypass updateCache(),
|
|
146
|
+
// so strip annotations here before references render existing synced block payloads.
|
|
145
147
|
return this.stripAnnotationMarksFromReferenceData(providerData);
|
|
146
148
|
}
|
|
147
149
|
return this.getFromSessionCache(resourceId);
|
|
@@ -175,6 +177,8 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
175
177
|
if (!raw) {
|
|
176
178
|
return undefined;
|
|
177
179
|
}
|
|
180
|
+
// Session cache entries written before this sanitizer existed may still include
|
|
181
|
+
// source annotation marks, so keep this read-time safety net for legacy data.
|
|
178
182
|
return this.stripAnnotationMarksFromReferenceData(JSON.parse(raw));
|
|
179
183
|
} catch (error) {
|
|
180
184
|
logException(error, {
|
|
@@ -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$
|
|
275
|
-
(_this$deps$
|
|
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$
|
|
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$
|
|
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);
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -204,6 +204,8 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
204
204
|
var syncBlockNode = createSyncBlockNode('', resourceId);
|
|
205
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;
|
|
206
206
|
if (providerData) {
|
|
207
|
+
// Initial provider cache data can come from SSR/prefetch and bypass updateCache(),
|
|
208
|
+
// so strip annotations here before references render existing synced block payloads.
|
|
207
209
|
return this.stripAnnotationMarksFromReferenceData(providerData);
|
|
208
210
|
}
|
|
209
211
|
return this.getFromSessionCache(resourceId);
|
|
@@ -241,6 +243,8 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
241
243
|
if (!raw) {
|
|
242
244
|
return undefined;
|
|
243
245
|
}
|
|
246
|
+
// Session cache entries written before this sanitizer existed may still include
|
|
247
|
+
// source annotation marks, so keep this read-time safety net for legacy data.
|
|
244
248
|
return this.stripAnnotationMarksFromReferenceData(JSON.parse(raw));
|
|
245
249
|
} catch (error) {
|
|
246
250
|
logException(error, {
|
|
@@ -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
|
|
272
|
-
|
|
383
|
+
var _iterator2 = _createForOfIteratorHelper(this.subscriptions.keys()),
|
|
384
|
+
_step2;
|
|
273
385
|
try {
|
|
274
|
-
for (
|
|
275
|
-
var resourceId =
|
|
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
|
-
|
|
391
|
+
_iterator2.e(err);
|
|
280
392
|
} finally {
|
|
281
|
-
|
|
393
|
+
_iterator2.f();
|
|
282
394
|
}
|
|
283
395
|
}
|
|
284
396
|
}, {
|
|
285
397
|
key: "cleanupAll",
|
|
286
398
|
value: function cleanupAll() {
|
|
287
|
-
var
|
|
288
|
-
|
|
399
|
+
var _iterator3 = _createForOfIteratorHelper(this.graphqlSubscriptions.values()),
|
|
400
|
+
_step3;
|
|
289
401
|
try {
|
|
290
|
-
for (
|
|
291
|
-
var unsubscribe =
|
|
402
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
403
|
+
var unsubscribe = _step3.value;
|
|
292
404
|
unsubscribe();
|
|
293
405
|
}
|
|
294
406
|
} catch (err) {
|
|
295
|
-
|
|
407
|
+
_iterator3.e(err);
|
|
296
408
|
} finally {
|
|
297
|
-
|
|
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
|
|
312
|
-
|
|
426
|
+
var _iterator4 = _createForOfIteratorHelper(this.pendingCacheDeletions.values()),
|
|
427
|
+
_step4;
|
|
313
428
|
try {
|
|
314
|
-
for (
|
|
315
|
-
var timeout =
|
|
429
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
430
|
+
var timeout = _step4.value;
|
|
316
431
|
clearTimeout(timeout);
|
|
317
432
|
}
|
|
318
433
|
} catch (err) {
|
|
319
|
-
|
|
434
|
+
_iterator4.e(err);
|
|
320
435
|
} finally {
|
|
321
|
-
|
|
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
|
|
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
|
|
345
|
-
(
|
|
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$
|
|
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$
|
|
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);
|
|
@@ -31,6 +31,7 @@ export type ParsedBlockSubscriptionData = {
|
|
|
31
31
|
};
|
|
32
32
|
type SubscriptionCallback = (data: ParsedBlockSubscriptionData) => void;
|
|
33
33
|
type ErrorCallback = (error: Error) => void;
|
|
34
|
+
type CompleteCallback = () => void;
|
|
34
35
|
type Unsubscribe = () => void;
|
|
35
36
|
/**
|
|
36
37
|
* Extracts a meaningful error message from the error: GraphQL WebSocket Error: {"isTrusted":true}
|
|
@@ -47,5 +48,5 @@ export declare const extractGraphQLWSErrorMessage: (error: unknown) => string;
|
|
|
47
48
|
* @param onError - Optional callback function invoked on subscription errors
|
|
48
49
|
* @returns Unsubscribe function to close the subscription
|
|
49
50
|
*/
|
|
50
|
-
export declare const subscribeToBlockUpdates: (blockAri: string, onData: SubscriptionCallback, onError?: ErrorCallback) => Unsubscribe;
|
|
51
|
+
export declare const subscribeToBlockUpdates: (blockAri: string, onData: SubscriptionCallback, onError?: ErrorCallback, onComplete?: CompleteCallback) => Unsubscribe;
|
|
51
52
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { ADFFetchProvider, ADFWriteProvider, BatchFetchConfig, BlockNodeIdentifiers, BlockSubscriptionErrorCallback, BlockUpdateCallback, SyncBlockDataProviderInterface, SyncBlockInstance, MediaEmojiProviderOptions, SyncedBlockRendererProviderOptions, SyncBlockRendererProviderCreator, SyncedBlockRendererDataProviders, Unsubscribe, UpdateReferenceSyncBlockResult, WriteSyncBlockResult, SyncBlockParentInfo, SyncBlockSourceInfo, SyncBlockJiraIssueType, } from '../providers/types';
|
|
1
|
+
export type { ADFFetchProvider, ADFWriteProvider, BatchFetchConfig, BlockNodeIdentifiers, BlockSubscriptionCompleteCallback, BlockSubscriptionErrorCallback, BlockUpdateCallback, SyncBlockDataProviderInterface, SyncBlockInstance, MediaEmojiProviderOptions, SyncedBlockRendererProviderOptions, SyncBlockRendererProviderCreator, SyncedBlockRendererDataProviders, Unsubscribe, UpdateReferenceSyncBlockResult, WriteSyncBlockResult, SyncBlockParentInfo, SyncBlockSourceInfo, SyncBlockJiraIssueType, } from '../providers/types';
|
|
@@ -73,7 +73,7 @@ declare class BlockServiceADFFetchProvider implements ADFFetchProvider {
|
|
|
73
73
|
* @param onError - Optional callback function invoked on subscription errors
|
|
74
74
|
* @returns Unsubscribe function to stop receiving updates
|
|
75
75
|
*/
|
|
76
|
-
subscribeToBlockUpdates(resourceId: ResourceId, onUpdate: (data: SyncBlockInstance) => void, onError?: (error: Error) => void): () => void;
|
|
76
|
+
subscribeToBlockUpdates(resourceId: ResourceId, onUpdate: (data: SyncBlockInstance) => void, onError?: (error: Error) => void, onComplete?: () => void): () => void;
|
|
77
77
|
}
|
|
78
78
|
interface BlockServiceADFWriteProviderProps {
|
|
79
79
|
cloudId: string;
|
|
@@ -98,7 +98,7 @@ export declare class SyncedBlockProvider extends SyncBlockDataProviderInterface
|
|
|
98
98
|
* @param onError - Optional callback function invoked on subscription errors
|
|
99
99
|
* @returns Unsubscribe function to stop receiving updates, or undefined if not supported
|
|
100
100
|
*/
|
|
101
|
-
subscribeToBlockUpdates(resourceId: string, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback): Unsubscribe | undefined;
|
|
101
|
+
subscribeToBlockUpdates(resourceId: string, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback, onComplete?: () => void): Unsubscribe | undefined;
|
|
102
102
|
}
|
|
103
103
|
type UseMemoizedSyncedBlockProviderProps = {
|
|
104
104
|
fetchProvider: ADFFetchProvider;
|
|
@@ -92,6 +92,7 @@ export type BatchFetchConfig = {
|
|
|
92
92
|
};
|
|
93
93
|
export type BlockUpdateCallback = (data: SyncBlockInstance) => void;
|
|
94
94
|
export type BlockSubscriptionErrorCallback = (error: Error) => void;
|
|
95
|
+
export type BlockSubscriptionCompleteCallback = () => void;
|
|
95
96
|
export type Unsubscribe = () => void;
|
|
96
97
|
export interface ADFFetchProvider {
|
|
97
98
|
batchFetchData: (blockNodeIdentifiers: BlockNodeIdentifiers[], config?: BatchFetchConfig) => Promise<SyncBlockInstance[]>;
|
|
@@ -100,7 +101,7 @@ export interface ADFFetchProvider {
|
|
|
100
101
|
/**
|
|
101
102
|
* Subscribes to real-time updates for a specific block.
|
|
102
103
|
*/
|
|
103
|
-
subscribeToBlockUpdates?: (resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback) => Unsubscribe;
|
|
104
|
+
subscribeToBlockUpdates?: (resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback, onComplete?: BlockSubscriptionCompleteCallback) => Unsubscribe;
|
|
104
105
|
}
|
|
105
106
|
export interface ADFWriteProvider {
|
|
106
107
|
createData: (data: SyncBlockData) => Promise<WriteSyncBlockResult>;
|
|
@@ -174,9 +175,10 @@ export declare abstract class SyncBlockDataProviderInterface extends NodeDataPro
|
|
|
174
175
|
* @param resourceId - The resource ID of the block to subscribe to
|
|
175
176
|
* @param onUpdate - Callback function invoked when the block is updated
|
|
176
177
|
* @param onError - Optional callback function invoked on subscription errors
|
|
178
|
+
* @param onComplete - Optional callback function invoked when the subscription completes
|
|
177
179
|
* @returns Unsubscribe function to stop receiving updates, or undefined if not supported
|
|
178
180
|
*/
|
|
179
|
-
subscribeToBlockUpdates?(resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback): Unsubscribe | undefined;
|
|
181
|
+
subscribeToBlockUpdates?(resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback, onComplete?: BlockSubscriptionCompleteCallback): Unsubscribe | undefined;
|
|
180
182
|
}
|
|
181
183
|
export type SubscriptionCallback = (data: SyncBlockInstance) => void;
|
|
182
184
|
export type TitleSubscriptionCallback = (title: string) => void;
|
|
@@ -26,6 +26,11 @@ export declare class SyncBlockSubscriptionManager {
|
|
|
26
26
|
private subscriptionChangeListeners;
|
|
27
27
|
private useRealTimeSubscriptions;
|
|
28
28
|
private pendingCacheDeletions;
|
|
29
|
+
private static readonly INITIAL_RETRY_DELAY_MS;
|
|
30
|
+
private static readonly RETRY_BACKOFF_MULTIPLIER;
|
|
31
|
+
private static readonly MAX_RETRY_ATTEMPTS;
|
|
32
|
+
private retryAttempts;
|
|
33
|
+
private pendingRetries;
|
|
29
34
|
constructor(deps: SyncBlockSubscriptionManagerDeps);
|
|
30
35
|
/**
|
|
31
36
|
* Returns the subscriptions map. Used by external consumers (e.g. batch fetcher, flush)
|
|
@@ -48,6 +53,24 @@ export declare class SyncBlockSubscriptionManager {
|
|
|
48
53
|
*/
|
|
49
54
|
notifySubscriptionCallbacks(resourceId: ResourceId, syncBlock: SyncBlockInstance): void;
|
|
50
55
|
setupSubscription(resourceId: ResourceId): void;
|
|
56
|
+
/**
|
|
57
|
+
* Handles subscription termination (complete or error) by cleaning up the
|
|
58
|
+
* stale entry and scheduling a reconnection with exponential backoff.
|
|
59
|
+
*/
|
|
60
|
+
private handleSubscriptionTerminated;
|
|
61
|
+
/**
|
|
62
|
+
* Schedules a reconnection attempt with exponential backoff.
|
|
63
|
+
* Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
|
|
64
|
+
* e.g. 1s, 2s, 4s, 8s, 16s
|
|
65
|
+
*/
|
|
66
|
+
private scheduleReconnection;
|
|
67
|
+
/**
|
|
68
|
+
* Resets the retry counter for a resource. Called when a successful
|
|
69
|
+
* update is received, indicating the subscription is healthy.
|
|
70
|
+
*/
|
|
71
|
+
private resetRetryCount;
|
|
72
|
+
private cancelPendingRetry;
|
|
73
|
+
private cancelAllPendingRetries;
|
|
51
74
|
cleanupSubscription(resourceId: ResourceId): void;
|
|
52
75
|
setupSubscriptionsForAllBlocks(): void;
|
|
53
76
|
cleanupAll(): void;
|
|
@@ -31,6 +31,7 @@ export type ParsedBlockSubscriptionData = {
|
|
|
31
31
|
};
|
|
32
32
|
type SubscriptionCallback = (data: ParsedBlockSubscriptionData) => void;
|
|
33
33
|
type ErrorCallback = (error: Error) => void;
|
|
34
|
+
type CompleteCallback = () => void;
|
|
34
35
|
type Unsubscribe = () => void;
|
|
35
36
|
/**
|
|
36
37
|
* Extracts a meaningful error message from the error: GraphQL WebSocket Error: {"isTrusted":true}
|
|
@@ -47,5 +48,5 @@ export declare const extractGraphQLWSErrorMessage: (error: unknown) => string;
|
|
|
47
48
|
* @param onError - Optional callback function invoked on subscription errors
|
|
48
49
|
* @returns Unsubscribe function to close the subscription
|
|
49
50
|
*/
|
|
50
|
-
export declare const subscribeToBlockUpdates: (blockAri: string, onData: SubscriptionCallback, onError?: ErrorCallback) => Unsubscribe;
|
|
51
|
+
export declare const subscribeToBlockUpdates: (blockAri: string, onData: SubscriptionCallback, onError?: ErrorCallback, onComplete?: CompleteCallback) => Unsubscribe;
|
|
51
52
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { ADFFetchProvider, ADFWriteProvider, BatchFetchConfig, BlockNodeIdentifiers, BlockSubscriptionErrorCallback, BlockUpdateCallback, SyncBlockDataProviderInterface, SyncBlockInstance, MediaEmojiProviderOptions, SyncedBlockRendererProviderOptions, SyncBlockRendererProviderCreator, SyncedBlockRendererDataProviders, Unsubscribe, UpdateReferenceSyncBlockResult, WriteSyncBlockResult, SyncBlockParentInfo, SyncBlockSourceInfo, SyncBlockJiraIssueType, } from '../providers/types';
|
|
1
|
+
export type { ADFFetchProvider, ADFWriteProvider, BatchFetchConfig, BlockNodeIdentifiers, BlockSubscriptionCompleteCallback, BlockSubscriptionErrorCallback, BlockUpdateCallback, SyncBlockDataProviderInterface, SyncBlockInstance, MediaEmojiProviderOptions, SyncedBlockRendererProviderOptions, SyncBlockRendererProviderCreator, SyncedBlockRendererDataProviders, Unsubscribe, UpdateReferenceSyncBlockResult, WriteSyncBlockResult, SyncBlockParentInfo, SyncBlockSourceInfo, SyncBlockJiraIssueType, } from '../providers/types';
|
|
@@ -73,7 +73,7 @@ declare class BlockServiceADFFetchProvider implements ADFFetchProvider {
|
|
|
73
73
|
* @param onError - Optional callback function invoked on subscription errors
|
|
74
74
|
* @returns Unsubscribe function to stop receiving updates
|
|
75
75
|
*/
|
|
76
|
-
subscribeToBlockUpdates(resourceId: ResourceId, onUpdate: (data: SyncBlockInstance) => void, onError?: (error: Error) => void): () => void;
|
|
76
|
+
subscribeToBlockUpdates(resourceId: ResourceId, onUpdate: (data: SyncBlockInstance) => void, onError?: (error: Error) => void, onComplete?: () => void): () => void;
|
|
77
77
|
}
|
|
78
78
|
interface BlockServiceADFWriteProviderProps {
|
|
79
79
|
cloudId: string;
|
|
@@ -98,7 +98,7 @@ export declare class SyncedBlockProvider extends SyncBlockDataProviderInterface
|
|
|
98
98
|
* @param onError - Optional callback function invoked on subscription errors
|
|
99
99
|
* @returns Unsubscribe function to stop receiving updates, or undefined if not supported
|
|
100
100
|
*/
|
|
101
|
-
subscribeToBlockUpdates(resourceId: string, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback): Unsubscribe | undefined;
|
|
101
|
+
subscribeToBlockUpdates(resourceId: string, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback, onComplete?: () => void): Unsubscribe | undefined;
|
|
102
102
|
}
|
|
103
103
|
type UseMemoizedSyncedBlockProviderProps = {
|
|
104
104
|
fetchProvider: ADFFetchProvider;
|
|
@@ -92,6 +92,7 @@ export type BatchFetchConfig = {
|
|
|
92
92
|
};
|
|
93
93
|
export type BlockUpdateCallback = (data: SyncBlockInstance) => void;
|
|
94
94
|
export type BlockSubscriptionErrorCallback = (error: Error) => void;
|
|
95
|
+
export type BlockSubscriptionCompleteCallback = () => void;
|
|
95
96
|
export type Unsubscribe = () => void;
|
|
96
97
|
export interface ADFFetchProvider {
|
|
97
98
|
batchFetchData: (blockNodeIdentifiers: BlockNodeIdentifiers[], config?: BatchFetchConfig) => Promise<SyncBlockInstance[]>;
|
|
@@ -100,7 +101,7 @@ export interface ADFFetchProvider {
|
|
|
100
101
|
/**
|
|
101
102
|
* Subscribes to real-time updates for a specific block.
|
|
102
103
|
*/
|
|
103
|
-
subscribeToBlockUpdates?: (resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback) => Unsubscribe;
|
|
104
|
+
subscribeToBlockUpdates?: (resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback, onComplete?: BlockSubscriptionCompleteCallback) => Unsubscribe;
|
|
104
105
|
}
|
|
105
106
|
export interface ADFWriteProvider {
|
|
106
107
|
createData: (data: SyncBlockData) => Promise<WriteSyncBlockResult>;
|
|
@@ -174,9 +175,10 @@ export declare abstract class SyncBlockDataProviderInterface extends NodeDataPro
|
|
|
174
175
|
* @param resourceId - The resource ID of the block to subscribe to
|
|
175
176
|
* @param onUpdate - Callback function invoked when the block is updated
|
|
176
177
|
* @param onError - Optional callback function invoked on subscription errors
|
|
178
|
+
* @param onComplete - Optional callback function invoked when the subscription completes
|
|
177
179
|
* @returns Unsubscribe function to stop receiving updates, or undefined if not supported
|
|
178
180
|
*/
|
|
179
|
-
subscribeToBlockUpdates?(resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback): Unsubscribe | undefined;
|
|
181
|
+
subscribeToBlockUpdates?(resourceId: ResourceId, onUpdate: BlockUpdateCallback, onError?: BlockSubscriptionErrorCallback, onComplete?: BlockSubscriptionCompleteCallback): Unsubscribe | undefined;
|
|
180
182
|
}
|
|
181
183
|
export type SubscriptionCallback = (data: SyncBlockInstance) => void;
|
|
182
184
|
export type TitleSubscriptionCallback = (title: string) => void;
|
|
@@ -26,6 +26,11 @@ export declare class SyncBlockSubscriptionManager {
|
|
|
26
26
|
private subscriptionChangeListeners;
|
|
27
27
|
private useRealTimeSubscriptions;
|
|
28
28
|
private pendingCacheDeletions;
|
|
29
|
+
private static readonly INITIAL_RETRY_DELAY_MS;
|
|
30
|
+
private static readonly RETRY_BACKOFF_MULTIPLIER;
|
|
31
|
+
private static readonly MAX_RETRY_ATTEMPTS;
|
|
32
|
+
private retryAttempts;
|
|
33
|
+
private pendingRetries;
|
|
29
34
|
constructor(deps: SyncBlockSubscriptionManagerDeps);
|
|
30
35
|
/**
|
|
31
36
|
* Returns the subscriptions map. Used by external consumers (e.g. batch fetcher, flush)
|
|
@@ -48,6 +53,24 @@ export declare class SyncBlockSubscriptionManager {
|
|
|
48
53
|
*/
|
|
49
54
|
notifySubscriptionCallbacks(resourceId: ResourceId, syncBlock: SyncBlockInstance): void;
|
|
50
55
|
setupSubscription(resourceId: ResourceId): void;
|
|
56
|
+
/**
|
|
57
|
+
* Handles subscription termination (complete or error) by cleaning up the
|
|
58
|
+
* stale entry and scheduling a reconnection with exponential backoff.
|
|
59
|
+
*/
|
|
60
|
+
private handleSubscriptionTerminated;
|
|
61
|
+
/**
|
|
62
|
+
* Schedules a reconnection attempt with exponential backoff.
|
|
63
|
+
* Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
|
|
64
|
+
* e.g. 1s, 2s, 4s, 8s, 16s
|
|
65
|
+
*/
|
|
66
|
+
private scheduleReconnection;
|
|
67
|
+
/**
|
|
68
|
+
* Resets the retry counter for a resource. Called when a successful
|
|
69
|
+
* update is received, indicating the subscription is healthy.
|
|
70
|
+
*/
|
|
71
|
+
private resetRetryCount;
|
|
72
|
+
private cancelPendingRetry;
|
|
73
|
+
private cancelAllPendingRetries;
|
|
51
74
|
cleanupSubscription(resourceId: ResourceId): void;
|
|
52
75
|
setupSubscriptionsForAllBlocks(): void;
|
|
53
76
|
cleanupAll(): void;
|
package/package.json
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
],
|
|
25
25
|
"atlaskit:src": "src/index.ts",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@atlaskit/adf-utils": "^19.
|
|
27
|
+
"@atlaskit/adf-utils": "^19.31.0",
|
|
28
28
|
"@atlaskit/editor-json-transformer": "^8.32.0",
|
|
29
29
|
"@atlaskit/editor-prosemirror": "^7.3.0",
|
|
30
30
|
"@atlaskit/node-data-provider": "^11.1.0",
|
|
31
31
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
32
|
-
"@atlaskit/tmp-editor-statsig": "^
|
|
32
|
+
"@atlaskit/tmp-editor-statsig": "^83.0.0",
|
|
33
33
|
"@babel/runtime": "^7.0.0",
|
|
34
34
|
"@compiled/react": "^0.20.0",
|
|
35
35
|
"graphql-ws": "^5.14.2",
|
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
"uuid": "^3.1.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@atlaskit/editor-common": "^114.
|
|
41
|
+
"@atlaskit/editor-common": "^114.46.0",
|
|
42
42
|
"react": "^18.2.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@testing-library/react": "^16.3.0",
|
|
46
|
+
"react": "^18.2.0",
|
|
46
47
|
"react-dom": "^18.2.0"
|
|
47
48
|
},
|
|
48
49
|
"techstack": {
|
|
@@ -81,7 +82,7 @@
|
|
|
81
82
|
}
|
|
82
83
|
},
|
|
83
84
|
"name": "@atlaskit/editor-synced-block-provider",
|
|
84
|
-
"version": "6.6.
|
|
85
|
+
"version": "6.6.8",
|
|
85
86
|
"description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
|
|
86
87
|
"author": "Atlassian Pty Ltd",
|
|
87
88
|
"license": "Apache-2.0",
|