@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
@@ -16,6 +16,7 @@ import * as api from "@fluidframework/protocol-definitions";
16
16
  import {
17
17
  ISummaryContext,
18
18
  DriverErrorType,
19
+ FetchSource,
19
20
  } from "@fluidframework/driver-definitions";
20
21
  import { RateLimiter, NonRetryableError } from "@fluidframework/driver-utils";
21
22
  import {
@@ -92,6 +93,7 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
92
93
  private readonly hostPolicy: HostStoragePolicyInternal,
93
94
  private readonly epochTracker: EpochTracker,
94
95
  private readonly flushCallback: () => Promise<FlushResult>,
96
+ private readonly relayServiceTenantAndSessionId: () => string,
95
97
  private readonly snapshotFormatFetchType?: SnapshotFormatSupportType,
96
98
  ) {
97
99
  super();
@@ -107,6 +109,7 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
107
109
  logger,
108
110
  epochTracker,
109
111
  !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
112
+ this.relayServiceTenantAndSessionId,
110
113
  );
111
114
  }
112
115
 
@@ -204,7 +207,7 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
204
207
  return super.getSnapshotTree(version, scenarioName);
205
208
  }
206
209
 
207
- public async getVersions(blobid: string | null, count: number, scenarioName?: string): Promise<api.IVersion[]> {
210
+ public async getVersions(blobid: string | null, count: number, scenarioName?: string, fetchSource?: FetchSource): Promise<api.IVersion[]> {
208
211
  // Regular load workflow uses blobId === documentID to indicate "latest".
209
212
  if (blobid !== this.documentId && blobid) {
210
213
  // FluidFetch & FluidDebugger tools use empty sting to query for versions
@@ -230,14 +233,19 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
230
233
  const hostSnapshotOptions = this.hostPolicy.snapshotOptions;
231
234
  const odspSnapshotCacheValue: ISnapshotContents = await PerformanceEvent.timedExecAsync(
232
235
  this.logger,
233
- { eventName: "ObtainSnapshot" },
236
+ { eventName: "ObtainSnapshot", fetchSource },
234
237
  async (event: PerformanceEvent) => {
235
238
  const props: GetVersionsTelemetryProps = {};
236
239
  let retrievedSnapshot: ISnapshotContents | undefined;
237
- // Here's the logic to grab the persistent cache snapshot implemented by the host
238
- // Epoch tracker is responsible for communicating with the persistent cache, handling epochs and cache versions
239
- const cachedSnapshotP: Promise<ISnapshotContents | undefined> =
240
- this.epochTracker.get(createCacheSnapshotKey(this.odspResolvedUrl))
240
+
241
+ let method: string;
242
+ if (fetchSource === FetchSource.noCache) {
243
+ retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions, scenarioName);
244
+ method = "networkOnly";
245
+ } else {
246
+ // Here's the logic to grab the persistent cache snapshot implemented by the host
247
+ // Epoch tracker is responsible for communicating with the persistent cache, handling epochs and cache versions
248
+ const cachedSnapshotP: Promise<ISnapshotContents | undefined> = this.epochTracker.get(createCacheSnapshotKey(this.odspResolvedUrl))
241
249
  .then(async (snapshotCachedEntry: ISnapshotCachedEntry) => {
242
250
  if (snapshotCachedEntry !== undefined) {
243
251
  // If the cached entry does not contain the entry time, then assign it a default of 30 days old.
@@ -264,46 +272,46 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
264
272
  return snapshotCachedEntry;
265
273
  });
266
274
 
267
- // Based on the concurrentSnapshotFetch policy:
268
- // Either retrieve both the network and cache snapshots concurrently and pick the first to return,
269
- // or grab the cache value and then the network value if the cache value returns undefined.
270
- let method: string;
271
- if (this.hostPolicy.concurrentSnapshotFetch && !this.hostPolicy.summarizerClient) {
272
- const networkSnapshotP = this.fetchSnapshot(hostSnapshotOptions, scenarioName);
273
-
274
- // Ensure that failures on both paths are ignored initially.
275
- // I.e. if cache fails for some reason, we will proceed with network result.
276
- // And vice versa - if (for example) client is offline and network request fails first, we
277
- // do want to attempt to succeed with cached data!
278
- const promiseRaceWinner = await promiseRaceWithWinner([
279
- cachedSnapshotP.catch(() => undefined),
280
- networkSnapshotP.catch(() => undefined),
281
- ]);
282
- retrievedSnapshot = promiseRaceWinner.value;
283
- method = promiseRaceWinner.index === 0 ? "cache" : "network";
284
-
285
- if (retrievedSnapshot === undefined) {
286
- // if network failed -> wait for cache ( then return network failure)
287
- // If cache returned empty or failed -> wait for network (success of failure)
288
- if (promiseRaceWinner.index === 1) {
289
- retrievedSnapshot = await cachedSnapshotP;
290
- method = "cache";
291
- }
275
+ // Based on the concurrentSnapshotFetch policy:
276
+ // Either retrieve both the network and cache snapshots concurrently and pick the first to return,
277
+ // or grab the cache value and then the network value if the cache value returns undefined.
278
+ if (this.hostPolicy.concurrentSnapshotFetch && !this.hostPolicy.summarizerClient) {
279
+ const networkSnapshotP = this.fetchSnapshot(hostSnapshotOptions, scenarioName);
280
+
281
+ // Ensure that failures on both paths are ignored initially.
282
+ // I.e. if cache fails for some reason, we will proceed with network result.
283
+ // And vice versa - if (for example) client is offline and network request fails first, we
284
+ // do want to attempt to succeed with cached data!
285
+ const promiseRaceWinner = await promiseRaceWithWinner([
286
+ cachedSnapshotP.catch(() => undefined),
287
+ networkSnapshotP.catch(() => undefined),
288
+ ]);
289
+ retrievedSnapshot = promiseRaceWinner.value;
290
+ method = promiseRaceWinner.index === 0 ? "cache" : "network";
291
+
292
292
  if (retrievedSnapshot === undefined) {
293
- retrievedSnapshot = await networkSnapshotP;
294
- method = "network";
293
+ // if network failed -> wait for cache ( then return network failure)
294
+ // If cache returned empty or failed -> wait for network (success of failure)
295
+ if (promiseRaceWinner.index === 1) {
296
+ retrievedSnapshot = await cachedSnapshotP;
297
+ method = "cache";
298
+ }
299
+ if (retrievedSnapshot === undefined) {
300
+ retrievedSnapshot = await networkSnapshotP;
301
+ method = "network";
302
+ }
295
303
  }
296
- }
297
- } else {
298
- // Note: There's a race condition here - another caller may come past the undefined check
299
- // while the first caller is awaiting later async code in this block.
304
+ } else {
305
+ // Note: There's a race condition here - another caller may come past the undefined check
306
+ // while the first caller is awaiting later async code in this block.
300
307
 
301
- retrievedSnapshot = await cachedSnapshotP;
308
+ retrievedSnapshot = await cachedSnapshotP;
302
309
 
303
- method = retrievedSnapshot !== undefined ? "cache" : "network";
310
+ method = retrievedSnapshot !== undefined ? "cache" : "network";
304
311
 
305
- if (retrievedSnapshot === undefined) {
306
- retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions, scenarioName);
312
+ if (retrievedSnapshot === undefined) {
313
+ retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions, scenarioName);
314
+ }
307
315
  }
308
316
  }
309
317
  if (method === "network") {
@@ -136,7 +136,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
136
136
  // We need to remove the nav param if set by host when setting the sharelink as otherwise the shareLinkId
137
137
  // when redeeming the share link during the redeem fallback for trees latest call becomes greater than
138
138
  // the eligible length.
139
- odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo || {},
139
+ odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo ?? {},
140
140
  { sharingLinkToRedeem: this.removeNavParam(request.url) });
141
141
  }
142
142
  if (odspResolvedUrl.itemId) {
@@ -171,10 +171,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
171
171
  return cachedLinkPromise;
172
172
  }
173
173
  const newLinkPromise = getFileLink(
174
- this.shareLinkFetcherProps.tokenFetcher,
175
- resolvedUrl,
176
- this.shareLinkFetcherProps.identityType,
177
- this.logger,
174
+ this.shareLinkFetcherProps.tokenFetcher, resolvedUrl, this.logger,
178
175
  ).catch((error) => {
179
176
  // This should imply that error is a non-retriable error.
180
177
  this.logger.sendErrorEvent({ eventName: "FluidFileUrlError" }, error);
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IFluidResolvedUrl } from "@fluidframework/driver-definitions";
7
+ import { IOdspResolvedUrl } from "@fluidframework/odsp-driver-definitions";
8
+ import { getOdspResolvedUrl } from "./odspUtils";
9
+
10
+ /**
11
+ * It takes a resolved url with old siteUrl and creates a new resolved url with updated site url domain.
12
+ * @param resolvedUrl - Previous odsp resolved url with older site url.
13
+ * @param redirectLocation - Url at which the network call has to be made. It contains new site info.
14
+ * @returns - Resolved url after patching the correct siteUrl.
15
+ */
16
+ export function patchOdspResolvedUrl(resolvedUrl: IFluidResolvedUrl, redirectLocation: string): IOdspResolvedUrl {
17
+ const odspResolvedUrl = { ...getOdspResolvedUrl(resolvedUrl) };
18
+ // Generate the new SiteUrl from the redirection location.
19
+ const newSiteDomain = new URL(redirectLocation).origin;
20
+ const newSiteUrl = `${newSiteDomain}${new URL(odspResolvedUrl.siteUrl).pathname}`;
21
+ odspResolvedUrl.siteUrl = newSiteUrl;
22
+ return odspResolvedUrl;
23
+ }
@@ -39,6 +39,7 @@ export class OdspSummaryUploadManager {
39
39
  logger: ITelemetryLogger,
40
40
  private readonly epochTracker: EpochTracker,
41
41
  private readonly forceAccessTokenViaAuthorizationHeader: boolean,
42
+ private readonly relayServiceTenantAndSessionId: () => string,
42
43
  ) {
43
44
  this.mc = loggerToMonitoringContext(logger);
44
45
  }
@@ -93,9 +94,8 @@ export class OdspSummaryUploadManager {
93
94
  this.forceAccessTokenViaAuthorizationHeader,
94
95
  );
95
96
  headers["Content-Type"] = "application/json";
96
- if (parentHandle) {
97
- headers["If-Match"] = `fluid:containerid=${parentHandle}`;
98
- }
97
+ headers["If-Match"] = `fluid:sessionid=${
98
+ this.relayServiceTenantAndSessionId()}${parentHandle ? `;containerid=${parentHandle}` : ""}`;
99
99
 
100
100
  const postBody = JSON.stringify(snapshot);
101
101
 
@@ -69,7 +69,7 @@ export function isOdcUrl(url: string | URL): boolean {
69
69
  // Format: /v2.1/drives('ABC123')/items('ABC123!123')
70
70
  const odcODataRegex = /\/v2.1\/drives\('[^/]+'\)\/items\('[\da-z]+!\d+'\)/;
71
71
 
72
- return !!(odcRegex.exec(path) || odcODataRegex.exec(path));
72
+ return !!(odcRegex.exec(path) ?? odcODataRegex.exec(path));
73
73
  }
74
74
 
75
75
  /**
package/src/odspUtils.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  NetworkErrorBasic,
14
14
  } from "@fluidframework/driver-utils";
15
15
  import { assert, performance } from "@fluidframework/common-utils";
16
- import { ChildLogger, PerformanceEvent, wrapError } from "@fluidframework/telemetry-utils";
16
+ import { ChildLogger, PerformanceEvent, TelemetryDataTag, wrapError } from "@fluidframework/telemetry-utils";
17
17
  import {
18
18
  fetchIncorrectResponse,
19
19
  throwOdspNetworkError,
@@ -123,7 +123,8 @@ export async function fetchHelper(
123
123
  }, (error) => {
124
124
  const online = isOnline();
125
125
  const errorText = `${error}`;
126
-
126
+ const urlRegex = /((http|https):\/\/(\S*))/i;
127
+ const redactedErrorText = errorText.replace(urlRegex, "REDACTED_URL");
127
128
  // This error is thrown by fetch() when AbortSignal is provided and it gets cancelled
128
129
  if (error.name === "AbortError") {
129
130
  throw new RetryableError(
@@ -143,13 +144,23 @@ export async function fetchHelper(
143
144
  if (online === OnlineStatus.Offline) {
144
145
  throw new RetryableError(
145
146
  // pre-0.58 error message prefix: Offline
146
- `ODSP fetch failure (Offline): ${errorText}`, DriverErrorType.offlineError, { driverVersion });
147
+ `ODSP fetch failure (Offline): ${redactedErrorText}`,
148
+ DriverErrorType.offlineError,
149
+ {
150
+ driverVersion,
151
+ rawErrorMessage: { value: errorText, tag: TelemetryDataTag.UserData },
152
+ });
147
153
  } else {
148
154
  // It is perhaps still possible that this is due to being offline, the error does not reveal enough
149
155
  // information to conclude. Could also be DNS errors, malformed fetch request, CSP violation, etc.
150
156
  throw new RetryableError(
151
157
  // pre-0.58 error message prefix: Fetch error
152
- `ODSP fetch failure: ${errorText}`, DriverErrorType.fetchFailure, { driverVersion });
158
+ `ODSP fetch failure: ${redactedErrorText}`,
159
+ DriverErrorType.fetchFailure,
160
+ {
161
+ driverVersion,
162
+ rawErrorMessage: { value: errorText, tag: TelemetryDataTag.UserData },
163
+ });
153
164
  }
154
165
  });
155
166
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "1.2.1";
9
+ export const pkgVersion = "2.0.0-internal.1.0.0";
package/src/retryUtils.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { ITelemetryLogger } from "@fluidframework/common-definitions";
7
7
  import { delay, performance } from "@fluidframework/common-utils";
8
- import { canRetryOnError } from "@fluidframework/driver-utils";
8
+ import { canRetryOnError, getRetryDelayFromError } from "@fluidframework/driver-utils";
9
9
  import { OdspErrorType } from "@fluidframework/odsp-driver-definitions";
10
10
  import { Odsp409Error } from "./epochTracker";
11
11
 
@@ -43,8 +43,10 @@ export async function runWithRetry<T>(
43
43
 
44
44
  const coherencyError = error?.[Odsp409Error] === true;
45
45
  const serviceReadonlyError = error?.errorType === OdspErrorType.serviceReadOnly;
46
- // Retry for retriable 409 coherency errors or serviceReadOnly errors.
47
- if (!(coherencyError || serviceReadonlyError || canRetry)) {
46
+ // Retry for retriable 409 coherency errors or serviceReadOnly errors. These errors are always retriable
47
+ // unless someone specifically set canRetry = false on the error like in fetchSnapshot() flow. So in
48
+ // that case don't retry.
49
+ if (!((coherencyError || serviceReadonlyError) && canRetry)) {
48
50
  throw error;
49
51
  }
50
52
 
@@ -54,8 +56,8 @@ export async function runWithRetry<T>(
54
56
  if (attempts === 5) {
55
57
  logger.sendErrorEvent(
56
58
  {
57
- eventName: coherencyError ?
58
- "CoherencyErrorTooManyRetries" : "ServiceReadonlyErrorTooManyRetries",
59
+ eventName: coherencyError ? "CoherencyErrorTooManyRetries" :
60
+ "ServiceReadonlyErrorTooManyRetries",
59
61
  callName,
60
62
  attempts,
61
63
  duration: performance.now() - start, // record total wait time.
@@ -66,6 +68,7 @@ export async function runWithRetry<T>(
66
68
  throw error;
67
69
  }
68
70
 
71
+ retryAfter = getRetryDelayFromError(error) ?? retryAfter;
69
72
  await delay(Math.floor(retryAfter));
70
73
  retryAfter += retryAfter / 4 * (1 + Math.random());
71
74
  lastError = error;