@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.1.0.148229

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 (112) hide show
  1. package/README.md +7 -4
  2. package/closeAndGetPendingLocalState.md +51 -0
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +43 -11
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +4 -4
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +7 -0
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts +44 -4
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +152 -93
  13. package/dist/container.js.map +1 -1
  14. package/dist/containerContext.d.ts +18 -8
  15. package/dist/containerContext.d.ts.map +1 -1
  16. package/dist/containerContext.js +47 -4
  17. package/dist/containerContext.js.map +1 -1
  18. package/dist/containerStorageAdapter.d.ts +41 -2
  19. package/dist/containerStorageAdapter.d.ts.map +1 -1
  20. package/dist/containerStorageAdapter.js +87 -11
  21. package/dist/containerStorageAdapter.js.map +1 -1
  22. package/dist/contracts.d.ts +2 -2
  23. package/dist/contracts.d.ts.map +1 -1
  24. package/dist/contracts.js.map +1 -1
  25. package/dist/deltaManager.d.ts +3 -2
  26. package/dist/deltaManager.d.ts.map +1 -1
  27. package/dist/deltaManager.js +4 -2
  28. package/dist/deltaManager.js.map +1 -1
  29. package/dist/deltaManagerProxy.d.ts +10 -22
  30. package/dist/deltaManagerProxy.d.ts.map +1 -1
  31. package/dist/deltaManagerProxy.js +14 -50
  32. package/dist/deltaManagerProxy.js.map +1 -1
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -3
  36. package/dist/index.js.map +1 -1
  37. package/dist/loader.d.ts +10 -1
  38. package/dist/loader.d.ts.map +1 -1
  39. package/dist/loader.js +22 -16
  40. package/dist/loader.js.map +1 -1
  41. package/dist/packageVersion.d.ts +1 -1
  42. package/dist/packageVersion.js +1 -1
  43. package/dist/packageVersion.js.map +1 -1
  44. package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
  45. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  46. package/dist/protocolTreeDocumentStorageService.js +7 -4
  47. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/utils.js +2 -1
  50. package/dist/utils.js.map +1 -1
  51. package/lib/connectionManager.d.ts.map +1 -1
  52. package/lib/connectionManager.js +44 -12
  53. package/lib/connectionManager.js.map +1 -1
  54. package/lib/connectionStateHandler.d.ts +4 -4
  55. package/lib/connectionStateHandler.d.ts.map +1 -1
  56. package/lib/connectionStateHandler.js +7 -0
  57. package/lib/connectionStateHandler.js.map +1 -1
  58. package/lib/container.d.ts +44 -4
  59. package/lib/container.d.ts.map +1 -1
  60. package/lib/container.js +155 -96
  61. package/lib/container.js.map +1 -1
  62. package/lib/containerContext.d.ts +18 -8
  63. package/lib/containerContext.d.ts.map +1 -1
  64. package/lib/containerContext.js +48 -5
  65. package/lib/containerContext.js.map +1 -1
  66. package/lib/containerStorageAdapter.d.ts +41 -2
  67. package/lib/containerStorageAdapter.d.ts.map +1 -1
  68. package/lib/containerStorageAdapter.js +85 -11
  69. package/lib/containerStorageAdapter.js.map +1 -1
  70. package/lib/contracts.d.ts +2 -2
  71. package/lib/contracts.d.ts.map +1 -1
  72. package/lib/contracts.js.map +1 -1
  73. package/lib/deltaManager.d.ts +3 -2
  74. package/lib/deltaManager.d.ts.map +1 -1
  75. package/lib/deltaManager.js +4 -2
  76. package/lib/deltaManager.js.map +1 -1
  77. package/lib/deltaManagerProxy.d.ts +10 -22
  78. package/lib/deltaManagerProxy.d.ts.map +1 -1
  79. package/lib/deltaManagerProxy.js +14 -50
  80. package/lib/deltaManagerProxy.js.map +1 -1
  81. package/lib/index.d.ts +3 -2
  82. package/lib/index.d.ts.map +1 -1
  83. package/lib/index.js +2 -2
  84. package/lib/index.js.map +1 -1
  85. package/lib/loader.d.ts +10 -1
  86. package/lib/loader.d.ts.map +1 -1
  87. package/lib/loader.js +21 -16
  88. package/lib/loader.js.map +1 -1
  89. package/lib/packageVersion.d.ts +1 -1
  90. package/lib/packageVersion.js +1 -1
  91. package/lib/packageVersion.js.map +1 -1
  92. package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
  93. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  94. package/lib/protocolTreeDocumentStorageService.js +7 -4
  95. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  96. package/lib/utils.d.ts.map +1 -1
  97. package/lib/utils.js +2 -1
  98. package/lib/utils.js.map +1 -1
  99. package/package.json +64 -56
  100. package/src/connectionManager.ts +48 -17
  101. package/src/connectionStateHandler.ts +17 -5
  102. package/src/container.ts +224 -116
  103. package/src/containerContext.ts +74 -11
  104. package/src/containerStorageAdapter.ts +113 -9
  105. package/src/contracts.ts +2 -2
  106. package/src/deltaManager.ts +9 -4
  107. package/src/deltaManagerProxy.ts +18 -73
  108. package/src/index.ts +2 -3
  109. package/src/loader.ts +28 -26
  110. package/src/packageVersion.ts +1 -1
  111. package/src/protocolTreeDocumentStorageService.ts +6 -3
  112. package/src/utils.ts +7 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "2.0.0-dev.3.1.0.125672",
