@atlaskit/editor-synced-block-provider 8.5.7 → 8.6.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.
@@ -3,6 +3,7 @@ import type { ViewMode } from '@atlaskit/editor-plugin-editor-viewmode';
3
3
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
4
  import type { ResourceId, SyncBlockAttrs, BlockInstanceId, DeletionReason, DeletionMechanism, ReferenceSyncBlockData } from '../common/types';
5
5
  import type { SyncBlockDataProviderInterface, SyncBlockSourceInfo } from '../providers/types';
6
+ import type { CreateSuccessEnrichment } from '../utils/errorHandling';
6
7
  export type ConfirmationCallback = (syncBlockIds: SyncBlockAttrs[], deleteReason: DeletionReason | undefined) => Promise<boolean>;
7
8
  type OnDelete = () => void;
8
9
  type OnCompletion = (success: boolean) => void;
@@ -35,6 +36,20 @@ export declare class SourceSyncBlockStoreManager {
35
36
  * multiple blocks are created concurrently. See EDITOR-7112.
36
37
  */
37
38
  private creationsTimedOutDuringFlush;
39
+ /**
40
+ * Creation-type signals captured at `createBodiedSyncBlockNode` time and read
41
+ * back in `commitPendingCreation` to enrich the `syncedBlockCreate` event.
42
+ * Kept out of the flushed cache record so it never leaks into Block Service /
43
+ * ADF; removed once consumed.
44
+ */
45
+ private creationEnrichment;
46
+ /**
47
+ * Resource IDs of blocks created empty this session that have not yet fired
48
+ * the first-content-added event. Added on empty creation, removed when the
49
+ * block first gains content (the event fires once). Blocks created with
50
+ * content or not created this session are never tracked.
51
+ */
52
+ private awaitingFirstContent;
38
53
  private createExperience;
39
54
  private saveExperience;
40
55
  private deleteExperience;
@@ -86,6 +101,13 @@ export declare class SourceSyncBlockStoreManager {
86
101
  * @param success
87
102
  */
88
103
  commitPendingCreation(success: boolean, resourceId: ResourceId): void;
104
+ /**
105
+ * Fire the first-content-added event once when a block created empty this
106
+ * session first gains content. The caller detects the in-block edit; this owns
107
+ * dedupe + emission (fires at most once per block, only for empty→content).
108
+ * Gated behind `platform_editor_blocks_patch_4`.
109
+ */
110
+ maybeEmitFirstContentAdded(resourceId: ResourceId, blockInstanceId?: BlockInstanceId): void;
89
111
  registerConfirmationCallback(callback: ConfirmationCallback): () => void;
90
112
  requireConfirmationBeforeDelete(): boolean;
91
113
  /**
@@ -97,8 +119,12 @@ export declare class SourceSyncBlockStoreManager {
97
119
  * @param attrs attributes Ids of the node
98
120
  * @param node the ProseMirror node to cache
99
121
  * @param onCompletion callback invoked when creation completes
122
+ * @param enrichment optional creation-type signals stashed and attached to the
123
+ * `syncedBlockCreate` event when the async create resolves. When
124
+ * `createdEmpty` is true the block is also registered for the
125
+ * first-content-added event.
100
126
  */
101
- createBodiedSyncBlockNode(attrs: SyncBlockAttrs, node: PMNode, onCompletion: OnCompletion): void;
127
+ createBodiedSyncBlockNode(attrs: SyncBlockAttrs, node: PMNode, onCompletion: OnCompletion, enrichment?: CreateSuccessEnrichment): void;
102
128
  private setPendingDeletion;
103
129
  /**
104
130
  * Drop dedupe entries older than {@link DELETE_DEDUPE_WINDOW_MS} so
@@ -37,6 +37,12 @@ export declare class SyncBlockSubscriptionManager {
37
37
  private static readonly MAX_RETRY_DELAY_MS;
38
38
  private retryAttempts;
39
39
  private pendingRetries;
40
+ private deferredExhausted;
41
+ private static readonly WAKE_DEBOUNCE_MS;
42
+ private wakeDebounceTimer;
43
+ private readonly onOnline;
44
+ private readonly onVisibilityChange;
45
+ private wakeListenerCleanups;
40
46
  private getMaxRetryAttempts;
41
47
  private getReconnectionDelay;
42
48
  constructor(deps: SyncBlockSubscriptionManagerDeps);
@@ -72,6 +78,28 @@ export declare class SyncBlockSubscriptionManager {
72
78
  * update is received, indicating the subscription is healthy.
73
79
  */
74
80
  private resetRetryCount;
81
+ /**
82
+ * Whether the tab is currently hidden. SSR/non-browser is treated as "not hidden" so
83
+ * we never defer where wake events can't fire (realtime never subscribes there anyway).
84
+ */
85
+ private isDocumentHidden;
86
+ /**
87
+ * Lazily register shared `online` / `visibilitychange` listeners on first deferral.
88
+ * Torn down by `unregisterWakeListeners` (via `handleWake`). No-op in SSR / non-browser.
89
+ */
90
+ private registerWakeListeners;
91
+ private unregisterWakeListeners;
92
+ /**
93
+ * Coalesce a burst of wake events into one debounced re-arm sweep. Callers decide
94
+ * eligibility: `onOnline` always sweeps; `onVisibilityChange` only sweeps when visible.
95
+ */
96
+ private scheduleWakeSweep;
97
+ /**
98
+ * Re-arm every deferred resource: full reset (attempts→0) + immediate reconnect now
99
+ * the tab is visible / online. Healthy subscriptions are untouched. A re-armed cycle
100
+ * that later exhausts while visible fires the terminal error normally.
101
+ */
102
+ private handleWake;
75
103
  private cancelPendingRetry;
76
104
  private cancelAllPendingRetries;
77
105
  cleanupSubscription(resourceId: ResourceId): void;
@@ -1,5 +1,5 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
- import type { RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPayload } from '@atlaskit/editor-common/analytics';
2
+ import type { INPUT_METHOD, RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPayload } from '@atlaskit/editor-common/analytics';
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;
@@ -111,6 +111,13 @@ export type FetchErrorAttributionAttributes = {
111
111
  * always sets it, so requiring it costs nothing.
112
112
  */
113
113
  benign: boolean;
114
+ /**
115
+ * Set when a `websocket_exhausted` outcome was deferred (tab hidden at exhaustion, so
116
+ * the subscription re-arms on online/visibilitychange) rather than surfaced as a
117
+ * terminal failure. Auditable on the dashboard but counted benign — the user isn't
118
+ * experiencing a failure while hidden. Spread only when true, never `undefined`.
119
+ */
120
+ deferred?: boolean;
114
121
  };
115
122
  /**
116
123
  * Builds the {@link FetchErrorAttributionAttributes} for a failed fetch/subscribe
@@ -122,7 +129,7 @@ export type FetchErrorAttributionAttributes = {
122
129
  * `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
123
130
  * helper stays pure and trivially unit-testable for both gate states.
124
131
  */
125
- export declare const buildFetchErrorAttribution: (gateEnabled: boolean, error?: string, statusCode?: number) => FetchErrorAttributionAttributes | undefined;
132
+ export declare const buildFetchErrorAttribution: (gateEnabled: boolean, error?: string, statusCode?: number, deferred?: boolean) => FetchErrorAttributionAttributes | undefined;
126
133
  /**
127
134
  * Shared operational ERROR payload builder for synced-block events.
128
135
  *
@@ -141,6 +148,7 @@ export declare function getErrorPayload<T extends ACTION_SUBJECT_ID>(actionSubje
141
148
  reason?: SyncBlockFetchErrorReason;
142
149
  statusCode?: number;
143
150
  benign?: boolean;
151
+ deferred?: boolean;
144
152
  }>;
145
153
  export declare function getErrorPayload<T extends ACTION_SUBJECT_ID>(actionSubjectId: T, error: string, resourceId?: string, sourceProduct?: string, attribution?: ErrorAttributionAttributes): OperationalAEP<ACTION.ERROR, ACTION_SUBJECT.SYNCED_BLOCK, T, {
146
154
  error: string;
@@ -175,11 +183,28 @@ export declare const fetchReferencesErrorPayload: (error: string, resourceId?: s
175
183
  export declare const fetchSuccessPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => RendererSyncBlockEventPayload;
176
184
  export declare const createSuccessPayload: (resourceId: string, sourceProduct?: string) => SyncBlockEventPayload;
177
185
  /**
178
- * Operational `syncedBlockCreate` success event (previously only error rows
179
- * existed, so dashboards had no create denominator). Fired behind
180
- * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
186
+ * Optional enrichment for the `syncedBlockCreate` success event. All fields
187
+ * optional so gate-off/legacy payloads are unchanged. `inputMethod`: creating
188
+ * surface (enum, PII-safe). `createdEmpty`: true from an empty selection, false
189
+ * when content was converted.
190
+ */
191
+ export type CreateSuccessEnrichment = {
192
+ createdEmpty?: boolean;
193
+ inputMethod?: INPUT_METHOD;
194
+ };
195
+ /**
196
+ * Operational `syncedBlockCreate` success event, behind
197
+ * `platform_editor_blocks_patch_4`, with the `blockInstanceId` join key and,
198
+ * when available, the `inputMethod` + `createdEmpty` creation-type signals.
199
+ */
200
+ export declare const createSuccessOperationalPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string, enrichment?: CreateSuccessEnrichment) => SyncBlockEventPayload;
201
+ /**
202
+ * Operational first-content-added event, behind
203
+ * `platform_editor_blocks_patch_4`. Fired once when a block created empty first
204
+ * gains user content. Join keys only (`resourceId` + `blockInstanceId`), no user
205
+ * content (PII-safe).
181
206
  */
182
- export declare const createSuccessOperationalPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
207
+ export declare const addContentSuccessPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
183
208
  export declare const updateSuccessPayload: (resourceId: string, hasReference?: boolean, sourceProduct?: string) => SyncBlockEventPayload;
184
209
  /**
185
210
  * Optional enrichment for the `syncedBlockDelete` success event behind
package/package.json CHANGED
@@ -17,20 +17,22 @@
17
17
  "atlaskit:src": "src/index.ts",
18
18
  "dependencies": {
19
19
  "@atlaskit/adf-utils": "^20.3.0",
20
+ "@atlaskit/browser-apis": "^1.1.0",
20
21
  "@atlaskit/editor-json-transformer": "^9.0.0",
21
22
  "@atlaskit/editor-prosemirror": "^8.0.0",
22
23
  "@atlaskit/node-data-provider": "^13.0.0",
23
24
  "@atlaskit/platform-feature-flags": "^2.0.0",
24
- "@atlaskit/tmp-editor-statsig": "^124.0.0",
25
+ "@atlaskit/tmp-editor-statsig": "^124.2.0",
25
26
  "@babel/runtime": "^7.0.0",
26
27
  "@compiled/react": "^0.20.0",
28
+ "bind-event-listener": "^3.0.0",
27
29
  "graphql-ws": "^5.14.2",
28
30
  "lodash": "^4.17.21",
29
31
  "raf-schd": "^4.0.3",
30
32
  "uuid": "^3.1.0"
31
33
  },
32
34
  "peerDependencies": {
33
- "@atlaskit/editor-common": "^116.24.0",
35
+ "@atlaskit/editor-common": "^116.27.0",
34
36
  "react": "^18.2.0"
35
37
  },
36
38
  "devDependencies": {
@@ -74,7 +76,7 @@
74
76
  }
75
77
  },
76
78
  "name": "@atlaskit/editor-synced-block-provider",
77
- "version": "8.5.7",
79
+ "version": "8.6.1",
78
80
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
81
  "author": "Atlassian Pty Ltd",
80
82
  "license": "Apache-2.0",
@@ -82,9 +84,6 @@
82
84
  "registry": "https://registry.npmjs.org/"
83
85
  },
84
86
  "platform-feature-flags": {
85
- "platform_editor_blocks_patch_2": {
86
- "type": "boolean"
87
- },
88
87
  "platform_editor_blocks_patch_3": {
89
88
  "type": "boolean"
90
89
  },