@atlaskit/editor-synced-block-provider 8.5.4 → 8.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 8.5.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 8.5.5
10
+
11
+ ### Patch Changes
12
+
13
+ - [`b1ff8c881a4aa`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b1ff8c881a4aa) -
14
+ [ux] Synced Blocks: de-opaque the renderer error-boundary fetch analytics event. The event now
15
+ routes through the shared fetch-error attribution builder (emitting structured
16
+ `reason`/`benign`/`sourceProduct`/`statusCode`, gated on `platform_editor_blocks_patch_4`) and a
17
+ PII-safe original error message is threaded from the upstream fetch catch so previously-opaque
18
+ `errored` failures can be classified and inspected.
19
+ - Updated dependencies
20
+
3
21
  ## 8.5.4
4
22
 
5
23
  ### Patch Changes
@@ -3,10 +3,22 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "buildFetchErrorAttribution", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _errorHandling.buildFetchErrorAttribution;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "fetchErrorPayload", {
7
13
  enumerable: true,
8
14
  get: function get() {
9
15
  return _errorHandling.fetchErrorPayload;
10
16
  }
11
17
  });
18
+ Object.defineProperty(exports, "getPiiSafeOriginalError", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _errorHandling.getPiiSafeOriginalError;
22
+ }
23
+ });
12
24
  var _errorHandling = require("../utils/errorHandling");
@@ -107,13 +107,16 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
107
107
  });
108
108
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent((0, _errorHandling.fetchErrorPayload)(_t.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), _t.message)));
109
109
 
110
- // Set error state if fetching fails
110
+ // Thread the PII-safe original message/name so the renderer can de-opaque
111
+ // this otherwise-bare `errored` failure. `originalMessage` carries the
112
+ // Error.message and is preferred over `reason` by the renderer, so no
113
+ // separate `reason` is set here.
111
114
  setFetchState({
112
115
  syncBlockInstance: {
113
116
  resourceId: resourceId || '',
114
- error: {
117
+ error: _objectSpread({
115
118
  type: _types.SyncBlockError.Errored
116
- }
119
+ }, (0, _errorHandling.getPiiSafeOriginalError)(_t))
117
120
  },
118
121
  isLoading: false
119
122
  });
@@ -415,12 +415,12 @@ var _batchFetchData = exports.batchFetchData = /*#__PURE__*/function () {
415
415
  case 21:
416
416
  return _context2.abrupt("return", blockNodeIdentifiers.map(function (blockNodeIdentifier) {
417
417
  return {
418
- error: _objectSpread({
418
+ error: _objectSpread(_objectSpread({
419
419
  type: _t4 instanceof _blockService.BlockError ? mapBlockError(_t4) : _types.SyncBlockError.Errored,
420
420
  reason: _t4.message
421
421
  }, _t4 instanceof _blockService.BlockError && {
422
422
  statusCode: _t4.status
423
- }),
423
+ }), (0, _errorHandling.getPiiSafeOriginalError)(_t4)),
424
424
  resourceId: blockNodeIdentifier.resourceId
425
425
  };
426
426
  }));
@@ -670,19 +670,19 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
670
670
  break;
671
671
  }
672
672
  return _context5.abrupt("return", {
673
- error: {
673
+ error: _objectSpread({
674
674
  type: mapBlockError(_t7),
675
675
  reason: _t7.message,
676
676
  statusCode: _t7.status
677
- },
677
+ }, (0, _errorHandling.getPiiSafeOriginalError)(_t7)),
678
678
  resourceId: resourceId
679
679
  });
680
680
  case 5:
681
681
  return _context5.abrupt("return", {
682
- error: {
682
+ error: _objectSpread({
683
683
  type: _types.SyncBlockError.Errored,
684
684
  reason: _t7.message
685
- },
685
+ }, (0, _errorHandling.getPiiSafeOriginalError)(_t7)),
686
686
  resourceId: resourceId
687
687
  });
688
688
  case 6:
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.fetchSuccessPayload = exports.fetchReferencesErrorPayload = exports.fetchErrorPayload = exports.deleteSuccessPayload = exports.deleteErrorPayload = exports.createSuccessPayload = exports.createSuccessOperationalPayload = exports.createErrorPayload = exports.classifyFetchErrorReason = exports.classifyErrorReason = exports.cacheDeletionForcedPayload = exports.buildFetchErrorAttribution = exports.buildErrorAttribution = exports.FETCH_BENIGN_REASONS = void 0;
8
8
  exports.getErrorPayload = getErrorPayload;
