@atlaskit/editor-synced-block-provider 3.12.1 → 3.13.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 (41) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/clients/block-service/blockSubscription.js +124 -0
  3. package/dist/cjs/clients/jira/sourceInfo.js +152 -0
  4. package/dist/cjs/providers/block-service/blockServiceAPI.js +43 -6
  5. package/dist/cjs/providers/syncBlockProvider.js +40 -8
  6. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +351 -118
  7. package/dist/cjs/store-manager/syncBlockStoreManager.js +2 -2
  8. package/dist/cjs/utils/resolveSyncBlockInstance.js +1 -1
  9. package/dist/es2019/clients/block-service/blockSubscription.js +125 -0
  10. package/dist/es2019/clients/jira/sourceInfo.js +87 -0
  11. package/dist/es2019/providers/block-service/blockServiceAPI.js +40 -5
  12. package/dist/es2019/providers/syncBlockProvider.js +26 -2
  13. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +237 -49
  14. package/dist/es2019/store-manager/syncBlockStoreManager.js +2 -2
  15. package/dist/es2019/utils/resolveSyncBlockInstance.js +1 -1
  16. package/dist/esm/clients/block-service/blockSubscription.js +118 -0
  17. package/dist/esm/clients/jira/sourceInfo.js +147 -0
  18. package/dist/esm/providers/block-service/blockServiceAPI.js +43 -6
  19. package/dist/esm/providers/syncBlockProvider.js +38 -6
  20. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +351 -118
  21. package/dist/esm/store-manager/syncBlockStoreManager.js +2 -2
  22. package/dist/esm/utils/resolveSyncBlockInstance.js +1 -1
  23. package/dist/types/clients/block-service/blockService.d.ts +2 -2
  24. package/dist/types/clients/block-service/blockSubscription.d.ts +38 -0
  25. package/dist/types/clients/jira/sourceInfo.d.ts +2 -0
  26. package/dist/types/common/types.d.ts +4 -2
  27. package/dist/types/index.d.ts +2 -2
  28. package/dist/types/providers/block-service/blockServiceAPI.d.ts +8 -0
  29. package/dist/types/providers/syncBlockProvider.d.ts +9 -1
  30. package/dist/types/providers/types.d.ts +22 -6
  31. package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +59 -0
  32. package/dist/types-ts4.5/clients/block-service/blockService.d.ts +2 -2
  33. package/dist/types-ts4.5/clients/block-service/blockSubscription.d.ts +38 -0
  34. package/dist/types-ts4.5/clients/jira/sourceInfo.d.ts +2 -0
  35. package/dist/types-ts4.5/common/types.d.ts +4 -2
  36. package/dist/types-ts4.5/index.d.ts +2 -2
  37. package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +8 -0
  38. package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +9 -1
  39. package/dist/types-ts4.5/providers/types.d.ts +22 -6
  40. package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +59 -0
  41. package/package.json +2 -1
@@ -15,17 +15,16 @@ import { createSyncBlockNode } from '../utils/utils';
15
15
  // Handles fetching source URL and title for sync blocks.
16
16
  // Can be used in both editor and renderer contexts.
