@fluidframework/odsp-driver 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -1
- package/dist/WriteBufferUtils.d.ts.map +1 -1
- package/dist/WriteBufferUtils.js +0 -1
- package/dist/WriteBufferUtils.js.map +1 -1
- package/dist/contractsPublic.d.ts.map +1 -1
- package/dist/contractsPublic.js.map +1 -1
- package/dist/createFile.d.ts +2 -6
- package/dist/createFile.d.ts.map +1 -1
- package/dist/createFile.js +13 -125
- package/dist/createFile.js.map +1 -1
- package/dist/createNewUtils.d.ts +16 -0
- package/dist/createNewUtils.d.ts.map +1 -1
- package/dist/createNewUtils.js +131 -1
- package/dist/createNewUtils.js.map +1 -1
- package/dist/fetchSnapshot.js +1 -1
- package/dist/fetchSnapshot.js.map +1 -1
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.d.ts.map +1 -1
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.js +1 -1
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.js.map +1 -1
- package/dist/odspDelayLoadedDeltaStream.d.ts +75 -0
- package/dist/odspDelayLoadedDeltaStream.d.ts.map +1 -0
- package/dist/odspDelayLoadedDeltaStream.js +259 -0
- package/dist/odspDelayLoadedDeltaStream.js.map +1 -0
- package/dist/odspDocumentDeltaConnection.d.ts +1 -3
- package/dist/odspDocumentDeltaConnection.d.ts.map +1 -1
- package/dist/odspDocumentDeltaConnection.js +6 -6
- package/dist/odspDocumentDeltaConnection.js.map +1 -1
- package/dist/odspDocumentService.d.ts +10 -25
- package/dist/odspDocumentService.d.ts.map +1 -1
- package/dist/odspDocumentService.js +71 -204
- package/dist/odspDocumentService.js.map +1 -1
- package/dist/odspDocumentServiceFactory.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactory.js +1 -2
- package/dist/odspDocumentServiceFactory.js.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts +1 -3
- package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.js +3 -3
- package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
- package/dist/odspDocumentServiceFactoryWithCodeSplit.d.ts +4 -0
- package/dist/odspDocumentServiceFactoryWithCodeSplit.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactoryWithCodeSplit.js +5 -20
- package/dist/odspDocumentServiceFactoryWithCodeSplit.js.map +1 -1
- package/dist/odspDocumentStorageManager.d.ts.map +1 -1
- package/dist/odspDocumentStorageManager.js +0 -2
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspDocumentStorageServiceBase.d.ts.map +1 -1
- package/dist/odspDocumentStorageServiceBase.js +0 -2
- package/dist/odspDocumentStorageServiceBase.js.map +1 -1
- package/dist/odspSummaryUploadManager.d.ts.map +1 -1
- package/dist/odspSummaryUploadManager.js +0 -2
- package/dist/odspSummaryUploadManager.js.map +1 -1
- package/dist/odspUtils.d.ts +10 -1
- package/dist/odspUtils.d.ts.map +1 -1
- package/dist/odspUtils.js +5 -1
- package/dist/odspUtils.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/zipItDataRepresentationUtils.d.ts.map +1 -1
- package/dist/zipItDataRepresentationUtils.js +0 -1
- package/dist/zipItDataRepresentationUtils.js.map +1 -1
- package/lib/WriteBufferUtils.d.ts.map +1 -1
- package/lib/WriteBufferUtils.js +0 -1
- package/lib/WriteBufferUtils.js.map +1 -1
- package/lib/contractsPublic.d.ts.map +1 -1
- package/lib/contractsPublic.js.map +1 -1
- package/lib/createFile.d.ts +2 -6
- package/lib/createFile.d.ts.map +1 -1
- package/lib/createFile.js +15 -126
- package/lib/createFile.js.map +1 -1
- package/lib/createNewUtils.d.ts +16 -0
- package/lib/createNewUtils.d.ts.map +1 -1
- package/lib/createNewUtils.js +129 -1
- package/lib/createNewUtils.js.map +1 -1
- package/lib/fetchSnapshot.js +1 -1
- package/lib/fetchSnapshot.js.map +1 -1
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.d.ts.map +1 -1
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.js +1 -1
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.js.map +1 -1
- package/lib/odspDelayLoadedDeltaStream.d.ts +75 -0
- package/lib/odspDelayLoadedDeltaStream.d.ts.map +1 -0
- package/lib/odspDelayLoadedDeltaStream.js +255 -0
- package/lib/odspDelayLoadedDeltaStream.js.map +1 -0
- package/lib/odspDocumentDeltaConnection.d.ts +1 -3
- package/lib/odspDocumentDeltaConnection.d.ts.map +1 -1
- package/lib/odspDocumentDeltaConnection.js +6 -6
- package/lib/odspDocumentDeltaConnection.js.map +1 -1
- package/lib/odspDocumentService.d.ts +10 -25
- package/lib/odspDocumentService.d.ts.map +1 -1
- package/lib/odspDocumentService.js +56 -207
- package/lib/odspDocumentService.js.map +1 -1
- package/lib/odspDocumentServiceFactory.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactory.js +1 -2
- package/lib/odspDocumentServiceFactory.js.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts +1 -3
- package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.js +3 -3
- package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
- package/lib/odspDocumentServiceFactoryWithCodeSplit.d.ts +4 -0
- package/lib/odspDocumentServiceFactoryWithCodeSplit.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactoryWithCodeSplit.js +5 -1
- package/lib/odspDocumentServiceFactoryWithCodeSplit.js.map +1 -1
- package/lib/odspDocumentStorageManager.d.ts.map +1 -1
- package/lib/odspDocumentStorageManager.js +0 -2
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspDocumentStorageServiceBase.d.ts.map +1 -1
- package/lib/odspDocumentStorageServiceBase.js +0 -2
- package/lib/odspDocumentStorageServiceBase.js.map +1 -1
- package/lib/odspSummaryUploadManager.d.ts.map +1 -1
- package/lib/odspSummaryUploadManager.js +0 -2
- package/lib/odspSummaryUploadManager.js.map +1 -1
- package/lib/odspUtils.d.ts +10 -1
- package/lib/odspUtils.d.ts.map +1 -1
- package/lib/odspUtils.js +3 -0
- package/lib/odspUtils.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/zipItDataRepresentationUtils.d.ts.map +1 -1
- package/lib/zipItDataRepresentationUtils.js +0 -1
- package/lib/zipItDataRepresentationUtils.js.map +1 -1
- package/package.json +13 -12
- package/src/WriteBufferUtils.ts +0 -1
- package/src/contractsPublic.ts +0 -1
- package/src/createFile.ts +23 -168
- package/src/createNewUtils.ts +188 -2
- package/src/fetchSnapshot.ts +1 -1
- package/src/localOdspDriver/localOdspDocumentServiceFactory.ts +0 -1
- package/src/odspDelayLoadedDeltaStream.ts +359 -0
- package/src/odspDocumentDeltaConnection.ts +4 -7
- package/src/odspDocumentService.ts +63 -283
- package/src/odspDocumentServiceFactory.ts +0 -2
- package/src/odspDocumentServiceFactoryCore.ts +1 -3
- package/src/odspDocumentServiceFactoryWithCodeSplit.ts +4 -1
- package/src/odspDocumentStorageManager.ts +0 -4
- package/src/odspDocumentStorageServiceBase.ts +0 -3
- package/src/odspSummaryUploadManager.ts +0 -4
- package/src/odspUtils.ts +16 -1
- package/src/packageVersion.ts +1 -1
- package/src/zipItDataRepresentationUtils.ts +0 -1
- package/dist/getSocketIo.d.ts +0 -11
- package/dist/getSocketIo.d.ts.map +0 -1
- package/dist/getSocketIo.js +0 -20
- package/dist/getSocketIo.js.map +0 -1
- package/lib/getSocketIo.d.ts +0 -11
- package/lib/getSocketIo.d.ts.map +0 -1
- package/lib/getSocketIo.js +0 -13
- package/lib/getSocketIo.js.map +0 -1
- package/src/getSocketIo.ts +0 -14
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { assert, performance } from "@fluidframework/common-utils";
|
|
7
|
+
import {
|
|
8
|
+
IFluidErrorBase,
|
|
9
|
+
MonitoringContext,
|
|
10
|
+
normalizeError,
|
|
11
|
+
} from "@fluidframework/telemetry-utils";
|
|
12
|
+
import {
|
|
13
|
+
IDocumentDeltaConnection,
|
|
14
|
+
IResolvedUrl,
|
|
15
|
+
IDocumentServicePolicies,
|
|
16
|
+
DriverErrorType,
|
|
17
|
+
} from "@fluidframework/driver-definitions";
|
|
18
|
+
import { canRetryOnError, DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
|
|
19
|
+
import {
|
|
20
|
+
IClient,
|
|
21
|
+
ISequencedDocumentMessage,
|
|
22
|
+
} from "@fluidframework/protocol-definitions";
|
|
23
|
+
import {
|
|
24
|
+
IOdspResolvedUrl,
|
|
25
|
+
TokenFetchOptions,
|
|
26
|
+
HostStoragePolicy,
|
|
27
|
+
InstrumentedStorageTokenFetcher,
|
|
28
|
+
OdspErrorType,
|
|
29
|
+
} from "@fluidframework/odsp-driver-definitions";
|
|
30
|
+
import { hasFacetCodes } from "@fluidframework/odsp-doclib-utils";
|
|
31
|
+
import { ISocketStorageDiscovery } from "./contracts";
|
|
32
|
+
import { IOdspCache } from "./odspCache";
|
|
33
|
+
import { OdspDocumentDeltaConnection } from "./odspDocumentDeltaConnection";
|
|
34
|
+
import { getWithRetryForTokenRefresh, TokenFetchOptionsEx } from "./odspUtils";
|
|
35
|
+
import { fetchJoinSession } from "./vroom";
|
|
36
|
+
import { EpochTracker } from "./epochTracker";
|
|
37
|
+
import { pkgVersion as driverVersion } from "./packageVersion";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* This OdspDelayLoadedDeltaStream is used by OdspDocumentService.ts to delay load the delta connection
|
|
41
|
+
* as they are not on critical path of loading a container.
|
|
42
|
+
*/
|
|
43
|
+
export class OdspDelayLoadedDeltaStream {
|
|
44
|
+
// Timer which runs and executes the join session call after intervals.
|
|
45
|
+
private joinSessionRefreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
46
|
+
|
|
47
|
+
private readonly joinSessionKey: string;
|
|
48
|
+
|
|
49
|
+
private currentConnection?: OdspDocumentDeltaConnection;
|
|
50
|
+
|
|
51
|
+
private _relayServiceTenantAndSessionId: string | undefined;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param odspResolvedUrl - resolved url identifying document that will be managed by this service instance.
|
|
55
|
+
* @param policies - Document service policies.
|
|
56
|
+
* @param getStorageToken - function that can provide the storage token. This is is also referred to as
|
|
57
|
+
* the "Vroom" token in SPO.
|
|
58
|
+
* @param getWebsocketToken - function that can provide a token for accessing the web socket. This is also referred
|
|
59
|
+
* to as the "Push" token in SPO. If undefined then websocket token is expected to be returned with joinSession
|
|
60
|
+
* response payload.
|
|
61
|
+
* @param mc - a logger that can capture performance and diagnostic information
|
|
62
|
+
* @param cache - This caches response for joinSession.
|
|
63
|
+
* @param hostPolicy - host constructed policy which customizes service behavior.
|
|
64
|
+
* @param epochTracker - This helper class which adds epoch to backend calls made by this service instance.
|
|
65
|
+
* @param opsReceived - To register the ops received through socket.
|
|
66
|
+
* @param socketReferenceKeyPrefix - (optional) prefix to isolate socket reuse cache
|
|
67
|
+
*/
|
|
68
|
+
public constructor(
|
|
69
|
+
public readonly odspResolvedUrl: IOdspResolvedUrl,
|
|
70
|
+
public policies: IDocumentServicePolicies,
|
|
71
|
+
private readonly getStorageToken: InstrumentedStorageTokenFetcher,
|
|
72
|
+
private readonly getWebsocketToken: ((options: TokenFetchOptions) => Promise<string | null>) | undefined,
|
|
73
|
+
private readonly mc: MonitoringContext,
|
|
74
|
+
private readonly cache: IOdspCache,
|
|
75
|
+
private readonly hostPolicy: HostStoragePolicy,
|
|
76
|
+
private readonly epochTracker: EpochTracker,
|
|
77
|
+
private readonly opsReceived: (ops: ISequencedDocumentMessage[]) => void,
|
|
78
|
+
private readonly socketReferenceKeyPrefix?: string,
|
|
79
|
+
) {
|
|
80
|
+
this.joinSessionKey = `${this.odspResolvedUrl.hashedDocumentId}/joinsession`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public get resolvedUrl(): IResolvedUrl {
|
|
84
|
+
return this.odspResolvedUrl;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public get currentDeltaConnection(): OdspDocumentDeltaConnection | undefined {
|
|
88
|
+
return this.currentConnection;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public get relayServiceTenantAndSessionId(): string | undefined {
|
|
92
|
+
return this._relayServiceTenantAndSessionId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Annotate the given error indicating which connection step failed */
|
|
96
|
+
private annotateConnectionError(
|
|
97
|
+
error: any,
|
|
98
|
+
failedConnectionStep: string,
|
|
99
|
+
separateTokenRequest: boolean,
|
|
100
|
+
): IFluidErrorBase {
|
|
101
|
+
return normalizeError(error, { props: {
|
|
102
|
+
failedConnectionStep,
|
|
103
|
+
separateTokenRequest,
|
|
104
|
+
} });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Connects to a delta stream endpoint for emitting ops.
|
|
109
|
+
*
|
|
110
|
+
* @returns returns the document delta stream service for onedrive/sharepoint driver.
|
|
111
|
+
*/
|
|
112
|
+
public async connectToDeltaStream(client: IClient): Promise<IDocumentDeltaConnection> {
|
|
113
|
+
assert(this.currentConnection === undefined, 0x4ad /* Should not be called when connection is already present! */);
|
|
114
|
+
// Attempt to connect twice, in case we used expired token.
|
|
115
|
+
return getWithRetryForTokenRefresh<IDocumentDeltaConnection>(async (options) => {
|
|
116
|
+
// Presence of getWebsocketToken callback dictates whether callback is used for fetching
|
|
117
|
+
// websocket token or whether it is returned with joinSession response payload
|
|
118
|
+
const requestWebsocketTokenFromJoinSession = this.getWebsocketToken === undefined;
|
|
119
|
+
const websocketTokenPromise = requestWebsocketTokenFromJoinSession
|
|
120
|
+
? Promise.resolve(null)
|
|
121
|
+
: this.getWebsocketToken!(options);
|
|
122
|
+
|
|
123
|
+
const annotateAndRethrowConnectionError = (step: string) => (error: any) => {
|
|
124
|
+
throw this.annotateConnectionError(error, step, !requestWebsocketTokenFromJoinSession);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const joinSessionPromise = this.joinSession(requestWebsocketTokenFromJoinSession, options);
|
|
128
|
+
const [websocketEndpoint, websocketToken] =
|
|
129
|
+
await Promise.all([
|
|
130
|
+
joinSessionPromise.catch(annotateAndRethrowConnectionError("joinSession")),
|
|
131
|
+
websocketTokenPromise.catch(annotateAndRethrowConnectionError("getWebsocketToken")),
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const finalWebsocketToken = websocketToken ?? (websocketEndpoint.socketToken ?? null);
|
|
135
|
+
if (finalWebsocketToken === null) {
|
|
136
|
+
throw this.annotateConnectionError(
|
|
137
|
+
new NonRetryableError(
|
|
138
|
+
"Websocket token is null",
|
|
139
|
+
OdspErrorType.fetchTokenError,
|
|
140
|
+
{ driverVersion },
|
|
141
|
+
),
|
|
142
|
+
"getWebsocketToken",
|
|
143
|
+
!requestWebsocketTokenFromJoinSession);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const connection = await this.createDeltaConnection(
|
|
147
|
+
websocketEndpoint.tenantId,
|
|
148
|
+
websocketEndpoint.id,
|
|
149
|
+
finalWebsocketToken,
|
|
150
|
+
client,
|
|
151
|
+
websocketEndpoint.deltaStreamSocketUrl);
|
|
152
|
+
connection.on("op", (documentId, ops: ISequencedDocumentMessage[]) => {
|
|
153
|
+
this.opsReceived(ops);
|
|
154
|
+
});
|
|
155
|
+
// On disconnect with 401/403 error code, we can just clear the joinSession cache as we will again
|
|
156
|
+
// get the auth error on reconnecting and face latency.
|
|
157
|
+
connection.once("disconnect", (error: any) => {
|
|
158
|
+
// Clear the join session refresh timer so that it can be restarted on reconnection.
|
|
159
|
+
this.clearJoinSessionTimer();
|
|
160
|
+
if (typeof error === "object" && error !== null
|
|
161
|
+
&& error.errorType === DriverErrorType.authorizationError) {
|
|
162
|
+
this.cache.sessionJoinCache.remove(this.joinSessionKey);
|
|
163
|
+
}
|
|
164
|
+
// If we hit this assert, it means that "disconnect" event is emitted before the connection went through
|
|
165
|
+
// dispose flow which is not correct and could lead to a bunch of erros.
|
|
166
|
+
assert(connection.disposed, 0x4ae /* Connection should be disposed by now */);
|
|
167
|
+
this.currentConnection = undefined;
|
|
168
|
+
});
|
|
169
|
+
this.currentConnection = connection;
|
|
170
|
+
return connection;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.cache.sessionJoinCache.remove(this.joinSessionKey);
|
|
173
|
+
|
|
174
|
+
const normalizedError = this.annotateConnectionError(
|
|
175
|
+
error,
|
|
176
|
+
"createDeltaConnection",
|
|
177
|
+
!requestWebsocketTokenFromJoinSession);
|
|
178
|
+
if (typeof error === "object" && error !== null) {
|
|
179
|
+
normalizedError.addTelemetryProperties({ socketDocumentId: websocketEndpoint.id });
|
|
180
|
+
}
|
|
181
|
+
throw normalizedError;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private clearJoinSessionTimer() {
|
|
187
|
+
if (this.joinSessionRefreshTimer !== undefined) {
|
|
188
|
+
clearTimeout(this.joinSessionRefreshTimer);
|
|
189
|
+
this.joinSessionRefreshTimer = undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async scheduleJoinSessionRefresh(delta: number) {
|
|
194
|
+
await new Promise<void>((resolve, reject) => {
|
|
195
|
+
this.joinSessionRefreshTimer = setTimeout(() => {
|
|
196
|
+
getWithRetryForTokenRefresh(async (options) => {
|
|
197
|
+
await this.joinSession(false, options);
|
|
198
|
+
resolve();
|
|
199
|
+
}).catch((error) => {
|
|
200
|
+
reject(error);
|
|
201
|
+
});
|
|
202
|
+
}, delta);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async joinSession(
|
|
207
|
+
requestSocketToken: boolean,
|
|
208
|
+
options: TokenFetchOptionsEx,
|
|
209
|
+
) {
|
|
210
|
+
const response = await this.joinSessionCore(requestSocketToken, options).catch((e) => {
|
|
211
|
+
if (hasFacetCodes(e) && e.facetCodes !== undefined) {
|
|
212
|
+
for (const code of e.facetCodes) {
|
|
213
|
+
switch (code) {
|
|
214
|
+
case "sessionForbiddenOnPreservedFiles":
|
|
215
|
+
case "sessionForbiddenOnModerationEnabledLibrary":
|
|
216
|
+
case "sessionForbiddenOnRequireCheckout":
|
|
217
|
+
// This document can only be opened in storage-only mode.
|
|
218
|
+
// DeltaManager will recognize this error
|
|
219
|
+
// and load without a delta stream connection.
|
|
220
|
+
this.policies = { ...this.policies, storageOnly: true };
|
|
221
|
+
throw new DeltaStreamConnectionForbiddenError(code, { driverVersion });
|
|
222
|
+
default:
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw e;
|
|
228
|
+
});
|
|
229
|
+
this._relayServiceTenantAndSessionId = `${response.tenantId}/${response.id}`;
|
|
230
|
+
return response;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async joinSessionCore(
|
|
234
|
+
requestSocketToken: boolean,
|
|
235
|
+
options: TokenFetchOptionsEx,
|
|
236
|
+
): Promise<ISocketStorageDiscovery> {
|
|
237
|
+
const disableJoinSessionRefresh = this.mc.config.getBoolean("Fluid.Driver.Odsp.disableJoinSessionRefresh");
|
|
238
|
+
const executeFetch = async () => {
|
|
239
|
+
const joinSessionResponse = await fetchJoinSession(
|
|
240
|
+
this.odspResolvedUrl,
|
|
241
|
+
"opStream/joinSession",
|
|
242
|
+
"POST",
|
|
243
|
+
this.mc.logger,
|
|
244
|
+
this.getStorageToken,
|
|
245
|
+
this.epochTracker,
|
|
246
|
+
requestSocketToken,
|
|
247
|
+
options,
|
|
248
|
+
disableJoinSessionRefresh,
|
|
249
|
+
this.hostPolicy.sessionOptions?.unauthenticatedUserDisplayName,
|
|
250
|
+
);
|
|
251
|
+
return {
|
|
252
|
+
entryTime: Date.now(),
|
|
253
|
+
joinSessionResponse,
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const getResponseAndRefreshAfterDeltaMs = async () => {
|
|
258
|
+
const _response = await this.cache.sessionJoinCache.addOrGet(this.joinSessionKey, executeFetch);
|
|
259
|
+
// If the response does not contain refreshSessionDurationSeconds, then treat it as old flow and let the
|
|
260
|
+
// cache entry to be treated as expired after 1 hour.
|
|
261
|
+
_response.joinSessionResponse.refreshSessionDurationSeconds =
|
|
262
|
+
_response.joinSessionResponse.refreshSessionDurationSeconds ?? 3600;
|
|
263
|
+
return {
|
|
264
|
+
..._response,
|
|
265
|
+
refreshAfterDeltaMs: this.calculateJoinSessionRefreshDelta(
|
|
266
|
+
_response.entryTime, _response.joinSessionResponse.refreshSessionDurationSeconds),
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
let response = await getResponseAndRefreshAfterDeltaMs();
|
|
270
|
+
// This means that the cached entry has expired(This should not be possible if the response is fetched
|
|
271
|
+
// from the network call). In this case we remove the cached entry and fetch the new response.
|
|
272
|
+
if (response.refreshAfterDeltaMs <= 0) {
|
|
273
|
+
this.cache.sessionJoinCache.remove(this.joinSessionKey);
|
|
274
|
+
response = await getResponseAndRefreshAfterDeltaMs();
|
|
275
|
+
}
|
|
276
|
+
if (!disableJoinSessionRefresh) {
|
|
277
|
+
const props = {
|
|
278
|
+
entryTime: response.entryTime,
|
|
279
|
+
refreshSessionDurationSeconds:
|
|
280
|
+
response.joinSessionResponse.refreshSessionDurationSeconds,
|
|
281
|
+
refreshAfterDeltaMs: response.refreshAfterDeltaMs,
|
|
282
|
+
};
|
|
283
|
+
if (response.refreshAfterDeltaMs > 0) {
|
|
284
|
+
this.scheduleJoinSessionRefresh(response.refreshAfterDeltaMs)
|
|
285
|
+
.catch((error) => {
|
|
286
|
+
const canRetry = canRetryOnError(error);
|
|
287
|
+
// Only record error event in case it is non retriable.
|
|
288
|
+
if (!canRetry) {
|
|
289
|
+
this.mc.logger.sendErrorEvent({
|
|
290
|
+
eventName: "JoinSessionRefreshError",
|
|
291
|
+
details: JSON.stringify(props),
|
|
292
|
+
},
|
|
293
|
+
error,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
// Logging just for informational purposes to help with debugging as this is a new feature.
|
|
299
|
+
this.mc.logger.sendTelemetryEvent({
|
|
300
|
+
eventName: "JoinSessionRefreshNotScheduled",
|
|
301
|
+
details: JSON.stringify(props),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return response.joinSessionResponse;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private calculateJoinSessionRefreshDelta(responseFetchTime: number, refreshSessionDurationSeconds: number) {
|
|
309
|
+
// 30 seconds is buffer time to refresh the session.
|
|
310
|
+
return responseFetchTime + ((refreshSessionDurationSeconds * 1000) - 30000) - Date.now();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Creats a connection to the given delta stream endpoint
|
|
315
|
+
*
|
|
316
|
+
* @param tenantId - the ID of the tenant
|
|
317
|
+
* @param documentId - document ID
|
|
318
|
+
* @param token - authorization token for delta service
|
|
319
|
+
* @param client - information about the client
|
|
320
|
+
* @param webSocketUrl - websocket URL
|
|
321
|
+
*/
|
|
322
|
+
private async createDeltaConnection(
|
|
323
|
+
tenantId: string,
|
|
324
|
+
documentId: string,
|
|
325
|
+
token: string | null,
|
|
326
|
+
client: IClient,
|
|
327
|
+
webSocketUrl: string,
|
|
328
|
+
): Promise<OdspDocumentDeltaConnection> {
|
|
329
|
+
const startTime = performance.now();
|
|
330
|
+
const connection = await OdspDocumentDeltaConnection.create(
|
|
331
|
+
tenantId,
|
|
332
|
+
documentId,
|
|
333
|
+
token,
|
|
334
|
+
client,
|
|
335
|
+
webSocketUrl,
|
|
336
|
+
this.mc.logger,
|
|
337
|
+
60000,
|
|
338
|
+
this.epochTracker,
|
|
339
|
+
this.socketReferenceKeyPrefix,
|
|
340
|
+
);
|
|
341
|
+
const duration = performance.now() - startTime;
|
|
342
|
+
// This event happens rather often, so it adds up to cost of telemetry.
|
|
343
|
+
// Given that most reconnects result in reusing socket and happen very quickly,
|
|
344
|
+
// report event only if it took longer than threshold.
|
|
345
|
+
if (duration >= 2000) {
|
|
346
|
+
this.mc.logger.sendPerformanceEvent({
|
|
347
|
+
eventName: "ConnectionSuccess",
|
|
348
|
+
duration,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return connection;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public dispose(error?: any) {
|
|
355
|
+
this.clearJoinSessionTimer();
|
|
356
|
+
this.currentConnection?.dispose();
|
|
357
|
+
this.currentConnection = undefined;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
ISequencedDocumentMessage,
|
|
17
17
|
ISignalMessage,
|
|
18
18
|
} from "@fluidframework/protocol-definitions";
|
|
19
|
-
import
|
|
19
|
+
import { Socket, io as SocketIOClientStatic } from "socket.io-client";
|
|
20
20
|
import { v4 as uuid } from "uuid";
|
|
21
21
|
import { createGenericNetworkError } from "@fluidframework/driver-utils";
|
|
22
22
|
import { IOdspSocketError, IGetOpsResponse, IFlushOpsResponse } from "./contracts";
|
|
@@ -161,7 +161,7 @@ class SocketReference extends TypedEventEmitter<ISocketEvents> {
|
|
|
161
161
|
const socket = this._socket;
|
|
162
162
|
this._socket = undefined;
|
|
163
163
|
|
|
164
|
-
// Let all connections know they need to go through disconnect flow
|
|
164
|
+
// Let all connections know they need to go through disconnect flow.
|
|
165
165
|
this.emit("disconnect",
|
|
166
166
|
error ?? createGenericNetworkError(
|
|
167
167
|
"Socket closed without error",
|
|
@@ -202,7 +202,6 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
202
202
|
* @param tenantId - the ID of the tenant
|
|
203
203
|
* @param documentId - document ID
|
|
204
204
|
* @param token - authorization token for storage service
|
|
205
|
-
* @param io - websocket library
|
|
206
205
|
* @param client - information about the client
|
|
207
206
|
* @param mode - mode of the client
|
|
208
207
|
* @param url - websocket URL
|
|
@@ -215,7 +214,6 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
215
214
|
tenantId: string,
|
|
216
215
|
documentId: string,
|
|
217
216
|
token: string | null,
|
|
218
|
-
io: typeof SocketIOClientStatic,
|
|
219
217
|
client: IClient,
|
|
220
218
|
url: string,
|
|
221
219
|
telemetryLogger: ITelemetryLogger,
|
|
@@ -234,7 +232,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
234
232
|
const socketReferenceKey = enableMultiplexing ? key : `${key},${tenantId},${documentId}`;
|
|
235
233
|
|
|
236
234
|
const socketReference = OdspDocumentDeltaConnection.getOrCreateSocketIoReference(
|
|
237
|
-
|
|
235
|
+
timeoutMs, socketReferenceKey, url, enableMultiplexing, tenantId, documentId, telemetryLogger);
|
|
238
236
|
|
|
239
237
|
const socket = socketReference.socket;
|
|
240
238
|
|
|
@@ -316,7 +314,6 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
316
314
|
* Gets or create a socket io connection for the given key
|
|
317
315
|
*/
|
|
318
316
|
private static getOrCreateSocketIoReference(
|
|
319
|
-
io: typeof SocketIOClientStatic,
|
|
320
317
|
timeoutMs: number,
|
|
321
318
|
key: string,
|
|
322
319
|
url: string,
|
|
@@ -332,7 +329,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
332
329
|
|
|
333
330
|
const query = enableMultiplexing ? undefined : { documentId, tenantId };
|
|
334
331
|
|
|
335
|
-
const socket =
|
|
332
|
+
const socket = SocketIOClientStatic(
|
|
336
333
|
url,
|
|
337
334
|
{
|
|
338
335
|
multiplex: false, // Don't rely on socket.io built-in multiplexing
|