3
+ "version": "2.0.0-dev.4.1.0.148229",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -14,34 +14,6 @@
14
14
  "main": "dist/index.js",
15
15
  "module": "lib/index.js",
16
16
  "types": "dist/index.d.ts",
17
- "scripts": {
18
- "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
19
- "build:commonjs": "npm run tsc && npm run typetests:gen && npm run build:test",
20
- "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
21
- "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
22
- "build:esnext": "tsc --project ./tsconfig.esnext.json",
23
- "build:full": "npm run build",
24
- "build:full:compile": "npm run build:compile",
25
- "build:genver": "gen-version",
26
- "build:test": "tsc --project ./src/test/tsconfig.json",
27
- "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
28
- "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
29
- "eslint": "eslint --format stylish src",
30
- "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
31
- "format": "npm run prettier:fix",
32
- "lint": "npm run prettier && npm run eslint",
33
- "lint:fix": "npm run prettier:fix && npm run eslint:fix",
34
- "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
35
- "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
36
- "test": "npm run test:mocha",
37
- "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
38
- "test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
39
- "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
40
- "tsc": "tsc",
41
- "tsc:watch": "tsc --watch",
42
- "typetests:gen": "flub generate typetests --generate --dir .",
43
- "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
44
- },
45
17
  "nyc": {
46
18
  "all": true,
47
19
  "cache-dir": "nyc/.cache",
@@ -64,15 +36,15 @@
64
36
  },
65
37
  "dependencies": {
66
38
  "@fluidframework/common-definitions": "^0.20.1",
67
- "@fluidframework/common-utils": "^1.0.0",
68
- "@fluidframework/container-definitions": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
69
- "@fluidframework/container-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
70
- "@fluidframework/core-interfaces": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
71
- "@fluidframework/driver-definitions": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
72
- "@fluidframework/driver-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
73
- "@fluidframework/protocol-base": "^0.1038.2000",
39
+ "@fluidframework/common-utils": "^1.1.1",
40
+ "@fluidframework/container-definitions": "2.0.0-dev.4.1.0.148229",
41
+ "@fluidframework/container-utils": "2.0.0-dev.4.1.0.148229",
42
+ "@fluidframework/core-interfaces": "2.0.0-dev.4.1.0.148229",
43
+ "@fluidframework/driver-definitions": "2.0.0-dev.4.1.0.148229",
44
+ "@fluidframework/driver-utils": "2.0.0-dev.4.1.0.148229",
45
+ "@fluidframework/protocol-base": "^0.1039.1000",
74
46
  "@fluidframework/protocol-definitions": "^1.1.0",
75
- "@fluidframework/telemetry-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
47
+ "@fluidframework/telemetry-utils": "2.0.0-dev.4.1.0.148229",
76
48
  "abort-controller": "^3.0.0",
77
49
  "double-ended-queue": "^2.1.0-0",
78
50
  "events": "^3.1.0",
@@ -81,37 +53,73 @@
81
53
  "uuid": "^8.3.1"
82
54
  },
