@fluidframework/odsp-driver 0.54.2 → 0.56.0-49831
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 -2
- package/dist/checkUrl.d.ts.map +1 -1
- package/dist/checkUrl.js +0 -1
- package/dist/checkUrl.js.map +1 -1
- package/dist/contractsPublic.d.ts +7 -1
- package/dist/contractsPublic.d.ts.map +1 -1
- package/dist/contractsPublic.js +7 -1
- package/dist/contractsPublic.js.map +1 -1
- package/dist/createFile.d.ts +3 -3
- package/dist/createFile.d.ts.map +1 -1
- package/dist/createFile.js +7 -7
- package/dist/createFile.js.map +1 -1
- package/dist/epochTracker.d.ts +3 -3
- package/dist/epochTracker.d.ts.map +1 -1
- package/dist/epochTracker.js +23 -10
- package/dist/epochTracker.js.map +1 -1
- package/dist/fetchSnapshot.d.ts +3 -2
- package/dist/fetchSnapshot.d.ts.map +1 -1
- package/dist/fetchSnapshot.js +61 -47
- package/dist/fetchSnapshot.js.map +1 -1
- package/dist/getFileLink.js +4 -4
- package/dist/getFileLink.js.map +1 -1
- package/dist/getUrlAndHeadersWithAuth.d.ts +1 -1
- package/dist/getUrlAndHeadersWithAuth.d.ts.map +1 -1
- package/dist/getUrlAndHeadersWithAuth.js +20 -33
- package/dist/getUrlAndHeadersWithAuth.js.map +1 -1
- package/dist/odspDeltaStorageService.d.ts.map +1 -1
- package/dist/odspDeltaStorageService.js +1 -4
- package/dist/odspDeltaStorageService.js.map +1 -1
- package/dist/odspDocumentDeltaConnection.js +1 -1
- package/dist/odspDocumentDeltaConnection.js.map +1 -1
- package/dist/odspDocumentService.d.ts.map +1 -1
- package/dist/odspDocumentService.js +9 -0
- package/dist/odspDocumentService.js.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.js +2 -2
- package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
- package/dist/odspDocumentStorageManager.d.ts +1 -0
- package/dist/odspDocumentStorageManager.d.ts.map +1 -1
- package/dist/odspDocumentStorageManager.js +16 -9
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspDriverUrlResolver.d.ts.map +1 -1
- package/dist/odspDriverUrlResolver.js +5 -2
- package/dist/odspDriverUrlResolver.js.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.js +1 -2
- package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/dist/odspSummaryUploadManager.d.ts +2 -1
- package/dist/odspSummaryUploadManager.d.ts.map +1 -1
- package/dist/odspSummaryUploadManager.js +3 -2
- package/dist/odspSummaryUploadManager.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/prefetchLatestSnapshot.d.ts +2 -1
- package/dist/prefetchLatestSnapshot.d.ts.map +1 -1
- package/dist/prefetchLatestSnapshot.js +3 -2
- package/dist/prefetchLatestSnapshot.js.map +1 -1
- package/dist/retryErrorsStorageAdapter.d.ts +1 -1
- package/dist/retryErrorsStorageAdapter.d.ts.map +1 -1
- package/dist/retryErrorsStorageAdapter.js.map +1 -1
- package/dist/vroom.d.ts.map +1 -1
- package/dist/vroom.js +19 -20
- package/dist/vroom.js.map +1 -1
- package/lib/checkUrl.d.ts.map +1 -1
- package/lib/checkUrl.js +0 -1
- package/lib/checkUrl.js.map +1 -1
- package/lib/contractsPublic.d.ts +7 -1
- package/lib/contractsPublic.d.ts.map +1 -1
- package/lib/contractsPublic.js +6 -0
- package/lib/contractsPublic.js.map +1 -1
- package/lib/createFile.d.ts +3 -3
- package/lib/createFile.d.ts.map +1 -1
- package/lib/createFile.js +7 -7
- package/lib/createFile.js.map +1 -1
- package/lib/epochTracker.d.ts +3 -3
- package/lib/epochTracker.d.ts.map +1 -1
- package/lib/epochTracker.js +24 -11
- package/lib/epochTracker.js.map +1 -1
- package/lib/fetchSnapshot.d.ts +3 -2
- package/lib/fetchSnapshot.d.ts.map +1 -1
- package/lib/fetchSnapshot.js +61 -47
- package/lib/fetchSnapshot.js.map +1 -1
- package/lib/getFileLink.js +4 -4
- package/lib/getFileLink.js.map +1 -1
- package/lib/getUrlAndHeadersWithAuth.d.ts +1 -1
- package/lib/getUrlAndHeadersWithAuth.d.ts.map +1 -1
- package/lib/getUrlAndHeadersWithAuth.js +20 -33
- package/lib/getUrlAndHeadersWithAuth.js.map +1 -1
- package/lib/odspDeltaStorageService.d.ts.map +1 -1
- package/lib/odspDeltaStorageService.js +1 -4
- package/lib/odspDeltaStorageService.js.map +1 -1
- package/lib/odspDocumentDeltaConnection.js +1 -1
- package/lib/odspDocumentDeltaConnection.js.map +1 -1
- package/lib/odspDocumentService.d.ts.map +1 -1
- package/lib/odspDocumentService.js +9 -0
- package/lib/odspDocumentService.js.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.js +2 -2
- package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
- package/lib/odspDocumentStorageManager.d.ts +1 -0
- package/lib/odspDocumentStorageManager.d.ts.map +1 -1
- package/lib/odspDocumentStorageManager.js +16 -9
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspDriverUrlResolver.d.ts.map +1 -1
- package/lib/odspDriverUrlResolver.js +5 -2
- package/lib/odspDriverUrlResolver.js.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.js +1 -2
- package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/lib/odspSummaryUploadManager.d.ts +2 -1
- package/lib/odspSummaryUploadManager.d.ts.map +1 -1
- package/lib/odspSummaryUploadManager.js +3 -2
- package/lib/odspSummaryUploadManager.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/prefetchLatestSnapshot.d.ts +2 -1
- package/lib/prefetchLatestSnapshot.d.ts.map +1 -1
- package/lib/prefetchLatestSnapshot.js +3 -2
- package/lib/prefetchLatestSnapshot.js.map +1 -1
- package/lib/retryErrorsStorageAdapter.d.ts +1 -1
- package/lib/retryErrorsStorageAdapter.d.ts.map +1 -1
- package/lib/retryErrorsStorageAdapter.js.map +1 -1
- package/lib/vroom.d.ts.map +1 -1
- package/lib/vroom.js +19 -20
- package/lib/vroom.js.map +1 -1
- package/package.json +19 -18
- package/src/checkUrl.ts +0 -1
- package/src/contractsPublic.ts +11 -1
- package/src/createFile.ts +16 -4
- package/src/epochTracker.ts +24 -8
- package/src/fetchSnapshot.ts +74 -47
- package/src/getFileLink.ts +7 -2
- package/src/getUrlAndHeadersWithAuth.ts +24 -38
- package/src/odspDeltaStorageService.ts +1 -3
- package/src/odspDocumentDeltaConnection.ts +1 -1
- package/src/odspDocumentService.ts +9 -0
- package/src/odspDocumentServiceFactoryCore.ts +1 -0
- package/src/odspDocumentStorageManager.ts +39 -8
- package/src/odspDriverUrlResolver.ts +3 -0
- package/src/odspDriverUrlResolverForShareLink.ts +1 -2
- package/src/odspSummaryUploadManager.ts +6 -1
- package/src/packageVersion.ts +1 -1
- package/src/prefetchLatestSnapshot.ts +3 -0
- package/src/retryErrorsStorageAdapter.ts +1 -1
- package/src/vroom.ts +23 -23
package/src/createFile.ts
CHANGED
|
@@ -55,6 +55,7 @@ export async function createNewFluidFile(
|
|
|
55
55
|
epochTracker: EpochTracker,
|
|
56
56
|
fileEntry: IFileEntry,
|
|
57
57
|
createNewCaching: boolean,
|
|
58
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
58
59
|
): Promise<IOdspResolvedUrl> {
|
|
59
60
|
// Check for valid filename before the request to create file is actually made.
|
|
60
61
|
if (isInvalidFileName(newFileInfo.filename)) {
|
|
@@ -67,10 +68,17 @@ export async function createNewFluidFile(
|
|
|
67
68
|
let sharingLink: string | undefined;
|
|
68
69
|
let sharingLinkErrorReason: string | undefined;
|
|
69
70
|
if (createNewSummary === undefined) {
|
|
70
|
-
itemId = await createNewEmptyFluidFile(
|
|
71
|
+
itemId = await createNewEmptyFluidFile(
|
|
72
|
+
getStorageToken, newFileInfo, logger, epochTracker, forceAccessTokenViaAuthorizationHeader);
|
|
71
73
|
} else {
|
|
72
74
|
const content = await createNewFluidFileFromSummary(
|
|
73
|
-
getStorageToken,
|
|
75
|
+
getStorageToken,
|
|
76
|
+
newFileInfo,
|
|
77
|
+
logger,
|
|
78
|
+
createNewSummary,
|
|
79
|
+
epochTracker,
|
|
80
|
+
forceAccessTokenViaAuthorizationHeader,
|
|
81
|
+
);
|
|
74
82
|
itemId = content.itemId;
|
|
75
83
|
summaryHandle = content.id;
|
|
76
84
|
sharingLink = content.sharingLink;
|
|
@@ -108,6 +116,7 @@ export async function createNewEmptyFluidFile(
|
|
|
108
116
|
newFileInfo: INewFileInfo,
|
|
109
117
|
logger: ITelemetryLogger,
|
|
110
118
|
epochTracker: EpochTracker,
|
|
119
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
111
120
|
): Promise<string> {
|
|
112
121
|
const filePath = newFileInfo.filePath ? encodeURIComponent(`/${newFileInfo.filePath}`) : "";
|
|
113
122
|
// add .tmp extension to empty file (host is expected to rename)
|
|
@@ -123,7 +132,8 @@ export async function createNewEmptyFluidFile(
|
|
|
123
132
|
logger,
|
|
124
133
|
{ eventName: "createNewEmptyFile" },
|
|
125
134
|
async (event) => {
|
|
126
|
-
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
135
|
+
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
136
|
+
initialUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
|
|
127
137
|
headers["Content-Type"] = "application/json";
|
|
128
138
|
|
|
129
139
|
const fetchResponse = await runWithRetry(
|
|
@@ -163,6 +173,7 @@ export async function createNewFluidFileFromSummary(
|
|
|
163
173
|
logger: ITelemetryLogger,
|
|
164
174
|
createNewSummary: ISummaryTree,
|
|
165
175
|
epochTracker: EpochTracker,
|
|
176
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
166
177
|
): Promise<ICreateFileResponse> {
|
|
167
178
|
const filePath = newFileInfo.filePath ? encodeURIComponent(`/${newFileInfo.filePath}`) : "";
|
|
168
179
|
const encodedFilename = encodeURIComponent(newFileInfo.filename);
|
|
@@ -181,7 +192,8 @@ export async function createNewFluidFileFromSummary(
|
|
|
181
192
|
logger,
|
|
182
193
|
{ eventName: "createNewFile" },
|
|
183
194
|
async (event) => {
|
|
184
|
-
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
195
|
+
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
196
|
+
initialUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
|
|
185
197
|
headers["Content-Type"] = "application/json";
|
|
186
198
|
|
|
187
199
|
const fetchResponse = await runWithRetry(
|
package/src/epochTracker.ts
CHANGED
|
@@ -18,13 +18,14 @@ import {
|
|
|
18
18
|
} from "@fluidframework/odsp-driver-definitions";
|
|
19
19
|
import { DriverErrorType } from "@fluidframework/driver-definitions";
|
|
20
20
|
import { PerformanceEvent, isFluidError, normalizeError } from "@fluidframework/telemetry-utils";
|
|
21
|
-
import { fetchAndParseAsJSONHelper, fetchArray, IOdspResponse } from "./odspUtils";
|
|
21
|
+
import { fetchAndParseAsJSONHelper, fetchArray, getOdspResolvedUrl, IOdspResponse } from "./odspUtils";
|
|
22
22
|
import {
|
|
23
23
|
IOdspCache,
|
|
24
24
|
INonPersistentCache,
|
|
25
25
|
IPersistedFileCache,
|
|
26
26
|
} from "./odspCache";
|
|
27
27
|
import { IVersionedValueWithEpoch, persistedCacheValueVersion } from "./contracts";
|
|
28
|
+
import { ClpCompliantAppHeader } from "./contractsPublic";
|
|
28
29
|
|
|
29
30
|
export type FetchType = "blob" | "createBlob" | "createFile" | "joinSession" | "ops" | "test" | "snapshotTree" |
|
|
30
31
|
"treesLatest" | "uploadSummary" | "push" | "versions";
|
|
@@ -33,6 +34,7 @@ export type FetchTypeInternal = FetchType | "cache";
|
|
|
33
34
|
|
|
34
35
|
export const Odsp409Error = "Odsp409Error";
|
|
35
36
|
|
|
37
|
+
// Please update the README file in odsp-driver-definitions if you change the defaultCacheExpiryTimeoutMs.
|
|
36
38
|
export const defaultCacheExpiryTimeoutMs: number = 2 * 24 * 60 * 60 * 1000;
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -169,8 +171,9 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
169
171
|
fetchOptions: RequestInit,
|
|
170
172
|
fetchType: FetchType,
|
|
171
173
|
addInBody: boolean = false,
|
|
174
|
+
fetchReason?: string,
|
|
172
175
|
): Promise<IOdspResponse<T>> {
|
|
173
|
-
const clientCorrelationId = this.formatClientCorrelationId();
|
|
176
|
+
const clientCorrelationId = this.formatClientCorrelationId(fetchReason);
|
|
174
177
|
// Add epoch in fetch request.
|
|
175
178
|
this.addEpochInRequest(fetchOptions, addInBody, clientCorrelationId);
|
|
176
179
|
let epochFromResponse: string | undefined;
|
|
@@ -210,8 +213,9 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
210
213
|
fetchOptions: {[index: string]: any},
|
|
211
214
|
fetchType: FetchType,
|
|
212
215
|
addInBody: boolean = false,
|
|
216
|
+
fetchReason?: string,
|
|
213
217
|
) {
|
|
214
|
-
const clientCorrelationId = this.formatClientCorrelationId();
|
|
218
|
+
const clientCorrelationId = this.formatClientCorrelationId(fetchReason);
|
|
215
219
|
// Add epoch in fetch request.
|
|
216
220
|
this.addEpochInRequest(fetchOptions, addInBody, clientCorrelationId);
|
|
217
221
|
let epochFromResponse: string | undefined;
|
|
@@ -244,12 +248,16 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
244
248
|
addInBody: boolean,
|
|
245
249
|
clientCorrelationId: string,
|
|
246
250
|
) {
|
|
251
|
+
const isClpCompliantApp = getOdspResolvedUrl(this.fileEntry.resolvedUrl).isClpCompliantApp;
|
|
247
252
|
if (addInBody) {
|
|
248
253
|
const headers: {[key: string]: string} = {};
|
|
249
254
|
headers["X-RequestStats"] = clientCorrelationId;
|
|
250
255
|
if (this.fluidEpoch !== undefined) {
|
|
251
256
|
headers["x-fluid-epoch"] = this.fluidEpoch;
|
|
252
257
|
}
|
|
258
|
+
if (isClpCompliantApp) {
|
|
259
|
+
headers[ClpCompliantAppHeader.isClpCompliantApp] = isClpCompliantApp.toString();
|
|
260
|
+
}
|
|
253
261
|
this.addParamInBody(fetchOptions, headers);
|
|
254
262
|
} else {
|
|
255
263
|
const addHeader = (key: string, val: string) => {
|
|
@@ -263,6 +271,9 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
263
271
|
if (this.fluidEpoch !== undefined) {
|
|
264
272
|
addHeader("x-fluid-epoch", this.fluidEpoch);
|
|
265
273
|
}
|
|
274
|
+
if (isClpCompliantApp) {
|
|
275
|
+
addHeader(ClpCompliantAppHeader.isClpCompliantApp, isClpCompliantApp.toString());
|
|
276
|
+
}
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
|
|
@@ -284,8 +295,12 @@ export class EpochTracker implements IPersistedFileCache {
|
|
|
284
295
|
fetchOptions.body = formParams.join("\r\n");
|
|
285
296
|
}
|
|
286
297
|
|
|
287
|
-
private formatClientCorrelationId() {
|
|
288
|
-
|
|
298
|
+
private formatClientCorrelationId(fetchReason?: string) {
|
|
299
|
+
const items: string[] = [`driverId=${this.driverId}`, `RequestNumber=${this.networkCallNumber++}`];
|
|
300
|
+
if (fetchReason !== undefined) {
|
|
301
|
+
items.push(`fetchReason=${fetchReason}`);
|
|
302
|
+
}
|
|
303
|
+
return items.join(", ");
|
|
289
304
|
}
|
|
290
305
|
|
|
291
306
|
protected validateEpochFromResponse(
|
|
@@ -394,13 +409,14 @@ export class EpochTrackerWithRedemption extends EpochTracker {
|
|
|
394
409
|
fetchOptions: {[index: string]: any},
|
|
395
410
|
fetchType: FetchType,
|
|
396
411
|
addInBody: boolean = false,
|
|
412
|
+
fetchReason?: string,
|
|
397
413
|
): Promise<IOdspResponse<T>> {
|
|
398
414
|
// Optimize the flow if we know that treesLatestDeferral was already completed by the timer we started
|
|
399
415
|
// joinSession call. If we did - there is no reason to repeat the call as it will fail with same error.
|
|
400
416
|
const completed = this.treesLatestDeferral.isCompleted;
|
|
401
417
|
|
|
402
418
|
try {
|
|
403
|
-
return await super.fetchAndParseAsJSON<T>(url, fetchOptions, fetchType, addInBody);
|
|
419
|
+
return await super.fetchAndParseAsJSON<T>(url, fetchOptions, fetchType, addInBody, fetchReason);
|
|
404
420
|
} catch (error) {
|
|
405
421
|
// Only handling here treesLatest. If createFile failed, we should never try to do joinSession.
|
|
406
422
|
// Similar, if getVersions failed, we should not do any further storage calls.
|
|
@@ -428,8 +444,8 @@ export class EpochTrackerWithRedemption extends EpochTracker {
|
|
|
428
444
|
async (event) => {
|
|
429
445
|
const timeoutRes = 51; // anything will work here
|
|
430
446
|
let timer: ReturnType<typeof setTimeout>;
|
|
431
|
-
const timeoutP = new Promise<number>((
|
|
432
|
-
timer = setTimeout(() => {
|
|
447
|
+
const timeoutP = new Promise<number>((resolve) => {
|
|
448
|
+
timer = setTimeout(() => { resolve(timeoutRes); }, 15000);
|
|
433
449
|
});
|
|
434
450
|
const res = await Promise.race([
|
|
435
451
|
timeoutP,
|
package/src/fetchSnapshot.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { EpochTracker } from "./epochTracker";
|
|
|
39
39
|
* @param storageFetchWrapper - Implementation of the get/post methods used to fetch the snapshot
|
|
40
40
|
* @param versionId - id of specific snapshot to be fetched
|
|
41
41
|
* @param fetchFullSnapshot - whether we want to fetch full snapshot(with blobs)
|
|
42
|
+
* @param forceAccessTokenViaAuthorizationHeader - whether to force passing given token via authorization header
|
|
42
43
|
* @returns A promise of the snapshot and the status code of the response
|
|
43
44
|
*/
|
|
44
45
|
export async function fetchSnapshot(
|
|
@@ -46,6 +47,7 @@ export async function fetchSnapshot(
|
|
|
46
47
|
token: string | null,
|
|
47
48
|
versionId: string,
|
|
48
49
|
fetchFullSnapshot: boolean,
|
|
50
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
49
51
|
logger: ITelemetryLogger,
|
|
50
52
|
snapshotDownloader: (url: string, fetchOptions: {[index: string]: any}) => Promise<IOdspResponse<unknown>>,
|
|
51
53
|
): Promise<ISnapshotContents> {
|
|
@@ -61,7 +63,8 @@ export async function fetchSnapshot(
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
const queryString = getQueryString(queryParams);
|
|
64
|
-
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
66
|
+
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
67
|
+
`${snapshotUrl}${path}${queryString}`, token, forceAccessTokenViaAuthorizationHeader);
|
|
65
68
|
const response = await PerformanceEvent.timedExecAsync(
|
|
66
69
|
logger,
|
|
67
70
|
{
|
|
@@ -77,6 +80,7 @@ export async function fetchSnapshotWithRedeem(
|
|
|
77
80
|
odspResolvedUrl: IOdspResolvedUrl,
|
|
78
81
|
storageTokenFetcher: InstrumentedStorageTokenFetcher,
|
|
79
82
|
snapshotOptions: ISnapshotOptions | undefined,
|
|
83
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
80
84
|
logger: ITelemetryLogger,
|
|
81
85
|
snapshotDownloader: (
|
|
82
86
|
finalOdspResolvedUrl: IOdspResolvedUrl,
|
|
@@ -88,6 +92,12 @@ export async function fetchSnapshotWithRedeem(
|
|
|
88
92
|
removeEntries: () => Promise<void>,
|
|
89
93
|
enableRedeemFallback?: boolean,
|
|
90
94
|
): Promise<ISnapshotContents> {
|
|
95
|
+
// back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
|
|
96
|
+
const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
|
|
97
|
+
if(sharingLinkToRedeem) {
|
|
98
|
+
odspResolvedUrl.shareLinkInfo = {...odspResolvedUrl.shareLinkInfo, sharingLinkToRedeem}
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
return fetchLatestSnapshotCore(
|
|
92
102
|
odspResolvedUrl,
|
|
93
103
|
storageTokenFetcher,
|
|
@@ -95,6 +105,7 @@ export async function fetchSnapshotWithRedeem(
|
|
|
95
105
|
logger,
|
|
96
106
|
snapshotDownloader,
|
|
97
107
|
putInCache,
|
|
108
|
+
enableRedeemFallback,
|
|
98
109
|
).catch(async (error) => {
|
|
99
110
|
if (enableRedeemFallback && isRedeemSharingLinkError(odspResolvedUrl, error)) {
|
|
100
111
|
// Execute the redeem fallback
|
|
@@ -102,16 +113,15 @@ export async function fetchSnapshotWithRedeem(
|
|
|
102
113
|
eventName: "RedeemFallback",
|
|
103
114
|
errorType: error.errorType,
|
|
104
115
|
}, error);
|
|
105
|
-
await redeemSharingLink(
|
|
116
|
+
await redeemSharingLink(
|
|
117
|
+
odspResolvedUrl, storageTokenFetcher, logger, forceAccessTokenViaAuthorizationHeader);
|
|
106
118
|
const odspResolvedUrlWithoutShareLink: IOdspResolvedUrl =
|
|
107
|
-
{ ...odspResolvedUrl,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
sharingLinkToRedeem: undefined,
|
|
119
|
+
{ ...odspResolvedUrl,
|
|
120
|
+
shareLinkInfo: {
|
|
121
|
+
...odspResolvedUrl.shareLinkInfo,
|
|
122
|
+
sharingLinkToRedeem: undefined
|
|
123
|
+
}
|
|
113
124
|
};
|
|
114
|
-
}
|
|
115
125
|
|
|
116
126
|
return fetchLatestSnapshotCore(
|
|
117
127
|
odspResolvedUrlWithoutShareLink,
|
|
@@ -140,6 +150,7 @@ async function redeemSharingLink(
|
|
|
140
150
|
odspResolvedUrl: IOdspResolvedUrl,
|
|
141
151
|
storageTokenFetcher: InstrumentedStorageTokenFetcher,
|
|
142
152
|
logger: ITelemetryLogger,
|
|
153
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
143
154
|
) {
|
|
144
155
|
return PerformanceEvent.timedExecAsync(
|
|
145
156
|
logger,
|
|
@@ -147,12 +158,13 @@ async function redeemSharingLink(
|
|
|
147
158
|
eventName: "RedeemShareLink",
|
|
148
159
|
},
|
|
149
160
|
async () => getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
|
|
150
|
-
assert(!!odspResolvedUrl.sharingLinkToRedeem,
|
|
161
|
+
assert(!!odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem,
|
|
151
162
|
0x1ed /* "Share link should be present" */);
|
|
152
163
|
const storageToken = await storageTokenFetcher(tokenFetchOptions, "RedeemShareLink");
|
|
153
|
-
const encodedShareUrl = getEncodedShareUrl(odspResolvedUrl.sharingLinkToRedeem);
|
|
164
|
+
const encodedShareUrl = getEncodedShareUrl(odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem);
|
|
154
165
|
const redeemUrl = `${odspResolvedUrl.siteUrl}/_api/v2.0/shares/${encodedShareUrl}`;
|
|
155
|
-
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
166
|
+
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
167
|
+
redeemUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
|
|
156
168
|
headers.prefer = "redeemSharingLink";
|
|
157
169
|
return fetchAndParseAsJSONHelper(url, { headers });
|
|
158
170
|
}),
|
|
@@ -171,6 +183,7 @@ async function fetchLatestSnapshotCore(
|
|
|
171
183
|
controller?: AbortController,
|
|
172
184
|
) => Promise<ISnapshotRequestAndResponseOptions>,
|
|
173
185
|
putInCache: (valueWithEpoch: IVersionedValueWithEpoch) => Promise<void>,
|
|
186
|
+
enableRedeemFallback?: boolean,
|
|
174
187
|
): Promise<ISnapshotContents> {
|
|
175
188
|
return getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
|
|
176
189
|
const storageToken = await storageTokenFetcher(tokenFetchOptions, "TreesLatest", true);
|
|
@@ -187,6 +200,8 @@ async function fetchLatestSnapshotCore(
|
|
|
187
200
|
const perfEvent = {
|
|
188
201
|
eventName: "TreesLatest",
|
|
189
202
|
attempts: tokenFetchOptions.refresh ? 2 : 1,
|
|
203
|
+
shareLinkPresent: odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem !== undefined,
|
|
204
|
+
redeemFallbackEnabled: enableRedeemFallback,
|
|
190
205
|
};
|
|
191
206
|
if (snapshotOptions !== undefined) {
|
|
192
207
|
Object.entries(snapshotOptions).forEach(([key, value]) => {
|
|
@@ -344,30 +359,9 @@ async function fetchSnapshotContentsCoreV1(
|
|
|
344
359
|
): Promise<ISnapshotRequestAndResponseOptions> {
|
|
345
360
|
const snapshotUrl = odspResolvedUrl.endpoints.snapshotStorageUrl;
|
|
346
361
|
const url = `${snapshotUrl}/trees/latest?ump=1`;
|
|
347
|
-
const
|
|
348
|
-
const formParams: string[] = [];
|
|
349
|
-
formParams.push(`--${formBoundary}`);
|
|
350
|
-
formParams.push(`Authorization: Bearer ${storageToken}`);
|
|
351
|
-
formParams.push(`X-HTTP-Method-Override: GET`);
|
|
352
|
-
if (snapshotOptions !== undefined) {
|
|
353
|
-
Object.entries(snapshotOptions).forEach(([key, value]) => {
|
|
354
|
-
if (value !== undefined) {
|
|
355
|
-
formParams.push(`${key}: ${value}`);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
if (odspResolvedUrl.sharingLinkToRedeem) {
|
|
360
|
-
formParams.push(`sl: ${odspResolvedUrl.sharingLinkToRedeem}`);
|
|
361
|
-
}
|
|
362
|
-
formParams.push(`_post: 1`);
|
|
363
|
-
formParams.push(`\r\n--${formBoundary}--`);
|
|
364
|
-
const postBody = formParams.join("\r\n");
|
|
365
|
-
const headers: {[index: string]: any} = {
|
|
366
|
-
"Content-Type": `multipart/form-data;boundary=${formBoundary}`,
|
|
367
|
-
};
|
|
368
|
-
|
|
362
|
+
const { body, headers } = getFormBodyAndHeaders(odspResolvedUrl, storageToken, snapshotOptions);
|
|
369
363
|
const fetchOptions = {
|
|
370
|
-
body
|
|
364
|
+
body,
|
|
371
365
|
headers,
|
|
372
366
|
signal: controller?.signal,
|
|
373
367
|
method: "POST",
|
|
@@ -401,30 +395,57 @@ async function fetchSnapshotContentsCoreV2(
|
|
|
401
395
|
epochTracker?: EpochTracker,
|
|
402
396
|
): Promise<ISnapshotRequestAndResponseOptions> {
|
|
403
397
|
const fullUrl = `${odspResolvedUrl.siteUrl}/_api/v2.1/drives/${odspResolvedUrl.driveId}/items/${
|
|
404
|
-
odspResolvedUrl.itemId}/opStream/attachments/latest/content`;
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
408
|
-
queryParams["sl"] = odspResolvedUrl.sharingLinkToRedeem;
|
|
409
|
-
}
|
|
410
|
-
const queryString = getQueryString(queryParams);
|
|
411
|
-
const { url, headers } = getUrlAndHeadersWithAuth(`${fullUrl}${queryString}`, storageToken);
|
|
398
|
+
odspResolvedUrl.itemId}/opStream/attachments/latest/content?ump=1`;
|
|
399
|
+
|
|
400
|
+
const { body, headers } = getFormBodyAndHeaders(odspResolvedUrl, storageToken, snapshotOptions);
|
|
412
401
|
const fetchOptions = {
|
|
402
|
+
body,
|
|
413
403
|
headers,
|
|
414
404
|
signal: controller?.signal,
|
|
405
|
+
method: "POST",
|
|
415
406
|
};
|
|
416
|
-
|
|
417
|
-
|
|
407
|
+
|
|
408
|
+
const response = await (epochTracker?.fetchArray(fullUrl, fetchOptions, "treesLatest", true) ??
|
|
409
|
+
fetchArray(fullUrl, fetchOptions));
|
|
418
410
|
const snapshotContents: ISnapshotContents = parseCompactSnapshotResponse(
|
|
419
411
|
new ReadBuffer(new Uint8Array(response.content)));
|
|
420
412
|
const finalSnapshotContents: IOdspResponse<ISnapshotContents> = { ...response, content: snapshotContents };
|
|
421
413
|
return {
|
|
422
414
|
odspSnapshotResponse: finalSnapshotContents,
|
|
423
415
|
requestHeaders: headers,
|
|
424
|
-
requestUrl:
|
|
416
|
+
requestUrl: fullUrl,
|
|
425
417
|
};
|
|
426
418
|
}
|
|
427
419
|
|
|
420
|
+
function getFormBodyAndHeaders(
|
|
421
|
+
odspResolvedUrl: IOdspResolvedUrl,
|
|
422
|
+
storageToken: string,
|
|
423
|
+
snapshotOptions: ISnapshotOptions | undefined,
|
|
424
|
+
) {
|
|
425
|
+
const formBoundary = uuid();
|
|
426
|
+
const formParams: string[] = [];
|
|
427
|
+
formParams.push(`--${formBoundary}`);
|
|
428
|
+
formParams.push(`Authorization: Bearer ${storageToken}`);
|
|
429
|
+
formParams.push(`X-HTTP-Method-Override: GET`);
|
|
430
|
+
if (snapshotOptions !== undefined) {
|
|
431
|
+
Object.entries(snapshotOptions).forEach(([key, value]) => {
|
|
432
|
+
if (value !== undefined) {
|
|
433
|
+
formParams.push(`${key}: ${value}`);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem) {
|
|
438
|
+
formParams.push(`sl: ${odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem}`);
|
|
439
|
+
}
|
|
440
|
+
formParams.push(`_post: 1`);
|
|
441
|
+
formParams.push(`\r\n--${formBoundary}--`);
|
|
442
|
+
const postBody = formParams.join("\r\n");
|
|
443
|
+
const headers: {[index: string]: any} = {
|
|
444
|
+
"Content-Type": `multipart/form-data;boundary=${formBoundary}`,
|
|
445
|
+
};
|
|
446
|
+
return { body: postBody, headers };
|
|
447
|
+
}
|
|
448
|
+
|
|
428
449
|
function validateAndEvalBlobsAndTrees(snapshot: ISnapshotContents) {
|
|
429
450
|
assert(snapshot.snapshotTree !== undefined,
|
|
430
451
|
0x200 /* "Returned odsp snapshot is malformed. No trees!" */);
|
|
@@ -457,6 +478,12 @@ export async function downloadSnapshot(
|
|
|
457
478
|
controller?: AbortController,
|
|
458
479
|
epochTracker?: EpochTracker,
|
|
459
480
|
): Promise<ISnapshotRequestAndResponseOptions> {
|
|
481
|
+
// back-compat: This block to be removed with #8784 when we only consume/consider odsp resolvers that are >= 0.51
|
|
482
|
+
const sharingLinkToRedeem = (odspResolvedUrl as any).sharingLinkToRedeem;
|
|
483
|
+
if(sharingLinkToRedeem) {
|
|
484
|
+
odspResolvedUrl.shareLinkInfo = {...odspResolvedUrl.shareLinkInfo, sharingLinkToRedeem}
|
|
485
|
+
}
|
|
486
|
+
|
|
460
487
|
if (fetchBinarySnapshotFormat) {
|
|
461
488
|
// Logging an event here as it is not supposed to be used in production yet and only in experimental mode.
|
|
462
489
|
logger.sendTelemetryEvent({ eventName: "BinarySnapshotFetched" });
|
|
@@ -467,7 +494,7 @@ export async function downloadSnapshot(
|
|
|
467
494
|
}
|
|
468
495
|
|
|
469
496
|
function isRedeemSharingLinkError(odspResolvedUrl: IOdspResolvedUrl, error: any) {
|
|
470
|
-
if (odspResolvedUrl.sharingLinkToRedeem !== undefined
|
|
497
|
+
if (odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem !== undefined
|
|
471
498
|
&& (typeof error === "object" && error !== null)
|
|
472
499
|
&& (error.errorType === DriverErrorType.authorizationError
|
|
473
500
|
|| error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError)) {
|
package/src/getFileLink.ts
CHANGED
|
@@ -84,7 +84,7 @@ async function getFileLinkCore(
|
|
|
84
84
|
identityType: IdentityType,
|
|
85
85
|
logger: ITelemetryLogger,
|
|
86
86
|
): Promise<string> {
|
|
87
|
-
const fileItem = await getFileItemLite(getToken, odspUrlParts, logger);
|
|
87
|
+
const fileItem = await getFileItemLite(getToken, odspUrlParts, logger, identityType === "Consumer");
|
|
88
88
|
|
|
89
89
|
// ODC canonical link does not require any additional processing
|
|
90
90
|
if (identityType === "Consumer") {
|
|
@@ -104,7 +104,10 @@ async function getFileLinkCore(
|
|
|
104
104
|
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
105
105
|
`${odspUrlParts.siteUrl}/_api/web/GetFileByUrl(@a1)/ListItemAllFields/GetSharingInformation?@a1=${
|
|
106
106
|
encodeURIComponent(`'${fileItem.webDavUrl}'`)
|
|
107
|
-
}`,
|
|
107
|
+
}`,
|
|
108
|
+
tokenFromResponse(token),
|
|
109
|
+
false,
|
|
110
|
+
);
|
|
108
111
|
const requestInit = {
|
|
109
112
|
method: "POST",
|
|
110
113
|
headers: {
|
|
@@ -152,6 +155,7 @@ async function getFileItemLite(
|
|
|
152
155
|
getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
|
|
153
156
|
odspUrlParts: IOdspUrlParts,
|
|
154
157
|
logger: ITelemetryLogger,
|
|
158
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
155
159
|
): Promise<FileItemLite> {
|
|
156
160
|
return PerformanceEvent.timedExecAsync(
|
|
157
161
|
logger,
|
|
@@ -166,6 +170,7 @@ async function getFileItemLite(
|
|
|
166
170
|
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
167
171
|
`${siteUrl}/_api/v2.0/drives/${driveId}/items/${itemId}?select=webUrl,webDavUrl`,
|
|
168
172
|
tokenFromResponse(token),
|
|
173
|
+
forceAccessTokenViaAuthorizationHeader,
|
|
169
174
|
);
|
|
170
175
|
const requestInit = { method: "GET", headers };
|
|
171
176
|
const response = await fetchHelper(url, requestInit);
|
|
@@ -3,48 +3,34 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const queryParamStart = url.indexOf("?");
|
|
12
|
-
|
|
13
|
-
if (queryParamStart === -1) {
|
|
14
|
-
return 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return url.length - queryParamStart - 1;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// eslint-disable-next-line max-len
|
|
21
|
-
export function getUrlAndHeadersWithAuth(url: string, token: string | null): { url: string, headers: { [index: string]: string } } {
|
|
6
|
+
export function getUrlAndHeadersWithAuth(
|
|
7
|
+
url: string,
|
|
8
|
+
token: string | null,
|
|
9
|
+
forceAccessTokenViaAuthorizationHeader: boolean,
|
|
10
|
+
): { url: string, headers: { [index: string]: string } } {
|
|
22
11
|
if (!token || token.length === 0) {
|
|
23
12
|
return { url, headers: {} };
|
|
24
13
|
}
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
headers: {},
|
|
46
|
-
url: url + tokenQueryParam,
|
|
47
|
-
};
|
|
15
|
+
if (!forceAccessTokenViaAuthorizationHeader) {
|
|
16
|
+
// Pass access token via query string: this will make request be treated as 'simple' request
|
|
17
|
+
// which does not require OPTIONS call as part of CORS check.
|
|
18
|
+
const urlWithAccessTokenInQueryString = new URL(url);
|
|
19
|
+
// IMPORTANT: Do not apply encodeURIComponent to token, param value is automatically encoded
|
|
20
|
+
// when set via URLSearchParams class
|
|
21
|
+
urlWithAccessTokenInQueryString.searchParams.set("access_token", token);
|
|
22
|
+
// ODSP APIs have a limitation that the query string cannot exceed 2048 characters.
|
|
23
|
+
// If the query string exceeds 2048, we have to fall back to sending the access token as a header, which
|
|
24
|
+
// has a negative performance implication as it adds a performance overhead.
|
|
25
|
+
// NOTE: URL.search.length value includes '?' symbol and it is unclear whether backend logic which enforces
|
|
26
|
+
// query length limit accounts for it. This logic errs on side of caution and includes that key in overall
|
|
27
|
+
// query length.
|
|
28
|
+
if (urlWithAccessTokenInQueryString.search.length <= 2048) {
|
|
29
|
+
return {
|
|
30
|
+
headers: {},
|
|
31
|
+
url: urlWithAccessTokenInQueryString.href,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
48
34
|
}
|
|
49
35
|
|
|
50
36
|
return {
|
|
@@ -53,9 +53,6 @@ export class OdspDeltaStorageService {
|
|
|
53
53
|
let postBody = `--${formBoundary}\r\n`;
|
|
54
54
|
postBody += `Authorization: Bearer ${storageToken}\r\n`;
|
|
55
55
|
postBody += `X-HTTP-Method-Override: GET\r\n`;
|
|
56
|
-
if (fetchReason !== undefined) {
|
|
57
|
-
postBody += `fetchReason: ${fetchReason}\r\n`;
|
|
58
|
-
}
|
|
59
56
|
|
|
60
57
|
postBody += `_post: 1\r\n`;
|
|
61
58
|
postBody += `\r\n--${formBoundary}--`;
|
|
@@ -81,6 +78,7 @@ export class OdspDeltaStorageService {
|
|
|
81
78
|
},
|
|
82
79
|
"ops",
|
|
83
80
|
true,
|
|
81
|
+
fetchReason,
|
|
84
82
|
);
|
|
85
83
|
clearTimeout(timer);
|
|
86
84
|
const deltaStorageResponse = response.content;
|
|
@@ -234,7 +234,7 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
|
|
|
234
234
|
|
|
235
235
|
// Reference to this client supporting get_ops flow.
|
|
236
236
|
connectMessage.supportedFeatures = { };
|
|
237
|
-
if (mc.config.getBoolean("Fluid.Driver.Odsp.GetOpsEnabled")
|
|
237
|
+
if (mc.config.getBoolean("Fluid.Driver.Odsp.GetOpsEnabled") !== false) {
|
|
238
238
|
connectMessage.supportedFeatures[feature_get_ops] = true;
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
IResolvedUrl,
|
|
18
18
|
IDocumentStorageService,
|
|
19
19
|
IDocumentServicePolicies,
|
|
20
|
+
DriverErrorType,
|
|
20
21
|
} from "@fluidframework/driver-definitions";
|
|
21
22
|
import { DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
|
|
22
23
|
import { IFacetCodes } from "@fluidframework/odsp-doclib-utils";
|
|
@@ -281,6 +282,14 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
281
282
|
connection.on("op", (documentId, ops: ISequencedDocumentMessage[]) => {
|
|
282
283
|
this.opsReceived(ops);
|
|
283
284
|
});
|
|
285
|
+
// On disconnect with 401/403 error code, we can just clear the joinSession cache as we will again
|
|
286
|
+
// get the auth error on reconnecting and face latency.
|
|
287
|
+
connection.on("disconnect", (error: any) => {
|
|
288
|
+
if (typeof error === "object" && error !== null
|
|
289
|
+
&& error.errorType === DriverErrorType.authorizationError) {
|
|
290
|
+
this.cache.sessionJoinCache.remove(this.joinSessionKey);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
284
293
|
this.currentConnection = connection;
|
|
285
294
|
return connection;
|
|
286
295
|
} catch (error) {
|
|
@@ -104,6 +104,7 @@ export class OdspDocumentServiceFactoryCore implements IDocumentServiceFactory {
|
|
|
104
104
|
cacheAndTracker.epochTracker,
|
|
105
105
|
fileEntry,
|
|
106
106
|
this.hostPolicy.cacheCreateNewSummary ?? true,
|
|
107
|
+
!!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
|
|
107
108
|
);
|
|
108
109
|
const docService = this.createDocumentServiceCore(odspResolvedUrl, odspLogger, cacheAndTracker);
|
|
109
110
|
event.end({
|