@fluidframework/odsp-driver 1.2.1 → 2.0.0-internal.1.0.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.
Files changed (120) hide show
  1. package/dist/compactSnapshotParser.d.ts.map +1 -1
  2. package/dist/compactSnapshotParser.js +4 -1
  3. package/dist/compactSnapshotParser.js.map +1 -1
  4. package/dist/compactSnapshotWriter.d.ts.map +1 -1
  5. package/dist/compactSnapshotWriter.js +6 -3
  6. package/dist/compactSnapshotWriter.js.map +1 -1
  7. package/dist/epochTracker.d.ts +1 -0
  8. package/dist/epochTracker.d.ts.map +1 -1
  9. package/dist/epochTracker.js +24 -5
  10. package/dist/epochTracker.js.map +1 -1
  11. package/dist/fetchSnapshot.d.ts.map +1 -1
  12. package/dist/fetchSnapshot.js +15 -12
  13. package/dist/fetchSnapshot.js.map +1 -1
  14. package/dist/getFileLink.d.ts +3 -6
  15. package/dist/getFileLink.d.ts.map +1 -1
  16. package/dist/getFileLink.js +27 -38
  17. package/dist/getFileLink.js.map +1 -1
  18. package/dist/odspDeltaStorageService.d.ts +2 -1
  19. package/dist/odspDeltaStorageService.d.ts.map +1 -1
  20. package/dist/odspDeltaStorageService.js +5 -4
  21. package/dist/odspDeltaStorageService.js.map +1 -1
  22. package/dist/odspDocumentService.d.ts +1 -0
  23. package/dist/odspDocumentService.d.ts.map +1 -1
  24. package/dist/odspDocumentService.js +11 -5
  25. package/dist/odspDocumentService.js.map +1 -1
  26. package/dist/odspDocumentStorageManager.d.ts +4 -3
  27. package/dist/odspDocumentStorageManager.d.ts.map +1 -1
  28. package/dist/odspDocumentStorageManager.js +67 -60
  29. package/dist/odspDocumentStorageManager.js.map +1 -1
  30. package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  31. package/dist/odspDriverUrlResolverForShareLink.js +4 -4
  32. package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
  33. package/dist/odspLocationRedirection.d.ts +14 -0
  34. package/dist/odspLocationRedirection.d.ts.map +1 -0
  35. package/dist/odspLocationRedirection.js +24 -0
  36. package/dist/odspLocationRedirection.js.map +1 -0
  37. package/dist/odspSummaryUploadManager.d.ts +2 -1
  38. package/dist/odspSummaryUploadManager.d.ts.map +1 -1
  39. package/dist/odspSummaryUploadManager.js +3 -4
  40. package/dist/odspSummaryUploadManager.js.map +1 -1
  41. package/dist/odspUrlHelper.js +2 -1
  42. package/dist/odspUrlHelper.js.map +1 -1
  43. package/dist/odspUtils.d.ts.map +1 -1
  44. package/dist/odspUtils.js +10 -2
  45. package/dist/odspUtils.js.map +1 -1
  46. package/dist/packageVersion.d.ts +1 -1
  47. package/dist/packageVersion.d.ts.map +1 -1
  48. package/dist/packageVersion.js +1 -1
  49. package/dist/packageVersion.js.map +1 -1
  50. package/dist/retryUtils.d.ts.map +1 -1
  51. package/dist/retryUtils.js +8 -4
  52. package/dist/retryUtils.js.map +1 -1
  53. package/lib/compactSnapshotParser.d.ts.map +1 -1
  54. package/lib/compactSnapshotParser.js +4 -1
  55. package/lib/compactSnapshotParser.js.map +1 -1
  56. package/lib/compactSnapshotWriter.d.ts.map +1 -1
  57. package/lib/compactSnapshotWriter.js +6 -3
  58. package/lib/compactSnapshotWriter.js.map +1 -1
  59. package/lib/epochTracker.d.ts +1 -0
  60. package/lib/epochTracker.d.ts.map +1 -1
  61. package/lib/epochTracker.js +26 -7
  62. package/lib/epochTracker.js.map +1 -1
  63. package/lib/fetchSnapshot.d.ts.map +1 -1
  64. package/lib/fetchSnapshot.js +15 -12
  65. package/lib/fetchSnapshot.js.map +1 -1
  66. package/lib/getFileLink.d.ts +3 -6
  67. package/lib/getFileLink.d.ts.map +1 -1
  68. package/lib/getFileLink.js +29 -40
  69. package/lib/getFileLink.js.map +1 -1
  70. package/lib/odspDeltaStorageService.d.ts +2 -1
  71. package/lib/odspDeltaStorageService.d.ts.map +1 -1
  72. package/lib/odspDeltaStorageService.js +5 -4
  73. package/lib/odspDeltaStorageService.js.map +1 -1
  74. package/lib/odspDocumentService.d.ts +1 -0
  75. package/lib/odspDocumentService.d.ts.map +1 -1
  76. package/lib/odspDocumentService.js +13 -7
  77. package/lib/odspDocumentService.js.map +1 -1
  78. package/lib/odspDocumentStorageManager.d.ts +4 -3
  79. package/lib/odspDocumentStorageManager.d.ts.map +1 -1
  80. package/lib/odspDocumentStorageManager.js +68 -61
  81. package/lib/odspDocumentStorageManager.js.map +1 -1
  82. package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  83. package/lib/odspDriverUrlResolverForShareLink.js +4 -4
  84. package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
  85. package/lib/odspLocationRedirection.d.ts +14 -0
  86. package/lib/odspLocationRedirection.d.ts.map +1 -0
  87. package/lib/odspLocationRedirection.js +20 -0
  88. package/lib/odspLocationRedirection.js.map +1 -0
  89. package/lib/odspSummaryUploadManager.d.ts +2 -1
  90. package/lib/odspSummaryUploadManager.d.ts.map +1 -1
  91. package/lib/odspSummaryUploadManager.js +3 -4
  92. package/lib/odspSummaryUploadManager.js.map +1 -1
  93. package/lib/odspUrlHelper.js +2 -1
  94. package/lib/odspUrlHelper.js.map +1 -1
  95. package/lib/odspUtils.d.ts.map +1 -1
  96. package/lib/odspUtils.js +11 -3
  97. package/lib/odspUtils.js.map +1 -1
  98. package/lib/packageVersion.d.ts +1 -1
  99. package/lib/packageVersion.d.ts.map +1 -1
  100. package/lib/packageVersion.js +1 -1
  101. package/lib/packageVersion.js.map +1 -1
  102. package/lib/retryUtils.d.ts.map +1 -1
  103. package/lib/retryUtils.js +9 -5
  104. package/lib/retryUtils.js.map +1 -1
  105. package/package.json +16 -16
  106. package/src/compactSnapshotParser.ts +3 -1
  107. package/src/compactSnapshotWriter.ts +6 -3
  108. package/src/epochTracker.ts +47 -7
  109. package/src/fetchSnapshot.ts +29 -15
  110. package/src/getFileLink.ts +33 -46
  111. package/src/odspDeltaStorageService.ts +4 -2
  112. package/src/odspDocumentService.ts +16 -12
  113. package/src/odspDocumentStorageManager.ts +49 -41
  114. package/src/odspDriverUrlResolverForShareLink.ts +2 -5
  115. package/src/odspLocationRedirection.ts +23 -0
  116. package/src/odspSummaryUploadManager.ts +3 -3
  117. package/src/odspUrlHelper.ts +1 -1
  118. package/src/odspUtils.ts +15 -4
  119. package/src/packageVersion.ts +1 -1
  120. package/src/retryUtils.ts +8 -5