83
55
  "devDependencies": {
84
- "@fluid-tools/build-cli": "^0.8.0",
56
+ "@fluid-internal/test-loader-utils": "2.0.0-dev.4.1.0.148229",
57
+ "@fluid-tools/build-cli": "^0.13.1",
85
58
  "@fluidframework/build-common": "^1.1.0",
86
- "@fluidframework/build-tools": "^0.8.0",
87
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.3.0.0",
59
+ "@fluidframework/build-tools": "^0.13.1",
60
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.4.0.0",
88
61
  "@fluidframework/eslint-config-fluid": "^2.0.0",
89
- "@fluidframework/mocha-test-setup": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
90
- "@fluidframework/test-loader-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
91
- "@microsoft/api-extractor": "^7.22.2",
92
- "@rushstack/eslint-config": "^2.5.1",
62
+ "@fluidframework/mocha-test-setup": "2.0.0-dev.4.1.0.148229",
63
+ "@microsoft/api-extractor": "^7.34.4",
93
64
  "@types/double-ended-queue": "^2.1.0",
94
65
  "@types/events": "^3.0.0",
95
66
  "@types/lodash": "^4.14.118",
96
67
  "@types/mocha": "^9.1.1",
97
- "@types/node": "^14.18.36",
68
+ "@types/node": "^14.18.38",
98
69
  "@types/sinon": "^7.0.13",
99
- "concurrently": "^6.2.0",
70
+ "concurrently": "^7.6.0",
100
71
  "copyfiles": "^2.4.1",
101
- "cross-env": "^7.0.2",
72
+ "cross-env": "^7.0.3",
102
73
  "eslint": "~8.6.0",
103
- "mocha": "^10.0.0",
104
- "nyc": "^15.0.0",
74
+ "mocha": "^10.2.0",
75
+ "mocha-json-output-reporter": "^2.0.1",
76
+ "mocha-multi-reporters": "^1.5.1",
77
+ "moment": "^2.21.0",
78
+ "nyc": "^15.1.0",
105
79
  "prettier": "~2.6.2",
106
- "rimraf": "^2.6.2",
80
+ "rimraf": "^4.4.0",
107
81
  "sinon": "^7.4.2",
108
82
  "typescript": "~4.5.5"
109
83
  },
110
84
  "typeValidation": {
111
- "version": "2.0.0-internal.3.1.0",
112
- "previousVersionStyle": "~previousMinor",
113
- "baselineRange": ">=2.0.0-internal.3.0.0 <2.0.0-internal.3.1.0",
114
- "baselineVersion": "2.0.0-internal.3.0.0",
115
- "broken": {}
85
+ "broken": {
86
+ "InterfaceDeclaration_IContainerConfig": {
87
+ "forwardCompat": false,
88
+ "backCompat": false
89
+ },
90
+ "InterfaceDeclaration_IPendingContainerState": {
91
+ "forwardCompat": false,
92
+ "backCompat": false
93
+ }
94
+ }
95
+ },
96
+ "scripts": {
97
+ "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
98
+ "build:commonjs": "npm run tsc && npm run typetests:gen && npm run build:test",
99
+ "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
100
+ "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
101
+ "build:esnext": "tsc --project ./tsconfig.esnext.json",
102
+ "build:full": "npm run build",
103
+ "build:full:compile": "npm run build:compile",
104
+ "build:genver": "gen-version",
105
+ "build:test": "tsc --project ./src/test/tsconfig.json",
106
+ "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
107
+ "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
108
+ "eslint": "eslint --format stylish src",
109
+ "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
110
+ "format": "npm run prettier:fix",
111
+ "lint": "npm run prettier && npm run eslint",
112
+ "lint:fix": "npm run prettier:fix && npm run eslint:fix",
113
+ "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
114
+ "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
115
+ "test": "npm run test:mocha",
116
+ "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
117
+ "test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
118
+ "test:mocha:multireport": "cross-env FLUID_TEST_MULTIREPORT=1 npm run test:mocha",
119
+ "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
120
+ "tsc": "tsc",
121
+ "tsc:watch": "tsc --watch",
122
+ "typetests:gen": "fluid-type-test-generator",
123
+ "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
116
124
  }
117
- }
125
+ }
@@ -13,11 +13,12 @@ import { assert, performance, TypedEventEmitter } from "@fluidframework/common-u
13
13
  import {
14
14
  IDeltaQueue,
15
15
  ReadOnlyInfo,
16
- IConnectionDetails,
16
+ IConnectionDetailsInternal,
17
17
  ICriticalContainerError,
18
18
  } from "@fluidframework/container-definitions";
19
19
  import { GenericError, UsageError } from "@fluidframework/container-utils";
20
20
  import {
21
+ DriverErrorType,
21
22
  IAnyDriverError,
22
23
  IDocumentService,
23
24
  IDocumentDeltaConnection,
@@ -28,8 +29,6 @@ import {
28
29
  createWriteError,
29
30
  createGenericNetworkError,
30
31
  getRetryDelayFromError,
31
- waitForConnectedState,
32
- DeltaStreamConnectionForbiddenError,
33
32
  logNetworkFailure,
34
33
  isRuntimeMessage,
35
34
  } from "@fluidframework/driver-utils";
@@ -135,6 +134,19 @@ class NoDeltaStream
135
134
  }
