@atlaskit/editor-synced-block-provider 8.1.9 → 8.2.0
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 +27 -0
- package/dist/cjs/common/types.js +63 -2
- package/dist/cjs/hooks/useFetchSyncBlockData.js +48 -14
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +33 -4
- package/dist/cjs/store-manager/syncBlockBatchFetcher.js +27 -0
- package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +46 -11
- package/dist/es2019/common/types.js +47 -1
- package/dist/es2019/hooks/useFetchSyncBlockData.js +36 -3
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +26 -2
- package/dist/es2019/store-manager/syncBlockBatchFetcher.js +25 -0
- package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +41 -11
- package/dist/esm/common/types.js +62 -1
- package/dist/esm/hooks/useFetchSyncBlockData.js +49 -15
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +34 -5
- package/dist/esm/store-manager/syncBlockBatchFetcher.js +27 -0
- package/dist/esm/store-manager/syncBlockSubscriptionManager.js +46 -11
- package/dist/types/common/types.d.ts +29 -0
- package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +8 -0
- package/dist/types/store-manager/syncBlockBatchFetcher.d.ts +7 -0
- package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +4 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @atlaskit/editor-synced-block-provider
|
|
2
2
|
|
|
3
|
+
## 8.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`f13db8731461d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f13db8731461d) -
|
|
8
|
+
[EDITOR-7860] Gate synced block reference fetch and subscribe on data-provider readiness so
|
|
9
|
+
reference blocks no longer attempt to fetch before the (asynchronously wired) provider is ready.
|
|
10
|
+
This removes spurious "Data provider not set" fetch errors on Jira and keeps reference blocks
|
|
11
|
+
loading correctly once the provider resolves.
|
|
12
|
+
|
|
13
|
+
This also hardens the not-ready / torn-down window: a teardown-aware `hasDataProvider()` (also
|
|
14
|
+
checks `isDestroyed`), a tagged `ProviderNotReadyError` thrown at the fetch source, and catch-site
|
|
15
|
+
suppression in the hook, store manager and batch fetcher so an in-flight or queued fetch on an
|
|
16
|
+
orphaned manager re-queues for retry instead of emitting a false fetch error. All behaviour is
|
|
17
|
+
behind the `platform_editor_blocks_patch_3` feature gate.
|
|
18
|
+
|
|
19
|
+
## 8.1.10
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [`b4cfc35a62476`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b4cfc35a62476) -
|
|
24
|
+
Improve the reliability of synced-block real-time updates. Transient WebSocket drops now recover
|
|
25
|
+
silently via a more resilient reconnection strategy (more retry attempts, capped exponential
|
|
26
|
+
backoff with jitter) instead of being reported as failures. A failure is only surfaced once
|
|
27
|
+
reconnection is genuinely exhausted. Behind a feature gate.
|
|
28
|
+
- Updated dependencies
|
|
29
|
+
|
|
3
30
|
## 8.1.9
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/dist/cjs/common/types.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
|
-
exports.SyncBlockError = void 0;
|
|
7
|
+
exports.isProviderNotReadyError = exports.SyncBlockError = exports.ProviderNotReadyError = exports.PROVIDER_NOT_READY_MESSAGE = void 0;
|
|
8
|
+
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
9
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
11
|
+
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
|
|
12
|
+
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
|
|
13
|
+
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
|
|
14
|
+
var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper"));
|
|
15
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
16
|
+
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
|
|
17
|
+
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
7
18
|
var SyncBlockError = exports.SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
8
19
|
SyncBlockError["Errored"] = "errored";
|
|
9
20
|
SyncBlockError["NotFound"] = "not_found";
|
|
@@ -22,4 +33,54 @@ var SyncBlockError = exports.SyncBlockError = /*#__PURE__*/function (SyncBlockEr
|
|
|
22
33
|
// block does not exist on this site (e.g. cross-site reference or hard deleted)
|
|
23
34
|
SyncBlockError["EntityNotFound"] = "entity_not_found";
|
|
24
35
|
return SyncBlockError;
|
|
25
|
-
}({});
|
|
36
|
+
}({});
|
|
37
|
+
/**
|
|
38
|
+
* Helpers to distinguish "data provider not ready / torn down" from a genuine
|
|
39
|
+
* fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
|
|
40
|
+
* asynchronously and `destroy()` nulls it on orphaned managers, so queued/
|
|
41
|
+
* in-flight ops throw `Data provider not set` — previously mis-logged as a real
|
|
42
|
+
* error. These let throw and catch sites agree on one non-string-matched signal
|
|
43
|
+
* so the residual false errors are suppressed. Gated by
|
|
44
|
+
* `platform_editor_blocks_patch_3`.
|
|
45
|
+
*
|
|
46
|
+
* NB: these intentionally live here rather than in a dedicated module to avoid
|
|
47
|
+
* adding a downstream file to consuming Jira packages' Thunderstone complexity.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** Legacy message — kept identical for gate-off and historical events. */
|
|
51
|
+
var PROVIDER_NOT_READY_MESSAGE = exports.PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Thrown when a fetch/subscribe runs against a manager whose provider is not
|
|
55
|
+
* (yet) available or torn down. Keeps the legacy message + name so string
|
|
56
|
+
* matchers still work, while new code uses {@link isProviderNotReadyError}.
|
|
57
|
+
*/
|
|
58
|
+
var ProviderNotReadyError = exports.ProviderNotReadyError = /*#__PURE__*/function (_Error) {
|
|
59
|
+
function ProviderNotReadyError() {
|
|
60
|
+
var _this;
|
|
61
|
+
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PROVIDER_NOT_READY_MESSAGE;
|
|
62
|
+
(0, _classCallCheck2.default)(this, ProviderNotReadyError);
|
|
63
|
+
_this = _callSuper(this, ProviderNotReadyError, [message]);
|
|
64
|
+
(0, _defineProperty2.default)(_this, "isProviderNotReadyError", true);
|
|
65
|
+
_this.name = 'ProviderNotReadyError';
|
|
66
|
+
// Restore prototype chain so instanceof works after transpilation.
|
|
67
|
+
Object.setPrototypeOf(_this, ProviderNotReadyError.prototype);
|
|
68
|
+
return _this;
|
|
69
|
+
}
|
|
70
|
+
(0, _inherits2.default)(ProviderNotReadyError, _Error);
|
|
71
|
+
return (0, _createClass2.default)(ProviderNotReadyError);
|
|
72
|
+
}( /*#__PURE__*/(0, _wrapNativeSuper2.default)(Error));
|
|
73
|
+
/**
|
|
74
|
+
* True when the value is a "provider not ready / torn down" condition, not a
|
|
75
|
+
* genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
|
|
76
|
+
* legacy message (for errors thrown before rollout).
|
|
77
|
+
*/
|
|
78
|
+
var isProviderNotReadyError = exports.isProviderNotReadyError = function isProviderNotReadyError(error) {
|
|
79
|
+
if (error instanceof ProviderNotReadyError) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if ((0, _typeof2.default)(error) === 'object' && error !== null && error.isProviderNotReadyError === true) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
|
|
86
|
+
};
|
|
@@ -12,12 +12,14 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
|
|
|
12
12
|
var _react = require("react");
|
|
13
13
|
var _coreUtils = require("@atlaskit/editor-common/core-utils");
|
|
14
14
|
var _monitoring = require("@atlaskit/editor-common/monitoring");
|
|
15
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
15
16
|
var _types = require("../common/types");
|
|
16
17
|
var _errorHandling = require("../utils/errorHandling");
|
|
17
18
|
var _utils = require("../utils/utils");
|
|
18
19
|
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; }
|
|
19
20
|
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) { (0, _defineProperty2.default)(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; }
|
|
20
21
|
var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyncBlockData(manager, resourceId, localId, fireAnalyticsEvent) {
|
|
22
|
+
var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
|
|
21
23
|
// Initialize both states from a single cache lookup to avoid race conditions.
|
|
22
24
|
// When a block is moved/remounted, the old component's cleanup may clear the cache
|
|
23
25
|
// before or after the new component mounts. By doing a single lookup, we ensure
|
|
@@ -41,6 +43,14 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
|
|
|
41
43
|
syncBlockInstance = _useState2$.syncBlockInstance,
|
|
42
44
|
isLoading = _useState2$.isLoading,
|
|
43
45
|
setFetchState = _useState2[1];
|
|
46
|
+
|
|
47
|
+
// On Jira the data provider is wired asynchronously, so the manager can be
|
|
48
|
+
// constructed with `dataProvider === undefined`. Fetching/subscribing in that
|
|
49
|
+
// window throws `Data provider not set`, logged as a false fetch error
|
|
50
|
+
// (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
|
|
51
|
+
// instance is created so `referenceManager` changes identity and the effect
|
|
52
|
+
// below re-runs, subscribing exactly once.
|
|
53
|
+
var isDataProviderReady = (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3') ? (_manager$referenceMan2 = (_manager$referenceMan3 = (_manager$referenceMan4 = manager.referenceManager).hasDataProvider) === null || _manager$referenceMan3 === void 0 ? void 0 : _manager$referenceMan3.call(_manager$referenceMan4)) !== null && _manager$referenceMan2 !== void 0 ? _manager$referenceMan2 : false : true;
|
|
44
54
|
var reloadData = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
|
|
45
55
|
var syncBlockNode, _t;
|
|
46
56
|
return _regenerator.default.wrap(function (_context) {
|
|
@@ -52,14 +62,20 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
|
|
|
52
62
|
}
|
|
53
63
|
return _context.abrupt("return");
|
|
54
64
|
case 1:
|
|
55
|
-
|
|
65
|
+
if (isDataProviderReady) {
|
|
66
|
+
_context.next = 2;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
return _context.abrupt("return");
|
|
70
|
+
case 2:
|
|
71
|
+
_context.prev = 2;
|
|
56
72
|
syncBlockNode = resourceId && localId ? (0, _utils.createSyncBlockNode)(localId, resourceId) : null;
|
|
57
73
|
if (syncBlockNode) {
|
|
58
|
-
_context.next =
|
|
74
|
+
_context.next = 3;
|
|
59
75
|
break;
|
|
60
76
|
}
|
|
61
77
|
throw new Error('Failed to create sync block node from resourceid and localid');
|
|
62
|
-
case
|
|
78
|
+
case 3:
|
|
63
79
|
setFetchState(function (prev) {
|
|
64
80
|
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
65
81
|
isLoading: true
|
|
@@ -67,14 +83,25 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
|
|
|
67
83
|
});
|
|
68
84
|
|
|
69
85
|
// Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
|
|
70
|
-
_context.next =
|
|
86
|
+
_context.next = 4;
|
|
71
87
|
return manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
|
|
72
|
-
case 3:
|
|
73
|
-
_context.next = 5;
|
|
74
|
-
break;
|
|
75
88
|
case 4:
|
|
76
|
-
_context.
|
|
77
|
-
|
|
89
|
+
_context.next = 7;
|
|
90
|
+
break;
|
|
91
|
+
case 5:
|
|
92
|
+
_context.prev = 5;
|
|
93
|
+
_t = _context["catch"](2);
|
|
94
|
+
if (!((0, _types.isProviderNotReadyError)(_t) && (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'))) {
|
|
95
|
+
_context.next = 6;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
setFetchState(function (prev) {
|
|
99
|
+
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
100
|
+
isLoading: true
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
return _context.abrupt("return");
|
|
104
|
+
case 6:
|
|
78
105
|
(0, _monitoring.logException)(_t, {
|
|
79
106
|
location: 'editor-synced-block-provider/useFetchSyncBlockData'
|
|
80
107
|
});
|
|
@@ -91,24 +118,31 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
|
|
|
91
118
|
isLoading: false
|
|
92
119
|
});
|
|
93
120
|
return _context.abrupt("return");
|
|
94
|
-
case
|
|
121
|
+
case 7:
|
|
95
122
|
setFetchState(function (prev) {
|
|
96
123
|
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
97
124
|
isLoading: false
|
|
98
125
|
});
|
|
99
126
|
});
|
|
100
|
-
case
|
|
127
|
+
case 8:
|
|
101
128
|
case "end":
|
|
102
129
|
return _context.stop();
|
|
103
130
|
}
|
|
104
|
-
}, _callee, null, [[
|
|
105
|
-
})), [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
131
|
+
}, _callee, null, [[2, 5]]);
|
|
132
|
+
})), [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
106
133
|
(0, _react.useEffect)(function () {
|
|
107
134
|
if ((0, _coreUtils.isSSR)()) {
|
|
108
135
|
// in SSR, we don't need to subscribe to updates,
|
|
109
136
|
// instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
|
|
110
137
|
return;
|
|
111
138
|
}
|
|
139
|
+
|
|
140
|
+
// Not ready: skip subscribe (it would trigger a batched fetch and a false
|
|
141
|
+
// error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
|
|
142
|
+
// so the effect re-runs and subscribes once the provider resolves.
|
|
143
|
+
if (!isDataProviderReady) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
112
146
|
var unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', function (data) {
|
|
113
147
|
setFetchState({
|
|
114
148
|
syncBlockInstance: data,
|
|
@@ -118,7 +152,7 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
|
|
|
118
152
|
return function () {
|
|
119
153
|
unsubscribe();
|
|
120
154
|
};
|
|
121
|
-
}, [localId, manager.referenceManager, resourceId]);
|
|
155
|
+
}, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
|
|
122
156
|
var ssrProviders = (0, _react.useMemo)(function () {
|
|
123
157
|
return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
|
|
124
158
|
}, [resourceId, manager.referenceManager]);
|
|
@@ -128,6 +128,10 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
128
128
|
},
|
|
129
129
|
getFireAnalyticsEvent: function getFireAnalyticsEvent() {
|
|
130
130
|
return _this.fireAnalyticsEvent;
|
|
131
|
+
},
|
|
132
|
+
// EDITOR-7860: skip + re-queue fetches while not ready / torn down.
|
|
133
|
+
isProviderReady: function isProviderReady() {
|
|
134
|
+
return _this.hasDataProvider();
|
|
131
135
|
}
|
|
132
136
|
});
|
|
133
137
|
|
|
@@ -140,6 +144,19 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
140
144
|
return node.type.name === 'syncBlock';
|
|
141
145
|
}
|
|
142
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Whether the async data provider is wired and the manager is not torn down.
|
|
149
|
+
* Consumers gate fetch/subscribe on this to avoid a false `Data provider not
|
|
150
|
+
* set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
|
|
151
|
+
* orphaned mid-flight during an async provider swap, where `dataProvider`
|
|
152
|
+
* alone is insufficient.
|
|
153
|
+
*/
|
|
154
|
+
}, {
|
|
155
|
+
key: "hasDataProvider",
|
|
156
|
+
value: function hasDataProvider() {
|
|
157
|
+
return !this.isDestroyed && !!this.dataProvider;
|
|
158
|
+
}
|
|
159
|
+
|
|
143
160
|
/**
|
|
144
161
|
* Enables or disables real-time GraphQL subscriptions for block updates.
|
|
145
162
|
* When enabled, the store manager will subscribe to real-time updates
|
|
@@ -531,22 +548,28 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
531
548
|
return _context2.abrupt("return");
|
|
532
549
|
case 2:
|
|
533
550
|
if (this.dataProvider) {
|
|
551
|
+
_context2.next = 4;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
534
555
|
_context2.next = 3;
|
|
535
556
|
break;
|
|
536
557
|
}
|
|
537
|
-
throw new
|
|
558
|
+
throw new _types.ProviderNotReadyError();
|
|
538
559
|
case 3:
|
|
560
|
+
throw new Error('Data provider not set');
|
|
561
|
+
case 4:
|
|
539
562
|
nodesToFetch.forEach(function (node) {
|
|
540
563
|
_this4.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
|
|
541
564
|
});
|
|
542
565
|
(_this$fetchExperience6 = this.fetchExperience) === null || _this$fetchExperience6 === void 0 || _this$fetchExperience6.start({});
|
|
543
|
-
_context2.next =
|
|
566
|
+
_context2.next = 5;
|
|
544
567
|
return this.dataProvider.fetchNodesData(nodesToFetch).finally(function () {
|
|
545
568
|
nodesToFetch.forEach(function (node) {
|
|
546
569
|
_this4.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
|
|
547
570
|
});
|
|
548
571
|
});
|
|
549
|
-
case
|
|
572
|
+
case 5:
|
|
550
573
|
data = _context2.sent;
|
|
551
574
|
_this$processFetchedD2 = this.processFetchedData(data), hasUnexpectedError = _this$processFetchedD2.hasUnexpectedError, hasExpectedError = _this$processFetchedD2.hasExpectedError;
|
|
552
575
|
if (hasUnexpectedError) {
|
|
@@ -560,7 +583,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
560
583
|
} else {
|
|
561
584
|
(_this$fetchExperience9 = this.fetchExperience) === null || _this$fetchExperience9 === void 0 || _this$fetchExperience9.success();
|
|
562
585
|
}
|
|
563
|
-
case
|
|
586
|
+
case 6:
|
|
564
587
|
case "end":
|
|
565
588
|
return _context2.stop();
|
|
566
589
|
}
|
|
@@ -938,6 +961,12 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
|
|
|
938
961
|
return this._subscriptionManager.subscribeToSyncBlock(resourceId, localId, callback);
|
|
939
962
|
} catch (error) {
|
|
940
963
|
var _this$fireAnalyticsEv7;
|
|
964
|
+
// EDITOR-7860: benign not-ready/torn-down case — suppress both the
|
|
965
|
+
// exception-tracker log and the analytics event (checked first so the
|
|
966
|
+
// benign case stays fully silent). Gate-off behaviour is unchanged.
|
|
967
|
+
if ((0, _types.isProviderNotReadyError)(error) && (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
968
|
+
return function () {};
|
|
969
|
+
}
|
|
941
970
|
(0, _monitoring.logException)(error, {
|
|
942
971
|
location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
|
|
943
972
|
});
|
|
@@ -10,6 +10,8 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
|
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
11
|
var _rafSchd = _interopRequireDefault(require("raf-schd"));
|
|
12
12
|
var _monitoring = require("@atlaskit/editor-common/monitoring");
|
|
13
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
|
+
var _types = require("../common/types");
|
|
13
15
|
var _errorHandling = require("../utils/errorHandling");
|
|
14
16
|
var _utils = require("../utils/utils");
|
|
15
17
|
/**
|
|
@@ -32,6 +34,17 @@ var SyncBlockBatchFetcher = exports.SyncBlockBatchFetcher = /*#__PURE__*/functio
|
|
|
32
34
|
if (_this.pendingFetchRequests.size === 0) {
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
37
|
+
|
|
38
|
+
// EDITOR-7860: not ready — skip and leave resourceIds queued for the
|
|
39
|
+
// next batch once the provider resolves. Gate-off is unchanged (the
|
|
40
|
+
// readiness check is nested under the gate so it is not consulted when
|
|
41
|
+
// the gate is off, and `fg()` stays a standalone condition so gate
|
|
42
|
+
// exposure is still tracked — satisfies @atlaskit/platform/no-preconditioning).
|
|
43
|
+
if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
44
|
+
if (_this.deps.isProviderReady && !_this.deps.isProviderReady()) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
35
48
|
var resourceIds = Array.from(_this.pendingFetchRequests);
|
|
36
49
|
var syncBlockNodes = resourceIds.map(function (resId) {
|
|
37
50
|
var subscriptions = _this.deps.getSubscriptions().get(resId) || {};
|
|
@@ -45,6 +58,20 @@ var SyncBlockBatchFetcher = exports.SyncBlockBatchFetcher = /*#__PURE__*/functio
|
|
|
45
58
|
return _this.inFlightFetches.add(resId);
|
|
46
59
|
});
|
|
47
60
|
_this.deps.fetchSyncBlocksData(syncBlockNodes).catch(function (error) {
|
|
61
|
+
// EDITOR-7860: benign not-ready throw — re-queue for retry and emit
|
|
62
|
+
// nothing (no analytics, no exception-tracker noise). Checked before
|
|
63
|
+
// `logException` so the benign case stays silent. Re-schedule so the
|
|
64
|
+
// re-queued IDs are retried on the next frame even if no further
|
|
65
|
+
// `queueFetch()` arrives. Gate-off behaviour is unchanged.
|
|
66
|
+
if ((0, _types.isProviderNotReadyError)(error) && (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
67
|
+
resourceIds.forEach(function (resId) {
|
|
68
|
+
return _this.pendingFetchRequests.add(resId);
|
|
69
|
+
});
|
|
70
|
+
if (!_this.isDestroyed) {
|
|
71
|
+
_this.scheduledBatchFetch();
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
48
75
|
(0, _monitoring.logException)(error, {
|
|
49
76
|
location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
|
|
50
77
|
});
|
|
@@ -37,6 +37,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
37
37
|
// causing the cache to be deleted prematurely. We delay deletion to allow
|
|
38
38
|
// the new component to subscribe and cancel the pending deletion.
|
|
39
39
|
(0, _defineProperty2.default)(this, "pendingCacheDeletions", new Map());
|
|
40
|
+
// backoff cap (gate ON)
|
|
40
41
|
(0, _defineProperty2.default)(this, "retryAttempts", new Map());
|
|
41
42
|
(0, _defineProperty2.default)(this, "pendingRetries", new Map());
|
|
42
43
|
this.deps = deps;
|
|
@@ -47,6 +48,30 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
47
48
|
* that need to read the current subscription state.
|
|
48
49
|
*/
|
|
49
50
|
return (0, _createClass2.default)(SyncBlockSubscriptionManager, [{
|
|
51
|
+
key: "getMaxRetryAttempts",
|
|
52
|
+
value:
|
|
53
|
+
// EDITOR-7861: higher ceiling lets transient WS-gateway drops self-heal
|
|
54
|
+
// before a terminal failure is surfaced.
|
|
55
|
+
function getMaxRetryAttempts() {
|
|
56
|
+
return (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3') ? SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS_HARDENED : SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Backoff delay for the given attempt.
|
|
60
|
+
// Gate OFF: pure exponential (1s, 2s, 4s, 8s, 16s).
|
|
61
|
+
// Gate ON (EDITOR-7861): exponential capped at MAX_RETRY_DELAY_MS with equal
|
|
62
|
+
// jitter (capped/2 + random*capped/2) — de-synchronises simultaneous
|
|
63
|
+
// reconnects while guaranteeing a non-zero delay (full jitter could hit 0).
|
|
64
|
+
}, {
|
|
65
|
+
key: "getReconnectionDelay",
|
|
66
|
+
value: function getReconnectionDelay(attempts) {
|
|
67
|
+
var exponential = SyncBlockSubscriptionManager.INITIAL_RETRY_DELAY_MS * Math.pow(SyncBlockSubscriptionManager.RETRY_BACKOFF_MULTIPLIER, attempts);
|
|
68
|
+
if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
69
|
+
return exponential;
|
|
70
|
+
}
|
|
71
|
+
var half = Math.min(exponential, SyncBlockSubscriptionManager.MAX_RETRY_DELAY_MS) / 2;
|
|
72
|
+
return Math.round(half + Math.random() * half);
|
|
73
|
+
}
|
|
74
|
+
}, {
|
|
50
75
|
key: "getSubscriptions",
|
|
51
76
|
value: function getSubscriptions() {
|
|
52
77
|
return this.subscriptions;
|
|
@@ -273,11 +298,17 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
273
298
|
var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
|
|
274
299
|
_this5.handleGraphQLUpdate(syncBlockInstance);
|
|
275
300
|
}, function (error) {
|
|
276
|
-
var _this5$deps$getFireAn;
|
|
277
301
|
(0, _monitoring.logException)(error, {
|
|
278
302
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
|
|
279
303
|
});
|
|
280
|
-
|
|
304
|
+
// EDITOR-7861: a single socket drop is usually transient and
|
|
305
|
+
// recovers on reconnect, so under the gate we don't fire a
|
|
306
|
+
// user-facing error here — it's only surfaced on exhaustion (see
|
|
307
|
+
// scheduleReconnection). Gate OFF keeps the legacy fire-on-drop.
|
|
308
|
+
if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
|
|
309
|
+
var _this5$deps$getFireAn;
|
|
310
|
+
(_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)));
|
|
311
|
+
}
|
|
281
312
|
_this5.handleSubscriptionTerminated(resourceId);
|
|
282
313
|
}, function () {
|
|
283
314
|
_this5.handleSubscriptionTerminated(resourceId);
|
|
@@ -308,19 +339,19 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
308
339
|
}
|
|
309
340
|
}
|
|
310
341
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
* Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
|
|
314
|
-
* e.g. 1s, 2s, 4s, 8s, 16s
|
|
315
|
-
*/
|
|
342
|
+
// Schedules a reconnection with backoff (see getMaxRetryAttempts /
|
|
343
|
+
// getReconnectionDelay for the gated behaviour).
|
|
316
344
|
}, {
|
|
317
345
|
key: "scheduleReconnection",
|
|
318
346
|
value: function scheduleReconnection(resourceId) {
|
|
319
347
|
var _this$retryAttempts$g,
|
|
320
348
|
_this6 = this;
|
|
321
349
|
var attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
|
|
322
|
-
|
|
350
|
+
var maxAttempts = this.getMaxRetryAttempts();
|
|
351
|
+
if (attempts >= maxAttempts) {
|
|
323
352
|
var _this$deps$getFireAna;
|
|
353
|
+
// Exhausted all attempts — the only place a WS drop surfaces as a
|
|
354
|
+
// fetch error under the gate (EDITOR-7861).
|
|
324
355
|
var errorMessage = "Subscription reconnection failed after ".concat(attempts, " attempts");
|
|
325
356
|
(0, _monitoring.logException)(new Error(errorMessage), {
|
|
326
357
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
|
|
@@ -328,7 +359,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
328
359
|
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(errorMessage, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
|
|
329
360
|
return;
|
|
330
361
|
}
|
|
331
|
-
var delay =
|
|
362
|
+
var delay = this.getReconnectionDelay(attempts);
|
|
332
363
|
var timer = setTimeout(function () {
|
|
333
364
|
_this6.pendingRetries.delete(resourceId);
|
|
334
365
|
|
|
@@ -486,7 +517,11 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
|
|
|
486
517
|
}
|
|
487
518
|
}]);
|
|
488
519
|
}();
|
|
489
|
-
// Reconnection with exponential backoff
|
|
520
|
+
// Reconnection with exponential backoff.
|
|
490
521
|
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "INITIAL_RETRY_DELAY_MS", 1000);
|
|
491
522
|
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "RETRY_BACKOFF_MULTIPLIER", 2);
|
|
492
|
-
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
523
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
524
|
+
// legacy (gate OFF)
|
|
525
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS_HARDENED", 8);
|
|
526
|
+
// gate ON (EDITOR-7861)
|
|
527
|
+
(0, _defineProperty2.default)(SyncBlockSubscriptionManager, "MAX_RETRY_DELAY_MS", 30000);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
1
2
|
export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
2
3
|
SyncBlockError["Errored"] = "errored";
|
|
3
4
|
SyncBlockError["NotFound"] = "not_found";
|
|
@@ -16,4 +17,49 @@ export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
|
16
17
|
// block does not exist on this site (e.g. cross-site reference or hard deleted)
|
|
17
18
|
SyncBlockError["EntityNotFound"] = "entity_not_found";
|
|
18
19
|
return SyncBlockError;
|
|
19
|
-
}({});
|
|
20
|
+
}({});
|
|
21
|
+
/**
|
|
22
|
+
* Helpers to distinguish "data provider not ready / torn down" from a genuine
|
|
23
|
+
* fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
|
|
24
|
+
* asynchronously and `destroy()` nulls it on orphaned managers, so queued/
|
|
25
|
+
* in-flight ops throw `Data provider not set` — previously mis-logged as a real
|
|
26
|
+
* error. These let throw and catch sites agree on one non-string-matched signal
|
|
27
|
+
* so the residual false errors are suppressed. Gated by
|
|
28
|
+
* `platform_editor_blocks_patch_3`.
|
|
29
|
+
*
|
|
30
|
+
* NB: these intentionally live here rather than in a dedicated module to avoid
|
|
31
|
+
* adding a downstream file to consuming Jira packages' Thunderstone complexity.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Legacy message — kept identical for gate-off and historical events. */
|
|
35
|
+
export const PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when a fetch/subscribe runs against a manager whose provider is not
|
|
39
|
+
* (yet) available or torn down. Keeps the legacy message + name so string
|
|
40
|
+
* matchers still work, while new code uses {@link isProviderNotReadyError}.
|
|
41
|
+
*/
|
|
42
|
+
export class ProviderNotReadyError extends Error {
|
|
43
|
+
constructor(message = PROVIDER_NOT_READY_MESSAGE) {
|
|
44
|
+
super(message);
|
|
45
|
+
_defineProperty(this, "isProviderNotReadyError", true);
|
|
46
|
+
this.name = 'ProviderNotReadyError';
|
|
47
|
+
// Restore prototype chain so instanceof works after transpilation.
|
|
48
|
+
Object.setPrototypeOf(this, ProviderNotReadyError.prototype);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when the value is a "provider not ready / torn down" condition, not a
|
|
54
|
+
* genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
|
|
55
|
+
* legacy message (for errors thrown before rollout).
|
|
56
|
+
*/
|
|
57
|
+
export const isProviderNotReadyError = error => {
|
|
58
|
+
if (error instanceof ProviderNotReadyError) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (typeof error === 'object' && error !== null && error.isProviderNotReadyError === true) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
|
|
65
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { isSSR } from '@atlaskit/editor-common/core-utils';
|
|
3
3
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
4
|
-
import {
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { isProviderNotReadyError, SyncBlockError } from '../common/types';
|
|
5
6
|
import { fetchErrorPayload } from '../utils/errorHandling';
|
|
6
7
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
7
8
|
export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalyticsEvent) => {
|
|
9
|
+
var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
|
|
8
10
|
// Initialize both states from a single cache lookup to avoid race conditions.
|
|
9
11
|
// When a block is moved/remounted, the old component's cleanup may clear the cache
|
|
10
12
|
// before or after the new component mounts. By doing a single lookup, we ensure
|
|
@@ -26,10 +28,23 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
26
28
|
isLoading: true
|
|
27
29
|
};
|
|
28
30
|
});
|
|
31
|
+
|
|
32
|
+
// On Jira the data provider is wired asynchronously, so the manager can be
|
|
33
|
+
// constructed with `dataProvider === undefined`. Fetching/subscribing in that
|
|
34
|
+
// window throws `Data provider not set`, logged as a false fetch error
|
|
35
|
+
// (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
|
|
36
|
+
// instance is created so `referenceManager` changes identity and the effect
|
|
37
|
+
// below re-runs, subscribing exactly once.
|
|
38
|
+
const isDataProviderReady = fg('platform_editor_blocks_patch_3') ? (_manager$referenceMan2 = (_manager$referenceMan3 = (_manager$referenceMan4 = manager.referenceManager).hasDataProvider) === null || _manager$referenceMan3 === void 0 ? void 0 : _manager$referenceMan3.call(_manager$referenceMan4)) !== null && _manager$referenceMan2 !== void 0 ? _manager$referenceMan2 : false : true;
|
|
29
39
|
const reloadData = useCallback(async () => {
|
|
30
40
|
if (isLoading) {
|
|
31
41
|
return;
|
|
32
42
|
}
|
|
43
|
+
|
|
44
|
+
// Not ready: skip fetch, emit no error (see readiness note above).
|
|
45
|
+
if (!isDataProviderReady) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
33
48
|
try {
|
|
34
49
|
const syncBlockNode = resourceId && localId ? createSyncBlockNode(localId, resourceId) : null;
|
|
35
50
|
if (!syncBlockNode) {
|
|
@@ -43,6 +58,17 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
43
58
|
// Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
|
|
44
59
|
await manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
|
|
45
60
|
} catch (error) {
|
|
61
|
+
// EDITOR-7860: benign not-ready throw — emit no error and stay loading
|
|
62
|
+
// so it resolves on retry once the provider is wired. Checked before
|
|
63
|
+
// `logException` so the benign case produces no exception-tracker noise.
|
|
64
|
+
// Gate-off behaviour is unchanged.
|
|
65
|
+
if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
|
|
66
|
+
setFetchState(prev => ({
|
|
67
|
+
...prev,
|
|
68
|
+
isLoading: true
|
|
69
|
+
}));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
46
72
|
logException(error, {
|
|
47
73
|
location: 'editor-synced-block-provider/useFetchSyncBlockData'
|
|
48
74
|
});
|
|
@@ -64,13 +90,20 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
64
90
|
...prev,
|
|
65
91
|
isLoading: false
|
|
66
92
|
}));
|
|
67
|
-
}, [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
93
|
+
}, [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
68
94
|
useEffect(() => {
|
|
69
95
|
if (isSSR()) {
|
|
70
96
|
// in SSR, we don't need to subscribe to updates,
|
|
71
97
|
// instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
|
|
72
98
|
return;
|
|
73
99
|
}
|
|
100
|
+
|
|
101
|
+
// Not ready: skip subscribe (it would trigger a batched fetch and a false
|
|
102
|
+
// error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
|
|
103
|
+
// so the effect re-runs and subscribes once the provider resolves.
|
|
104
|
+
if (!isDataProviderReady) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
74
107
|
const unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', data => {
|
|
75
108
|
setFetchState({
|
|
76
109
|
syncBlockInstance: data,
|
|
@@ -80,7 +113,7 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
80
113
|
return () => {
|
|
81
114
|
unsubscribe();
|
|
82
115
|
};
|
|
83
|
-
}, [localId, manager.referenceManager, resourceId]);
|
|
116
|
+
}, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
|
|
84
117
|
const ssrProviders = useMemo(() => {
|
|
85
118
|
return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
|
|
86
119
|
}, [resourceId, manager.referenceManager]);
|