@@ -6,7 +6,12 @@
6
6
  import { v4 as uuid } from "uuid";
7
7
  import { assert, Deferred } from "@fluidframework/common-utils";
8
8
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
9
- import { ThrottlingError, RateLimiter, NonRetryableError } from "@fluidframework/driver-utils";
9
+ import {
10
+ ThrottlingError,
11
+ RateLimiter,
12
+ NonRetryableError,
13
+ LocationRedirectionError,
14
+ } from "@fluidframework/driver-utils";
10
15
  import { IConnected } from "@fluidframework/protocol-definitions";
11
16
  import {
12
17
  snapshotKey,
@@ -15,9 +20,16 @@ import {
15
20
  IFileEntry,
16
21
  IPersistedCache,
17
22
  IOdspError,
23
+ IOdspErrorAugmentations,
24
+ IOdspResolvedUrl,
18
25
  } from "@fluidframework/odsp-driver-definitions";
19
26
  import { DriverErrorType } from "@fluidframework/driver-definitions";
20
- import { PerformanceEvent, isFluidError, normalizeError } from "@fluidframework/telemetry-utils";
27
+ import {
28
+ PerformanceEvent,
29
+ isFluidError,
30
+ normalizeError,
31
+ loggerToMonitoringContext,
32
+ } from "@fluidframework/telemetry-utils";
21
33
  import { fetchAndParseAsJSONHelper, fetchArray, fetchHelper, getOdspResolvedUrl, IOdspResponse } from "./odspUtils";
22
34
  import {
23
35
  IOdspCache,
@@ -27,6 +39,7 @@ import {
27
39
  import { IVersionedValueWithEpoch, persistedCacheValueVersion } from "./contracts";
28
40
  import { ClpCompliantAppHeader } from "./contractsPublic";
29
41
  import { pkgVersion as driverVersion } from "./packageVersion";
42
+ import { patchOdspResolvedUrl } from "./odspLocationRedirection";
30
43
 
31
44
  export type FetchType = "blob" | "createBlob" | "createFile" | "joinSession" | "ops" | "test" | "snapshotTree" |
32
45
  "treesLatest" | "uploadSummary" | "push" | "versions";
@@ -47,6 +60,7 @@ export const defaultCacheExpiryTimeoutMs: number = 2 * 24 * 60 * 60 * 1000;
47
60
  export class EpochTracker implements IPersistedFileCache {
48
61
  private _fluidEpoch: string | undefined;
49
62
 
63
+ private readonly snapshotCacheExpiryTimeoutMs: number;
50
64
  public readonly rateLimiter: RateLimiter;
51
65
  private readonly driverId = uuid();
52
66
  // This tracks the request number made by the driver instance.
@@ -59,6 +73,12 @@ export class EpochTracker implements IPersistedFileCache {
59
73
  ) {
60
74
  // Limits the max number of concurrent requests to 24.
61
75
  this.rateLimiter = new RateLimiter(24);
76
+
77
+ // We need this for GC testing until we properly plumb through the snapshot expiration policy (see PR #11168)
78
+ this.snapshotCacheExpiryTimeoutMs =
79
+ loggerToMonitoringContext(logger).config.getBoolean("Fluid.Driver.Odsp.TestOverride.DisableSnapshotCache")
80
+ ? 0
81
+ : defaultCacheExpiryTimeoutMs;
62
82
  }
63
83
 
64
84
  // public for UT purposes only!
@@ -95,17 +115,17 @@ export class EpochTracker implements IPersistedFileCache {
95
115
  } else if (this._fluidEpoch !== value.fluidEpoch) {
96
116
  return undefined;
97
117
  }
98
- // Expire the cached snapshot if it's older than the defaultCacheExpiryTimeoutMs and immediately
118
+ // Expire the cached snapshot if it's older than snapshotCacheExpiryTimeoutMs and immediately
99
119
  // expire all old caches that do not have cacheEntryTime
100
120
  if (entry.type === snapshotKey) {
101
121
  const cacheTime = value.value?.cacheEntryTime;
102
122
  const currentTime = Date.now();
103
- if (cacheTime === undefined || currentTime - cacheTime >= defaultCacheExpiryTimeoutMs) {
123
+ if (cacheTime === undefined || currentTime - cacheTime >= this.snapshotCacheExpiryTimeoutMs) {
104
124
  this.logger.sendTelemetryEvent(
105
125
  {
106
126
  eventName: "odspVersionsCacheExpired",
107
127
  duration: currentTime - cacheTime,
108
- maxCacheAgeMs: defaultCacheExpiryTimeoutMs,
128
+ maxCacheAgeMs: this.snapshotCacheExpiryTimeoutMs,
109
129
  });
110
130
  await this.removeEntries();
111
131
  return undefined;
@@ -121,8 +141,8 @@ export class EpochTracker implements IPersistedFileCache {
121
141
 
122
142
  public async put(entry: IEntry, value: any) {
123
143
  assert(this._fluidEpoch !== undefined, 0x1dd /* "no epoch" */);
124
- // For snapshots, the value should have the cacheEntryTime. This will be used to expire snapshots older
125
- // than the defaultCacheExpiryTimeoutMs.
144
+ // For snapshots, the value should have the cacheEntryTime.
145
+ // This will be used to expire snapshots older than snapshotCacheExpiryTimeoutMs.
126
146
  if (entry.type === snapshotKey) {
127
147
  value.cacheEntryTime = value.cacheEntryTime ?? Date.now();
128
148
  }
@@ -224,6 +244,26 @@ export class EpochTracker implements IPersistedFileCache {
224
244
  }
225
245
  await this.checkForEpochError(error, epochFromResponse, fetchType);
226
246
  throw error;
247
+ }).catch((error) => {
248
+ // If the error is about location redirection, then we need to generate new resolved url with correct
249
+ // location info.
250
+ if (isFluidError(error) && error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError) {
251
+ const redirectLocation = (error as IOdspErrorAugmentations).redirectLocation;
252
+ if (redirectLocation !== undefined) {
253
+ const redirectUrl: IOdspResolvedUrl = patchOdspResolvedUrl(
254
+ this.fileEntry.resolvedUrl,
255
+ redirectLocation,
256
+ );
257
+ const locationRedirectionError = new LocationRedirectionError(
258
+ error.message,
259
+ redirectUrl,
260
+ { driverVersion, redirectLocation },
261
+ );
262
+ locationRedirectionError.addTelemetryProperties(error.getTelemetryProperties());
263
+ throw locationRedirectionError;
264
+ }
265
+ }
266
+ throw error;
227
267
  }).catch((error) => {
228
268
  const fluidError = normalizeError(error, { props: { XRequestStatsHeader: clientCorrelationId } });
229
269
  throw fluidError;
@@ -16,7 +16,7 @@ import {
16
16
  InstrumentedStorageTokenFetcher,
17
17
  } from "@fluidframework/odsp-driver-definitions";
18
18
  import { ISnapshotTree } from "@fluidframework/protocol-definitions";
19
- import { isRuntimeMessage, NonRetryableError } from "@fluidframework/driver-utils";
19
+ import { DriverErrorTelemetryProps, isRuntimeMessage, NonRetryableError } from "@fluidframework/driver-utils";
20
20
  import { IOdspSnapshot, ISnapshotCachedEntry, IVersionedValueWithEpoch, persistedCacheValueVersion } from "./contracts";
21
21
  import { getQueryString } from "./getQueryString";
22
22
  import { getUrlAndHeadersWithAuth } from "./getUrlAndHeadersWithAuth";
@@ -234,19 +234,30 @@ async function fetchLatestSnapshotCore(
234
234
  snapshotOptions,
235
235
  controller,
236
236
  );
237
+
237
238
  const odspResponse = response.odspResponse;
238
239
  const contentType = odspResponse.headers.get("content-type");
239
- odspResponse.propsToLog = {
240
+
241
+ const propsToLog: DriverErrorTelemetryProps = {
240
242
  ...odspResponse.propsToLog,
241
243
  contentType,
242
244
  accept: response.requestHeaders.accept,
245
+ driverVersion: pkgVersion,
243
246
  };
247
+
248
+ // Measure how much time we spend processing payload
249
+ const snapshotParseEvent = PerformanceEvent.start(logger, {
250
+ eventName: "SnapshotParse",
251
+ ...propsToLog,
252
+ });
253
+
244
254
  let parsedSnapshotContents: IOdspResponse<ISnapshotContents> | undefined;
255
+
245
256
  try {
246
257
  switch (contentType) {
247
258
  case "application/json": {
248
259
  const text = await odspResponse.content.text();
249
- odspResponse.propsToLog.bodySize = text.length;
260
+ propsToLog.bodySize = text.length;
250
261
  const content: IOdspSnapshot = JSON.parse(text);
251
262
  validateBlobsAndTrees(content);
252
263
  const snapshotContents: ISnapshotContents =
@@ -256,7 +267,7 @@ async function fetchLatestSnapshotCore(
256
267
  }
257
268
  case "application/ms-fluid": {
258
269
  const content = await odspResponse.content.arrayBuffer();
259
- odspResponse.propsToLog.bodySize = content.byteLength;
270
+ propsToLog.bodySize = content.byteLength;
260
271
  const snapshotContents: ISnapshotContents = parseCompactSnapshotResponse(
261
272
  new ReadBuffer(new Uint8Array(content)));
262
273
  if (snapshotContents.snapshotTree.trees === undefined ||
@@ -264,7 +275,7 @@ async function fetchLatestSnapshotCore(
264
275
  throw new NonRetryableError(
265
276
  "Returned odsp snapshot is malformed. No trees or blobs!",
266
277
  DriverErrorType.incorrectServerResponse,
267
- { driverVersion: pkgVersion, ...odspResponse.propsToLog },
278
+ propsToLog,
268
279
  );
269
280
  }
270
281
  parsedSnapshotContents = { ...odspResponse, content: snapshotContents };
@@ -274,12 +285,12 @@ async function fetchLatestSnapshotCore(
274
285
  throw new NonRetryableError(
275
286
  "Unknown snapshot content type",
276
287
  DriverErrorType.incorrectServerResponse,
277
- { driverVersion: pkgVersion, ...odspResponse.propsToLog },
288
+ propsToLog,
278
289
  );
279
290
  }
280
291
  } catch (error) {
281
292
  if (isFluidError(error)) {
282
- error.addTelemetryProperties({ driverVersion: pkgVersion, ...odspResponse.propsToLog });
293
+ error.addTelemetryProperties(propsToLog);
283
294
  throw error;
284
295
  }
285
296
  const enhancedError = wrapError(
@@ -287,11 +298,14 @@ async function fetchLatestSnapshotCore(
287
298
  (errorMessage) => new NonRetryableError(
288
299
  `Error parsing snapshot response: ${errorMessage}`,
289
300
  DriverErrorType.genericError,
290
- { driverVersion: pkgVersion, ...odspResponse.propsToLog }));
301
+ propsToLog));
291
302
  throw enhancedError;
292
303
  }
304
+
293
305
  assert(parsedSnapshotContents !== undefined, 0x312 /* snapshot should be parsed */);
294
306
  const snapshot = parsedSnapshotContents.content;
307
+ const { trees, numBlobs, encodedBlobsSize } = evalBlobsAndTrees(snapshot);
308
+
295
309
  // From: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming
296
310
  // fetchStart: immediately before the browser starts to fetch the resource.
297
311
  // requestStart: immediately before the browser starts requesting the resource from the server
@@ -344,9 +358,6 @@ async function fetchLatestSnapshotCore(
344
358
  }
345
359
  }
346
360
 
347
- const { numTrees, numBlobs, encodedBlobsSize } =
348
- evalBlobsAndTrees(parsedSnapshotContents.content);
349
-
350
361
  // There are some scenarios in ODSP where we cannot cache, trees/latest will explicitly tell us when we
351
362
  // cannot cache using an HTTP response header.
352
363
  const canCache =
@@ -375,8 +386,11 @@ async function fetchLatestSnapshotCore(
375
386
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
376
387
  putInCache(valueWithEpoch);
377
388
  }
389
+
390
+ snapshotParseEvent.end();
391
+
378
392
  event.end({
379
- trees: numTrees,
393
+ trees,
380
394
  blobs: snapshot.blobs?.size ?? 0,
381
395
  leafNodes: numBlobs,
382
396
  encodedBlobsSize,
@@ -406,7 +420,7 @@ async function fetchLatestSnapshotCore(
406
420
  // Azure Fluid Relay service is the redeem status (S means success), and FRP is a flag to indicate
407
421
  // if the permission has changed.
408
422
  sltelemetry: odspResponse.headers.get("x-fluid-sltelemetry"),
409
- ...odspResponse.propsToLog,
423
+ ...propsToLog,
410
424
  });
411
425
  return snapshot;
412
426
  },
@@ -466,13 +480,13 @@ function getFormBodyAndHeaders(
466
480
  }
467
481
 
468
482
  function evalBlobsAndTrees(snapshot: ISnapshotContents) {
469
- const numTrees = countTreesInSnapshotTree(snapshot.snapshotTree);
483
+ const trees = countTreesInSnapshotTree(snapshot.snapshotTree);
470
484
  const numBlobs = snapshot.blobs.size;
471
485
  let encodedBlobsSize = 0;
472
486
  for (const [_, blobContent] of snapshot.blobs) {
473
487
  encodedBlobsSize += blobContent.byteLength;
474
488
  }
475
- return { numTrees, numBlobs, encodedBlobsSize };
489
+ return { trees, numBlobs, encodedBlobsSize };
476
490
  }
477
491
 
478
492
  export function validateBlobsAndTrees(snapshot: IOdspSnapshot) {
@@ -4,19 +4,15 @@
4
4
  */
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
- import { assert, delay } from "@fluidframework/common-utils";
8
- import { canRetryOnError, getRetryDelayFromError, NonRetryableError } from "@fluidframework/driver-utils";
7
+ import { assert } from "@fluidframework/common-utils";
8
+ import { canRetryOnError, NonRetryableError } from "@fluidframework/driver-utils";
9
9
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
10
10
  import { DriverErrorType } from "@fluidframework/driver-definitions";
11
- import {
12
- IOdspUrlParts,
13
- OdspResourceTokenFetchOptions,
14
- IdentityType,
15
- TokenFetcher,
16
- } from "@fluidframework/odsp-driver-definitions";
11
+ import { IOdspUrlParts, OdspResourceTokenFetchOptions, TokenFetcher } from "@fluidframework/odsp-driver-definitions";
17
12
  import { getUrlAndHeadersWithAuth } from "./getUrlAndHeadersWithAuth";
18
13
  import { fetchHelper, getWithRetryForTokenRefresh, toInstrumentedOdspTokenFetcher } from "./odspUtils";
19
14
  import { pkgVersion as driverVersion } from "./packageVersion";
15
+ import { runWithRetry } from "./retryUtils";
20
16
 
21
17
  // Store cached responses for the lifetime of web session as file link remains the same for given file item
22
18
  const fileLinkCache = new Map<string, Promise<string>>();
@@ -28,17 +24,13 @@ const fileLinkCache = new Map<string, Promise<string>>();
28
24
  * throttling error. In future, we are thinking of app allowing to pass some cancel token, with which
29
25
  * we would be able to stop retrying.
30
26
  * @param getToken - used to fetch access tokens needed to execute operation
31
- * @param siteUrl - url of the site that contains the file
32
- * @param driveId - drive where file is stored
33
- * @param itemId - file id
34
- * @param identityType - type of client account
27
+ * @param odspUrlParts - object describing file storage identity
35
28
  * @param logger - used to log results of operation, including any error
36
29
  * @returns Promise which resolves to file link url when successful; otherwise, undefined.
37
30
  */
38
31
  export async function getFileLink(
39
32
  getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
40
33
  odspUrlParts: IOdspUrlParts,
41
- identityType: IdentityType,
42
34
  logger: ITelemetryLogger,
43
35
  ): Promise<string> {
44
36
  const cacheKey = `${odspUrlParts.siteUrl}_${odspUrlParts.driveId}_${odspUrlParts.itemId}`;
@@ -47,33 +39,29 @@ export async function getFileLink(
47
39
  return maybeFileLinkCacheEntry;
48
40
  }
49
41
 
50
- const valueGenerator = async function() {
51
- let result: string | undefined;
52
- let success = false;
53
- let retryAfterMs = 1000;
54
- do {
55
- try {
56
- result = await getFileLinkCore(getToken, odspUrlParts, identityType, logger);
57
- success = true;
58
- } catch (err) {
59
- // If it is not retriable, then just throw
60
- if (!canRetryOnError(err)) {
61
- // Delete from the cache to permit retrying later.
62
- fileLinkCache.delete(cacheKey);
63
- throw err;
64
- }
65
- // If the error is throttling error, then wait for the specified time before retrying.
66
- // If the waitTime is not specified, then we start with retrying immediately to max of 8s.
67
- retryAfterMs = getRetryDelayFromError(err) ?? Math.min(retryAfterMs * 2, 8000);
68
- await delay(retryAfterMs);
42
+ const fileLinkGenerator = async function() {
43
+ let fileLinkCore: string;
44
+ try {
45
+ fileLinkCore = await runWithRetry(
46
+ async () => getFileLinkCore(getToken, odspUrlParts, logger),
47
+ "getFileLinkCore",
48
+ logger,
49
+ );
50
+ } catch (err) {
51
+ // runWithRetry throws a non retriable error after it hits the max # of attempts
52
+ // or encounters an unexpected error type
53
+ if (!canRetryOnError(err)) {
54
+ // Delete from the cache to permit retrying later.
55
+ fileLinkCache.delete(cacheKey);
69
56
  }
70
- } while (!success);
57
+ throw err;
58
+ }
71
59
 
72
60
  // We are guaranteed to run the getFileLinkCore at least once with successful result (which must be a string)
73
- assert(result !== undefined, 0x292 /* "Unexpected undefined result from getFileLinkCore" */);
74
- return result;
61
+ assert(fileLinkCore !== undefined, 0x292 /* "Unexpected undefined result from getFileLinkCore" */);
62
+ return fileLinkCore;
75
63
  };
76
- const fileLink = valueGenerator();
64
+ const fileLink = fileLinkGenerator();
77
65
  fileLinkCache.set(cacheKey, fileLink);
78
66
  return fileLink;
79
67
  }
@@ -81,15 +69,14 @@ export async function getFileLink(
81
69
  async function getFileLinkCore(
82
70
  getToken: TokenFetcher<OdspResourceTokenFetchOptions>,
83
71
  odspUrlParts: IOdspUrlParts,
84
- identityType: IdentityType,
85
72
  logger: ITelemetryLogger,
86
73
  ): Promise<string> {
87
- const fileItem = await getFileItemLite(getToken, odspUrlParts, logger, identityType === "Consumer");
74
+ const fileItem = await getFileItemLite(getToken, odspUrlParts, logger, true);
88
75
 
89
76
  // ODSP link requires extra call to return link that is resistant to file being renamed or moved to different folder
90
77
  return PerformanceEvent.timedExecAsync(
91
78
  logger,
92
- { eventName: "odspFileLink", requestName: "getSharingLink" },
79
+ { eventName: "odspFileLink", requestName: "getSharingInformation" },
93
80
  async (event) => {
94
81
  let attempts = 0;
95
82
  let additionalProps;
@@ -106,11 +93,11 @@ async function getFileLinkCore(
106
93
  0x2bb /* "Instrumented token fetcher with throwOnNullToken = true should never return null" */);
107
94
 
108
95
  const { url, headers } = getUrlAndHeadersWithAuth(
109
- `${odspUrlParts.siteUrl}/_api/web/GetFileByServerRelativeUrl(@a1)/Linkingurl?@a1=${
110
- encodeURIComponent(`'${new URL(fileItem.webDavUrl).pathname}'`)
96
+ `${odspUrlParts.siteUrl}/_api/web/GetFileByUrl(@a1)/ListItemAllFields/GetSharingInformation?@a1=${
97
+ encodeURIComponent(`'${fileItem.webDavUrl}'`)
111
98
  }`,
112
99
  storageToken,
113
- false,
100
+ true,
114
101
  );
115
102
  const requestInit = {
116
103
  method: "POST",
@@ -124,15 +111,15 @@ async function getFileLinkCore(
124
111
  additionalProps = response.propsToLog;
125
112
 
126
113
  const sharingInfo = await response.content.json();
127
- const linkingUrl = sharingInfo?.d?.LinkingUrl;
128
- if (typeof linkingUrl !== "string") {
114
+ const directUrl = sharingInfo?.d?.directUrl;
115
+ if (typeof directUrl !== "string") {
129
116
  // This will retry once in getWithRetryForTokenRefresh
130
117
  throw new NonRetryableError(
131
- "Malformed GetSharingLink response",
118
+ "Malformed GetSharingInformation response",
132
119
  DriverErrorType.incorrectServerResponse,
133
120
  { driverVersion });
134
121
  }
135
- return linkingUrl;
122
+ return directUrl;
136
123
  });
137
124
  event.end({ ...additionalProps, attempts });
138
125
  return fileLink;
@@ -35,13 +35,14 @@ export class OdspDeltaStorageService {
35
35
  * @param from - inclusive
36
36
  * @param to - exclusive
37
37
  * @param telemetryProps - properties to add when issuing telemetry events
38
+ * @param scenarioName - reason for fetching ops
38
39
  * @returns ops retrieved & info if result was partial (i.e. more is available)
39
40
  */
40
41
  public async get(
41
42
  from: number,
42
43
  to: number,
43
44
  telemetryProps: ITelemetryProperties,
44
- fetchReason?: string,
45
+ scenarioName?: string,
45
46
  ): Promise<IDeltasFetchResult> {
46
47
  return getWithRetryForTokenRefresh(async (options) => {
47
48
  // Note - this call ends up in getSocketStorageDiscovery() and can refresh token
@@ -78,7 +79,7 @@ export class OdspDeltaStorageService {
78
79
  },
79
80
  "ops",
80
81
  true,
81
- fetchReason,
82
+ scenarioName,
82
83
  );
83
84
  clearTimeout(timer);
84
85
  const deltaStorageResponse = response.content;
@@ -99,6 +100,7 @@ export class OdspDeltaStorageService {
99
100
  from,
100
101
  to,
101
102
  ...telemetryProps,
103
+ reason: scenarioName,
102
104
  });
103
105
 
104
106
  // It is assumed that server always returns all the ops that it has in the range that was requested.
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
- import { performance } from "@fluidframework/common-utils";
7
+ import { assert, performance } from "@fluidframework/common-utils";
8
8
  import {
9
9
  ChildLogger,
10
10
  IFluidErrorBase,
@@ -21,12 +21,7 @@ import {
21
21
  IDocumentServicePolicies,
22
22
  DriverErrorType,
23
23
  } from "@fluidframework/driver-definitions";
24
- import {
25
- canRetryOnError,
26
- DeltaStreamConnectionForbiddenError,
27
- NonRetryableError,
28
- } from "@fluidframework/driver-utils";
29
- import { IFacetCodes } from "@fluidframework/odsp-doclib-utils";
24
+ import { canRetryOnError, DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
30
25
  import {
31
26
  IClient,
32
27
  ISequencedDocumentMessage,
@@ -39,6 +34,7 @@ import {
39
34
  InstrumentedStorageTokenFetcher,
40
35
  OdspErrorType,
41
36
  } from "@fluidframework/odsp-driver-definitions";
37
+ import { hasFacetCodes } from "@fluidframework/odsp-doclib-utils";
42
38
  import type { io as SocketIOClientStatic } from "socket.io-client";
43
39
  import { HostStoragePolicyInternal, ISocketStorageDiscovery } from "./contracts";
44
40
  import { IOdspCache } from "./odspCache";
@@ -113,6 +109,8 @@ export class OdspDocumentService implements IDocumentService {
113
109
 
114
110
  private currentConnection?: OdspDocumentDeltaConnection;
115
111
 
112
+ private relayServiceTenantAndSessionId: string | undefined;
113
+
116
114
  /**
117
115
  * @param odspResolvedUrl - resolved url identifying document that will be managed by this service instance.
118
116
  * @param getStorageToken - function that can provide the storage token. This is is also referred to as
@@ -189,6 +187,11 @@ export class OdspDocumentService implements IDocumentService {
189
187
  }
190
188
  throw new Error("Disconnected while uploading summary (attempt to perform flush())");
191
189
  },
190
+ () => {
191
+ assert(this.relayServiceTenantAndSessionId !== undefined,
192
+ 0x37b /* relayServiceTenantAndSessionId should be present */);
193
+ return this.relayServiceTenantAndSessionId;
194
+ },
192
195
  this.mc.config.getNumber("Fluid.Driver.Odsp.snapshotFormatFetchType"),
193
196
  );
194
197
  }
@@ -271,7 +274,7 @@ export class OdspDocumentService implements IDocumentService {
271
274
  this.socketIoClientFactory().catch(annotateAndRethrowConnectionError("socketIoClientFactory")),
272
275
  ]);
273
276
 
274
- const finalWebsocketToken = websocketToken ?? (websocketEndpoint.socketToken || null);
277
+ const finalWebsocketToken = websocketToken ?? (websocketEndpoint.socketToken ?? null);
275
278
  if (finalWebsocketToken === null) {
276
279
  throw this.annotateConnectionError(
277
280
  new NonRetryableError(
@@ -344,10 +347,9 @@ export class OdspDocumentService implements IDocumentService {
344
347
  requestSocketToken: boolean,
345
348
  options: TokenFetchOptionsEx,
346
349
  ) {
347
- return this.joinSessionCore(requestSocketToken, options).catch((e) => {
348
- const likelyFacetCodes = e as IFacetCodes;
349
- if (Array.isArray(likelyFacetCodes.facetCodes)) {
350
- for (const code of likelyFacetCodes.facetCodes) {
350
+ const response = await this.joinSessionCore(requestSocketToken, options).catch((e) => {
351
+ if (hasFacetCodes(e) && e.facetCodes !== undefined) {
352
+ for (const code of e.facetCodes) {
351
353
  switch (code) {
352
354
  case "sessionForbiddenOnPreservedFiles":
353
355
  case "sessionForbiddenOnModerationEnabledLibrary":
@@ -364,6 +366,8 @@ export class OdspDocumentService implements IDocumentService {
364
366
  }
365
367
  throw e;
366
368
  });
369
+ this.relayServiceTenantAndSessionId = `${response.tenantId}/${response.id}`;
370
+ return response;
367
371
  }
368
372
 
369
373
  private async joinSessionCore(