9
- exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = void 0;
9
+ exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = exports.getPiiSafeOriginalError = void 0;
10
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
11
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
12
  var _analytics = require("@atlaskit/editor-common/analytics");
@@ -21,6 +21,24 @@ var stringifyError = exports.stringifyError = function stringifyError(error) {
21
21
  }
22
22
  };
23
23
 
24
+ /**
25
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
26
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
27
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
28
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
29
+ * into analytics.
30
+ */
31
+ var getPiiSafeOriginalError = exports.getPiiSafeOriginalError = function getPiiSafeOriginalError(error) {
32
+ if (error instanceof Error) {
33
+ return _objectSpread(_objectSpread({}, error.message && {
34
+ originalMessage: error.message
35
+ }), error.name && {
36
+ originalName: error.name
37
+ });
38
+ }
39
+ return {};
40
+ };
41
+
24
42
  /**
25
43
  * The set of categorical failure reasons emitted on synced-block operational error
26
44
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -92,13 +110,16 @@ var buildErrorAttribution = exports.buildErrorAttribution = function buildErrorA
92
110
  var FETCH_BENIGN_REASONS = exports.FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
93
111
  // Mirror the benign write-path enum equivalents so already-classified errors are
94
112
  // treated consistently.
95
- 'not_found', 'forbidden', 'unpublished'
96
- // NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
97
- // - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
98
- // suppressed during retries); by the time the error event fires, retries are
99
- // exhausted and it is a genuine failure.
100
- // - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
101
- // Both still emit `reason` (so they can be inspected) but are counted as true errors.
113
+ 'not_found', 'forbidden', 'unpublished',
114
+ // `offline` is an expected client connectivity state, not a read-path system failure:
115
+ // the error component is rendered for the offline experience, so it should not inflate
116
+ // the true error rate. It still emits `reason` (so its volume can be inspected) but is
117
+ // excluded from true-error counts.
118
+ 'offline'
119
+ // NOTE: `entity_not_found` is intentionally NOT benign it is retried up to
120
+ // ENTITY_NOT_FOUND_MAX_RETRIES (analytics are suppressed during retries); by the time
121
+ // the error event fires, retries are exhausted and it is a genuine failure. It still
122
+ // emits `reason` (so it can be inspected) but is counted as a true error.
102
123
  ]);
103
124
 
104
125
  /**
@@ -1,2 +1,2 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
- export { fetchErrorPayload } from '../utils/errorHandling';
2
+ export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
@@ -3,7 +3,7 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  import { isProviderNotReadyError, SyncBlockError } from '../common/types';
6
- import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
6
+ import { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
7
7
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
8
8
  export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalyticsEvent) => {
9
9
  var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
@@ -74,12 +74,16 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
74
74
  });
75
75
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 ? void 0 : fireAnalyticsEvent(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
76
76
 
77
- // Set error state if fetching fails
77
+ // Thread the PII-safe original message/name so the renderer can de-opaque
78
+ // this otherwise-bare `errored` failure. `originalMessage` carries the
79
+ // Error.message and is preferred over `reason` by the renderer, so no
80
+ // separate `reason` is set here.
78
81
  setFetchState({
79
82
  syncBlockInstance: {
80
83
  resourceId: resourceId || '',
81
84
  error: {
82
- type: SyncBlockError.Errored
85
+ type: SyncBlockError.Errored,
86
+ ...getPiiSafeOriginalError(error)
83
87
  }
84
88
  },
85
89
  isLoading: false
@@ -4,7 +4,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
4
  import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
5
5
  import { batchRetrieveSyncedBlocks, BlockError, BlockNotFoundError, BlockTimeoutError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
6
6
  import { SyncBlockError } from '../../common/types';
7
- import { stringifyError } from '../../utils/errorHandling';
7
+ import { getPiiSafeOriginalError, stringifyError } from '../../utils/errorHandling';
8
8
  import { createResourceIdForReference } from '../../utils/resourceId';
9
9
  import { convertContentUpdatedAt } from '../../utils/utils';
10
10
  const BLOCK_ARI_TO_RESOURCE_ID_REGEX = /^ari:cloud:blocks:.*:synced-block\/(.+)$/;
@@ -304,14 +304,16 @@ export const batchFetchData = async (cloudId, parentAri, blockNodeIdentifiers, c
304
304
 
305
305
  // If batch request fails, return error for all resourceIds. Capture the HTTP
306
306
  // status from `BlockError` so fetch analytics can break failures down by
307
- // statusCode (EDITOR-7862); undefined for non-HTTP failures.
307
+ // statusCode (EDITOR-7862); undefined for non-HTTP failures. Thread the PII-safe
308
+ // original message/name so the renderer can de-opaque the `errored` bucket.
308
309
  return blockNodeIdentifiers.map(blockNodeIdentifier => ({
309
310
  error: {
310
311
  type: error instanceof BlockError ? mapBlockError(error) : SyncBlockError.Errored,
311
312
  reason: error.message,
312
313
  ...(error instanceof BlockError && {
313
314
  statusCode: error.status
314
- })
315
+ }),
316
+ ...getPiiSafeOriginalError(error)
315
317
  },
316
318
  resourceId: blockNodeIdentifier.resourceId
317
319
  }));
@@ -466,12 +468,14 @@ class BlockServiceADFFetchProvider {
466
468
  } catch (error) {
467
469
  if (error instanceof BlockError) {
468
470
  // Capture the HTTP status so fetch analytics can break failures down by
469
- // statusCode (EDITOR-7862).
471
+ // statusCode (EDITOR-7862). Thread the PII-safe original message/name so
472
+ // the renderer can de-opaque failures.
470
473
  return {
471
474
  error: {
472
475
  type: mapBlockError(error),
473
476
  reason: error.message,
474
- statusCode: error.status
477
+ statusCode: error.status,
478
+ ...getPiiSafeOriginalError(error)
475
479
  },
476
480
  resourceId
477
481
  };
@@ -479,7 +483,8 @@ class BlockServiceADFFetchProvider {
479
483
  return {
480
484
  error: {
481
485
  type: SyncBlockError.Errored,
482
- reason: error.message
486
+ reason: error.message,
487
+ ...getPiiSafeOriginalError(error)
483
488
  },
484
489
  resourceId
485
490
  };
@@ -8,6 +8,27 @@ export const stringifyError = error => {
8
8
  }
9
9
  };
10
10
 
11
+ /**
12
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
13
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
14
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
15
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
16
+ * into analytics.
17
+ */
18
+ export const getPiiSafeOriginalError = error => {
19
+ if (error instanceof Error) {
20
+ return {
21
+ ...(error.message && {
22
+ originalMessage: error.message
23
+ }),
24
+ ...(error.name && {
25
+ originalName: error.name
26
+ })
27
+ };
28
+ }
29
+ return {};
30
+ };
31
+
11
32
  /**
12
33
  * The set of categorical failure reasons emitted on synced-block operational error
13
34
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -80,13 +101,16 @@ export const buildErrorAttribution = (gateEnabled, error, statusCode) => {
80
101
  export const FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
81
102
  // Mirror the benign write-path enum equivalents so already-classified errors are
82
103
  // treated consistently.
83
- 'not_found', 'forbidden', 'unpublished'
84
- // NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
85
- // - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
86
- // suppressed during retries); by the time the error event fires, retries are
87
- // exhausted and it is a genuine failure.
88
- // - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
89
- // Both still emit `reason` (so they can be inspected) but are counted as true errors.
104
+ 'not_found', 'forbidden', 'unpublished',
105
+ // `offline` is an expected client connectivity state, not a read-path system failure:
106
+ // the error component is rendered for the offline experience, so it should not inflate
107
+ // the true error rate. It still emits `reason` (so its volume can be inspected) but is
108
+ // excluded from true-error counts.
109
+ 'offline'
110
+ // NOTE: `entity_not_found` is intentionally NOT benign it is retried up to
111
+ // ENTITY_NOT_FOUND_MAX_RETRIES (analytics are suppressed during retries); by the time
112
+ // the error event fires, retries are exhausted and it is a genuine failure. It still
113
+ // emits `reason` (so it can be inspected) but is counted as a true error.
90
114
  ]);
91
115
 
92
116
  /**
@@ -1,2 +1,2 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
- export { fetchErrorPayload } from '../utils/errorHandling';
2
+ export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
@@ -9,7 +9,7 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
9
9
  import { logException } from '@atlaskit/editor-common/monitoring';
10
10
  import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { isProviderNotReadyError, SyncBlockError } from '../common/types';
12
- import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
12
+ import { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
13
13
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
14
14
  export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resourceId, localId, fireAnalyticsEvent) {
15
15
  var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
@@ -100,13 +100,16 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
100
100
  });
101
101
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent(fetchErrorPayload(_t.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), _t.message)));
102
102
 
103
- // Set error state if fetching fails
103
+ // Thread the PII-safe original message/name so the renderer can de-opaque
104
+ // this otherwise-bare `errored` failure. `originalMessage` carries the
105
+ // Error.message and is preferred over `reason` by the renderer, so no
106
+ // separate `reason` is set here.
104
107
  setFetchState({
105
108
  syncBlockInstance: {
106
109
  resourceId: resourceId || '',
107
- error: {
110
+ error: _objectSpread({
108
111
  type: SyncBlockError.Errored
109
- }
112
+ }, getPiiSafeOriginalError(_t))
110
113
  },
111
114
  isLoading: false
112
115
  });
@@ -15,7 +15,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
15
15
  import { generateBlockAri as _generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
16
16
  import { batchRetrieveSyncedBlocks, BlockError, BlockNotFoundError, BlockTimeoutError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
17
17
  import { SyncBlockError } from '../../common/types';
18
- import { stringifyError } from '../../utils/errorHandling';
18
+ import { getPiiSafeOriginalError, stringifyError } from '../../utils/errorHandling';
19
19
  import { createResourceIdForReference } from '../../utils/resourceId';
20
20
  import { convertContentUpdatedAt } from '../../utils/utils';
21
21
  var BLOCK_ARI_TO_RESOURCE_ID_REGEX = /^ari:cloud:blocks:.*:synced-block\/(.+)$/;
@@ -407,12 +407,12 @@ var _batchFetchData = /*#__PURE__*/function () {
407
407
  case 21:
408
408
  return _context2.abrupt("return", blockNodeIdentifiers.map(function (blockNodeIdentifier) {
409
409
  return {
410
- error: _objectSpread({
410
+ error: _objectSpread(_objectSpread({
411
411
  type: _t4 instanceof BlockError ? mapBlockError(_t4) : SyncBlockError.Errored,
412
412
  reason: _t4.message
413
413
  }, _t4 instanceof BlockError && {
414
414
  statusCode: _t4.status
415
- }),
415
+ }), getPiiSafeOriginalError(_t4)),
416
416
  resourceId: blockNodeIdentifier.resourceId
417
417
  };
418
418
  }));
@@ -663,19 +663,19 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
663
663
  break;
664
664
  }
