Skip to content

Update odsp driver for new header #24819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 19, 2025
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
11cb41f
update the shareLink enum
MarioJGMsoft Jun 10, 2025
d51a137
feat: added non-durable redeem.
MarioJGMsoft Jun 11, 2025
9a2e061
Merge branch 'main' into ODSPHeaderNonDurableRedeem
MarioJGMsoft Jun 11, 2025
282b9b9
fix: added nondurable redeem to manual redirect
MarioJGMsoft Jun 11, 2025
91aa627
docs: changed variable name
MarioJGMsoft Jun 11, 2025
667cb44
docs: updated variable name
MarioJGMsoft Jun 11, 2025
11db37b
test: added tests and covered edge case
MarioJGMsoft Jun 11, 2025
c4686cb
feat: added all logic to a single if.
MarioJGMsoft Jun 11, 2025
1f0cb9c
fix: updated name of header variable
MarioJGMsoft Jun 12, 2025
5169eef
fix: fixed test variables
MarioJGMsoft Jun 12, 2025
ad1e6a1
test: initial test to review headers
MarioJGMsoft Jun 12, 2025
073fbf9
test-fix: Fixed.only of test
MarioJGMsoft Jun 12, 2025
f73a3c9
test: added full test
MarioJGMsoft Jun 13, 2025
57d56d7
test: added event prop
MarioJGMsoft Jun 13, 2025
fff21d2
fix: applied changes based on feedback
MarioJGMsoft Jun 13, 2025
4185ecb
Merge branch 'main' into ODSPHeaderNonDurableRedeem
MarioJGMsoft Jun 13, 2025
cd6cbeb
comments: applied changes based on comments
MarioJGMsoft Jun 17, 2025
5e427f2
Merge branch 'main' into ODSPHeaderNonDurableRedeem
MarioJGMsoft Jun 17, 2025
edc05b2
docs: updated comment
MarioJGMsoft Jun 17, 2025
e4a414d
revert: reverted unnecessary change
MarioJGMsoft Jun 17, 2025
6067787
docs: applied changes based on comments
MarioJGMsoft Jun 17, 2025
e4e01d3
Merge branch 'main' into ODSPHeaderNonDurableRedeem
MarioJGMsoft Jun 17, 2025
68181da
telemetry: added isRedemptionNonDurable in fetchLatestSnapshotCore ev…
MarioJGMsoft Jun 18, 2025
2967ca7
test: updated assert error message
MarioJGMsoft Jun 18, 2025
a12f59d
telemetry: moved isRedemptionNonDurable
MarioJGMsoft Jun 19, 2025
23b356e
test: added isRedemptionNonDurable to details variable
MarioJGMsoft Jun 19, 2025
0a0f628
Merge branch 'main' into ODSPHeaderNonDurableRedeem
MarioJGMsoft Jun 19, 2025
9230287
fix: added isRedemptionNonDurable to details and updated biome format
MarioJGMsoft Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -241,6 +241,7 @@ export interface ShareLinkInfoType {
error?: any;
shareId?: string;
};
isRedemptionNonDurable?: boolean;
sharingLinkToRedeem?: string;
}

8 changes: 8 additions & 0 deletions packages/drivers/odsp-driver-definitions/src/resolvedUrl.ts
Original file line number Diff line number Diff line change
@@ -95,6 +95,14 @@ export interface ShareLinkInfoType {
* permission then this link can be redeemed for the permissions in the same network call.
*/
sharingLinkToRedeem?: string;

/**
* When a sharingLinkToRedeem is used, this parameter can be set to make the request with
* the redemption expire after a certain time period.
* The duration of the redemption is not set by Fluid.
* @defaultValue false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@defaultValue is intended to describe the behavior that results from not specifying a value (or setting it to undefined). So here it should describe the default boolean value that will be used when this is undefined.

*/
isRedemptionNonDurable?: boolean;
}
/**
* @legacy
12 changes: 10 additions & 2 deletions packages/drivers/odsp-driver/src/contractsPublic.ts
Original file line number Diff line number Diff line change
@@ -21,16 +21,24 @@ export interface OdspFluidDataStoreLocator extends IOdspUrlParts {
* @internal
*/
export enum SharingLinkHeader {
// Can be used in request made to resolver, to tell the resolver that the passed in URL is a sharing link
// which can be redeemed at server to get permissions.
/**
* Can be used in request made to resolver, to tell the resolver that the passed in URL is a sharing link
* which can be redeemed at server to get permissions.
*/
isSharingLinkToRedeem = "isSharingLinkToRedeem",
/**
* When isSharingLinkToRedeem is true, this header can be used to tell the server that the redemption of the sharing link
* is meant to be non-durable.
*/
isRedemptionNonDurable = "isRedemptionNonDurable",
}

