@univerjs-pro/collaboration-client 1.1.0 → 1.2.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 (100) hide show
  1. package/README.md +2 -0
  2. package/lib/cjs/facade.js +1 -1
  3. package/lib/cjs/index.js +1 -1
  4. package/lib/cjs/locale/ar-SA.js +1 -1
  5. package/lib/cjs/locale/ca-ES.js +1 -1
  6. package/lib/cjs/locale/de-DE.js +1 -1
  7. package/lib/cjs/locale/en-US.js +1 -1
  8. package/lib/cjs/locale/es-ES.js +1 -1
  9. package/lib/cjs/locale/fa-IR.js +1 -1
  10. package/lib/cjs/locale/fr-FR.js +1 -1
  11. package/lib/cjs/locale/id-ID.js +1 -1
  12. package/lib/cjs/locale/it-IT.js +1 -1
  13. package/lib/cjs/locale/ja-JP.js +1 -1
  14. package/lib/cjs/locale/ko-KR.js +1 -1
  15. package/lib/cjs/locale/pl-PL.js +1 -1
  16. package/lib/cjs/locale/pt-BR.js +1 -1
  17. package/lib/cjs/locale/ru-RU.js +1 -1
  18. package/lib/cjs/locale/sk-SK.js +1 -1
  19. package/lib/cjs/locale/vi-VN.js +1 -1
  20. package/lib/cjs/locale/zh-CN.js +1 -1
  21. package/lib/cjs/locale/zh-HK.js +1 -1
  22. package/lib/cjs/locale/zh-TW.js +1 -1
  23. package/lib/es/facade.js +1 -1
  24. package/lib/es/index.js +1 -1
  25. package/lib/es/locale/ar-SA.js +1 -1
  26. package/lib/es/locale/ca-ES.js +1 -1
  27. package/lib/es/locale/de-DE.js +1 -1
  28. package/lib/es/locale/en-US.js +1 -1
  29. package/lib/es/locale/es-ES.js +1 -1
  30. package/lib/es/locale/fa-IR.js +1 -1
  31. package/lib/es/locale/fr-FR.js +1 -1
  32. package/lib/es/locale/id-ID.js +1 -1
  33. package/lib/es/locale/it-IT.js +1 -1
  34. package/lib/es/locale/ja-JP.js +1 -1
  35. package/lib/es/locale/ko-KR.js +1 -1
  36. package/lib/es/locale/pl-PL.js +1 -1
  37. package/lib/es/locale/pt-BR.js +1 -1
  38. package/lib/es/locale/ru-RU.js +1 -1
  39. package/lib/es/locale/sk-SK.js +1 -1
  40. package/lib/es/locale/vi-VN.js +1 -1
  41. package/lib/es/locale/zh-CN.js +1 -1
  42. package/lib/es/locale/zh-HK.js +1 -1
  43. package/lib/es/locale/zh-TW.js +1 -1
  44. package/lib/facade.js +1 -1
  45. package/lib/index.js +1 -1
  46. package/lib/locale/ar-SA.js +1 -1
  47. package/lib/locale/ca-ES.js +1 -1
  48. package/lib/locale/de-DE.js +1 -1
  49. package/lib/locale/en-US.js +1 -1
  50. package/lib/locale/es-ES.js +1 -1
  51. package/lib/locale/fa-IR.js +1 -1
  52. package/lib/locale/fr-FR.js +1 -1
  53. package/lib/locale/id-ID.js +1 -1
  54. package/lib/locale/it-IT.js +1 -1
  55. package/lib/locale/ja-JP.js +1 -1
  56. package/lib/locale/ko-KR.js +1 -1
  57. package/lib/locale/pl-PL.js +1 -1
  58. package/lib/locale/pt-BR.js +1 -1
  59. package/lib/locale/ru-RU.js +1 -1
  60. package/lib/locale/sk-SK.js +1 -1
  61. package/lib/locale/vi-VN.js +1 -1
  62. package/lib/locale/zh-CN.js +1 -1
  63. package/lib/locale/zh-HK.js +1 -1
  64. package/lib/locale/zh-TW.js +1 -1
  65. package/lib/types/config/config.d.ts +19 -0
  66. package/lib/types/controllers/collaboration/changeset-submitter.d.ts +132 -0
  67. package/lib/types/controllers/collaboration/collaboration-entity.d.ts +28 -1
  68. package/lib/types/controllers/collaboration/collaboration-state.d.ts +162 -70
  69. package/lib/types/controllers/collaboration/collaboration.controller.d.ts +7 -0
  70. package/lib/types/controllers/collaboration/utils/changeset-utils.d.ts +7 -3
  71. package/lib/types/facade/f-collaboration.d.ts +17 -0
  72. package/lib/types/facade/f-event.d.ts +87 -2
  73. package/lib/types/index.d.ts +5 -3
  74. package/lib/types/services/collaboration-session/collaboration-session.d.ts +4 -8
  75. package/lib/types/services/collaboration-session/collaboration-session.service.d.ts +1 -1
  76. package/lib/types/services/collaboration-ui-event/collaboration-ui-event.d.ts +8 -0
  77. package/lib/types/services/socket/collaboration-socket.service.d.ts +10 -2
  78. package/lib/types/services/undoredo/collaborative-undoredo.service.d.ts +5 -0
  79. package/lib/umd/facade.js +1 -1
  80. package/lib/umd/index.js +1 -1
  81. package/lib/umd/locale/ar-SA.js +1 -1
  82. package/lib/umd/locale/ca-ES.js +1 -1
  83. package/lib/umd/locale/de-DE.js +1 -1
  84. package/lib/umd/locale/en-US.js +1 -1
  85. package/lib/umd/locale/es-ES.js +1 -1
  86. package/lib/umd/locale/fa-IR.js +1 -1
  87. package/lib/umd/locale/fr-FR.js +1 -1
  88. package/lib/umd/locale/id-ID.js +1 -1
  89. package/lib/umd/locale/it-IT.js +1 -1
  90. package/lib/umd/locale/ja-JP.js +1 -1
  91. package/lib/umd/locale/ko-KR.js +1 -1
  92. package/lib/umd/locale/pl-PL.js +1 -1
  93. package/lib/umd/locale/pt-BR.js +1 -1
  94. package/lib/umd/locale/ru-RU.js +1 -1
  95. package/lib/umd/locale/sk-SK.js +1 -1
  96. package/lib/umd/locale/vi-VN.js +1 -1
  97. package/lib/umd/locale/zh-CN.js +1 -1
  98. package/lib/umd/locale/zh-HK.js +1 -1
  99. package/lib/umd/locale/zh-TW.js +1 -1
  100. package/package.json +15 -15
