@fluidframework/container-loader 2.0.0-internal.1.1.2 → 2.0.0-internal.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/dist/catchUpMonitor.d.ts +6 -17
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +5 -36
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js +2 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +8 -11
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +80 -26
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +170 -89
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +22 -11
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +130 -142
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +18 -8
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +10 -24
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +50 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.d.ts +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +18 -6
- package/dist/deltaManager.js.map +1 -1
- package/dist/loader.d.ts +1 -1
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +6 -17
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +5 -35
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js +3 -2
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +9 -14
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +80 -26
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +170 -90
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +22 -11
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +133 -145
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +19 -9
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +10 -24
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +50 -15
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.d.ts +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +18 -6
- package/lib/deltaManager.js.map +1 -1
- package/lib/loader.d.ts +1 -1
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +2 -1
- package/lib/protocol.js.map +1 -1
- package/package.json +13 -13
- package/src/catchUpMonitor.ts +7 -47
- package/src/collabWindowTracker.ts +4 -3
- package/src/connectionManager.ts +9 -11
- package/src/connectionStateHandler.ts +231 -106
- package/src/container.ts +156 -168
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +64 -15
- package/src/deltaManager.ts +20 -7
- package/src/loader.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +2 -1
package/src/containerContext.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import {
|
|
7
|
+
import { LazyPromise } from "@fluidframework/common-utils";
|
|
8
8
|
import {
|
|
9
9
|
IAudience,
|
|
10
10
|
IContainerContext,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ICodeDetailsLoader,
|
|
23
23
|
IFluidModuleWithDetails,
|
|
24
24
|
ISnapshotTreeWithBlobContents,
|
|
25
|
+
IBatchMessage,
|
|
25
26
|
} from "@fluidframework/container-definitions";
|
|
26
27
|
import {
|
|
27
28
|
IRequest,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
ISummaryTree,
|
|
43
44
|
IVersion,
|
|
44
45
|
MessageType,
|
|
46
|
+
ISummaryContent,
|
|
45
47
|
} from "@fluidframework/protocol-definitions";
|
|
46
48
|
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
47
49
|
import { Container } from "./container";
|
|
@@ -59,6 +61,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
59
61
|
quorum: IQuorum,
|
|
60
62
|
loader: ILoader,
|
|
61
63
|
submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
64
|
+
submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
65
|
+
submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
62
66
|
submitSignalFn: (contents: any) => void,
|
|
63
67
|
closeFn: (error?: ICriticalContainerError) => void,
|
|
64
68
|
version: string,
|
|
@@ -76,6 +80,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
76
80
|
quorum,
|
|
77
81
|
loader,
|
|
78
82
|
submitFn,
|
|
83
|
+
submitSummaryFn,
|
|
84
|
+
submitBatchFn,
|
|
79
85
|
submitSignalFn,
|
|
80
86
|
closeFn,
|
|
81
87
|
version,
|
|
@@ -107,8 +113,13 @@ export class ContainerContext implements IContainerContext {
|
|
|
107
113
|
return this.container.clientDetails;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
private _connected: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* When true, ops are free to flow
|
|
119
|
+
* When false, ops should be kept as pending or rejected
|
|
120
|
+
*/
|
|
110
121
|
public get connected(): boolean {
|
|
111
|
-
return this.
|
|
122
|
+
return this._connected;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
public get canSummarize(): boolean {
|
|
@@ -166,6 +177,9 @@ export class ContainerContext implements IContainerContext {
|
|
|
166
177
|
quorum: IQuorum,
|
|
167
178
|
public readonly loader: ILoader,
|
|
168
179
|
public readonly submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
180
|
+
public readonly submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
181
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
182
|
+
public readonly submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
169
183
|
public readonly submitSignalFn: (contents: any) => void,
|
|
170
184
|
public readonly closeFn: (error?: ICriticalContainerError) => void,
|
|
171
185
|
public readonly version: string,
|
|
@@ -174,6 +188,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
174
188
|
public readonly pendingLocalState?: unknown,
|
|
175
189
|
|
|
176
190
|
) {
|
|
191
|
+
this._connected = this.container.connected;
|
|
177
192
|
this._quorum = quorum;
|
|
178
193
|
this.taggedLogger = container.subLogger;
|
|
179
194
|
this._fluidModuleP = new LazyPromise<IFluidModuleWithDetails>(
|
|
@@ -183,9 +198,10 @@ export class ContainerContext implements IContainerContext {
|
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
/**
|
|
186
|
-
* @deprecated
|
|
187
|
-
* ContainerContext should only take an IQuorumClients
|
|
188
|
-
*
|
|
201
|
+
* @deprecated Temporary migratory API, to be removed when customers no longer need it.
|
|
202
|
+
* When removed, `ContainerContext` should only take an {@link @fluidframework/container-definitions#IQuorumClients}
|
|
203
|
+
* rather than an {@link @fluidframework/protocol-definitions#IQuorum}.
|
|
204
|
+
* See {@link @fluidframework/container-definitions#IContainerContext} for more details.
|
|
189
205
|
*/
|
|
190
206
|
public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
|
|
191
207
|
return (this._quorum.get("code") ?? this._quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
@@ -223,9 +239,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
223
239
|
|
|
224
240
|
public setConnectionState(connected: boolean, clientId?: string) {
|
|
225
241
|
const runtime = this.runtime;
|
|
226
|
-
|
|
227
|
-
assert(connected === this.connected, 0x0de /* "Mismatch in connection state while setting" */);
|
|
228
|
-
|
|
242
|
+
this._connected = connected;
|
|
229
243
|
runtime.setConnectionState(connected, clientId);
|
|
230
244
|
}
|
|
231
245
|
|
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
6
|
+
import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
+
import { assert } from "@fluidframework/common-utils";
|
|
7
8
|
import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions";
|
|
8
9
|
import {
|
|
9
10
|
FetchSource,
|
|
11
|
+
IDocumentService,
|
|
10
12
|
IDocumentStorageService,
|
|
11
13
|
IDocumentStorageServicePolicies,
|
|
12
14
|
ISummaryContext,
|
|
13
15
|
} from "@fluidframework/driver-definitions";
|
|
16
|
+
import { UsageError } from "@fluidframework/driver-utils";
|
|
14
17
|
import {
|
|
15
18
|
ICreateBlobResponse,
|
|
16
19
|
ISnapshotTree,
|
|
@@ -19,14 +22,52 @@ import {
|
|
|
19
22
|
IVersion,
|
|
20
23
|
} from "@fluidframework/protocol-definitions";
|
|
21
24
|
import { IDetachedBlobStorage } from "./loader";
|
|
25
|
+
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
26
|
+
import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* This class wraps the actual storage and make sure no wrong apis are called according to
|
|
25
30
|
* container attach state.
|
|
26
31
|
*/
|
|
27
|
-
export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
32
|
+
export class ContainerStorageAdapter implements IDocumentStorageService, IDisposable {
|
|
28
33
|
private readonly blobContents: { [id: string]: ArrayBufferLike; } = {};
|
|
29
|
-
|
|
34
|
+
private _storageService: IDocumentStorageService & Partial<IDisposable>;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
detachedBlobStorage: IDetachedBlobStorage | undefined,
|
|
38
|
+
private readonly logger: ITelemetryLogger,
|
|
39
|
+
private readonly captureProtocolSummary?: () => ISummaryTree,
|
|
40
|
+
) {
|
|
41
|
+
this._storageService = new BlobOnlyStorage(detachedBlobStorage, logger);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
disposed: boolean = false;
|
|
45
|
+
dispose(error?: Error): void {
|
|
46
|
+
this._storageService?.dispose?.(error);
|
|
47
|
+
this.disposed = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async connectToService(service: IDocumentService): Promise<void> {
|
|
51
|
+
if (!(this._storageService instanceof BlobOnlyStorage)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const storageService = await service.connectToStorage();
|
|
56
|
+
const retriableStorage = this._storageService =
|
|
57
|
+
new RetriableDocumentStorageService(
|
|
58
|
+
storageService,
|
|
59
|
+
this.logger);
|
|
60
|
+
|
|
61
|
+
if (this.captureProtocolSummary !== undefined) {
|
|
62
|
+
this.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
63
|
+
this._storageService =
|
|
64
|
+
new ProtocolTreeStorageService(retriableStorage, this.captureProtocolSummary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ensure we did not lose that policy in the process of wrapping
|
|
68
|
+
assert(storageService.policies?.minBlobSize === this._storageService.policies?.minBlobSize,
|
|
69
|
+
0x0e0 /* "lost minBlobSize policy" */);
|
|
70
|
+
}
|
|
30
71
|
|
|
31
72
|
public loadSnapshotForRehydratingContainer(snapshotTree: ISnapshotTreeWithBlobContents) {
|
|
32
73
|
this.getBlobContents(snapshotTree);
|
|
@@ -45,17 +86,17 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
45
86
|
// back-compat 0.40 containerRuntime requests policies even in detached container if storage is present
|
|
46
87
|
// and storage is always present in >=0.41.
|
|
47
88
|
try {
|
|
48
|
-
return this.
|
|
89
|
+
return this._storageService.policies;
|
|
49
90
|
} catch (e) {}
|
|
50
91
|
return undefined;
|
|
51
92
|
}
|
|
52
93
|
|
|
53
94
|
public get repositoryUrl(): string {
|
|
54
|
-
return this.
|
|
95
|
+
return this._storageService.repositoryUrl;
|
|
55
96
|
}
|
|
56
97
|
|
|
57
98
|
public async getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null> {
|
|
58
|
-
return this.
|
|
99
|
+
return this._storageService.getSnapshotTree(version, scenarioName);
|
|
59
100
|
}
|
|
60
101
|
|
|
61
102
|
public async readBlob(id: string): Promise<ArrayBufferLike> {
|
|
@@ -63,7 +104,7 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
63
104
|
if (blob !== undefined) {
|
|
64
105
|
return blob;
|
|
65
106
|
}
|
|
66
|
-
return this.
|
|
107
|
+
return this._storageService.readBlob(id);
|
|
67
108
|
}
|
|
68
109
|
|
|
69
110
|
public async getVersions(
|
|
@@ -72,19 +113,19 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
72
113
|
scenarioName?: string,
|
|
73
114
|
fetchSource?: FetchSource,
|
|
74
115
|
): Promise<IVersion[]> {
|
|
75
|
-
return this.
|
|
116
|
+
return this._storageService.getVersions(versionId, count, scenarioName, fetchSource);
|
|
76
117
|
}
|
|
77
118
|
|
|
78
119
|
public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
|
|
79
|
-
return this.
|
|
120
|
+
return this._storageService.uploadSummaryWithContext(summary, context);
|
|
80
121
|
}
|
|
81
122
|
|
|
82
123
|
public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
|
|
83
|
-
return this.
|
|
124
|
+
return this._storageService.downloadSummary(handle);
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
87
|
-
return this.
|
|
128
|
+
return this._storageService.createBlob(file);
|
|
88
129
|
}
|
|
89
130
|
}
|
|
90
131
|
|
|
@@ -92,18 +133,25 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
92
133
|
* Storage which only supports createBlob() and readBlob(). This is used with IDetachedBlobStorage to support
|
|
93
134
|
* blobs in detached containers.
|
|
94
135
|
*/
|
|
95
|
-
|
|
136
|
+
class BlobOnlyStorage implements IDocumentStorageService {
|
|
96
137
|
constructor(
|
|
97
|
-
private readonly
|
|
138
|
+
private readonly detachedStorage: IDetachedBlobStorage | undefined,
|
|
98
139
|
private readonly logger: ITelemetryLogger,
|
|
99
140
|
) { }
|
|
100
141
|
|
|
101
142
|
public async createBlob(content: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
102
|
-
return this.
|
|
143
|
+
return this.verifyStorage().createBlob(content);
|
|
103
144
|
}
|
|
104
145
|
|
|
105
146
|
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
106
|
-
return this.
|
|
147
|
+
return this.verifyStorage().readBlob(blobId);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private verifyStorage(): IDetachedBlobStorage {
|
|
151
|
+
if (this.detachedStorage === undefined) {
|
|
152
|
+
throw new UsageError("Real storage calls not allowed in Unattached container");
|
|
153
|
+
}
|
|
154
|
+
return this.detachedStorage;
|
|
107
155
|
}
|
|
108
156
|
|
|
109
157
|
public get policies(): IDocumentStorageServicePolicies | undefined {
|
|
@@ -123,6 +171,7 @@ export class BlobOnlyStorage implements IDocumentStorageService {
|
|
|
123
171
|
/* eslint-enable @typescript-eslint/unbound-method */
|
|
124
172
|
|
|
125
173
|
private notCalled(): never {
|
|
174
|
+
this.verifyStorage();
|
|
126
175
|
try {
|
|
127
176
|
// some browsers may not populate stack unless exception is thrown
|
|
128
177
|
throw new Error("BlobOnlyStorage not implemented method used");
|
package/src/deltaManager.ts
CHANGED
|
@@ -199,12 +199,12 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
199
199
|
public get readOnlyInfo() { return this.connectionManager.readOnlyInfo; }
|
|
200
200
|
public get clientDetails() { return this.connectionManager.clientDetails; }
|
|
201
201
|
|
|
202
|
-
public submit(type: MessageType, contents
|
|
202
|
+
public submit(type: MessageType, contents?: string, batch = false, metadata?: any) {
|
|
203
203
|
if (this.currentlyProcessingOps && this.preventConcurrentOpSend) {
|
|
204
204
|
this.close(new UsageError("Making changes to data model is disallowed while processing ops."));
|
|
205
205
|
}
|
|
206
206
|
const messagePartial: Omit<IDocumentMessage, "clientSequenceNumber"> = {
|
|
207
|
-
contents
|
|
207
|
+
contents,
|
|
208
208
|
metadata,
|
|
209
209
|
referenceSequenceNumber: this.lastProcessedSequenceNumber,
|
|
210
210
|
type,
|
|
@@ -218,7 +218,9 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
218
218
|
return -1;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
if (contents !== undefined) {
|
|
222
|
+
this.opsSize += contents.length;
|
|
223
|
+
}
|
|
222
224
|
|
|
223
225
|
this.messageBuffer.push(message);
|
|
224
226
|
|
|
@@ -233,15 +235,26 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
233
235
|
public submitSignal(content: any) { return this.connectionManager.submitSignal(content); }
|
|
234
236
|
|
|
235
237
|
public flush() {
|
|
236
|
-
|
|
238
|
+
const batch = this.messageBuffer;
|
|
239
|
+
if (batch.length === 0) {
|
|
237
240
|
return;
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
this.messageBuffer = [];
|
|
244
|
+
|
|
240
245
|
// The prepareFlush event allows listeners to append metadata to the batch prior to submission.
|
|
241
|
-
this.emit("prepareSend",
|
|
246
|
+
this.emit("prepareSend", batch);
|
|
242
247
|
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
if (batch.length === 1) {
|
|
249
|
+
assert(batch[0].metadata?.batch === undefined, 0x3c9 /* no batch markup on single message */);
|
|
250
|
+
} else {
|
|
251
|
+
assert(batch[0].metadata?.batch === true, 0x3ca /* no start batch markup */);
|
|
252
|
+
assert(batch[batch.length - 1].metadata?.batch === false, 0x3cb /* no end batch markup */);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.connectionManager.sendMessages(batch);
|
|
256
|
+
|
|
257
|
+
assert(this.messageBuffer.length === 0, 0x3cc /* reentrancy */);
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
public get connectionProps(): ITelemetryProperties {
|
package/src/loader.ts
CHANGED
|
@@ -133,7 +133,7 @@ export interface ILoaderOptions extends ILoaderOptions1 {
|
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* @deprecated IFluidModuleWithDetails interface is moved to
|
|
136
|
-
* {@link @fluidframework/container-
|
|
136
|
+
* {@link @fluidframework/container-definitions#IFluidModuleWithDetails}
|
|
137
137
|
* to have all the code loading modules in one package. #8193
|
|
138
138
|
* Encapsulates a module entry point with corresponding code details.
|
|
139
139
|
*/
|
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
ISignalMessage,
|
|
19
19
|
MessageType,
|
|
20
20
|
} from "@fluidframework/protocol-definitions";
|
|
21
|
+
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Function to be used for creating a protocol handler.
|
|
@@ -68,7 +69,7 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
68
69
|
throw new Error("Remote message's clientId is missing from the quorum");
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
if (client?.shouldHaveLeft === true && message
|
|
72
|
+
if (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {
|
|
72
73
|
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
73
74
|
throw new Error("Remote message's clientId already should have left");
|
|
74
75
|
}
|