@atlaskit/editor-synced-block-provider 8.6.0 → 8.6.2
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 +17 -0
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +1 -1
- package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +216 -66
- package/dist/cjs/utils/errorHandling.js +9 -4
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +1 -1
- package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +133 -7
- package/dist/es2019/utils/errorHandling.js +11 -2
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +1 -1
- package/dist/esm/store-manager/syncBlockSubscriptionManager.js +216 -66
- package/dist/esm/utils/errorHandling.js +9 -4
- package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +28 -0
- package/dist/types/utils/errorHandling.d.ts +9 -1
- package/package.json +5 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @atlaskit/editor-synced-block-provider
|
|
2
2
|
|
|
3
|
+
## 8.6.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 8.6.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`f543995529d9d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f543995529d9d) -
|
|
14
|
+
[ux] Synced blocks now recover automatically from realtime connection drops that happen while a
|
|
15
|
+
tab is hidden or the network is offline. Instead of showing a fetch error when reconnection
|
|
16
|
+
attempts are exhausted in the background, the block silently re-arms and reconnects when the tab
|
|
17
|
+
becomes visible again or the network comes back online.
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
|
|
3
20
|
## 8.6.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -1020,7 +1020,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
1020
1020
|
_context4.next = 1;
|
|
1021
1021
|
break;
|
|
1022
1022
|
}
|
|
1023
|
-
return _context4.abrupt("return",
|
|
1023
|
+
return _context4.abrupt("return", true);
|
|
1024
1024
|
case 1:
|
|
1025
1025
|
if (this.isCacheDirty) {
|
|
1026
1026
|
_context4.next = 2;
|
|
@@ -8,6 +8,8 @@ exports.SyncBlockSubscriptionManager = void 0;
|
|
|
8
8
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
9
9
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
var _bindEventListener = require("bind-event-listener");
|
|
12
|
+
var _browserApis = require("@atlaskit/browser-apis");
|
|
11
13
|
var _monitoring = require("@atlaskit/editor-common/monitoring");
|
|
12
14
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
13
15
|
var _errorHandling = require("../utils/errorHandling");
|
|
@@ -26,6 +28,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
26
28
|
*/
|
|
27
29
|
var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
28
30
|
function SyncBlockSubscriptionManager(deps) {
|
|
31
|
+
var _this = this;
|
|
29
32
|
(0, _classCallCheck2.default)(this, SyncBlockSubscriptionManager);
|
|
30
33
|
(0, _defineProperty2.default)(this, "subscriptions", new Map());
|
|
31
34
|
(0, _defineProperty2.default)(this, "titleSubscriptions", new Map());
|
|
@@ -40,6 +43,24 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
40
43
|
// backoff cap (gate ON)
|
|
41
44
|
(0, _defineProperty2.default)(this, "retryAttempts", new Map());
|
|
42
45
|
(0, _defineProperty2.default)(this, "pendingRetries", new Map());
|
|
46
|
+
// Resources whose reconnection exhausted while the tab was hidden: parked here and
|
|
47
|
+
// re-armed on the next online / visibilitychange→visible instead of surfacing a
|
|
48
|
+
// terminal error. Only a re-armed attempt that also exhausts while visible fails.
|
|
49
|
+
(0, _defineProperty2.default)(this, "deferredExhausted", new Set());
|
|
50
|
+
(0, _defineProperty2.default)(this, "wakeDebounceTimer", null);
|
|
51
|
+
// `online` always sweeps (network is back regardless of tab visibility); a
|
|
52
|
+
// `visibilitychange` only sweeps when the tab became visible.
|
|
53
|
+
(0, _defineProperty2.default)(this, "onOnline", function () {
|
|
54
|
+
return _this.scheduleWakeSweep();
|
|
55
|
+
});
|
|
56
|
+
(0, _defineProperty2.default)(this, "onVisibilityChange", function () {
|
|
57
|
+
if (!_this.isDocumentHidden()) {
|
|
58
|
+
_this.scheduleWakeSweep();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// Cleanup fns returned by `bind` for the registered wake listeners; a non-empty
|
|
62
|
+
// array means listeners are currently registered. Emptied on teardown.
|
|
63
|
+
(0, _defineProperty2.default)(this, "wakeListenerCleanups", []);
|
|
43
64
|
this.deps = deps;
|
|
44
65
|
}
|
|
45
66
|
|
|
@@ -102,25 +123,25 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
102
123
|
}, {
|
|
103
124
|
key: "onSubscriptionsChanged",
|
|
104
125
|
value: function onSubscriptionsChanged(listener) {
|
|
105
|
-
var
|
|
126
|
+
var _this2 = this;
|
|
106
127
|
this.subscriptionChangeListeners.add(listener);
|
|
107
128
|
return function () {
|
|
108
|
-
|
|
129
|
+
_this2.subscriptionChangeListeners.delete(listener);
|
|
109
130
|
};
|
|
110
131
|
}
|
|
111
132
|
}, {
|
|
112
133
|
key: "notifySubscriptionChangeListeners",
|
|
113
134
|
value: function notifySubscriptionChangeListeners() {
|
|
114
|
-
var
|
|
135
|
+
var _this3 = this;
|
|
115
136
|
this.subscriptionChangeListeners.forEach(function (listener) {
|
|
116
137
|
try {
|
|
117
138
|
listener();
|
|
118
139
|
} catch (error) {
|
|
119
|
-
var
|
|
140
|
+
var _this3$deps$getFireAn;
|
|
120
141
|
(0, _monitoring.logException)(error, {
|
|
121
142
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/notifySubscriptionChangeListeners'
|
|
122
143
|
});
|
|
123
|
-
(
|
|
144
|
+
(_this3$deps$getFireAn = _this3.deps.getFireAnalyticsEvent()) === null || _this3$deps$getFireAn === void 0 || _this3$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message, undefined, undefined, (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), error.message)));
|
|
124
145
|
}
|
|
125
146
|
});
|
|
126
147
|
}
|
|
@@ -140,7 +161,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
140
161
|
}, {
|
|
141
162
|
key: "subscribeToSyncBlock",
|
|
142
163
|
value: function subscribeToSyncBlock(resourceId, localId, callback) {
|
|
143
|
-
var
|
|
164
|
+
var _this4 = this;
|
|
144
165
|
// Cancel any pending cache deletion for this resourceId.
|
|
145
166
|
// This handles the case where a block is moved - the old component unmounts
|
|
146
167
|
// (scheduling deletion) but the new component mounts and subscribes before
|
|
@@ -180,25 +201,25 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
180
201
|
this.setupSubscription(resourceId);
|
|
181
202
|
}
|
|
182
203
|
return function () {
|
|
183
|
-
var resourceSubscriptions =
|
|
204
|
+
var resourceSubscriptions = _this4.subscriptions.get(resourceId);
|
|
184
205
|
if (resourceSubscriptions) {
|
|
185
206
|
// Unsubscription means a reference synced block is removed from the document
|
|
186
|
-
|
|
207
|
+
_this4.deps.markCacheDirty();
|
|
187
208
|
delete resourceSubscriptions[localId];
|
|
188
209
|
var remainingIds = Object.keys(resourceSubscriptions);
|
|
189
210
|
if (remainingIds.length === 0) {
|
|
190
|
-
|
|
211
|
+
_this4.subscriptions.delete(resourceId);
|
|
191
212
|
|
|
192
213
|
// Clean up GraphQL subscription when no more local subscribers
|
|
193
|
-
|
|
214
|
+
_this4.cleanupSubscription(resourceId);
|
|
194
215
|
|
|
195
216
|
// Notify listeners that subscription was removed
|
|
196
|
-
|
|
217
|
+
_this4.notifySubscriptionChangeListeners();
|
|
197
218
|
|
|
198
219
|
// Under the flag, delegate cache deletion to the store manager
|
|
199
220
|
// which uses a 30s grace period with guard re-checks.
|
|
200
221
|
if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_14')) {
|
|
201
|
-
|
|
222
|
+
_this4.deps.scheduleCacheDeletion(resourceId);
|
|
202
223
|
} else {
|
|
203
224
|
// Legacy path (unchanged): delay cache deletion to handle
|
|
204
225
|
// block moves (unmount/remount). When a block is moved, the
|
|
@@ -209,18 +230,18 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
209
230
|
// TODO: EDITOR-4152 - Rework this logic (superseded by
|
|
210
231
|
// `platform_synced_block_patch_14`).
|
|
211
232
|
var deletionTimeout = setTimeout(function () {
|
|
212
|
-
var hasSubscribers =
|
|
233
|
+
var hasSubscribers = _this4.subscriptions.has(resourceId);
|
|
213
234
|
|
|
214
235
|
// Only delete if still no subscribers (wasn't re-subscribed)
|
|
215
236
|
if (!hasSubscribers) {
|
|
216
|
-
|
|
237
|
+
_this4.deps.deleteFromCache(resourceId);
|
|
217
238
|
}
|
|
218
|
-
|
|
239
|
+
_this4.pendingCacheDeletions.delete(resourceId);
|
|
219
240
|
}, 1000);
|
|
220
|
-
|
|
241
|
+
_this4.pendingCacheDeletions.set(resourceId, deletionTimeout);
|
|
221
242
|
}
|
|
222
243
|
} else {
|
|
223
|
-
|
|
244
|
+
_this4.subscriptions.set(resourceId, resourceSubscriptions);
|
|
224
245
|
}
|
|
225
246
|
}
|
|
226
247
|
};
|
|
@@ -229,7 +250,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
229
250
|
key: "subscribeToSourceTitle",
|
|
230
251
|
value: function subscribeToSourceTitle(node, callback) {
|
|
231
252
|
var _cachedData$data,
|
|
232
|
-
|
|
253
|
+
_this5 = this;
|
|
233
254
|
// check node is a sync block, as we only support sync block subscriptions
|
|
234
255
|
if (node.type.name !== 'syncBlock') {
|
|
235
256
|
return function () {};
|
|
@@ -249,13 +270,13 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
249
270
|
var resourceSubscriptions = this.titleSubscriptions.get(resourceId) || {};
|
|
250
271
|
this.titleSubscriptions.set(resourceId, _objectSpread(_objectSpread({}, resourceSubscriptions), {}, (0, _defineProperty2.default)({}, localId, callback)));
|
|
251
272
|
return function () {
|
|
252
|
-
var resourceSubscriptions =
|
|
273
|
+
var resourceSubscriptions = _this5.titleSubscriptions.get(resourceId);
|
|
253
274
|
if (resourceSubscriptions) {
|
|
254
275
|
delete resourceSubscriptions[localId];
|
|
255
276
|
if (Object.keys(resourceSubscriptions).length === 0) {
|
|
256
|
-
|
|
277
|
+
_this5.titleSubscriptions.delete(resourceId);
|
|
257
278
|
} else {
|
|
258
|
-
|
|
279
|
+
_this5.titleSubscriptions.set(resourceId, resourceSubscriptions);
|
|
259
280
|
}
|
|
260
281
|
}
|
|
261
282
|
};
|
|
@@ -287,7 +308,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
287
308
|
}, {
|
|
288
309
|
key: "setupSubscription",
|
|
289
310
|
value: function setupSubscription(resourceId) {
|
|
290
|
-
var
|
|
311
|
+
var _this6 = this;
|
|
291
312
|
if (this.graphqlSubscriptions.has(resourceId)) {
|
|
292
313
|
return;
|
|
293
314
|
}
|
|
@@ -296,7 +317,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
296
317
|
return;
|
|
297
318
|
}
|
|
298
319
|
var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
|
|
299
|
-
|
|
320
|
+
_this6.handleGraphQLUpdate(syncBlockInstance);
|
|
300
321
|
}, function (error) {
|
|
301
322
|
(0, _monitoring.logException)(error, {
|
|
302
323
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
|
|
@@ -309,12 +330,12 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
309
330
|
// would return undefined; the structured attribution (EDITOR-7862) is therefore
|
|
310
331
|
// applied at the gate-ON exhaustion site in scheduleReconnection instead.
|
|
311
332
|
if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
312
|
-
var
|
|
313
|
-
(
|
|
333
|
+
var _this6$deps$getFireAn;
|
|
334
|
+
(_this6$deps$getFireAn = _this6.deps.getFireAnalyticsEvent()) === null || _this6$deps$getFireAn === void 0 || _this6$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
|
|
314
335
|
}
|
|
315
|
-
|
|
336
|
+
_this6.handleSubscriptionTerminated(resourceId);
|
|
316
337
|
}, function () {
|
|
317
|
-
|
|
338
|
+
_this6.handleSubscriptionTerminated(resourceId);
|
|
318
339
|
});
|
|
319
340
|
if (unsubscribe) {
|
|
320
341
|
this.graphqlSubscriptions.set(resourceId, unsubscribe);
|
|
@@ -348,36 +369,48 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
348
369
|
key: "scheduleReconnection",
|
|
349
370
|
value: function scheduleReconnection(resourceId) {
|
|
350
371
|
var _this$retryAttempts$g,
|
|
351
|
-
|
|
372
|
+
_this7 = this;
|
|
352
373
|
var attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
|
|
353
374
|
var maxAttempts = this.getMaxRetryAttempts();
|
|
354
375
|
if (attempts >= maxAttempts) {
|
|
355
|
-
var _this$deps$
|
|
376
|
+
var _this$deps$getFireAna2;
|
|
356
377
|
// Exhausted all attempts — the only place a WS drop surfaces as a
|
|
357
378
|
// fetch error under the gate (EDITOR-7861).
|
|
358
379
|
var errorMessage = "Subscription reconnection failed after ".concat(attempts, " attempts");
|
|
380
|
+
|
|
381
|
+
// Tab hidden at exhaustion: don't surface a terminal failure (user isn't
|
|
382
|
+
// looking, and most exhaustions self-recover once foregrounded). Park + re-arm
|
|
383
|
+
// on wake, emitting a benign `deferred` signal so suppression stays auditable.
|
|
384
|
+
var shouldDefer = (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3') && this.isDocumentHidden();
|
|
385
|
+
if (shouldDefer) {
|
|
386
|
+
var _this$deps$getFireAna;
|
|
387
|
+
this.deferredExhausted.add(resourceId);
|
|
388
|
+
this.registerWakeListeners();
|
|
389
|
+
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(errorMessage, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)(true, errorMessage, undefined, /* deferred */true)));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
359
392
|
(0, _monitoring.logException)(new Error(errorMessage), {
|
|
360
393
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
|
|
361
394
|
});
|
|
362
|
-
(_this$deps$
|
|
395
|
+
(_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2((0, _errorHandling.fetchErrorPayload)(errorMessage, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), errorMessage)));
|
|
363
396
|
return;
|
|
364
397
|
}
|
|
365
398
|
var delay = this.getReconnectionDelay(attempts);
|
|
366
399
|
var timer = setTimeout(function () {
|
|
367
|
-
|
|
400
|
+
_this7.pendingRetries.delete(resourceId);
|
|
368
401
|
|
|
369
402
|
// Only re-subscribe if still relevant
|
|
370
|
-
if (
|
|
371
|
-
|
|
403
|
+
if (_this7.subscriptions.has(resourceId) && _this7.shouldUseRealTime()) {
|
|
404
|
+
_this7.setupSubscription(resourceId);
|
|
372
405
|
|
|
373
406
|
// Only increment if the subscription was actually established
|
|
374
407
|
// (setupSubscription may be a no-op if another code path already re-established it)
|
|
375
|
-
if (
|
|
376
|
-
|
|
408
|
+
if (_this7.graphqlSubscriptions.has(resourceId)) {
|
|
409
|
+
_this7.retryAttempts.set(resourceId, attempts + 1);
|
|
377
410
|
}
|
|
378
411
|
} else {
|
|
379
412
|
// Conditions no longer met — clean up stale retry state
|
|
380
|
-
|
|
413
|
+
_this7.retryAttempts.delete(resourceId);
|
|
381
414
|
}
|
|
382
415
|
}, delay);
|
|
383
416
|
this.pendingRetries.set(resourceId, timer);
|
|
@@ -392,6 +425,106 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
392
425
|
value: function resetRetryCount(resourceId) {
|
|
393
426
|
this.retryAttempts.delete(resourceId);
|
|
394
427
|
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Whether the tab is currently hidden. SSR/non-browser is treated as "not hidden" so
|
|
431
|
+
* we never defer where wake events can't fire (realtime never subscribes there anyway).
|
|
432
|
+
*/
|
|
433
|
+
}, {
|
|
434
|
+
key: "isDocumentHidden",
|
|
435
|
+
value: function isDocumentHidden() {
|
|
436
|
+
var _getDocument;
|
|
437
|
+
return ((_getDocument = (0, _browserApis.getDocument)()) === null || _getDocument === void 0 ? void 0 : _getDocument.hidden) === true;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Lazily register shared `online` / `visibilitychange` listeners on first deferral.
|
|
442
|
+
* Torn down by `unregisterWakeListeners` (via `handleWake`). No-op in SSR / non-browser.
|
|
443
|
+
*/
|
|
444
|
+
}, {
|
|
445
|
+
key: "registerWakeListeners",
|
|
446
|
+
value: function registerWakeListeners() {
|
|
447
|
+
if (this.wakeListenerCleanups.length > 0 || typeof window === 'undefined') {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
this.wakeListenerCleanups.push((0, _bindEventListener.bind)(window, {
|
|
451
|
+
type: 'online',
|
|
452
|
+
listener: this.onOnline
|
|
453
|
+
}));
|
|
454
|
+
var doc = (0, _browserApis.getDocument)();
|
|
455
|
+
if (doc) {
|
|
456
|
+
this.wakeListenerCleanups.push((0, _bindEventListener.bind)(doc, {
|
|
457
|
+
type: 'visibilitychange',
|
|
458
|
+
listener: this.onVisibilityChange
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}, {
|
|
463
|
+
key: "unregisterWakeListeners",
|
|
464
|
+
value: function unregisterWakeListeners() {
|
|
465
|
+
if (this.wakeDebounceTimer !== null) {
|
|
466
|
+
clearTimeout(this.wakeDebounceTimer);
|
|
467
|
+
this.wakeDebounceTimer = null;
|
|
468
|
+
}
|
|
469
|
+
var _iterator = _createForOfIteratorHelper(this.wakeListenerCleanups),
|
|
470
|
+
_step;
|
|
471
|
+
try {
|
|
472
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
473
|
+
var cleanup = _step.value;
|
|
474
|
+
cleanup();
|
|
475
|
+
}
|
|
476
|
+
} catch (err) {
|
|
477
|
+
_iterator.e(err);
|
|
478
|
+
} finally {
|
|
479
|
+
_iterator.f();
|
|
480
|
+
}
|
|
481
|
+
this.wakeListenerCleanups = [];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Coalesce a burst of wake events into one debounced re-arm sweep. Callers decide
|
|
486
|
+
* eligibility: `onOnline` always sweeps; `onVisibilityChange` only sweeps when visible.
|
|
487
|
+
*/
|
|
488
|
+
}, {
|
|
489
|
+
key: "scheduleWakeSweep",
|
|
490
|
+
value: function scheduleWakeSweep() {
|
|
491
|
+
var _this8 = this;
|
|
492
|
+
if (this.wakeDebounceTimer !== null) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
this.wakeDebounceTimer = setTimeout(function () {
|
|
496
|
+
_this8.wakeDebounceTimer = null;
|
|
497
|
+
_this8.handleWake();
|
|
498
|
+
}, SyncBlockSubscriptionManager.WAKE_DEBOUNCE_MS);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Re-arm every deferred resource: full reset (attempts→0) + immediate reconnect now
|
|
503
|
+
* the tab is visible / online. Healthy subscriptions are untouched. A re-armed cycle
|
|
504
|
+
* that later exhausts while visible fires the terminal error normally.
|
|
505
|
+
*/
|
|
506
|
+
}, {
|
|
507
|
+
key: "handleWake",
|
|
508
|
+
value: function handleWake() {
|
|
509
|
+
if (this.deferredExhausted.size === 0) {
|
|
510
|
+
this.unregisterWakeListeners();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
var toRearm = Array.from(this.deferredExhausted);
|
|
514
|
+
this.deferredExhausted.clear();
|
|
515
|
+
for (var _i = 0, _toRearm = toRearm; _i < _toRearm.length; _i++) {
|
|
516
|
+
var resourceId = _toRearm[_i];
|
|
517
|
+
// Only re-arm resources that still have active subscribers and realtime on.
|
|
518
|
+
if (!this.subscriptions.has(resourceId) || !this.shouldUseRealTime()) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
this.resetRetryCount(resourceId);
|
|
522
|
+
this.cancelPendingRetry(resourceId);
|
|
523
|
+
this.setupSubscription(resourceId);
|
|
524
|
+
}
|
|
525
|
+
// No deferred resources remain until another hidden exhaustion re-adds one.
|
|
526
|
+
this.unregisterWakeListeners();
|
|
527
|
+
}
|
|
395
528
|
}, {
|
|
396
529
|
key: "cancelPendingRetry",
|
|
397
530
|
value: function cancelPendingRetry(resourceId) {
|
|
@@ -404,20 +537,23 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
404
537
|
}, {
|
|
405
538
|
key: "cancelAllPendingRetries",
|
|
406
539
|
value: function cancelAllPendingRetries() {
|
|
407
|
-
var
|
|
408
|
-
|
|
540
|
+
var _iterator2 = _createForOfIteratorHelper(this.pendingRetries.values()),
|
|
541
|
+
_step2;
|
|
409
542
|
try {
|
|
410
|
-
for (
|
|
411
|
-
var timer =
|
|
543
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
544
|
+
var timer = _step2.value;
|
|
412
545
|
clearTimeout(timer);
|
|
413
546
|
}
|
|
414
547
|
} catch (err) {
|
|
415
|
-
|
|
548
|
+
_iterator2.e(err);
|
|
416
549
|
} finally {
|
|
417
|
-
|
|
550
|
+
_iterator2.f();
|
|
418
551
|
}
|
|
419
552
|
this.pendingRetries.clear();
|
|
420
553
|
this.retryAttempts.clear();
|
|
554
|
+
// Nothing left to re-arm: drop parked exhaustions and tear down wake listeners.
|
|
555
|
+
this.deferredExhausted.clear();
|
|
556
|
+
this.unregisterWakeListeners();
|
|
421
557
|
}
|
|
422
558
|
}, {
|
|
423
559
|
key: "cleanupSubscription",
|
|
@@ -429,37 +565,42 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
429
565
|
}
|
|
430
566
|
this.cancelPendingRetry(resourceId);
|
|
431
567
|
this.retryAttempts.delete(resourceId);
|
|
568
|
+
// No subscribers left, so this resource can't be re-armed.
|
|
569
|
+
this.deferredExhausted.delete(resourceId);
|
|
570
|
+
if (this.deferredExhausted.size === 0) {
|
|
571
|
+
this.unregisterWakeListeners();
|
|
572
|
+
}
|
|
432
573
|
}
|
|
433
574
|
}, {
|
|
434
575
|
key: "setupSubscriptionsForAllBlocks",
|
|
435
576
|
value: function setupSubscriptionsForAllBlocks() {
|
|
436
|
-
var
|
|
437
|
-
|
|
577
|
+
var _iterator3 = _createForOfIteratorHelper(this.subscriptions.keys()),
|
|
578
|
+
_step3;
|
|
438
579
|
try {
|
|
439
|
-
for (
|
|
440
|
-
var resourceId =
|
|
580
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
581
|
+
var resourceId = _step3.value;
|
|
441
582
|
this.setupSubscription(resourceId);
|
|
442
583
|
}
|
|
443
584
|
} catch (err) {
|
|
444
|
-
|
|
585
|
+
_iterator3.e(err);
|
|
445
586
|
} finally {
|
|
446
|
-
|
|
587
|
+
_iterator3.f();
|
|
447
588
|
}
|
|
448
589
|
}
|
|
449
590
|
}, {
|
|
450
591
|
key: "cleanupAll",
|
|
451
592
|
value: function cleanupAll() {
|
|
452
|
-
var
|
|
453
|
-
|
|
593
|
+
var _iterator4 = _createForOfIteratorHelper(this.graphqlSubscriptions.values()),
|
|
594
|
+
_step4;
|
|
454
595
|
try {
|
|
455
|
-
for (
|
|
456
|
-
var unsubscribe =
|
|
596
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
597
|
+
var unsubscribe = _step4.value;
|
|
457
598
|
unsubscribe();
|
|
458
599
|
}
|
|
459
600
|
} catch (err) {
|
|
460
|
-
|
|
601
|
+
_iterator4.e(err);
|
|
461
602
|
} finally {
|
|
462
|
-
|
|
603
|
+
_iterator4.f();
|
|
463
604
|
}
|
|
464
605
|
this.graphqlSubscriptions.clear();
|
|
465
606
|
this.cancelAllPendingRetries();
|
|
@@ -474,17 +615,17 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
474
615
|
this.useRealTimeSubscriptions = false;
|
|
475
616
|
|
|
476
617
|
// Clear any pending cache deletions
|
|
477
|
-
var
|
|
478
|
-
|
|
618
|
+
var _iterator5 = _createForOfIteratorHelper(this.pendingCacheDeletions.values()),
|
|
619
|
+
_step5;
|
|
479
620
|
try {
|
|
480
|
-
for (
|
|
481
|
-
var timeout =
|
|
621
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
622
|
+
var timeout = _step5.value;
|
|
482
623
|
clearTimeout(timeout);
|
|
483
624
|
}
|
|
484
625
|
} catch (err) {
|
|
485
|
-
|
|
626
|
+
_iterator5.e(err);
|
|
486
627
|
} finally {
|
|
487
|
-
|
|
628
|
+
_iterator5.f();
|
|
488
629
|
}
|
|
489
630
|
this.pendingCacheDeletions.clear();
|
|
490
631
|
}
|
|
@@ -496,7 +637,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
496
637
|
}, {
|
|
497
638
|
key: "handleGraphQLUpdate",
|
|
498
639
|
value: function handleGraphQLUpdate(syncBlockInstance) {
|
|
499
|
-
var
|
|
640
|
+
var _this9 = this;
|
|
500
641
|
if (!syncBlockInstance.resourceId) {
|
|
501
642
|
return;
|
|
502
643
|
}
|
|
@@ -505,21 +646,28 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
505
646
|
this.deps.updateCache(resolved);
|
|
506
647
|
if (!syncBlockInstance.error) {
|
|
507
648
|
this.resetRetryCount(syncBlockInstance.resourceId);
|
|
649
|
+
// A healthy update means a parked exhaustion recovered on its own before any
|
|
650
|
+
// wake sweep — drop it and tear down listeners if nothing else is parked.
|
|
651
|
+
if (this.deferredExhausted.delete(syncBlockInstance.resourceId)) {
|
|
652
|
+
if (this.deferredExhausted.size === 0) {
|
|
653
|
+
this.unregisterWakeListeners();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
508
656
|
var callbacks = this.subscriptions.get(syncBlockInstance.resourceId);
|
|
509
657
|
var localIds = callbacks ? Object.keys(callbacks) : [];
|
|
510
658
|
localIds.forEach(function (localId) {
|
|
511
|
-
var
|
|
512
|
-
(
|
|
659
|
+
var _this9$deps$getFireAn, _syncBlockInstance$da, _syncBlockInstance$da2;
|
|
660
|
+
(_this9$deps$getFireAn = _this9.deps.getFireAnalyticsEvent()) === null || _this9$deps$getFireAn === void 0 || _this9$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)));
|
|
513
661
|
});
|
|
514
662
|
this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
|
|
515
663
|
} else {
|
|
516
|
-
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$
|
|
664
|
+
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna3, _syncBlockInstance$da3, _syncBlockInstance$da4, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
|
|
517
665
|
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);
|
|
518
666
|
|
|
519
667
|
// Prefer the structured `type` (a `SyncBlockError` enum value) for classification
|
|
520
668
|
// and fall back to the free-text `reason` so source-state/permission strings are
|
|
521
669
|
// still bucketed (EDITOR-7862). The emitted free-text `error` attribute is unchanged.
|
|
522
|
-
(_this$deps$
|
|
670
|
+
(_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 || _this$deps$getFireAna3((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), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason), (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
|
|
523
671
|
}
|
|
524
672
|
}
|
|
525
673
|
}]);
|
|
@@ -531,4 +679,6 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
531
679
|
// legacy (gate OFF)
|
|
532
680
|
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS_HARDENED", 8);
|
|
533
681
|
// gate ON (EDITOR-7861)
|
|
534
|
-
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_DELAY_MS", 30000);
|
|
682
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_DELAY_MS", 30000);
|
|
683
|
+
// Coalesce wake-event bursts into one re-arm sweep to avoid a reconnection storm.
|
|
684
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "WAKE_DEBOUNCE_MS", 1000);
|
|
@@ -202,16 +202,19 @@ var classifyFetchErrorReason = exports.classifyFetchErrorReason = function class
|
|
|
202
202
|
* `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
|
|
203
203
|
* helper stays pure and trivially unit-testable for both gate states.
|
|
204
204
|
*/
|
|
205
|
-
var buildFetchErrorAttribution = exports.buildFetchErrorAttribution = function buildFetchErrorAttribution(gateEnabled, error, statusCode) {
|
|
205
|
+
var buildFetchErrorAttribution = exports.buildFetchErrorAttribution = function buildFetchErrorAttribution(gateEnabled, error, statusCode, deferred) {
|
|
206
206
|
if (!gateEnabled) {
|
|
207
207
|
return undefined;
|
|
208
208
|
}
|
|
209
209
|
var reason = classifyFetchErrorReason(error);
|
|
210
|
-
return _objectSpread({
|
|
210
|
+
return _objectSpread(_objectSpread({
|
|
211
211
|
reason: reason,
|
|
212
|
-
benign
|
|
212
|
+
// A deferred (tab-hidden) exhaustion is benign regardless of reason bucket.
|
|
213
|
+
benign: deferred === true || FETCH_BENIGN_REASONS.has(reason)
|
|
213
214
|
}, statusCode !== undefined && {
|
|
214
215
|
statusCode: statusCode
|
|
216
|
+
}), deferred === true && {
|
|
217
|
+
deferred: true
|
|
215
218
|
});
|
|
216
219
|
};
|
|
217
220
|
|
|
@@ -239,7 +242,7 @@ function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attr
|
|
|
239
242
|
actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
|
|
240
243
|
actionSubjectId: actionSubjectId,
|
|
241
244
|
eventType: _analytics.EVENT_TYPE.OPERATIONAL,
|
|
242
|
-
attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
|
|
245
|
+
attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
|
|
243
246
|
error: error
|
|
244
247
|
}, resourceId && {
|
|
245
248
|
resourceId: resourceId
|
|
@@ -251,6 +254,8 @@ function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attr
|
|
|
251
254
|
statusCode: attribution.statusCode
|
|
252
255
|
}), attribution && 'benign' in attribution && {
|
|
253
256
|
benign: attribution.benign
|
|
257
|
+
}), attribution && 'deferred' in attribution && attribution.deferred === true && {
|
|
258
|
+
deferred: true
|
|
254
259
|
})
|
|
255
260
|
};
|
|
256
261
|
}
|
|
@@ -841,7 +841,7 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
841
841
|
if (this.viewMode === 'view') {
|
|
842
842
|
// Reference flushes are only meaningful while editing. Treat view-mode flush attempts as
|
|
843
843
|
// gated no-op successes so lifecycle/teardown calls do not report false save failures.
|
|
844
|
-
return
|
|
844
|
+
return true;
|
|
845
845
|
}
|
|
846
846
|
if (!this.isCacheDirty) {
|
|
847
847
|
// we use the isCacheDirty flag as a quick check.
|