@fluidframework/odsp-driver 0.55.2 → 0.56.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 (127) hide show
  1. package/dist/checkUrl.d.ts.map +1 -1
  2. package/dist/checkUrl.js +0 -1
  3. package/dist/checkUrl.js.map +1 -1
  4. package/dist/contractsPublic.d.ts +7 -1
  5. package/dist/contractsPublic.d.ts.map +1 -1
  6. package/dist/contractsPublic.js +7 -1
  7. package/dist/contractsPublic.js.map +1 -1
  8. package/dist/createFile.d.ts +3 -3
  9. package/dist/createFile.d.ts.map +1 -1
  10. package/dist/createFile.js +7 -7
  11. package/dist/createFile.js.map +1 -1
  12. package/dist/epochTracker.d.ts +3 -3
  13. package/dist/epochTracker.d.ts.map +1 -1
  14. package/dist/epochTracker.js +20 -8
  15. package/dist/epochTracker.js.map +1 -1
  16. package/dist/fetchSnapshot.d.ts +3 -2
  17. package/dist/fetchSnapshot.d.ts.map +1 -1
  18. package/dist/fetchSnapshot.js +28 -16
  19. package/dist/fetchSnapshot.js.map +1 -1
  20. package/dist/getFileLink.js +4 -4
  21. package/dist/getFileLink.js.map +1 -1
  22. package/dist/getUrlAndHeadersWithAuth.d.ts +1 -1
  23. package/dist/getUrlAndHeadersWithAuth.d.ts.map +1 -1
  24. package/dist/getUrlAndHeadersWithAuth.js +20 -33
  25. package/dist/getUrlAndHeadersWithAuth.js.map +1 -1
  26. package/dist/odspDeltaStorageService.d.ts.map +1 -1
  27. package/dist/odspDeltaStorageService.js +1 -4
  28. package/dist/odspDeltaStorageService.js.map +1 -1
  29. package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
  30. package/dist/odspDocumentServiceFactoryCore.js +2 -2
  31. package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
  32. package/dist/odspDocumentStorageManager.d.ts +1 -0
  33. package/dist/odspDocumentStorageManager.d.ts.map +1 -1
  34. package/dist/odspDocumentStorageManager.js +16 -9
  35. package/dist/odspDocumentStorageManager.js.map +1 -1
  36. package/dist/odspDriverUrlResolver.d.ts.map +1 -1
  37. package/dist/odspDriverUrlResolver.js +5 -2
  38. package/dist/odspDriverUrlResolver.js.map +1 -1
  39. package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  40. package/dist/odspDriverUrlResolverForShareLink.js +1 -2
  41. package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
  42. package/dist/odspSummaryUploadManager.d.ts +2 -1
  43. package/dist/odspSummaryUploadManager.d.ts.map +1 -1
  44. package/dist/odspSummaryUploadManager.js +3 -2
  45. package/dist/odspSummaryUploadManager.js.map +1 -1
  46. package/dist/packageVersion.d.ts +1 -1
  47. package/dist/packageVersion.js +1 -1
  48. package/dist/packageVersion.js.map +1 -1
  49. package/dist/prefetchLatestSnapshot.d.ts +2 -1
  50. package/dist/prefetchLatestSnapshot.d.ts.map +1 -1
  51. package/dist/prefetchLatestSnapshot.js +3 -2
  52. package/dist/prefetchLatestSnapshot.js.map +1 -1
  53. package/dist/retryErrorsStorageAdapter.d.ts +1 -1
  54. package/dist/retryErrorsStorageAdapter.d.ts.map +1 -1
  55. package/dist/retryErrorsStorageAdapter.js.map +1 -1
  56. package/lib/checkUrl.d.ts.map +1 -1
  57. package/lib/checkUrl.js +0 -1
  58. package/lib/checkUrl.js.map +1 -1
  59. package/lib/contractsPublic.d.ts +7 -1
  60. package/lib/contractsPublic.d.ts.map +1 -1
  61. package/lib/contractsPublic.js +6 -0
  62. package/lib/contractsPublic.js.map +1 -1
  63. package/lib/createFile.d.ts +3 -3
  64. package/lib/createFile.d.ts.map +1 -1
  65. package/lib/createFile.js +7 -7
  66. package/lib/createFile.js.map +1 -1
  67. package/lib/epochTracker.d.ts +3 -3
  68. package/lib/epochTracker.d.ts.map +1 -1
  69. package/lib/epochTracker.js +21 -9
  70. package/lib/epochTracker.js.map +1 -1
  71. package/lib/fetchSnapshot.d.ts +3 -2
  72. package/lib/fetchSnapshot.d.ts.map +1 -1
  73. package/lib/fetchSnapshot.js +28 -16
  74. package/lib/fetchSnapshot.js.map +1 -1
  75. package/lib/getFileLink.js +4 -4
  76. package/lib/getFileLink.js.map +1 -1
  77. package/lib/getUrlAndHeadersWithAuth.d.ts +1 -1
  78. package/lib/getUrlAndHeadersWithAuth.d.ts.map +1 -1
  79. package/lib/getUrlAndHeadersWithAuth.js +20 -33
  80. package/lib/getUrlAndHeadersWithAuth.js.map +1 -1
  81. package/lib/odspDeltaStorageService.d.ts.map +1 -1
  82. package/lib/odspDeltaStorageService.js +1 -4
  83. package/lib/odspDeltaStorageService.js.map +1 -1
  84. package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
  85. package/lib/odspDocumentServiceFactoryCore.js +2 -2
  86. package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
  87. package/lib/odspDocumentStorageManager.d.ts +1 -0
  88. package/lib/odspDocumentStorageManager.d.ts.map +1 -1
  89. package/lib/odspDocumentStorageManager.js +16 -9
  90. package/lib/odspDocumentStorageManager.js.map +1 -1
  91. package/lib/odspDriverUrlResolver.d.ts.map +1 -1
  92. package/lib/odspDriverUrlResolver.js +5 -2
  93. package/lib/odspDriverUrlResolver.js.map +1 -1
  94. package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  95. package/lib/odspDriverUrlResolverForShareLink.js +1 -2
  96. package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
  97. package/lib/odspSummaryUploadManager.d.ts +2 -1
  98. package/lib/odspSummaryUploadManager.d.ts.map +1 -1
  99. package/lib/odspSummaryUploadManager.js +3 -2
  100. package/lib/odspSummaryUploadManager.js.map +1 -1
  101. package/lib/packageVersion.d.ts +1 -1
  102. package/lib/packageVersion.js +1 -1
  103. package/lib/packageVersion.js.map +1 -1
  104. package/lib/prefetchLatestSnapshot.d.ts +2 -1
  105. package/lib/prefetchLatestSnapshot.d.ts.map +1 -1
  106. package/lib/prefetchLatestSnapshot.js +3 -2
  107. package/lib/prefetchLatestSnapshot.js.map +1 -1
  108. package/lib/retryErrorsStorageAdapter.d.ts +1 -1
  109. package/lib/retryErrorsStorageAdapter.d.ts.map +1 -1
  110. package/lib/retryErrorsStorageAdapter.js.map +1 -1
  111. package/package.json +9 -9
  112. package/src/checkUrl.ts +0 -1
  113. package/src/contractsPublic.ts +11 -1
  114. package/src/createFile.ts +16 -4
  115. package/src/epochTracker.ts +21 -6
  116. package/src/fetchSnapshot.ts +33 -16
  117. package/src/getFileLink.ts +7 -2
  118. package/src/getUrlAndHeadersWithAuth.ts +24 -38
  119. package/src/odspDeltaStorageService.ts +1 -3
  120. package/src/odspDocumentServiceFactoryCore.ts +1 -0
  121. package/src/odspDocumentStorageManager.ts +39 -8
  122. package/src/odspDriverUrlResolver.ts +3 -0
  123. package/src/odspDriverUrlResolverForShareLink.ts +1 -2
  124. package/src/odspSummaryUploadManager.ts +6 -1
  125. package/src/packageVersion.ts +1 -1
  126. package/src/prefetchLatestSnapshot.ts +3 -0
  127. package/src/retryErrorsStorageAdapter.ts +1 -1
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(getStorageToken, newFileInfo, logger, epochTracker);
71
+ itemId = await createNewEmptyFluidFile(
72
+ getStorageToken, newFileInfo, logger, epochTracker, forceAccessTokenViaAuthorizationHeader);
71
73
  } else {
72
74
  const content = await createNewFluidFileFromSummary(
73
- getStorageToken, newFileInfo, logger, createNewSummary, epochTracker);
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(initialUrl, storageToken);
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(initialUrl, storageToken);
195
+ const { url, headers } = getUrlAndHeadersWithAuth(
196
+ initialUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
185
197
  headers["Content-Type"] = "application/json";
186
198
 
187
199
  const fetchResponse = await runWithRetry(
@@ -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";
@@ -170,8 +171,9 @@ export class EpochTracker implements IPersistedFileCache {
170
171
  fetchOptions: RequestInit,
171
172
  fetchType: FetchType,
172
173
  addInBody: boolean = false,
174
+ fetchReason?: string,
173
175
  ): Promise<IOdspResponse<T>> {
174
- const clientCorrelationId = this.formatClientCorrelationId();
176
+ const clientCorrelationId = this.formatClientCorrelationId(fetchReason);
175
177
  // Add epoch in fetch request.
176
178
  this.addEpochInRequest(fetchOptions, addInBody, clientCorrelationId);
177
179
  let epochFromResponse: string | undefined;
@@ -211,8 +213,9 @@ export class EpochTracker implements IPersistedFileCache {
211
213
  fetchOptions: {[index: string]: any},
212
214
  fetchType: FetchType,
213
215
  addInBody: boolean = false,
216
+ fetchReason?: string,
214
217
  ) {
215
- const clientCorrelationId = this.formatClientCorrelationId();
218
+ const clientCorrelationId = this.formatClientCorrelationId(fetchReason);
216
219
  // Add epoch in fetch request.
217
220
  this.addEpochInRequest(fetchOptions, addInBody, clientCorrelationId);
218
221
  let epochFromResponse: string | undefined;
@@ -245,12 +248,16 @@ export class EpochTracker implements IPersistedFileCache {
245
248
  addInBody: boolean,
246
249
  clientCorrelationId: string,
247
250
  ) {
251
+ const isClpCompliantApp = getOdspResolvedUrl(this.fileEntry.resolvedUrl).isClpCompliantApp;
248
252
  if (addInBody) {
249
253
  const headers: {[key: string]: string} = {};
250
254
  headers["X-RequestStats"] = clientCorrelationId;
251
255
  if (this.fluidEpoch !== undefined) {
252
256
  headers["x-fluid-epoch"] = this.fluidEpoch;
253
257
  }
258
+ if (isClpCompliantApp) {
259
+ headers[ClpCompliantAppHeader.isClpCompliantApp] = isClpCompliantApp.toString();
260
+ }
254
261
  this.addParamInBody(fetchOptions, headers);
255
262
  } else {
256
263
  const addHeader = (key: string, val: string) => {
@@ -264,6 +271,9 @@ export class EpochTracker implements IPersistedFileCache {
264
271
  if (this.fluidEpoch !== undefined) {
265
272
  addHeader("x-fluid-epoch", this.fluidEpoch);
266
273
  }
274
+ if (isClpCompliantApp) {
275
+ addHeader(ClpCompliantAppHeader.isClpCompliantApp, isClpCompliantApp.toString());
276
+ }
267
277
  }
268
278
  }
269
279
 
@@ -285,8 +295,12 @@ export class EpochTracker implements IPersistedFileCache {
285
295
  fetchOptions.body = formParams.join("\r\n");
286
296
  }
287
297
 
288
- private formatClientCorrelationId() {
289
- return `driverId=${this.driverId}, RequestNumber=${this.networkCallNumber++}`;
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(", ");
290
304
  }
291
305
 
292
306
  protected validateEpochFromResponse(
@@ -395,13 +409,14 @@ export class EpochTrackerWithRedemption extends EpochTracker {
395
409
  fetchOptions: {[index: string]: any},
396
410
  fetchType: FetchType,
397
411
  addInBody: boolean = false,
412
+ fetchReason?: string,
398
413
  ): Promise<IOdspResponse<T>> {
399
414
  // Optimize the flow if we know that treesLatestDeferral was already completed by the timer we started
400
415
  // joinSession call. If we did - there is no reason to repeat the call as it will fail with same error.
401
416
  const completed = this.treesLatestDeferral.isCompleted;
402
417
 
403
418
  try {
404
- return await super.fetchAndParseAsJSON<T>(url, fetchOptions, fetchType, addInBody);
419
+ return await super.fetchAndParseAsJSON<T>(url, fetchOptions, fetchType, addInBody, fetchReason);
405
420
  } catch (error) {
406
421
  // Only handling here treesLatest. If createFile failed, we should never try to do joinSession.
407
422
  // Similar, if getVersions failed, we should not do any further storage calls.
@@ -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(`${snapshotUrl}${path}${queryString}`, token);
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,
@@ -103,16 +113,15 @@ export async function fetchSnapshotWithRedeem(
103
113
  eventName: "RedeemFallback",
104
114
  errorType: error.errorType,
105
115
  }, error);
106
- await redeemSharingLink(odspResolvedUrl, storageTokenFetcher, logger);
116
+ await redeemSharingLink(
117
+ odspResolvedUrl, storageTokenFetcher, logger, forceAccessTokenViaAuthorizationHeader);
107
118
  const odspResolvedUrlWithoutShareLink: IOdspResolvedUrl =
108
- { ...odspResolvedUrl, sharingLinkToRedeem: undefined };
109
-
110
- if(odspResolvedUrlWithoutShareLink.shareLinkInfo) {
111
- odspResolvedUrlWithoutShareLink.shareLinkInfo = {
112
- ...odspResolvedUrlWithoutShareLink.shareLinkInfo,
113
- sharingLinkToRedeem: undefined,
119
+ { ...odspResolvedUrl,
120
+ shareLinkInfo: {
121
+ ...odspResolvedUrl.shareLinkInfo,
122
+ sharingLinkToRedeem: undefined
123
+ }
114
124
  };
115
- }
116
125
 
117
126
  return fetchLatestSnapshotCore(
118
127
  odspResolvedUrlWithoutShareLink,
@@ -141,6 +150,7 @@ async function redeemSharingLink(
141
150
  odspResolvedUrl: IOdspResolvedUrl,
142
151
  storageTokenFetcher: InstrumentedStorageTokenFetcher,
143
152
  logger: ITelemetryLogger,
153
+ forceAccessTokenViaAuthorizationHeader: boolean,
144
154
  ) {
145
155
  return PerformanceEvent.timedExecAsync(
146
156
  logger,
@@ -148,12 +158,13 @@ async function redeemSharingLink(
148
158
  eventName: "RedeemShareLink",
149
159
  },
150
160
  async () => getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
151
- assert(!!odspResolvedUrl.sharingLinkToRedeem,
161
+ assert(!!odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem,
152
162
  0x1ed /* "Share link should be present" */);
153
163
  const storageToken = await storageTokenFetcher(tokenFetchOptions, "RedeemShareLink");
154
- const encodedShareUrl = getEncodedShareUrl(odspResolvedUrl.sharingLinkToRedeem);
164
+ const encodedShareUrl = getEncodedShareUrl(odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem);
155
165
  const redeemUrl = `${odspResolvedUrl.siteUrl}/_api/v2.0/shares/${encodedShareUrl}`;
156
- const { url, headers } = getUrlAndHeadersWithAuth(redeemUrl, storageToken);
166
+ const { url, headers } = getUrlAndHeadersWithAuth(
167
+ redeemUrl, storageToken, forceAccessTokenViaAuthorizationHeader);
157
168
  headers.prefer = "redeemSharingLink";
158
169
  return fetchAndParseAsJSONHelper(url, { headers });
159
170
  }),
@@ -189,7 +200,7 @@ async function fetchLatestSnapshotCore(
189
200
  const perfEvent = {
190
201
  eventName: "TreesLatest",
191
202
  attempts: tokenFetchOptions.refresh ? 2 : 1,
192
- shareLinkPresent: odspResolvedUrl.sharingLinkToRedeem !== undefined,
203
+ shareLinkPresent: odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem !== undefined,
193
204
  redeemFallbackEnabled: enableRedeemFallback,
194
205
  };
195
206
  if (snapshotOptions !== undefined) {
@@ -423,8 +434,8 @@ function getFormBodyAndHeaders(
423
434
  }
424
435
  });
425
436
  }
426
- if (odspResolvedUrl.sharingLinkToRedeem) {
427
- formParams.push(`sl: ${odspResolvedUrl.sharingLinkToRedeem}`);
437
+ if (odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem) {
438
+ formParams.push(`sl: ${odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem}`);
428
439
  }
429
440
  formParams.push(`_post: 1`);
430
441
  formParams.push(`\r\n--${formBoundary}--`);
@@ -467,6 +478,12 @@ export async function downloadSnapshot(
467
478
  controller?: AbortController,
468
479
  epochTracker?: EpochTracker,
469
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
+
470
487
  if (fetchBinarySnapshotFormat) {
471
488
  // Logging an event here as it is not supposed to be used in production yet and only in experimental mode.
472
489
  logger.sendTelemetryEvent({ eventName: "BinarySnapshotFetched" });
@@ -477,7 +494,7 @@ export async function downloadSnapshot(
477
494
  }
478
495
 
479
496
  function isRedeemSharingLinkError(odspResolvedUrl: IOdspResolvedUrl, error: any) {
480
- if (odspResolvedUrl.sharingLinkToRedeem !== undefined
497
+ if (odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem !== undefined
481
498
  && (typeof error === "object" && error !== null)
482
499
  && (error.errorType === DriverErrorType.authorizationError
483
500
  || error.errorType === DriverErrorType.fileNotFoundOrAccessDeniedError)) {
@@ -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
- }`, tokenFromResponse(token));
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
- * Gets the length of the query string portion of a url.
8
- * @param url The full url
9
- */
10
- function getQueryStringLength(url: string): number {
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
- const queryParamStart = url.indexOf("?");
27
-
28
- // Determine if we need to add ?, &, or nothing (if the url ends with ?)
29
- let tokenQueryParam = queryParamStart === -1 ? "?" : (queryParamStart !== url.length - 1 ? `&` : "");
30
-
31
- const tokenIsQueryParam = token.startsWith("?");
32
- if (tokenIsQueryParam) {
33
- // The token itself is a query param
34
- tokenQueryParam += token.substring(1);
35
- } else {
36
- tokenQueryParam += `access_token=${encodeURIComponent(token)}`;
37
- }
38
-
39
- // ODSP APIs have a limitation that the query string cannot exceed 2048 characters.
40
- // We try to stick the access token in the URL to make it a simple XHR request and avoid an options call.
41
- // If the query string exceeds 2048, we have to fall back to sending the access token as a header, which
42
- // has a negative performance implication as it adds a performance overhead.
43
- if (tokenIsQueryParam || getQueryStringLength(url + tokenQueryParam) <= 2048) {
44
- return {
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;
@@ -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({
@@ -40,7 +40,7 @@ import {
40
40
  getWithRetryForTokenRefresh,
41
41
  ISnapshotContents,
42
42
  } from "./odspUtils";
43
- import { EpochTracker } from "./epochTracker";
43
+ import { defaultCacheExpiryTimeoutMs, EpochTracker } from "./epochTracker";
44
44
  import { OdspSummaryUploadManager } from "./odspSummaryUploadManager";
45
45
  import { FlushResult } from "./odspDocumentDeltaConnection";
46
46
 
@@ -167,6 +167,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
167
167
  // Note that duplication of content should not have significant impact for bytes over wire as
168
168
  // compression of http payload mostly takes care of it, but it does impact storage size and in-memory sizes.
169
169
  minBlobSize: 2048,
170
+ maximumCacheDurationMs: defaultCacheExpiryTimeoutMs,
170
171
  };
171
172
 
172
173
  private readonly commitCache: Map<string, api.ISnapshotTree> = new Map();
@@ -224,7 +225,13 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
224
225
  this.snapshotUrl = this.odspResolvedUrl.endpoints.snapshotStorageUrl;
225
226
  this.attachmentPOSTUrl = this.odspResolvedUrl.endpoints.attachmentPOSTStorageUrl;
226
227
  this.attachmentGETUrl = this.odspResolvedUrl.endpoints.attachmentGETStorageUrl;
227
- this.odspSummaryUploadManager = new OdspSummaryUploadManager(this.snapshotUrl, getStorageToken, logger, epochTracker);
228
+ this.odspSummaryUploadManager = new OdspSummaryUploadManager(
229
+ this.snapshotUrl,
230
+ getStorageToken,
231
+ logger,
232
+ epochTracker,
233
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
234
+ );
228
235
  }
229
236
 
230
237
  public get repositoryUrl(): string {
@@ -236,7 +243,11 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
236
243
 
237
244
  const response = await getWithRetryForTokenRefresh(async (options) => {
238
245
  const storageToken = await this.getStorageToken(options, "CreateBlob");
239
- const { url, headers } = getUrlAndHeadersWithAuth(`${this.attachmentPOSTUrl}/content`, storageToken);
246
+ const { url, headers } = getUrlAndHeadersWithAuth(
247
+ `${this.attachmentPOSTUrl}/content`,
248
+ storageToken,
249
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
250
+ );
240
251
  headers["Content-Type"] = "application/octet-stream";
241
252
 
242
253
  return PerformanceEvent.timedExecAsync(
@@ -279,7 +290,11 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
279
290
  blob = await getWithRetryForTokenRefresh(async (options) => {
280
291
  const storageToken = await this.getStorageToken(options, "GetBlob");
281
292
  const unAuthedUrl = `${this.attachmentGETUrl}/${encodeURIComponent(blobId)}/content`;
282
- const { url, headers } = getUrlAndHeadersWithAuth(unAuthedUrl, storageToken);
293
+ const { url, headers } = getUrlAndHeadersWithAuth(
294
+ unAuthedUrl,
295
+ storageToken,
296
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
297
+ );
283
298
 
284
299
  return PerformanceEvent.timedExecAsync(
285
300
  this.logger,
@@ -495,7 +510,11 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
495
510
 
496
511
  return getWithRetryForTokenRefresh(async (options) => {
497
512
  const storageToken = await this.getStorageToken(options, "GetVersions");
498
- const { url, headers } = getUrlAndHeadersWithAuth(`${this.snapshotUrl}/versions?count=${count}`, storageToken);
513
+ const { url, headers } = getUrlAndHeadersWithAuth(
514
+ `${this.snapshotUrl}/versions?count=${count}`,
515
+ storageToken,
516
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
517
+ );
499
518
 
500
519
  // Fetch the latest snapshot versions for the document
501
520
  const response = await PerformanceEvent.timedExecAsync(
@@ -594,11 +613,13 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
594
613
  this.odspResolvedUrl,
595
614
  this.getStorageToken,
596
615
  snapshotOptions,
616
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
597
617
  this.logger,
598
618
  snapshotDownloader,
599
619
  putInCache,
600
620
  removeEntries,
601
- this.hostPolicy.enableRedeemFallback);
621
+ this.hostPolicy.enableRedeemFallback,
622
+ );
602
623
  return odspSnapshot;
603
624
  } catch (error) {
604
625
  const errorType = error.errorType;
@@ -617,11 +638,13 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
617
638
  this.odspResolvedUrl,
618
639
  this.getStorageToken,
619
640
  snapshotOptionsWithoutBlobs,
641
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
620
642
  this.logger,
621
643
  snapshotDownloader,
622
644
  putInCache,
623
645
  removeEntries,
624
- this.hostPolicy.enableRedeemFallback);
646
+ this.hostPolicy.enableRedeemFallback,
647
+ );
625
648
  return odspSnapshot;
626
649
  }
627
650
  throw error;
@@ -729,7 +752,15 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
729
752
  "snapshotTree",
730
753
  );
731
754
  };
732
- const snapshot = await fetchSnapshot(this.snapshotUrl!, storageToken, id, this.fetchFullSnapshot, this.logger, snapshotDownloader);
755
+ const snapshot = await fetchSnapshot(
756
+ this.snapshotUrl!,
757
+ storageToken,
758
+ id,
759
+ this.fetchFullSnapshot,
760
+ !!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
761
+ this.logger,
762
+ snapshotDownloader,
763
+ );
733
764
  let treeId = "";
734
765
  if (snapshot.snapshotTree) {
735
766
  assert(snapshot.snapshotTree.id !== undefined, 0x222 /* "Root tree should contain the id!!" */);
@@ -11,6 +11,7 @@ import { createOdspUrl } from "./createOdspUrl";
11
11
  import { getApiRoot } from "./odspUrlHelper";
12
12
  import { getOdspResolvedUrl } from "./odspUtils";
13
13
  import { getHashedDocumentId } from "./odspPublicUtils";
14
+ import { ClpCompliantAppHeader } from "./contractsPublic";
14
15
 
15
16
  function getUrlBase(siteUrl: string, driveId: string, itemId: string, fileVersion?: string) {
16
17
  const siteOrigin = new URL(siteUrl).origin;
@@ -101,6 +102,7 @@ export class OdspDriverUrlResolver implements IUrlResolver {
101
102
  },
102
103
  fileVersion: undefined,
103
104
  shareLinkInfo,
105
+ isClpCompliantApp: request.headers?.[ClpCompliantAppHeader.isClpCompliantApp],
104
106
  };
105
107
  }
106
108
  const { siteUrl, driveId, itemId, path, containerPackageName, fileVersion } = decodeOdspUrl(request.url);
@@ -142,6 +144,7 @@ export class OdspDriverUrlResolver implements IUrlResolver {
142
144
  containerPackageName,
143
145
  },
144
146
  fileVersion,
147
+ isClpCompliantApp: request.headers?.[ClpCompliantAppHeader.isClpCompliantApp],
145
148
  };
146
149
  }
147
150
 
@@ -133,9 +133,8 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
133
133
  // We need to remove the nav param if set by host when setting the sharelink as otherwise the shareLinkId
134
134
  // when redeeming the share link during the redeem fallback for trees latest call becomes greater than
135
135
  // the eligible length.
136
- odspResolvedUrl.sharingLinkToRedeem = this.removeNavParam(request.url);
137
136
  odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo || {},
138
- {sharingLinkToRedeem: odspResolvedUrl.sharingLinkToRedeem});
137
+ {sharingLinkToRedeem: this.removeNavParam(request.url)});
139
138
  }
140
139
  if (odspResolvedUrl.itemId) {
141
140
  // Kick start the sharing link request if we don't have it already as a performance optimization.
@@ -38,6 +38,7 @@ export class OdspSummaryUploadManager {
38
38
  private readonly getStorageToken: InstrumentedStorageTokenFetcher,
39
39
  logger: ITelemetryLogger,
40
40
  private readonly epochTracker: EpochTracker,
41
+ private readonly forceAccessTokenViaAuthorizationHeader: boolean,
41
42
  ) {
42
43
  this.mc = loggerToMonitoringContext(logger);
43
44
  }
@@ -84,7 +85,11 @@ export class OdspSummaryUploadManager {
84
85
  return getWithRetryForTokenRefresh(async (options) => {
85
86
  const storageToken = await this.getStorageToken(options, "WriteSummaryTree");
86
87
 
87
- const { url, headers } = getUrlAndHeadersWithAuth(`${this.snapshotUrl}/snapshot`, storageToken);
88
+ const { url, headers } = getUrlAndHeadersWithAuth(
89
+ `${this.snapshotUrl}/snapshot`,
90
+ storageToken,
91
+ this.forceAccessTokenViaAuthorizationHeader,
92
+ );
88
93
  headers["Content-Type"] = "application/json";
89
94
  if (parentHandle) {
90
95
  headers["If-Match"] = `fluid:containerid=${parentHandle}`;
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "0.55.2";
9
+ export const pkgVersion = "0.56.2";
@@ -31,6 +31,7 @@ import { IVersionedValueWithEpoch } from "./contracts";
31
31
  * @param getStorageToken - function that can provide the storage token for a given site. This is
32
32
  * is also referred to as the "VROOM" token in SPO.
33
33
  * @param persistedCache - Cache to store the fetched snapshot.
34
+ * @param forceAccessTokenViaAuthorizationHeader - whether to force passing given token via authorization header.
34
35
  * @param logger - Logger to have telemetry events.
35
36
  * @param hostSnapshotFetchOptions - Options to fetch the snapshot if any. Otherwise default will be used.
36
37
  * @param enableRedeemFallback - True to have the sharing link redeem fallback in case the Trees Latest/Redeem
@@ -42,6 +43,7 @@ export async function prefetchLatestSnapshot(
42
43
  resolvedUrl: IResolvedUrl,
43
44
  getStorageToken: TokenFetcher<OdspResourceTokenFetchOptions>,
44
45
  persistedCache: IPersistedCache,
46
+ forceAccessTokenViaAuthorizationHeader: boolean,
45
47
  logger: ITelemetryBaseLogger,
46
48
  hostSnapshotFetchOptions: ISnapshotOptions | undefined,
47
49
  enableRedeemFallback?: boolean,
@@ -84,6 +86,7 @@ export async function prefetchLatestSnapshot(
84
86
  odspResolvedUrl,
85
87
  storageTokenFetcher,
86
88
  hostSnapshotFetchOptions,
89
+ forceAccessTokenViaAuthorizationHeader,
87
90
  odspLogger,
88
91
  snapshotDownloader,
89
92
  putInCache,
@@ -54,7 +54,7 @@ export class RetryErrorsStorageAdapter implements IDocumentStorageService, IDisp
54
54
  );
55
55
  }
56
56
 
57
- public async getVersions(versionId: string, count: number): Promise<IVersion[]> {
57
+ public async getVersions(versionId: string | null, count: number): Promise<IVersion[]> {
58
58
  return this.runWithRetry(
59
59
  async () => this.internalStorageService.getVersions(versionId, count),
60
60
  "storage_getVersions",