@@ -1 +1 @@
1
- const _0x3b8851=_0x5e7a;(function(_0x458da0,_0x2d6656){const _0x4880f6=_0x5e7a,_0x226fc1=_0x458da0();while(!![]){try{const _0x19d86d=parseInt(_0x4880f6(0x1f3))/0x1*(parseInt(_0x4880f6(0x1db))/0x2)+parseInt(_0x4880f6(0x1de))/0x3+-parseInt(_0x4880f6(0x1e0))/0x4*(-parseInt(_0x4880f6(0x1f1))/0x5)+parseInt(_0x4880f6(0x1ec))/0x6*(-parseInt(_0x4880f6(0x1ea))/0x7)+-parseInt(_0x4880f6(0x1f4))/0x8*(parseInt(_0x4880f6(0x1eb))/0x9)+parseInt(_0x4880f6(0x1ed))/0xa*(parseInt(_0x4880f6(0x1dd))/0xb)+-parseInt(_0x4880f6(0x1ee))/0xc;if(_0x19d86d===_0x2d6656)break;else _0x226fc1['push'](_0x226fc1['shift']());}catch(_0x37d32c){_0x226fc1['push'](_0x226fc1['shift']());}}}(_0x47fc,0x6e244));const e={'collaboration-client':{'collabClient':{'tooltip':{'reconnect':'點擊重新連線'}},'collabStatus':{'fetchMiss':_0x3b8851(0x1e2),'conflict':_0x3b8851(0x1e5),'notCollab':_0x3b8851(0x1e1),'synced':_0x3b8851(0x1e3),'syncing':_0x3b8851(0x1e9),'offline':'已離線,編輯將儲存在本機'},'session':{'connection-failed':_0x3b8851(0x1f2),'will-retry':_0x3b8851(0x1ef),'room-full':_0x3b8851(0x1d9),'join-failed':_0x3b8851(0x1e6),'room-not-exists':'加入協作失敗,您的編輯將暫存於本機。','room-permission-denied':_0x3b8851(0x1e7),'room-cnt-exceeds':'協作文件數量超過限制,您的編輯將暫存於本機。','collaboration-timeout':'伺服器未回應協作請求,您的編輯將儲存在本機。'},'conflict':{'title':_0x3b8851(0x1dc),'content':_0x3b8851(0x1e4)},'permission':{'title':_0x3b8851(0x1df),'content':_0x3b8851(0x1e8)},'collaboration':{'single-unit':{'warning':_0x3b8851(0x1da)},'closeRoom':'由於協作房間已關閉,編輯權限已被收回。'},'auth':{'needGotoLoginAlert':_0x3b8851(0x1f0)}}};function _0x5e7a(_0x3f4940,_0x1cfd05){_0x3f4940=_0x3f4940-0x1d9;const _0x47fc5e=_0x47fc();let _0x5e7a99=_0x47fc5e[_0x3f4940];return _0x5e7a99;}function _0x47fc(){const _0x3bae62=['沒有協作權限,您的編輯將暫存於本機。','您的操作與伺服器權限存在衝突。請將您的本機編輯儲存至其他地方,重新整理頁面後將會被捨棄。','同步中...','259nbSoVZ','99vKJwxg','47418WhBGHN','70eVkUSc','7097544RBEGxw','連線失敗,將在稍後重試連線。','您的登入已過期,點擊「確定」重新登入,點擊「取消」儲存您的本機編輯。','5tMjDit','連線失敗,請檢查您的網路。','503545sraJxd','68984GdEPIv','此文件協作人數已達上限,您的編輯將暫存於本機。','您在另一個分頁開啟了同一個檔案。為避免資料遺失,此分頁將無法進行編輯。','2VVXenT','協作衝突','1355739bhtQrz','105480mSqOzv','認證錯誤','113672DbtIwV','本機檔案','正在擷取伺服器資料...','已同步','您的本機文件與伺服器文件存在衝突。請將您的本機編輯儲存至其他地方,因為重新整理頁面後將會遺失。','編輯衝突','加入協作失敗,請稍後重試,您的編輯將暫存於本機。'];_0x47fc=function(){return _0x3bae62;};return _0x47fc();}export{e as default};
1
+ function _0x4f67(_0x37b400,_0xa878c9){_0x37b400=_0x37b400-0x113;const _0x368765=_0x3687();let _0x4f67b5=_0x368765[_0x37b400];return _0x4f67b5;}const _0x2914f5=_0x4f67;(function(_0x11c453,_0x3134de){const _0xa3b454=_0x4f67,_0x34354d=_0x11c453();while(!![]){try{const _0x18c234=-parseInt(_0xa3b454(0x116))/0x1+parseInt(_0xa3b454(0x113))/0x2*(-parseInt(_0xa3b454(0x11e))/0x3)+-parseInt(_0xa3b454(0x128))/0x4*(parseInt(_0xa3b454(0x12e))/0x5)+-parseInt(_0xa3b454(0x118))/0x6*(-parseInt(_0xa3b454(0x127))/0x7)+-parseInt(_0xa3b454(0x12d))/0x8*(parseInt(_0xa3b454(0x129))/0x9)+-parseInt(_0xa3b454(0x122))/0xa+parseInt(_0xa3b454(0x114))/0xb*(parseInt(_0xa3b454(0x126))/0xc);if(_0x18c234===_0x3134de)break;else _0x34354d['push'](_0x34354d['shift']());}catch(_0x28af09){_0x34354d['push'](_0x34354d['shift']());}}}(_0x3687,0x839c9));function _0x3687(){const _0x26e3b6=['您的操作與伺服器權限存在衝突。請將您的本機編輯儲存至其他地方,重新整理頁面後將會被捨棄。','已同步','6348VlHQSA','736127VGSwPM','4iZRvyT','1368243XWoAUP','您在另一個分頁開啟了同一個檔案。為避免資料遺失,此分頁將無法進行編輯。','認證錯誤','連線失敗,請檢查您的網路。','40rvCMSL','5317320XKYKLl','11854MJBSmg','64823YnscKq','伺服器未回應協作請求,您的編輯將儲存在本機。','501223miiTlD','加入協作失敗,請稍後重試,您的編輯將暫存於本機。','30MFGwsb','您的本機文件與伺服器文件存在衝突。請將您的本機編輯儲存至其他地方,因為重新整理頁面後將會遺失。','此文件協作人數已達上限,您的編輯將暫存於本機。','您的登入已過期,點擊「確定」重新登入,點擊「取消」儲存您的本機編輯。','本機檔案','協作文件數量超過限制,您的編輯將暫存於本機。','393PuwtWd','編輯衝突','協作衝突','加入協作失敗,您的編輯將暫存於本機。','28620IDcTPh','同步中...'];_0x3687=function(){return _0x26e3b6;};return _0x3687();}const e={'collaboration-client':{'collabClient':{'tooltip':{'reconnect':'點擊重新連線'}},'collabStatus':{'fetchMiss':'正在擷取伺服器資料...','conflict':_0x2914f5(0x11f),'notCollab':_0x2914f5(0x11c),'synced':_0x2914f5(0x125),'syncing':_0x2914f5(0x123),'offline':'已離線,編輯將儲存在本機'},'session':{'connection-failed':_0x2914f5(0x12c),'will-retry':'連線失敗,將在稍後重試連線。','room-full':_0x2914f5(0x11a),'join-failed':_0x2914f5(0x117),'room-not-exists':_0x2914f5(0x121),'room-permission-denied':'沒有協作權限,您的編輯將暫存於本機。','room-cnt-exceeds':_0x2914f5(0x11d),'collaboration-timeout':_0x2914f5(0x115)},'conflict':{'title':_0x2914f5(0x120),'content':_0x2914f5(0x119)},'permission':{'title':_0x2914f5(0x12b),'content':_0x2914f5(0x124)},'collaboration':{'single-unit':{'warning':_0x2914f5(0x12a)},'closeRoom':'由於協作房間已關閉,編輯權限已被收回。'},'auth':{'needGotoLoginAlert':_0x2914f5(0x11b)}}};export{e as default};
@@ -28,7 +28,26 @@ export interface IUniverCollaborationClientConfig {
28
28
  * Override internal modules with custom implementations.
29
29
  */
30
30
  override?: DependencyOverride;
31
+ /**
32
+ * How long local edits are batched before they are sent as one changeset, in milliseconds. Edits made within
33
+ * this window after the first one go into the same changeset. This is a batching delay, not a network timeout;
34
+ * for the acknowledgement timeout see `changesetAckTimeout`.
35
+ * @default 2000
36
+ */
31
37
  sendChangesetTimeout?: number;
38
+ /**
39
+ * How long to wait for the server's acknowledgement of a changeset before sending it again, in milliseconds.
40
+ * Later resends wait twice as long each time, up to `maxChangesetAckTimeout`. The request that submits the
41
+ * changeset is given the same timeout.
42
+ * @default 20000
43
+ */
44
+ changesetAckTimeout?: number;
45
+ /**
46
+ * The longest wait between two resends of an unacknowledged changeset, in milliseconds. Set it equal to
47
+ * `changesetAckTimeout` for a constant interval.
48
+ * @default 120000
49
+ */
50
+ maxChangesetAckTimeout?: number;
32
51
  snapshotServerUrl?: string;
33
52
  collabSubmitChangesetUrl?: string;
34
53
  collabWebSocketUrl?: string;
@@ -0,0 +1,132 @@
1
+ import type { IChangeset } from '@univerjs-pro/collaboration';
2
+ import type { ICollaborationSendOptions } from '../../services/socket/collaboration-socket.service';
3
+ /** Why the awaiting changeset was sent again. */
4
+ export type ChangesetResendReason = 'ack_timeout' | 'should_retry' | 'reconnect' | 'request_failed';
5
+ /** Emitted every time an awaiting changeset is sent again. The first send is not an event. */
6
+ export interface IChangesetResendEvent {
7
+ unitID: string;
8
+ reqId: number;
9
+ reason: ChangesetResendReason;
10
+ /** How many times the changeset has been resent, counting this one. */
11
+ attempt: number;
12
+ /** Milliseconds since the changeset was first sent. */
13
+ elapsedMs: number;
14
+ /** Mutations in the awaiting changeset plus pending mutations not yet sent. */
15
+ unsyncedMutationCount: number;
16
+ /** Milliseconds until the next resend if nothing is heard from the server. */
17
+ nextDelayMs: number;
18
+ }
19
+ export type ChangesetResendInfo = Omit<IChangesetResendEvent, 'unitID'>;
20
+ /** The first wait for an acknowledgement before the awaiting changeset is resent. */
21
+ export declare const DEFAULT_ACK_TIMEOUT = 20000;
22
+ /** The longest wait for an acknowledgement between two resends. */
23
+ export declare const MAX_ACK_TIMEOUT = 120000;
24
+ /** The longest backoff between two resends requested by the server or caused by a failed request. */
25
+ export declare const MAX_RETRY_TIMEOUT = 20000;
26
+ /**
27
+ * Hands a changeset to the transport. `options.timeout` is how long the transport may wait for the server to
28
+ * answer: it equals the wait for the acknowledgement, so a request that is still unanswered when the changeset is
29
+ * sent again is given up at the same moment. The returned promise, when there is one, rejects when the request
30
+ * failed; its resolution is not used.
31
+ */
32
+ export type SendChangeset = (changeset: IChangeset, options: ICollaborationSendOptions) => void | Promise<void>;
33
+ /** What the bound state provides for a resend: the awaiting changeset as it currently is, and the unsynced count. */
34
+ export interface IChangesetSubmitterSource {
35
+ changeset: IChangeset;
36
+ /** Mutations in the awaiting changeset plus pending mutations not yet sent. */
37
+ unsyncedMutationCount: number;
38
+ }
39
+ export interface IChangesetSubmitterOptions {
40
+ /** The first wait for an acknowledgement. Defaults to `DEFAULT_ACK_TIMEOUT`. */
41
+ ackTimeout?: number;
42
+ /** The longest wait between two resends. Defaults to `MAX_ACK_TIMEOUT`, and never less than `ackTimeout`. */
43
+ maxAckTimeout?: number;
44
+ onResend?: (info: ChangesetResendInfo) => void;
45
+ now?: () => number;
46
+ }
47
+ /**
48
+ * Submits one changeset and sends it again until the server acknowledges it.
49
+ *
50
+ * It is created when the changeset is first sent and handed from one state to the next while the
51
+ * changeset is kept, so the pending timer, the intervals and the attempt count survive state transitions.
52
+ * The states only tell it to `submit`, `requestRetry` and `dispose`; everything about delivery (timers, backoff,
53
+ * attempt counting) stays here. There is at most one timer at a time: every trigger replaces the previous one.
54
+ *
55
+ * - After every send, an acknowledgement timeout is armed (`ackTimeout`, then doubled up to `maxAckTimeout`).
56
+ * The request itself is given the same timeout, so an unanswered request is cancelled when the changeset is
57
+ * sent again.
58
+ * - A `changeset_should_retry` from the server replaces the timer with a short backoff (0, 1s, 2s, ... up to
59
+ * `MAX_RETRY_TIMEOUT`). A request that fails for a transient reason (network error, 408, 429, 5xx) does the
60
+ * same; other failures and the request timeout leave the timer alone. The two intervals are counted
61
+ * independently.
62
+ * - A timer that fires while no state is bound (e.g. while missing changesets are fetched) is remembered and
63
+ * runs as soon as the next awaiting state binds.
64
+ */
65
+ export declare class ChangesetSubmitter {
66
+ private readonly _send;
67
+ private readonly _options;
68
+ private _attempt;
69
+ private readonly _firstSentAt;
70
+ private _ackTimeout;
71
+ private readonly _maxAckTimeout;
72
+ private _retryBackoff;
73
+ private _nextDelay;
74
+ private _timer;
75
+ private _pendingReason;
76
+ private _source;
77
+ /** Identifies the latest send; the outcome of an earlier send is ignored. */
78
+ private _sendToken;
79
+ private _disposed;
80
+ private readonly _now;
81
+ private _reqId;
82
+ constructor(reqId: number, _send: SendChangeset, _options?: IChangesetSubmitterOptions);
83
+ /** The request id of the changeset. It is assigned by the transport on the first send and read back then. */
84
+ get reqId(): number;
85
+ get attempt(): number;
86
+ get nextDelayMs(): number;
87
+ get elapsedMs(): number;
88
+ /**
89
+ * Bind the state that owns the awaiting changeset. `source` returns the changeset as it currently is (it may
90
+ * have been transformed since the last send) and the number of unsynced mutations. A resend that came due
91
+ * while nothing was bound runs now.
92
+ */
93
+ bind(source: () => IChangesetSubmitterSource): void;
94
+ /** Detach the current state while the changeset is handed to the next one. Timers keep running. */
95
+ unbind(): void;
96
+ /**
97
+ * Send the changeset and wait for its acknowledgement.
98
+ *
99
+ * Without `reason` this is the first send of the changeset, which is not reported as a resend. With `reason`
100
+ * it is a resend triggered outside this submitter (e.g. after a reconnect): it counts as an attempt and
101
+ * notifies the listener.
102
+ */
103
+ submit(changeset: IChangeset, unsyncedMutationCount: number, reason?: ChangesetResendReason): void;
104
+ /**
105
+ * Wait for the acknowledgement of a send that happened outside this submitter, e.g. a changeset restored
106
+ * from the cache that the entity resends itself.
107
+ */
108
+ armAckTimeout(): void;
109
+ /** The server asked for the changeset to be resent: replace the current timer with the short backoff. */
110
+ requestRetry(): void;
111
+ /** The changeset was acknowledged, rejected or dropped. */
112
+ dispose(): void;
113
+ private _deliver;
114
+ /**
115
+ * The request did not reach the server. A transient failure brings the next send forward to the short
116
+ * backoff; the acknowledgement timeout keeps its own count. A request that ran into its timeout is ignored:
117
+ * the changeset is being sent again at that very moment. So is the failure of a request that a later send
118
+ * superseded, e.g. when the server asked for a retry before the request was answered.
119
+ */
120
+ private _onRequestFailed;
121
+ private _schedule;
122
+ private _resend;
123
+ /** Count a resend and move the interval it belongs to; the request timeout follows the new interval. */
124
+ private _countResend;
125
+ private _notifyResend;
126
+ private _clearTimer;
127
+ }
128
+ /**
129
+ * How long to wait before sending again after a failed request, or `null` when the failure should not bring
130
+ * the next send forward.
131
+ */
132
+ export declare function getRetryDelay(error: unknown, backoff: number): number | null;
@@ -1,7 +1,9 @@
1
1
  import type { IDisposable, IMutationInfo, Nullable, UniverInstanceType } from '@univerjs/core';
2
2
  import type { Observable } from 'rxjs';
3
3
  import type { CollaborationSession } from '../../services/collaboration-session/collaboration-session';
4
- import type { CollaborationState, ICollaborationStateHandler, PrepareTransformSelectionsHandler, SyncEditingCollabCursorHandler, TransformIMEHandler, TransformRemoteChangesetByIMECache, TransformRemoteChangesetByStateCache, TransformSelectionsHandler, TransformStateHandler } from './collaboration-state';
4
+ import type { IUnitCacheData } from '../../services/local-cache/local-cache.service';
5
+ import type { IChangesetResendEvent } from './changeset-submitter';
6
+ import type { CollaborationState, ICollaborationStateHandler, IConflictInfo, PrepareTransformSelectionsHandler, SyncEditingCollabCursorHandler, TransformIMEHandler, TransformRemoteChangesetByIMECache, TransformRemoteChangesetByStateCache, TransformSelectionsHandler, TransformStateHandler } from './collaboration-state';
5
7
  import { CompressMutationService, RevisionService } from '@univerjs-pro/collaboration';
6
8
  import { ICommandService, ILogService, Injector, IPermissionService, IUniverInstanceService, LocaleService, RxDisposable } from '@univerjs/core';
7
9
  import { DocStateChangeManagerService } from '@univerjs/docs';
@@ -11,6 +13,20 @@ import { ILocalCacheService } from '../../services/local-cache/local-cache.servi
11
13
  import { SheetTransformSelectionsService } from '../../services/range-selection/sheet-transform-selections.service';
12
14
  import { ISingleActiveUnitService } from '../../services/single-active-unit/single-active-unit.service';
13
15
  import { CollaborationStatus } from './collaboration-state';
16
+ /**
17
+ * Emitted once when a unit enters the conflict state, after its offline cache has been cleared. Carries
18
+ * `IConflictInfo` and the local edits the server has not acknowledged.
19
+ */
20
+ export interface ICollaborationConflictEvent extends IConflictInfo {
21
+ unitID: string;
22
+ /**
23
+ * The local edits the server has not acknowledged, in the format of the offline cache, or `null` when there are
24
+ * none. `awaitingChangeset` has been sent; whether the server applied it is unknown.
25
+ */
26
+ localChanges: IUnitCacheData | null;
27
+ /** Whether the offline cache of the unit was cleared. When `false`, a page reload replays the cached edits. */
28
+ cacheCleared: boolean;
29
+ }
14
30
  /**
15
31
  * Each univer document instance would map to an `CollaborationEntity` to handle collaborated editing events.
16
32
  */
@@ -37,6 +53,12 @@ export declare abstract class CollaborationEntity extends RxDisposable {
37
53
  protected _changesetSessionId: string;
38
54
  protected _changesetReqId: number;
39
55
  readonly status$: Observable<CollaborationStatus>;
56
+ private readonly _changesetResend$;
57
+ /** Emits every time the awaiting changeset is sent again because it was not acknowledged. */
58
+ readonly changesetResend$: Observable<IChangesetResendEvent>;
59
+ private readonly _conflict$;
60
+ /** Emits once when the unit enters the conflict state, after its offline cache has been cleared. */
61
+ readonly conflict$: Observable<ICollaborationConflictEvent>;
40
62
  constructor(unitID: string, session: CollaborationSession, _type: UniverInstanceType, _injector: Injector, _compressMutationService: CompressMutationService, _localeService: LocaleService, _revisionService: RevisionService, _eventService: CollaborationUIEventService, _univerInstanceService: IUniverInstanceService, _logService: ILogService, _commandService: ICommandService, _permissionService: IPermissionService, _singleActiveUnitService?: ISingleActiveUnitService | undefined, _localCacheService?: ILocalCacheService | undefined);
41
63
  init(): Promise<void>;
42
64
  /**
@@ -45,6 +67,9 @@ export declare abstract class CollaborationEntity extends RxDisposable {
45
67
  */
46
68
  pauseCollaboration(): IDisposable;
47
69
  onLocalMutation(command: IMutationInfo): void;
70
+ /** Whether there are local mutations that the server has not acknowledged yet. */
71
+ hasWritePending(): boolean;
72
+ dispose(): void;
48
73
  private _updateState;
49
74
  protected _init(): Promise<CollaborationState>;
50
75
  /**
@@ -53,6 +78,8 @@ export declare abstract class CollaborationEntity extends RxDisposable {
53
78
  private _transitionLocked;
54
79
  private _unlockTransition;
55
80
  private _lockTransition;
81
+ /** Run one transition under the lock. The lock is released even when the state throws, so the next event can be handled. */
82
+ private _transit;
56
83
  protected _onLocalMutation(command: IMutationInfo): void;
57
84
  private _remoteChangesetQueue;
58
85
  private _onRemoteChangeset;
@@ -1,15 +1,18 @@
1
1
  import type { IAcknowledgedChangeset, IChangeset } from '@univerjs-pro/collaboration';
2
2
  import type { IMutationInfo, Nullable } from '@univerjs/core';
3
- import type { Observable } from 'rxjs';
3
+ import type { IUnitCacheData } from '../../services/local-cache/local-cache.service';
4
4
  import type { ICollaborativeUndoRedoService } from '../../services/undoredo/collaborative-undoredo.service';
5
+ import type { ChangesetResendInfo, ChangesetResendReason } from './changeset-submitter';
6
+ import type { ICollaborationSendOptions } from '../../services/socket/collaboration-socket.service';
5
7
  import { ITransformService, RevisionService } from '@univerjs-pro/collaboration';
6
- import { ICommandService, IConfigService, ILogService, Injector, IPermissionService, IUniverInstanceService, LocaleService, UniverInstanceType, UserManagerService } from '@univerjs/core';
8
+ import { ICommandService, IConfigService, ILogService, Injector, IPermissionService, IUniverInstanceService, UniverInstanceType, UserManagerService } from '@univerjs/core';
7
9
  import { CollaborationUIEventService } from '../../services/collaboration-ui-event/collaboration-ui-event';
8
10
  import { ILocalCacheService } from '../../services/local-cache/local-cache.service';
11
+ import { ChangesetSubmitter } from './changeset-submitter';
9
12
  /** Provide both states when and after changing, so the parent could examine if state transition is correct. */
10
13
  export type StateChangeHandler = (before: CollaborationState, after: CollaborationState) => void;
11
14
  /** A network interface to send changeset and */
12
- export type SendChangesetHandler = (changeset: IChangeset) => void;
15
+ export type SendChangesetHandler = (changeset: IChangeset, options: ICollaborationSendOptions) => void | Promise<void>;
13
16
  export type MissingChangesetsHandler = (params: {
14
17
  from: number;
15
18
  to: number;
@@ -36,7 +39,57 @@ export interface ICollaborationStateHandler {
36
39
  onSyncEditingCollabCursor?: SyncEditingCollabCursorHandler;
37
40
  onTransformRemoteChangesetByIMECache?: TransformRemoteChangesetByIMECache;
38
41
  onTransformRemoteChangesetByStateCache?: TransformRemoteChangesetByStateCache;
42
+ /** The awaiting changeset was sent again. */
43
+ onChangesetResend?: (info: ChangesetResendInfo) => void;
44
+ /**
45
+ * The unit entered `ConflictState` and its recovery preparation is done: the unacknowledged local edits are backed
46
+ * up and the offline cache is cleared (`cacheCleared` tells whether that succeeded). Called once per conflict.
47
+ */
48
+ onConflict?: (info: IConflictInfo, localChanges: IUnitCacheData | null, cacheCleared: boolean) => void;
49
+ /** How long to wait for an acknowledgement before resending; see `IUniverCollaborationClientConfig`. */
50
+ resendTimeouts?: {
51
+ ackTimeout?: number;
52
+ maxAckTimeout?: number;
53
+ };
54
+ }
55
+ /** Why a unit entered `ConflictState`. */
56
+ export type ConflictReason =
57
+ /** A remote changeset could not be transformed against the local edits. */
58
+ 'transform_failed'
59
+ /** A remote changeset was transformed but one of its mutations failed to execute. */
60
+ | 'apply_failed'
61
+ /** The server rejected the local changeset. */
62
+ | 'changeset_rejected'
63
+ /** The server rejected the local changeset for lack of permission. */
64
+ | 'permission_rejected'
65
+ /** Another collaborator reverted the unit to a historical revision. */
66
+ | 'remote_revert';
67
+ /** The remote revert that caused a `remote_revert` conflict. */
68
+ export interface IRemoteRevertInfo {
69
+ /** The revision that was restored. */
70
+ revision: number;
71
+ /** The member ID of the collaborator who restored it. */
72
+ memberId: string;
73
+ /** The user ID of the collaborator who restored it. */
74
+ userId: string;
75
+ }
76
+ /** Why a unit entered `ConflictState`. */
77
+ export interface IConflictInfo {
78
+ reason: ConflictReason;
79
+ /** The error message, when the conflict was caused by an error. */
80
+ message?: string;
81
+ /** Present when `reason` is `remote_revert`. */
82
+ revert?: IRemoteRevertInfo;
83
+ }
84
+ /** A remote changeset was transformed successfully but executing its mutations failed. */
85
+ export declare class RemoteChangesetApplyError extends Error {
86
+ readonly applyError: unknown;
87
+ constructor(applyError: unknown);
39
88
  }
89
+ /** How a remote changeset relates to the local copy, see `CollaborationState._classifyRemoteChangeset`. */
90
+ export type RemoteChangesetKind = 'echo' | 'remote_revert' | 'applied' | 'missing' | 'apply';
91
+ /** How an acknowledgement relates to the local copy, see `CollaborationState._classifyAck`. */
92
+ export type RemoteAckKind = 'ack_of_awaiting' | 'stale' | 'missing' | 'next';
40
93
  export declare enum CollaborationStatus {
41
94
  NOT_COLLAB = "not_collab",
42
95
  SYNCED = "synced",
@@ -47,20 +100,6 @@ export declare enum CollaborationStatus {
47
100
  CONFLICT = "conflict",
48
101
  OFFLINE = "offline"
49
102
  }
50
- /**
51
- * The retry of an awaiting changeset. It is handed from one state to the next while the awaiting changeset is
52
- * kept, so the pending timer, the backoff and the total retry time survive state transitions.
53
- */
54
- export interface IResendSchedule {
55
- /** The pending retry. `undefined` once it has fired and the awaiting changeset was resent. */
56
- timer: Observable<{
57
- reqId: number;
58
- }> | undefined;
59
- /** The backoff before the next retry. */
60
- timeout: number;
61
- /** The total backoff spent on this awaiting changeset, bounded by `MAX_RETRY_TOTAL_TIMEOUT`. */
62
- total: number;
63
- }
64
103
  /**
65
104
  * Use state machine to manage syncing states.
66
105
  * An explanation to how these states are transformed to each other.
@@ -82,7 +121,18 @@ export declare abstract class CollaborationState {
82
121
  constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: Nullable<IChangeset>, pendingMutations: IMutationInfo[],
83
122
  /** State may change internally. Parent module should provide a callback to handle this event. */
84
123
  _handler: ICollaborationStateHandler, _commandService: ICommandService, _undoRedoService: ICollaborativeUndoRedoService, _revisionService: RevisionService, _localCacheService?: ILocalCacheService | undefined);
85
- abstract resend(): void;
124
+ /**
125
+ * Send the awaiting changeset again. Only states that keep an awaiting changeset implement it.
126
+ * @param reason Why the changeset is resent. Defaults to `reconnect`.
127
+ */
128
+ abstract resend(reason?: ChangesetResendReason): void;
129
+ /** Whether there are local mutations that the server has not acknowledged yet. */
130
+ hasLocalChanges(): boolean;
131
+ /** Release timers held by the state. Called when the entity is disposed while the state is current. */
132
+ dispose(): void;
133
+ /** Mutations in the awaiting changeset plus the pending mutations. */
134
+ protected _unsyncedMutationCount(): number;
135
+ protected _createChangesetSubmitter(changeset: IChangeset): ChangesetSubmitter;
86
136
  /**
87
137
  * Append a location mutation to collaboration queue.
88
138
  *
@@ -111,13 +161,48 @@ export declare abstract class CollaborationState {
111
161
  abstract toggleOffline(): CollaborationState;
112
162
  abstract toggleOnline(): CollaborationState;
113
163
  /**
114
- * Check if there are some changesets missing. If true, the state should abort this changeset and request
115
- * for missing changesets. Missing changesets will later be passed to `onRemoteChangeset` method.
164
+ * Classify a remote changeset against the local copy. Pure: nothing is requested from the server.
116
165
  *
117
- * @param changeset The changeset sent from the remote collaboration server.
118
- * @returns There is some changeset missing.
166
+ * - `echo`: the awaiting changeset came back as a broadcast, e.g. when the request was committed after the client
167
+ * reconnected with another member id. It acknowledges the awaiting changeset even when the loaded snapshot
168
+ * already contains its revision, so it is checked first.
169
+ * - `applied`: servers may deliver a changeset more than once or out of order, e.g. when several server nodes
170
+ * relay changesets, or after the snapshot used to join already contains it. Applied revisions are ignored.
171
+ * - `missing`: there is a gap before this changeset. The missing changesets have to be fetched first.
172
+ * - `apply`: the changeset is the next revision and can be transformed and applied.
119
173
  */
120
- protected _checkMissing(changeset: IChangeset | IAcknowledgedChangeset): boolean;
174
+ protected _classifyRemoteChangeset(changeset: IChangeset): RemoteChangesetKind;
175
+ /**
176
+ * Classify an acknowledgement against the local copy. Pure: nothing is requested from the server.
177
+ *
178
+ * - `ack_of_awaiting`: the revision is already applied but sid/reqId match the awaiting changeset, i.e. the
179
+ * changeset was committed before the loaded snapshot, which already contains it.
180
+ * - `stale`: the revision is already applied and belongs to an earlier changeset.
181
+ * - `missing`: there is a gap before the acknowledged revision. The missing changesets have to be fetched first.
182
+ * - `next`: the acknowledged revision is the next one.
183
+ */
184
+ protected _classifyAck(ack: IAcknowledgedChangeset): RemoteAckKind;
185
+ /**
186
+ * Handle a remote changeset the way every online state does: an echo acknowledges the awaiting changeset, an
187
+ * applied revision is ignored, a gap is requested from the server before `onMissing` transits to
188
+ * `FetchingMissState`, and the next revision is handed to `onApply`.
189
+ */
190
+ protected _dispatchRemoteChangeset(changeset: IChangeset, handlers: {
191
+ onMissing: (changeset: IChangeset) => CollaborationState;
192
+ onApply: (changeset: IChangeset) => CollaborationState;
193
+ /** The unit was reverted on the server: enter `ConflictState` with `_remoteRevertInfo(changeset)`. */
194
+ onRemoteRevert: (changeset: IChangeset) => CollaborationState;
195
+ }): CollaborationState;
196
+ /** The conflict info of a remote changeset that reverts this unit. */
197
+ protected _remoteRevertInfo(changeset: IChangeset): IConflictInfo;
198
+ /** The conflict info of an error thrown while transforming or applying a remote changeset. */
199
+ protected _conflictInfoFromError(error: unknown): IConflictInfo;
200
+ /** The conflict info of a rejection from the server. */
201
+ protected _rejectionInfo(config?: {
202
+ isPermissionRej: boolean;
203
+ }): IConflictInfo;
204
+ /** Request the changesets after the current revision up to `revision`: left open, right closed. */
205
+ protected _requestMissingChangesets(revision: number): void;
121
206
  /** Transform undo redo mutations in the undo redo stack. */
122
207
  protected _transformUndoredo(changeset: IChangeset): void;
123
208
  /** Transform selections by calling the selection manager service. */
@@ -129,15 +214,7 @@ export declare abstract class CollaborationState {
129
214
  protected _transformRemoteChangesetByStateCache(changeset: IChangeset): IChangeset;
130
215
  protected _syncEditingCollabCursor(changeset: IChangeset): void;
131
216
  protected _getCurrentRevision(): number;
132
- /**
133
- * Servers may deliver a changeset more than once or out of order, e.g. when several server nodes relay
134
- * changesets, or after the snapshot used to join already contains it. Revisions already applied are ignored.
135
- */
136
- protected _isAppliedRevision(changeset: IChangeset): boolean;
137
- /**
138
- * The awaiting changeset may come back as a broadcast instead of an acknowledgement, e.g. when the request
139
- * was committed after the client reconnected with another member id.
140
- */
217
+ /** The awaiting changeset came back as a broadcast instead of an acknowledgement. */
141
218
  protected _isEchoedAwaitingChangeset(remoteChangeset: IChangeset): boolean;
142
219
  /**
143
220
  * The acknowledgement echoes the committed changeset, so it can be matched to the awaiting changeset by sid/reqId
@@ -160,6 +237,7 @@ export declare class SyncedState extends CollaborationState {
160
237
  constructor(unitID: string, type: UniverInstanceType, handler: ICollaborationStateHandler, revisionService: RevisionService, _injector: Injector, undoRedoService: ICollaborativeUndoRedoService, commandService: ICommandService, _logService: ILogService, _transformService: ITransformService, localCacheService?: ILocalCacheService | undefined);
161
238
  appendMutation(mutation: IMutationInfo): CollaborationState;
162
239
  onRemoteChangeset(changeset: IChangeset): CollaborationState;
240
+ private _transformAndApply;
163
241
  private _onConflict;
164
242
  onRemoteAck(changeset: IAcknowledgedChangeset): CollaborationState;
165
243
  onRemoteRej(): CollaborationState;
@@ -187,6 +265,7 @@ export declare class PendingState extends CollaborationState {
187
265
  constructor(unitID: string, type: UniverInstanceType, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, _injector: Injector, _revisionService: RevisionService, _userManagerService: UserManagerService, _logService: ILogService, commandService: ICommandService, _configService: IConfigService, _transformService: ITransformService, _univerInstanceService: IUniverInstanceService, _undoRedoService: ICollaborativeUndoRedoService, _localCacheService?: ILocalCacheService);
188
266
  appendMutation(mutation: IMutationInfo): CollaborationState;
189
267
  onRemoteChangeset(changeset: IChangeset): CollaborationState;
268
+ private _transformAndApply;
190
269
  onRemoteAck(changeset: IAcknowledgedChangeset): CollaborationState;
191
270
  onRemoteRej(): CollaborationState;
192
271
  onRemoteRetry(): CollaborationState;
@@ -195,6 +274,7 @@ export declare class PendingState extends CollaborationState {
195
274
  private _getSendChangesetTimeout;
196
275
  resend(): void;
197
276
  private _clearScheduledTask;
277
+ dispose(): void;
198
278
  private _onConflict;
199
279
  }
200
280
  /**
@@ -205,27 +285,25 @@ export declare class AwaitingState extends CollaborationState {
205
285
  private readonly _logService;
206
286
  private readonly _transformService;
207
287
  readonly status = CollaborationStatus.AWAITING;
208
- private _resendTimeout;
209
- private _maxTotalRetryTimeout;
210
- private _resendTimer;
211
- private _sender;
212
- constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: IChangeset, handler: ICollaborationStateHandler, schedule: IResendSchedule | undefined, _injector: Injector, revisionService: RevisionService, commandService: ICommandService, _logService: ILogService, _transformService: ITransformService, undoRedoService: ICollaborativeUndoRedoService, localCacheService?: ILocalCacheService);
288
+ /** Resends the awaiting changeset until it is acknowledged. Handed to the next state that keeps the changeset. */
289
+ private readonly _submitter;
290
+ constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: IChangeset, handler: ICollaborationStateHandler, submitter: ChangesetSubmitter | undefined, _injector: Injector, revisionService: RevisionService, commandService: ICommandService, _logService: ILogService, _transformService: ITransformService, undoRedoService: ICollaborativeUndoRedoService, localCacheService?: ILocalCacheService);
213
291
  appendMutation(mutation: IMutationInfo): CollaborationState;
214
292
  onRemoteChangeset(changeset: IChangeset): CollaborationState;
293
+ private _transformAndApply;
215
294
  onRemoteAck(changeset: IAcknowledgedChangeset): CollaborationState;
295
+ private _toSynced;
216
296
  onRemoteRej(config?: {
217
297
  isPermissionRej: boolean;
218
298
  }): CollaborationState;
219
299
  onRemoteRetry(changeset: IChangeset): CollaborationState;
220
300
  toggleOffline(): CollaborationState;
221
301
  toggleOnline(): CollaborationState;
222
- resend(): void;
302
+ resend(reason?: ChangesetResendReason): void;
223
303
  private _onConflict;
224
- private _subscribeResendTimer;
225
- private _runScheduledResend;
226
- /** The retry of the awaiting changeset, to hand to the next state that keeps the changeset. */
227
- private _resendSchedule;
228
- private _clearScheduledTask;
304
+ dispose(): void;
305
+ /** Detach from the submitter before the awaiting changeset is handed to the next state. */
306
+ private _detachSubmitter;
229
307
  }
230
308
  /**
231
309
  * Some local mutations are sent to the server and waiting for acknowledgement, yet still some local mutations
@@ -237,44 +315,51 @@ export declare class AwaitingWithPendingState extends CollaborationState {
237
315
  private readonly _transformService;
238
316
  private readonly localCacheService?;
239
317
  readonly status = CollaborationStatus.AWAITING_WITH_PENDING;
240
- private _resendTimeout;
241
- private _maxTotalRetryTimeout;
242
- private _resendTimer;
243
- private _sender;
244
- constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: IChangeset, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, schedule: IResendSchedule | undefined, _injector: Injector, revisionService: RevisionService, commandService: ICommandService, _logService: ILogService, _transformService: ITransformService, undoRedoService: ICollaborativeUndoRedoService, localCacheService?: ILocalCacheService | undefined);
318
+ /** Resends the awaiting changeset until it is acknowledged. Handed to the next state that keeps the changeset. */
319
+ private readonly _submitter;
320
+ constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: IChangeset, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, submitter: ChangesetSubmitter | undefined, _injector: Injector, revisionService: RevisionService, commandService: ICommandService, _logService: ILogService, _transformService: ITransformService, undoRedoService: ICollaborativeUndoRedoService, localCacheService?: ILocalCacheService | undefined);
245
321
  appendMutation(mutation: IMutationInfo): CollaborationState;
246
322
  onRemoteChangeset(changeset: IChangeset): CollaborationState;
323
+ private _transformAndApply;
247
324
  onRemoteAck(changeset: IAcknowledgedChangeset): CollaborationState;
325
+ /** Transit to `PendingState` to schedule another sending task. */
326
+ private _toPending;
248
327
  onRemoteRej(config?: {
249
328
  isPermissionRej: boolean;
250
329
  }): CollaborationState;
251
330
  onRemoteRetry(changeset: IChangeset): CollaborationState;
252
331
  toggleOffline(): CollaborationState;
253
332
  toggleOnline(): CollaborationState;
254
- resend(): void;
333
+ resend(reason?: ChangesetResendReason): void;
255
334
  private _onConflict;
256
- private _subscribeResendTimer;
257
- private _runScheduledResend;
258
- /** The retry of the awaiting changeset, to hand to the next state that keeps the changeset. */
259
- private _resendSchedule;
260
- private _clearScheduledTask;
335
+ dispose(): void;
336
+ /** Detach from the submitter before the awaiting changeset is handed to the next state. */
337
+ private _detachSubmitter;
261
338
  }
262
339
  /**
263
- * This state is special for handling collaboration conflicts.
264
- *
265
- * `ConflictState` is a dead end state. It cannot transit to any other state. User could only reload the page to
266
- * reload the document.
340
+ * A dead end state: the unit can no longer follow the server. Editing is disabled. `info` says why and `localChanges`
341
+ * holds the local edits the server has not acknowledged. Reported once through `CollaborationEntity.conflict$`.
267
342
  */
268
343
  export declare class ConflictState extends CollaborationState {
269
- private _isPermissionRej;
344
+ readonly info: IConflictInfo;
270
345
  private readonly _permissionService;
271
- private readonly _localeService;
272
346
  private readonly _userManagerService;
273
347
  private readonly _logService;
274
348
  private readonly _collaborationUIEventService;
275
349
  private readonly localCacheService?;
276
350
  readonly status = CollaborationStatus.CONFLICT;
277
- constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: Nullable<IChangeset>, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, _isPermissionRej: boolean | undefined, _permissionService: IPermissionService, commandService: ICommandService, undoRedoService: ICollaborativeUndoRedoService, revisionService: RevisionService, _localeService: LocaleService, _userManagerService: UserManagerService, _logService: ILogService, _collaborationUIEventService: CollaborationUIEventService, localCacheService?: ILocalCacheService | undefined);
351
+ /**
352
+ * The local edits the server has not acknowledged, in the format of the offline cache, or `null` when there are
353
+ * none. `awaitingChangeset` has been sent; whether the server applied it is unknown.
354
+ */
355
+ readonly localChanges: IUnitCacheData | null;
356
+ constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: Nullable<IChangeset>, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, info: IConflictInfo, _permissionService: IPermissionService, commandService: ICommandService, undoRedoService: ICollaborativeUndoRedoService, revisionService: RevisionService, _userManagerService: UserManagerService, _logService: ILogService, _collaborationUIEventService: CollaborationUIEventService, localCacheService?: ILocalCacheService | undefined);
357
+ /**
358
+ * Back up the unacknowledged local edits, then clear the offline cache so that the next load does not replay them
359
+ * (a cache with pending edits would also make the loader fetch the cached revision). Only then is the conflict
360
+ * reported, so that the handler may reload the page safely.
361
+ */
362
+ private _prepareConflictRecovery;
278
363
  appendMutation(): CollaborationState;
