@fluidframework/odsp-driver 2.10.0 → 2.12.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 (106) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/api-report/odsp-driver.legacy.alpha.api.md +5 -2
  3. package/dist/contracts.d.ts +6 -0
  4. package/dist/contracts.d.ts.map +1 -1
  5. package/dist/contracts.js.map +1 -1
  6. package/dist/createFile/createFile.d.ts +8 -4
  7. package/dist/createFile/createFile.d.ts.map +1 -1
  8. package/dist/createFile/createFile.js +51 -6
  9. package/dist/createFile/createFile.js.map +1 -1
  10. package/dist/createFile/createNewModule.d.ts +1 -1
  11. package/dist/createFile/createNewModule.d.ts.map +1 -1
  12. package/dist/createFile/createNewModule.js +2 -1
  13. package/dist/createFile/createNewModule.js.map +1 -1
  14. package/dist/createFile/createNewUtils.d.ts.map +1 -1
  15. package/dist/createFile/createNewUtils.js +3 -1
  16. package/dist/createFile/createNewUtils.js.map +1 -1
  17. package/dist/createOdspCreateContainerRequest.d.ts +4 -1
  18. package/dist/createOdspCreateContainerRequest.d.ts.map +1 -1
  19. package/dist/createOdspCreateContainerRequest.js +4 -2
  20. package/dist/createOdspCreateContainerRequest.js.map +1 -1
  21. package/dist/epochTracker.d.ts +1 -1
  22. package/dist/epochTracker.d.ts.map +1 -1
  23. package/dist/epochTracker.js.map +1 -1
  24. package/dist/fetchSnapshot.d.ts.map +1 -1
  25. package/dist/fetchSnapshot.js +5 -0
  26. package/dist/fetchSnapshot.js.map +1 -1
  27. package/dist/legacy.d.ts +1 -0
  28. package/dist/odspDocumentDeltaConnection.d.ts.map +1 -1
  29. package/dist/odspDocumentDeltaConnection.js +4 -0
  30. package/dist/odspDocumentDeltaConnection.js.map +1 -1
  31. package/dist/odspDocumentStorageManager.d.ts.map +1 -1
  32. package/dist/odspDocumentStorageManager.js +13 -0
  33. package/dist/odspDocumentStorageManager.js.map +1 -1
  34. package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  35. package/dist/odspDriverUrlResolverForShareLink.js +7 -35
  36. package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
  37. package/dist/odspUrlHelper.d.ts +6 -0
  38. package/dist/odspUrlHelper.d.ts.map +1 -1
  39. package/dist/odspUrlHelper.js +18 -1
  40. package/dist/odspUrlHelper.js.map +1 -1
  41. package/dist/odspUtils.d.ts +22 -2
  42. package/dist/odspUtils.d.ts.map +1 -1
  43. package/dist/odspUtils.js +61 -2
  44. package/dist/odspUtils.js.map +1 -1
  45. package/dist/packageVersion.d.ts +1 -1
  46. package/dist/packageVersion.js +1 -1
  47. package/dist/packageVersion.js.map +1 -1
  48. package/lib/contracts.d.ts +6 -0
  49. package/lib/contracts.d.ts.map +1 -1
  50. package/lib/contracts.js.map +1 -1
  51. package/lib/createFile/createFile.d.ts +8 -4
  52. package/lib/createFile/createFile.d.ts.map +1 -1
  53. package/lib/createFile/createFile.js +51 -7
  54. package/lib/createFile/createFile.js.map +1 -1
  55. package/lib/createFile/createNewModule.d.ts +1 -1
  56. package/lib/createFile/createNewModule.d.ts.map +1 -1
  57. package/lib/createFile/createNewModule.js +1 -1
  58. package/lib/createFile/createNewModule.js.map +1 -1
  59. package/lib/createFile/createNewUtils.d.ts.map +1 -1
  60. package/lib/createFile/createNewUtils.js +3 -1
  61. package/lib/createFile/createNewUtils.js.map +1 -1
  62. package/lib/createOdspCreateContainerRequest.d.ts +4 -1
  63. package/lib/createOdspCreateContainerRequest.d.ts.map +1 -1
  64. package/lib/createOdspCreateContainerRequest.js +6 -4
  65. package/lib/createOdspCreateContainerRequest.js.map +1 -1
  66. package/lib/epochTracker.d.ts +1 -1
  67. package/lib/epochTracker.d.ts.map +1 -1
  68. package/lib/epochTracker.js.map +1 -1
  69. package/lib/fetchSnapshot.d.ts.map +1 -1
  70. package/lib/fetchSnapshot.js +5 -0
  71. package/lib/fetchSnapshot.js.map +1 -1
  72. package/lib/legacy.d.ts +1 -0
  73. package/lib/odspDocumentDeltaConnection.d.ts.map +1 -1
  74. package/lib/odspDocumentDeltaConnection.js +4 -0
  75. package/lib/odspDocumentDeltaConnection.js.map +1 -1
  76. package/lib/odspDocumentStorageManager.d.ts.map +1 -1
  77. package/lib/odspDocumentStorageManager.js +13 -0
  78. package/lib/odspDocumentStorageManager.js.map +1 -1
  79. package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
  80. package/lib/odspDriverUrlResolverForShareLink.js +8 -36
  81. package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
  82. package/lib/odspUrlHelper.d.ts +6 -0
  83. package/lib/odspUrlHelper.d.ts.map +1 -1
  84. package/lib/odspUrlHelper.js +16 -0
  85. package/lib/odspUrlHelper.js.map +1 -1
  86. package/lib/odspUtils.d.ts +22 -2
  87. package/lib/odspUtils.d.ts.map +1 -1
  88. package/lib/odspUtils.js +58 -1
  89. package/lib/odspUtils.js.map +1 -1
  90. package/lib/packageVersion.d.ts +1 -1
  91. package/lib/packageVersion.js +1 -1
  92. package/lib/packageVersion.js.map +1 -1
  93. package/package.json +14 -14
  94. package/src/contracts.ts +7 -0
  95. package/src/createFile/createFile.ts +100 -7
  96. package/src/createFile/createNewModule.ts +1 -1
  97. package/src/createFile/createNewUtils.ts +3 -1
  98. package/src/createOdspCreateContainerRequest.ts +9 -3
  99. package/src/epochTracker.ts +2 -1
  100. package/src/fetchSnapshot.ts +5 -0
  101. package/src/odspDocumentDeltaConnection.ts +4 -0
  102. package/src/odspDocumentStorageManager.ts +28 -0
  103. package/src/odspDriverUrlResolverForShareLink.ts +21 -38
  104. package/src/odspUrlHelper.ts +18 -0
  105. package/src/odspUtils.ts +75 -2
  106. package/src/packageVersion.ts +1 -1
