@atlaskit/editor-synced-block-provider 3.30.6 → 3.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cjs/clients/block-service/blockService.js +98 -1
  3. package/dist/cjs/clients/block-service/blockSubscription.js +48 -3
  4. package/dist/cjs/index.js +12 -33
  5. package/dist/cjs/providers/block-service/blockServiceAPI.js +509 -185
  6. package/dist/cjs/providers/syncBlockProvider.js +36 -4
  7. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +117 -9
  8. package/dist/cjs/store-manager/syncBlockBatchFetcher.js +76 -0
  9. package/dist/cjs/store-manager/syncBlockInMemorySessionCache.js +26 -0
  10. package/dist/cjs/store-manager/syncBlockProviderFactoryManager.js +175 -0
  11. package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +198 -0
  12. package/dist/es2019/clients/block-service/blockService.js +88 -0
  13. package/dist/es2019/clients/block-service/blockSubscription.js +62 -2
  14. package/dist/es2019/index.js +2 -6
  15. package/dist/es2019/providers/block-service/blockServiceAPI.js +172 -23
  16. package/dist/es2019/providers/syncBlockProvider.js +26 -0
  17. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +93 -9
  18. package/dist/es2019/store-manager/syncBlockBatchFetcher.js +56 -0
  19. package/dist/es2019/store-manager/syncBlockInMemorySessionCache.js +24 -0
  20. package/dist/es2019/store-manager/syncBlockProviderFactoryManager.js +158 -0
  21. package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +136 -0
  22. package/dist/esm/clients/block-service/blockService.js +97 -0
  23. package/dist/esm/clients/block-service/blockSubscription.js +47 -2
  24. package/dist/esm/index.js +2 -6
  25. package/dist/esm/providers/block-service/blockServiceAPI.js +513 -189
  26. package/dist/esm/providers/syncBlockProvider.js +36 -4
  27. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +117 -9
  28. package/dist/esm/store-manager/syncBlockBatchFetcher.js +69 -0
  29. package/dist/esm/store-manager/syncBlockInMemorySessionCache.js +26 -0
  30. package/dist/esm/store-manager/syncBlockProviderFactoryManager.js +168 -0
  31. package/dist/esm/store-manager/syncBlockSubscriptionManager.js +191 -0
  32. package/dist/types/clients/block-service/blockService.d.ts +36 -0
  33. package/dist/types/clients/block-service/blockSubscription.d.ts +26 -1
  34. package/dist/types/index.d.ts +2 -6
  35. package/dist/types/providers/block-service/blockServiceAPI.d.ts +15 -7
  36. package/dist/types/providers/types.d.ts +6 -0
  37. package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +3 -0
  38. package/dist/types/store-manager/syncBlockBatchFetcher.d.ts +25 -0
  39. package/dist/types/store-manager/syncBlockInMemorySessionCache.d.ts +18 -0
  40. package/dist/types/store-manager/syncBlockProviderFactoryManager.d.ts +25 -0
  41. package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +38 -0
  42. package/dist/types-ts4.5/clients/block-service/blockService.d.ts +36 -0
  43. package/dist/types-ts4.5/clients/block-service/blockSubscription.d.ts +26 -1
  44. package/dist/types-ts4.5/index.d.ts +2 -6
  45. package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +15 -7
  46. package/dist/types-ts4.5/providers/types.d.ts +6 -0
  47. package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +3 -0
  48. package/dist/types-ts4.5/store-manager/syncBlockBatchFetcher.d.ts +25 -0
  49. package/dist/types-ts4.5/store-manager/syncBlockInMemorySessionCache.d.ts +18 -0
  50. package/dist/types-ts4.5/store-manager/syncBlockProviderFactoryManager.d.ts +25 -0
  51. package/dist/types-ts4.5/store-manager/syncBlockSubscriptionManager.d.ts +38 -0
  52. package/package.json +5 -3
  53. package/dist/cjs/clients/block-service/sharedSubscriptionUtils.js +0 -82
  54. package/dist/cjs/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -94
  55. package/dist/cjs/utils/relayResponseConverter.js +0 -76
  56. package/dist/cjs/utils/relaySubscriptionUtils.js +0 -130
  57. package/dist/es2019/clients/block-service/sharedSubscriptionUtils.js +0 -91
  58. package/dist/es2019/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -88
  59. package/dist/es2019/utils/relayResponseConverter.js +0 -69
  60. package/dist/es2019/utils/relaySubscriptionUtils.js +0 -125
  61. package/dist/esm/clients/block-service/sharedSubscriptionUtils.js +0 -76
  62. package/dist/esm/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -88
  63. package/dist/esm/utils/relayResponseConverter.js +0 -69
  64. package/dist/esm/utils/relaySubscriptionUtils.js +0 -123
  65. package/dist/types/clients/block-service/sharedSubscriptionUtils.d.ts +0 -61
  66. package/dist/types/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.d.ts +0 -31
  67. package/dist/types/utils/relayResponseConverter.d.ts +0 -47
  68. package/dist/types/utils/relaySubscriptionUtils.d.ts +0 -61
  69. package/dist/types-ts4.5/clients/block-service/sharedSubscriptionUtils.d.ts +0 -61
  70. package/dist/types-ts4.5/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.d.ts +0 -31
  71. package/dist/types-ts4.5/utils/relayResponseConverter.d.ts +0 -47
  72. package/dist/types-ts4.5/utils/relaySubscriptionUtils.d.ts +0 -61
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.SyncBlockSubscriptionManager = void 0;
8
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var _monitoring = require("@atlaskit/editor-common/monitoring");
12
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
+ var _errorHandling = require("../utils/errorHandling");
14
+ var _resolveSyncBlockInstance = require("../utils/resolveSyncBlockInstance");
15
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
16
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
17
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
18
+ /**
19
+ * Manages the lifecycle of GraphQL WebSocket subscriptions for sync block
20
+ * real-time updates, and provides a listener API so React components can
21
+ * react when the set of subscribed resource IDs changes.
22
+ */
23
+ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__PURE__*/function () {
24
+ function SyncBlockSubscriptionManager(deps) {
25
+ (0, _classCallCheck2.default)(this, SyncBlockSubscriptionManager);
26
+ (0, _defineProperty2.default)(this, "graphqlSubscriptions", new Map());
27
+ (0, _defineProperty2.default)(this, "subscriptionChangeListeners", new Set());
28
+ (0, _defineProperty2.default)(this, "useRealTimeSubscriptions", false);
29
+ this.deps = deps;
30
+ }
31
+ return (0, _createClass2.default)(SyncBlockSubscriptionManager, [{
32
+ key: "setRealTimeSubscriptionsEnabled",
33
+ value: function setRealTimeSubscriptionsEnabled(enabled) {
34
+ if (this.useRealTimeSubscriptions === enabled) {
35
+ return;
36
+ }
37
+ this.useRealTimeSubscriptions = enabled;
38
+ if (enabled) {
39
+ this.setupSubscriptionsForAllBlocks();
40
+ } else {
41
+ this.cleanupAll();
42
+ }
43
+ }
44
+ }, {
45
+ key: "isRealTimeSubscriptionsEnabled",
46
+ value: function isRealTimeSubscriptionsEnabled() {
47
+ return this.useRealTimeSubscriptions;
48
+ }
49
+ }, {
50
+ key: "getSubscribedResourceIds",
51
+ value: function getSubscribedResourceIds() {
52
+ return Array.from(this.deps.getSubscriptions().keys());
53
+ }
54
+ }, {
55
+ key: "onSubscriptionsChanged",
56
+ value: function onSubscriptionsChanged(listener) {
57
+ var _this = this;
58
+ this.subscriptionChangeListeners.add(listener);
59
+ return function () {
60
+ _this.subscriptionChangeListeners.delete(listener);
61
+ };
62
+ }
63
+ }, {
64
+ key: "notifySubscriptionChangeListeners",
65
+ value: function notifySubscriptionChangeListeners() {
66
+ var _this2 = this;
67
+ this.subscriptionChangeListeners.forEach(function (listener) {
68
+ try {
69
+ listener();
70
+ } catch (error) {
71
+ var _this2$deps$getFireAn;
72
+ (0, _monitoring.logException)(error, {
73
+ location: 'editor-synced-block-provider/syncBlockSubscriptionManager/notifySubscriptionChangeListeners'
74
+ });
75
+ (_this2$deps$getFireAn = _this2.deps.getFireAnalyticsEvent()) === null || _this2$deps$getFireAn === void 0 || _this2$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message));
76
+ }
77
+ });
78
+ }
79
+ }, {
80
+ key: "handleSubscriptionUpdate",
81
+ value: function handleSubscriptionUpdate(syncBlockInstance) {
82
+ if (!syncBlockInstance.resourceId) {
83
+ return;
84
+ }
85
+ var existing = this.deps.getFromCache(syncBlockInstance.resourceId);
86
+ var resolved = existing ? (0, _resolveSyncBlockInstance.resolveSyncBlockInstance)(existing, syncBlockInstance) : syncBlockInstance;
87
+ this.deps.updateCache(resolved);
88
+ if (!syncBlockInstance.error) {
89
+ this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
90
+ }
91
+ }
92
+ }, {
93
+ key: "setupSubscription",
94
+ value: function setupSubscription(resourceId) {
95
+ var _this3 = this;
96
+ if (this.graphqlSubscriptions.has(resourceId)) {
97
+ return;
98
+ }
99
+ var dataProvider = this.deps.getDataProvider();
100
+ if (!(dataProvider !== null && dataProvider !== void 0 && dataProvider.subscribeToBlockUpdates)) {
101
+ return;
102
+ }
103
+ var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
104
+ _this3.handleGraphQLUpdate(syncBlockInstance);
105
+ }, function (error) {
106
+ var _this3$deps$getFireAn;
107
+ (0, _monitoring.logException)(error, {
108
+ location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
109
+ });
110
+ (_this3$deps$getFireAn = _this3.deps.getFireAnalyticsEvent()) === null || _this3$deps$getFireAn === void 0 || _this3$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message));
111
+ });
112
+ if (unsubscribe) {
113
+ this.graphqlSubscriptions.set(resourceId, unsubscribe);
114
+ }
115
+ }
116
+ }, {
117
+ key: "cleanupSubscription",
118
+ value: function cleanupSubscription(resourceId) {
119
+ var unsubscribe = this.graphqlSubscriptions.get(resourceId);
120
+ if (unsubscribe) {
121
+ unsubscribe();
122
+ this.graphqlSubscriptions.delete(resourceId);
123
+ }
124
+ }
125
+ }, {
126
+ key: "setupSubscriptionsForAllBlocks",
127
+ value: function setupSubscriptionsForAllBlocks() {
128
+ var _iterator = _createForOfIteratorHelper(this.deps.getSubscriptions().keys()),
129
+ _step;
130
+ try {
131
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
132
+ var resourceId = _step.value;
133
+ this.setupSubscription(resourceId);
134
+ }
135
+ } catch (err) {
136
+ _iterator.e(err);
137
+ } finally {
138
+ _iterator.f();
139
+ }
140
+ }
141
+ }, {
142
+ key: "cleanupAll",
143
+ value: function cleanupAll() {
144
+ var _iterator2 = _createForOfIteratorHelper(this.graphqlSubscriptions.values()),
145
+ _step2;
146
+ try {
147
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
148
+ var unsubscribe = _step2.value;
149
+ unsubscribe();
150
+ }
151
+ } catch (err) {
152
+ _iterator2.e(err);
153
+ } finally {
154
+ _iterator2.f();
155
+ }
156
+ this.graphqlSubscriptions.clear();
157
+ }
158
+ }, {
159
+ key: "destroy",
160
+ value: function destroy() {
161
+ this.cleanupAll();
162
+ this.subscriptionChangeListeners.clear();
163
+ this.useRealTimeSubscriptions = false;
164
+ }
165
+ }, {
166
+ key: "shouldUseRealTime",
167
+ value: function shouldUseRealTime() {
168
+ return this.useRealTimeSubscriptions;
169
+ }
170
+ }, {
171
+ key: "handleGraphQLUpdate",
172
+ value: function handleGraphQLUpdate(syncBlockInstance) {
173
+ var _this4 = this;
174
+ if (!syncBlockInstance.resourceId) {
175
+ if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_5')) {
176
+ return;
177
+ }
178
+ throw new Error('Sync block instance provided to graphql subscription update missing resource id');
179
+ }
180
+ var existing = this.deps.getFromCache(syncBlockInstance.resourceId);
181
+ var resolved = existing ? (0, _resolveSyncBlockInstance.resolveSyncBlockInstance)(existing, syncBlockInstance) : syncBlockInstance;
182
+ this.deps.updateCache(resolved);
183
+ if (!syncBlockInstance.error) {
184
+ var callbacks = this.deps.getSubscriptions().get(syncBlockInstance.resourceId);
185
+ var localIds = callbacks ? Object.keys(callbacks) : [];
186
+ localIds.forEach(function (localId) {
187
+ var _this4$deps$getFireAn, _syncBlockInstance$da;
188
+ (_this4$deps$getFireAn = _this4.deps.getFireAnalyticsEvent()) === null || _this4$deps$getFireAn === void 0 || _this4$deps$getFireAn((0, _errorHandling.fetchSuccessPayload)(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = syncBlockInstance.data) === null || _syncBlockInstance$da === void 0 ? void 0 : _syncBlockInstance$da.product));
189
+ });
190
+ this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
191
+ } else {
192
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _syncBlockInstance$er3, _this$deps$getFireAna;
193
+ var errorMessage = (0, _platformFeatureFlags.fg)('platform_synced_block_patch_3') ? ((_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) : (_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type;
194
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(errorMessage, syncBlockInstance.resourceId));
195
+ }
196
+ }
197
+ }]);
198
+ }();
@@ -81,6 +81,7 @@ const UPDATE_DOCUMENT_REFERENCES_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_UPDATE_DO
81
81
  const BATCH_RETRIEVE_BLOCKS_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_BATCH_RETRIEVE_BLOCKS';
