@fluidframework/container-loader 2.0.0-internal.6.0.0 → 2.0.0-internal.6.1.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 (59) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/connectionManager.d.ts +3 -3
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +33 -24
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +14 -14
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +17 -12
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts.map +1 -1
  11. package/dist/container.js +26 -38
  12. package/dist/container.js.map +1 -1
  13. package/dist/contracts.d.ts +11 -7
  14. package/dist/contracts.d.ts.map +1 -1
  15. package/dist/contracts.js.map +1 -1
  16. package/dist/deltaManager.d.ts +4 -4
  17. package/dist/deltaManager.d.ts.map +1 -1
  18. package/dist/deltaManager.js +5 -4
  19. package/dist/deltaManager.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/protocol.d.ts +4 -2
  24. package/dist/protocol.d.ts.map +1 -1
  25. package/dist/protocol.js +23 -1
  26. package/dist/protocol.js.map +1 -1
  27. package/lib/connectionManager.d.ts +3 -3
  28. package/lib/connectionManager.d.ts.map +1 -1
  29. package/lib/connectionManager.js +33 -24
  30. package/lib/connectionManager.js.map +1 -1
  31. package/lib/connectionStateHandler.d.ts +14 -14
  32. package/lib/connectionStateHandler.d.ts.map +1 -1
  33. package/lib/connectionStateHandler.js +17 -12
  34. package/lib/connectionStateHandler.js.map +1 -1
  35. package/lib/container.d.ts.map +1 -1
  36. package/lib/container.js +27 -39
  37. package/lib/container.js.map +1 -1
  38. package/lib/contracts.d.ts +11 -7
  39. package/lib/contracts.d.ts.map +1 -1
  40. package/lib/contracts.js.map +1 -1
  41. package/lib/deltaManager.d.ts +4 -4
  42. package/lib/deltaManager.d.ts.map +1 -1
  43. package/lib/deltaManager.js +5 -4
  44. package/lib/deltaManager.js.map +1 -1
  45. package/lib/packageVersion.d.ts +1 -1
  46. package/lib/packageVersion.js +1 -1
  47. package/lib/packageVersion.js.map +1 -1
  48. package/lib/protocol.d.ts +4 -2
  49. package/lib/protocol.d.ts.map +1 -1
  50. package/lib/protocol.js +23 -1
  51. package/lib/protocol.js.map +1 -1
  52. package/package.json +13 -17
  53. package/src/connectionManager.ts +46 -31
  54. package/src/connectionStateHandler.ts +28 -36
  55. package/src/container.ts +46 -51
  56. package/src/contracts.ts +12 -6
  57. package/src/deltaManager.ts +19 -13
  58. package/src/packageVersion.ts +1 -1
  59. package/src/protocol.ts +33 -1
package/src/container.ts CHANGED
@@ -40,17 +40,16 @@ import {
40
40
  IProvideFluidCodeDetailsComparer,
41
41
  IFluidCodeDetailsComparer,
42
42
  IRuntime,
43
- isFluidCodeDetails,
44
- IThrottlingWarning,
45
43
  ReadOnlyInfo,
44
+ isFluidCodeDetails,
46
45
  } from "@fluidframework/container-definitions";
47
46
  import { GenericError, UsageError } from "@fluidframework/container-utils";
48
47
  import {
49
- IAnyDriverError,
50
48
  IDocumentService,
51
49
  IDocumentServiceFactory,
52
50
  IDocumentStorageService,
53
51
  IResolvedUrl,
52
+ IThrottlingWarning,
54
53
  IUrlResolver,
55
54
  } from "@fluidframework/driver-definitions";