package/src/contracts.ts CHANGED
@@ -189,6 +189,13 @@ export interface ICreateFileResponse {
189
189
  "sharing"?: any;
190
190
  "sharingLink"?: string;
191
191
  "sharingLinkErrorReason"?: string;
192
+ "name": string;
193
+ }
194
+
195
+ export interface IRenameFileResponse {
196
+ "@odata.context": string;
197
+ "id": string;
198
+ "name": string;
192
199
  }
193
200
 
194
201
  export interface IVersionedValueWithEpoch {
@@ -13,6 +13,7 @@ import {
13
13
  InstrumentedStorageTokenFetcher,
14
14
  OdspErrorTypes,
15
15
  ShareLinkInfoType,
16
+ type IOdspUrlParts,
16
17
  } from "@fluidframework/odsp-driver-definitions/internal";
17
18
  import {
18
19
  ITelemetryLoggerExt,
@@ -20,15 +21,16 @@ import {
20
21
  PerformanceEvent,
21
22
  } from "@fluidframework/telemetry-utils/internal";
22
23
 
23
- import { ICreateFileResponse } from "./../contracts.js";
24
+ import { ICreateFileResponse, type IRenameFileResponse } from "./../contracts.js";
24
25
  import { ClpCompliantAppHeader } from "./../contractsPublic.js";
25
26
  import { createOdspUrl } from "./../createOdspUrl.js";
26
27
  import { EpochTracker } from "./../epochTracker.js";
27
28
  import { getHeadersWithAuth } from "./../getUrlAndHeadersWithAuth.js";
28
29
  import { OdspDriverUrlResolver } from "./../odspDriverUrlResolver.js";
29
- import { getApiRoot } from "./../odspUrlHelper.js";
30
+ import { checkForKnownServerFarmType, getApiRoot } from "./../odspUrlHelper.js";
30
31
  import {
31
32
  INewFileInfo,
33
+ appendNavParam,
32
34
  buildOdspShareLinkReqParams,
33
35
  createCacheSnapshotKey,
34
36
  getWithRetryForTokenRefresh,
@@ -62,6 +64,7 @@ export async function createNewFluidFile(
62
64
  forceAccessTokenViaAuthorizationHeader: boolean,
63
65
  isClpCompliantApp?: boolean,
64
66
  enableSingleRequestForShareLinkWithCreate?: boolean,
67
+ resolvedUrl?: IOdspResolvedUrl,
65
68
  ): Promise<IOdspResolvedUrl> {
66
69
  // Check for valid filename before the request to create file is actually made.
67
70
  if (isInvalidFileName(newFileInfo.filename)) {
@@ -74,10 +77,18 @@ export async function createNewFluidFile(
74
77
  }
75
78
 
76
79
  let itemId: string;
80
+ let pendingRename: string | undefined;
77
81
  let summaryHandle: string = "";
78
82
  let shareLinkInfo: ShareLinkInfoType | undefined;
79
83
  if (createNewSummary === undefined) {
80
- itemId = await createNewEmptyFluidFile(getAuthHeader, newFileInfo, logger, epochTracker);
84
+ const content = await createNewEmptyFluidFile(
85
+ getAuthHeader,
86
+ newFileInfo,
87
+ logger,
88
+ epochTracker,
89
+ );
90
+ itemId = content.itemId;
91
+ pendingRename = newFileInfo.filename;
81
92
  } else {
82
93
  const content = await createNewFluidFileFromSummary(
83
94
  getAuthHeader,
@@ -102,7 +113,23 @@ export async function createNewFluidFile(
102
113
  fileEntry.docId = odspResolvedUrl.hashedDocumentId;
103
114
  fileEntry.resolvedUrl = odspResolvedUrl;
104
115
 
116
+ odspResolvedUrl.context = resolvedUrl?.context;
117
+ odspResolvedUrl.appName = resolvedUrl?.appName;
118
+ odspResolvedUrl.codeHint = resolvedUrl?.codeHint;
119
+
120
+ if (shareLinkInfo?.createLink?.link) {
121
+ let newWebUrl = shareLinkInfo.createLink.link.webUrl;
122
+ newWebUrl = appendNavParam(
123
+ newWebUrl,
124
+ odspResolvedUrl,
125
+ odspResolvedUrl.dataStorePath ?? "/",
126
+ odspResolvedUrl.codeHint?.containerPackageName,
127
+ );
128
+ shareLinkInfo.createLink.link.webUrl = newWebUrl;
129
+ }
130
+
105
131
  odspResolvedUrl.shareLinkInfo = shareLinkInfo;
132
+ odspResolvedUrl.pendingRename = pendingRename;
106
133
 
107
134
  if (createNewSummary !== undefined && createNewCaching) {
108
135
  assert(summaryHandle !== undefined, 0x203 /* "Summary handle is undefined" */);
@@ -178,9 +205,8 @@ export async function createNewEmptyFluidFile(
178
205
  newFileInfo: INewFileInfo,
179
206
  logger: ITelemetryLoggerExt,
180
207
  epochTracker: EpochTracker,
181
- ): Promise<string> {
208
+ ): Promise<{ itemId: string; fileName: string }> {
182
209
  const filePath = encodeFilePath(newFileInfo.filePath);
183
- // add .tmp extension to empty file (host is expected to rename)
184
210
  const encodedFilename = encodeURIComponent(`${newFileInfo.filename}.tmp`);
185
211
  const initialUrl = `${getApiRoot(new URL(newFileInfo.siteUrl))}/drives/${
186
212
  newFileInfo.driveId
@@ -193,10 +219,16 @@ export async function createNewEmptyFluidFile(
193
219
  { ...options, request: { url, method } },
194
220
  "CreateNewFile",
195
221
  );
222
+ const internalFarmType = checkForKnownServerFarmType(newFileInfo.siteUrl);
196
223
 
197
224
  return PerformanceEvent.timedExecAsync(
198
225
  logger,
199
- { eventName: "createNewEmptyFile" },
226
+ {
227
+ eventName: "createNewEmptyFile",
228
+ details: {
229
+ internalFarmType,
230
+ },
231
+ },
200
232
  async (event) => {
201
233
  const headers = getHeadersWithAuth(authHeader);
202
234
  headers["Content-Type"] = "application/json";
@@ -228,7 +260,68 @@ export async function createNewEmptyFluidFile(
228
260
  event.end({
229
261
  ...fetchResponse.propsToLog,
230
262
  });
231
- return content.id;
263
+ return { itemId: content.id, fileName: content.name };
264
+ },
265
+ { end: true, cancel: "error" },
266
+ );
267
+ });
268
+ }
269
+
270
+ export async function renameEmptyFluidFile(
271
+ getAuthHeader: InstrumentedStorageTokenFetcher,
272
+ odspParts: IOdspUrlParts,
273
+ requestedFileName: string,
274
+ logger: ITelemetryLoggerExt,
275
+ epochTracker: EpochTracker,
276
+ ): Promise<IRenameFileResponse> {
277
+ const initialUrl = `${getApiRoot(new URL(odspParts.siteUrl))}/drives/${
278
+ odspParts.driveId
279
+ }/items/${odspParts.itemId}?@name.conflictBehavior=rename`;
280
+
281
+ return getWithRetryForTokenRefresh(async (options) => {
282
+ const url = initialUrl;
283
+ const method = "PATCH";
284
+ const authHeader = await getAuthHeader(
285
+ { ...options, request: { url, method } },
286
+ "renameFile",
287
+ );
288
+
289
+ return PerformanceEvent.timedExecAsync(
290
+ logger,
291
+ { eventName: "renameFile" },
292
+ async (event) => {
293
+ const headers = getHeadersWithAuth(authHeader);
294
+ headers["Content-Type"] = "application/json";
295
+
296
+ const fetchResponse = await runWithRetry(
297
+ async () =>
298
+ epochTracker.fetchAndParseAsJSON<IRenameFileResponse>(
299
+ url,
300
+ {
301
+ body: JSON.stringify({
302
+ name: requestedFileName,
303
+ }),
304
+ headers,
305
+ method: "PATCH",
306
+ },
307
+ "renameFile",
308
+ ),
309
+ "renameFile",
310
+ logger,
311
+ );
312
+
313
+ const content = fetchResponse.content;
314
+ if (!content?.id) {
315
+ throw new NonRetryableError(
316
+ "ODSP RenameFile call returned no item ID (for empty file)",
317
+ OdspErrorTypes.incorrectServerResponse,
318
+ { driverVersion },
319
+ );
320
+ }
321
+ event.end({
322
+ ...fetchResponse.propsToLog,
323
+ });
324
+ return content;
232
325
  },
233
326
  { end: true, cancel: "error" },
234
327
  );
@@ -3,6 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- export { createNewFluidFile } from "./createFile.js";
6
+ export { createNewFluidFile, renameEmptyFluidFile } from "./createFile.js";
7
7
  export { createNewContainerOnExistingFile } from "./createNewContainerOnExistingFile.js";
8
8
  export { convertCreateNewSummaryTreeToTreeAndBlobs } from "./createNewUtils.js";
@@ -32,6 +32,7 @@ import {
32
32
  } from "./../contracts.js";
33
33
  import { EpochTracker, FetchType } from "./../epochTracker.js";
34
34
  import { getHeadersWithAuth } from "./../getUrlAndHeadersWithAuth.js";
35
+ import { checkForKnownServerFarmType } from "./../odspUrlHelper.js";
35
36
  import { getWithRetryForTokenRefresh, maxUmpPostBodySize } from "./../odspUtils.js";
36
37
  import { runWithRetry } from "./../retryUtils.js";
37
38
 
@@ -222,11 +223,12 @@ export async function createNewFluidContainerCore<T>(args: {
222
223
  fetchType,
223
224
  validateResponseCallback,
224
225
  } = args;
226
+ const internalFarmType = checkForKnownServerFarmType(initialUrl);
225
227
 
226
228
  return getWithRetryForTokenRefresh(async (options) => {
227
229
  return PerformanceEvent.timedExecAsync(
228
230
  logger,
229
- { eventName: telemetryName },
231
+ { eventName: telemetryName, details: { internalFarmType } },
230
232
  async (event) => {
231
233
  const snapshotBody = JSON.stringify(containerSnapshot);
232
234
  let url: string;
@@ -4,10 +4,13 @@
4
4
  */
5
5
 
6
6
  import { IRequest } from "@fluidframework/core-interfaces";
7
- import { DriverHeader } from "@fluidframework/driver-definitions/internal";
7
+ import {
8
+ DriverHeader,
9
+ type IContainerPackageInfo,
10
+ } from "@fluidframework/driver-definitions/internal";
8
11
  import { ISharingLinkKind } from "@fluidframework/odsp-driver-definitions/internal";
9
12
 
10
- import { buildOdspShareLinkReqParams } from "./odspUtils.js";
13
+ import { buildOdspShareLinkReqParams, getContainerPackageName } from "./odspUtils.js";
11
14
 
12
15
  /**
13
16
  * Create the request object with url and headers for creating a new file on OneDrive Sharepoint
@@ -17,6 +20,8 @@ import { buildOdspShareLinkReqParams } from "./odspUtils.js";
17
20
  * @param fileName - name of the new file to be created
18
21
  * @param createShareLinkType - type of sharing link you would like to create for this file. ShareLinkTypes
19
22
  * will be deprecated soon, so for any new implementation please provide createShareLinkType of type ShareLink
23
+ * @param containerPackageInfo - container package information which will be used to extract the container package name.
24
+ * If not given that means that the container package does not have a name.
20
25
  * @legacy
21
26
  * @alpha
22
27
  */
@@ -26,12 +31,13 @@ export function createOdspCreateContainerRequest(
26
31
  filePath: string,
27
32
  fileName: string,
28
33
  createShareLinkType?: ISharingLinkKind,
34
+ containerPackageInfo?: IContainerPackageInfo | undefined,
29
35
  ): IRequest {
30
36
  const shareLinkRequestParams = buildOdspShareLinkReqParams(createShareLinkType);
31
37
  const createNewRequest: IRequest = {
32
38
  url: `${siteUrl}?driveId=${encodeURIComponent(driveId)}&path=${encodeURIComponent(
33
39
  filePath,
34
- )}${shareLinkRequestParams ? `&${shareLinkRequestParams}` : ""}`,
40
+ )}${containerPackageInfo ? `&containerPackageName=${getContainerPackageName(containerPackageInfo)}` : ""}${shareLinkRequestParams ? `&${shareLinkRequestParams}` : ""}`,
35
41
  headers: {
36
42
  [DriverHeader.createNew]: {
37
43
  fileName,
@@ -61,7 +61,8 @@ export type FetchType =
61
61
  | "treesLatest"
62
62
  | "uploadSummary"
63
63
  | "push"
64
- | "versions";
64
+ | "versions"
65
+ | "renameFile";
65
66
 
66
67
  /**
67
68
  * @legacy
@@ -47,6 +47,7 @@ import { EpochTracker } from "./epochTracker.js";
47
47
  import { getQueryString } from "./getQueryString.js";
48
48
  import { getHeadersWithAuth } from "./getUrlAndHeadersWithAuth.js";
49
49
  import { convertOdspSnapshotToSnapshotTreeAndBlobs } from "./odspSnapshotParser.js";
50
+ import { checkForKnownServerFarmType } from "./odspUrlHelper.js";
50
51
  import {
51
52
  IOdspResponse,
52
53
  fetchAndParseAsJSONHelper,
@@ -297,6 +298,7 @@ async function fetchLatestSnapshotCore(
297
298
  return getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
298
299
  const fetchSnapshotForLoadingGroup = isSnapshotFetchForLoadingGroup(loadingGroupIds);
299
300
  const eventName = fetchSnapshotForLoadingGroup ? "TreesLatestForGroup" : "TreesLatest";
301
+ const internalFarmType = checkForKnownServerFarmType(odspResolvedUrl.siteUrl);
300
302
 
301
303
  const perfEvent = {
302
304
  eventName,
@@ -304,6 +306,9 @@ async function fetchLatestSnapshotCore(
304
306
  shareLinkPresent: odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem !== undefined,
305
307
  isSummarizer: odspResolvedUrl.summarizer,
306
308
  redeemFallbackEnabled: enableRedeemFallback,
309
+ details: {
310
+ internalFarmType,
311
+ },
307
312
  };
308
313
  if (snapshotOptions !== undefined) {
309
314
  for (const [key, value] of Object.entries(snapshotOptions)) {
@@ -699,6 +699,10 @@ export class OdspDocumentDeltaConnection extends DocumentDeltaConnection {
699
699
  );
700
700
 
701
701
  if (filteredMsgs.length > 0) {
702
+ // This ternary is needed for signal-based layer compat tests to pass,
703
+ // specifically the layer version combination where you have an old loader and the most recent driver layer.
704
+ // Old loader doesn't send or receive batched signals (ISignalMessage[]),
705
+ // so only individual ISignalMessage's should be passed when there's one element for backcompat.
702
706
  listener(filteredMsgs.length === 1 ? filteredMsgs[0] : filteredMsgs, documentId);
703
707
  }
704
708
  },
@@ -43,6 +43,7 @@ import {
43
43
  ISnapshotCachedEntry2,
44
44
  IVersionedValueWithEpoch,
45
45
  } from "./contracts.js";
46
+ import { useCreateNewModule } from "./createFile/index.js";
46
47
  import { EpochTracker } from "./epochTracker.js";
47
48
  import {
48
49
  ISnapshotRequestAndResponseOptions,
@@ -769,6 +770,33 @@ export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
769
770
  0x56e /* summary upload manager should have been initialized */,
770
771
  );
771
772
  const id = await this.odspSummaryUploadManager.writeSummaryTree(summary, context);
773
+ const { pendingRename } = this.odspResolvedUrl;
774
+ if (
775
+ pendingRename !== undefined &&
776
+ this.config.getBoolean("Fluid.Driver.Odsp.disablePendingRename") !== true
777
+ ) {
778
+ // This is a temporary file, so we need to rename it to remove the .tmp extension
779
+ // This should only happen for the initial summary upload for a new file
780
+ assert(
781
+ context.ackHandle === undefined &&
782
+ context.proposalHandle === undefined &&
783
+ context.referenceSequenceNumber === 0,
784
+ 0xa88 /* temporaryFileName should only be set for new file creation in the empty file create flow */,
785
+ );
786
+
787
+ const renameResponse = await useCreateNewModule(this.logger, async (m) =>
788
+ m.renameEmptyFluidFile(
789
+ this.getAuthHeader,
790
+ this.odspResolvedUrl,
791
+ pendingRename,
792
+ this.logger,
793
+ this.epochTracker,
794
+ ),
795
+ );
796
+ this.odspResolvedUrl.pendingRename = undefined;
797
+ this.odspResolvedUrl.fileName = renameResponse.name;
798
+ }
799
+
772
800
  return id;
773
801
  }
774
802
 
@@ -27,7 +27,12 @@ import {
27
27
  locatorQueryParamName,
28
28
  storeLocatorInOdspUrl,
29
29
  } from "./odspFluidFileLink.js";
30
- import { createOdspLogger, getOdspResolvedUrl } from "./odspUtils.js";
30
+ import {
31
+ appendNavParam,
32
+ createOdspLogger,
33
+ getOdspResolvedUrl,
34
+ getContainerPackageName,
35
+ } from "./odspUtils.js";
31
36
 
32
37
  /**
33
38
  * Properties passed to the code responsible for fetching share link for a file.
@@ -45,10 +50,6 @@ export interface ShareLinkFetcherProps {
45
50
  identityType: IdentityType;
46
51
  }
47
52
 
48
- // back-compat: GitHub #9653
49
- const isFluidPackage = (pkg: Record<string, unknown>): boolean =>
50
- typeof pkg === "object" && typeof pkg?.name === "string" && typeof pkg?.fluid === "object";
51
-
52
53
  /**
53
54
  * Resolver to resolve urls like the ones created by createOdspUrl which is driver inner
54
55
  * url format and the ones which have things like driveId, siteId, itemId etc encoded in nav param.
@@ -147,6 +148,13 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
147
148
 
148
149
  const odspResolvedUrl = await new OdspDriverUrlResolver().resolve(requestToBeResolved);
149
150
 
151
+ odspResolvedUrl.context = await this.getContext?.(
152
+ odspResolvedUrl,
153
+ odspResolvedUrl.dataStorePath ?? "",
154
+ );
155
+
156
+ odspResolvedUrl.appName = this.appName;
157
+
150
158
  if (isSharingLinkToRedeem) {
151
159
  // We need to remove the nav param if set by host when setting the sharelink as otherwise the shareLinkId
152
160
  // when redeeming the share link during the redeem fallback for trees latest call becomes greater than
@@ -240,45 +248,20 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
240
248
  dataStorePath: string,
241
249
  packageInfoSource?: IContainerPackageInfo,
242
250
  ): Promise<string> {
243
- const url = new URL(baseUrl);
244
251
  const odspResolvedUrl = getOdspResolvedUrl(resolvedUrl);
245
252
 
246
253
  // If the user has passed an empty dataStorePath, then extract it from the resolved url.
247
254
  const actualDataStorePath = dataStorePath || (odspResolvedUrl.dataStorePath ?? "");
248
255
 
249
- let containerPackageName: string | undefined;
250
- if (packageInfoSource && "name" in packageInfoSource) {
251
- containerPackageName = packageInfoSource.name;
252
- // packageInfoSource is cast to any as it is typed to IContainerPackageInfo instead of IFluidCodeDetails
253
- // TODO: use a stronger type
254
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
255
- } else if (isFluidPackage((packageInfoSource as any)?.package)) {
256
- // TODO: use a stronger type
257
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
258
- containerPackageName = (packageInfoSource as any)?.package.name;
259
- } else {
260
- // TODO: use a stronger type
261
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
262
- containerPackageName = (packageInfoSource as any)?.package;
263
- }
264
- // TODO: use a stronger type
265
- containerPackageName =
266
- containerPackageName ?? odspResolvedUrl.codeHint?.containerPackageName;
267
-
268
- const context = await this.getContext?.(odspResolvedUrl, actualDataStorePath);
269
-
270
- storeLocatorInOdspUrl(url, {
271
- siteUrl: odspResolvedUrl.siteUrl,
272
- driveId: odspResolvedUrl.driveId,
273
- itemId: odspResolvedUrl.itemId,
274
- dataStorePath: actualDataStorePath,
275
- appName: this.appName,
276
- containerPackageName,
277
- fileVersion: odspResolvedUrl.fileVersion,
278
- context,
279
- });
256
+ odspResolvedUrl.context = await this.getContext?.(odspResolvedUrl, actualDataStorePath);
280
257
 
281
- return url.href;
258
+ const containerPackageName: string | undefined =
259
+ getContainerPackageName(packageInfoSource) ??
260
+ odspResolvedUrl.codeHint?.containerPackageName;
261
+
262
+ odspResolvedUrl.appName = this.appName;
263
+
264
+ return appendNavParam(baseUrl, odspResolvedUrl, actualDataStorePath, containerPackageName);
282
265
  }
283
266
 
284
267
  /**
@@ -126,3 +126,21 @@ export async function getOdspUrlParts(url: URL): Promise<IOdspUrlParts | undefin
126
126
  return { siteUrl: `${url.origin}${url.pathname}`, driveId, itemId };
127
127
  }
128
128
  }
129
+
130
+ /**
131
+ * Inspect the ODSP siteUrl to guess if this document is in SPDF or MSIT, to aid livesite investigations
132
+ * @param urlOnSite - The URL of the site or a resource on the site
133
+ * @returns undefined if the URL doesn't match known SPDF/MSIT patterns, "SPDF" if it's SPDF, "MSIT" if it's MSIT
134
+ */
135
+ export function checkForKnownServerFarmType(urlOnSite: string): "SPDF" | "MSIT" | undefined {
136
+ const domain = new URL(urlOnSite).hostname.toLowerCase();
137
+ if (domain.endsWith(".sharepoint-df.com")) {
138
+ return "SPDF";
139
+ } else if (
140
+ domain === "microsoft.sharepoint.com" ||
141
+ domain === "microsoft-my.sharepoint.com"
142
+ ) {
143
+ return "MSIT";
144
+ }
145
+ return undefined;
146
+ }
package/src/odspUtils.ts CHANGED
@@ -9,7 +9,11 @@ import {
9
9
  ITelemetryBaseProperties,
10
10
  } from "@fluidframework/core-interfaces";
11
11
  import { assert } from "@fluidframework/core-utils/internal";
12
- import { IResolvedUrl, ISnapshot } from "@fluidframework/driver-definitions/internal";
12
+ import {
13
+ IResolvedUrl,
14
+ ISnapshot,
15
+ IContainerPackageInfo,
16
+ } from "@fluidframework/driver-definitions/internal";
13
17
  import {
14
18
  type AuthorizationError,
15
19
  NetworkErrorBasic,
@@ -51,6 +55,7 @@ import {
51
55
  } from "@fluidframework/telemetry-utils/internal";
52
56
 
53
57
  import { fetch } from "./fetch.js";
58
+ import { storeLocatorInOdspUrl } from "./odspFluidFileLink.js";
54
59
  // eslint-disable-next-line import/no-deprecated
55
60
  import { ISnapshotContents } from "./odspPublicUtils.js";
56
61
  import { pkgVersion as driverVersion } from "./packageVersion.js";
@@ -335,7 +340,8 @@ export function getOdspResolvedUrl(resolvedUrl: IResolvedUrl): IOdspResolvedUrl
335
340
  /**
336
341
  * Type narrowing utility to determine if the provided {@link @fluidframework/driver-definitions#IResolvedUrl}
337
342
  * is an {@link @fluidframework/odsp-driver-definitions#IOdspResolvedUrl}.
338
- * @internal
343
+ * @legacy
344
+ * @alpha
339
345
  */
340
346
  export function isOdspResolvedUrl(resolvedUrl: IResolvedUrl): resolvedUrl is IOdspResolvedUrl {
341
347
  return "odspResolvedUrl" in resolvedUrl && resolvedUrl.odspResolvedUrl === true;
@@ -550,3 +556,70 @@ export function useLegacyFlowWithoutGroupsForSnapshotFetch(
550
556
  ): boolean {
551
557
  return loadingGroupIds === undefined;
552
558
  }
559
+
560
+ // back-compat: GitHub #9653
561
+ const isFluidPackage = (pkg: Record<string, unknown>): boolean =>
562
+ typeof pkg === "object" && typeof pkg?.name === "string" && typeof pkg?.fluid === "object";
563
+
564
+ /**
565
+ * Appends the store locator properties to the provided base URL. This function is useful for scenarios where an application
566
+ * has a base URL (for example a sharing link) of the Fluid file, but does not have the locator information that would be used by Fluid
567
+ * to load the file later.
568
+ * @param baseUrl - The input URL on which the locator params will be appended.
569
+ * @param resolvedUrl - odsp-driver's resolvedURL object.
570
+ * @param dataStorePath - The relative data store path URL.
571
+ * For requesting a driver URL, this value should always be '/'. If an empty string is passed, then dataStorePath
572
+ * will be extracted from the resolved url if present.
573
+ * @param containerPackageName - Name of the package to be included in the URL.
574
+ * @returns The provided base URL appended with odsp-specific locator information
575
+ */
576
+ export function appendNavParam(
577
+ baseUrl: string,
578
+ odspResolvedUrl: IOdspResolvedUrl,
579
+ dataStorePath: string,
580
+ containerPackageName?: string,
581
+ ): string {
582
+ const url = new URL(baseUrl);
583
+
584
+ // If the user has passed an empty dataStorePath, then extract it from the resolved url.
585
+ const actualDataStorePath = dataStorePath || (odspResolvedUrl.dataStorePath ?? "");
586
+
587
+ storeLocatorInOdspUrl(url, {
588
+ siteUrl: odspResolvedUrl.siteUrl,
589
+ driveId: odspResolvedUrl.driveId,
590
+ itemId: odspResolvedUrl.itemId,
591
+ dataStorePath: actualDataStorePath,
592
+ appName: odspResolvedUrl.appName,
593
+ containerPackageName,
594
+ fileVersion: odspResolvedUrl.fileVersion,
595
+ context: odspResolvedUrl.context,
596
+ });
597
+
598
+ return url.href;
599
+ }
600
+
601
+ /**
602
+ * Returns the package name of the container package information.
603
+ * @param packageInfoSource - Information of the package connected to the URL
604
+ * @returns The package name of the container package
605
+ */
606
+ export function getContainerPackageName(
607
+ packageInfoSource: IContainerPackageInfo | undefined,
608
+ ): string | undefined {
609
+ let containerPackageName: string | undefined;
610
+ if (packageInfoSource && "name" in packageInfoSource) {
611
+ containerPackageName = packageInfoSource.name;
612
+ // packageInfoSource is cast to any as it is typed to IContainerPackageInfo instead of IFluidCodeDetails
613
+ // TODO: use a stronger type
614
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
615
+ } else if (isFluidPackage((packageInfoSource as any)?.package)) {
616
+ // TODO: use a stronger type
617
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
618
+ containerPackageName = (packageInfoSource as any)?.package.name;
619
+ } else {
620
+ // TODO: use a stronger type
621
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
622
+ containerPackageName = (packageInfoSource as any)?.package;
623
+ }
624
+ return containerPackageName;
625
+ }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/odsp-driver";
9
- export const pkgVersion = "2.10.0";
9
+ export const pkgVersion = "2.12.0";