17
17
  export class ReferenceSyncBlockStoreManager {
18
- // Track pending cache deletions to handle block moves (unmount/remount)
19
- // When a block is moved, the old component unmounts before the new one mounts,
20
- // causing the cache to be deleted prematurely. We delay deletion to allow
21
- // the new component to subscribe and cancel the pending deletion.
18
+ // Listeners for subscription changes (used by React components to know when to update)
22
19
 
23
20
  constructor(dataProvider) {
24
21
  // Keeps track of addition and deletion of reference synced blocks on the document
25
22
  // This starts as true to always flush the cache when document is saved for the first time
26
- // to cater the case when a editor seesion is closed without document being updated right after reference block is deleted
23
+ // to cater the case when a editor session is closed without document being updated right after reference block is deleted
27
24
  _defineProperty(this, "isCacheDirty", true);
28
25
  _defineProperty(this, "isRefreshingSubscriptions", false);
26
+ // Flag to indicate if real-time subscriptions are enabled
27
+ _defineProperty(this, "useRealTimeSubscriptions", false);
29
28
  this.syncBlockCache = new Map();
30
29
  this.subscriptions = new Map();
31
30
  this.titleSubscriptions = new Map();
@@ -35,6 +34,89 @@ export class ReferenceSyncBlockStoreManager {
35
34
  this.syncBlockSourceInfoRequests = new Map();
36
35
  this.providerFactories = new Map();
37
36
  this.pendingCacheDeletions = new Map();
37
+ this.graphqlSubscriptions = new Map();
38
+ this.subscriptionChangeListeners = new Set();
39
+ }
40
+
41
+ /**
42
+ * Enables or disables real-time GraphQL subscriptions for block updates.
43
+ * When enabled, the store manager will subscribe to real-time updates
44
+ * instead of relying on polling.
45
+ * @param enabled - Whether to enable real-time subscriptions
46
+ */
47
+ setRealTimeSubscriptionsEnabled(enabled) {
48
+ if (this.useRealTimeSubscriptions === enabled) {
49
+ return;
50
+ }
51
+ this.useRealTimeSubscriptions = enabled;
52
+ if (enabled) {
53
+ // Set up subscriptions for all currently subscribed blocks
54
+ this.setupGraphQLSubscriptionsForAllBlocks();
55
+ } else {
56
+ // Clean up all GraphQL subscriptions
57
+ this.cleanupAllGraphQLSubscriptions();
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Checks if real-time subscriptions are currently enabled.
63
+ */
64
+ isRealTimeSubscriptionsEnabled() {
65
+ return this.useRealTimeSubscriptions;
66
+ }
67
+
68
+ /**
69
+ * Returns all resource IDs that are currently subscribed to.
70
+ * Used by React components to render subscription components.
71
+ */
72
+ getSubscribedResourceIds() {
73
+ return Array.from(this.subscriptions.keys());
74
+ }
75
+
76
+ /**
77
+ * Registers a listener that will be called when subscriptions change.
78
+ * @param listener - Callback function to invoke when subscriptions change
79
+ * @returns Unsubscribe function to remove the listener
80
+ */
81
+ onSubscriptionsChanged(listener) {
82
+ this.subscriptionChangeListeners.add(listener);
83
+ return () => {
84
+ this.subscriptionChangeListeners.delete(listener);
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Notifies all subscription change listeners.
90
+ */
91
+ notifySubscriptionChangeListeners() {
92
+ this.subscriptionChangeListeners.forEach(listener => {
93
+ try {
94
+ listener();
95
+ } catch (error) {
96
+ var _this$fireAnalyticsEv;
97
+ logException(error, {
98
+ location: 'editor-synced-block-provider/referenceSyncBlockStoreManager/notifySubscriptionChangeListeners'
99
+ });
100
+ (_this$fireAnalyticsEv = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv === void 0 ? void 0 : _this$fireAnalyticsEv.call(this, fetchErrorPayload(error.message));
101
+ }
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Handles incoming data from a GraphQL subscription.
107
+ * Called by React subscription components when they receive updates.
108
+ * @param syncBlockInstance - The updated sync block instance
109
+ */
110
+ handleSubscriptionUpdate(syncBlockInstance) {
111
+ if (!syncBlockInstance.resourceId) {
112
+ return;
113
+ }
114
+ const existingSyncBlock = this.getFromCache(syncBlockInstance.resourceId);
115
+ const resolvedSyncBlockInstance = existingSyncBlock ? resolveSyncBlockInstance(existingSyncBlock, syncBlockInstance) : syncBlockInstance;
116
+ this.updateCache(resolvedSyncBlockInstance);
117
+ if (!syncBlockInstance.error) {
118
+ this.fetchSyncBlockSourceInfo(resolvedSyncBlockInstance.resourceId);
119
+ }
38
120
  }
39
121
  setFireAnalyticsEvent(fireAnalyticsEvent) {
40
122
  this.fireAnalyticsEvent = fireAnalyticsEvent;
@@ -59,9 +141,14 @@ export class ReferenceSyncBlockStoreManager {
59
141
 
60
142
  /**
61
143
  * Refreshes the subscriptions for all sync blocks.
144
+ * This is a fallback polling mechanism when real-time subscriptions are not enabled.
62
145
  * @returns {Promise<void>}
63
146
  */
64
147
  async refreshSubscriptions() {
148
+ // Skip polling refresh if real-time subscriptions are enabled
149
+ if (this.useRealTimeSubscriptions) {
150
+ return;
151
+ }
65
152
  if (this.isRefreshingSubscriptions) {
66
153
  return;
67
154
  }
@@ -77,15 +164,90 @@ export class ReferenceSyncBlockStoreManager {
77
164
  // this function will update the cache and call the subscriptions
78
165
  await this.fetchSyncBlocksData(syncBlocks);
79
166
  } catch (error) {
80
- var _this$fireAnalyticsEv;
167
+ var _this$fireAnalyticsEv2;
81
168
  logException(error, {
82
169
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
83
170
  });
84
- (_this$fireAnalyticsEv = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv === void 0 ? void 0 : _this$fireAnalyticsEv.call(this, fetchErrorPayload(error.message));
171
+ (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 ? void 0 : _this$fireAnalyticsEv2.call(this, fetchErrorPayload(error.message));
85
172
  } finally {
86
173
  this.isRefreshingSubscriptions = false;
87
174
  }
88
175
  }
176
+
177
+ /**
178
+ * Sets up a GraphQL subscription for a specific block.
179
+ * @param resourceId - The resource ID of the block to subscribe to
180
+ */
181
+ setupGraphQLSubscription(resourceId) {
182
+ var _this$dataProvider2;
183
+ // Don't set up duplicate subscriptions
184
+ if (this.graphqlSubscriptions.has(resourceId)) {
185
+ return;
186
+ }
187
+ if (!((_this$dataProvider2 = this.dataProvider) !== null && _this$dataProvider2 !== void 0 && _this$dataProvider2.subscribeToBlockUpdates)) {
188
+ return;
189
+ }
190
+ const unsubscribe = this.dataProvider.subscribeToBlockUpdates(resourceId, syncBlockInstance => {
191
+ // Handle the subscription update
192
+ this.handleGraphQLSubscriptionUpdate(syncBlockInstance);
193
+ }, error => {
194
+ var _this$fireAnalyticsEv3;
195
+ logException(error, {
196
+ location: 'editor-synced-block-provider/referenceSyncBlockStoreManager/graphql-subscription'
197
+ });
198
+ (_this$fireAnalyticsEv3 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 ? void 0 : _this$fireAnalyticsEv3.call(this, fetchErrorPayload(error.message));
199
+ });
200
+ if (unsubscribe) {
201
+ this.graphqlSubscriptions.set(resourceId, unsubscribe);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Handles updates received from GraphQL subscriptions.
207
+ * @param syncBlockInstance - The updated sync block instance
208
+ */
209
+ handleGraphQLSubscriptionUpdate(syncBlockInstance) {
210
+ if (!syncBlockInstance.resourceId) {
211
+ return;
212
+ }
213
+ const existingSyncBlock = this.getFromCache(syncBlockInstance.resourceId);
214
+ const resolvedSyncBlockInstance = existingSyncBlock ? resolveSyncBlockInstance(existingSyncBlock, syncBlockInstance) : syncBlockInstance;
215
+ this.updateCache(resolvedSyncBlockInstance);
216
+ if (!syncBlockInstance.error) {
217
+ this.fetchSyncBlockSourceInfo(resolvedSyncBlockInstance.resourceId);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Cleans up the GraphQL subscription for a specific block.
223
+ * @param resourceId - The resource ID of the block to unsubscribe from
224
+ */
225
+ cleanupGraphQLSubscription(resourceId) {
226
+ const unsubscribe = this.graphqlSubscriptions.get(resourceId);
227
+ if (unsubscribe) {
228
+ unsubscribe();
229
+ this.graphqlSubscriptions.delete(resourceId);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Sets up GraphQL subscriptions for all currently subscribed blocks.
235
+ */
236
+ setupGraphQLSubscriptionsForAllBlocks() {
237
+ for (const resourceId of this.subscriptions.keys()) {
238
+ this.setupGraphQLSubscription(resourceId);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Cleans up all GraphQL subscriptions.
244
+ */
245
+ cleanupAllGraphQLSubscriptions() {
246
+ for (const unsubscribe of this.graphqlSubscriptions.values()) {
247
+ unsubscribe();
248
+ }
249
+ this.graphqlSubscriptions.clear();
250
+ }
89
251
  fetchSyncBlockSourceInfo(resourceId) {
90
252
  try {
91
253
  if (!resourceId || !this.dataProvider) {
@@ -109,7 +271,7 @@ export class ReferenceSyncBlockStoreManager {
109
271
  blockInstanceId,
110
272
  sourceURL,
111
273
  sourceTitle,
112
- onSamePage,
274
+ onSameDocument,
113
275
  sourceSubType
114
276
  } = existingSyncBlock.data || {};
115
277
  // skip if source URL and title are already present
@@ -120,7 +282,7 @@ export class ReferenceSyncBlockStoreManager {
120
282
  url: sourceURL,
121
283
  subType: sourceSubType,
122
284
  sourceAri: sourceAri || '',
123
- onSamePage,
285
+ onSameDocument,
124
286
  productType: product
125
287
  });
126
288
  } else {
@@ -128,8 +290,8 @@ export class ReferenceSyncBlockStoreManager {
128
290
  }
129
291
  }
130
292
  if (!sourceAri || !product || !blockInstanceId) {
131
- var _this$fireAnalyticsEv2;
132
- (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 ? void 0 : _this$fireAnalyticsEv2.call(this, getSourceInfoErrorPayload('SourceAri, product or blockInstanceId missing', resourceId));
293
+ var _this$fireAnalyticsEv4;
294
+ (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 ? void 0 : _this$fireAnalyticsEv4.call(this, getSourceInfoErrorPayload('SourceAri, product or blockInstanceId missing', resourceId));
133
295
  return Promise.resolve(undefined);
134
296
  }
135
297
  if (fg('platform_synced_block_dogfooding')) {
@@ -139,11 +301,11 @@ export class ReferenceSyncBlockStoreManager {
139
301
  const sourceInfoPromise = this.dataProvider.fetchSyncBlockSourceInfo(blockInstanceId, sourceAri, product, this.fireAnalyticsEvent).then(sourceInfo => {
140
302
  if (!sourceInfo) {
141
303
  if (fg('platform_synced_block_dogfooding')) {
142
- var _this$fetchSourceInfo2, _this$fireAnalyticsEv3;
304
+ var _this$fetchSourceInfo2, _this$fireAnalyticsEv5;
143
305
  (_this$fetchSourceInfo2 = this.fetchSourceInfoExperience) === null || _this$fetchSourceInfo2 === void 0 ? void 0 : _this$fetchSourceInfo2.failure({
144
306
  reason: 'No source info returned'
145
307
  });
146
- (_this$fireAnalyticsEv3 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 ? void 0 : _this$fireAnalyticsEv3.call(this, getSourceInfoErrorPayload('No source info returned', resourceId));
308
+ (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 ? void 0 : _this$fireAnalyticsEv5.call(this, getSourceInfoErrorPayload('No source info returned', resourceId));
147
309
  }
148
310
  return undefined;
149
311
  }
@@ -156,23 +318,23 @@ export class ReferenceSyncBlockStoreManager {
156
318
  var _this$fetchSourceInfo3;
157
319
  (_this$fetchSourceInfo3 = this.fetchSourceInfoExperience) === null || _this$fetchSourceInfo3 === void 0 ? void 0 : _this$fetchSourceInfo3.success();
158
320
  } else {
159
- var _this$fetchSourceInfo4, _this$fireAnalyticsEv4;
321
+ var _this$fetchSourceInfo4, _this$fireAnalyticsEv6;
160
322
  (_this$fetchSourceInfo4 = this.fetchSourceInfoExperience) === null || _this$fetchSourceInfo4 === void 0 ? void 0 : _this$fetchSourceInfo4.failure({
161
323
  reason: 'Missing title or url'
162
324
  });
163
- (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 ? void 0 : _this$fireAnalyticsEv4.call(this, getSourceInfoErrorPayload('Missing title or url', resourceId));
325
+ (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 ? void 0 : _this$fireAnalyticsEv6.call(this, getSourceInfoErrorPayload('Missing title or url', resourceId));
164
326
  }
165
327
  return sourceInfo;
166
328
  }
167
329
  }).catch(error => {
168
- var _this$fireAnalyticsEv5;
330
+ var _this$fireAnalyticsEv7;
169
331
  if (fg('platform_synced_block_dogfooding')) {
170
332
  var _this$fetchSourceInfo5;
171
333
  (_this$fetchSourceInfo5 = this.fetchSourceInfoExperience) === null || _this$fetchSourceInfo5 === void 0 ? void 0 : _this$fetchSourceInfo5.failure({
172
334
  reason: error.message
173
335
  });
174
336
  }
175
- (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 ? void 0 : _this$fireAnalyticsEv5.call(this, getSourceInfoErrorPayload(error.message, resourceId));
337
+ (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 ? void 0 : _this$fireAnalyticsEv7.call(this, getSourceInfoErrorPayload(error.message, resourceId));
176
338
  return undefined;
177
339
  }).finally(() => {
178
340
  if (fg('platform_synced_block_dogfooding')) {
@@ -188,11 +350,11 @@ export class ReferenceSyncBlockStoreManager {
188
350
  this.syncBlockSourceInfoRequestsOld.set(resourceId, true);
189
351
  }
190
352
  } catch (error) {
191
- var _this$fireAnalyticsEv6;
353
+ var _this$fireAnalyticsEv8;
192
354
  logException(error, {
193
355
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
194
356
  });
195
- (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 ? void 0 : _this$fireAnalyticsEv6.call(this, getSourceInfoErrorPayload(error.message, resourceId));
357
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, getSourceInfoErrorPayload(error.message, resourceId));
196
358
  }
197
359
  return Promise.resolve(undefined);
198
360
  }
@@ -242,8 +404,8 @@ export class ReferenceSyncBlockStoreManager {
242
404
  let hasExpectedError = false;
243
405
  data.forEach(syncBlockInstance => {
244
406
  if (!syncBlockInstance.resourceId) {
245
- var _this$fireAnalyticsEv7;
246
- (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 ? void 0 : _this$fireAnalyticsEv7.call(this, fetchErrorPayload(syncBlockInstance.error || 'Returned sync block instance does not have resource id'));
407
+ var _this$fireAnalyticsEv9;
408
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, fetchErrorPayload(syncBlockInstance.error || 'Returned sync block instance does not have resource id'));
247
409
  return;
248
410
  }
249
411
  const existingSyncBlock = this.getFromCache(syncBlockInstance.resourceId);
@@ -251,8 +413,8 @@ export class ReferenceSyncBlockStoreManager {
251
413
  this.updateCache(resolvedSyncBlockInstance);
252
414
  resolvedData.push(resolvedSyncBlockInstance);
253
415
  if (syncBlockInstance.error) {
254
- var _this$fireAnalyticsEv8;
255
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, fetchErrorPayload(syncBlockInstance.error, syncBlockInstance.resourceId));
416
+ var _this$fireAnalyticsEv0;
417
+ (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 ? void 0 : _this$fireAnalyticsEv0.call(this, fetchErrorPayload(syncBlockInstance.error, syncBlockInstance.resourceId));
256
418
  if (syncBlockInstance.error === SyncBlockError.NotFound || syncBlockInstance.error === SyncBlockError.Forbidden) {
257
419
  hasExpectedError = true;
258
420
  } else if (syncBlockInstance.error) {
@@ -260,8 +422,8 @@ export class ReferenceSyncBlockStoreManager {
260
422
  }
261
423
  return;
262
424
  } else if (fg('platform_synced_block_dogfooding')) {
263
- var _this$fireAnalyticsEv9, _syncBlockInstance$da, _syncBlockInstance$da2;
264
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, fetchSuccessPayload(syncBlockInstance.resourceId, (_syncBlockInstance$da = syncBlockInstance.data) === null || _syncBlockInstance$da === void 0 ? void 0 : _syncBlockInstance$da.blockInstanceId, (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product));
425
+ var _this$fireAnalyticsEv1, _syncBlockInstance$da, _syncBlockInstance$da2;
426
+ (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 ? void 0 : _this$fireAnalyticsEv1.call(this, fetchSuccessPayload(syncBlockInstance.resourceId, (_syncBlockInstance$da = syncBlockInstance.data) === null || _syncBlockInstance$da === void 0 ? void 0 : _syncBlockInstance$da.blockInstanceId, (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product));
265
427
  }
266
428
  this.fetchSyncBlockSourceInfo(resolvedSyncBlockInstance.resourceId);
267
429
  });
@@ -290,7 +452,7 @@ export class ReferenceSyncBlockStoreManager {
290
452
  ...existingSyncBlock.data,
291
453
  sourceURL: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url,
292
454
  sourceTitle: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.title,
293
- onSamePage: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSamePage,
455
+ onSameDocument: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSameDocument,
294
456
  sourceSubType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.subType
295
457
  };
296
458
  this.updateCache(existingSyncBlock);
@@ -326,7 +488,7 @@ export class ReferenceSyncBlockStoreManager {
326
488
  this.providerFactories.delete(resourceId);
327
489
  }
328
490
  subscribeToSyncBlock(resourceId, localId, callback) {
329
- var _this$dataProvider2, _this$dataProvider2$g;
491
+ var _this$dataProvider3, _this$dataProvider3$g;
330
492
  // Cancel any pending cache deletion for this resourceId.
331
493
  // This handles the case where a block is moved - the old component unmounts
332
494
  // (scheduling deletion) but the new component mounts and subscribes before
@@ -339,6 +501,7 @@ export class ReferenceSyncBlockStoreManager {
339
501
 
340
502
  // add to subscriptions map
341
503
  const resourceSubscriptions = this.subscriptions.get(resourceId) || {};
504
+ const isNewResourceSubscription = Object.keys(resourceSubscriptions).length === 0;
342
505
  this.subscriptions.set(resourceId, {
343
506
  ...resourceSubscriptions,
344
507
  [localId]: callback
@@ -346,22 +509,32 @@ export class ReferenceSyncBlockStoreManager {
346
509
 
347
510
  // New subscription means new reference synced block is added to the document
348
511
  this.isCacheDirty = true;
512
+
513
+ // Notify listeners if this is a new resource subscription
514
+ if (isNewResourceSubscription) {
515
+ this.notifySubscriptionChangeListeners();
516
+ }
349
517
  const syncBlockNode = createSyncBlockNode(localId, resourceId);
350
518
 
351
519
  // call the callback immediately if we have cached data
352
520
  // prefer cache from store manager first, should update data provider to use the same cache
353
- const cachedData = this.getFromCache(resourceId) || ((_this$dataProvider2 = this.dataProvider) === null || _this$dataProvider2 === void 0 ? void 0 : (_this$dataProvider2$g = _this$dataProvider2.getNodeDataFromCache(syncBlockNode)) === null || _this$dataProvider2$g === void 0 ? void 0 : _this$dataProvider2$g.data);
521
+ const cachedData = this.getFromCache(resourceId) || ((_this$dataProvider3 = this.dataProvider) === null || _this$dataProvider3 === void 0 ? void 0 : (_this$dataProvider3$g = _this$dataProvider3.getNodeDataFromCache(syncBlockNode)) === null || _this$dataProvider3$g === void 0 ? void 0 : _this$dataProvider3$g.data);
354
522
  if (cachedData) {
355
523
  callback(cachedData);
356
524
  } else {
357
525
  this.fetchSyncBlocksData([syncBlockNode]).catch(error => {
358
- var _this$fireAnalyticsEv0;
526
+ var _this$fireAnalyticsEv10;
359
527
  logException(error, {
360
528
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
361
529
  });
362
- (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 ? void 0 : _this$fireAnalyticsEv0.call(this, fetchErrorPayload(error.message, resourceId));
530
+ (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 ? void 0 : _this$fireAnalyticsEv10.call(this, fetchErrorPayload(error.message, resourceId));
363
531
  });
364
532
  }
533
+
534
+ // Set up GraphQL subscription if real-time subscriptions are enabled
535
+ if (this.useRealTimeSubscriptions) {
536
+ this.setupGraphQLSubscription(resourceId);
537
+ }
365
538
  return () => {
366
539
  const resourceSubscriptions = this.subscriptions.get(resourceId);
367
540
  if (resourceSubscriptions) {
@@ -370,6 +543,13 @@ export class ReferenceSyncBlockStoreManager {
370
543
  delete resourceSubscriptions[localId];
371
544
  if (Object.keys(resourceSubscriptions).length === 0) {
372
545
  this.subscriptions.delete(resourceId);
546
+
547
+ // Clean up GraphQL subscription when no more local subscribers
548
+ this.cleanupGraphQLSubscription(resourceId);
549
+
550
+ // Notify listeners that subscription was removed
551
+ this.notifySubscriptionChangeListeners();
552
+
373
553
  // Delay cache deletion to handle block moves (unmount/remount).
374
554
  // When a block is moved, the old component unmounts before the new one mounts.
375
555
  // By delaying deletion, we give the new component time to subscribe and
@@ -440,11 +620,11 @@ export class ReferenceSyncBlockStoreManager {
440
620
  }
441
621
  return this.subscribeToSyncBlock(resourceId, localId, callback);
442
622
  } catch (error) {
443
- var _this$fireAnalyticsEv1;
623
+ var _this$fireAnalyticsEv11;
444
624
  logException(error, {
445
625
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
446
626
  });
447
- (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 ? void 0 : _this$fireAnalyticsEv1.call(this, fetchErrorPayload(error.message));
627
+ (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, fetchErrorPayload(error.message));
448
628
  return () => {};
449
629
  }
450
630
  }
@@ -464,12 +644,12 @@ export class ReferenceSyncBlockStoreManager {
464
644
  }
465
645
  getProviderFactory(resourceId) {
466
646
  if (!this.dataProvider) {
467
- var _this$fireAnalyticsEv10;
647
+ var _this$fireAnalyticsEv12;
468
648
  const error = new Error('Data provider not set');
469
649
  logException(error, {
470
650
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
471
651
  });
472
- (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 ? void 0 : _this$fireAnalyticsEv10.call(this, fetchErrorPayload(error.message));
652
+ (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, fetchErrorPayload(error.message));
473
653
  return undefined;
474
654
  }
475
655
  const {
@@ -499,11 +679,11 @@ export class ReferenceSyncBlockStoreManager {
499
679
  try {
500
680
  this.retrieveDynamicProviders(resourceId, providerFactory, providerCreator);
501
681
  } catch (error) {
502
- var _this$fireAnalyticsEv11;
682
+ var _this$fireAnalyticsEv13;
503
683
  logException(error, {
504
684
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
505
685
  });
506
- (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, fetchErrorPayload(error.message, resourceId));
686
+ (_this$fireAnalyticsEv13 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv13 === void 0 ? void 0 : _this$fireAnalyticsEv13.call(this, fetchErrorPayload(error.message, resourceId));
507
687
  }
508
688
  }
509
689
  return providerFactory;
@@ -547,7 +727,7 @@ export class ReferenceSyncBlockStoreManager {
547
727
  return null;
548
728
  }
549
729
  retrieveDynamicProviders(resourceId, providerFactory, providerCreator) {
550
- var _syncBlock$data2, _syncBlock$data3, _syncBlock$data4, _syncBlock$data5;
730
+ var _syncBlock$data2, _syncBlock$data3;
551
731
  if (!this.dataProvider) {
552
732
  throw new Error('Data provider not set');
553
733
  }
@@ -558,15 +738,15 @@ export class ReferenceSyncBlockStoreManager {
558
738
  return;
559
739
  }
560
740
  const syncBlock = this.getFromCache(resourceId);
561
- if (!syncBlock) {
741
+ if (!(syncBlock !== null && syncBlock !== void 0 && syncBlock.data)) {
562
742
  return;
563
743
  }
564
- if (!((_syncBlock$data2 = syncBlock.data) !== null && _syncBlock$data2 !== void 0 && _syncBlock$data2.sourceAri) || !((_syncBlock$data3 = syncBlock.data) !== null && _syncBlock$data3 !== void 0 && _syncBlock$data3.product)) {
565
- var _this$fireAnalyticsEv12;
566
- (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, fetchErrorPayload('Sync block source ari or product not found'));
744
+ if (!syncBlock.data.sourceAri || !syncBlock.data.product) {
745
+ var _this$fireAnalyticsEv14;
746
+ (_this$fireAnalyticsEv14 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv14 === void 0 ? void 0 : _this$fireAnalyticsEv14.call(this, fetchErrorPayload('Sync block source ari or product not found'));
567
747
  return;
568
748
  }
569
- const parentInfo = this.dataProvider.retrieveSyncBlockParentInfo((_syncBlock$data4 = syncBlock.data) === null || _syncBlock$data4 === void 0 ? void 0 : _syncBlock$data4.sourceAri, (_syncBlock$data5 = syncBlock.data) === null || _syncBlock$data5 === void 0 ? void 0 : _syncBlock$data5.product);
749
+ const parentInfo = this.dataProvider.retrieveSyncBlockParentInfo((_syncBlock$data2 = syncBlock.data) === null || _syncBlock$data2 === void 0 ? void 0 : _syncBlock$data2.sourceAri, (_syncBlock$data3 = syncBlock.data) === null || _syncBlock$data3 === void 0 ? void 0 : _syncBlock$data3.product);
570
750
  if (!parentInfo) {
571
751
  throw new Error('Unable to retrive sync block parent info');
572
752
  }
@@ -628,9 +808,13 @@ export class ReferenceSyncBlockStoreManager {
628
808
  });
629
809
  });
630
810
  });
631
- if (blocks.length === 0) {
632
- this.isCacheDirty = false;
633
- return true;
811
+ if (!fg('platform_synced_block_dogfooding')) {
812
+ // It's possible that the last reference block on the document was just deleted,
813
+ // we still want to write to BE to update reference count
814
+ if (blocks.length === 0) {
815
+ this.isCacheDirty = false;
816
+ return true;
817
+ }
634
818
  }
635
819
  if (!this.dataProvider) {
636
820
  throw new Error('Data provider not set');
@@ -648,7 +832,7 @@ export class ReferenceSyncBlockStoreManager {
648
832
  }
649
833
  const updateResult = await this.dataProvider.updateReferenceData(blocks);
650
834
  if (!updateResult.success) {
651
- var _this$fireAnalyticsEv13;
835
+ var _this$fireAnalyticsEv15;
652
836
  success = false;
653
837
  if (fg('platform_synced_block_dogfooding')) {
654
838
  var _this$saveExperience2;
@@ -656,10 +840,10 @@ export class ReferenceSyncBlockStoreManager {
656
840
  reason: updateResult.error || 'Failed to update reference synced blocks on the document'
657
841
  });
658
842
  }
659
- (_this$fireAnalyticsEv13 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv13 === void 0 ? void 0 : _this$fireAnalyticsEv13.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document'));
843
+ (_this$fireAnalyticsEv15 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv15 === void 0 ? void 0 : _this$fireAnalyticsEv15.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document'));
660
844
  }
661
845
  } catch (error) {
662
- var _this$fireAnalyticsEv14;
846
+ var _this$fireAnalyticsEv16;
663
847
  success = false;
664
848
  logException(error, {
665
849
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
@@ -670,7 +854,7 @@ export class ReferenceSyncBlockStoreManager {
670
854
  reason: error.message
671
855
  });
672
856
  }
673
- (_this$fireAnalyticsEv14 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv14 === void 0 ? void 0 : _this$fireAnalyticsEv14.call(this, updateReferenceErrorPayload(error.message));
857
+ (_this$fireAnalyticsEv16 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv16 === void 0 ? void 0 : _this$fireAnalyticsEv16.call(this, updateReferenceErrorPayload(error.message));
674
858
  } finally {
675
859
  if (!success) {
676
860
  // set isCacheDirty back to true for cases where it failed to update the reference synced blocks on the BE
@@ -684,6 +868,8 @@ export class ReferenceSyncBlockStoreManager {
684
868
  }
685
869
  destroy() {
686
870
  var _this$saveExperience5, _this$fetchExperience5, _this$fetchSourceInfo6;
871
+ // Clean up all GraphQL subscriptions first
872
+ this.cleanupAllGraphQLSubscriptions();
687
873
  this.dataProvider = undefined;
688
874
  this.syncBlockCache.clear();
689
875
  this.subscriptions.clear();
@@ -693,6 +879,8 @@ export class ReferenceSyncBlockStoreManager {
693
879
  this.syncBlockSourceInfoRequests.clear();
694
880
  this.providerFactories.clear();
695
881
  this.isRefreshingSubscriptions = false;
882
+ this.useRealTimeSubscriptions = false;
883
+ this.subscriptionChangeListeners.clear();
696
884
  this.providerFactories.forEach(providerFactory => {
697
885
  providerFactory.destroy();
698
886
  });
@@ -47,7 +47,7 @@ export class SyncBlockStoreManager {
47
47
  }
48
48
  return {
49
49
  ...sourceInfo,
50
- onSamePage: reference.onSamePage,
50
+ onSameDocument: reference.onSameDocument,
51
51
  hasAccess: reference.hasAccess,
52
52
  productType: sourceInfo.productType
53
53
  };
@@ -57,7 +57,7 @@ export class SyncBlockStoreManager {
57
57
  if (sourceSyncBlockData) {
58
58
  sourceInfos.push({
59
59
  ...sourceSyncBlockData,
60
- onSamePage: Boolean(sourceSyncBlockData === null || sourceSyncBlockData === void 0 ? void 0 : sourceSyncBlockData.onSamePage),
60
+ onSameDocument: Boolean(sourceSyncBlockData === null || sourceSyncBlockData === void 0 ? void 0 : sourceSyncBlockData.onSameDocument),
61
61
  hasAccess: true,
62
62
  isSource: true,
63
63
  productType: sourceSyncBlockData === null || sourceSyncBlockData === void 0 ? void 0 : sourceSyncBlockData.productType
@@ -33,7 +33,7 @@ export const resolveSyncBlockInstance = (oldResult, newResult) => {
33
33
  sourceTitle: ((_newResult$data2 = newResult.data) === null || _newResult$data2 === void 0 ? void 0 : _newResult$data2.sourceTitle) || ((_oldResult$data2 = oldResult.data) === null || _oldResult$data2 === void 0 ? void 0 : _oldResult$data2.sourceTitle) || undefined,
34
34
  ...(fg('platform_synced_block_dogfooding') && {
35
35
  sourceSubType: ((_newResult$data3 = newResult.data) === null || _newResult$data3 === void 0 ? void 0 : _newResult$data3.sourceSubType) || ((_oldResult$data3 = oldResult.data) === null || _oldResult$data3 === void 0 ? void 0 : _oldResult$data3.sourceSubType) || undefined,
36
- onSamePage: ((_newResult$data4 = newResult.data) === null || _newResult$data4 === void 0 ? void 0 : _newResult$data4.onSamePage) || ((_oldResult$data4 = oldResult.data) === null || _oldResult$data4 === void 0 ? void 0 : _oldResult$data4.onSamePage) || undefined
36
+ onSameDocument: ((_newResult$data4 = newResult.data) === null || _newResult$data4 === void 0 ? void 0 : _newResult$data4.onSameDocument) || ((_oldResult$data4 = oldResult.data) === null || _oldResult$data4 === void 0 ? void 0 : _oldResult$data4.onSameDocument) || undefined
37
37
  })
38
38
  }
39
39
  };