Skip to content

Commit

Permalink
Fix error when accessing some not found pages
Browse files Browse the repository at this point in the history
Fix GITBOOK-OPEN-1XDB
Fix GITBOOK-OPEN-1XDC
Fix GITBOOK-OPEN-1X07
Fix GITBOOK-OPEN-1XCJ
Fix GITBOOK-OPEN-1X86
  • Loading branch information
gregberge committed Jan 15, 2025
1 parent 65cc4af commit d07aed1
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-berries-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gitbook': patch
---

Fix error when accessing some not found pages.
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import { TrackPageViewEvent } from '@/components/Insights';
import { getSpaceLanguage, t } from '@/intl/server';
import { languages } from '@/intl/translations';
import { getSiteData, getSpaceContentData } from '@/lib/api';
import { checkIsFromMiddleware } from '@/lib/pages';
import { getSiteContentPointer } from '@/lib/pointer';
import { tcls } from '@/lib/tailwind';
import Link from 'next/link';

export default async function NotFound() {
const fromMiddleware = await checkIsFromMiddleware();
if (!fromMiddleware) {
return (
<div className="flex flex-1 flex-row items-center justify-center py-9 h-screen">
<div className="max-w-80">
<h2 className="text-2xl font-semibold mb-2">Not found</h2>
<p className="text-base mb-4">This page could not be found</p>
<Link href="/" className="text-blue-500 hover:text-blue-700 underline">
Go back to home
</Link>
</div>
</div>
);
}
const pointer = await getSiteContentPointer();
const [{ space }, { customization }] = await Promise.all([
getSpaceContentData(pointer, pointer.siteShareKey),
getSiteData(pointer),
]);

const language = getSpaceLanguage(customization);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import React from 'react';
import { PageAside } from '@/components/PageAside';
import { PageBody, PageCover } from '@/components/PageBody';
import { PageHrefContext, getAbsoluteHref, getPageHref } from '@/lib/links';
import { getPagePath, resolveFirstDocument } from '@/lib/pages';
import { checkIsFromMiddleware, getPagePath, resolveFirstDocument } from '@/lib/pages';
import { ContentRefContext } from '@/lib/references';
import { isSpaceIndexable, isPageIndexable } from '@/lib/seo';
import { getContentTitle } from '@/lib/utils';

import { PageClientLayout } from './PageClientLayout';
import { PagePathParams, fetchPageData, getPathnameParam, normalizePathname } from '../../fetch';
import { headers } from 'next/headers';

export const runtime = 'edge';
export const dynamic = 'force-dynamic';
Expand All @@ -24,6 +25,8 @@ export default async function Page(props: {
params: Promise<PagePathParams>;
searchParams: Promise<{ fallback?: string }>;
}) {
await ensureIsFromMiddleware();

const { params: rawParams, searchParams: rawSearchParams } = props;

const params = await rawParams;
Expand Down Expand Up @@ -127,6 +130,7 @@ export async function generateViewport({
}: {
params: Promise<PagePathParams>;
}): Promise<Viewport> {
await ensureIsFromMiddleware();
const { customization } = await fetchPageData(await params);
return {
colorScheme: customization.themes.toggeable
Expand All @@ -144,6 +148,8 @@ export async function generateMetadata({
params: Promise<PagePathParams>;
searchParams: Promise<{ fallback?: string }>;
}): Promise<Metadata> {
await ensureIsFromMiddleware();

const { space, pages, page, customization, site, ancestors } = await getPageDataWithFallback({
pagePathParams: await params,
searchParams: await searchParams,
Expand Down Expand Up @@ -206,3 +212,15 @@ async function getPageDataWithFallback(args: {
page,
};
}

/**
* Returns a page not found if the request is not from the middleware.
* Some pages can be
*/
async function ensureIsFromMiddleware() {
// To check if the request is from the middleware, we check if the x-gitbook-token is set in the headers.
const fromMiddleware = await checkIsFromMiddleware();
if (!fromMiddleware) {
notFound();
}
}
18 changes: 18 additions & 0 deletions packages/gitbook/src/app/(site)/(content)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getContentTitle } from '@/lib/utils';
import { ClientContexts } from './ClientContexts';
import { RocketLoaderDetector } from './RocketLoaderDetector';
import { fetchContentData } from '../fetch';
import { checkIsFromMiddleware } from '@/lib/pages';

export const runtime = 'edge';
export const dynamic = 'force-dynamic';
Expand All @@ -29,6 +30,10 @@ export const dynamic = 'force-dynamic';
*/
export default async function ContentLayout(props: { children: React.ReactNode }) {
const { children } = props;
const fromMiddleware = await checkIsFromMiddleware();
if (!fromMiddleware) {
return props.children;
}

const nonce = await getContentSecurityPolicyNonce();
const {
Expand Down Expand Up @@ -106,6 +111,11 @@ export default async function ContentLayout(props: { children: React.ReactNode }
}

export async function generateViewport(): Promise<Viewport> {
const fromMiddleware = await checkIsFromMiddleware();
if (!fromMiddleware) {
return {};
}

const { customization } = await fetchContentData();
return {
colorScheme: customization.themes.toggeable
Expand All @@ -117,6 +127,14 @@ export async function generateViewport(): Promise<Viewport> {
}

export async function generateMetadata(): Promise<Metadata> {
const fromMiddleware = await checkIsFromMiddleware();
if (!fromMiddleware) {
return {
title: 'Not found',
robots: 'noindex, nofollow',
};
}

const { space, site, customization } = await fetchContentData();
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;

Expand Down
16 changes: 14 additions & 2 deletions packages/gitbook/src/app/(site)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { CustomizationRootLayout } from '@/components/RootLayout';
import { getSiteData } from '@/lib/api';
import { checkIsFromMiddleware } from '@/lib/pages';
import { getSiteContentPointer } from '@/lib/pointer';

/**
* Layout to be used for the site root. It fetches the customization data for the site
* and initializes the CustomizationRootLayout with it.
*/
export default async function SiteRootLayout(props: { children: React.ReactNode }) {
const { children } = props;
const fromMiddleware = await checkIsFromMiddleware();
if (!fromMiddleware) {
return (
<html lang="en">
<body className="font-[sans-serif]">
<main>{props.children}</main>
</body>
</html>
);
}

const pointer = await getSiteContentPointer();
const { customization } = await getSiteData(pointer);

return (
<CustomizationRootLayout customization={customization}>{children}</CustomizationRootLayout>
<CustomizationRootLayout customization={customization}>
{props.children}
</CustomizationRootLayout>
);
}
4 changes: 2 additions & 2 deletions packages/gitbook/src/components/DocumentView/Embed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { tcls } from '@/lib/tailwind';
import { BlockProps } from './Block';
import { Caption } from './Caption';
import { IntegrationBlock } from './Integration';
import { getContentSecurityPolicyNonce } from '@/lib/csp';

export async function Embed(props: BlockProps<gitbookAPI.DocumentBlockEmbed>) {
const { block, context, ...otherProps } = props;
const headersList = await headers();
const nonce = headersList.get('x-nonce') || undefined;
const nonce = await getContentSecurityPolicyNonce();

ReactDOM.preload('https://cdn.iframe.ly/embed.js', { as: 'script', nonce });

Expand Down
7 changes: 3 additions & 4 deletions packages/gitbook/src/lib/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { headers } from 'next/headers';

import { assetsDomain } from './assets';
import { filterOutNullable } from './typescript';
import { assert } from 'ts-essentials';

/**
* Get the current nonce for the current request.
*/
export async function getContentSecurityPolicyNonce(): Promise<string> {
const headersList = await headers();
const nonce = headersList.get('x-nonce');
if (!nonce) {
throw new Error('No nonce found in headers');
}

assert(nonce, 'x-nonce should be set in the headers by the middleware');
return nonce;
}

Expand Down
11 changes: 11 additions & 0 deletions packages/gitbook/src/lib/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
RevisionPageGroup,
RevisionPageType,
} from '@gitbook/api';
import { headers } from 'next/headers';

export type AncestorRevisionPage = RevisionPageDocument | RevisionPageGroup;

Expand Down Expand Up @@ -184,3 +185,13 @@ function flattenPages(

return result;
}

/**
* Returns a page not found if the request is not from the middleware.
* Some pages can be
*/
export async function checkIsFromMiddleware() {
const headerList = await headers();
// To check if the request is from the middleware, we check if the x-gitbook-token is set in the headers.
return Boolean(headerList.get('x-gitbook-token'));
}
47 changes: 27 additions & 20 deletions packages/gitbook/src/lib/pointer.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
import { headers } from 'next/headers';

import { SiteContentPointer, SpaceContentPointer } from './api';
import { assert } from 'ts-essentials';

/**
* Get the current site content pointer from the headers
*/
export async function getSiteContentPointer(): Promise<SiteContentPointer> {
const headersList = await headers();

const spaceId = headersList.get('x-gitbook-content-space');
assert(spaceId, 'x-gitbook-content-space should be set in the headers by the middleware');

const siteId = headersList.get('x-gitbook-content-site');
assert(siteId, 'x-gitbook-content-site should be set in the headers by the middleware');

const organizationId = headersList.get('x-gitbook-content-organization');
const siteSpaceId = headersList.get('x-gitbook-content-site-space');
const siteSectionId = headersList.get('x-gitbook-content-site-section');
const siteShareKey = headersList.get('x-gitbook-content-site-share-key');
assert(
organizationId,
'x-gitbook-content-organization should be set in the headers by the middleware',
);

if (!spaceId || !siteId || !organizationId) {
throw new Error(
'getSiteContentPointer is called outside the scope of a request processed by the middleware',
);
}
const siteSectionId = headersList.get('x-gitbook-content-site-section') ?? undefined;
const siteSpaceId = headersList.get('x-gitbook-content-site-space') ?? undefined;
const siteShareKey = headersList.get('x-gitbook-content-site-share-key') ?? undefined;
const revisionId = headersList.get('x-gitbook-content-revision') ?? undefined;
const changeRequestId = headersList.get('x-gitbook-content-changerequest') ?? undefined;

const pointer: SiteContentPointer = {
siteId,
spaceId,
siteSectionId: siteSectionId ?? undefined,
siteSpaceId: siteSpaceId ?? undefined,
siteShareKey: siteShareKey ?? undefined,
organizationId,
revisionId: headersList.get('x-gitbook-content-revision') ?? undefined,
changeRequestId: headersList.get('x-gitbook-content-changerequest') ?? undefined,
siteSectionId,
siteSpaceId,
siteShareKey,
revisionId,
changeRequestId,
};

return pointer;
Expand All @@ -40,17 +47,17 @@ export async function getSiteContentPointer(): Promise<SiteContentPointer> {
*/
export async function getSpacePointer(): Promise<SpaceContentPointer> {
const headersList = await headers();

const spaceId = headersList.get('x-gitbook-content-space');
if (!spaceId) {
throw new Error(
'getSpacePointer is called outside the scope of a request processed by the middleware',
);
}
assert(spaceId, 'x-gitbook-content-space should be set in the headers by the middleware');

const revisionId = headersList.get('x-gitbook-content-revision') ?? undefined;
const changeRequestId = headersList.get('x-gitbook-content-changerequest') ?? undefined;

const pointer: SpaceContentPointer = {
spaceId,
revisionId: headersList.get('x-gitbook-content-revision') ?? undefined,
changeRequestId: headersList.get('x-gitbook-content-changerequest') ?? undefined,
revisionId,
changeRequestId,
};

return pointer;
Expand Down

0 comments on commit d07aed1

Please sign in to comment.