@atlaskit/editor-synced-block-provider 2.12.2 → 2.13.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/clients/block-service/ari.js +26 -9
  3. package/dist/cjs/clients/block-service/blockService.js +6 -5
  4. package/dist/cjs/clients/confluence/contentProperty.js +4 -3
  5. package/dist/cjs/clients/confluence/sourceInfo.js +2 -1
  6. package/dist/cjs/common/types.js +5 -0
  7. package/dist/cjs/index.js +12 -6
  8. package/dist/cjs/providers/block-service/blockServiceAPI.js +79 -49
  9. package/dist/cjs/providers/confluence/confluenceContentAPI.js +6 -0
  10. package/dist/cjs/providers/syncBlockProvider.js +18 -8
  11. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +8 -0
  12. package/dist/cjs/utils/resolveSyncBlockInstance.js +3 -3
  13. package/dist/cjs/utils/retry.js +66 -0
  14. package/dist/es2019/clients/block-service/ari.js +25 -8
  15. package/dist/es2019/clients/block-service/blockService.js +6 -5
  16. package/dist/es2019/clients/confluence/contentProperty.js +4 -3
  17. package/dist/es2019/clients/confluence/sourceInfo.js +2 -1
  18. package/dist/es2019/common/types.js +5 -0
  19. package/dist/es2019/index.js +1 -1
  20. package/dist/es2019/providers/block-service/blockServiceAPI.js +43 -13
  21. package/dist/es2019/providers/confluence/confluenceContentAPI.js +4 -0
  22. package/dist/es2019/providers/syncBlockProvider.js +6 -0
  23. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +6 -0
  24. package/dist/es2019/utils/resolveSyncBlockInstance.js +3 -3
  25. package/dist/es2019/utils/retry.js +26 -0
  26. package/dist/esm/clients/block-service/ari.js +25 -8
  27. package/dist/esm/clients/block-service/blockService.js +6 -5
  28. package/dist/esm/clients/confluence/contentProperty.js +4 -3
  29. package/dist/esm/clients/confluence/sourceInfo.js +2 -1
  30. package/dist/esm/common/types.js +5 -0
  31. package/dist/esm/index.js +1 -1
  32. package/dist/esm/providers/block-service/blockServiceAPI.js +79 -50
  33. package/dist/esm/providers/confluence/confluenceContentAPI.js +6 -0
  34. package/dist/esm/providers/syncBlockProvider.js +18 -8
  35. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +8 -0
  36. package/dist/esm/utils/resolveSyncBlockInstance.js +3 -3
  37. package/dist/esm/utils/retry.js +60 -0
  38. package/dist/types/clients/block-service/ari.d.ts +13 -5
  39. package/dist/types/common/types.d.ts +7 -0
  40. package/dist/types/index.d.ts +1 -1
  41. package/dist/types/providers/block-service/blockServiceAPI.d.ts +6 -3
  42. package/dist/types/providers/confluence/confluenceContentAPI.d.ts +3 -1
  43. package/dist/types/providers/syncBlockProvider.d.ts +2 -0
  44. package/dist/types/providers/types.d.ts +7 -0
  45. package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +1 -0
  46. package/dist/types/utils/retry.d.ts +1 -0
  47. package/dist/types-ts4.5/clients/block-service/ari.d.ts +13 -5
  48. package/dist/types-ts4.5/common/types.d.ts +7 -0
  49. package/dist/types-ts4.5/index.d.ts +1 -1
  50. package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +6 -3
  51. package/dist/types-ts4.5/providers/confluence/confluenceContentAPI.d.ts +3 -1
  52. package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +2 -0
  53. package/dist/types-ts4.5/providers/types.d.ts +7 -0
  54. package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +1 -0
  55. package/dist/types-ts4.5/utils/retry.d.ts +1 -0
  56. package/package.json +2 -2
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.fetchWithRetry = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
+ var parseRetryAfter = function parseRetryAfter(retryAfter) {
11
+ var newDelay;
12
+
13
+ // retryAfter can either be in ms or HTTP date
14
+ var parsedRetryAfter = parseInt(retryAfter);
15
+ if (!isNaN(parsedRetryAfter)) {
16
+ newDelay = parsedRetryAfter * 1000;
17
+ } else {
18
+ var retryDate = new Date(retryAfter);
19
+ var delayFromDate = retryDate.getTime() - Date.now();
20
+ if (delayFromDate > 0) {
21
+ newDelay = delayFromDate;
22
+ }
23
+ }
24
+ return newDelay;
25
+ };
26
+ var _fetchWithRetry = exports.fetchWithRetry = /*#__PURE__*/function () {
27
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(url, options) {
28
+ var retriesRemaining,
29
+ delay,
30
+ response,
31
+ shouldRetry,
32
+ retryAfter,
33
+ _args = arguments;
34
+ return _regenerator.default.wrap(function _callee$(_context) {
35
+ while (1) switch (_context.prev = _context.next) {
36
+ case 0:
37
+ retriesRemaining = _args.length > 2 && _args[2] !== undefined ? _args[2] : 3;
38
+ delay = _args.length > 3 && _args[3] !== undefined ? _args[3] : 1000;
39
+ _context.next = 4;
40
+ return fetch(url, options);
41
+ case 4:
42
+ response = _context.sent;
43
+ shouldRetry = !response.ok && response.status === 429 && retriesRemaining > 1;
44
+ if (shouldRetry) {
45
+ _context.next = 8;
46
+ break;
47
+ }
48
+ return _context.abrupt("return", response);
49
+ case 8:
50
+ retryAfter = response.headers.get('Retry-After');
51
+ _context.next = 11;
52
+ return new Promise(function (resolve) {
53
+ return setTimeout(resolve, retryAfter ? parseRetryAfter(retryAfter) : delay);
54
+ });
55
+ case 11:
56
+ return _context.abrupt("return", _fetchWithRetry(url, options, retriesRemaining - 1, delay * 2));
57
+ case 12:
58
+ case "end":
59
+ return _context.stop();
60
+ }
61
+ }, _callee);
62
+ }));
63
+ return function fetchWithRetry(_x, _x2) {
64
+ return _ref.apply(this, arguments);
65
+ };
66
+ }();
@@ -1,17 +1,34 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
+
3
+ /**
4
+ * Generates the block ARI from the source page ARI and the source block's resource ID.
5
+ * @param sourceAri - the ARI of the document. E.G ari:cloud:confluence:cloudId:page/pageId
6
+ * @param resourceId - the resource ID of the block node. A randomly generated UUID
7
+ * @returns the block ARI. E.G ari:cloud:blocks:<cloudId>:synced-block/<product>/<pageId>/<resourceId>
8
+ */
9
+ export const generateBlockAri = (sourceAri, resourceId, product) => {
10
+ const match = sourceAri.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/(\d+)/);
11
+ if (!(match !== null && match !== void 0 && match[1])) {
12
+ throw new Error(`Invalid source ARI: ${sourceAri}`);
13
+ }
14
+ const cloudId = match[1];
15
+ const pageId = match[3];
16
+ return `ari:cloud:blocks:${cloudId}:synced-block/${product}/${pageId}/${resourceId}`;
17
+ };
18
+
2
19
  /**
3
- * Generates a unique block ARI from a source ARI and a local ID.
4
- * @param sourceId - the ARI of the document. E.G ari:cloud:confluence:cloudId:page/pageId
5
- * @param localId - the localId of the block node. A randomly generated UUID
6
- * @returns the block ARI. E.G ari:cloud:blocks:cloudId:synced-block/localId
20
+ *
21
+ * @param sourceAri - the ARI of the document. E.G ari:cloud:confluence:cloudId:page/pageId
22
+ * @param resourceId - the resource ID of the reference synced block. E.G confluence-page/pageId/sourceResourceId
23
+ * @returns the block ARI. E.G ari:cloud:blocks:<cloudId>:synced-block/<product>/<pageId>/<resourceId>
7
24
  */