279
364
  onRemoteChangeset(): CollaborationState;
280
365
  onRemoteAck(): CollaborationState;
@@ -283,18 +368,17 @@ export declare class ConflictState extends CollaborationState {
283
368
  toggleOffline(): CollaborationState;
284
369
  toggleOnline(): CollaborationState;
285
370
  resend(): void;
286
- private _prepareConflictRecovery;
287
371
  private _showConflictProcessingNotification;
288
- private _showConflictNotification;
289
372
  private _disableEditing;
290
373
  }
291
374
  export declare class OfflineState extends CollaborationState {
292
375
  private readonly _injector;
376
+ private readonly _logService;
293
377
  readonly status = CollaborationStatus.OFFLINE;
294
- constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: Nullable<IChangeset>, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, _injector: Injector, revisionService: RevisionService, commandService: ICommandService, undoRedoService: ICollaborativeUndoRedoService, localCacheService?: ILocalCacheService);
378
+ constructor(unitID: string, type: UniverInstanceType, awaitingChangeset: Nullable<IChangeset>, pendingMutations: IMutationInfo[], handler: ICollaborationStateHandler, _injector: Injector, revisionService: RevisionService, commandService: ICommandService, undoRedoService: ICollaborativeUndoRedoService, _logService: ILogService, localCacheService?: ILocalCacheService);
295
379
  appendMutation(mutation: IMutationInfo): CollaborationState;