82
82
  const GET_BLOCK_REFERENCES_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_GET_REFERENCES';
83
83
  const GET_BLOCK_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_GET_BLOCK';
84
+ const BATCH_UPDATE_BLOCKS_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_BATCH_UPDATE_BLOCKS';
84
85
  const buildGetDocumentReferenceBlocksQuery = documentAri => `query ${GET_DOCUMENT_REFERENCE_BLOCKS_OPERATION_NAME} {
85
86
  blockService_getDocumentReferenceBlocks(documentAri: ${fg('platform_synced_block_patch_4') ? JSON.stringify(documentAri) : `"${documentAri}"`}) {
86
87
  blocks {
@@ -257,6 +258,40 @@ const buildGetBlockReferencesQuery = blockAri => {
257
258
  }
258
259
  }`;
259
260
  };
261
+ const buildBatchUpdateBlocksMutation = blocks => {
262
+ const blocksArray = blocks.map(block => {
263
+ const inputParts = [`blockAri: ${JSON.stringify(block.blockAri)}`, `content: ${JSON.stringify(block.content)}`];
264
+ if (block.stepVersion !== undefined) {
265
+ inputParts.push(`stepVersion: ${block.stepVersion}`);
266
+ }
267
+ if (block.status !== undefined) {
268
+ inputParts.push(`status: ${JSON.stringify(block.status)}`);
269
+ }
270
+ return `{ ${inputParts.join(', ')} }`;
271
+ }).join(', ');
272
+ return `mutation ${BATCH_UPDATE_BLOCKS_OPERATION_NAME} {
273
+ blockService_batchUpdateBlocks(input: { blocks: [${blocksArray}] }) {
274
+ success {
275
+ blockAri
276
+ blockInstanceId
277
+ content
278
+ contentUpdatedAt
279
+ createdAt
280
+ createdBy
281
+ deletionReason
282
+ product
283
+ sourceAri
284
+ status
285
+ version
286
+ }
287
+ error {
288
+ blockAri
289
+ code
290
+ reason
291
+ }
292
+ }
293
+ }`;
294
+ };
260
295
  export class BlockError extends Error {
261
296
  constructor(status) {
262
297
  super(`Block error`);
@@ -473,4 +508,57 @@ export const getReferenceSyncedBlocksByBlockAri = async ({
473
508
  references: graphqlResponse.references || [],
474
509
  errors: graphqlResponse.errors || []
475
510
  };
511
+ };
512
+
513
+ /**
514
+ * Batch updates multiple synced blocks.
515
+ *
516
+ * Calls the Block Service GraphQL API: `blockService_batchUpdateBlocks`
517
+ *
518
+ * @param blocks - Array of block updates to apply
519
+ * @returns A promise containing arrays of successfully updated blocks and any errors encountered
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * const result = await updateSyncedBlocks({
524
+ * blocks: [
525
+ * {
526
+ * blockAri: 'ari:cloud:blocks:site-123:synced-block/uuid-456',
527
+ * content: '{"type":"doc","version":1,"content":[]}',
528
+ * status: 'active',
529
+ * stepVersion: 42
530
+ * }
531
+ * ]
532
+ * });
533
+ * ```
534
+ */
535
+ export const updateSyncedBlocks = async ({
536
+ blocks
537
+ }) => {
538
+ var _result$data8;
539
+ const bodyData = {
540
+ query: buildBatchUpdateBlocksMutation(blocks),
541
+ operationName: BATCH_UPDATE_BLOCKS_OPERATION_NAME
542
+ };
543
+ const url = fg('platform_synced_block_patch_3') ? `${GRAPHQL_ENDPOINT}?operation=editorSyncedBlockBatchUpdateBlocks` : GRAPHQL_ENDPOINT;
544
+ const response = await fetchWithRetry(url, {
545
+ method: 'POST',
546
+ headers: COMMON_HEADERS,
547
+ body: JSON.stringify(bodyData)
548
+ });
549
+ if (!response.ok) {
550
+ throw new BlockError(response.status);
551
+ }
552
+ const result = await response.json();
553
+ if (result.errors && result.errors.length > 0) {
554
+ throw new Error(result.errors.map(e => e.message).join(', '));
555
+ }
556
+ if (!((_result$data8 = result.data) !== null && _result$data8 !== void 0 && _result$data8.blockService_batchUpdateBlocks)) {
557
+ throw new Error('No data returned from GraphQL mutation');
558
+ }
559
+ const graphqlResponse = result.data.blockService_batchUpdateBlocks;
560
+ return {
561
+ success: graphqlResponse.success,
562
+ error: graphqlResponse.error
563
+ };
476
564
  };
@@ -1,6 +1,6 @@
1
1
  import { createClient } from 'graphql-ws';
2
2
  import { isSSR } from '@atlaskit/editor-common/core-utils';
3
- import { BLOCK_SERVICE_SUBSCRIPTION_QUERY, parseSubscriptionPayload } from './sharedSubscriptionUtils';
3
+ import { convertContentUpdatedAt } from '../../utils/utils';
4
4
  const GRAPHQL_WS_ENDPOINT = '/gateway/api/graphql/subscriptions';
5
5
  let blockServiceClient = null;
6
6
  const getBlockServiceClient = () => {
@@ -19,6 +19,66 @@ const getBlockServiceClient = () => {
19
19
  }
20
20
  return blockServiceClient;
21
21
  };
22
+ const SUBSCRIPTION_QUERY = `
23
+ subscription EDITOR_SYNCED_BLOCK_ON_BLOCK_UPDATED($resourceId: ID!) {
24
+ blockService_onBlockUpdated(resourceId: $resourceId) {
25
+ blockAri
26
+ blockInstanceId
27
+ content
28
+ contentUpdatedAt
29
+ createdAt
30
+ createdBy
31
+ deletionReason
32
+ product
33
+ sourceAri
34
+ status
35
+ }
36
+ }
37
+ `;
38
+ /**
39
+ * Extracts the resourceId from a block ARI.
40
+ * Block ARI format: ari:cloud:blocks:<cloudId>:synced-block/<resourceId>
41
+ * @param blockAri - The block ARI string
42
+ * @returns The resourceId portion of the ARI
43
+ */
44
+ const extractResourceIdFromBlockAri = blockAri => {
45
+ // eslint-disable-next-line require-unicode-regexp
46
+ const match = blockAri.match(/ari:cloud:blocks:[^:]+:synced-block\/(.+)$/);
47
+ return (match === null || match === void 0 ? void 0 : match[1]) || null;
48
+ };
49
+
50
+ /**
51
+ * Parses the subscription payload into a standardized format.
52
+ * @param payload - The raw subscription payload
53
+ * @returns Parsed block data or null if parsing fails
54
+ */
55
+ const parseSubscriptionPayload = payload => {
56
+ try {
57
+ const resourceId = extractResourceIdFromBlockAri(payload.blockAri);
58
+ if (!resourceId) {
59
+ return null;
60
+ }
61
+ let createdAt;
62
+ if (payload.createdAt !== undefined && payload.createdAt !== null) {
63
+ createdAt = new Date(payload.createdAt).toISOString();
64
+ }
65
+ return {
66
+ blockAri: payload.blockAri,
67
+ blockInstanceId: payload.blockInstanceId,
68
+ content: JSON.parse(payload.content),
69
+ contentUpdatedAt: convertContentUpdatedAt(payload.contentUpdatedAt),
70
+ createdAt,
71
+ createdBy: payload.createdBy,
72
+ product: payload.product,
73
+ resourceId,
74
+ sourceAri: payload.sourceAri,
75
+ status: payload.status
76
+ };
77
+ } catch {
78
+ return null;
79
+ }
80
+ };
81
+
22
82
  /**
23
83
  * Creates a GraphQL subscription to block updates using the shared graphql-ws client.
24
84
  *
@@ -34,7 +94,7 @@ export const subscribeToBlockUpdates = (blockAri, onData, onError) => {
34
94
  return () => {};
35
95
  }
36
96
  const unsubscribe = client.subscribe({
37
- query: BLOCK_SERVICE_SUBSCRIPTION_QUERY,
97
+ query: SUBSCRIPTION_QUERY,
38
98
  variables: {
39
99
  resourceId: blockAri
40
100
  },
@@ -10,13 +10,13 @@ export { useHandleContentChanges } from './hooks/useHandleContentChanges';
10
10
 
11
11
  // clients
12
12
  export { generateBlockAri, generateBlockAriFromReference, getLocalIdFromBlockResourceId } from './clients/block-service/ari';
13
- export { BlockError } from './clients/block-service/blockService';
13
+ export { BlockError, updateSyncedBlocks } from './clients/block-service/blockService';
14
14
  export { getConfluencePageAri, getPageIdAndTypeFromConfluencePageAri } from './clients/confluence/ari';
15
15
  export { fetchMediaToken } from './clients/confluence/fetchMediaToken';
16
16
  export { getJiraWorkItemAri, getJiraWorkItemIdFromAri } from './clients/jira/ari';
17
17
 
18
18
  // providers
19
- export { useMemoizedBlockServiceAPIProviders, useMemoizedBlockServiceFetchOnlyAPIProvider, fetchReferences, batchFetchData, blockAriToResourceId, convertToSyncBlockData, extractResourceIdFromBlockAri } from './providers/block-service/blockServiceAPI';
19
+ export { useMemoizedBlockServiceAPIProviders, useMemoizedBlockServiceFetchOnlyAPIProvider, fetchReferences, batchFetchData, writeDataBatch, blockAriToResourceId, convertToSyncBlockData, extractResourceIdFromBlockAri } from './providers/block-service/blockServiceAPI';
20
20
  export { fetchConfluencePageInfo } from './clients/confluence/sourceInfo';
21
21
  export { SyncedBlockProvider, useMemoizedSyncedBlockProvider } from './providers/syncBlockProvider';
22
22
  // store managers
@@ -25,10 +25,6 @@ export { SyncBlockInMemorySessionCache, syncBlockInMemorySessionCache } from './
25
25
  export { SyncBlockStoreManager, useMemoizedSyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
26
26
 
27
27
  // utils
28
- export { BLOCK_SERVICE_SUBSCRIPTION_QUERY } from './clients/block-service/sharedSubscriptionUtils';
29
- export { parseSubscriptionPayload } from './clients/block-service/sharedSubscriptionUtils';
30
- export { convertRelayResponseToSyncBlockInstance } from './utils/relayResponseConverter';
31
- export { createRelayBlockSubscription, createRelaySubscriptionFunction } from './utils/relaySubscriptionUtils';
32
28
  export { resolveSyncBlockInstance } from './utils/resolveSyncBlockInstance';
33
29
  export { parseResourceId, createResourceIdForReference } from './utils/resourceId';
34
30
  export { createSyncBlockNode, convertSyncBlockPMNodeToSyncBlockData, convertSyncBlockJSONNodeToSyncBlockNode, convertPMNodesToSyncBlockNodes, getContentIdAndProductFromResourceId } from './utils/utils';
@@ -2,11 +2,10 @@
2
2
  import { useMemo } from 'react';
3
3
  import { fg } from '@atlaskit/platform-feature-flags';
4
4
  import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
5
- import { batchRetrieveSyncedBlocks, BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock } from '../../clients/block-service/blockService';
5
+ import { batchRetrieveSyncedBlocks, BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
6
6
  import { subscribeToBlockUpdates as subscribeToBlockUpdatesWS } from '../../clients/block-service/blockSubscription';
7
7
  import { SyncBlockError } from '../../common/types';
8
8
  import { stringifyError } from '../../utils/errorHandling';
9
- import { createRelaySubscriptionFunction } from '../../utils/relaySubscriptionUtils';
10
9
  import { createResourceIdForReference } from '../../utils/resourceId';
11
10
  import { convertContentUpdatedAt } from '../../utils/utils';
12
11
  const mapBlockError = error => {
@@ -308,18 +307,102 @@ export const batchFetchData = async (cloudId, parentAri, blockNodeIdentifiers) =
308
307
  }));
309
308
  }
310
309
  };
310
+
311
+ /**
312
+ * Batch writes multiple synced blocks.
313
+ * @param cloudId - The cloudId of the block. E.G the cloudId of the confluence page, or the cloudId of the Jira instance
314
+ * @param parentAri - The ARI of the parent of the block. E.G the ARI of the confluence page, or the ARI of the Jira work item
315
+ * @param parentId - The parentId of the block. E.G the pageId for a confluence page, or the issueId for a Jira work item
316
+ * @param product - The product of the block. E.G 'confluence-page', 'jira-work-item'
317
+ * @param data - Array of SyncBlockData to write
318
+ * @param stepVersion - Optional version number
319
+ * @returns Array of WriteSyncBlockResult results
320
+ */
321
+ export const writeDataBatch = async (cloudId, parentAri, parentId, product, data, stepVersion) => {
322
+ if (!parentAri || !parentId) {
323
+ return data.map(block => ({
324
+ error: SyncBlockError.Errored,
325
+ resourceId: block.resourceId
326
+ }));
327
+ }
328
+ try {
329
+ // Create a map from blockAri to original resourceId for matching responses
330
+ const blockAriToResourceIdMap = new Map();
331
+ const blocks = data.map(block => {
332
+ const blockAri = generateBlockAri({
333
+ cloudId,
334
+ parentId,
335
+ product,
336
+ resourceId: block.resourceId
337
+ });
338
+ blockAriToResourceIdMap.set(blockAri, block.resourceId);
339
+ return {
340
+ blockAri,
341
+ content: JSON.stringify(block.content),
342
+ status: block.status,
343
+ stepVersion
344
+ };
345
+ });
346
+ const response = await updateSyncedBlocks({
347
+ blocks
348
+ });
349
+ const results = [];
350
+
351
+ // Process successful updates
352
+ if (response.success) {
353
+ const successResourceIds = new Set(response.success.map(block => blockAriToResourceIdMap.get(block.blockAri)));
354
+ for (const block of data) {
355
+ if (successResourceIds.has(block.resourceId)) {
356
+ results.push({
357
+ resourceId: block.resourceId
358
+ });
359
+ }
360
+ }
361
+ }
362
+ if (response.error) {
363
+ const errorResourceIds = new Map(response.error.map(err => [
364
+ // Use the map to get the original resourceId
365
+ blockAriToResourceIdMap.get(err.blockAri) || '', mapErrorResponseCode(err.code)]));
366
+ for (const block of data) {
367
+ const error = errorResourceIds.get(block.resourceId);
368
+ if (error) {
369
+ results.push({
370
+ error,
371
+ resourceId: block.resourceId
372
+ });
373
+ } else if (!results.some(r => r.resourceId === block.resourceId)) {
374
+ // If not in success or error lists, mark as errored
375
+ results.push({
376
+ error: SyncBlockError.Errored,
377
+ resourceId: block.resourceId
378
+ });
379
+ }
380
+ }
381
+ }
382
+ return results;
383
+ } catch (error) {
384
+ if (error instanceof BlockError) {
385
+ return data.map(block => ({
386
+ error: mapBlockError(error),
387
+ resourceId: block.resourceId
388
+ }));
389
+ }
390
+ return data.map(block => ({
391
+ error: stringifyError(error),
392
+ resourceId: block.resourceId
393
+ }));
394
+ }
395
+ };
311
396
  /**
312
397
  * ADFFetchProvider implementation that fetches synced block data from Block Service API
313
398
  */
314
399
  class BlockServiceADFFetchProvider {
315
400
  constructor({
316
401
  cloudId,
317
- parentAri,
318
- relayEnvironment
402
+ parentAri
319
403
  }) {
320
404
  this.cloudId = cloudId;
321
405
  this.parentAri = parentAri;
322
- this.relayEnvironment = relayEnvironment;
323
406
  }
324
407
 
325
408
  // resourceId of the reference synced block.
@@ -445,7 +528,6 @@ class BlockServiceADFFetchProvider {
445
528
 
446
529
  /**
447
530
  * Subscribes to real-time updates for a specific block using GraphQL WebSocket subscriptions.
448
- * If a Relay environment is provided, uses Relay subscriptions; otherwise falls back to WebSocket.
449
531
  * @param resourceId - The resource ID of the block to subscribe to
450
532
  * @param onUpdate - Callback function invoked when the block is updated
451
533
  * @param onError - Optional callback function invoked on subscription errors
@@ -456,14 +538,6 @@ class BlockServiceADFFetchProvider {
456
538
  cloudId: this.cloudId,
457
539
  resourceId
458
540
  });
459
-
460
- // If Relay environment is available, use Relay subscriptions
461
- if (this.relayEnvironment && fg('platform_synced_block_patch_3')) {
462
- const relaySubscribeToBlockUpdates = createRelaySubscriptionFunction(this.cloudId, this.relayEnvironment);
463
- return relaySubscribeToBlockUpdates(resourceId, onUpdate, onError);
464
- }
465
-
466
- // Fall back to WebSocket subscriptions
467
541
  return subscribeToBlockUpdatesWS(blockAri, parsedData => {
468
542
  // Convert ParsedBlockSubscriptionData to SyncBlockInstance
469
543
  const syncBlockInstance = {
@@ -643,6 +717,14 @@ class BlockServiceADFWriteProvider {
643
717
  generateResourceId() {
644
718
  return crypto.randomUUID();
645
719
  }
720
+ generateBlockAri(resourceId) {
721
+ return generateBlockAri({
722
+ cloudId: this.cloudId,
723
+ parentId: this.parentId || '',
724
+ product: this.product,
725
+ resourceId
726
+ });
727
+ }
646
728
  async updateReferenceData(blocks, noContent) {
647
729
  if (!this.parentAri) {
648
730
  return {
@@ -678,20 +760,89 @@ class BlockServiceADFWriteProvider {
678
760
  };
679
761
  }
680
762
  }
763
+ async writeDataBatch(data) {
764
+ if (!this.parentAri || !this.parentId) {
765
+ return data.map(block => ({
766
+ error: SyncBlockError.Errored,
767
+ resourceId: block.resourceId
768
+ }));
769
+ }
770
+ const stepVersion = this.getVersion ? await this.getVersion() : undefined;
771
+ try {
772
+ // Create a map from blockAri to original resourceId for matching responses
773
+ const blockAriToResourceIdMap = new Map();
774
+ const blocks = data.map(block => {
775
+ const blockAri = this.generateBlockAri(block.resourceId);
776
+ blockAriToResourceIdMap.set(blockAri, block.resourceId);
777
+ return {
778
+ blockAri,
779
+ content: JSON.stringify(block.content),
780
+ status: block.status,
781
+ stepVersion
782
+ };
783
+ });
784
+ const response = await updateSyncedBlocks({
785
+ blocks
786
+ });
787
+ const results = [];
788
+
789
+ // Process successful updates
790
+ if (response.success) {
791
+ const successResourceIds = new Set(response.success.map(block => blockAriToResourceIdMap.get(block.blockAri)));
792
+ for (const block of data) {
793
+ if (successResourceIds.has(block.resourceId)) {
794
+ results.push({
795
+ resourceId: block.resourceId
796
+ });
797
+ }
798
+ }
799
+ }
800
+ if (response.error) {
801
+ const errorResourceIds = new Map(response.error.map(err => [
802
+ // Use the map to get the original resourceId
803
+ blockAriToResourceIdMap.get(err.blockAri) || '', mapErrorResponseCode(err.code)]));
804
+ for (const block of data) {
805
+ const error = errorResourceIds.get(block.resourceId);
806
+ if (error) {
807
+ results.push({
808
+ error,
809
+ resourceId: block.resourceId
810
+ });
811
+ } else if (!results.some(r => r.resourceId === block.resourceId)) {
812
+ // If not in success or error lists, mark as errored
813
+ results.push({
814
+ error: SyncBlockError.Errored,
815
+ resourceId: block.resourceId
816
+ });
817
+ }
818
+ }
819
+ }
820
+ return results;
821
+ } catch (error) {
822
+ if (error instanceof BlockError) {
823
+ return data.map(block => ({
824
+ error: mapBlockError(error),
825
+ resourceId: block.resourceId
826
+ }));
827
+ }
828
+ return data.map(block => ({
829
+ error: stringifyError(error),
830
+ resourceId: block.resourceId
831
+ }));
832
+ }
833
+ }
681
834
  }
682
835
  const createBlockServiceAPIProviders = ({
683
836
  cloudId,
684
837
  parentAri,
685
838
  parentId,
686
839
  product,
687
- getVersion,
688
- relayEnvironment
840
+ getVersion
689
841
  }) => {
690
842
  return {
691
843
  fetchProvider: new BlockServiceADFFetchProvider({
692
844
  cloudId,
693
- parentAri,
694
- relayEnvironment
845
+ parentAri
695
846
  }),
696
847
  writeProvider: new BlockServiceADFWriteProvider({
697
848
  cloudId,
@@ -707,8 +858,7 @@ export const useMemoizedBlockServiceAPIProviders = ({
707
858
  parentAri,
708
859
  parentId,
709
860
  product,
710
- getVersion,
711
- relayEnvironment
861
+ getVersion
712
862
  }) => {
713
863
  return useMemo(() => {
714
864
  return createBlockServiceAPIProviders({
@@ -716,10 +866,9 @@ export const useMemoizedBlockServiceAPIProviders = ({
716
866
  parentAri,
717
867
  parentId,
718
868
  product,
719
- getVersion,
720
- relayEnvironment
869
+ getVersion
721
870
  });
722
- }, [cloudId, parentAri, parentId, product, getVersion, relayEnvironment]);
871
+ }, [cloudId, parentAri, parentId, product, getVersion]);
723
872
  };
724
873
  const createBlockServiceFetchOnlyAPIProvider = ({
725
874
  cloudId,