8
- export const blockResourceIdFromSourceAndLocalId = (sourceId, localId) => {
9
- const match = sourceId.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/.*/);
25
+ export const generateBlockAriFromReference = (sourceAri, resourceId) => {
26
+ const match = sourceAri.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/(\d+)/);
10
27
  if (!(match !== null && match !== void 0 && match[1])) {
11
- throw new Error(`Invalid source ARI: ${sourceId}`);
28
+ throw new Error(`Invalid source ARI: ${sourceAri}`);
12
29
  }
13
30
  const cloudId = match[1];
14
- return `ari:cloud:blocks:${cloudId}:synced-block/${localId}`;
31
+ return `ari:cloud:blocks:${cloudId}:synced-block/${resourceId}`;
15
32
  };
16
33
 
17
34
  /**
@@ -1,3 +1,4 @@
1
+ import { fetchWithRetry } from '../../utils/retry';
1
2
  export const isBlockContentResponse = response => {
2
3
  const content = response.content;
3
4
  return typeof content === 'string';
@@ -46,7 +47,7 @@ export const isBlockContentResponse = response => {
46
47
  * Check https://block-service.dev.atl-paas.net/ for latest API documentation.
47
48
  */
48
49
  export const getReferenceSyncedBlocks = async documentAri => {
49
- const response = await fetch(`${BLOCK_SERVICE_API_URL}/block/document/reference/${encodeURIComponent(documentAri)}`, {
50
+ const response = await fetchWithRetry(`${BLOCK_SERVICE_API_URL}/block/document/reference/${encodeURIComponent(documentAri)}`, {
50
51
  method: 'GET',
51
52
  headers: COMMON_HEADERS
52
53
  });
@@ -69,7 +70,7 @@ export class BlockError extends Error {
69
70
  export const getSyncedBlockContent = async ({
70
71
  blockAri
71
72
  }) => {
72
- const response = await fetch(`${BLOCK_SERVICE_API_URL}/block/${encodeURIComponent(blockAri)}`, {
73
+ const response = await fetchWithRetry(`${BLOCK_SERVICE_API_URL}/block/${encodeURIComponent(blockAri)}`, {
73
74
  method: 'GET',
74
75
  headers: COMMON_HEADERS
75
76
  });
@@ -81,7 +82,7 @@ export const getSyncedBlockContent = async ({
81
82
  export const deleteSyncedBlock = async ({
82
83
  blockAri
83
84
  }) => {
84
- const response = await fetch(`${BLOCK_SERVICE_API_URL}/block/${encodeURIComponent(blockAri)}`, {
85
+ const response = await fetchWithRetry(`${BLOCK_SERVICE_API_URL}/block/${encodeURIComponent(blockAri)}`, {
85
86
  method: 'DELETE',
86
87
  headers: COMMON_HEADERS
87
88
  });
@@ -93,7 +94,7 @@ export const updateSyncedBlock = async ({
93
94
  blockAri,
94
95
  content
95
96
  }) => {
96
- const response = await fetch(`${BLOCK_SERVICE_API_URL}/block/${encodeURIComponent(blockAri)}`, {
97
+ const response = await fetchWithRetry(`${BLOCK_SERVICE_API_URL}/block/${encodeURIComponent(blockAri)}`, {
97
98
  method: 'PUT',
98
99
  headers: COMMON_HEADERS,
99
100
  body: JSON.stringify({
@@ -111,7 +112,7 @@ export const createSyncedBlock = async ({
111
112
  product,
112
113
  content
113
114
  }) => {
114
- const response = await fetch(`${BLOCK_SERVICE_API_URL}/block`, {
115
+ const response = await fetchWithRetry(`${BLOCK_SERVICE_API_URL}/block`, {
115
116
  method: 'POST',
116
117
  headers: COMMON_HEADERS,
117
118
  body: JSON.stringify({
@@ -1,3 +1,4 @@
1
+ import { fetchWithRetry } from '../../utils/retry';
1
2
  import { getConfluencePageAri } from './ari';
2
3
  import { isBlogPageType } from './utils';
3
4
  const COMMON_HEADERS = {
@@ -151,7 +152,7 @@ export const getContentProperty = async ({
151
152
  keys: [key]
152
153
  }
153
154
  };
154
- const response = await fetch(GRAPHQL_ENDPOINT, {
155
+ const response = await fetchWithRetry(GRAPHQL_ENDPOINT, {
155
156
  method: 'POST',
156
157
  headers: {
157
158
  ...COMMON_HEADERS,
@@ -237,7 +238,7 @@ export const createContentProperty = async ({
237
238
  }
238
239
  }
239
240
  };
240
- const response = await fetch(GRAPHQL_ENDPOINT, {
241
+ const response = await fetchWithRetry(GRAPHQL_ENDPOINT, {
241
242
  method: 'POST',
242
243
  headers: {
243
244
  ...COMMON_HEADERS,
@@ -272,7 +273,7 @@ export const deleteContentProperty = async ({
272
273
  }
273
274
  }
274
275
  };
275
- const response = await fetch(GRAPHQL_ENDPOINT, {
276
+ const response = await fetchWithRetry(GRAPHQL_ENDPOINT, {
276
277
  method: 'POST',
277
278
  headers: {
278
279
  ...COMMON_HEADERS,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { getSourceInfoErrorPayload } from '../../utils/errorHandling';
5
+ import { fetchWithRetry } from '../../utils/retry';
5
6
  import { getPageIdAndTypeFromConfluencePageAri } from './ari';
6
7
  import { isBlogPageType } from './utils';
7
8
  const COMMON_HEADERS = {
@@ -41,7 +42,7 @@ const getConfluenceSourceInfo = async ari => {
41
42
  id: ari
42
43
  }
43
44
  };
44
- const response = await fetch(GRAPHQL_ENDPOINT, {
45
+ const response = await fetchWithRetry(GRAPHQL_ENDPOINT, {
45
46
  method: 'POST',
46
47
  headers: {
47
48
  ...COMMON_HEADERS,
@@ -2,6 +2,11 @@ export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
2
2
  SyncBlockError["Errored"] = "errored";
3
3
  SyncBlockError["NotFound"] = "not_found";
4
4
  SyncBlockError["Forbidden"] = "forbidden";
5
+ SyncBlockError["InvalidRequest"] = "invalid_request";
6
+ SyncBlockError["RateLimited"] = "rate_limited";
7
+ SyncBlockError["Conflict"] = "conflict";
8
+ // attempt to create block that already exists
9
+ SyncBlockError["ServerError"] = "server_error";
5
10
  SyncBlockError["InvalidContent"] = "invalid_content"; // content is not a valid JSON
6
11
  return SyncBlockError;
7
12
  }({});
@@ -9,7 +9,7 @@ export { useFetchSyncBlockTitle } from './hooks/useFetchSyncBlockTitle';
9
9
  export { useHandleContentChanges } from './hooks/useHandleContentChanges';
10
10
 
11
11
  // clients
12
- export { blockResourceIdFromSourceAndLocalId, getLocalIdFromBlockResourceId } from './clients/block-service/ari';
12
+ export { generateBlockAri, generateBlockAriFromReference, getLocalIdFromBlockResourceId } from './clients/block-service/ari';
13
13
  export { getConfluencePageAri, getLocalIdFromConfluencePageAri, getPageARIFromContentPropertyResourceId, getPageIdAndTypeFromConfluencePageAri, resourceIdFromConfluencePageSourceIdAndLocalId } from './clients/confluence/ari';
14
14
 
15
15
  // providers
@@ -1,14 +1,26 @@
1
+ /* eslint-disable require-unicode-regexp */
1
2
  import { useMemo } from 'react';
2
- import { blockResourceIdFromSourceAndLocalId, getLocalIdFromBlockResourceId } from '../../clients/block-service/ari';
3
+ import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
3
4
  import { BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getSyncedBlockContent, updateSyncedBlock } from '../../clients/block-service/blockService';
4
5
  import { SyncBlockError } from '../../common/types';
5
6
  import { stringifyError } from '../../utils/errorHandling';
6
7
  const mapBlockError = error => {
7
8
  switch (error.status) {
9
+ case 400:
10
+ case 401:
11
+ return SyncBlockError.InvalidRequest;
8
12
  case 403:
9
13
  return SyncBlockError.Forbidden;
10
14
  case 404:
11
15
  return SyncBlockError.NotFound;
16
+ case 409:
17
+ return SyncBlockError.Conflict;
18
+ case 429:
19
+ return SyncBlockError.RateLimited;
20
+ case 500:
21
+ case 503:
22
+ case 504:
23
+ return SyncBlockError.ServerError;
12
24
  }
13
25
  return SyncBlockError.Errored;
14
26
  };
@@ -70,13 +82,17 @@ export const fetchReferences = async documentAri => {
70
82
  * ADFFetchProvider implementation that fetches synced block data from Block Service API
71
83
  */
72
84
  class BlockServiceADFFetchProvider {
73
- // resourceId is the ARI of the block. E.G ari:cloud:blocks:site-123:synced-block/uuid-456
74
- // in the content API provider, this was the concatenation of the source document's ARI and the local ID. E.G ari:cloud:confluence:site-123:page/pageId/uuid-456
85
+ constructor(sourceAri) {
86
+ this.sourceAri = sourceAri;
87
+ }
88
+
89
+ // resourceId of the reference synced block.
90
+ // the ARI must be constructed to call the block service API
75
91
  async fetchData(resourceId) {
76
- const localId = getLocalIdFromBlockResourceId(resourceId);
92
+ const blockAri = generateBlockAriFromReference(this.sourceAri, resourceId);
77
93
  try {
78
94
  const blockContentResponse = await getSyncedBlockContent({
79
- blockAri: resourceId
95
+ blockAri
80
96
  });
81
97
  const value = blockContentResponse.content;
82
98
  if (!value) {
@@ -91,8 +107,9 @@ class BlockServiceADFFetchProvider {
91
107
  return {
92
108
  data: {
93
109
  content: syncedBlockData,
94
- resourceId,
95
- blockInstanceId: localId,
110
+ resourceId: blockAri,
111
+ blockInstanceId: blockContentResponse.blockInstanceId,
112
+ // this was the node's localId, but has become the resourceId.
96
113
  sourceAri: blockContentResponse.sourceAri,
97
114
  product: blockContentResponse.product
98
115
  },
@@ -127,10 +144,11 @@ class BlockServiceADFWriteProvider {
127
144
  const {
128
145
  resourceId
129
146
  } = data;
147
+ const blockAri = generateBlockAri(this.sourceAri, resourceId, this.product);
130
148
  try {
131
149
  // Try update existing block's content
132
150
  await updateSyncedBlock({
133
- blockAri: resourceId,
151
+ blockAri,
134
152
  content: JSON.stringify(data.content)
135
153
  });
136
154
  return {
@@ -153,9 +171,10 @@ class BlockServiceADFWriteProvider {
153
171
  const {
154
172
  resourceId
155
173
  } = data;
174
+ const blockAri = generateBlockAri(this.sourceAri, resourceId, this.product);
156
175
  try {
157
176
  await createSyncedBlock({
158
- blockAri: resourceId,
177
+ blockAri,
159
178
  blockInstanceId: data.blockInstanceId,
160
179
  sourceAri: this.sourceAri,
161
180
  product: this.product,
@@ -180,9 +199,10 @@ class BlockServiceADFWriteProvider {
180
199
 
181
200
  // soft deletes the source synced block
182
201
  async deleteData(resourceId) {
202
+ const blockAri = generateBlockAri(this.sourceAri, resourceId, this.product);
183
203
  try {
184
204
  await deleteSyncedBlock({
185
- blockAri: resourceId
205
+ blockAri
186
206
  });
187
207
  return {
188
208
  resourceId,
@@ -204,8 +224,18 @@ class BlockServiceADFWriteProvider {
204
224
  };
205
225
  }
206
226
  }
207
- generateResourceId(sourceAri, localId) {
208
- return blockResourceIdFromSourceAndLocalId(sourceAri, localId);
227
+
228
+ // the sourceId is the resourceId of the source synced block.
229
+ generateResourceIdForReference(sourceId) {
230
+ const match = this.sourceAri.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/(\d+)/);
231
+ if (!(match !== null && match !== void 0 && match[1])) {
232
+ throw new Error(`Invalid source ARI: ${this.sourceAri}`);
233
+ }
234
+ const pageId = match[3];
235
+ return `${this.product}/${pageId}/${sourceId}`;
236
+ }
237
+ generateResourceId() {
238
+ return crypto.randomUUID();
209
239
  }
210
240
  }
211
241
 
@@ -213,7 +243,7 @@ class BlockServiceADFWriteProvider {
213
243
  * Factory function to create both providers with shared configuration
214
244
  */
215
245
  const createBlockServiceAPIProviders = (sourceAri, product) => {
216
- const fetchProvider = new BlockServiceADFFetchProvider();
246
+ const fetchProvider = new BlockServiceADFFetchProvider(sourceAri);
217
247
  const writeProvider = new BlockServiceADFWriteProvider(sourceAri, product);
218
248
  return {
219
249
  fetchProvider,
@@ -115,6 +115,7 @@ class ConfluenceADFFetchProvider {
115
115
  */
116
116
  class ConfluenceADFWriteProvider {
117
117
  constructor(config) {
118
+ _defineProperty(this, "product", 'confluence-page');
118
119
  _defineProperty(this, "createNewContentProperty", async (pageId, key, value, pageType) => {
119
120
  const options = {
120
121
  pageId,
@@ -271,6 +272,9 @@ class ConfluenceADFWriteProvider {
271
272
  generateResourceId(sourceId, localId) {
272
273
  return resourceIdFromConfluencePageSourceIdAndLocalId(sourceId, localId);
273
274
  }
275
+ generateResourceIdForReference(sourceId) {
276
+ return sourceId;
277
+ }
274
278
  }
275
279
 
276
280
  /**
@@ -23,6 +23,9 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
23
23
  this.sourceId = sourceId;
24
24
  this.providerOptions = providerOptions;
25
25
  }
26
+ getProduct() {
27
+ return this.writeProvider.product;
28
+ }
26
29
 
27
30
  /**
28
31
  * Check if the node is supported by the provider
@@ -160,6 +163,9 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
160
163
  generateResourceId(sourceId, localId) {
161
164
  return this.writeProvider.generateResourceId(sourceId, localId);
162
165
  }
166
+ generateResourceIdForReference(sourceId) {
167
+ return this.writeProvider.generateResourceIdForReference(sourceId);
168
+ }
163
169
 
164
170
  /**
165
171
  * Get the synced block renderer provider options
@@ -22,6 +22,12 @@ export class ReferenceSyncBlockStoreManager {
22
22
  this.providerFactories = new Map();
23
23
  this.fireAnalyticsEvent = fireAnalyticsEvent;
24
24
  }
25
+ generateResourceIdForReference(sourceId) {
26
+ if (!this.dataProvider) {
27
+ throw new Error('Data provider not set');
28
+ }
29
+ return this.dataProvider.generateResourceIdForReference(sourceId);
30
+ }
25
31
  updateFireAnalyticsEvent(fireAnalyticsEvent) {
26
32
  this.fireAnalyticsEvent = fireAnalyticsEvent;
27
33
  }
@@ -16,10 +16,10 @@ export const resolveSyncBlockInstance = (oldResult, newResult) => {
16
16
  return newResult;
17
17
  } else if (!newResult.data) {
18
18
  // return the old result if there was an error, e.g. network error, but not if not found or forbidden
19
- if (newResult.error === SyncBlockError.Errored) {
20
- return oldResult;
21
- } else {
19
+ if (newResult.error === SyncBlockError.NotFound || newResult.error === SyncBlockError.Forbidden) {
22
20
  return newResult;
21
+ } else {
22
+ return oldResult;
23
23
  }
24
24
  }
25
25
 
@@ -0,0 +1,26 @@
1
+ const parseRetryAfter = retryAfter => {
2
+ let newDelay;
3
+
4
+ // retryAfter can either be in ms or HTTP date
5
+ const parsedRetryAfter = parseInt(retryAfter);
6
+ if (!isNaN(parsedRetryAfter)) {
7
+ newDelay = parsedRetryAfter * 1000;
8
+ } else {
9
+ const retryDate = new Date(retryAfter);
10
+ const delayFromDate = retryDate.getTime() - Date.now();
11
+ if (delayFromDate > 0) {
12
+ newDelay = delayFromDate;
13
+ }
14
+ }
15
+ return newDelay;
16
+ };
17
+ export const fetchWithRetry = async (url, options, retriesRemaining = 3, delay = 1000) => {
18
+ const response = await fetch(url, options);
19
+ const shouldRetry = !response.ok && response.status === 429 && retriesRemaining > 1;
20
+ if (!shouldRetry) {
21
+ return response;
22
+ }
23
+ const retryAfter = response.headers.get('Retry-After');
24
+ await new Promise(resolve => setTimeout(resolve, retryAfter ? parseRetryAfter(retryAfter) : delay));
25
+ return fetchWithRetry(url, options, retriesRemaining - 1, delay * 2);
26
+ };
@@ -1,17 +1,34 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
+
3
+ /**
4
+ * Generates the block ARI from the source page ARI and the source block's resource ID.
5
+ * @param sourceAri - the ARI of the document. E.G ari:cloud:confluence:cloudId:page/pageId
6
+ * @param resourceId - the resource ID of the block node. A randomly generated UUID
7
+ * @returns the block ARI. E.G ari:cloud:blocks:<cloudId>:synced-block/<product>/<pageId>/<resourceId>
8
+ */
9
+ export var generateBlockAri = function generateBlockAri(sourceAri, resourceId, product) {
10
+ var match = sourceAri.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/(\d+)/);
11
+ if (!(match !== null && match !== void 0 && match[1])) {
12
+ throw new Error("Invalid source ARI: ".concat(sourceAri));
13
+ }
14
+ var cloudId = match[1];
15
+ var pageId = match[3];
16
+ return "ari:cloud:blocks:".concat(cloudId, ":synced-block/").concat(product, "/").concat(pageId, "/").concat(resourceId);
17
+ };
18
+
2
19
  /**
3
- * Generates a unique block ARI from a source ARI and a local ID.
4
- * @param sourceId - the ARI of the document. E.G ari:cloud:confluence:cloudId:page/pageId
5
- * @param localId - the localId of the block node. A randomly generated UUID
6
- * @returns the block ARI. E.G ari:cloud:blocks:cloudId:synced-block/localId
20
+ *
21
+ * @param sourceAri - the ARI of the document. E.G ari:cloud:confluence:cloudId:page/pageId
22
+ * @param resourceId - the resource ID of the reference synced block. E.G confluence-page/pageId/sourceResourceId
23
+ * @returns the block ARI. E.G ari:cloud:blocks:<cloudId>:synced-block/<product>/<pageId>/<resourceId>
7
24
  */
8
- export var blockResourceIdFromSourceAndLocalId = function blockResourceIdFromSourceAndLocalId(sourceId, localId) {
9
- var match = sourceId.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/.*/);
25
+ export var generateBlockAriFromReference = function generateBlockAriFromReference(sourceAri, resourceId) {
26
+ var match = sourceAri.match(/ari:cloud:confluence:([^:]+):(page|blogpost)\/(\d+)/);
10
27
  if (!(match !== null && match !== void 0 && match[1])) {
11
- throw new Error("Invalid source ARI: ".concat(sourceId));
28
+ throw new Error("Invalid source ARI: ".concat(sourceAri));
12
29
  }
13
30
  var cloudId = match[1];
14
- return "ari:cloud:blocks:".concat(cloudId, ":synced-block/").concat(localId);
31
+ return "ari:cloud:blocks:".concat(cloudId, ":synced-block/").concat(resourceId);
15
32
  };
16
33
 
17
34
  /**
@@ -8,6 +8,7 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
8
8
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
9
9
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
10
10
  import _regeneratorRuntime from "@babel/runtime/regenerator";
11
+ import { fetchWithRetry } from '../../utils/retry';
11
12
  export var isBlockContentResponse = function isBlockContentResponse(response) {
12
13
  var content = response.content;
13
14
  return typeof content === 'string';
@@ -62,7 +63,7 @@ export var getReferenceSyncedBlocks = /*#__PURE__*/function () {
62
63
  while (1) switch (_context.prev = _context.next) {
63
64
  case 0:
64
65
  _context.next = 2;
65
- return fetch("".concat(BLOCK_SERVICE_API_URL, "/block/document/reference/").concat(encodeURIComponent(documentAri)), {
66
+ return fetchWithRetry("".concat(BLOCK_SERVICE_API_URL, "/block/document/reference/").concat(encodeURIComponent(documentAri)), {
66
67
  method: 'GET',
67
68
  headers: COMMON_HEADERS
68
69
  });
@@ -112,7 +113,7 @@ export var getSyncedBlockContent = /*#__PURE__*/function () {
112
113
  case 0:
113
114
  blockAri = _ref2.blockAri;
114
115
  _context2.next = 3;
115
- return fetch("".concat(BLOCK_SERVICE_API_URL, "/block/").concat(encodeURIComponent(blockAri)), {
116
+ return fetchWithRetry("".concat(BLOCK_SERVICE_API_URL, "/block/").concat(encodeURIComponent(blockAri)), {
116
117
  method: 'GET',
117
118
  headers: COMMON_HEADERS
118
119
  });
@@ -146,7 +147,7 @@ export var deleteSyncedBlock = /*#__PURE__*/function () {
146
147
  case 0:
147
148
  blockAri = _ref4.blockAri;
148
149
  _context3.next = 3;
149
- return fetch("".concat(BLOCK_SERVICE_API_URL, "/block/").concat(encodeURIComponent(blockAri)), {
150
+ return fetchWithRetry("".concat(BLOCK_SERVICE_API_URL, "/block/").concat(encodeURIComponent(blockAri)), {
150
151
  method: 'DELETE',
151
152
  headers: COMMON_HEADERS
152
153
  });
@@ -175,7 +176,7 @@ export var updateSyncedBlock = /*#__PURE__*/function () {
175
176
  case 0:
176
177
  blockAri = _ref6.blockAri, content = _ref6.content;
177
178
  _context4.next = 3;
178
- return fetch("".concat(BLOCK_SERVICE_API_URL, "/block/").concat(encodeURIComponent(blockAri)), {
179
+ return fetchWithRetry("".concat(BLOCK_SERVICE_API_URL, "/block/").concat(encodeURIComponent(blockAri)), {
179
180
  method: 'PUT',
180
181
  headers: COMMON_HEADERS,
181
182
  body: JSON.stringify({
@@ -207,7 +208,7 @@ export var createSyncedBlock = /*#__PURE__*/function () {
207
208
  case 0:
208
209
  blockAri = _ref8.blockAri, blockInstanceId = _ref8.blockInstanceId, sourceAri = _ref8.sourceAri, product = _ref8.product, content = _ref8.content;
209
210
  _context5.next = 3;
210
- return fetch("".concat(BLOCK_SERVICE_API_URL, "/block"), {
211
+ return fetchWithRetry("".concat(BLOCK_SERVICE_API_URL, "/block"), {
211
212
  method: 'POST',
212
213
  headers: COMMON_HEADERS,
213
214
  body: JSON.stringify({
@@ -3,6 +3,7 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
3
3
  import _regeneratorRuntime from "@babel/runtime/regenerator";
4
4
  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; }
5
5
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
+ import { fetchWithRetry } from '../../utils/retry';
6
7
  import { getConfluencePageAri } from './ari';
7
8
  import { isBlogPageType } from './utils';
8
9
  var COMMON_HEADERS = {
@@ -88,7 +89,7 @@ export var getContentProperty = /*#__PURE__*/function () {
88
89
  }
89
90
  };
90
91
  _context.next = 6;
91
- return fetch(GRAPHQL_ENDPOINT, {
92
+ return fetchWithRetry(GRAPHQL_ENDPOINT, {
92
93
  method: 'POST',
93
94
  headers: _objectSpread(_objectSpread({}, COMMON_HEADERS), AGG_HEADERS),
94
95
  body: JSON.stringify(bodyData)
@@ -197,7 +198,7 @@ export var createContentProperty = /*#__PURE__*/function () {
197
198
  }
198
199
  };
199
200
  _context3.next = 6;
200
- return fetch(GRAPHQL_ENDPOINT, {
201
+ return fetchWithRetry(GRAPHQL_ENDPOINT, {
201
202
  method: 'POST',
202
203
  headers: _objectSpread(_objectSpread({}, COMMON_HEADERS), AGG_HEADERS),
203
204
  body: JSON.stringify(bodyData)
@@ -247,7 +248,7 @@ export var deleteContentProperty = /*#__PURE__*/function () {
247
248
  }
248
249
  };
249
250
  _context4.next = 6;
250
- return fetch(GRAPHQL_ENDPOINT, {
251
+ return fetchWithRetry(GRAPHQL_ENDPOINT, {
251
252
  method: 'POST',
252
253
  headers: _objectSpread(_objectSpread({}, COMMON_HEADERS), AGG_HEADERS),
253
254
  body: JSON.stringify(bodyData)
@@ -7,6 +7,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
7
7
 
8
8
  import { logException } from '@atlaskit/editor-common/monitoring';
9
9
  import { getSourceInfoErrorPayload } from '../../utils/errorHandling';
10
+ import { fetchWithRetry } from '../../utils/retry';
10
11
  import { getPageIdAndTypeFromConfluencePageAri } from './ari';
11
12
  import { isBlogPageType } from './utils';
12
13
  var COMMON_HEADERS = {
@@ -38,7 +39,7 @@ var getConfluenceSourceInfo = /*#__PURE__*/function () {
38
39
  }
39
40
  };
40
41
  _context.next = 3;
41
- return fetch(GRAPHQL_ENDPOINT, {
42
+ return fetchWithRetry(GRAPHQL_ENDPOINT, {
42
43
  method: 'POST',
43
44
  headers: _objectSpread(_objectSpread({}, COMMON_HEADERS), AGG_HEADERS),
44
45
  body: JSON.stringify(bodyData)
@@ -2,6 +2,11 @@ export var SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
2
2
  SyncBlockError["Errored"] = "errored";
3
3
  SyncBlockError["NotFound"] = "not_found";
4
4
  SyncBlockError["Forbidden"] = "forbidden";
5
+ SyncBlockError["InvalidRequest"] = "invalid_request";
6
+ SyncBlockError["RateLimited"] = "rate_limited";
7
+ SyncBlockError["Conflict"] = "conflict";
8
+ // attempt to create block that already exists
9
+ SyncBlockError["ServerError"] = "server_error";
5
10
  SyncBlockError["InvalidContent"] = "invalid_content"; // content is not a valid JSON
6
11
  return SyncBlockError;
7
12
  }({});
package/dist/esm/index.js CHANGED
@@ -9,7 +9,7 @@ export { useFetchSyncBlockTitle } from './hooks/useFetchSyncBlockTitle';
9
9
  export { useHandleContentChanges } from './hooks/useHandleContentChanges';
10
10
 
11
11
  // clients
12
- export { blockResourceIdFromSourceAndLocalId, getLocalIdFromBlockResourceId } from './clients/block-service/ari';
12
+ export { generateBlockAri, generateBlockAriFromReference, getLocalIdFromBlockResourceId } from './clients/block-service/ari';
13
13
  export { getConfluencePageAri, getLocalIdFromConfluencePageAri, getPageARIFromContentPropertyResourceId, getPageIdAndTypeFromConfluencePageAri, resourceIdFromConfluencePageSourceIdAndLocalId } from './clients/confluence/ari';
14
14
 
15
15
  // providers