@fluidframework/odsp-driver 0.54.3 → 0.55.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.eslintrc.js +1 -2
  2. package/dist/epochTracker.d.ts.map +1 -1
  3. package/dist/epochTracker.js +3 -2
  4. package/dist/epochTracker.js.map +1 -1
  5. package/dist/fetchSnapshot.d.ts.map +1 -1
  6. package/dist/fetchSnapshot.js +36 -34
  7. package/dist/fetchSnapshot.js.map +1 -1
  8. package/dist/odspDocumentDeltaConnection.js +1 -1
  9. package/dist/odspDocumentDeltaConnection.js.map +1 -1
  10. package/dist/odspDocumentService.d.ts.map +1 -1
  11. package/dist/odspDocumentService.js +9 -0
  12. package/dist/odspDocumentService.js.map +1 -1
  13. package/dist/packageVersion.d.ts +1 -1
  14. package/dist/packageVersion.js +1 -1
  15. package/dist/packageVersion.js.map +1 -1
  16. package/dist/vroom.d.ts.map +1 -1
  17. package/dist/vroom.js +19 -20
  18. package/dist/vroom.js.map +1 -1
  19. package/lib/epochTracker.d.ts.map +1 -1
  20. package/lib/epochTracker.js +3 -2
  21. package/lib/epochTracker.js.map +1 -1
  22. package/lib/fetchSnapshot.d.ts.map +1 -1
  23. package/lib/fetchSnapshot.js +36 -34
  24. package/lib/fetchSnapshot.js.map +1 -1
  25. package/lib/odspDocumentDeltaConnection.js +1 -1
  26. package/lib/odspDocumentDeltaConnection.js.map +1 -1
  27. package/lib/odspDocumentService.d.ts.map +1 -1
  28. package/lib/odspDocumentService.js +9 -0
  29. package/lib/odspDocumentService.js.map +1 -1
  30. package/lib/packageVersion.d.ts +1 -1
  31. package/lib/packageVersion.js +1 -1
  32. package/lib/packageVersion.js.map +1 -1
  33. package/lib/vroom.d.ts.map +1 -1
  34. package/lib/vroom.js +19 -20
  35. package/lib/vroom.js.map +1 -1
  36. package/package.json +17 -16
  37. package/src/epochTracker.ts +3 -2
  38. package/src/fetchSnapshot.ts +44 -34
  39. package/src/odspDocumentDeltaConnection.ts +1 -1
  40. package/src/odspDocumentService.ts +9 -0
  41. package/src/packageVersion.ts +1 -1
  42. package/src/vroom.ts +23 -23