665
665
  return _context5.abrupt("return", {
666
- error: {
666
+ error: _objectSpread({
667
667
  type: mapBlockError(_t7),
668
668
  reason: _t7.message,
669
669
  statusCode: _t7.status
670
- },
670
+ }, getPiiSafeOriginalError(_t7)),
671
671
  resourceId: resourceId
672
672
  });
673
673
  case 5:
674
674
  return _context5.abrupt("return", {
675
- error: {
675
+ error: _objectSpread({
676
676
  type: SyncBlockError.Errored,
677
677
  reason: _t7.message
678
- },
678
+ }, getPiiSafeOriginalError(_t7)),
679
679
  resourceId: resourceId
680
680
  });
681
681
  case 6:
@@ -12,6 +12,24 @@ export var stringifyError = function stringifyError(error) {
12
12
  }
13
13
  };
14
14
 
15
+ /**
16
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
17
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
18
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
19
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
20
+ * into analytics.
21
+ */
22
+ export var getPiiSafeOriginalError = function getPiiSafeOriginalError(error) {
23
+ if (error instanceof Error) {
24
+ return _objectSpread(_objectSpread({}, error.message && {
25
+ originalMessage: error.message
26
+ }), error.name && {
27
+ originalName: error.name
28
+ });
29
+ }
30
+ return {};
31
+ };
32
+
15
33
  /**
16
34
  * The set of categorical failure reasons emitted on synced-block operational error
17
35
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -83,13 +101,16 @@ export var buildErrorAttribution = function buildErrorAttribution(gateEnabled, e
83
101
  export var FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
84
102
  // Mirror the benign write-path enum equivalents so already-classified errors are
85
103
  // treated consistently.
86
- 'not_found', 'forbidden', 'unpublished'
87
- // NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
88
- // - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
89
- // suppressed during retries); by the time the error event fires, retries are
90
- // exhausted and it is a genuine failure.
91
- // - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
92
- // Both still emit `reason` (so they can be inspected) but are counted as true errors.
104
+ 'not_found', 'forbidden', 'unpublished',
105
+ // `offline` is an expected client connectivity state, not a read-path system failure:
106
+ // the error component is rendered for the offline experience, so it should not inflate
107
+ // the true error rate. It still emits `reason` (so its volume can be inspected) but is
108
+ // excluded from true-error counts.
109
+ 'offline'
110
+ // NOTE: `entity_not_found` is intentionally NOT benign it is retried up to
111
+ // ENTITY_NOT_FOUND_MAX_RETRIES (analytics are suppressed during retries); by the time
112
+ // the error event fires, retries are exhausted and it is a genuine failure. It still
113
+ // emits `reason` (so it can be inspected) but is counted as a true error.
93
114
  ]);
94
115
 
95
116
  /**
@@ -1 +1 @@
1
- export { fetchErrorPayload } from '../utils/errorHandling';
1
+ export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError, } from '../utils/errorHandling';
@@ -5,6 +5,17 @@ import { NodeDataProvider } from '@atlaskit/node-data-provider';
5
5
  import type { TaskDecisionProvider } from '@atlaskit/task-decision/types';
6
6
  import type { SyncBlockData, SyncBlockStatus, ResourceId, SyncBlockError, SyncBlockNode, SyncBlockProduct, BlockInstanceId, SyncBlockAttrs, ReferenceSyncBlockData, DeletionReason } from '../common/types';
7
7
  type SyncBlockErrorInfo = {
8
+ /**
9
+ * PII-safe `Error.name` from the upstream catch, used to de-opaque `errored`
10
+ * failures. Only ever `Error.name` — never node content, titles, or any UGC.
11
+ */
12
+ originalName?: string;
13
+ /**
14
+ * PII-safe `Error.message` from the upstream catch, so the classifier can bucket
15
+ * the real cause. Distinct from `reason` (may be synthetic/enum): carries the raw
16
+ * framework/HTTP text. Only ever `Error.message` — never node content or UGC.
17
+ */
18
+ originalMessage?: string;
8
19
  reason?: string;