56
55
  import {
@@ -60,7 +59,6 @@ import {
60
59
  runWithRetry,
61
60
  isCombinedAppAndProtocolSummary,
62
61
  MessageType2,
63
- canBeCoalescedByService,
64
62
  } from "@fluidframework/driver-utils";
65
63
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
66
64
  import {
@@ -102,6 +100,7 @@ import {
102
100
  IConnectionManagerFactoryArgs,
103
101
  getPackageName,
104
102
  IConnectionDetailsInternal,
103
+ IConnectionStateChangeReason,
105
104
  } from "./contracts";
106
105
  import { DeltaManager, IConnectionArgs } from "./deltaManager";
107
106
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
@@ -761,7 +760,19 @@ export class Container
761
760
  this.scope = scope;
762
761
  this.detachedBlobStorage = detachedBlobStorage;
763
762
  this.protocolHandlerBuilder =
764
- protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
763
+ protocolHandlerBuilder ??
764
+ ((
765
+ attributes: IDocumentAttributes,
766
+ quorumSnapshot: IQuorumSnapshot,
767
+ sendProposal: (key: string, value: any) => number,
768
+ ) =>
769
+ new ProtocolHandler(
770
+ attributes,
771
+ quorumSnapshot,
772
+ sendProposal,
773
+ new Audience(),
774
+ (clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
775
+ ));
765
776
 
766
777
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
767
778
  this.clone = async (
@@ -827,11 +838,11 @@ export class Container
827
838
  this.connectionStateHandler = createConnectionStateHandler(
828
839
  {
829
840
  logger: this.mc.logger,
830
- connectionStateChanged: (value, oldState, reason, error) => {
841
+ connectionStateChanged: (value, oldState, reason) => {
831
842
  if (value === ConnectionState.Connected) {
832
843
  this._clientId = this.connectionStateHandler.pendingClientId;
833
844
  }
834
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
845
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
835
846
  if (this._lifecycleState === "loaded") {
836
847
  this.propagateConnectionState(
837
848
  false /* initial transition */,
@@ -873,8 +884,9 @@ export class Container
873
884
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
874
885
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
875
886
  if (mode === "read") {
876
- this.disconnect();
877
- this.connect();
887
+ const reason = { text: "NoJoinSignal" };
888
+ this.disconnectInternal(reason);
889
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
878
890
  }
879
891
  },
880
892
  clientShouldHaveLeft: (clientId: string) => {
@@ -1071,7 +1083,7 @@ export class Container
1071
1083
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
1072
1084
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
1073
1085
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
1074
- this.disconnect(); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1086
+ this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1075
1087
  const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
1076
1088
  this.close();
1077
1089
  return pendingState;
@@ -1267,7 +1279,7 @@ export class Container
1267
1279
  if (!this.closed) {
1268
1280
  this.resumeInternal({
1269
1281
  fetchOpsFromStorage: false,
1270
- reason: "createDetached",
1282
+ reason: { text: "createDetached" },
1271
1283
  });
1272
1284
  }
1273
1285
  } catch (error) {
@@ -1291,7 +1303,7 @@ export class Container
1291
1303
  );
1292
1304
  }
1293
1305
 
1294
- private setAutoReconnectInternal(mode: ReconnectMode) {
1306
+ private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
1295
1307
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
1296
1308
 
1297
1309
  if (currentMode === mode) {
@@ -1310,7 +1322,7 @@ export class Container
1310
1322
  duration,
1311
1323
  });
1312
1324
 
1313
- this._deltaManager.connectionManager.setAutoReconnect(mode);
1325
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
1314
1326
  }
1315
1327
 
1316
1328
  public connect() {
@@ -1322,7 +1334,10 @@ export class Container
1322
1334
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
1323
1335
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
1324
1336
  // assuming that connect() is called quickly after initial container boot.
1325
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
1337
+ this.connectInternal({
1338
+ reason: { text: "DocumentConnect" },
1339
+ fetchOpsFromStorage: false,
1340
+ });
1326
1341
  }
1327
1342
  }
1328
1343
 
@@ -1338,23 +1353,23 @@ export class Container
1338
1353
 
1339
1354
  // Set Auto Reconnect Mode
1340
1355
  const mode = ReconnectMode.Enabled;
1341
- this.setAutoReconnectInternal(mode);
1356
+ this.setAutoReconnectInternal(mode, args.reason);
1342
1357
  }
1343
1358
 
1344
1359
  public disconnect() {
1345
1360
  if (this.closed) {
1346
1361
  throw new UsageError(`The Container is closed and cannot be disconnected`);
1347
1362
  } else {
1348
- this.disconnectInternal();
1363
+ this.disconnectInternal({ text: "DocumentDisconnect" });
1349
1364
  }
1350
1365
  }
1351
1366
 
1352
- private disconnectInternal() {
1367
+ private disconnectInternal(reason: IConnectionStateChangeReason) {
1353
1368
  assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
1354
1369
 
1355
1370
  // Set Auto Reconnect Mode
1356
1371
  const mode = ReconnectMode.Disabled;
1357
- this.setAutoReconnectInternal(mode);
1372
+ this.setAutoReconnectInternal(mode, reason);
1358
1373
  }
1359
1374
 
1360
1375
  private resumeInternal(args: IConnectionArgs) {
@@ -1506,7 +1521,7 @@ export class Container
1506
1521
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
1507
1522
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
1508
1523
  const connectionArgs: IConnectionArgs = {
1509
- reason: "DocumentOpen",
1524
+ reason: { text: "DocumentOpen" },
1510
1525
  mode: "write",
1511
1526
  fetchOpsFromStorage: false,
1512
1527
  };
@@ -2009,18 +2024,18 @@ export class Container
2009
2024
  this.connectionStateHandler.receivedConnectEvent(details);
2010
2025
  });
2011
2026
 
2012
- deltaManager.on("establishingConnection", (reason: string) => {
2027
+ deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
2013
2028
  this.connectionStateHandler.establishingConnection(reason);
2014
2029
  });
2015
2030
 
2016
- deltaManager.on("cancelEstablishingConnection", (reason: string) => {
2031
+ deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
2017
2032
  this.connectionStateHandler.cancelEstablishingConnection(reason);
2018
2033
  });
2019
2034
 
2020
- deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
2035
+ deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
2021
2036
  this.noopHeuristic?.notifyDisconnect();
2022
2037
  if (!this.closed) {
2023
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
2038
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
2024
2039
  }
2025
2040
  });
2026
2041
 
@@ -2073,8 +2088,7 @@ export class Container
2073
2088
  private logConnectionStateChangeTelemetry(
2074
2089
  value: ConnectionState,
2075
2090
  oldState: ConnectionState,
2076
- reason?: string,
2077
- error?: IAnyDriverError,
2091
+ reason?: IConnectionStateChangeReason,
2078
2092
  ) {
2079
2093
  // Log actual event
2080
2094
  const time = performance.now();
@@ -2113,7 +2127,7 @@ export class Container
2113
2127
  from: ConnectionState[oldState],
2114
2128
  duration,
2115
2129
  durationFromDisconnected,
2116
- reason,
2130
+ reason: reason?.text,
2117
2131
  connectionInitiationReason,
2118
2132
  pendingClientId: this.connectionStateHandler.pendingClientId,
2119
2133
  clientId: this.clientId,
@@ -2129,7 +2143,7 @@ export class Container
2129
2143
  isDirty: this.isDirty,
2130
2144
  ...this._deltaManager.connectionProps,
2131
2145
  },
2132
- error,
2146
+ reason?.error,
2133
2147
  );
2134
2148
 
2135
2149
  if (value === ConnectionState.Connected) {
@@ -2137,7 +2151,10 @@ export class Container
2137
2151
  }
2138
2152
  }
2139
2153
 
2140
- private propagateConnectionState(initialTransition: boolean, disconnectedReason?: string) {
2154
+ private propagateConnectionState(
2155
+ initialTransition: boolean,
2156
+ disconnectedReason?: IConnectionStateChangeReason,
2157
+ ) {
2141
2158
  // When container loaded, we want to propagate initial connection state.
2142
2159
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
2143
2160
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -2154,7 +2171,7 @@ export class Container
2154
2171
 
2155
2172
  this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
2156
2173
  this.protocolHandler.setConnectionState(state, this.clientId);
2157
- raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
2174
+ raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
2158
2175
  }
2159
2176
 
2160
2177
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
@@ -2247,28 +2264,6 @@ export class Container
2247
2264
  }
2248
2265
  const local = this.clientId === message.clientId;
2249
2266
 
2250
- // Check and report if we're getting messages from a clientId that we previously
2251
- // flagged should have left, or from a client that's not in the quorum but should be
2252
- if (message.clientId != null) {
2253
- const client = this.protocolHandler.quorum.getMember(message.clientId);
2254
-
2255
- if (client === undefined && message.type !== MessageType.ClientJoin) {
2256
- // pre-0.58 error message: messageClientIdMissingFromQuorum
2257
- throw new Error("Remote message's clientId is missing from the quorum");
2258
- }
2259
-
2260
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
2261
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
2262
- // document we don't need to blow up aggressively.
2263
- if (
2264
- this.clientsWhoShouldHaveLeft.has(message.clientId) &&
2265
- !canBeCoalescedByService(message)
2266
- ) {
2267
- // pre-0.58 error message: messageClientIdShouldHaveLeft
2268
- throw new Error("Remote message's clientId already should have left");
2269
- }
2270
- }
2271
-
2272
2267
  // Allow the protocol handler to process the message
2273
2268
  const result = this.protocolHandler.processMessage(message, local);
2274
2269
 
package/src/contracts.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  IConnectionDetails,
9
9
  ICriticalContainerError,
10
10
  IDeltaQueue,
11
+ IErrorBase,
11
12
  IFluidCodeDetails,
12
13
  isFluidPackage,
13
14
  ReadOnlyInfo,
@@ -21,7 +22,7 @@ import {
21
22
  ISignalClient,
22
23
  ISignalMessage,
23
24
  } from "@fluidframework/protocol-definitions";
24
- import { IAnyDriverError, IContainerPackageInfo } from "@fluidframework/driver-definitions";
25
+ import { IContainerPackageInfo } from "@fluidframework/driver-definitions";
25
26
 
26
27
  export enum ReconnectMode {
27
28
  Never = "Never",
@@ -29,6 +30,11 @@ export enum ReconnectMode {
29
30
  Enabled = "Enabled",
30
31
  }
31
32
 
33
+ export interface IConnectionStateChangeReason<T extends IErrorBase = IErrorBase> {
34
+ text: string;
35
+ error?: T;
36
+ }
37
+
32
38
  /**
33
39
  * Internal version of IConnectionDetails with props are only exposed internally
34
40
  */
@@ -36,7 +42,7 @@ export interface IConnectionDetailsInternal extends IConnectionDetails {
36
42
  mode: ConnectionMode;
37
43
  version: string;
38
44
  initialClients: ISignalClient[];
39
- reason: string;
45
+ reason: IConnectionStateChangeReason;
40
46
  }
41
47
 
42
48
  /**
@@ -106,7 +112,7 @@ export interface IConnectionManager {
106
112
  /**
107
113
  * Initiates connection to relay service (noop if already connected).
108
114
  */
109
- connect(reason: string, connectionMode?: ConnectionMode): void;
115
+ connect(reason: IConnectionStateChangeReason, connectionMode?: ConnectionMode): void;
110
116
 
111
117
  /**
112
118
  * Disposed connection manager
@@ -150,7 +156,7 @@ export interface IConnectionManagerFactoryArgs {
150
156
  /**
151
157
  * Called whenever connection to relay service is lost.
152
158
  */
153
- readonly disconnectHandler: (reason: string, error?: IAnyDriverError) => void;
159
+ readonly disconnectHandler: (reason: IConnectionStateChangeReason) => void;
154
160
 
155
161
  /**
156
162
  * Called whenever new connection to rely service is established
@@ -180,12 +186,12 @@ export interface IConnectionManagerFactoryArgs {
180
186
  /**
181
187
  * Called whenever we try to start establishing a new connection.
182
188
  */
183
- readonly establishConnectionHandler: (reason: string) => void;
189
+ readonly establishConnectionHandler: (reason: IConnectionStateChangeReason) => void;
184
190
 
185
191
  /**
186
192
  * Called whenever we cancel the connection in progress.
187
193
  */
188
- readonly cancelConnectionHandler: (reason: string) => void;
194
+ readonly cancelConnectionHandler: (reason: IConnectionStateChangeReason) => void;
189
195
  }
190
196
 
191
197
  /**
@@ -25,7 +25,6 @@ import {
25
25
  IDocumentDeltaStorageService,
26
26
  IDocumentService,
27
27
  DriverErrorType,
28
- IAnyDriverError,
29
28
  } from "@fluidframework/driver-definitions";
30
29
  import {
31
30
  IDocumentMessage,
@@ -46,6 +45,7 @@ import {
46
45
  IConnectionDetailsInternal,
47
46
  IConnectionManager,
48
47
  IConnectionManagerFactoryArgs,
48
+ IConnectionStateChangeReason,
49
49
  } from "./contracts";
50
50
  import { DeltaQueue } from "./deltaQueue";
51
51
  import { OnlyValidTermValue } from "./protocol";
@@ -53,7 +53,7 @@ import { OnlyValidTermValue } from "./protocol";
53
53
  export interface IConnectionArgs {
54
54
  mode?: ConnectionMode;
55
55
  fetchOpsFromStorage?: boolean;
56
- reason: string;
56
+ reason: IConnectionStateChangeReason;
57
57
  }
58
58
 
59
59
  /**
@@ -64,8 +64,11 @@ export interface IDeltaManagerInternalEvents extends IDeltaManagerEvents {
64
64
  (event: "throttled", listener: (error: IThrottlingWarning) => void);
65
65
  (event: "closed" | "disposed", listener: (error?: ICriticalContainerError) => void);
66
66
  (event: "connect", listener: (details: IConnectionDetailsInternal, opsBehind?: number) => void);
67
- (event: "establishingConnection", listener: (reason: string) => void);
68
- (event: "cancelEstablishingConnection", listener: (reason: string) => void);
67
+ (event: "establishingConnection", listener: (reason: IConnectionStateChangeReason) => void);
68
+ (
69
+ event: "cancelEstablishingConnection",
70
+ listener: (reason: IConnectionStateChangeReason) => void,
71
+ );
69
72
  }
70
73
 
71
74
  /**
@@ -347,6 +350,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
347
350
  return {
348
351
  sequenceNumber: this.lastSequenceNumber,
349
352
  opsSize: this.opsSize > 0 ? this.opsSize : undefined,
353
+ deltaManagerState: this._disposed ? "disposed" : this._closed ? "closed" : "open",
350
354
  ...this.connectionManager.connectionProps,
351
355
  };
352
356
  }
@@ -400,15 +404,17 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
400
404
  reconnectionDelayHandler: (delayMs: number, error: unknown) =>
401
405
  this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
402
406
  closeHandler: (error: any) => this.close(error),
403
- disconnectHandler: (reason: string, error?: IAnyDriverError) =>
404
- this.disconnectHandler(reason, error),
407
+ disconnectHandler: (reason: IConnectionStateChangeReason) =>
408
+ this.disconnectHandler(reason),
405
409
  connectHandler: (connection: IConnectionDetailsInternal) =>
406
410
  this.connectHandler(connection),
407
411
  pongHandler: (latency: number) => this.emit("pong", latency),
408
412
  readonlyChangeHandler: (readonly?: boolean) =>
409
413
  safeRaiseEvent(this, this.logger, "readonly", readonly),
410
- establishConnectionHandler: (reason: string) => this.establishingConnection(reason),
411
- cancelConnectionHandler: (reason: string) => this.cancelEstablishingConnection(reason),
414
+ establishConnectionHandler: (reason: IConnectionStateChangeReason) =>
415
+ this.establishingConnection(reason),
416
+ cancelConnectionHandler: (reason: IConnectionStateChangeReason) =>
417
+ this.cancelEstablishingConnection(reason),
412
418
  };
413
419
 
414
420
  this.connectionManager = createConnectionManager(props);
@@ -448,11 +454,11 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
448
454
  // - inbound & inboundSignal are resumed in attachOpHandler() when we have handler setup
449
455
  }
450
456
 
451
- private cancelEstablishingConnection(reason: string) {
457
+ private cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
452
458
  this.emit("cancelEstablishingConnection", reason);
453
459
  }
454
460
 
455
- private establishingConnection(reason: string) {
461
+ private establishingConnection(reason: IConnectionStateChangeReason) {
456
462
  this.emit("establishingConnection", reason);
457
463
  }
458
464
 
@@ -586,7 +592,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
586
592
  // on the wire, we might be always behind.
587
593
  // See comment at the end of "connect" handler
588
594
  if (fetchOpsFromStorage) {
589
- this.fetchMissingDeltas(args.reason);
595
+ this.fetchMissingDeltas(args.reason.text);
590
596
  }
591
597
 
592
598
  this.connectionManager.connect(args.reason, args.mode);
@@ -772,9 +778,9 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
772
778
  }
773
779
  }
774
780
 
775
- private disconnectHandler(reason: string, error?: IAnyDriverError) {
781
+ private disconnectHandler(reason: IConnectionStateChangeReason) {
776
782
  this.messageBuffer.length = 0;
777
- this.emit("disconnect", reason, error);
783
+ this.emit("disconnect", reason);
778
784
  }
779
785
 
780
786
  /**
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.6.0.0";
9
+ export const pkgVersion = "2.0.0-internal.6.1.0";
package/src/protocol.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IAudienceOwner } from "@fluidframework/container-definitions";
7
+ import { canBeCoalescedByService } from "@fluidframework/driver-utils";
7
8
  import {
8
9
  IProtocolHandler as IBaseProtocolHandler,
9
10
  IQuorumSnapshot,
@@ -11,8 +12,12 @@ import {
11
12
  } from "@fluidframework/protocol-base";
12
13
  import {
13
14
  IDocumentAttributes,
15
+ IProcessMessageResult,
16
+ ISequencedClient,
17
+ ISequencedDocumentMessage,
14
18
  ISignalClient,
15
19
  ISignalMessage,
20
+ MessageType,
16
21
  } from "@fluidframework/protocol-definitions";
17
22
 
18
23
  // "term" was an experimental feature that is being removed. The only safe value to use is 1.
@@ -44,7 +49,8 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
44
49
  attributes: IDocumentAttributes,
45
50
  quorumSnapshot: IQuorumSnapshot,
46
51
  sendProposal: (key: string, value: any) => number,
47
- readonly audience: IAudienceOwner,
52
+ public readonly audience: IAudienceOwner,
53
+ private readonly shouldClientHaveLeft: (clientId: string) => boolean,
48
54
  ) {
49
55
  super(
50
56
  attributes.minimumSequenceNumber,
@@ -65,6 +71,32 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
65
71
  }
66
72
  }
67
73
 
74
+ public processMessage(
75
+ message: ISequencedDocumentMessage,
76
+ local: boolean,
77
+ ): IProcessMessageResult {
78
+ const client: ISequencedClient | undefined = this.quorum.getMember(message.clientId);
79
+
80
+ // Check and report if we're getting messages from a clientId that we previously
81
+ // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
82
+ if (message.clientId != null) {
83
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
84
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
85
+ throw new Error("Remote message's clientId is missing from the quorum");
86
+ }
87
+
88
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
89
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
90
+ // document we don't need to blow up aggressively.
91
+ if (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {
92
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
93
+ throw new Error("Remote message's clientId already should have left");
94
+ }
95
+ }
96
+
97
+ return super.processMessage(message, local);
98
+ }
99
+
68
100
  public processSignal(message: ISignalMessage) {
69
101
  const innerContent = message.content as { content: any; type: string };
70
102
  switch (innerContent.type) {