/**
* @internal
*/
export interface ISharingLinkHeader {
[SharingLinkHeader.isSharingLinkToRedeem]: boolean;
[SharingLinkHeader.isRedemptionNonDurable]: boolean;
}
/**
* @internal
17 changes: 15 additions & 2 deletions packages/drivers/odsp-driver/src/fetchSnapshot.ts
Original file line number Diff line number Diff line change
@@ -224,6 +224,9 @@ async function redeemSharingLink(
odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem,
);

const isRedemptionNonDurable: boolean =
odspResolvedUrl.shareLinkInfo?.isRedemptionNonDurable === true;

let redeemUrl: string | undefined;
async function callSharesAPI(baseUrl: string): Promise<void> {
await getWithRetryForTokenRefresh(async (tokenFetchOptions) => {
@@ -240,7 +243,7 @@ async function redeemSharingLink(
"RedeemShareLink",
);
const headers = getHeadersWithAuth(authHeader);
headers.prefer = "redeemSharingLink";
headers.prefer = isRedemptionNonDurable ? "nonDurableRedeem" : "redeemSharingLink";
await fetchAndParseAsJSONHelper(url, { headers, method });
});
}
@@ -251,6 +254,7 @@ async function redeemSharingLink(
queryParamsLength: new URL(odspResolvedUrl.shareLinkInfo?.sharingLinkToRedeem).search
.length,
useHeaders: true,
isRedemptionNonDurable,
});
// There is an issue where if we use the siteUrl in /shares, then the allowed length of url is just a few hundred characters(300-400)
// and we fail to do the redeem. But if we use the tenant domain in the url, then the allowed length becomes 2048. So,
@@ -287,6 +291,8 @@ async function fetchLatestSnapshotCore(
const fetchSnapshotForLoadingGroup = isSnapshotFetchForLoadingGroup(loadingGroupIds);
const eventName = fetchSnapshotForLoadingGroup ? "TreesLatestForGroup" : "TreesLatest";
const internalFarmType = checkForKnownServerFarmType(odspResolvedUrl.siteUrl);
const isRedemptionNonDurable: boolean =
odspResolvedUrl.shareLinkInfo?.isRedemptionNonDurable === true;

const perfEvent = {
eventName,
@@ -296,6 +302,8 @@ async function fetchLatestSnapshotCore(
redeemFallbackEnabled: enableRedeemFallback,
details: {
internalFarmType,
// Whether the redemption used is non-durable or not.
isRedemptionNonDurable,
},
};
if (snapshotOptions !== undefined) {
@@ -724,11 +732,16 @@ export const downloadSnapshot = mockify(
const queryString = getQueryString(queryParams);
const url = `${snapshotUrl}/trees/latest${queryString}`;
const method = "POST";
const isRedemptionNonDurable: boolean =
odspResolvedUrl.shareLinkInfo?.isRedemptionNonDurable === true;
// The location of file can move on Spo in which case server returns 308(Permanent Redirect) error.
// Adding below header will make VROOM API return 404 instead of 308 and browser can intercept it.
// This error thrown by server will contain the new redirect location. Look at the 404 error parsing
// for further reference here: \packages\utils\odsp-doclib-utils\src\odspErrorUtils.ts
const header = { prefer: "manualredirect" };
// If the share link is non-durable, we will add the nonDurableRedeem header to the header.prefer.
const header = isRedemptionNonDurable
? { prefer: "manualredirect, nonDurableRedeem" }
: { prefer: "manualredirect" };
const authHeader = await getAuthHeader(
{ ...tokenFetchOptions, request: { url, method } },
"downloadSnapshot",
Original file line number Diff line number Diff line change
@@ -137,6 +137,8 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
const requestToBeResolved = { headers: request.headers, url: request.url };
const isSharingLinkToRedeem =
requestToBeResolved.headers?.[SharingLinkHeader.isSharingLinkToRedeem];
const isRedemptionNonDurable =
requestToBeResolved.headers?.[SharingLinkHeader.isRedemptionNonDurable];
try {
const url = new URL(request.url);

@@ -167,6 +169,7 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
// the eligible length.
odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo ?? {}, {
sharingLinkToRedeem: this.removeNavParam(request.url),
isRedemptionNonDurable: isRedemptionNonDurable ?? false,
});
}
if (odspResolvedUrl.itemId) {
55 changes: 54 additions & 1 deletion packages/drivers/odsp-driver/src/test/fetchSnapshot.spec.ts
Original file line number Diff line number Diff line change
@@ -561,7 +561,60 @@ describe("Tests1 for snapshot fetch", () => {
assert(
mockLogger.matchEvents([
{ eventName: "TreesLatest_cancel", shareLinkPresent: true },
{ eventName: "RedeemShareLink_end" },
{
eventName: "RedeemShareLink_end",
details:
'{"shareLinkUrlLength":45,"queryParamsLength":0,"useHeaders":true,"isRedemptionNonDurable":false}',
},
{ eventName: "RedeemFallback", errorType: "fileNotFoundOrAccessDeniedError" },
{ eventName: "TreesLatest_end" },
]),
);
});

it("nonDurableRedeem header is set during RedeemFallback behavior", async () => {
resolved.shareLinkInfo = {
sharingLinkToRedeem: "https://microsoft.sharepoint-df.com/sharelink",
isRedemptionNonDurable: true,
};
hostPolicy.enableRedeemFallback = true;

const snapshot: ISnapshot = {
blobContents,
snapshotTree: snapshotTreeWithGroupId,
ops: [],
latestSequenceNumber: 0,
sequenceNumber: 0,
snapshotFormatV: 1,
};
const response = (await createResponse(
{ "x-fluid-epoch": "epoch1", "content-type": "application/ms-fluid" },
convertToCompactSnapshot(snapshot),
200,
)) as unknown as Response;

await assert.doesNotReject(
async () =>
mockFetchMultiple(
async () => service.getSnapshot({}),
[
notFound,
async (): Promise<MockResponse> => okResponse({}, {}),
async (): Promise<Response> => {
return response;
},
],
),
"Should succeed",
);
assert(
mockLogger.matchEvents([
{ eventName: "TreesLatest_cancel", shareLinkPresent: true },
{
eventName: "RedeemShareLink_end",
details:
'{"shareLinkUrlLength":45,"queryParamsLength":0,"useHeaders":true,"isRedemptionNonDurable":true}',
},
{ eventName: "RedeemFallback", errorType: "fileNotFoundOrAccessDeniedError" },
{ eventName: "TreesLatest_end" },
]),
Original file line number Diff line number Diff line change
@@ -356,6 +356,41 @@ describe("Tests for OdspDriverUrlResolverForShareLink resolver", () => {
);
});

it("isNonDurableRedeem should be set when isNonDurableRedeem header is set", async () => {
const url = new URL(sharelink);
const resolvedUrl = await mockGetFileLink(Promise.resolve(sharelink), async () => {
storeLocatorInOdspUrl(url, { siteUrl, driveId, itemId, dataStorePath });
return urlResolverWithShareLinkFetcher.resolve({
url: url.toString(),
headers: {
[SharingLinkHeader.isSharingLinkToRedeem]: true,
[SharingLinkHeader.isRedemptionNonDurable]: true,
},
});
});
assert(
resolvedUrl.shareLinkInfo?.isRedemptionNonDurable === true,
"isRedemptionNonDurable should be set in shareLinkInfo of resolved url",
);
});

it("isNonDurableRedeem should not be set when isSharingLinkToRedeem header is not set", async () => {
const url = new URL(sharelink);
const resolvedUrl = await mockGetFileLink(Promise.resolve(sharelink), async () => {
storeLocatorInOdspUrl(url, { siteUrl, driveId, itemId, dataStorePath });
return urlResolverWithShareLinkFetcher.resolve({
url: url.toString(),
headers: {
[SharingLinkHeader.isRedemptionNonDurable]: true,
},
});
});
assert(
resolvedUrl.shareLinkInfo?.isRedemptionNonDurable === undefined,
"isRedemptionNonDurable should not be set in shareLinkInfo and shareLinkInfo should be undefined",
);
});

it("Encode and decode nav param", async () => {
const encodedUrl = new URL(sharelink);
storeLocatorInOdspUrl(encodedUrl, {
Loading
Oops, something went wrong.