136
135
  }
137
136
 
137
+ const waitForOnline = async (): Promise<void> => {
138
+ // Only wait if we have a strong signal that we're offline - otherwise assume we're online.
139
+ if (globalThis.navigator?.onLine === false && globalThis.addEventListener !== undefined) {
140
+ return new Promise<void>((resolve) => {
141
+ const resolveAndRemoveListener = () => {
142
+ resolve();
143
+ globalThis.removeEventListener("online", resolveAndRemoveListener);
144
+ };
145
+ globalThis.addEventListener("online", resolveAndRemoveListener);
146
+ });
147
+ }
148
+ };
149
+
138
150
  /**
139
151
  * Interface to track the current in-progress connection attempt.
140
152
  */
@@ -302,11 +314,12 @@ export class ConnectionManager implements IConnectionManager {
302
314
  return { readonly: this._readonlyPermissions };
303
315
  }
304
316
 
305
- private static detailsFromConnection(connection: IDocumentDeltaConnection): IConnectionDetails {
317
+ private static detailsFromConnection(
318
+ connection: IDocumentDeltaConnection,
319
+ ): IConnectionDetailsInternal {
306
320
  return {
307
321
  claims: connection.claims,
308
322
  clientId: connection.clientId,
309
- existing: connection.existing,
310
323
  checkpointSequenceNumber: connection.checkpointSequenceNumber,
311
324
  get initialClients() {
312
325
  return connection.initialClients;
@@ -355,10 +368,7 @@ export class ConnectionManager implements IConnectionManager {
355
368
 
356
369
  this._outbound.clear();
357
370
 
358
- const disconnectReason =
359
- error !== undefined
360
- ? `Closing DeltaManager (${error.message})`
361
- : "Closing DeltaManager";
371
+ const disconnectReason = "Closing DeltaManager";
362
372
 
363
373
  // This raises "disconnect" event if we have active connection.
364
374
  this.disconnectFromDeltaStream(disconnectReason);
@@ -543,7 +553,7 @@ export class ConnectionManager implements IConnectionManager {
543
553
  if (
544
554
  typeof origError === "object" &&
545
555
  origError !== null &&
546
- origError?.errorType === DeltaStreamConnectionForbiddenError.errorType
556
+ origError?.errorType === DriverErrorType.deltaStreamConnectionForbidden
547
557
  ) {
548
558
  connection = new NoDeltaStream();
549
559
  requestedMode = "read";
@@ -572,12 +582,25 @@ export class ConnectionManager implements IConnectionManager {
572
582
  lastError = origError;
573
583
 
574
584
  const retryDelayFromError = getRetryDelayFromError(origError);
575
- delayMs = retryDelayFromError ?? Math.min(delayMs * 2, MaxReconnectDelayInMs);
576
-
577
585
  if (retryDelayFromError !== undefined) {
586
+ // If the error told us to wait, then we wait.
578
587
  this.props.reconnectionDelayHandler(retryDelayFromError, origError);
588
+ await new Promise<void>((resolve) => {
589
+ setTimeout(resolve, retryDelayFromError);
590
+ });
591
+ } else if (globalThis.navigator?.onLine !== false) {
592
+ // If the error didn't tell us to wait, let's still wait a little bit before retrying.
593
+ // We skip this delay if we're confident we're offline, because we probably just need to wait to come back online.
594
+ await new Promise<void>((resolve) => {
595
+ setTimeout(resolve, delayMs);
596
+ delayMs = Math.min(delayMs * 2, MaxReconnectDelayInMs);
597
+ });
579
598
  }
580
- await waitForConnectedState(delayMs);
599
+
600
+ // If we believe we're offline, we assume there's no point in trying until we at least think we're online.
601
+ // NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
602
+ // should probably live in the driver.
603
+ await waitForOnline();
581
604
  }
582
605
  }
583
606
 
@@ -615,7 +638,7 @@ export class ConnectionManager implements IConnectionManager {
615
638
  * @param args - The connection arguments
616
639
  */
617
640
  private triggerConnect(connectionMode: ConnectionMode) {
618
- // reconnect() has async await of waitForConnectedState(), and that causes potential race conditions
641
+ // reconnect() includes async awaits, and that causes potential race conditions
619
642
  // where we might already have a connection. If it were to happen, it's possible that we will connect
620
643
  // with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
621
644
  // fine (if needed, reconnect flow will get triggered again). Places where new mode matters should encode it
@@ -664,10 +687,10 @@ export class ConnectionManager implements IConnectionManager {
664
687
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
665
688
  this._outbound.pause();
666
689
  this._outbound.clear();
667
- this.props.disconnectHandler(reason);
668
-
669
690
  connection.dispose();
670
691
 
692
+ this.props.disconnectHandler(reason);
693
+
671
694
  this._connectionVerboseProps = {};
672
695
 
673
696
  return true;
@@ -888,12 +911,20 @@ export class ConnectionManager implements IConnectionManager {
888
911
  return;
889
912
  }
890
913
 
914
+ // If the error tells us to wait before retrying, then do so.
891
915
  const delayMs = getRetryDelayFromError(error);
892
916
  if (error !== undefined && delayMs !== undefined) {
893
917
  this.props.reconnectionDelayHandler(delayMs, error);
894
- await waitForConnectedState(delayMs);
918
+ await new Promise<void>((resolve) => {
919
+ setTimeout(resolve, delayMs);
920
+ });
895
921
  }
896
922
 
923
+ // If we believe we're offline, we assume there's no point in trying again until we at least think we're online.
924
+ // NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
925
+ // should probably live in the driver.
926
+ await waitForOnline();
927
+
897
928
  this.triggerConnect(requestedMode);
898
929
  }
899
930
 
@@ -9,7 +9,7 @@ import {
9
9
  TelemetryEventCategory,
10
10
  } from "@fluidframework/common-definitions";
11
11
  import { assert, Timer } from "@fluidframework/common-utils";
12
- import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
12
+ import { IConnectionDetailsInternal, IDeltaManager } from "@fluidframework/container-definitions";
13
13
  import { ILocalSequencedClient } from "@fluidframework/protocol-base";
14
14
  import { ISequencedClient, IClient } from "@fluidframework/protocol-definitions";
15
15
  import { PerformanceEvent, loggerToMonitoringContext } from "@fluidframework/telemetry-utils";
@@ -56,7 +56,7 @@ export interface IConnectionStateHandler {
56
56
  containerSaved(): void;
57
57
  dispose(): void;
58
58
  initProtocol(protocol: IProtocolHandler): void;
59
- receivedConnectEvent(details: IConnectionDetails): void;
59
+ receivedConnectEvent(details: IConnectionDetailsInternal): void;
60
60
  receivedDisconnectEvent(reason: string): void;
61
61
  }
62
62
 
@@ -143,7 +143,7 @@ class ConnectionStateHandlerPassThrough
143
143
  return this.pimpl.receivedDisconnectEvent(reason);
144
144
  }
145
145
 
146
- public receivedConnectEvent(details: IConnectionDetails) {
146
+ public receivedConnectEvent(details: IConnectionDetailsInternal) {
147
147
  return this.pimpl.receivedConnectEvent(details);
148
148
  }
149
149
 
@@ -288,7 +288,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
288
288
  private readonly prevClientLeftTimer: Timer;
289
289
  private readonly joinOpTimer: Timer;
290
290
  private protocol?: IProtocolHandler;
291
- private connection?: IConnectionDetails;
291
+ private connection?: IConnectionDetailsInternal;
292
292
  private _clientId?: string;
293
293
 
294
294
  private waitEvent: PerformanceEvent | undefined;
@@ -398,6 +398,18 @@ class ConnectionStateHandler implements IConnectionStateHandler {
398
398
  });
399
399
  }
400
400
  this.applyForConnectedState("addMemberEvent");
401
+ } else if (clientId === this.clientId) {
402
+ // If we see our clientId and it's not also our pending ID, it's our own join op
403
+ // being replayed, so start the timer in case our previous client is still in quorum
404
+ assert(
405
+ !this.waitingForLeaveOp,
406
+ "Unexpected join op with current clientId while waiting",
407
+ );
408
+ assert(
409
+ this.connectionState !== ConnectionState.Connected,
410
+ "Unexpected join op with current clientId while connected",
411
+ );
412
+ this.prevClientLeftTimer.restart();
401
413
  }
402
414
  }
403
415
 
@@ -472,7 +484,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
472
484
  * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
473
485
  * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
474
486
  */
475
- public receivedConnectEvent(details: IConnectionDetails) {
487
+ public receivedConnectEvent(details: IConnectionDetailsInternal) {
476
488
  this.connection = details;
477
489
 
478
490
  const oldState = this._connectionState;