Skip to content

Commit 99b6c92

Browse files
authored
Resolve site level redirects (#2577)
1 parent 4d56f11 commit 99b6c92

File tree

6 files changed

+96
-18
lines changed

6 files changed

+96
-18
lines changed

bun.lockb

0 Bytes
Binary file not shown.

packages/gitbook/e2e/pages.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,19 @@ const testCases: TestsCase[] = [
491491
},
492492
],
493493
},
494+
{
495+
name: 'Site Redirects',
496+
baseUrl: 'https://gitbook-open-e2e-sites.gitbook.io/gitbook-doc/',
497+
tests: [
498+
{
499+
name: 'Redirect to SSO page',
500+
url: 'a/redirect/to/sso',
501+
run: async (page) => {
502+
await expect(page.locator('h1')).toHaveText('SSO');
503+
},
504+
},
505+
],
506+
},
494507
{
495508
name: 'Share links',
496509
baseUrl: 'https://gitbook.gitbook.io/gbo-tests-share-links/',

packages/gitbook/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static"
1717
},
1818
"dependencies": {
19-
"@gitbook/api": "^0.76.0",
19+
"@gitbook/api": "^0.77.0",
2020
"@gitbook/cache-do": "workspace:*",
2121
"@gitbook/emoji-codepoints": "workspace:*",
2222
"@gitbook/icons": "workspace:*",

packages/gitbook/src/app/(site)/fetch.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { RevisionPage } from '@gitbook/api';
2+
import { redirect } from 'next/navigation';
23

34
import {
45
getRevisionPageByPath,
56
getDocument,
6-
ContentTarget,
77
getSpaceContentData,
88
getSiteData,
9+
getSiteRedirectBySource,
910
} from '@/lib/api';
1011
import { resolvePagePath, resolvePageId } from '@/lib/pages';
1112
import { getSiteContentPointer } from '@/lib/pointer';
@@ -41,6 +42,7 @@ export async function fetchContentData() {
4142
site,
4243
sections,
4344
spaces,
45+
shareKey: content.siteShareKey,
4446
customization,
4547
scripts,
4648
ancestors: [],
@@ -54,7 +56,15 @@ export async function fetchContentData() {
5456
export async function fetchPageData(params: PagePathParams | PageIdParams) {
5557
const contentData = await fetchContentData();
5658

57-
const page = await resolvePage(contentData.contentTarget, contentData.pages, params);
59+
const page = await resolvePage({
60+
organizationId: contentData.space.organization,
61+
siteId: contentData.site.id,
62+
spaceId: contentData.contentTarget.spaceId,
63+
revisionId: contentData.contentTarget.revisionId,
64+
pages: contentData.pages,
65+
shareKey: contentData.shareKey,
66+
params,
67+
});
5868
const document = page?.page.documentId
5969
? await getDocument(contentData.space.id, page.page.documentId)
6070
: null;
@@ -70,11 +80,17 @@ export async function fetchPageData(params: PagePathParams | PageIdParams) {
7080
* Resolve a page from the params.
7181
* If the path can't be found, we try to resolve it from the API to handle redirects.
7282
*/
73-
async function resolvePage(
74-
contentTarget: ContentTarget,
75-
pages: RevisionPage[],
76-
params: PagePathParams | PageIdParams,
77-
) {
83+
async function resolvePage(input: {
84+
organizationId: string;
85+
siteId: string;
86+
spaceId: string;
87+
revisionId: string;
88+
shareKey: string | undefined;
89+
pages: RevisionPage[];
90+
params: PagePathParams | PageIdParams;
91+
}) {
92+
const { organizationId, siteId, spaceId, revisionId, pages, shareKey, params } = input;
93+
7894
if ('pageId' in params) {
7995
return resolvePageId(pages, params.pageId);
8096
}
@@ -88,20 +104,26 @@ async function resolvePage(
88104
return page;
89105
}
90106

91-
// If page can't be found, we try with the API, in case we have a redirect
92-
// We use the raw pathname to handle special/malformed redirects setup by users in the GitSync.
93-
// The page rendering will take care of redirecting to a normalized pathname.
94-
//
95107
// We don't test path that are too long as GitBook doesn't support them and will return a 404 anyway.
96108
if (rawPathname.length <= 512) {
97-
const resolved = await getRevisionPageByPath(
98-
contentTarget.spaceId,
99-
contentTarget.revisionId,
100-
rawPathname,
101-
);
109+
// If page can't be found, we try with the API, in case we have a redirect at space level.
110+
// We use the raw pathname to handle special/malformed redirects setup by users in the GitSync.
111+
// The page rendering will take care of redirecting to a normalized pathname.
112+
const resolved = await getRevisionPageByPath(spaceId, revisionId, rawPathname);
102113
if (resolved) {
103114
return resolvePageId(pages, resolved.id);
104115
}
116+
117+
// If a page still can't be found, we try with the API, in case we have a redirect at site level.
118+
const resolvedSiteRedirect = await getSiteRedirectBySource({
119+
organizationId,
120+
siteId,
121+
source: rawPathname.startsWith('/') ? rawPathname : `/${rawPathname}`,
122+
siteShareKey: input.shareKey,
123+
});
124+
if (resolvedSiteRedirect) {
125+
return redirect(resolvedSiteRedirect.target);
126+
}
105127
}
106128

107129
return undefined;

packages/gitbook/src/lib/api.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,49 @@ export const getDocument = cache({
620620
timeout: 20 * 1000,
621621
});
622622

623+
/**
624+
* Resolve a site redirect by its source path.
625+
*/
626+
export const getSiteRedirectBySource = cache({
627+
name: 'api.getSiteRedirectBySource',
628+
tag: ({ siteId }) => getAPICacheTag({ tag: 'site', site: siteId }),
629+
get: async (
630+
args: {
631+
organizationId: string;
632+
siteId: string;
633+
/** Site share key that can be used as context to resolve site space published urls */
634+
siteShareKey: string | undefined;
635+
source: string;
636+
},
637+
options: CacheFunctionOptions,
638+
) => {
639+
try {
640+
const response = await api().orgs.getSiteRedirectBySource(
641+
args.organizationId,
642+
args.siteId,
643+
{
644+
shareKey: args.siteShareKey,
645+
source: args.source,
646+
},
647+
{
648+
...noCacheFetchOptions,
649+
signal: options.signal,
650+
},
651+
);
652+
return cacheResponse(response, cacheTtl_1day);
653+
} catch (error) {
654+
if ((error as GitBookAPIError).code === 404) {
655+
return {
656+
data: null,
657+
...cacheTtl_1day,
658+
};
659+
}
660+
661+
throw error;
662+
}
663+
},
664+
});
665+
623666
/**
624667
* Get the infos about a site by its ID.
625668
*/

packages/react-contentkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"dependencies": {
1212
"classnames": "^2.5.1",
13-
"@gitbook/api": "^0.76.0",
13+
"@gitbook/api": "^0.77.0",
1414
"assert-never": "^1.2.1"
1515
},
1616
"peerDependencies": {

0 commit comments

Comments
 (0)