@@ -95,6 +95,7 @@ export async function fetchSnapshotWithRedeem(
95
95
  logger,
96
96
  snapshotDownloader,
97
97
  putInCache,
98
+ enableRedeemFallback,
98
99
  ).catch(async (error) => {
99
100
  if (enableRedeemFallback && isRedeemSharingLinkError(odspResolvedUrl, error)) {
100
101
  // Execute the redeem fallback
@@ -171,6 +172,7 @@ async function fetchLatestSnapshotCore(
171
172
  controller?: AbortController,
172
173
  ) => Promise<ISnapshotRequestAndResponseOptions>,
173
174
  putInCache: (valueWithEpoch: IVersionedValueWithEpoch) => Promise<void>,
175
+ enableRedeemFallback?: boolean,
174
176
  ): Promise<ISnapshotContents> {
175
177
  return getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
176
178
  const storageToken = await storageTokenFetcher(tokenFetchOptions, "TreesLatest", true);
@@ -187,6 +189,8 @@ async function fetchLatestSnapshotCore(
187
189
  const perfEvent = {
188
190
  eventName: "TreesLatest",
189
191
  attempts: tokenFetchOptions.refresh ? 2 : 1,
192
+ shareLinkPresent: odspResolvedUrl.sharingLinkToRedeem !== undefined,
193
+ redeemFallbackEnabled: enableRedeemFallback,
190
194
  };
191
195
  if (snapshotOptions !== undefined) {
192
196
  Object.entries(snapshotOptions).forEach(([key, value]) => {
@@ -344,30 +348,9 @@ async function fetchSnapshotContentsCoreV1(
344
348
  ): Promise<ISnapshotRequestAndResponseOptions> {
345
349
  const snapshotUrl = odspResolvedUrl.endpoints.snapshotStorageUrl;
346
350
  const url = `${snapshotUrl}/trees/latest?ump=1`;
347
- const formBoundary = uuid();
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
-
351
+ const { body, headers } = getFormBodyAndHeaders(odspResolvedUrl, storageToken, snapshotOptions);
369
352
  const fetchOptions = {
370
- body: postBody,
353
+ body,
371
354
  headers,
372
355
  signal: controller?.signal,
373
356
  method: "POST",
@@ -401,28 +384,55 @@ async function fetchSnapshotContentsCoreV2(
401
384
  epochTracker?: EpochTracker,
402
385
  ): Promise<ISnapshotRequestAndResponseOptions> {
403
386
  const fullUrl = `${odspResolvedUrl.siteUrl}/_api/v2.1/drives/${odspResolvedUrl.driveId}/items/${
404
- odspResolvedUrl.itemId}/opStream/attachments/latest/content`;
405
- const queryParams = { ...snapshotOptions };
406
- if (odspResolvedUrl.sharingLinkToRedeem) {
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);
387
+ odspResolvedUrl.itemId}/opStream/attachments/latest/content?ump=1`;
388
+
389
+ const { body, headers } = getFormBodyAndHeaders(odspResolvedUrl, storageToken, snapshotOptions);
412
390
  const fetchOptions = {
391
+ body,
413
392
  headers,
414
393
  signal: controller?.signal,
394
+ method: "POST",
415
395
  };
416
- const response = await (epochTracker?.fetchArray(url, fetchOptions, "treesLatest") ??
417
- fetchArray(url, fetchOptions));
396
+
397
+ const response = await (epochTracker?.fetchArray(fullUrl, fetchOptions, "treesLatest", true) ??
398
+ fetchArray(fullUrl, fetchOptions));
418
399
  const snapshotContents: ISnapshotContents = parseCompactSnapshotResponse(
419
400
  new ReadBuffer(new Uint8Array(response.content)));
420
401
  const finalSnapshotContents: IOdspResponse<ISnapshotContents> = { ...response, content: snapshotContents };
421
402
  return {
422
403
  odspSnapshotResponse: finalSnapshotContents,
423
404
  requestHeaders: headers,
424
- requestUrl: url,
405
+ requestUrl: fullUrl,
406
+ };
407
+ }
408
+
409
+ function getFormBodyAndHeaders(
410
+ odspResolvedUrl: IOdspResolvedUrl,
411
+ storageToken: string,
412
+ snapshotOptions: ISnapshotOptions | undefined,
413
+ ) {
414
+ const formBoundary = uuid();
415
+ const formParams: string[] = [];
416
+ formParams.push(`--${formBoundary}`);
417
+ formParams.push(`Authorization: Bearer ${storageToken}`);
418
+ formParams.push(`X-HTTP-Method-Override: GET`);
419
+ if (snapshotOptions !== undefined) {
420
+ Object.entries(snapshotOptions).forEach(([key, value]) => {
421
+ if (value !== undefined) {
422
+ formParams.push(`${key}: ${value}`);
423
+ }
424
+ });
425
+ }
426
+ if (odspResolvedUrl.sharingLinkToRedeem) {
427
+ formParams.push(`sl: ${odspResolvedUrl.sharingLinkToRedeem}`);
428
+ }
429
+ formParams.push(`_post: 1`);
430
+ formParams.push(`\r\n--${formBoundary}--`);
431
+ const postBody = formParams.join("\r\n");
432
+ const headers: {[index: string]: any} = {
433
+ "Content-Type": `multipart/form-data;boundary=${formBoundary}`,
425
434
  };
435
+ return { body: postBody, headers };
426
436
  }
427
437
 
428
438
  function validateAndEvalBlobsAndTrees(snapshot: ISnapshotContents) {
@@ -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") === true) {
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) {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "0.54.3";
9
+ export const pkgVersion = "0.55.2";
package/src/vroom.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { v4 as uuid } from "uuid";
6
7
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
8
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
8
9
  import { InstrumentedStorageTokenFetcher, IOdspUrlParts } from "@fluidframework/odsp-driver-definitions";
@@ -13,8 +14,8 @@ import { EpochTracker } from "./epochTracker";
13
14
  import { runWithRetry } from "./retryUtils";
14
15
 
15
16
  interface IJoinSessionBody {
16
- requestSocketToken?: boolean;
17
- guestDisplayName?: string;
17
+ requestSocketToken: boolean;
18
+ guestDisplayName: string;
18
19
  }
19
20
 
20
21
  /**
@@ -54,35 +55,34 @@ export async function fetchJoinSession(
54
55
  ...extraProps,
55
56
  },
56
57
  async (event) => {
57
- // TODO Extract the auth header-vs-query logic out
58
58
  const siteOrigin = getOrigin(urlParts.siteUrl);
59
- let queryParams = `access_token=${token}`;
60
- let headers = {};
61
- if (queryParams.length > 2048) {
62
- queryParams = "";
63
- headers = { Authorization: `Bearer ${token}` };
64
- }
65
- let body: IJoinSessionBody | undefined;
66
- if (requestSocketToken || guestDisplayName) {
67
- body = {};
68
- if (requestSocketToken) {
69
- body.requestSocketToken = true;
70
- }
71
- if (guestDisplayName) {
72
- body.guestDisplayName = guestDisplayName;
73
- }
74
- // IMPORTANT: Must set content-type header explicitly to application/json when request has body.
75
- // By default, request will use text/plain as content-type and will be rejected by backend.
76
- headers["Content-Type"] = "application/json";
59
+ const formBoundary = uuid();
60
+ let postBody = `--${formBoundary}\r\n`;
61
+ postBody += `Authorization: Bearer ${token}\r\n`;
62
+ postBody += `X-HTTP-Method-Override: POST\r\n`;
63
+ postBody += `Content-Type: application/json\r\n`;
64
+ postBody += `_post: 1\r\n`;
65
+ // Name should be there when socket token is requested and vice-versa.
66
+ if (requestSocketToken && guestDisplayName !== undefined) {
67
+ const body: IJoinSessionBody = {
68
+ requestSocketToken: true,
69
+ guestDisplayName,
70
+ };
71
+ postBody += `\r\n${JSON.stringify(body)}\r\n`;
77
72
  }
73
+ postBody += `\r\n--${formBoundary}--`;
74
+ const headers: {[index: string]: string} = {
75
+ "Content-Type": `multipart/form-data;boundary=${formBoundary}`,
76
+ };
78
77
 
79
78
  const response = await runWithRetry(
80
79
  async () => epochTracker.fetchAndParseAsJSON<ISocketStorageDiscovery>(
81
80
  `${getApiRoot(siteOrigin)}/drives/${
82
81
  urlParts.driveId
83
- }/items/${urlParts.itemId}/${path}?${queryParams}`,
84
- { method, headers, body: body ? JSON.stringify(body) : undefined },
82
+ }/items/${urlParts.itemId}/${path}?ump=1`,
83
+ { method, headers, body: postBody },
85
84
  "joinSession",
85
+ true,
86
86
  ),
87
87
  "joinSession",
88
88
  logger,