9
20
  sourceAri?: string;
10
21
  /**
@@ -3,6 +3,17 @@ import type { RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPaylo
3
3
  import { SyncBlockError } from '../common/types';
4
4
  import type { DeletionMechanism, DeletionReason } from '../common/types';
5
5
  export declare const stringifyError: (error: unknown) => string | undefined;
6
+ /**
7
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
8
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
9
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
10
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
11
+ * into analytics.
12
+ */
13
+ export declare const getPiiSafeOriginalError: (error: unknown) => {
14
+ originalMessage?: string;
15
+ originalName?: string;
16
+ };
6
17
  /**
7
18
  * The set of categorical failure reasons emitted on synced-block operational error
8
19
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "@atlaskit/editor-prosemirror": "^8.0.0",
22
22
  "@atlaskit/node-data-provider": "^13.0.0",
23
23
  "@atlaskit/platform-feature-flags": "^2.0.0",
24
- "@atlaskit/tmp-editor-statsig": "^121.1.0",
24
+ "@atlaskit/tmp-editor-statsig": "^123.0.0",
25
25
  "@babel/runtime": "^7.0.0",
26
26
  "@compiled/react": "^0.20.0",
27
27
  "graphql-ws": "^5.14.2",
@@ -30,7 +30,7 @@
30
30
  "uuid": "^3.1.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "@atlaskit/editor-common": "^116.22.0",
33
+ "@atlaskit/editor-common": "^116.24.0",
34
34
  "react": "^18.2.0"
35
35
  },
36
36
  "devDependencies": {
@@ -74,7 +74,7 @@
74
74
  }
75
75
  },
76
76
  "name": "@atlaskit/editor-synced-block-provider",
77
- "version": "8.5.4",
77
+ "version": "8.5.6",
78
78
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
79
  "author": "Atlassian Pty Ltd",
80
80
  "license": "Apache-2.0",