-
Notifications
You must be signed in to change notification settings - Fork 535
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
Fix: Creating new Loop pages after SharePoint domain change #19443
Changes from 8 commits
6c6dd6f
0123021
496fdfd
8c9f544
73700ba
b688feb
eeac31d
cc50bab
fec84e8
ab6ccca
dcea3ec
d2816ab
bfd5f75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,11 @@ import { | |
OdspResourceTokenFetchOptions, | ||
TokenFetcher, | ||
} from "@fluidframework/odsp-driver-definitions"; | ||
import { DriverErrorTypes } from "@fluidframework/driver-definitions"; | ||
import { getUrlAndHeadersWithAuth } from "./getUrlAndHeadersWithAuth"; | ||
import { | ||
IOdspResponse, | ||
TokenFetchOptionsEx, | ||
fetchHelper, | ||
getWithRetryForTokenRefresh, | ||
toInstrumentedOdspTokenFetcher, | ||
|
@@ -90,6 +93,83 @@ export async function getFileLink( | |
return fileLink; | ||
} | ||
|
||
async function getRequestInformation( | ||
getToken: TokenFetcher<OdspResourceTokenFetchOptions>, | ||
odspUrlParts: IOdspUrlParts, | ||
logger: ITelemetryLoggerExt, | ||
options: TokenFetchOptionsEx, | ||
fileItem: FileItemLite, | ||
) { | ||
const storageTokenFetcher = toInstrumentedOdspTokenFetcher( | ||
logger, | ||
odspUrlParts, | ||
getToken, | ||
true /* throwOnNullToken */, | ||
); | ||
const storageToken = await storageTokenFetcher(options, "GetFileLinkCore"); | ||
assert( | ||
storageToken !== null, | ||
0x2bb /* "Instrumented token fetcher with throwOnNullToken = true should never return null" */, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every place that calls storageTokenFetcher() asserts that non-null result is returned. Given that's a wrapper produced by driver, can we at least move this assert into toInstrumentedOdspTokenFetcher() itself? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmmm that is actually part of the original code before moving it out to its own function. We can probably follow that up on a separate PR to keep this one small, is that alright? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. raised #20409 to fix it |
||
); | ||
|
||
// IMPORTANT: In past we were using GetFileByUrl() API to get to the list item that was corresponding | ||
// to the file. This was intentionally replaced with GetFileById() to solve the following issue: | ||
// GetFileByUrl() uses webDavUrl to locate list item. This API does not work for Consumer scenarios | ||
// where webDavUrl is constructed using legacy ODC format for backward compatibility reasons. | ||
// GetFileByUrl() does not understand that format and thus fails. GetFileById() relies on file item | ||
// unique guid (sharepointIds.listItemUniqueId) and it works uniformly across Consumer and Commercial. | ||
const { url, headers } = getUrlAndHeadersWithAuth( | ||
`${ | ||
odspUrlParts.siteUrl | ||
}/_api/web/GetFileById(@a1)/ListItemAllFields/GetSharingInformation?@a1=guid${encodeURIComponent( | ||
`'${fileItem.sharepointIds.listItemUniqueId}'`, | ||
)}`, | ||
storageToken, | ||
true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add comments with arg names where it's not very clear what is the arg semantics true, // forceAccessTokenViaAuthorizationHeader |
||
); | ||
const requestInit = { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json;odata=verbose", | ||
"Accept": "application/json;odata=verbose", | ||
...headers, | ||
}, | ||
}; | ||
|
||
return { url, requestInit }; | ||
} | ||
|
||
async function fetchWithLocationRedirectionHandling<T>( | ||
getToken: TokenFetcher<OdspResourceTokenFetchOptions>, | ||
odspUrlParts: IOdspUrlParts, | ||
logger: ITelemetryLoggerExt, | ||
options: TokenFetchOptionsEx, | ||
fileItem: FileItemLite, | ||
): Promise<IOdspResponse<Response>> { | ||
for (;;) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reason for this loop? My understanding from the changes in the code here is that we only want to perform a retry once after we get the 403 error. If this is true, can we structure the code such that we perform retry within the catch block? let { url, requestInit } = await getRequestInformation...
try {
response = await fetchHelper();
}catch(e){
// 403 error occured and we need to retry with correct headers here
if(error.redirectLocation){
{ url, requestInit } = = await getRequestInformation...
response = await fetchHelper();
}
}
return response; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redirects can actually be chained together! One redirect may point to another redirect and so on. This was actually a pattern taken from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add comments to explain this logic. |
||
const { url, requestInit } = await getRequestInformation( | ||
getToken, | ||
odspUrlParts, | ||
logger, | ||
options, | ||
fileItem, | ||
); | ||
|
||
try { | ||
return await fetchHelper(url, requestInit); | ||
} catch (error: any) { | ||
if (error.errorType !== DriverErrorTypes.locationRedirection) { | ||
throw error; | ||
} | ||
|
||
const newSiteDomain = new URL(error.redirectUrl).origin; | ||
const oldSiteDomain = new URL(odspUrlParts.siteUrl).origin; | ||
|
||
odspUrlParts.siteUrl = odspUrlParts.siteUrl.replace(oldSiteDomain, newSiteDomain); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing input parameters of a function is not the best programming practice. (Although I do see it in some parts of the existing code already, but let's avoid adding any new ones.) If possible, always return the new value to the caller, and let the caller assign the value as it may. |
||
} | ||
} | ||
} | ||
|
||
async function getFileLinkCore( | ||
getToken: TokenFetcher<OdspResourceTokenFetchOptions>, | ||
odspUrlParts: IOdspUrlParts, | ||
|
@@ -106,42 +186,15 @@ async function getFileLinkCore( | |
let additionalProps; | ||
const fileLink = await getWithRetryForTokenRefresh(async (options) => { | ||
attempts++; | ||
const storageTokenFetcher = toInstrumentedOdspTokenFetcher( | ||
logger, | ||
odspUrlParts, | ||
|
||
const response = await fetchWithLocationRedirectionHandling( | ||
getToken, | ||
true /* throwOnNullToken */, | ||
); | ||
const storageToken = await storageTokenFetcher(options, "GetFileLinkCore"); | ||
assert( | ||
storageToken !== null, | ||
0x2bb /* "Instrumented token fetcher with throwOnNullToken = true should never return null" */, | ||
odspUrlParts, | ||
logger, | ||
options, | ||
fileItem, | ||
); | ||
|
||
// IMPORTANT: In past we were using GetFileByUrl() API to get to the list item that was corresponding | ||
// to the file. This was intentionally replaced with GetFileById() to solve the following issue: | ||
// GetFileByUrl() uses webDavUrl to locate list item. This API does not work for Consumer scenarios | ||
// where webDavUrl is constructed using legacy ODC format for backward compatibility reasons. | ||
// GetFileByUrl() does not understand that format and thus fails. GetFileById() relies on file item | ||
// unique guid (sharepointIds.listItemUniqueId) and it works uniformly across Consumer and Commercial. | ||
const { url, headers } = getUrlAndHeadersWithAuth( | ||
`${ | ||
odspUrlParts.siteUrl | ||
}/_api/web/GetFileById(@a1)/ListItemAllFields/GetSharingInformation?@a1=guid${encodeURIComponent( | ||
`'${fileItem.sharepointIds.listItemUniqueId}'`, | ||
)}`, | ||
storageToken, | ||
true, | ||
); | ||
const requestInit = { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json;odata=verbose", | ||
"Accept": "application/json;odata=verbose", | ||
...headers, | ||
}, | ||
}; | ||
const response = await fetchHelper(url, requestInit); | ||
additionalProps = response.propsToLog; | ||
|
||
const sharingInfo = await response.content.json(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend adding code docs to explain the purpose of this function.