@fluidframework/container-loader 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419
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/.eslintrc.js +18 -21
- package/.mocharc.js +12 -0
- package/CHANGELOG.md +364 -0
- package/README.md +152 -56
- package/api-extractor-lint.json +4 -0
- package/api-extractor.json +2 -2
- package/api-report/container-loader.api.md +143 -0
- package/dist/{audience.js → audience.cjs} +15 -13
- package/dist/audience.cjs.map +1 -0
- package/dist/audience.d.ts +3 -6
- package/dist/audience.d.ts.map +1 -1
- package/dist/catchUpMonitor.cjs +43 -0
- package/dist/catchUpMonitor.cjs.map +1 -0
- package/dist/catchUpMonitor.d.ts +29 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/{connectionManager.js → connectionManager.cjs} +397 -240
- package/dist/connectionManager.cjs.map +1 -0
- package/dist/connectionManager.d.ts +23 -33
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/{connectionState.js → connectionState.cjs} +5 -7
- package/dist/connectionState.cjs.map +1 -0
- package/dist/connectionState.d.ts +3 -5
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionStateHandler.cjs +474 -0
- package/dist/connectionStateHandler.cjs.map +1 -0
- package/dist/connectionStateHandler.d.ts +127 -29
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/container-loader-alpha.d.ts +274 -0
- package/dist/container-loader-beta.d.ts +75 -0
- package/dist/container-loader-public.d.ts +75 -0
- package/dist/container-loader-untrimmed.d.ts +331 -0
- package/dist/container.cjs +1585 -0
- package/dist/container.cjs.map +1 -0
- package/dist/container.d.ts +227 -83
- package/dist/container.d.ts.map +1 -1
- package/dist/containerContext.cjs +74 -0
- package/dist/containerContext.cjs.map +1 -0
- package/dist/containerContext.d.ts +33 -59
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerStorageAdapter.cjs +234 -0
- package/dist/containerStorageAdapter.cjs.map +1 -0
- package/dist/containerStorageAdapter.d.ts +48 -23
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/{contracts.js → contracts.cjs} +5 -5
- package/dist/contracts.cjs.map +1 -0
- package/dist/contracts.d.ts +45 -17
- package/dist/contracts.d.ts.map +1 -1
- package/dist/debugLogger.cjs +101 -0
- package/dist/debugLogger.cjs.map +1 -0
- package/dist/debugLogger.d.ts +30 -0
- package/dist/debugLogger.d.ts.map +1 -0
- package/dist/{deltaManager.js → deltaManager.cjs} +379 -186
- package/dist/deltaManager.cjs.map +1 -0
- package/dist/deltaManager.d.ts +54 -18
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/{deltaQueue.js → deltaQueue.cjs} +29 -28
- package/dist/deltaQueue.cjs.map +1 -0
- package/dist/deltaQueue.d.ts +3 -4
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/disposal.cjs +25 -0
- package/dist/disposal.cjs.map +1 -0
- package/dist/disposal.d.ts +13 -0
- package/dist/disposal.d.ts.map +1 -0
- package/dist/error.cjs +32 -0
- package/dist/error.cjs.map +1 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/loader.cjs +148 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.ts +38 -19
- package/dist/loader.d.ts.map +1 -1
- package/dist/location-redirection-utilities/index.cjs +11 -0
- package/dist/location-redirection-utilities/index.cjs.map +1 -0
- package/dist/location-redirection-utilities/index.d.ts +6 -0
- package/dist/location-redirection-utilities/index.d.ts.map +1 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs +53 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.cjs.map +1 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +24 -0
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
- package/dist/{collabWindowTracker.js → noopHeuristic.cjs} +37 -39
- package/dist/noopHeuristic.cjs.map +1 -0
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
- package/dist/packageVersion.cjs.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/protocol.cjs +99 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.ts +38 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/{protocolTreeDocumentStorageService.js → protocolTreeDocumentStorageService.cjs} +8 -5
- package/dist/protocolTreeDocumentStorageService.cjs.map +1 -0
- package/dist/protocolTreeDocumentStorageService.d.ts +8 -4
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/quorum.cjs +16 -0
- package/dist/quorum.cjs.map +1 -0
- package/dist/quorum.d.ts +1 -14
- package/dist/quorum.d.ts.map +1 -1
- package/dist/{retriableDocumentStorageService.js → retriableDocumentStorageService.cjs} +36 -21
- package/dist/retriableDocumentStorageService.cjs.map +1 -0
- package/dist/retriableDocumentStorageService.d.ts +7 -5
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/{utils.js → utils.cjs} +52 -14
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.ts +34 -1
- package/dist/utils.d.ts.map +1 -1
- package/lib/{audience.d.ts → audience.d.mts} +3 -10
- package/lib/audience.d.mts.map +1 -0
- package/lib/{audience.js → audience.mjs} +15 -17
- package/lib/audience.mjs.map +1 -0
- package/lib/catchUpMonitor.d.mts +29 -0
- package/lib/catchUpMonitor.d.mts.map +1 -0
- package/lib/catchUpMonitor.mjs +39 -0
- package/lib/catchUpMonitor.mjs.map +1 -0
- package/lib/{connectionManager.d.ts → connectionManager.d.mts} +23 -33
- package/lib/connectionManager.d.mts.map +1 -0
- package/lib/{connectionManager.js → connectionManager.mjs} +378 -218
- package/lib/connectionManager.mjs.map +1 -0
- package/lib/{connectionState.d.ts → connectionState.d.mts} +3 -5
- package/lib/connectionState.d.mts.map +1 -0
- package/lib/{connectionState.js → connectionState.mjs} +4 -6
- package/lib/connectionState.mjs.map +1 -0
- package/lib/connectionStateHandler.d.mts +179 -0
- package/lib/connectionStateHandler.d.mts.map +1 -0
- package/lib/connectionStateHandler.mjs +469 -0
- package/lib/connectionStateHandler.mjs.map +1 -0
- package/lib/container-loader-alpha.d.mts +274 -0
- package/lib/container-loader-beta.d.mts +75 -0
- package/lib/container-loader-public.d.mts +75 -0
- package/lib/container-loader-untrimmed.d.mts +331 -0
- package/lib/container.d.mts +382 -0
- package/lib/container.d.mts.map +1 -0
- package/lib/container.mjs +1579 -0
- package/lib/container.mjs.map +1 -0
- package/lib/containerContext.d.mts +58 -0
- package/lib/containerContext.d.mts.map +1 -0
- package/lib/containerContext.mjs +70 -0
- package/lib/containerContext.mjs.map +1 -0
- package/lib/containerStorageAdapter.d.mts +73 -0
- package/lib/containerStorageAdapter.d.mts.map +1 -0
- package/lib/containerStorageAdapter.mjs +228 -0
- package/lib/containerStorageAdapter.mjs.map +1 -0
- package/lib/{contracts.d.ts → contracts.d.mts} +45 -17
- package/lib/contracts.d.mts.map +1 -0
- package/lib/{contracts.js → contracts.mjs} +4 -4
- package/lib/contracts.mjs.map +1 -0
- package/lib/debugLogger.d.mts +30 -0
- package/lib/debugLogger.d.mts.map +1 -0
- package/lib/debugLogger.mjs +93 -0
- package/lib/debugLogger.mjs.map +1 -0
- package/lib/{deltaManager.d.ts → deltaManager.d.mts} +54 -18
- package/lib/deltaManager.d.mts.map +1 -0
- package/lib/{deltaManager.js → deltaManager.mjs} +361 -165
- package/lib/deltaManager.mjs.map +1 -0
- package/lib/{deltaQueue.d.ts → deltaQueue.d.mts} +3 -4
- package/lib/deltaQueue.d.mts.map +1 -0
- package/lib/{deltaQueue.js → deltaQueue.mjs} +25 -24
- package/lib/deltaQueue.mjs.map +1 -0
- package/lib/disposal.d.mts +13 -0
- package/lib/disposal.d.mts.map +1 -0
- package/lib/disposal.mjs +21 -0
- package/lib/disposal.mjs.map +1 -0
- package/lib/error.d.mts +23 -0
- package/lib/error.d.mts.map +1 -0
- package/lib/error.mjs +28 -0
- package/lib/error.mjs.map +1 -0
- package/lib/index.d.mts +11 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +10 -0
- package/lib/index.mjs.map +1 -0
- package/lib/{loader.d.ts → loader.d.mts} +39 -20
- package/lib/loader.d.mts.map +1 -0
- package/lib/loader.mjs +143 -0
- package/lib/loader.mjs.map +1 -0
- package/lib/location-redirection-utilities/index.d.mts +6 -0
- package/lib/location-redirection-utilities/index.d.mts.map +1 -0
- package/lib/location-redirection-utilities/index.mjs +6 -0
- package/lib/location-redirection-utilities/index.mjs.map +1 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts +24 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.mts.map +1 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs +48 -0
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.mjs.map +1 -0
- package/lib/noopHeuristic.d.mts +23 -0
- package/lib/noopHeuristic.d.mts.map +1 -0
- package/lib/{collabWindowTracker.js → noopHeuristic.mjs} +33 -35
- package/lib/noopHeuristic.mjs.map +1 -0
- package/lib/{packageVersion.d.ts → packageVersion.d.mts} +1 -1
- package/lib/{packageVersion.d.ts.map → packageVersion.d.mts.map} +1 -1
- package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
- package/lib/packageVersion.mjs.map +1 -0
- package/lib/protocol.d.mts +38 -0
- package/lib/protocol.d.mts.map +1 -0
- package/lib/protocol.mjs +94 -0
- package/lib/protocol.mjs.map +1 -0
- package/lib/{protocolTreeDocumentStorageService.d.ts → protocolTreeDocumentStorageService.d.mts} +8 -4
- package/lib/protocolTreeDocumentStorageService.d.mts.map +1 -0
- package/lib/{protocolTreeDocumentStorageService.js → protocolTreeDocumentStorageService.mjs} +8 -5
- package/lib/protocolTreeDocumentStorageService.mjs.map +1 -0
- package/lib/quorum.d.mts +4 -0
- package/lib/quorum.d.mts.map +1 -0
- package/lib/quorum.mjs +12 -0
- package/lib/quorum.mjs.map +1 -0
- package/lib/{retriableDocumentStorageService.d.ts → retriableDocumentStorageService.d.mts} +7 -5
- package/lib/retriableDocumentStorageService.d.mts.map +1 -0
- package/lib/{retriableDocumentStorageService.js → retriableDocumentStorageService.mjs} +35 -20
- package/lib/retriableDocumentStorageService.mjs.map +1 -0
- package/lib/utils.d.mts +67 -0
- package/lib/utils.d.mts.map +1 -0
- package/lib/{utils.js → utils.mjs} +47 -11
- package/lib/utils.mjs.map +1 -0
- package/package.json +163 -70
- package/prettier.config.cjs +8 -0
- package/src/audience.ts +59 -49
- package/src/catchUpMonitor.ts +61 -0
- package/src/connectionManager.ts +1154 -910
- package/src/connectionState.ts +22 -25
- package/src/connectionStateHandler.ts +689 -319
- package/src/container.ts +2476 -1792
- package/src/containerContext.ts +98 -330
- package/src/containerStorageAdapter.ts +301 -105
- package/src/contracts.ts +184 -146
- package/src/debugLogger.ts +123 -0
- package/src/deltaManager.ts +1165 -900
- package/src/deltaQueue.ts +156 -152
- package/src/disposal.ts +25 -0
- package/src/error.ts +44 -0
- package/src/index.ts +14 -15
- package/src/loader.ts +356 -427
- package/src/location-redirection-utilities/index.ts +9 -0
- package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +61 -0
- package/src/noopHeuristic.ts +107 -0
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +150 -0
- package/src/protocolTreeDocumentStorageService.ts +35 -35
- package/src/quorum.ts +11 -50
- package/src/retriableDocumentStorageService.ts +135 -95
- package/src/utils.ts +159 -86
- package/tsc-multi.test.json +4 -0
- package/tsconfig.json +10 -12
- package/dist/audience.js.map +0 -1
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js.map +0 -1
- package/dist/connectionManager.js.map +0 -1
- package/dist/connectionState.js.map +0 -1
- package/dist/connectionStateHandler.js +0 -280
- package/dist/connectionStateHandler.js.map +0 -1
- package/dist/container.js +0 -1284
- package/dist/container.js.map +0 -1
- package/dist/containerContext.js +0 -217
- package/dist/containerContext.js.map +0 -1
- package/dist/containerStorageAdapter.js +0 -104
- package/dist/containerStorageAdapter.js.map +0 -1
- package/dist/contracts.js.map +0 -1
- package/dist/deltaManager.js.map +0 -1
- package/dist/deltaManagerProxy.d.ts +0 -54
- package/dist/deltaManagerProxy.d.ts.map +0 -1
- package/dist/deltaManagerProxy.js +0 -115
- package/dist/deltaManagerProxy.js.map +0 -1
- package/dist/deltaQueue.js.map +0 -1
- package/dist/index.js +0 -16
- package/dist/index.js.map +0 -1
- package/dist/loader.js +0 -241
- package/dist/loader.js.map +0 -1
- package/dist/packageVersion.js.map +0 -1
- package/dist/protocolTreeDocumentStorageService.js.map +0 -1
- package/dist/quorum.js +0 -44
- package/dist/quorum.js.map +0 -1
- package/dist/retriableDocumentStorageService.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/lib/audience.d.ts.map +0 -1
- package/lib/audience.js.map +0 -1
- package/lib/collabWindowTracker.d.ts +0 -19
- package/lib/collabWindowTracker.d.ts.map +0 -1
- package/lib/collabWindowTracker.js.map +0 -1
- package/lib/connectionManager.d.ts.map +0 -1
- package/lib/connectionManager.js.map +0 -1
- package/lib/connectionState.d.ts.map +0 -1
- package/lib/connectionState.js.map +0 -1
- package/lib/connectionStateHandler.d.ts +0 -81
- package/lib/connectionStateHandler.d.ts.map +0 -1
- package/lib/connectionStateHandler.js +0 -276
- package/lib/connectionStateHandler.js.map +0 -1
- package/lib/container.d.ts +0 -238
- package/lib/container.d.ts.map +0 -1
- package/lib/container.js +0 -1276
- package/lib/container.js.map +0 -1
- package/lib/containerContext.d.ts +0 -84
- package/lib/containerContext.d.ts.map +0 -1
- package/lib/containerContext.js +0 -213
- package/lib/containerContext.js.map +0 -1
- package/lib/containerStorageAdapter.d.ts +0 -48
- package/lib/containerStorageAdapter.d.ts.map +0 -1
- package/lib/containerStorageAdapter.js +0 -99
- package/lib/containerStorageAdapter.js.map +0 -1
- package/lib/contracts.d.ts.map +0 -1
- package/lib/contracts.js.map +0 -1
- package/lib/deltaManager.d.ts.map +0 -1
- package/lib/deltaManager.js.map +0 -1
- package/lib/deltaManagerProxy.d.ts +0 -54
- package/lib/deltaManagerProxy.d.ts.map +0 -1
- package/lib/deltaManagerProxy.js +0 -110
- package/lib/deltaManagerProxy.js.map +0 -1
- package/lib/deltaQueue.d.ts.map +0 -1
- package/lib/deltaQueue.js.map +0 -1
- package/lib/index.d.ts +0 -8
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -8
- package/lib/index.js.map +0 -1
- package/lib/loader.d.ts.map +0 -1
- package/lib/loader.js +0 -236
- package/lib/loader.js.map +0 -1
- package/lib/packageVersion.js.map +0 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +0 -1
- package/lib/protocolTreeDocumentStorageService.js.map +0 -1
- package/lib/quorum.d.ts +0 -21
- package/lib/quorum.d.ts.map +0 -1
- package/lib/quorum.js +0 -38
- package/lib/quorum.js.map +0 -1
- package/lib/retriableDocumentStorageService.d.ts.map +0 -1
- package/lib/retriableDocumentStorageService.js.map +0 -1
- package/lib/utils.d.ts +0 -34
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js.map +0 -1
- package/src/collabWindowTracker.ts +0 -102
- package/src/deltaManagerProxy.ts +0 -158
- package/tsconfig.esnext.json +0 -7
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { assert
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
//
|
|
10
|
-
} from "@fluidframework/driver-
|
|
5
|
+
import { LogLevel } from "@fluidframework/core-interfaces";
|
|
6
|
+
import { assert } from "@fluidframework/core-utils";
|
|
7
|
+
import { performance, TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
8
|
+
import {
|
|
9
|
+
// eslint-disable-next-line import/no-deprecated
|
|
10
|
+
DriverErrorType, } from "@fluidframework/driver-definitions";
|
|
11
|
+
import { canRetryOnError, createWriteError, createGenericNetworkError, getRetryDelayFromError, logNetworkFailure, isRuntimeMessage, calculateMaxWaitTime, } from "@fluidframework/driver-utils";
|
|
11
12
|
import { MessageType, ScopeType, } from "@fluidframework/protocol-definitions";
|
|
12
|
-
import {
|
|
13
|
-
import { ReconnectMode, } from "./contracts";
|
|
14
|
-
import { DeltaQueue } from "./deltaQueue";
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import { formatTick, GenericError, isFluidError, normalizeError, UsageError, } from "@fluidframework/telemetry-utils";
|
|
14
|
+
import { ReconnectMode, } from "./contracts.mjs";
|
|
15
|
+
import { DeltaQueue } from "./deltaQueue.mjs";
|
|
16
|
+
import { SignalType } from "./protocol.mjs";
|
|
17
|
+
import { isDeltaStreamConnectionForbiddenError } from "./utils.mjs";
|
|
18
|
+
// We double this value in first try in when we calculate time to wait for in "calculateMaxWaitTime" function.
|
|
19
|
+
const InitialReconnectDelayInMs = 500;
|
|
17
20
|
const DefaultChunkSize = 16 * 1024;
|
|
18
21
|
const fatalConnectErrorProp = { fatalConnectError: true };
|
|
19
22
|
function getNackReconnectInfo(nackContent) {
|
|
@@ -26,10 +29,25 @@ function getNackReconnectInfo(nackContent) {
|
|
|
26
29
|
* Implementation of IDocumentDeltaConnection that does not support submitting
|
|
27
30
|
* or receiving ops. Used in storage-only mode.
|
|
28
31
|
*/
|
|
32
|
+
const clientNoDeltaStream = {
|
|
33
|
+
mode: "read",
|
|
34
|
+
details: { capabilities: { interactive: true } },
|
|
35
|
+
permission: [],
|
|
36
|
+
user: { id: "storage-only client" },
|
|
37
|
+
scopes: [],
|
|
38
|
+
};
|
|
39
|
+
const clientIdNoDeltaStream = "storage-only client";
|
|
29
40
|
class NoDeltaStream extends TypedEventEmitter {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Connection which is not connected to socket.
|
|
43
|
+
* @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.
|
|
44
|
+
* @param readonlyConnectionReason - reason/error if any which lead to using NoDeltaStream.
|
|
45
|
+
*/
|
|
46
|
+
constructor(storageOnlyReason, readonlyConnectionReason) {
|
|
47
|
+
super();
|
|
48
|
+
this.storageOnlyReason = storageOnlyReason;
|
|
49
|
+
this.readonlyConnectionReason = readonlyConnectionReason;
|
|
50
|
+
this.clientId = clientIdNoDeltaStream;
|
|
33
51
|
this.claims = {
|
|
34
52
|
scopes: [ScopeType.DocRead],
|
|
35
53
|
};
|
|
@@ -39,11 +57,12 @@ class NoDeltaStream extends TypedEventEmitter {
|
|
|
39
57
|
this.version = "";
|
|
40
58
|
this.initialMessages = [];
|
|
41
59
|
this.initialSignals = [];
|
|
42
|
-
this.initialClients = [
|
|
60
|
+
this.initialClients = [
|
|
61
|
+
{ client: clientNoDeltaStream, clientId: clientIdNoDeltaStream },
|
|
62
|
+
];
|
|
43
63
|
this.serviceConfiguration = {
|
|
44
64
|
maxMessageSize: 0,
|
|
45
65
|
blockSize: 0,
|
|
46
|
-
summary: undefined,
|
|
47
66
|
};
|
|
48
67
|
this.checkpointSequenceNumber = undefined;
|
|
49
68
|
this._disposed = false;
|
|
@@ -62,85 +81,49 @@ class NoDeltaStream extends TypedEventEmitter {
|
|
|
62
81
|
content: { message: "Cannot submit signal with storage-only connection", code: 403 },
|
|
63
82
|
});
|
|
64
83
|
}
|
|
65
|
-
get disposed() {
|
|
66
|
-
|
|
84
|
+
get disposed() {
|
|
85
|
+
return this._disposed;
|
|
86
|
+
}
|
|
87
|
+
dispose() {
|
|
88
|
+
this._disposed = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function isNoDeltaStreamConnection(connection) {
|
|
92
|
+
return connection instanceof NoDeltaStream;
|
|
67
93
|
}
|
|
94
|
+
const waitForOnline = async () => {
|
|
95
|
+
// Only wait if we have a strong signal that we're offline - otherwise assume we're online.
|
|
96
|
+
if (globalThis.navigator?.onLine === false && globalThis.addEventListener !== undefined) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const resolveAndRemoveListener = () => {
|
|
99
|
+
resolve();
|
|
100
|
+
globalThis.removeEventListener("online", resolveAndRemoveListener);
|
|
101
|
+
};
|
|
102
|
+
globalThis.addEventListener("online", resolveAndRemoveListener);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
68
106
|
/**
|
|
69
107
|
* Implementation of IConnectionManager, used by Container class
|
|
70
|
-
* Implements constant connectivity to relay service, by reconnecting in case of
|
|
71
|
-
* Exposes various controls to
|
|
108
|
+
* Implements constant connectivity to relay service, by reconnecting in case of lost connection or error.
|
|
109
|
+
* Exposes various controls to influence this process, including manual reconnects, forced read-only mode, etc.
|
|
72
110
|
*/
|
|
73
111
|
export class ConnectionManager {
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
this.client = client;
|
|
77
|
-
this.logger = logger;
|
|
78
|
-
this.props = props;
|
|
79
|
-
/** tracks host requiring read-only mode. */
|
|
80
|
-
this._forceReadonly = false;
|
|
81
|
-
/** True if there is pending (async) reconnection from "read" to "write" */
|
|
82
|
-
this.pendingReconnect = false;
|
|
83
|
-
this.clientSequenceNumber = 0;
|
|
84
|
-
this.clientSequenceNumberObserved = 0;
|
|
85
|
-
/** Counts the number of noops sent by the client which may not be acked. */
|
|
86
|
-
this.trailingNoopCount = 0;
|
|
87
|
-
this.connectFirstConnection = true;
|
|
88
|
-
this._connectionVerboseProps = {};
|
|
89
|
-
this._connectionProps = {};
|
|
90
|
-
this.closed = false;
|
|
91
|
-
this.opHandler = (documentId, messagesArg) => {
|
|
92
|
-
const messages = Array.isArray(messagesArg) ? messagesArg : [messagesArg];
|
|
93
|
-
this.props.incomingOpHandler(messages, "opHandler");
|
|
94
|
-
};
|
|
95
|
-
// Always connect in write mode after getting nacked.
|
|
96
|
-
this.nackHandler = (documentId, messages) => {
|
|
97
|
-
const message = messages[0];
|
|
98
|
-
if (this._readonlyPermissions === true) {
|
|
99
|
-
this.props.closeHandler(createWriteError("writeOnReadOnlyDocument", { driverVersion: undefined }));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const reconnectInfo = getNackReconnectInfo(message.content);
|
|
103
|
-
// If the nack indicates we cannot retry, then close the container outright
|
|
104
|
-
if (!reconnectInfo.canRetry) {
|
|
105
|
-
this.props.closeHandler(reconnectInfo);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
this.reconnectOnError("write", reconnectInfo);
|
|
109
|
-
};
|
|
110
|
-
// Connection mode is always read on disconnect/error unless the system mode was write.
|
|
111
|
-
this.disconnectHandlerInternal = (disconnectReason) => {
|
|
112
|
-
// Note: we might get multiple disconnect calls on same socket, as early disconnect notification
|
|
113
|
-
// ("server_disconnect", ODSP-specific) is mapped to "disconnect"
|
|
114
|
-
this.reconnectOnError(this.defaultReconnectionMode, disconnectReason);
|
|
115
|
-
};
|
|
116
|
-
this.errorHandler = (error) => {
|
|
117
|
-
this.reconnectOnError(this.defaultReconnectionMode, error);
|
|
118
|
-
};
|
|
119
|
-
this.clientDetails = this.client.details;
|
|
120
|
-
this.defaultReconnectionMode = this.client.mode;
|
|
121
|
-
this._reconnectMode = reconnectAllowed ? ReconnectMode.Enabled : ReconnectMode.Never;
|
|
122
|
-
// Outbound message queue. The outbound queue is represented as a queue of an array of ops. Ops contained
|
|
123
|
-
// within an array *must* fit within the maxMessageSize and are guaranteed to be ordered sequentially.
|
|
124
|
-
this._outbound = new DeltaQueue((messages) => {
|
|
125
|
-
if (this.connection === undefined) {
|
|
126
|
-
throw new Error("Attempted to submit an outbound message without connection");
|
|
127
|
-
}
|
|
128
|
-
this.connection.submit(messages);
|
|
129
|
-
});
|
|
130
|
-
this._outbound.on("error", (error) => {
|
|
131
|
-
this.props.closeHandler(normalizeError(error));
|
|
132
|
-
});
|
|
112
|
+
get connectionVerboseProps() {
|
|
113
|
+
return this._connectionVerboseProps;
|
|
133
114
|
}
|
|
134
|
-
get connectionVerboseProps() { return this._connectionVerboseProps; }
|
|
135
115
|
/**
|
|
136
116
|
* The current connection mode, initially read.
|
|
137
117
|
*/
|
|
138
118
|
get connectionMode() {
|
|
139
|
-
|
|
140
|
-
|
|
119
|
+
return this.connection?.mode ?? "read";
|
|
120
|
+
}
|
|
121
|
+
get connected() {
|
|
122
|
+
return this.connection !== undefined;
|
|
123
|
+
}
|
|
124
|
+
get clientId() {
|
|
125
|
+
return this.connection?.clientId;
|
|
141
126
|
}
|
|
142
|
-
get connected() { return this.connection !== undefined; }
|
|
143
|
-
get clientId() { var _a; return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.clientId; }
|
|
144
127
|
/**
|
|
145
128
|
* Automatic reconnecting enabled or disabled.
|
|
146
129
|
* If set to Never, then reconnecting will never be allowed.
|
|
@@ -149,8 +132,7 @@ export class ConnectionManager {
|
|
|
149
132
|
return this._reconnectMode;
|
|
150
133
|
}
|
|
151
134
|
get maxMessageSize() {
|
|
152
|
-
|
|
153
|
-
return (_c = (_b = (_a = this.connection) === null || _a === void 0 ? void 0 : _a.serviceConfiguration) === null || _b === void 0 ? void 0 : _b.maxMessageSize) !== null && _c !== void 0 ? _c : DefaultChunkSize;
|
|
135
|
+
return this.connection?.serviceConfiguration?.maxMessageSize ?? DefaultChunkSize;
|
|
154
136
|
}
|
|
155
137
|
get version() {
|
|
156
138
|
if (this.connection === undefined) {
|
|
@@ -159,12 +141,10 @@ export class ConnectionManager {
|
|
|
159
141
|
return this.connection.version;
|
|
160
142
|
}
|
|
161
143
|
get serviceConfiguration() {
|
|
162
|
-
|
|
163
|
-
return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.serviceConfiguration;
|
|
144
|
+
return this.connection?.serviceConfiguration;
|
|
164
145
|
}
|
|
165
146
|
get scopes() {
|
|
166
|
-
|
|
167
|
-
return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.claims.scopes;
|
|
147
|
+
return this.connection?.claims.scopes;
|
|
168
148
|
}
|
|
169
149
|
get outbound() {
|
|
170
150
|
return this._outbound;
|
|
@@ -172,20 +152,31 @@ export class ConnectionManager {
|
|
|
172
152
|
/**
|
|
173
153
|
* Returns set of props that can be logged in telemetry that provide some insights / statistics
|
|
174
154
|
* about current or last connection (if there is no connection at the moment)
|
|
175
|
-
|
|
155
|
+
*/
|
|
176
156
|
get connectionProps() {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return Object.assign(Object.assign({}, this._connectionProps), {
|
|
157
|
+
return this.connection !== undefined
|
|
158
|
+
? this._connectionProps
|
|
159
|
+
: {
|
|
160
|
+
...this._connectionProps,
|
|
182
161
|
// Report how many ops this client sent in last disconnected session
|
|
183
|
-
sentOps: this.clientSequenceNumber
|
|
184
|
-
|
|
162
|
+
sentOps: this.clientSequenceNumber,
|
|
163
|
+
};
|
|
185
164
|
}
|
|
186
165
|
shouldJoinWrite() {
|
|
187
166
|
// We don't have to wait for ack for topmost NoOps. So subtract those.
|
|
188
|
-
|
|
167
|
+
const outstandingOps = this.clientSequenceNumberObserved < this.clientSequenceNumber - this.localOpsToIgnore;
|
|
168
|
+
// Previous behavior was to force write mode here only when there are outstanding ops (besides
|
|
169
|
+
// no-ops). The dirty signal from runtime should provide the same behavior, but also support
|
|
170
|
+
// stashed ops that weren't submitted to container layer yet. For safety, we want to retain the
|
|
171
|
+
// same behavior whenever dirty is false.
|
|
172
|
+
const isDirty = this.containerDirty();
|
|
173
|
+
if (outstandingOps !== isDirty) {
|
|
174
|
+
this.logger.sendTelemetryEvent({
|
|
175
|
+
eventName: "DesiredConnectionModeMismatch",
|
|
176
|
+
details: JSON.stringify({ outstandingOps, isDirty }),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return outstandingOps || isDirty;
|
|
189
180
|
}
|
|
190
181
|
/**
|
|
191
182
|
* Tells if container is in read-only mode.
|
|
@@ -197,82 +188,141 @@ export class ConnectionManager {
|
|
|
197
188
|
* and do not know if user has write access to a file.
|
|
198
189
|
*/
|
|
199
190
|
get readonly() {
|
|
200
|
-
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
return this._readonlyPermissions;
|
|
191
|
+
return this.readOnlyInfo.readonly;
|
|
204
192
|
}
|
|
205
193
|
get readOnlyInfo() {
|
|
206
|
-
|
|
194
|
+
let storageOnly = false;
|
|
195
|
+
let storageOnlyReason;
|
|
196
|
+
if (isNoDeltaStreamConnection(this.connection)) {
|
|
197
|
+
storageOnly = true;
|
|
198
|
+
storageOnlyReason = this.connection.storageOnlyReason;
|
|
199
|
+
}
|
|
207
200
|
if (storageOnly || this._forceReadonly || this._readonlyPermissions === true) {
|
|
208
201
|
return {
|
|
209
202
|
readonly: true,
|
|
210
203
|
forced: this._forceReadonly,
|
|
211
204
|
permissions: this._readonlyPermissions,
|
|
212
205
|
storageOnly,
|
|
206
|
+
storageOnlyReason,
|
|
213
207
|
};
|
|
214
208
|
}
|
|
215
209
|
return { readonly: this._readonlyPermissions };
|
|
216
210
|
}
|
|
217
|
-
static detailsFromConnection(connection) {
|
|
211
|
+
static detailsFromConnection(connection, reason) {
|
|
218
212
|
return {
|
|
219
213
|
claims: connection.claims,
|
|
220
214
|
clientId: connection.clientId,
|
|
221
|
-
existing: connection.existing,
|
|
222
215
|
checkpointSequenceNumber: connection.checkpointSequenceNumber,
|
|
223
|
-
get initialClients() {
|
|
216
|
+
get initialClients() {
|
|
217
|
+
return connection.initialClients;
|
|
218
|
+
},
|
|
224
219
|
mode: connection.mode,
|
|
225
220
|
serviceConfiguration: connection.serviceConfiguration,
|
|
226
221
|
version: connection.version,
|
|
222
|
+
reason,
|
|
227
223
|
};
|
|
228
224
|
}
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
constructor(serviceProvider, containerDirty, client, reconnectAllowed, logger, props) {
|
|
226
|
+
this.serviceProvider = serviceProvider;
|
|
227
|
+
this.containerDirty = containerDirty;
|
|
228
|
+
this.client = client;
|
|
229
|
+
this.logger = logger;
|
|
230
|
+
this.props = props;
|
|
231
|
+
/** tracks host requiring read-only mode. */
|
|
232
|
+
this._forceReadonly = false;
|
|
233
|
+
/** True if there is pending (async) reconnection from "read" to "write" */
|
|
234
|
+
this.pendingReconnect = false;
|
|
235
|
+
this.clientSequenceNumber = 0;
|
|
236
|
+
this.clientSequenceNumberObserved = 0;
|
|
237
|
+
/** Counts the number of non-runtime ops sent by the client which may not be acked. */
|
|
238
|
+
this.localOpsToIgnore = 0;
|
|
239
|
+
this.connectFirstConnection = true;
|
|
240
|
+
this._connectionVerboseProps = {};
|
|
241
|
+
this._connectionProps = {};
|
|
242
|
+
this._disposed = false;
|
|
243
|
+
this.opHandler = (documentId, messagesArg) => {
|
|
244
|
+
const messages = Array.isArray(messagesArg) ? messagesArg : [messagesArg];
|
|
245
|
+
this.props.incomingOpHandler(messages, "opHandler");
|
|
246
|
+
};
|
|
247
|
+
this.signalHandler = (signalsArg) => {
|
|
248
|
+
const signals = Array.isArray(signalsArg) ? signalsArg : [signalsArg];
|
|
249
|
+
this.props.signalHandler(signals);
|
|
250
|
+
};
|
|
251
|
+
// Always connect in write mode after getting nacked.
|
|
252
|
+
this.nackHandler = (documentId, messages) => {
|
|
253
|
+
const message = messages[0];
|
|
254
|
+
if (this._readonlyPermissions === true) {
|
|
255
|
+
this.props.closeHandler(createWriteError("writeOnReadOnlyDocument", { driverVersion: undefined }));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const reconnectInfo = getNackReconnectInfo(message.content);
|
|
259
|
+
// If the nack indicates we cannot retry, then close the container outright
|
|
260
|
+
if (!reconnectInfo.canRetry) {
|
|
261
|
+
this.props.closeHandler(reconnectInfo);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.reconnectOnError("write", reconnectInfo);
|
|
265
|
+
};
|
|
266
|
+
// Connection mode is always read on disconnect/error unless the system mode was write.
|
|
267
|
+
this.disconnectHandlerInternal = (disconnectReason) => {
|
|
268
|
+
// Note: we might get multiple disconnect calls on same socket, as early disconnect notification
|
|
269
|
+
// ("server_disconnect", ODSP-specific) is mapped to "disconnect"
|
|
270
|
+
this.reconnectOnError(this.defaultReconnectionMode, disconnectReason);
|
|
271
|
+
};
|
|
272
|
+
this.errorHandler = (error) => {
|
|
273
|
+
this.reconnectOnError(this.defaultReconnectionMode, error);
|
|
274
|
+
};
|
|
275
|
+
this.clientDetails = this.client.details;
|
|
276
|
+
this.defaultReconnectionMode = this.client.mode;
|
|
277
|
+
this._reconnectMode = reconnectAllowed ? ReconnectMode.Enabled : ReconnectMode.Never;
|
|
278
|
+
// Outbound message queue. The outbound queue is represented as a queue of an array of ops. Ops contained
|
|
279
|
+
// within an array *must* fit within the maxMessageSize and are guaranteed to be ordered sequentially.
|
|
280
|
+
this._outbound = new DeltaQueue((messages) => {
|
|
281
|
+
if (this.connection === undefined) {
|
|
282
|
+
throw new Error("Attempted to submit an outbound message without connection");
|
|
283
|
+
}
|
|
284
|
+
this.connection.submit(messages);
|
|
285
|
+
});
|
|
286
|
+
this._outbound.on("error", (error) => {
|
|
287
|
+
this.props.closeHandler(normalizeError(error));
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
dispose(error, switchToReadonly = true) {
|
|
291
|
+
if (this._disposed) {
|
|
231
292
|
return;
|
|
232
293
|
}
|
|
233
|
-
this.
|
|
234
|
-
this.pendingConnection = undefined;
|
|
294
|
+
this._disposed = true;
|
|
235
295
|
// Ensure that things like triggerConnect() will short circuit
|
|
236
296
|
this._reconnectMode = ReconnectMode.Never;
|
|
237
297
|
this._outbound.clear();
|
|
238
|
-
const disconnectReason =
|
|
239
|
-
|
|
240
|
-
|
|
298
|
+
const disconnectReason = {
|
|
299
|
+
text: "Closing DeltaManager",
|
|
300
|
+
error,
|
|
301
|
+
};
|
|
302
|
+
const oldReadonlyValue = this.readonly;
|
|
241
303
|
// This raises "disconnect" event if we have active connection.
|
|
242
304
|
this.disconnectFromDeltaStream(disconnectReason);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
305
|
+
if (switchToReadonly) {
|
|
306
|
+
// Notify everyone we are in read-only state.
|
|
307
|
+
// Useful for data stores in case we hit some critical error,
|
|
308
|
+
// to switch to a mode where user edits are not accepted
|
|
309
|
+
this.set_readonlyPermissions(true, oldReadonlyValue, disconnectReason);
|
|
310
|
+
}
|
|
247
311
|
}
|
|
248
312
|
/**
|
|
249
313
|
* Enables or disables automatic reconnecting.
|
|
250
314
|
* Will throw an error if reconnectMode set to Never.
|
|
251
|
-
|
|
252
|
-
setAutoReconnect(mode) {
|
|
315
|
+
*/
|
|
316
|
+
setAutoReconnect(mode, reason) {
|
|
253
317
|
assert(mode !== ReconnectMode.Never && this._reconnectMode !== ReconnectMode.Never, 0x278 /* "API is not supported for non-connecting or closed container" */);
|
|
254
318
|
this._reconnectMode = mode;
|
|
255
319
|
if (mode !== ReconnectMode.Enabled) {
|
|
256
320
|
// immediately disconnect - do not rely on service eventually dropping connection.
|
|
257
|
-
this.disconnectFromDeltaStream(
|
|
321
|
+
this.disconnectFromDeltaStream(reason);
|
|
258
322
|
}
|
|
259
323
|
}
|
|
260
324
|
/**
|
|
261
|
-
*
|
|
262
|
-
* Hosts may have read only views, indicating to data stores that no edits are allowed.
|
|
263
|
-
* This is independent from this._readonlyPermissions (permissions) and this.connectionMode
|
|
264
|
-
* (server can return "write" mode even when asked for "read")
|
|
265
|
-
* Leveraging same "readonly" event as runtime & data stores should behave the same in such case
|
|
266
|
-
* as in read-only permissions.
|
|
267
|
-
* But this.active can be used by some DDSes to figure out if ops can be sent
|
|
268
|
-
* (for example, read-only view still participates in code proposals / upgrades decisions)
|
|
269
|
-
*
|
|
270
|
-
* Forcing Readonly does not prevent DDS from generating ops. It is up to user code to honour
|
|
271
|
-
* the readonly flag. If ops are generated, they will accumulate locally and not be sent. If
|
|
272
|
-
* there are pending in the outbound queue, it will stop sending until force readonly is
|
|
273
|
-
* cleared.
|
|
274
|
-
*
|
|
275
|
-
* @param readonly - set or clear force readonly.
|
|
325
|
+
* {@inheritDoc Container.forceReadonly}
|
|
276
326
|
*/
|
|
277
327
|
forceReadonly(readonly) {
|
|
278
328
|
if (readonly !== this._forceReadonly) {
|
|
@@ -297,42 +347,40 @@ export class ConnectionManager {
|
|
|
297
347
|
// host logic error.
|
|
298
348
|
this.logger.sendErrorEvent({ eventName: "ForceReadonlyPendingChanged" });
|
|
299
349
|
}
|
|
300
|
-
reconnect = this.disconnectFromDeltaStream("Force readonly");
|
|
350
|
+
reconnect = this.disconnectFromDeltaStream({ text: "Force readonly" });
|
|
301
351
|
}
|
|
302
352
|
this.props.readonlyChangeHandler(this.readonly);
|
|
303
353
|
if (reconnect) {
|
|
304
354
|
// reconnect if we disconnected from before.
|
|
305
|
-
this.triggerConnect("read");
|
|
355
|
+
this.triggerConnect({ text: "Force Readonly" }, "read");
|
|
306
356
|
}
|
|
307
357
|
}
|
|
308
358
|
}
|
|
309
|
-
set_readonlyPermissions(
|
|
310
|
-
|
|
311
|
-
this.
|
|
312
|
-
|
|
313
|
-
this.props.readonlyChangeHandler(this.readonly);
|
|
359
|
+
set_readonlyPermissions(newReadonlyValue, oldReadonlyValue, readonlyConnectionReason) {
|
|
360
|
+
this._readonlyPermissions = newReadonlyValue;
|
|
361
|
+
if (oldReadonlyValue !== this.readonly) {
|
|
362
|
+
this.props.readonlyChangeHandler(this.readonly, readonlyConnectionReason);
|
|
314
363
|
}
|
|
315
364
|
}
|
|
316
|
-
connect(connectionMode) {
|
|
317
|
-
this.connectCore(connectionMode).catch((
|
|
318
|
-
const normalizedError = normalizeError(
|
|
365
|
+
connect(reason, connectionMode) {
|
|
366
|
+
this.connectCore(reason, connectionMode).catch((e) => {
|
|
367
|
+
const normalizedError = normalizeError(e, { props: fatalConnectErrorProp });
|
|
319
368
|
this.props.closeHandler(normalizedError);
|
|
320
369
|
});
|
|
321
370
|
}
|
|
322
|
-
async connectCore(connectionMode) {
|
|
323
|
-
|
|
324
|
-
assert(!this.closed, 0x26a /* "not closed" */);
|
|
371
|
+
async connectCore(reason, connectionMode) {
|
|
372
|
+
assert(!this._disposed, 0x26a /* "not closed" */);
|
|
325
373
|
if (this.connection !== undefined) {
|
|
326
374
|
return; // Connection attempt already completed successfully
|
|
327
375
|
}
|
|
328
376
|
let pendingConnectionMode;
|
|
329
377
|
if (this.pendingConnection !== undefined) {
|
|
330
378
|
pendingConnectionMode = this.pendingConnection.connectionMode;
|
|
331
|
-
this.cancelConnection(); // Throw out in-progress connection attempt in favor of new attempt
|
|
379
|
+
this.cancelConnection(reason); // Throw out in-progress connection attempt in favor of new attempt
|
|
332
380
|
assert(this.pendingConnection === undefined, 0x344 /* this.pendingConnection should be undefined */);
|
|
333
381
|
}
|
|
334
382
|
// If there is no specified ConnectionMode, try the previous mode, if there is no previous mode use default
|
|
335
|
-
let requestedMode =
|
|
383
|
+
let requestedMode = connectionMode ?? pendingConnectionMode ?? this.defaultReconnectionMode;
|
|
336
384
|
// if we have any non-acked ops from last connection, reconnect as "write".
|
|
337
385
|
// without that we would connect in view-only mode, which will result in immediate
|
|
338
386
|
// firing of "connected" event from Container and switch of current clientId (as tracked
|
|
@@ -344,9 +392,9 @@ export class ConnectionManager {
|
|
|
344
392
|
const docService = this.serviceProvider();
|
|
345
393
|
assert(docService !== undefined, 0x2a7 /* "Container is not attached" */);
|
|
346
394
|
let connection;
|
|
347
|
-
if (
|
|
395
|
+
if (docService.policies?.storageOnly === true) {
|
|
348
396
|
connection = new NoDeltaStream();
|
|
349
|
-
this.setupNewSuccessfulConnection(connection, "read");
|
|
397
|
+
this.setupNewSuccessfulConnection(connection, "read", reason);
|
|
350
398
|
assert(this.pendingConnection === undefined, 0x2b3 /* "logic error" */);
|
|
351
399
|
return;
|
|
352
400
|
}
|
|
@@ -356,17 +404,23 @@ export class ConnectionManager {
|
|
|
356
404
|
let lastError;
|
|
357
405
|
const abortController = new AbortController();
|
|
358
406
|
const abortSignal = abortController.signal;
|
|
359
|
-
this.pendingConnection = {
|
|
407
|
+
this.pendingConnection = {
|
|
408
|
+
abort: () => {
|
|
409
|
+
abortController.abort();
|
|
410
|
+
},
|
|
411
|
+
connectionMode: requestedMode,
|
|
412
|
+
};
|
|
413
|
+
this.props.establishConnectionHandler(reason);
|
|
360
414
|
// This loop will keep trying to connect until successful, with a delay between each iteration.
|
|
361
415
|
while (connection === undefined) {
|
|
362
|
-
if (this.
|
|
416
|
+
if (this._disposed) {
|
|
363
417
|
throw new Error("Attempting to connect a closed DeltaManager");
|
|
364
418
|
}
|
|
365
419
|
if (abortSignal.aborted === true) {
|
|
366
420
|
this.logger.sendTelemetryEvent({
|
|
367
421
|
eventName: "ConnectionAttemptCancelled",
|
|
368
422
|
attempts: connectRepeatCount,
|
|
369
|
-
duration:
|
|
423
|
+
duration: formatTick(performance.now() - connectStartTime),
|
|
370
424
|
connectionEstablished: false,
|
|
371
425
|
});
|
|
372
426
|
return;
|
|
@@ -374,17 +428,42 @@ export class ConnectionManager {
|
|
|
374
428
|
connectRepeatCount++;
|
|
375
429
|
try {
|
|
376
430
|
this.client.mode = requestedMode;
|
|
377
|
-
connection = await docService.connectToDeltaStream(
|
|
431
|
+
connection = await docService.connectToDeltaStream({
|
|
432
|
+
...this.client,
|
|
433
|
+
mode: requestedMode,
|
|
434
|
+
});
|
|
378
435
|
if (connection.disposed) {
|
|
379
436
|
// Nobody observed this connection, so drop it on the floor and retry.
|
|
380
437
|
this.logger.sendTelemetryEvent({ eventName: "ReceivedClosedConnection" });
|
|
381
438
|
connection = undefined;
|
|
382
439
|
}
|
|
440
|
+
this.logger.sendTelemetryEvent({
|
|
441
|
+
eventName: "ConnectionReceived",
|
|
442
|
+
connected: connection !== undefined && connection.disposed === false,
|
|
443
|
+
}, undefined, LogLevel.verbose);
|
|
383
444
|
}
|
|
384
445
|
catch (origError) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
connection
|
|
446
|
+
this.logger.sendTelemetryEvent({
|
|
447
|
+
eventName: "ConnectToDeltaStreamException",
|
|
448
|
+
connected: connection !== undefined && connection.disposed === false,
|
|
449
|
+
}, undefined, LogLevel.verbose);
|
|
450
|
+
if (isDeltaStreamConnectionForbiddenError(origError)) {
|
|
451
|
+
connection = new NoDeltaStream(origError.storageOnlyReason, {
|
|
452
|
+
text: origError.message,
|
|
453
|
+
error: origError,
|
|
454
|
+
});
|
|
455
|
+
requestedMode = "read";
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
else if (isFluidError(origError) &&
|
|
459
|
+
// eslint-disable-next-line import/no-deprecated
|
|
460
|
+
origError.errorType === DriverErrorType.outOfStorageError) {
|
|
461
|
+
// If we get out of storage error from calling joinsession, then use the NoDeltaStream object so
|
|
462
|
+
// that user can at least load the container.
|
|
463
|
+
connection = new NoDeltaStream(undefined, {
|
|
464
|
+
text: origError.message,
|
|
465
|
+
error: origError,
|
|
466
|
+
});
|
|
388
467
|
requestedMode = "read";
|
|
389
468
|
break;
|
|
390
469
|
}
|
|
@@ -399,15 +478,36 @@ export class ConnectionManager {
|
|
|
399
478
|
attempts: connectRepeatCount,
|
|
400
479
|
delay: delayMs,
|
|
401
480
|
eventName: "DeltaConnectionFailureToConnect",
|
|
402
|
-
duration:
|
|
481
|
+
duration: formatTick(performance.now() - connectStartTime),
|
|
403
482
|
}, origError);
|
|
404
483
|
lastError = origError;
|
|
484
|
+
const waitStartTime = performance.now();
|
|
405
485
|
const retryDelayFromError = getRetryDelayFromError(origError);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
486
|
+
// If the error told us to wait or browser signals us that we are offline, then calculate the time we
|
|
487
|
+
// want to wait for before retrying. then we wait for that time. If the error didn't tell us to wait,
|
|
488
|
+
// let's still wait a little bit before retrying. We can skip this delay if we're confident we're offline,
|
|
489
|
+
// because we probably just need to wait to come back online. But we never have strong signal of being
|
|
490
|
+
// offline, so we at least wait for sometime.
|
|
491
|
+
if (retryDelayFromError !== undefined || globalThis.navigator?.onLine !== false) {
|
|
492
|
+
delayMs = calculateMaxWaitTime(delayMs, origError);
|
|
409
493
|
}
|
|
410
|
-
|
|
494
|
+
// Raise event in case the delay was there.
|
|
495
|
+
this.props.reconnectionDelayHandler(delayMs, origError);
|
|
496
|
+
await new Promise((resolve) => {
|
|
497
|
+
setTimeout(resolve, delayMs);
|
|
498
|
+
});
|
|
499
|
+
// If we believe we're offline, we assume there's no point in trying until we at least think we're online.
|
|
500
|
+
// NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
|
|
501
|
+
// should probably live in the driver.
|
|
502
|
+
await waitForOnline();
|
|
503
|
+
this.logger.sendPerformanceEvent({
|
|
504
|
+
eventName: "WaitBetweenConnectionAttempts",
|
|
505
|
+
duration: performance.now() - waitStartTime,
|
|
506
|
+
details: JSON.stringify({
|
|
507
|
+
retryDelayFromError,
|
|
508
|
+
delayMs,
|
|
509
|
+
}),
|
|
510
|
+
});
|
|
411
511
|
}
|
|
412
512
|
}
|
|
413
513
|
// If we retried more than once, log an event about how long it took (this will not log to error table)
|
|
@@ -415,44 +515,50 @@ export class ConnectionManager {
|
|
|
415
515
|
logNetworkFailure(this.logger, {
|
|
416
516
|
eventName: "MultipleDeltaConnectionFailures",
|
|
417
517
|
attempts: connectRepeatCount,
|
|
418
|
-
duration:
|
|
518
|
+
duration: formatTick(performance.now() - connectStartTime),
|
|
419
519
|
}, lastError);
|
|
420
520
|
}
|
|
421
|
-
// Check for abort signal after while loop as well
|
|
422
|
-
if (abortSignal.aborted === true) {
|
|
521
|
+
// Check for abort signal after while loop as well or we've been disposed
|
|
522
|
+
if (abortSignal.aborted === true || this._disposed) {
|
|
423
523
|
connection.dispose();
|
|
424
524
|
this.logger.sendTelemetryEvent({
|
|
425
525
|
eventName: "ConnectionAttemptCancelled",
|
|
426
526
|
attempts: connectRepeatCount,
|
|
427
|
-
duration:
|
|
527
|
+
duration: formatTick(performance.now() - connectStartTime),
|
|
428
528
|
connectionEstablished: true,
|
|
429
529
|
});
|
|
430
530
|
return;
|
|
431
531
|
}
|
|
432
|
-
this.setupNewSuccessfulConnection(connection, requestedMode);
|
|
532
|
+
this.setupNewSuccessfulConnection(connection, requestedMode, reason);
|
|
433
533
|
}
|
|
434
534
|
/**
|
|
435
|
-
* Start the connection. Any error should result in container being
|
|
436
|
-
* And report the error if it
|
|
535
|
+
* Start the connection. Any error should result in container being closed.
|
|
536
|
+
* And report the error if it escapes for any reason.
|
|
437
537
|
* @param args - The connection arguments
|
|
438
538
|
*/
|
|
439
|
-
triggerConnect(connectionMode) {
|
|
440
|
-
|
|
539
|
+
triggerConnect(reason, connectionMode) {
|
|
540
|
+
// reconnect() includes async awaits, and that causes potential race conditions
|
|
541
|
+
// where we might already have a connection. If it were to happen, it's possible that we will connect
|
|
542
|
+
// with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
|
|
543
|
+
// fine (if needed, reconnect flow will get triggered again). Places where new mode matters should encode it
|
|
544
|
+
// directly in connectCore - see this.shouldJoinWrite() test as an example.
|
|
545
|
+
// assert(this.connection === undefined, 0x239 /* "called only in disconnected state" */);
|
|
441
546
|
if (this.reconnectMode !== ReconnectMode.Enabled) {
|
|
442
547
|
return;
|
|
443
548
|
}
|
|
444
|
-
this.connect(connectionMode);
|
|
549
|
+
this.connect(reason, connectionMode);
|
|
445
550
|
}
|
|
446
551
|
/**
|
|
447
552
|
* Disconnect the current connection.
|
|
448
553
|
* @param reason - Text description of disconnect reason to emit with disconnect event
|
|
554
|
+
* @param error - Error causing the disconnect if any.
|
|
449
555
|
* @returns A boolean that indicates if there was an existing connection (or pending connection) to disconnect
|
|
450
556
|
*/
|
|
451
557
|
disconnectFromDeltaStream(reason) {
|
|
452
558
|
this.pendingReconnect = false;
|
|
453
559
|
if (this.connection === undefined) {
|
|
454
560
|
if (this.pendingConnection !== undefined) {
|
|
455
|
-
this.cancelConnection();
|
|
561
|
+
this.cancelConnection(reason);
|
|
456
562
|
return true;
|
|
457
563
|
}
|
|
458
564
|
return false;
|
|
@@ -463,7 +569,7 @@ export class ConnectionManager {
|
|
|
463
569
|
this.connection = undefined;
|
|
464
570
|
// Remove listeners first so we don't try to retrigger this flow accidentally through reconnectOnError
|
|
465
571
|
connection.off("op", this.opHandler);
|
|
466
|
-
connection.off("signal", this.
|
|
572
|
+
connection.off("signal", this.signalHandler);
|
|
467
573
|
connection.off("nack", this.nackHandler);
|
|
468
574
|
connection.off("disconnect", this.disconnectHandlerInternal);
|
|
469
575
|
connection.off("error", this.errorHandler);
|
|
@@ -471,48 +577,60 @@ export class ConnectionManager {
|
|
|
471
577
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
472
578
|
this._outbound.pause();
|
|
473
579
|
this._outbound.clear();
|
|
474
|
-
this.props.disconnectHandler(reason);
|
|
475
580
|
connection.dispose();
|
|
581
|
+
this.props.disconnectHandler(reason);
|
|
476
582
|
this._connectionVerboseProps = {};
|
|
477
583
|
return true;
|
|
478
584
|
}
|
|
479
585
|
/**
|
|
480
586
|
* Cancel in-progress connection attempt.
|
|
481
587
|
*/
|
|
482
|
-
cancelConnection() {
|
|
588
|
+
cancelConnection(reason) {
|
|
483
589
|
assert(this.pendingConnection !== undefined, 0x345 /* this.pendingConnection is undefined when trying to cancel */);
|
|
484
590
|
this.pendingConnection.abort();
|
|
485
591
|
this.pendingConnection = undefined;
|
|
486
592
|
this.logger.sendTelemetryEvent({ eventName: "ConnectionCancelReceived" });
|
|
593
|
+
this.props.cancelConnectionHandler({
|
|
594
|
+
text: `Cancel Pending Connection due to ${reason.text}`,
|
|
595
|
+
error: reason.error,
|
|
596
|
+
});
|
|
487
597
|
}
|
|
488
598
|
/**
|
|
489
599
|
* Once we've successfully gotten a connection, we need to set up state, attach event listeners, and process
|
|
490
600
|
* initial messages.
|
|
491
601
|
* @param connection - The newly established connection
|
|
492
602
|
*/
|
|
493
|
-
setupNewSuccessfulConnection(connection, requestedMode) {
|
|
603
|
+
setupNewSuccessfulConnection(connection, requestedMode, reason) {
|
|
494
604
|
// Old connection should have been cleaned up before establishing a new one
|
|
495
605
|
assert(this.connection === undefined, 0x0e6 /* "old connection exists on new connection setup" */);
|
|
496
606
|
assert(!connection.disposed, 0x28a /* "can't be disposed - Callers need to ensure that!" */);
|
|
497
607
|
this.pendingConnection = undefined;
|
|
608
|
+
const oldReadonlyValue = this.readonly;
|
|
498
609
|
this.connection = connection;
|
|
499
610
|
// Does information in scopes & mode matches?
|
|
500
611
|
// If we asked for "write" and got "read", then file is read-only
|
|
501
612
|
// But if we ask read, server can still give us write.
|
|
502
613
|
const readonly = !connection.claims.scopes.includes(ScopeType.DocWrite);
|
|
614
|
+
if (connection.mode !== requestedMode) {
|
|
615
|
+
this.logger.sendTelemetryEvent({
|
|
616
|
+
eventName: "ConnectionModeMismatch",
|
|
617
|
+
requestedMode,
|
|
618
|
+
mode: connection.mode,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
503
621
|
// This connection mode validation logic is moving to the driver layer in 0.44. These two asserts can be
|
|
504
622
|
// removed after those packages have released and become ubiquitous.
|
|
505
623
|
assert(requestedMode === "read" || readonly === (this.connectionMode === "read"), 0x0e7 /* "claims/connectionMode mismatch" */);
|
|
506
624
|
assert(!readonly || this.connectionMode === "read", 0x0e8 /* "readonly perf with write connection" */);
|
|
507
|
-
this.set_readonlyPermissions(readonly);
|
|
508
|
-
if (this.
|
|
625
|
+
this.set_readonlyPermissions(readonly, oldReadonlyValue, isNoDeltaStreamConnection(connection) ? connection.readonlyConnectionReason : undefined);
|
|
626
|
+
if (this._disposed) {
|
|
509
627
|
// Raise proper events, Log telemetry event and close connection.
|
|
510
|
-
this.disconnectFromDeltaStream("ConnectionManager already closed");
|
|
628
|
+
this.disconnectFromDeltaStream({ text: "ConnectionManager already closed" });
|
|
511
629
|
return;
|
|
512
630
|
}
|
|
513
631
|
this._outbound.resume();
|
|
514
632
|
connection.on("op", this.opHandler);
|
|
515
|
-
connection.on("signal", this.
|
|
633
|
+
connection.on("signal", this.signalHandler);
|
|
516
634
|
connection.on("nack", this.nackHandler);
|
|
517
635
|
connection.on("disconnect", this.disconnectHandlerInternal);
|
|
518
636
|
connection.on("error", this.errorHandler);
|
|
@@ -538,7 +656,8 @@ export class ConnectionManager {
|
|
|
538
656
|
this._connectionProps.connectionMode = connection.mode;
|
|
539
657
|
let last = -1;
|
|
540
658
|
if (initialMessages.length !== 0) {
|
|
541
|
-
this._connectionVerboseProps.connectionInitialOpsFrom =
|
|
659
|
+
this._connectionVerboseProps.connectionInitialOpsFrom =
|
|
660
|
+
initialMessages[0].sequenceNumber;
|
|
542
661
|
last = initialMessages[initialMessages.length - 1].sequenceNumber;
|
|
543
662
|
this._connectionVerboseProps.connectionInitialOpsTo = last + 1;
|
|
544
663
|
// Update knowledge of how far we are behind, before raising "connect" event
|
|
@@ -549,15 +668,39 @@ export class ConnectionManager {
|
|
|
549
668
|
}
|
|
550
669
|
}
|
|
551
670
|
this.props.incomingOpHandler(initialMessages, this.connectFirstConnection ? "InitialOps" : "ReconnectOps");
|
|
552
|
-
|
|
553
|
-
for (const signal of connection.initialSignals) {
|
|
554
|
-
this.props.signalHandler(signal);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
const details = ConnectionManager.detailsFromConnection(connection);
|
|
671
|
+
const details = ConnectionManager.detailsFromConnection(connection, reason);
|
|
558
672
|
details.checkpointSequenceNumber = checkpointSequenceNumber;
|
|
559
673
|
this.props.connectHandler(details);
|
|
560
674
|
this.connectFirstConnection = false;
|
|
675
|
+
// Synthesize clear & join signals out of initialClients state.
|
|
676
|
+
// This allows us to have single way to process signals, and makes it simpler to initialize
|
|
677
|
+
// protocol in Container.
|
|
678
|
+
const clearSignal = {
|
|
679
|
+
clientId: null,
|
|
680
|
+
content: JSON.stringify({
|
|
681
|
+
type: SignalType.Clear,
|
|
682
|
+
}),
|
|
683
|
+
};
|
|
684
|
+
// list of signals to process due to this new connection
|
|
685
|
+
let signalsToProcess = [clearSignal];
|
|
686
|
+
const clientJoinSignals = (connection.initialClients ?? []).map((priorClient) => ({
|
|
687
|
+
clientId: null,
|
|
688
|
+
content: JSON.stringify({
|
|
689
|
+
type: SignalType.ClientJoin,
|
|
690
|
+
content: priorClient, // ISignalClient
|
|
691
|
+
}),
|
|
692
|
+
}));
|
|
693
|
+
if (clientJoinSignals.length > 0) {
|
|
694
|
+
signalsToProcess = signalsToProcess.concat(clientJoinSignals);
|
|
695
|
+
}
|
|
696
|
+
// Unfortunately, there is no defined order between initialSignals (including join & leave signals)
|
|
697
|
+
// and connection.initialClients. In practice, connection.initialSignals quite often contains join signal
|
|
698
|
+
// for "self" and connection.initialClients does not contain "self", so we have to process them after
|
|
699
|
+
// "clear" signal above.
|
|
700
|
+
if (connection.initialSignals !== undefined && connection.initialSignals.length > 0) {
|
|
701
|
+
signalsToProcess = signalsToProcess.concat(connection.initialSignals);
|
|
702
|
+
}
|
|
703
|
+
this.props.signalHandler(signalsToProcess);
|
|
561
704
|
}
|
|
562
705
|
/**
|
|
563
706
|
* Disconnect the current connection and reconnect. Closes the container if it fails.
|
|
@@ -567,8 +710,7 @@ export class ConnectionManager {
|
|
|
567
710
|
* @returns A promise that resolves when the connection is reestablished or we stop trying
|
|
568
711
|
*/
|
|
569
712
|
reconnectOnError(requestedMode, error) {
|
|
570
|
-
this.reconnect(requestedMode, error.message, error)
|
|
571
|
-
.catch(this.props.closeHandler);
|
|
713
|
+
this.reconnect(requestedMode, { text: error.message, error }).catch(this.props.closeHandler);
|
|
572
714
|
}
|
|
573
715
|
/**
|
|
574
716
|
* Disconnect the current connection and reconnect.
|
|
@@ -577,20 +719,20 @@ export class ConnectionManager {
|
|
|
577
719
|
* @param error - Error reconnect information including whether or not to reconnect
|
|
578
720
|
* @returns A promise that resolves when the connection is reestablished or we stop trying
|
|
579
721
|
*/
|
|
580
|
-
async reconnect(requestedMode,
|
|
722
|
+
async reconnect(requestedMode, reason) {
|
|
581
723
|
// We quite often get protocol errors before / after observing nack/disconnect
|
|
582
724
|
// we do not want to run through same sequence twice.
|
|
583
725
|
// If we're already disconnected/disconnecting it's not appropriate to call this again.
|
|
584
726
|
assert(this.connection !== undefined, 0x0eb /* "Missing connection for reconnect" */);
|
|
585
|
-
this.disconnectFromDeltaStream(
|
|
727
|
+
this.disconnectFromDeltaStream(reason);
|
|
586
728
|
// We will always trigger reconnect, even if canRetry is false.
|
|
587
729
|
// Any truly fatal error state will result in container close upon attempted reconnect,
|
|
588
730
|
// which is a preferable to closing abruptly when a live connection fails.
|
|
589
|
-
if (error
|
|
731
|
+
if (reason.error?.canRetry === false) {
|
|
590
732
|
this.logger.sendTelemetryEvent({
|
|
591
733
|
eventName: "reconnectingDespiteFatalError",
|
|
592
734
|
reconnectMode: this.reconnectMode,
|
|
593
|
-
}, error);
|
|
735
|
+
}, reason.error);
|
|
594
736
|
}
|
|
595
737
|
if (this.reconnectMode === ReconnectMode.Never) {
|
|
596
738
|
// Do not raise container error if we are closing just because we lost connection.
|
|
@@ -599,18 +741,29 @@ export class ConnectionManager {
|
|
|
599
741
|
this.props.closeHandler();
|
|
600
742
|
}
|
|
601
743
|
// If closed then we can't reconnect
|
|
602
|
-
if (this.
|
|
744
|
+
if (this._disposed || this.reconnectMode !== ReconnectMode.Enabled) {
|
|
603
745
|
return;
|
|
604
746
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
747
|
+
// If the error tells us to wait before retrying, then do so.
|
|
748
|
+
const delayMs = getRetryDelayFromError(reason.error);
|
|
749
|
+
if (reason.error !== undefined && delayMs !== undefined) {
|
|
750
|
+
this.props.reconnectionDelayHandler(delayMs, reason.error);
|
|
751
|
+
await new Promise((resolve) => {
|
|
752
|
+
setTimeout(resolve, delayMs);
|
|
753
|
+
});
|
|
609
754
|
}
|
|
610
|
-
|
|
755
|
+
// If we believe we're offline, we assume there's no point in trying again until we at least think we're online.
|
|
756
|
+
// NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
|
|
757
|
+
// should probably live in the driver.
|
|
758
|
+
await waitForOnline();
|
|
759
|
+
this.triggerConnect({
|
|
760
|
+
text: reason.error !== undefined
|
|
761
|
+
? "Reconnecting due to Error"
|
|
762
|
+
: `Reconnecting due to: ${reason.text}`,
|
|
763
|
+
error: reason.error,
|
|
764
|
+
}, requestedMode);
|
|
611
765
|
}
|
|
612
766
|
prepareMessageToSend(message) {
|
|
613
|
-
var _a, _b;
|
|
614
767
|
if (this.readonly === true) {
|
|
615
768
|
assert(this.readOnlyInfo.readonly === true, 0x1f0 /* "Unexpected mismatch in readonly" */);
|
|
616
769
|
const error = new GenericError("deltaManagerReadonlySubmit", undefined /* error */, {
|
|
@@ -618,6 +771,7 @@ export class ConnectionManager {
|
|
|
618
771
|
forcedReadonly: this.readOnlyInfo.forced,
|
|
619
772
|
readonlyPermissions: this.readOnlyInfo.permissions,
|
|
620
773
|
storageOnly: this.readOnlyInfo.storageOnly,
|
|
774
|
+
storageOnlyReason: this.readOnlyInfo.storageOnlyReason,
|
|
621
775
|
});
|
|
622
776
|
this.props.closeHandler(error);
|
|
623
777
|
return undefined;
|
|
@@ -626,22 +780,25 @@ export class ConnectionManager {
|
|
|
626
780
|
// we keep info about old connection as long as possible to be able to account for all non-acked ops
|
|
627
781
|
// that we pick up on next connection.
|
|
628
782
|
assert(!!this.connection, 0x0e4 /* "Lost old connection!" */);
|
|
629
|
-
if (this.lastSubmittedClientId !==
|
|
630
|
-
this.lastSubmittedClientId =
|
|
783
|
+
if (this.lastSubmittedClientId !== this.connection?.clientId) {
|
|
784
|
+
this.lastSubmittedClientId = this.connection?.clientId;
|
|
631
785
|
this.clientSequenceNumber = 0;
|
|
632
786
|
this.clientSequenceNumberObserved = 0;
|
|
633
787
|
}
|
|
634
|
-
if (message
|
|
635
|
-
this.
|
|
788
|
+
if (!isRuntimeMessage(message)) {
|
|
789
|
+
this.localOpsToIgnore++;
|
|
636
790
|
}
|
|
637
791
|
else {
|
|
638
|
-
this.
|
|
792
|
+
this.localOpsToIgnore = 0;
|
|
639
793
|
}
|
|
640
|
-
return
|
|
794
|
+
return {
|
|
795
|
+
...message,
|
|
796
|
+
clientSequenceNumber: ++this.clientSequenceNumber,
|
|
797
|
+
};
|
|
641
798
|
}
|
|
642
|
-
submitSignal(content) {
|
|
799
|
+
submitSignal(content, targetClientId) {
|
|
643
800
|
if (this.connection !== undefined) {
|
|
644
|
-
this.connection.submitSignal(content);
|
|
801
|
+
this.connection.submitSignal(content, targetClientId);
|
|
645
802
|
}
|
|
646
803
|
else {
|
|
647
804
|
this.logger.sendErrorEvent({ eventName: "submitSignalDisconnected" });
|
|
@@ -658,10 +815,12 @@ export class ConnectionManager {
|
|
|
658
815
|
if (this.connectionMode === "read") {
|
|
659
816
|
if (!this.pendingReconnect) {
|
|
660
817
|
this.pendingReconnect = true;
|
|
661
|
-
Promise.resolve()
|
|
662
|
-
|
|
818
|
+
Promise.resolve()
|
|
819
|
+
.then(async () => {
|
|
820
|
+
if (this.pendingReconnect) {
|
|
821
|
+
// still valid?
|
|
663
822
|
await this.reconnect("write", // connectionMode
|
|
664
|
-
"Switch to write");
|
|
823
|
+
{ text: "Switch to write" });
|
|
665
824
|
}
|
|
666
825
|
})
|
|
667
826
|
.catch(() => { });
|
|
@@ -674,7 +833,8 @@ export class ConnectionManager {
|
|
|
674
833
|
beforeProcessingIncomingOp(message) {
|
|
675
834
|
// if we have connection, and message is local, then we better treat is as local!
|
|
676
835
|
assert(this.clientId !== message.clientId || this.lastSubmittedClientId === message.clientId, 0x0ee /* "Not accounting local messages correctly" */);
|
|
677
|
-
if (this.lastSubmittedClientId !== undefined &&
|
|
836
|
+
if (this.lastSubmittedClientId !== undefined &&
|
|
837
|
+
this.lastSubmittedClientId === message.clientId) {
|
|
678
838
|
const clientSequenceNumber = message.clientSequenceNumber;
|
|
679
839
|
assert(this.clientSequenceNumberObserved < clientSequenceNumber, 0x0ef /* "client seq# not growing" */);
|
|
680
840
|
assert(clientSequenceNumber <= this.clientSequenceNumber, 0x0f0 /* "Incoming local client seq# > generated by this client" */);
|
|
@@ -693,11 +853,11 @@ export class ConnectionManager {
|
|
|
693
853
|
// Clients need to be able to transition to "read" state after some time of inactivity!
|
|
694
854
|
// Note - this may close container!
|
|
695
855
|
this.reconnect("read", // connectionMode
|
|
696
|
-
"Switch to read").catch((error) => {
|
|
856
|
+
{ text: "Switch to read" }).catch((error) => {
|
|
697
857
|
this.logger.sendErrorEvent({ eventName: "SwitchToReadConnection" }, error);
|
|
698
858
|
});
|
|
699
859
|
}
|
|
700
860
|
}
|
|
701
861
|
}
|
|
702
862
|
}
|
|
703
|
-
//# sourceMappingURL=connectionManager.
|
|
863
|
+
//# sourceMappingURL=connectionManager.mjs.map
|