296
- onRemoteChangeset(_changeset: IChangeset): CollaborationState;
297
- onRemoteAck(): CollaborationState;
380
+ onRemoteChangeset(changeset: IChangeset): CollaborationState;
381
+ onRemoteAck(changeset: IAcknowledgedChangeset): CollaborationState;
298
382
  onRemoteRej(): CollaborationState;
299
383
  onRemoteRetry(): CollaborationState;
300
384
  toggleOffline(): CollaborationState;
@@ -315,8 +399,11 @@ export declare class FetchingMissState extends CollaborationState {
315
399
  */
316
400
  private _acknowledgedAwaitingRevision;
317
401
  private _queuedRemoteChangesets;
318
- /** The retry of the awaiting changeset. The next awaiting state runs it after the fetch. */
319
- private _resendSchedule;
402
+ /**
403
+ * The resends of the awaiting changeset. It is not bound while the gap is fetched: a resend that comes due
404
+ * meanwhile runs once the next awaiting state binds it.
405
+ */
406
+ private _submitter;
320
407
  private readonly _injector;
321
408
  private readonly _logService;
322
409
  private readonly _transformService;
@@ -329,8 +416,11 @@ export declare class FetchingMissState extends CollaborationState {
329
416
  * the acknowledged server revision here.
330
417
  */
331
418
  _acknowledgedAwaitingRevision: Nullable<number>, _queuedRemoteChangesets: IChangeset[], handler: ICollaborationStateHandler,
332
- /** The retry of the awaiting changeset. The next awaiting state runs it after the fetch. */
333
- _resendSchedule: IResendSchedule | undefined, _injector: Injector, revisionService: RevisionService, _logService: ILogService, commandService: ICommandService, undoRedoService: ICollaborativeUndoRedoService, _transformService: ITransformService, localCacheService?: ILocalCacheService | undefined);
419
+ /**
420
+ * The resends of the awaiting changeset. It is not bound while the gap is fetched: a resend that comes due
421
+ * meanwhile runs once the next awaiting state binds it.
422
+ */
423
+ _submitter: ChangesetSubmitter | undefined, _injector: Injector, revisionService: RevisionService, _logService: ILogService, commandService: ICommandService, undoRedoService: ICollaborativeUndoRedoService, _transformService: ITransformService, localCacheService?: ILocalCacheService | undefined);
334
424
  onMissedChangesetFetched(changesets: IChangeset[]): CollaborationState;
