@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.2.0.153917
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/CHANGELOG.md +14 -0
- package/README.md +45 -4
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/connectionManager.d.ts +2 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +59 -17
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +4 -4
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +7 -0
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +44 -4
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +160 -105
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -8
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +47 -4
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +41 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +87 -11
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +4 -5
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +7 -10
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +10 -22
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +14 -50
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +10 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +25 -16
- 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 +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +4 -2
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +7 -4
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +60 -18
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +4 -4
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +7 -0
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +44 -4
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +164 -109
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -8
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +48 -5
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +41 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +85 -11
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +2 -2
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +4 -5
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +7 -10
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +10 -22
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +14 -50
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +10 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +24 -16
- 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 +1 -0
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +3 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +7 -4
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +2 -1
- package/lib/utils.js.map +1 -1
- package/package.json +64 -56
- package/src/connectionManager.ts +65 -24
- package/src/connectionStateHandler.ts +17 -5
- package/src/container.ts +239 -137
- package/src/containerContext.ts +74 -11
- package/src/containerStorageAdapter.ts +113 -9
- package/src/contracts.ts +2 -2
- package/src/deltaManager.ts +12 -14
- package/src/deltaManagerProxy.ts +18 -73
- package/src/index.ts +3 -3
- package/src/loader.ts +31 -26
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +4 -1
- package/src/protocolTreeDocumentStorageService.ts +6 -3
- package/src/utils.ts +7 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @fluidframework/container-loader
|
|
2
|
+
|
|
3
|
+
## 2.0.0-internal.4.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Container-loader deprecations ([#14891](https://github.com/microsoft/FluidFramework/pull-requests/14891)) [961e96f3c9](https://github.com/microsoft/FluidFramework/commits/961e96f3c92d1dcf9575e56c703fe1779af5442d)
|
|
8
|
+
|
|
9
|
+
The following types in the @fluidframework/container-loader package are not used by, or necessary to use our public api, so will be removed from export in the next major release:
|
|
10
|
+
|
|
11
|
+
- IContainerLoadOptions
|
|
12
|
+
- IContainerConfig
|
|
13
|
+
- IPendingContainerState
|
|
14
|
+
- ISerializableBlobContents
|
package/README.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @fluidframework/container-loader
|
|
2
2
|
|
|
3
|
+
<!-- AUTO-GENERATED-CONTENT:START (README_DEPENDENCY_GUIDELINES_SECTION:includeHeading=TRUE) -->
|
|
4
|
+
|
|
5
|
+
<!-- prettier-ignore-start -->
|
|
6
|
+
<!-- NOTE: This section is automatically generated using @fluid-tools/markdown-magic. Do not update these generated contents directly. -->
|
|
7
|
+
|
|
8
|
+
## Using Fluid Framework libraries
|
|
9
|
+
|
|
10
|
+
When taking a dependency on a Fluid Framework library, we recommend using a `^` (caret) version range, such as `^1.3.4`.
|
|
11
|
+
While Fluid Framework libraries may use different ranges with interdependencies between other Fluid Framework libraries,
|
|
12
|
+
library consumers should always prefer `^`.
|
|
13
|
+
|
|
14
|
+
Note that when depending on a library version of the form 2.0.0-internal.x.y.z, called the Fluid internal version
|
|
15
|
+
scheme, you must use a `>= <` dependency range. Standard `^` and `~` ranges will not work as expected. See the
|
|
16
|
+
[@fluid-tools/version-tools](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/version-tools/README.md)
|
|
17
|
+
package for more information including tools to convert between version schemes.
|
|
18
|
+
|
|
19
|
+
<!-- prettier-ignore-end -->
|
|
20
|
+
|
|
21
|
+
<!-- AUTO-GENERATED-CONTENT:END -->
|
|
22
|
+
|
|
3
23
|
**Topics covered below:**
|
|
4
24
|
|
|
5
25
|
- [@fluidframework/container-loader](#fluidframeworkcontainer-loader)
|
|
@@ -10,6 +30,8 @@
|
|
|
10
30
|
- [Loading](#loading)
|
|
11
31
|
- [Connectivity](#connectivity)
|
|
12
32
|
- [Closure](#closure)
|
|
33
|
+
- [`Container.close()`](#containerclose)
|
|
34
|
+
- [`Container.dispose()`](#containerdispose)
|
|
13
35
|
- [Audience](#audience)
|
|
14
36
|
- [ClientID and client identification](#clientid-and-client-identification)
|
|
15
37
|
- [Error handling](#error-handling)
|
|
@@ -141,13 +163,14 @@ Errors are of [ICriticalContainerError](../../../common/lib/container-definition
|
|
|
141
163
|
readonly errorType: string;
|
|
142
164
|
```
|
|
143
165
|
|
|
144
|
-
There are
|
|
166
|
+
There are 4 sources of errors:
|
|
145
167
|
|
|
146
168
|
1. [ContainerErrorType](../../../common/lib/container-definitions/src/error.ts) - errors & warnings raised at loader level
|
|
147
|
-
2. [
|
|
148
|
-
3.
|
|
169
|
+
2. [DriverErrorType](../../common/driver-definitions/src/driverError.ts) - errors that are likely to be raised from the driver level
|
|
170
|
+
3. [OdspErrorType](../../drivers/odsp-driver/src/odspError.ts) and [RouterliciousErrorType](../../drivers/routerlicious-driver/src/documentDeltaConnection.ts) - errors raised by ODSP and R11S drivers.
|
|
171
|
+
4. Runtime errors, like `"summarizingError"`, `"dataCorruptionError"`. This class of errors is not pre-determined and depends on type of container loaded.
|
|
149
172
|
|
|
150
|
-
`ICriticalContainerError.errorType` is a string, which represents a union of
|
|
173
|
+
`ICriticalContainerError.errorType` is a string, which represents a union of 4 error types described above. Hosting application may package different drivers and open different types of containers, and only hosting application may have enough information to enumerate all possible error codes in such scenarios.
|
|
151
174
|
|
|
152
175
|
Hosts must listen to `"closed"` event. If error object is present there, container was closed due to error and this information needs to be communicated to user in some way. If there is no error object, it was closed due to host application calling Container.close() (without specifying error).
|
|
153
176
|
When container is closed, it is no longer connected to ordering service. It is also in read-only state, communicating to data stores not to allow user to make changes to container.
|
|
@@ -216,3 +239,21 @@ This information can be used by a host to build appropriate UX that allows user
|
|
|
216
239
|
Note that when an active connection is in place, it's just a matter of time before changes will be flushed to storage unless there is some source of continuous local changes being generated that prevents container from ever being fully saved. But if there is no active connection, because the user is offline, for example, then a document may stay in a dirty state for very long time.
|
|
217
240
|
|
|
218
241
|
`Container.isDirty` can be used to get current state of container.
|
|
242
|
+
|
|
243
|
+
<!-- AUTO-GENERATED-CONTENT:START (README_TRADEMARK_SECTION:includeHeading=TRUE) -->
|
|
244
|
+
|
|
245
|
+
<!-- prettier-ignore-start -->
|
|
246
|
+
<!-- NOTE: This section is automatically generated using @fluid-tools/markdown-magic. Do not update these generated contents directly. -->
|
|
247
|
+
|
|
248
|
+
## Trademark
|
|
249
|
+
|
|
250
|
+
This project may contain Microsoft trademarks or logos for Microsoft projects, products, or services.
|
|
251
|
+
|
|
252
|
+
Use of these trademarks or logos must follow Microsoft's [Trademark & Brand
|
|
253
|
+
Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
|
254
|
+
|
|
255
|
+
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
|
256
|
+
|
|
257
|
+
<!-- prettier-ignore-end -->
|
|
258
|
+
|
|
259
|
+
<!-- AUTO-GENERATED-CONTENT:END -->
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# closeAndGetPendingLocalState
|
|
2
|
+
|
|
3
|
+
Pending local state is a mechanism that Fluid provides for mitigating data loss, particularly in offline scenarios.
|
|
4
|
+
When changes are made in a Container, it puts the Container into a "dirty" state until the changes have been confirmed by the server.
|
|
5
|
+
If the Container is closed in this state, the changes may be lost if they did not reach the server (for example, if the user is offline).
|
|
6
|
+
|
|
7
|
+
`Container.closeAndGetPendingLocalState()` solves this problem by returning pending changes when closing a Container so they may be resupplied to a new container instance and resubmitted.
|
|
8
|
+
|
|
9
|
+
Additionally, the blob returned by closeAndGetPendingLocalState() contains enough data to load a container offline.
|
|
10
|
+
This method will still return a such a blob even if the container is not dirty when called.
|
|
11
|
+
|
|
12
|
+
## Using `Container.closeAndGetPendingLocalState()`
|
|
13
|
+
|
|
14
|
+
When closeAndGetPendingLocalState() is called it will return a serialized blob containing the necessary data to load and resubmit the changes in a new container.
|
|
15
|
+
|
|
16
|
+
The stashed changes blob can be used by passing it to `Loader.resolve()` when loading a new container.
|
|
17
|
+
The container will then automatically submit the stashed changes when the Container reaches the "connected" state, if the changes were not originally successful.
|
|
18
|
+
|
|
19
|
+
**It's important that these blobs are not reused, since it can result in the same changes being submitted multiple times, possibly resulting in document corruption.**
|
|
20
|
+
Instead, closeAndGetPendingLocalState() should be called again on the new container, which will return a new blob containing all its pending changes, including any still-pending stashed changes it was loaded with.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const myContainer = await myLoader.resolve(myRequest);
|
|
24
|
+
const pendingChanges = myContainer.closeAndGetPendingLocalState();
|
|
25
|
+
const myNewContainer = await myLoader.resolve(myRequest, pendingChanges);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Note that closeAndGetPendingLocalState() cannot be called on a Container that is already closed or disposed.
|
|
29
|
+
The purpose of closeAndGetPendingLocalState() is to avoid data loss when a dirty container must be closed, not recovering from internal Container errors.
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
The blob contains ops and attachment blob uploads that were still pending whe the container was closed.
|
|
34
|
+
It also contains the container's last client ID, a snapshot older than the reference sequence number of the oldest pending op, and all sequenced ops the container has processed since the snapshot.
|
|
35
|
+
|
|
36
|
+
When the blob is supplied to `Loader.resolve()`, it will return a new container.
|
|
37
|
+
This container will load from the snapshot in the blob, and "replay" the saved ops in the blob by processing them one by one.
|
|
38
|
+
applyStashedOp() will be called for each stashed op after the op whose sequence number matches the stashed op's reference sequence number is processed (i.e., when the document is in the same state as when the op was originally made).
|
|
39
|
+
|
|
40
|
+
When the container connects to the delta stream and starts processing new ops, they are matched to stashed ops by client ID, so the container will not submit stashed ops that were originally successfully submitted.
|
|
41
|
+
When it processes its own join op (i.e., reaches "connected" state), the container will have seen any previously successful stashed ops, and it is safe to resubmit any remaining stashed ops.
|
|
42
|
+
|
|
43
|
+
### applyStashedOp()
|
|
44
|
+
|
|
45
|
+
When an op is created, it is given a "reference sequence number," which is the sequence number of the last op processed by the container.
|
|
46
|
+
For various reasons, the server will only sequence ops if they are within the "collab window," limited by the "minimum sequence number" of the document.
|
|
47
|
+
For this reason, if an op is not successfully submitted while its reference sequence number is within this window, runtime will call SharedObject.reSubmit() to ask the DDS to merge it with later remote changes and resubmit the change.
|
|
48
|
+
Once the op is sequenced by the server, runtime will pass it to SharedObject.process().
|
|
49
|
+
|
|
50
|
+
The job of applyStashedOp() is to return a new DDS in a new container to the same state, so that the DDS is able to handle calls to reSubmit() or process() with the op, even though the DDS itself did not submit it.
|
|
51
|
+
Once stashed ops are passed to applyStashedOp(), they are handled by runtime the same as any other pending ops.
|
|
@@ -14,6 +14,7 @@ import { ReconnectMode, IConnectionManager, IConnectionManagerFactoryArgs } from
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class ConnectionManager implements IConnectionManager {
|
|
16
16
|
private readonly serviceProvider;
|
|
17
|
+
readonly containerDirty: () => boolean;
|
|
17
18
|
private client;
|
|
18
19
|
private readonly logger;
|
|
19
20
|
private readonly props;
|
|
@@ -83,7 +84,7 @@ export declare class ConnectionManager implements IConnectionManager {
|
|
|
83
84
|
private get readonly();
|
|
84
85
|
get readOnlyInfo(): ReadOnlyInfo;
|
|
85
86
|
private static detailsFromConnection;
|
|
86
|
-
constructor(serviceProvider: () => IDocumentService | undefined, client: IClient, reconnectAllowed: boolean, logger: ITelemetryLogger, props: IConnectionManagerFactoryArgs);
|
|
87
|
+
constructor(serviceProvider: () => IDocumentService | undefined, containerDirty: () => boolean, client: IClient, reconnectAllowed: boolean, logger: ITelemetryLogger, props: IConnectionManagerFactoryArgs);
|
|
87
88
|
dispose(error?: ICriticalContainerError, switchToReadonly?: boolean): void;
|
|
88
89
|
/**
|
|
89
90
|
* Enables or disables automatic reconnecting.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectionManager.d.ts","sourceRoot":"","sources":["../src/connectionManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAEN,gBAAgB,EAChB,oBAAoB,EACpB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EACN,WAAW,EACX,YAAY,EAEZ,uBAAuB,EACvB,MAAM,uCAAuC,CAAC;AAE/C,OAAO,
|
|
1
|
+
{"version":3,"file":"connectionManager.d.ts","sourceRoot":"","sources":["../src/connectionManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ exports.ConnectionManager = void 0;
|
|
|
11
11
|
const abort_controller_1 = __importDefault(require("abort-controller"));
|
|
12
12
|
const common_utils_1 = require("@fluidframework/common-utils");
|
|
13
13
|
const container_utils_1 = require("@fluidframework/container-utils");
|
|
14
|
+
const driver_definitions_1 = require("@fluidframework/driver-definitions");
|
|
14
15
|
const driver_utils_1 = require("@fluidframework/driver-utils");
|
|
15
16
|
const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
|
|
16
17
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
@@ -83,14 +84,28 @@ class NoDeltaStream extends common_utils_1.TypedEventEmitter {
|
|
|
83
84
|
this._disposed = true;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
87
|
+
const waitForOnline = async () => {
|
|
88
|
+
var _a;
|
|
89
|
+
// Only wait if we have a strong signal that we're offline - otherwise assume we're online.
|
|
90
|
+
if (((_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.onLine) === false && globalThis.addEventListener !== undefined) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
const resolveAndRemoveListener = () => {
|
|
93
|
+
resolve();
|
|
94
|
+
globalThis.removeEventListener("online", resolveAndRemoveListener);
|
|
95
|
+
};
|
|
96
|
+
globalThis.addEventListener("online", resolveAndRemoveListener);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
};
|
|
86
100
|
/**
|
|
87
101
|
* Implementation of IConnectionManager, used by Container class
|
|
88
102
|
* Implements constant connectivity to relay service, by reconnecting in case of lost connection or error.
|
|
89
103
|
* Exposes various controls to influence this process, including manual reconnects, forced read-only mode, etc.
|
|
90
104
|
*/
|
|
91
105
|
class ConnectionManager {
|
|
92
|
-
constructor(serviceProvider, client, reconnectAllowed, logger, props) {
|
|
106
|
+
constructor(serviceProvider, containerDirty, client, reconnectAllowed, logger, props) {
|
|
93
107
|
this.serviceProvider = serviceProvider;
|
|
108
|
+
this.containerDirty = containerDirty;
|
|
94
109
|
this.client = client;
|
|
95
110
|
this.logger = logger;
|
|
96
111
|
this.props = props;
|
|
@@ -207,7 +222,19 @@ class ConnectionManager {
|
|
|
207
222
|
}
|
|
208
223
|
shouldJoinWrite() {
|
|
209
224
|
// We don't have to wait for ack for topmost NoOps. So subtract those.
|
|
210
|
-
|
|
225
|
+
const outstandingOps = this.clientSequenceNumberObserved < this.clientSequenceNumber - this.localOpsToIgnore;
|
|
226
|
+
// Previous behavior was to force write mode here only when there are outstanding ops (besides
|
|
227
|
+
// no-ops). The dirty signal from runtime should provide the same behavior, but also support
|
|
228
|
+
// stashed ops that weren't submitted to container layer yet. For safety, we want to retain the
|
|
229
|
+
// same behavior whenever dirty is false.
|
|
230
|
+
const isDirty = this.containerDirty();
|
|
231
|
+
if (outstandingOps !== isDirty) {
|
|
232
|
+
this.logger.sendTelemetryEvent({
|
|
233
|
+
eventName: "DesiredConnectionModeMismatch",
|
|
234
|
+
details: JSON.stringify({ outstandingOps, isDirty }),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return outstandingOps || isDirty;
|
|
211
238
|
}
|
|
212
239
|
/**
|
|
213
240
|
* Tells if container is in read-only mode.
|
|
@@ -219,10 +246,7 @@ class ConnectionManager {
|
|
|
219
246
|
* and do not know if user has write access to a file.
|
|
220
247
|
*/
|
|
221
248
|
get readonly() {
|
|
222
|
-
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
return this._readonlyPermissions;
|
|
249
|
+
return this.readOnlyInfo.readonly;
|
|
226
250
|
}
|
|
227
251
|
get readOnlyInfo() {
|
|
228
252
|
const storageOnly = this.connection !== undefined && this.connection instanceof NoDeltaStream;
|
|
@@ -240,7 +264,6 @@ class ConnectionManager {
|
|
|
240
264
|
return {
|
|
241
265
|
claims: connection.claims,
|
|
242
266
|
clientId: connection.clientId,
|
|
243
|
-
existing: connection.existing,
|
|
244
267
|
checkpointSequenceNumber: connection.checkpointSequenceNumber,
|
|
245
268
|
get initialClients() {
|
|
246
269
|
return connection.initialClients;
|
|
@@ -259,9 +282,7 @@ class ConnectionManager {
|
|
|
259
282
|
// Ensure that things like triggerConnect() will short circuit
|
|
260
283
|
this._reconnectMode = contracts_1.ReconnectMode.Never;
|
|
261
284
|
this._outbound.clear();
|
|
262
|
-
const disconnectReason =
|
|
263
|
-
? `Closing DeltaManager (${error.message})`
|
|
264
|
-
: "Closing DeltaManager";
|
|
285
|
+
const disconnectReason = "Closing DeltaManager";
|
|
265
286
|
// This raises "disconnect" event if we have active connection.
|
|
266
287
|
this.disconnectFromDeltaStream(disconnectReason);
|
|
267
288
|
if (switchToReadonly) {
|
|
@@ -346,7 +367,7 @@ class ConnectionManager {
|
|
|
346
367
|
});
|
|
347
368
|
}
|
|
348
369
|
async connectCore(connectionMode) {
|
|
349
|
-
var _a, _b;
|
|
370
|
+
var _a, _b, _c;
|
|
350
371
|
(0, common_utils_1.assert)(!this._disposed, 0x26a /* "not closed" */);
|
|
351
372
|
if (this.connection !== undefined) {
|
|
352
373
|
return; // Connection attempt already completed successfully
|
|
@@ -415,7 +436,7 @@ class ConnectionManager {
|
|
|
415
436
|
catch (origError) {
|
|
416
437
|
if (typeof origError === "object" &&
|
|
417
438
|
origError !== null &&
|
|
418
|
-
(origError === null || origError === void 0 ? void 0 : origError.errorType) ===
|
|
439
|
+
(origError === null || origError === void 0 ? void 0 : origError.errorType) === driver_definitions_1.DriverErrorType.deltaStreamConnectionForbidden) {
|
|
419
440
|
connection = new NoDeltaStream();
|
|
420
441
|
requestedMode = "read";
|
|
421
442
|
break;
|
|
@@ -435,11 +456,25 @@ class ConnectionManager {
|
|
|
435
456
|
}, origError);
|
|
436
457
|
lastError = origError;
|
|
437
458
|
const retryDelayFromError = (0, driver_utils_1.getRetryDelayFromError)(origError);
|
|
438
|
-
delayMs = retryDelayFromError !== null && retryDelayFromError !== void 0 ? retryDelayFromError : Math.min(delayMs * 2, MaxReconnectDelayInMs);
|
|
439
459
|
if (retryDelayFromError !== undefined) {
|
|
460
|
+
// If the error told us to wait, then we wait.
|
|
440
461
|
this.props.reconnectionDelayHandler(retryDelayFromError, origError);
|
|
462
|
+
await new Promise((resolve) => {
|
|
463
|
+
setTimeout(resolve, retryDelayFromError);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
else if (((_c = globalThis.navigator) === null || _c === void 0 ? void 0 : _c.onLine) !== false) {
|
|
467
|
+
// If the error didn't tell us to wait, let's still wait a little bit before retrying.
|
|
468
|
+
// We skip this delay if we're confident we're offline, because we probably just need to wait to come back online.
|
|
469
|
+
await new Promise((resolve) => {
|
|
470
|
+
setTimeout(resolve, delayMs);
|
|
471
|
+
delayMs = Math.min(delayMs * 2, MaxReconnectDelayInMs);
|
|
472
|
+
});
|
|
441
473
|
}
|
|
442
|
-
|
|
474
|
+
// If we believe we're offline, we assume there's no point in trying until we at least think we're online.
|
|
475
|
+
// NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
|
|
476
|
+
// should probably live in the driver.
|
|
477
|
+
await waitForOnline();
|
|
443
478
|
}
|
|
444
479
|
}
|
|
445
480
|
// If we retried more than once, log an event about how long it took (this will not log to error table)
|
|
@@ -469,7 +504,7 @@ class ConnectionManager {
|
|
|
469
504
|
* @param args - The connection arguments
|
|
470
505
|
*/
|
|
471
506
|
triggerConnect(connectionMode) {
|
|
472
|
-
// reconnect()
|
|
507
|
+
// reconnect() includes async awaits, and that causes potential race conditions
|
|
473
508
|
// where we might already have a connection. If it were to happen, it's possible that we will connect
|
|
474
509
|
// with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
|
|
475
510
|
// fine (if needed, reconnect flow will get triggered again). Places where new mode matters should encode it
|
|
@@ -508,8 +543,8 @@ class ConnectionManager {
|
|
|
508
543
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
509
544
|
this._outbound.pause();
|
|
510
545
|
this._outbound.clear();
|
|
511
|
-
this.props.disconnectHandler(reason);
|
|
512
546
|
connection.dispose();
|
|
547
|
+
this.props.disconnectHandler(reason);
|
|
513
548
|
this._connectionVerboseProps = {};
|
|
514
549
|
return true;
|
|
515
550
|
}
|
|
@@ -671,11 +706,18 @@ class ConnectionManager {
|
|
|
671
706
|
if (this._disposed || this.reconnectMode !== contracts_1.ReconnectMode.Enabled) {
|
|
672
707
|
return;
|
|
673
708
|
}
|
|
709
|
+
// If the error tells us to wait before retrying, then do so.
|
|
674
710
|
const delayMs = (0, driver_utils_1.getRetryDelayFromError)(error);
|
|
675
711
|
if (error !== undefined && delayMs !== undefined) {
|
|
676
712
|
this.props.reconnectionDelayHandler(delayMs, error);
|
|
677
|
-
await
|
|
713
|
+
await new Promise((resolve) => {
|
|
714
|
+
setTimeout(resolve, delayMs);
|
|
715
|
+
});
|
|
678
716
|
}
|
|
717
|
+
// If we believe we're offline, we assume there's no point in trying again until we at least think we're online.
|
|
718
|
+
// NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
|
|
719
|
+
// should probably live in the driver.
|
|
720
|
+
await waitForOnline();
|
|
679
721
|
this.triggerConnect(requestedMode);
|
|
680
722
|
}
|
|
681
723
|
prepareMessageToSend(message) {
|