@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.
- package/README.md +2 -0
- package/lib/cjs/facade.js +1 -1
- package/lib/cjs/index.js +1 -1
- package/lib/cjs/locale/ar-SA.js +1 -1
- package/lib/cjs/locale/ca-ES.js +1 -1
- package/lib/cjs/locale/de-DE.js +1 -1
- package/lib/cjs/locale/en-US.js +1 -1
- package/lib/cjs/locale/es-ES.js +1 -1
- package/lib/cjs/locale/fa-IR.js +1 -1
- package/lib/cjs/locale/fr-FR.js +1 -1
- package/lib/cjs/locale/id-ID.js +1 -1
- package/lib/cjs/locale/it-IT.js +1 -1
- package/lib/cjs/locale/ja-JP.js +1 -1
- package/lib/cjs/locale/ko-KR.js +1 -1
- package/lib/cjs/locale/pl-PL.js +1 -1
- package/lib/cjs/locale/pt-BR.js +1 -1
- package/lib/cjs/locale/ru-RU.js +1 -1
- package/lib/cjs/locale/sk-SK.js +1 -1
- package/lib/cjs/locale/vi-VN.js +1 -1
- package/lib/cjs/locale/zh-CN.js +1 -1
- package/lib/cjs/locale/zh-HK.js +1 -1
- package/lib/cjs/locale/zh-TW.js +1 -1
- package/lib/es/facade.js +1 -1
- package/lib/es/index.js +1 -1
- package/lib/es/locale/ar-SA.js +1 -1
- package/lib/es/locale/ca-ES.js +1 -1
- package/lib/es/locale/de-DE.js +1 -1
- package/lib/es/locale/en-US.js +1 -1
- package/lib/es/locale/es-ES.js +1 -1
- package/lib/es/locale/fa-IR.js +1 -1
- package/lib/es/locale/fr-FR.js +1 -1
- package/lib/es/locale/id-ID.js +1 -1
- package/lib/es/locale/it-IT.js +1 -1
- package/lib/es/locale/ja-JP.js +1 -1
- package/lib/es/locale/ko-KR.js +1 -1
- package/lib/es/locale/pl-PL.js +1 -1
- package/lib/es/locale/pt-BR.js +1 -1
- package/lib/es/locale/ru-RU.js +1 -1
- package/lib/es/locale/sk-SK.js +1 -1
- package/lib/es/locale/vi-VN.js +1 -1
- package/lib/es/locale/zh-CN.js +1 -1
- package/lib/es/locale/zh-HK.js +1 -1
- package/lib/es/locale/zh-TW.js +1 -1
- package/lib/facade.js +1 -1
- package/lib/index.js +1 -1
- package/lib/locale/ar-SA.js +1 -1
- package/lib/locale/ca-ES.js +1 -1
- package/lib/locale/de-DE.js +1 -1
- package/lib/locale/en-US.js +1 -1
- package/lib/locale/es-ES.js +1 -1
- package/lib/locale/fa-IR.js +1 -1
- package/lib/locale/fr-FR.js +1 -1
- package/lib/locale/id-ID.js +1 -1
- package/lib/locale/it-IT.js +1 -1
- package/lib/locale/ja-JP.js +1 -1
- package/lib/locale/ko-KR.js +1 -1
- package/lib/locale/pl-PL.js +1 -1
- package/lib/locale/pt-BR.js +1 -1
- package/lib/locale/ru-RU.js +1 -1
- package/lib/locale/sk-SK.js +1 -1
- package/lib/locale/vi-VN.js +1 -1
- package/lib/locale/zh-CN.js +1 -1
- package/lib/locale/zh-HK.js +1 -1
- package/lib/locale/zh-TW.js +1 -1
- package/lib/types/config/config.d.ts +19 -0
- package/lib/types/controllers/collaboration/changeset-submitter.d.ts +132 -0
- package/lib/types/controllers/collaboration/collaboration-entity.d.ts +28 -1
- package/lib/types/controllers/collaboration/collaboration-state.d.ts +162 -70
- package/lib/types/controllers/collaboration/collaboration.controller.d.ts +7 -0
- package/lib/types/controllers/collaboration/utils/changeset-utils.d.ts +7 -3
- package/lib/types/facade/f-collaboration.d.ts +17 -0
- package/lib/types/facade/f-event.d.ts +87 -2
- package/lib/types/index.d.ts +5 -3
- package/lib/types/services/collaboration-session/collaboration-session.d.ts +4 -8
- package/lib/types/services/collaboration-session/collaboration-session.service.d.ts +1 -1
- package/lib/types/services/collaboration-ui-event/collaboration-ui-event.d.ts +8 -0
- package/lib/types/services/socket/collaboration-socket.service.d.ts +10 -2
- package/lib/types/services/undoredo/collaborative-undoredo.service.d.ts +5 -0
- package/lib/umd/facade.js +1 -1
- package/lib/umd/index.js +1 -1
- package/lib/umd/locale/ar-SA.js +1 -1
- package/lib/umd/locale/ca-ES.js +1 -1
- package/lib/umd/locale/de-DE.js +1 -1
- package/lib/umd/locale/en-US.js +1 -1
- package/lib/umd/locale/es-ES.js +1 -1
- package/lib/umd/locale/fa-IR.js +1 -1
- package/lib/umd/locale/fr-FR.js +1 -1
- package/lib/umd/locale/id-ID.js +1 -1
- package/lib/umd/locale/it-IT.js +1 -1
- package/lib/umd/locale/ja-JP.js +1 -1
- package/lib/umd/locale/ko-KR.js +1 -1
- package/lib/umd/locale/pl-PL.js +1 -1
- package/lib/umd/locale/pt-BR.js +1 -1
- package/lib/umd/locale/ru-RU.js +1 -1
- package/lib/umd/locale/sk-SK.js +1 -1
- package/lib/umd/locale/vi-VN.js +1 -1
- package/lib/umd/locale/zh-CN.js +1 -1
- package/lib/umd/locale/zh-HK.js +1 -1
- package/lib/umd/locale/zh-TW.js +1 -1
- package/package.json +15 -15
package/lib/locale/zh-TW.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
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 {
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
118
|
-
*
|
|
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
|
|
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
|
-
|
|
209
|
-
private
|
|
210
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
241
|
-
private
|
|
242
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
/**
|
|
319
|
-
|
|
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
|
-
/**
|
|
333
|
-
|
|
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.
|