335
425
  private _handleRemoteChangesets;
336
426
  private _transformAndApplyRemoteChangeset;
@@ -338,6 +428,8 @@ export declare class FetchingMissState extends CollaborationState {
338
428
  private _acknowledgeAwaiting;
339
429
  /** The awaiting changeset was committed before the loaded snapshot, so there is no new revision to count. */
340
430
  private _dropAcknowledgedAwaiting;
431
+ private _disposeSubmitter;
432
+ dispose(): void;
341
433
  resend(): void;
342
434
  appendMutation(mutation: IMutationInfo<object>): CollaborationState;
343
435
  onRemoteChangeset(changeset: IChangeset): CollaborationState;
@@ -14,8 +14,15 @@ export declare class CollaborationController extends RxDisposable {
14
14
  constructor(_injector: Injector, _collabSessionService: CollaborationSessionService, _configService: IConfigService, _univerInstanceService: IUniverInstanceService);
15
15
  dispose(): void;
16
16
  getCollabEntity(id: string): Nullable<CollaborationEntity>;
17
+ /** The entities of every unit that is collaborating now. */
18
+ getCollabEntities(): CollaborationEntity[];
17
19
  getCollabEntity$(id: string): Observable<CollaborationEntity>;
18
20
  readyForCollab(id: string): Promise<void>;
21
+ /**
22
+ * Whether a unit has local mutations that the server has not acknowledged yet.
23
+ * @param unitId The unit to check. When omitted, every collaborating unit is checked.
24
+ */
25
+ hasWritePending(unitId?: string): boolean;
19
26
  private _init;
20
27
  /**
21
28
  * Start collaboration on a document.