From 87b8ea8c0584805c533c71809ca2bbf560f93926 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Greg=20Berg=C3=A9?=
Date: Mon, 13 Jan 2025 12:44:00 +0100
Subject: [PATCH] Revert reading a context from headers and passing it (#2727)
---
.changeset/friendly-trains-exercise.md | 5 -
.changeset/seven-hounds-grow.md | 5 +
.../src/app/(global)/~gitbook/image/route.ts | 4 +-
.../(content)/[[...pathname]]/not-found.tsx | 10 +-
.../(site)/(content)/[[...pathname]]/page.tsx | 87 +++--
.../src/app/(site)/(content)/layout.tsx | 35 +-
.../src/app/(site)/(core)/robots.txt/route.ts | 12 +-
.../app/(site)/(core)/sitemap.xml/route.ts | 46 +--
.../app/(site)/(core)/~gitbook/icon/route.tsx | 10 +-
.../~gitbook/ogimage/[pageId]/route.tsx | 61 ++--
packages/gitbook/src/app/(site)/fetch.ts | 42 ++-
packages/gitbook/src/app/(site)/layout.tsx | 8 +-
.../src/app/(space)/~gitbook/pdf/layout.tsx | 18 +-
.../src/app/(space)/~gitbook/pdf/page.tsx | 25 +-
.../src/app/(space)/~gitbook/pdf/pointer.ts | 11 +-
.../components/AdminToolbar/AdminToolbar.tsx | 9 +-
.../RefreshChangeRequestButton.tsx | 3 -
packages/gitbook/src/components/Ads/Ad.tsx | 9 +-
.../src/components/Ads/AdClassicRendering.tsx | 7 +-
.../src/components/Ads/AdCoverRendering.tsx | 5 +-
.../gitbook/src/components/Ads/renderAd.tsx | 33 +-
.../AutoRefreshContent/server-actions.ts | 20 +-
.../useCheckForContentUpdate.ts | 15 +-
.../DocumentView/BlockContentRef.tsx | 6 +-
.../src/components/DocumentView/Embed.tsx | 9 +-
.../Integration/IntegrationBlock.tsx | 9 +-
.../DocumentView/ReusableContent.tsx | 9 +-
.../components/Footer/FooterLinksGroup.tsx | 5 +-
.../src/components/Header/HeaderLink.tsx | 8 +-
.../src/components/Header/HeaderLinkMore.tsx | 5 +-
.../src/components/Header/HeaderLogo.tsx | 5 +-
.../src/components/PageAside/PageAside.tsx | 16 +-
.../src/components/PageBody/PageBody.tsx | 11 +-
.../PageBody/PageBodyBlankslate.tsx | 7 +-
.../src/components/PageBody/PageCover.tsx | 5 +-
.../PageBody/PageFooterNavigation.tsx | 7 +-
.../src/components/PageBody/PageHeader.tsx | 5 +-
.../PageFeedback/PageFeedbackForm.tsx | 7 +-
.../components/PageFeedback/server-actions.ts | 20 +-
.../src/components/Search/SearchAskAnswer.tsx | 30 +-
.../src/components/Search/SearchModal.tsx | 5 +-
.../src/components/Search/SearchResults.tsx | 16 +-
.../src/components/Search/server-actions.tsx | 172 +++++-----
.../src/components/Space/SpaceIcon.tsx | 9 +-
.../components/SpaceLayout/SpaceLayout.tsx | 11 +-
.../TableOfContents/PageDocumentItem.tsx | 5 +-
.../TableOfContents/PageLinkItem.tsx | 5 +-
.../gitbook/src/components/utils/Image.tsx | 23 +-
packages/gitbook/src/lib/api.ts | 309 ++++++++----------
packages/gitbook/src/lib/cache/cache.test.ts | 12 +-
packages/gitbook/src/lib/cache/cache.ts | 8 +-
packages/gitbook/src/lib/csp.ts | 9 +-
packages/gitbook/src/lib/gitbook-context.ts | 83 -----
packages/gitbook/src/lib/image-signatures.ts | 36 +-
packages/gitbook/src/lib/images.ts | 19 +-
packages/gitbook/src/lib/links.ts | 46 +--
packages/gitbook/src/lib/pointer.ts | 26 +-
packages/gitbook/src/lib/references.tsx | 33 +-
packages/gitbook/src/lib/seo.ts | 21 +-
packages/gitbook/src/lib/tracking.ts | 9 +-
packages/gitbook/src/lib/visitor-token.ts | 9 +
packages/gitbook/src/middleware.ts | 57 ++--
62 files changed, 638 insertions(+), 929 deletions(-)
delete mode 100644 .changeset/friendly-trains-exercise.md
create mode 100644 .changeset/seven-hounds-grow.md
delete mode 100644 packages/gitbook/src/lib/gitbook-context.ts
diff --git a/.changeset/friendly-trains-exercise.md b/.changeset/friendly-trains-exercise.md
deleted file mode 100644
index eb1616633..000000000
--- a/.changeset/friendly-trains-exercise.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'gitbook': patch
----
-
-Fix multiple bugs due to headers read in an anarchic way in the app.
diff --git a/.changeset/seven-hounds-grow.md b/.changeset/seven-hounds-grow.md
new file mode 100644
index 000000000..d125b8eea
--- /dev/null
+++ b/.changeset/seven-hounds-grow.md
@@ -0,0 +1,5 @@
+---
+'gitbook': patch
+---
+
+Fix issue leading to increase the storage write and the stability of the platform
diff --git a/packages/gitbook/src/app/(global)/~gitbook/image/route.ts b/packages/gitbook/src/app/(global)/~gitbook/image/route.ts
index 67096ba32..f96d0bc90 100644
--- a/packages/gitbook/src/app/(global)/~gitbook/image/route.ts
+++ b/packages/gitbook/src/app/(global)/~gitbook/image/route.ts
@@ -1,6 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import {
CURRENT_SIGNATURE_VERSION,
isSignatureVersion,
@@ -40,8 +39,7 @@ export async function GET(request: NextRequest) {
}
// Verify the signature
- const ctx = getGitBookContextFromHeaders(request.headers);
- const verified = await verifyImageSignature(ctx, url, { signature, version: signatureVersion });
+ const verified = await verifyImageSignature(url, { signature, version: signatureVersion });
if (!verified) {
return new Response(`Invalid signature "${signature ?? ''}" for "${url}"`, { status: 400 });
}
diff --git a/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/not-found.tsx b/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/not-found.tsx
index bff278a06..cd36e5b26 100644
--- a/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/not-found.tsx
+++ b/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/not-found.tsx
@@ -1,18 +1,14 @@
-import { headers } from 'next/headers';
-
import { TrackPageViewEvent } from '@/components/Insights';
import { getSpaceLanguage, t } from '@/intl/server';
import { getSiteData, getSpaceContentData } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getSiteContentPointer } from '@/lib/pointer';
import { tcls } from '@/lib/tailwind';
export default async function NotFound() {
- const ctx = getGitBookContextFromHeaders(await headers());
- const pointer = getSiteContentPointer(ctx);
+ const pointer = await getSiteContentPointer();
const [{ space }, { customization }] = await Promise.all([
- getSpaceContentData(ctx, pointer, pointer.siteShareKey),
- getSiteData(ctx, pointer),
+ getSpaceContentData(pointer, pointer.siteShareKey),
+ getSiteData(pointer),
]);
const language = getSpaceLanguage(customization);
diff --git a/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/page.tsx b/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/page.tsx
index a9f5a279a..786d5d6eb 100644
--- a/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/page.tsx
+++ b/packages/gitbook/src/app/(site)/(content)/[[...pathname]]/page.tsx
@@ -1,12 +1,10 @@
import { CustomizationHeaderPreset, CustomizationThemeMode } from '@gitbook/api';
import { Metadata, Viewport } from 'next';
-import { headers } from 'next/headers';
import { notFound, redirect } from 'next/navigation';
import React from 'react';
import { PageAside } from '@/components/PageAside';
import { PageBody, PageCover } from '@/components/PageBody';
-import { getGitBookContextFromHeaders, GitBookContext } from '@/lib/gitbook-context';
import { PageHrefContext, getAbsoluteHref, getPageHref } from '@/lib/links';
import { getPagePath, resolveFirstDocument } from '@/lib/pages';
import { ContentRefContext } from '@/lib/references';
@@ -19,21 +17,17 @@ import { PagePathParams, fetchPageData, getPathnameParam, normalizePathname } fr
export const runtime = 'edge';
export const dynamic = 'force-dynamic';
-type Props = {
- params: Promise;
- searchParams: Promise<{ fallback?: string }>;
-};
-
/**
* Fetch and render a page.
*/
-export default async function Page(props: Props) {
- const [headersList, params, searchParams] = await Promise.all([
- headers(),
- props.params,
- props.searchParams,
- ]);
- const ctx = getGitBookContextFromHeaders(headersList);
+export default async function Page(props: {
+ params: Promise;
+ searchParams: Promise<{ fallback?: string }>;
+}) {
+ const { params: rawParams, searchParams: rawSearchParams } = props;
+
+ const params = await rawParams;
+ const searchParams = await rawSearchParams;
const {
content: contentPointer,
@@ -46,7 +40,7 @@ export default async function Page(props: Props) {
page,
ancestors,
document,
- } = await getPageDataWithFallback(ctx, {
+ } = await getPageDataWithFallback({
pagePathParams: params,
searchParams,
redirectOnFallback: true,
@@ -59,12 +53,12 @@ export default async function Page(props: Props) {
if (pathname !== rawPathname) {
// If the pathname was not normalized, redirect to the normalized version
// before trying to resolve the page again
- redirect(getAbsoluteHref(ctx, pathname));
+ redirect(await getAbsoluteHref(pathname));
} else {
notFound();
}
} else if (getPagePath(pages, page) !== rawPathname) {
- redirect(getPageHref(ctx, pages, page, linksContext));
+ redirect(await getPageHref(pages, page, linksContext));
}
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
@@ -128,10 +122,12 @@ export default async function Page(props: Props) {
);
}
-export async function generateViewport(props: Props): Promise {
- const [params, headersList] = await Promise.all([props.params, headers()]);
- const ctx = getGitBookContextFromHeaders(headersList);
- const { customization } = await fetchPageData(ctx, params);
+export async function generateViewport({
+ params,
+}: {
+ params: Promise;
+}): Promise {
+ const { customization } = await fetchPageData(await params);
return {
colorScheme: customization.themes.toggeable
? customization.themes.default === CustomizationThemeMode.Dark
@@ -141,20 +137,17 @@ export async function generateViewport(props: Props): Promise {
};
}
-export async function generateMetadata(props: Props): Promise {
- const [params, searchParams, headersList] = await Promise.all([
- props.params,
- props.searchParams,
- headers(),
- ]);
- const ctx = getGitBookContextFromHeaders(headersList);
- const { space, pages, page, customization, site, ancestors } = await getPageDataWithFallback(
- ctx,
- {
- pagePathParams: params,
- searchParams: searchParams,
- },
- );
+export async function generateMetadata({
+ params,
+ searchParams,
+}: {
+ params: Promise;
+ searchParams: Promise<{ fallback?: string }>;
+}): Promise {
+ const { space, pages, page, customization, site, ancestors } = await getPageDataWithFallback({
+ pagePathParams: await params,
+ searchParams: await searchParams,
+ });
if (!page) {
notFound();
@@ -167,16 +160,17 @@ export async function generateMetadata(props: Props): Promise {
description: page.description ?? '',
alternates: {
// Trim trailing slashes in canonical URL to match the redirect behavior
- canonical: getAbsoluteHref(ctx, getPagePath(pages, page), true).replace(/\/+$/, ''),
+ canonical: (await getAbsoluteHref(getPagePath(pages, page), true)).replace(/\/+$/, ''),
},
openGraph: {
images: [
customization.socialPreview.url ??
- getAbsoluteHref(ctx, `~gitbook/ogimage/${page.id}`, true),
+ (await getAbsoluteHref(`~gitbook/ogimage/${page.id}`, true)),
],
},
robots:
- isSpaceIndexable(ctx, { space, site: site ?? null }) && isPageIndexable(ancestors, page)
+ (await isSpaceIndexable({ space, site: site ?? null })) &&
+ isPageIndexable(ancestors, page)
? 'index, follow'
: 'noindex, nofollow',
};
@@ -185,17 +179,14 @@ export async function generateMetadata(props: Props): Promise {
/**
* Fetches the page data matching the requested pathname and fallback to root page when page is not found.
*/
-async function getPageDataWithFallback(
- ctx: GitBookContext,
- args: {
- pagePathParams: PagePathParams;
- searchParams: { fallback?: string };
- redirectOnFallback?: boolean;
- },
-) {
+async function getPageDataWithFallback(args: {
+ pagePathParams: PagePathParams;
+ searchParams: { fallback?: string };
+ redirectOnFallback?: boolean;
+}) {
const { pagePathParams, searchParams, redirectOnFallback = false } = args;
- const { pages, page: targetPage, ...otherPageData } = await fetchPageData(ctx, pagePathParams);
+ const { pages, page: targetPage, ...otherPageData } = await fetchPageData(pagePathParams);
let page = targetPage;
const canFallback = !!searchParams.fallback;
@@ -203,7 +194,7 @@ async function getPageDataWithFallback(
const rootPage = resolveFirstDocument(pages, []);
if (redirectOnFallback && rootPage?.page) {
- redirect(getPageHref(ctx, pages, rootPage?.page));
+ redirect(await getPageHref(pages, rootPage?.page));
}
page = rootPage?.page;
diff --git a/packages/gitbook/src/app/(site)/(content)/layout.tsx b/packages/gitbook/src/app/(site)/(content)/layout.tsx
index cd7132fc3..9c0d17476 100644
--- a/packages/gitbook/src/app/(site)/(content)/layout.tsx
+++ b/packages/gitbook/src/app/(site)/(content)/layout.tsx
@@ -13,7 +13,6 @@ import { api } from '@/lib/api';
import { assetsDomain } from '@/lib/assets';
import { buildVersion } from '@/lib/build';
import { getContentSecurityPolicyNonce } from '@/lib/csp';
-import { GitBookContext, getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref, getBaseUrl } from '@/lib/links';
import { isSpaceIndexable } from '@/lib/seo';
import { getContentTitle } from '@/lib/utils';
@@ -29,10 +28,9 @@ export const dynamic = 'force-dynamic';
* Layout when rendering the content.
*/
export default async function ContentLayout(props: { children: React.ReactNode }) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { children } = props;
- const nonce = getContentSecurityPolicyNonce(ctx);
+ const nonce = await getContentSecurityPolicyNonce();
const {
content,
space,
@@ -44,9 +42,10 @@ export default async function ContentLayout(props: { children: React.ReactNode }
ancestors,
scripts,
sections,
- } = await fetchContentData(ctx);
+ } = await fetchContentData();
- ReactDOM.preconnect(api(ctx).client.endpoint);
+ const apiCtx = await api();
+ ReactDOM.preconnect(apiCtx.client.endpoint);
if (assetsDomain) {
ReactDOM.preconnect(assetsDomain);
}
@@ -58,7 +57,7 @@ export default async function ContentLayout(props: { children: React.ReactNode }
});
});
- const queryStringTheme = getQueryStringTheme(ctx);
+ const queryStringTheme = await getQueryStringTheme();
return (
@@ -107,8 +106,7 @@ export default async function ContentLayout(props: { children: React.ReactNode }
}
export async function generateViewport(): Promise {
- const ctx = getGitBookContextFromHeaders(await headers());
- const { customization } = await fetchContentData(ctx);
+ const { customization } = await fetchContentData();
return {
colorScheme: customization.themes.toggeable
? customization.themes.default === CustomizationThemeMode.Dark
@@ -119,43 +117,46 @@ export async function generateViewport(): Promise {
}
export async function generateMetadata(): Promise {
- const ctx = getGitBookContextFromHeaders(await headers());
- const { space, site, customization } = await fetchContentData(ctx);
+ const { space, site, customization } = await fetchContentData();
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;
return {
title: getContentTitle(space, customization, site),
generator: `GitBook (${buildVersion()})`,
- metadataBase: new URL(getBaseUrl(ctx)),
+ metadataBase: new URL(await getBaseUrl()),
icons: {
icon: [
{
url:
customIcon?.light ??
- getAbsoluteHref(ctx, '~gitbook/icon?size=small&theme=light', true),
+ (await getAbsoluteHref('~gitbook/icon?size=small&theme=light', true)),
type: 'image/png',
media: '(prefers-color-scheme: light)',
},
{
url:
customIcon?.dark ??
- getAbsoluteHref(ctx, '~gitbook/icon?size=small&theme=dark', true),
+ (await getAbsoluteHref('~gitbook/icon?size=small&theme=dark', true)),
type: 'image/png',
media: '(prefers-color-scheme: dark)',
},
],
},
- robots: isSpaceIndexable(ctx, { space, site }) ? 'index, follow' : 'noindex, nofollow',
+ robots: (await isSpaceIndexable({ space, site })) ? 'index, follow' : 'noindex, nofollow',
};
}
/**
* For preview, the theme can be set via query string (?theme=light).
*/
-function getQueryStringTheme(ctx: GitBookContext) {
- if (!ctx.theme) {
+async function getQueryStringTheme() {
+ const headersList = await headers();
+ const queryStringTheme = headersList.get('x-gitbook-theme');
+ if (!queryStringTheme) {
return null;
}
- return ctx.theme === 'light' ? CustomizationThemeMode.Light : CustomizationThemeMode.Dark;
+ return queryStringTheme === 'light'
+ ? CustomizationThemeMode.Light
+ : CustomizationThemeMode.Dark;
}
diff --git a/packages/gitbook/src/app/(site)/(core)/robots.txt/route.ts b/packages/gitbook/src/app/(site)/(core)/robots.txt/route.ts
index 9db6cf286..8035d6fcb 100644
--- a/packages/gitbook/src/app/(site)/(core)/robots.txt/route.ts
+++ b/packages/gitbook/src/app/(site)/(core)/robots.txt/route.ts
@@ -1,7 +1,6 @@
import { NextRequest } from 'next/server';
import { getSpace, getSite } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref } from '@/lib/links';
import { getSiteContentPointer } from '@/lib/pointer';
import { isSpaceIndexable } from '@/lib/seo';
@@ -12,18 +11,17 @@ export const runtime = 'edge';
* Generate a robots.txt for the current space.
*/
export async function GET(req: NextRequest) {
- const ctx = getGitBookContextFromHeaders(req.headers);
- const pointer = getSiteContentPointer(ctx);
+ const pointer = await getSiteContentPointer();
const [site, space] = await Promise.all([
- getSite(ctx, pointer.organizationId, pointer.siteId),
- getSpace(ctx, pointer.spaceId, pointer.siteShareKey),
+ getSite(pointer.organizationId, pointer.siteId),
+ getSpace(pointer.spaceId, pointer.siteShareKey),
]);
const lines = [
`User-agent: *`,
'Disallow: /~gitbook/',
- ...(isSpaceIndexable(ctx, { space, site })
- ? [`Allow: /`, `Sitemap: ${getAbsoluteHref(ctx, `/sitemap.xml`, true)}`]
+ ...((await isSpaceIndexable({ space, site }))
+ ? [`Allow: /`, `Sitemap: ${await getAbsoluteHref(`/sitemap.xml`, true)}`]
: [`Disallow: /`]),
];
const content = lines.join('\n');
diff --git a/packages/gitbook/src/app/(site)/(core)/sitemap.xml/route.ts b/packages/gitbook/src/app/(site)/(core)/sitemap.xml/route.ts
index fc211e3a4..23fa77de6 100644
--- a/packages/gitbook/src/app/(site)/(core)/sitemap.xml/route.ts
+++ b/packages/gitbook/src/app/(site)/(core)/sitemap.xml/route.ts
@@ -3,7 +3,6 @@ import jsontoxml from 'jsontoxml';
import { NextRequest } from 'next/server';
import { getSpaceContentData } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref } from '@/lib/links';
import { getPagePath } from '@/lib/pages';
import { getSiteContentPointer } from '@/lib/pointer';
@@ -15,32 +14,33 @@ export const runtime = 'edge';
* Generate a sitemap.xml for the current space.
*/
export async function GET(req: NextRequest) {
- const ctx = getGitBookContextFromHeaders(req.headers);
- const pointer = getSiteContentPointer(ctx);
- const { pages: rootPages } = await getSpaceContentData(ctx, pointer, pointer.siteShareKey);
+ const pointer = await getSiteContentPointer();
+ const { pages: rootPages } = await getSpaceContentData(pointer, pointer.siteShareKey);
const pages = flattenPages(rootPages, (page) => !page.hidden && isPageIndexable([], page));
- const urls = pages.map(({ page, depth }) => {
- // Decay priority with depth
- const priority = Math.pow(2, -0.25 * depth);
- // Normalize to keep 2 decimals
- const normalizedPriority = Math.floor(100 * priority) / 100;
+ const urls = await Promise.all(
+ pages.map(async ({ page, depth }) => {
+ // Decay priority with depth
+ const priority = Math.pow(2, -0.25 * depth);
+ // Normalize to keep 2 decimals
+ const normalizedPriority = Math.floor(100 * priority) / 100;
- const lastModified = page.updatedAt || page.createdAt;
+ const lastModified = page.updatedAt || page.createdAt;
- return {
- url: {
- loc: getAbsoluteHref(ctx, getPagePath(rootPages, page), true),
- priority: normalizedPriority,
- ...(lastModified
- ? {
- // lastmod format is YYYY-MM-DD
- lastmod: new Date(lastModified).toISOString().split('T')[0],
- }
- : {}),
- },
- };
- });
+ return {
+ url: {
+ loc: await getAbsoluteHref(getPagePath(rootPages, page), true),
+ priority: normalizedPriority,
+ ...(lastModified
+ ? {
+ // lastmod format is YYYY-MM-DD
+ lastmod: new Date(lastModified).toISOString().split('T')[0],
+ }
+ : {}),
+ },
+ };
+ }),
+ );
const xml = jsontoxml(
[
diff --git a/packages/gitbook/src/app/(site)/(core)/~gitbook/icon/route.tsx b/packages/gitbook/src/app/(site)/(core)/~gitbook/icon/route.tsx
index ea8f720a5..1640266f6 100644
--- a/packages/gitbook/src/app/(site)/(core)/~gitbook/icon/route.tsx
+++ b/packages/gitbook/src/app/(site)/(core)/~gitbook/icon/route.tsx
@@ -5,7 +5,6 @@ import React from 'react';
import { getSite, getSiteData, getSpace } from '@/lib/api';
import { getEmojiForCode } from '@/lib/emojis';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getSiteContentPointer } from '@/lib/pointer';
import { tcls } from '@/lib/tailwind';
import { getContentTitle } from '@/lib/utils';
@@ -33,18 +32,17 @@ const SIZES = {
* Render an icon for the space.
*/
export async function GET(req: NextRequest) {
- const ctx = getGitBookContextFromHeaders(req.headers);
const options = getOptions(req.url);
const size = SIZES[options.size];
- const pointer = getSiteContentPointer(ctx);
+ const pointer = await getSiteContentPointer();
const spaceId = pointer.spaceId;
const [space, { customization }] = await Promise.all([
- getSpace(ctx, spaceId, pointer.siteShareKey),
- getSiteData(ctx, pointer),
+ getSpace(spaceId, pointer.siteShareKey),
+ getSiteData(pointer),
]);
- const site = await getSite(ctx, pointer.organizationId, pointer.siteId);
+ const site = await getSite(pointer.organizationId, pointer.siteId);
const contentTitle = getContentTitle(space, customization, site);
return new ImageResponse(
diff --git a/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx b/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx
index 447a8bd2a..0ea76602e 100644
--- a/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx
+++ b/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx
@@ -6,7 +6,6 @@ import colorContrast from 'postcss-color-contrast/js';
import React from 'react';
import { googleFontsMap } from '@/fonts';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref } from '@/lib/links';
import { filterOutNullable } from '@/lib/typescript';
import { getContentTitle } from '@/lib/utils';
@@ -57,8 +56,7 @@ async function loadGoogleFont(input: { fontFamily: string; text: string; weight:
* Render the OpenGraph image for a space.
*/
export async function GET(req: NextRequest, { params }: { params: Promise }) {
- const ctx = getGitBookContextFromHeaders(req.headers);
- const { space, page, customization, site } = await fetchPageData(ctx, await params);
+ const { space, page, customization, site } = await fetchPageData(await params);
// If user configured a custom social preview, we redirect to it.
if (customization.socialPreview.url) {
@@ -106,8 +104,10 @@ export async function GET(req: NextRequest, { params }: { params: Promise {
+ if ('icon' in customization.favicon)
+ return (
+
+ );
+ if ('emoji' in customization.favicon)
+ return (
+
+ {String.fromCodePoint(parseInt('0x' + customization.favicon.emoji))}
+
+ );
+ const src = await getAbsoluteHref(
+ `~gitbook/icon?size=medium&theme=${customization.themes.default}`,
+ true,
+ );
+ return
;
+ })();
+
return new ImageResponse(
(
) : (
- {(() => {
- if ('icon' in customization.favicon)
- return (
-

- );
- if ('emoji' in customization.favicon)
- return (
-
- {String.fromCodePoint(
- parseInt('0x' + customization.favicon.emoji),
- )}
-
- );
- const src = getAbsoluteHref(
- ctx,
- `~gitbook/icon?size=medium&theme=${customization.themes.default}`,
- true,
- );
- return

;
- })()}
+ {favicon}
{contentTitle}
)}
diff --git a/packages/gitbook/src/app/(site)/fetch.ts b/packages/gitbook/src/app/(site)/fetch.ts
index 4448a5bd9..9cebb3c6c 100644
--- a/packages/gitbook/src/app/(site)/fetch.ts
+++ b/packages/gitbook/src/app/(site)/fetch.ts
@@ -8,7 +8,6 @@ import {
getSiteData,
getSiteRedirectBySource,
} from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { resolvePagePath, resolvePageId } from '@/lib/pages';
import { getSiteContentPointer } from '@/lib/pointer';
@@ -23,13 +22,13 @@ export interface PageIdParams {
/**
* Fetch all the data needed to render the content layout.
*/
-export async function fetchContentData(ctx: GitBookContext) {
- const content = getSiteContentPointer(ctx);
+export async function fetchContentData() {
+ const content = await getSiteContentPointer();
const [{ space, contentTarget, pages }, { customization, site, sections, spaces, scripts }] =
await Promise.all([
- getSpaceContentData(ctx, content, content.siteShareKey),
- getSiteData(ctx, content),
+ getSpaceContentData(content, content.siteShareKey),
+ getSiteData(content),
]);
// we grab the space attached to the parent as it contains overriden customizations
@@ -54,10 +53,10 @@ export async function fetchContentData(ctx: GitBookContext) {
* Fetch all the data needed to render the content.
* Optimized to fetch in parallel as much as possible.
*/
-export async function fetchPageData(ctx: GitBookContext, params: PagePathParams | PageIdParams) {
- const contentData = await fetchContentData(ctx);
+export async function fetchPageData(params: PagePathParams | PageIdParams) {
+ const contentData = await fetchContentData();
- const page = await resolvePage(ctx, {
+ const page = await resolvePage({
organizationId: contentData.space.organization,
siteId: contentData.site.id,
spaceId: contentData.contentTarget.spaceId,
@@ -67,7 +66,7 @@ export async function fetchPageData(ctx: GitBookContext, params: PagePathParams
params,
});
const document = page?.page.documentId
- ? await getDocument(ctx, contentData.space.id, page.page.documentId)
+ ? await getDocument(contentData.space.id, page.page.documentId)
: null;
return {
@@ -81,18 +80,15 @@ export async function fetchPageData(ctx: GitBookContext, params: PagePathParams
* Resolve a page from the params.
* If the path can't be found, we try to resolve it from the API to handle redirects.
*/
-async function resolvePage(
- ctx: GitBookContext,
- input: {
- organizationId: string;
- siteId: string;
- spaceId: string;
- revisionId: string;
- shareKey: string | undefined;
- pages: RevisionPage[];
- params: PagePathParams | PageIdParams;
- },
-) {
+async function resolvePage(input: {
+ organizationId: string;
+ siteId: string;
+ spaceId: string;
+ revisionId: string;
+ shareKey: string | undefined;
+ pages: RevisionPage[];
+ params: PagePathParams | PageIdParams;
+}) {
const { organizationId, siteId, spaceId, revisionId, pages, shareKey, params } = input;
if ('pageId' in params) {
@@ -113,13 +109,13 @@ async function resolvePage(
// If page can't be found, we try with the API, in case we have a redirect at space level.
// We use the raw pathname to handle special/malformed redirects setup by users in the GitSync.
// The page rendering will take care of redirecting to a normalized pathname.
- const resolved = await getRevisionPageByPath(ctx, spaceId, revisionId, rawPathname);
+ const resolved = await getRevisionPageByPath(spaceId, revisionId, rawPathname);
if (resolved) {
return resolvePageId(pages, resolved.id);
}
// If a page still can't be found, we try with the API, in case we have a redirect at site level.
- const resolvedSiteRedirect = await getSiteRedirectBySource(ctx, {
+ const resolvedSiteRedirect = await getSiteRedirectBySource({
organizationId,
siteId,
source: rawPathname.startsWith('/') ? rawPathname : `/${rawPathname}`,
diff --git a/packages/gitbook/src/app/(site)/layout.tsx b/packages/gitbook/src/app/(site)/layout.tsx
index 9aa2a62a6..431c97b7d 100644
--- a/packages/gitbook/src/app/(site)/layout.tsx
+++ b/packages/gitbook/src/app/(site)/layout.tsx
@@ -1,8 +1,5 @@
-import { headers } from 'next/headers';
-
import { CustomizationRootLayout } from '@/components/RootLayout';
import { getSiteData } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getSiteContentPointer } from '@/lib/pointer';
/**
@@ -11,10 +8,9 @@ import { getSiteContentPointer } from '@/lib/pointer';
*/
export default async function SiteRootLayout(props: { children: React.ReactNode }) {
const { children } = props;
- const ctx = getGitBookContextFromHeaders(await headers());
- const pointer = getSiteContentPointer(ctx);
- const { customization } = await getSiteData(ctx, pointer);
+ const pointer = await getSiteContentPointer();
+ const { customization } = await getSiteData(pointer);
return (
{children}
diff --git a/packages/gitbook/src/app/(space)/~gitbook/pdf/layout.tsx b/packages/gitbook/src/app/(space)/~gitbook/pdf/layout.tsx
index 0a2d6e73f..3ce76b104 100644
--- a/packages/gitbook/src/app/(space)/~gitbook/pdf/layout.tsx
+++ b/packages/gitbook/src/app/(space)/~gitbook/pdf/layout.tsx
@@ -1,9 +1,7 @@
import { SpaceIntegrationScript } from '@gitbook/api';
-import { headers } from 'next/headers';
import { CustomizationRootLayout } from '@/components/RootLayout';
import { getSiteData, getSpaceCustomization } from '@/lib/api';
-import { getGitBookContextFromHeaders, GitBookContext } from '@/lib/gitbook-context';
import { getSiteOrSpacePointerForPDF } from './pointer';
@@ -12,13 +10,12 @@ import { getSiteOrSpacePointerForPDF } from './pointer';
* site or space and initializes the CustomizationRootLayout with it.
*/
export default async function PDFRootLayout(props: { children: React.ReactNode }) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { children } = props;
- const pointer = getSiteOrSpacePointerForPDF(ctx);
+ const pointer = await getSiteOrSpacePointerForPDF();
const { customization } = await ('siteId' in pointer
- ? getSiteData(ctx, pointer)
- : getSpaceLayoutData(ctx));
+ ? getSiteData(pointer)
+ : getSpaceLayoutData());
return (
{children}
@@ -28,11 +25,14 @@ export default async function PDFRootLayout(props: { children: React.ReactNode }
/**
* Fetch all the layout data about a space at once.
*/
-async function getSpaceLayoutData(ctx: GitBookContext) {
- const { customization } = await getSpaceCustomization(ctx);
+async function getSpaceLayoutData() {
+ const [{ customization }, scripts] = await Promise.all([
+ getSpaceCustomization(),
+ [] as SpaceIntegrationScript[],
+ ]);
return {
customization,
- scripts: [] as SpaceIntegrationScript[],
+ scripts,
};
}
diff --git a/packages/gitbook/src/app/(space)/~gitbook/pdf/page.tsx b/packages/gitbook/src/app/(space)/~gitbook/pdf/page.tsx
index ec9328490..d13201d53 100644
--- a/packages/gitbook/src/app/(space)/~gitbook/pdf/page.tsx
+++ b/packages/gitbook/src/app/(space)/~gitbook/pdf/page.tsx
@@ -8,7 +8,6 @@ import {
} from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { Metadata } from 'next';
-import { headers } from 'next/headers';
import { notFound } from 'next/navigation';
import * as React from 'react';
@@ -24,7 +23,6 @@ import {
getSpaceContentData,
getSiteData,
} from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getPagePDFContainerId, PageHrefContext, getAbsoluteHref } from '@/lib/links';
import { resolvePageId } from '@/lib/pages';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
@@ -42,11 +40,10 @@ export const runtime = 'edge';
export const dynamic = 'force-dynamic';
export async function generateMetadata(): Promise {
- const ctx = getGitBookContextFromHeaders(await headers());
- const pointer = getSiteOrSpacePointerForPDF(ctx);
+ const pointer = await getSiteOrSpacePointerForPDF();
const [space, { customization }] = await Promise.all([
- getSpace(ctx, pointer.spaceId, 'siteId' in pointer ? pointer.siteShareKey : undefined),
- 'siteId' in pointer ? getSiteData(ctx, pointer) : getSpaceCustomization(ctx),
+ getSpace(pointer.spaceId, 'siteId' in pointer ? pointer.siteShareKey : undefined),
+ 'siteId' in pointer ? getSiteData(pointer) : getSpaceCustomization(),
]);
return {
@@ -61,20 +58,19 @@ export async function generateMetadata(): Promise {
export default async function PDFHTMLOutput(props: {
searchParams: Promise<{ [key: string]: string }>;
}) {
- const ctx = getGitBookContextFromHeaders(await headers());
- const pointer = getSiteOrSpacePointerForPDF(ctx);
+ const pointer = await getSiteOrSpacePointerForPDF();
const searchParams = new URLSearchParams(await props.searchParams);
const pdfParams = getPDFSearchParams(new URLSearchParams(searchParams));
// Build current PDF URL and preserve all search params
- let currentPDFUrl = getAbsoluteHref(ctx, '~gitbook/pdf', true);
+ let currentPDFUrl = await getAbsoluteHref('~gitbook/pdf', true);
currentPDFUrl += '?' + searchParams.toString();
// Load the content,
const [{ customization }, { space, contentTarget, pages: rootPages }] = await Promise.all([
- 'siteId' in pointer ? getSiteData(ctx, pointer) : getSpaceCustomization(ctx),
- getSpaceContentData(ctx, pointer, 'siteId' in pointer ? pointer.siteShareKey : undefined),
+ 'siteId' in pointer ? getSiteData(pointer) : getSpaceCustomization(),
+ getSpaceContentData(pointer, 'siteId' in pointer ? pointer.siteShareKey : undefined),
]);
const language = getSpaceLanguage(customization);
@@ -93,7 +89,7 @@ export default async function PDFHTMLOutput(props: {
@@ -255,7 +250,7 @@ async function PDFPageDocument(props: {
revisionId: refContext.revisionId,
},
contentRefContext: refContext,
- resolveContentRef: (ref) => resolveContentRef(ctx, ref, refContext),
+ resolveContentRef: (ref) => resolveContentRef(ref, refContext),
getId: (id) => getPagePDFContainerId(page, id),
}}
/>
diff --git a/packages/gitbook/src/app/(space)/~gitbook/pdf/pointer.ts b/packages/gitbook/src/app/(space)/~gitbook/pdf/pointer.ts
index 926a9ff51..c1d28c328 100644
--- a/packages/gitbook/src/app/(space)/~gitbook/pdf/pointer.ts
+++ b/packages/gitbook/src/app/(space)/~gitbook/pdf/pointer.ts
@@ -1,5 +1,4 @@
import { SiteContentPointer, SpaceContentPointer } from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { getSiteContentPointer, getSpacePointer } from '@/lib/pointer';
/**
@@ -9,12 +8,12 @@ import { getSiteContentPointer, getSpacePointer } from '@/lib/pointer';
*
* This function returns the pointer depending on the context.
*/
-export function getSiteOrSpacePointerForPDF(
- ctx: GitBookContext,
-): SiteContentPointer | SpaceContentPointer {
+export async function getSiteOrSpacePointerForPDF(): Promise<
+ SiteContentPointer | SpaceContentPointer
+> {
try {
- return getSiteContentPointer(ctx);
+ return await getSiteContentPointer();
} catch (error) {
- return getSpacePointer(ctx);
+ return getSpacePointer();
}
}
diff --git a/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx b/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx
index ebff66775..dda6343a6 100644
--- a/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx
+++ b/packages/gitbook/src/components/AdminToolbar/AdminToolbar.tsx
@@ -1,10 +1,8 @@
import { Space } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
-import { headers } from 'next/headers';
import React from 'react';
import { getChangeRequest, getRevision, SiteContentPointer } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { tcls } from '@/lib/tailwind';
import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
@@ -70,9 +68,8 @@ export function AdminToolbar(props: AdminToolbarProps) {
async function ChangeRequestToolbar(props: { spaceId: string; changeRequestId: string }) {
const { spaceId, changeRequestId } = props;
- const ctx = getGitBookContextFromHeaders(await headers());
- const changeRequest = await getChangeRequest(ctx, spaceId, changeRequestId);
+ const changeRequest = await getChangeRequest(spaceId, changeRequestId);
return (
@@ -92,7 +89,6 @@ async function ChangeRequestToolbar(props: { spaceId: string; changeRequestId: s
{
const result = showPlaceholderAd
- ? await renderAd({ source: 'placeholder', ipAndUserAgent })
+ ? await renderAd({ source: 'placeholder' })
: realZoneId
? await renderAd({
placement,
@@ -104,7 +100,6 @@ export function Ad({
zoneId: realZoneId,
mode,
source: 'live',
- ipAndUserAgent,
})
: undefined;
@@ -120,7 +115,7 @@ export function Ad({
return () => {
cancelled = true;
};
- }, [visible, zoneId, ignore, placement, mode, siteAdsStatus, ipAndUserAgent]);
+ }, [visible, zoneId, ignore, placement, mode, siteAdsStatus]);
return (
diff --git a/packages/gitbook/src/components/Ads/AdClassicRendering.tsx b/packages/gitbook/src/components/Ads/AdClassicRendering.tsx
index 3d42ac604..dc2859a5b 100644
--- a/packages/gitbook/src/components/Ads/AdClassicRendering.tsx
+++ b/packages/gitbook/src/components/Ads/AdClassicRendering.tsx
@@ -1,7 +1,5 @@
-import { headers } from 'next/headers';
import * as React from 'react';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getResizedImageURL } from '@/lib/images';
import { tcls } from '@/lib/tailwind';
@@ -11,11 +9,10 @@ import { AdItem } from './types';
* Classic rendering for an ad.
*/
export async function AdClassicRendering({ ad }: { ad: AdItem }) {
- const ctx = getGitBookContextFromHeaders(await headers());
const smallImgSrc =
- 'smallImage' in ad ? getResizedImageURL(ctx, ad.smallImage, { width: 192, dpr: 2 }) : null;
+ 'smallImage' in ad ? await getResizedImageURL(ad.smallImage, { width: 192, dpr: 2 }) : null;
const logoSrc =
- 'logo' in ad ? getResizedImageURL(ctx, ad.logo, { width: 192 - 48, dpr: 2 }) : null;
+ 'logo' in ad ? await getResizedImageURL(ad.logo, { width: 192 - 48, dpr: 2 }) : null;
return (
{
- const { ip, userAgent } = ipAndUserAgent;
+ const { ip, userAgent } = await getUserAgentAndIp();
+
const url = new URL(`https://srv.buysellads.com/ads/${zoneId}.json`);
url.searchParams.set('segment', `placement:${placement}`);
url.searchParams.set('v', 'true');
@@ -91,7 +86,9 @@ async function fetchAd({
return null;
}
-function getPlaceholderAd(options: FetchPlaceholderAdOptions): { ad: AdItem; ip: string } {
+async function getPlaceholderAd(): Promise<{ ad: AdItem; ip: string }> {
+ const { ip } = await getUserAgentAndIp();
+
return {
ad: {
active: '1',
@@ -118,6 +115,20 @@ function getPlaceholderAd(options: FetchPlaceholderAdOptions): { ad: AdItem; ip:
zoneid: '',
zonekey: '',
},
- ip: options.ipAndUserAgent.ip,
+ ip,
};
}
+
+async function getUserAgentAndIp() {
+ const headersSet = await headers();
+ const ip =
+ headersSet.get('x-gitbook-ipv4') ??
+ headersSet.get('x-gitbook-ip') ??
+ headersSet.get('cf-pseudo-ipv4') ??
+ headersSet.get('cf-connecting-ip') ??
+ headersSet.get('x-forwarded-for') ??
+ '';
+ const userAgent = headersSet.get('user-agent') ?? '';
+
+ return { ip, userAgent };
+}
diff --git a/packages/gitbook/src/components/AutoRefreshContent/server-actions.ts b/packages/gitbook/src/components/AutoRefreshContent/server-actions.ts
index afc574e28..9888af8fc 100644
--- a/packages/gitbook/src/components/AutoRefreshContent/server-actions.ts
+++ b/packages/gitbook/src/components/AutoRefreshContent/server-actions.ts
@@ -1,23 +1,15 @@
'use server';
import { getChangeRequest } from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
/**
* Return true if a change-request has been updated.
*/
-export async function hasContentBeenUpdated(
- ctx: GitBookContext,
- props: {
- spaceId: string;
- changeRequestId: string;
- revisionId: string;
- },
-) {
- const changeRequest = await getChangeRequest.revalidate(
- ctx,
- props.spaceId,
- props.changeRequestId,
- );
+export async function hasContentBeenUpdated(props: {
+ spaceId: string;
+ changeRequestId: string;
+ revisionId: string;
+}) {
+ const changeRequest = await getChangeRequest.revalidate(props.spaceId, props.changeRequestId);
return changeRequest.revision !== props.revisionId;
}
diff --git a/packages/gitbook/src/components/AutoRefreshContent/useCheckForContentUpdate.ts b/packages/gitbook/src/components/AutoRefreshContent/useCheckForContentUpdate.ts
index 9ad2ddc95..6048b3a15 100644
--- a/packages/gitbook/src/components/AutoRefreshContent/useCheckForContentUpdate.ts
+++ b/packages/gitbook/src/components/AutoRefreshContent/useCheckForContentUpdate.ts
@@ -1,9 +1,6 @@
'use client';
import React from 'react';
-import { useEventCallback } from 'usehooks-ts';
-
-import { GitBookContext } from '@/lib/gitbook-context';
import { hasContentBeenUpdated } from './server-actions';
@@ -11,23 +8,17 @@ import { hasContentBeenUpdated } from './server-actions';
* Return a callback to check if a change request has been updated and to refresh the page if it has.
*/
export function useCheckForContentUpdate(props: {
- ctx: GitBookContext;
spaceId: string;
changeRequestId: string;
revisionId: string;
}) {
- const { ctx, spaceId, changeRequestId, revisionId } = props;
- const getCtx = useEventCallback(() => ctx);
+ const { spaceId, changeRequestId, revisionId } = props;
return React.useCallback(async () => {
- const updated = await hasContentBeenUpdated(getCtx(), {
- spaceId,
- changeRequestId,
- revisionId,
- });
+ const updated = await hasContentBeenUpdated({ spaceId, changeRequestId, revisionId });
if (updated) {
window.location.reload();
}
- }, [spaceId, changeRequestId, revisionId, getCtx]);
+ }, [spaceId, changeRequestId, revisionId]);
}
diff --git a/packages/gitbook/src/components/DocumentView/BlockContentRef.tsx b/packages/gitbook/src/components/DocumentView/BlockContentRef.tsx
index c96d89e2e..b98997efe 100644
--- a/packages/gitbook/src/components/DocumentView/BlockContentRef.tsx
+++ b/packages/gitbook/src/components/DocumentView/BlockContentRef.tsx
@@ -1,9 +1,7 @@
import { DocumentBlockContentRef } from '@gitbook/api';
-import { headers } from 'next/headers';
import { Card } from '@/components/primitives';
import { getSpaceCustomization, ignoreAPIError } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { ResolvedContentRef } from '@/lib/references';
import { BlockProps } from './Block';
@@ -54,9 +52,7 @@ async function SpaceRefCard(
return null;
}
- const ctx = getGitBookContextFromHeaders(await headers());
-
- const { customization: spaceCustomization } = await getSpaceCustomization(ctx);
+ const { customization: spaceCustomization } = await getSpaceCustomization();
const customFavicon = spaceCustomization?.favicon;
const customEmoji = customFavicon && 'emoji' in customFavicon ? customFavicon.emoji : undefined;
const customIcon = customFavicon && 'icon' in customFavicon ? customFavicon.icon : undefined;
diff --git a/packages/gitbook/src/components/DocumentView/Embed.tsx b/packages/gitbook/src/components/DocumentView/Embed.tsx
index dade807c9..a44d9d220 100644
--- a/packages/gitbook/src/components/DocumentView/Embed.tsx
+++ b/packages/gitbook/src/components/DocumentView/Embed.tsx
@@ -5,7 +5,6 @@ import ReactDOM from 'react-dom';
import { Card } from '@/components/primitives';
import { getEmbedByUrlInSpace, getEmbedByUrl } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { tcls } from '@/lib/tailwind';
import { BlockProps } from './Block';
@@ -13,15 +12,15 @@ import { Caption } from './Caption';
import { IntegrationBlock } from './Integration';
export async function Embed(props: BlockProps) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { block, context, ...otherProps } = props;
- const nonce = ctx.nonce || undefined;
+ const headersList = await headers();
+ const nonce = headersList.get('x-nonce') || undefined;
ReactDOM.preload('https://cdn.iframe.ly/embed.js', { as: 'script', nonce });
const embed = await (context.content
- ? getEmbedByUrlInSpace(ctx, context.content.spaceId, block.data.url)
- : getEmbedByUrl(ctx, block.data.url));
+ ? getEmbedByUrlInSpace(context.content.spaceId, block.data.url)
+ : getEmbedByUrl(block.data.url));
return (
diff --git a/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx b/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx
index b9843035b..72ede9d59 100644
--- a/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx
+++ b/packages/gitbook/src/components/DocumentView/Integration/IntegrationBlock.tsx
@@ -1,11 +1,9 @@
import { ContentKitContext, DocumentBlockIntegration } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { ContentKit, ContentKitOutput, ContentKitServerContext } from '@gitbook/react-contentkit';
-import { headers } from 'next/headers';
import { ignoreAPIError, renderIntegrationUi } from '@/lib/api';
import { INTEGRATIONS_HOST } from '@/lib/csp';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { parseMarkdown } from '@/lib/markdown';
import { tcls } from '@/lib/tailwind';
@@ -47,8 +45,6 @@ export async function IntegrationBlock(props: BlockProps {
'use server';
- const output = await renderIntegrationUi(ctx, block.data.integration, request);
+ const output = await renderIntegrationUi(block.data.integration, request);
return {
children: ,
diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx
index 61ad0f210..e8ed31312 100644
--- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx
+++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx
@@ -1,14 +1,11 @@
import { DocumentBlockReusableContent } from '@gitbook/api';
-import { headers } from 'next/headers';
import { getDocument } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { BlockProps } from './Block';
import { UnwrappedBlocks } from './Blocks';
export async function ReusableContent(props: BlockProps) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { block, context, ancestorBlocks } = props;
if (!context.content) {
@@ -20,11 +17,7 @@ export async function ReusableContent(props: BlockProps
diff --git a/packages/gitbook/src/components/Header/HeaderLogo.tsx b/packages/gitbook/src/components/Header/HeaderLogo.tsx
index 6262b4033..18c1201ee 100644
--- a/packages/gitbook/src/components/Header/HeaderLogo.tsx
+++ b/packages/gitbook/src/components/Header/HeaderLogo.tsx
@@ -5,10 +5,8 @@ import {
SiteCustomizationSettings,
Space,
} from '@gitbook/api';
-import { headers } from 'next/headers';
import { Image } from '@/components/utils';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref } from '@/lib/links';
import { tcls } from '@/lib/tailwind';
import { getContentTitle } from '@/lib/utils';
@@ -27,9 +25,8 @@ interface HeaderLogoProps {
*/
export async function HeaderLogo(props: HeaderLogoProps) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { customization } = props;
- const href = getAbsoluteHref(ctx, '');
+ const href = await getAbsoluteHref('');
return (
{withPageFeedback ? (
-
+
) : null}
{customization.git.showEditLink && space.gitSync?.url && page.git ? (
@@ -229,7 +223,6 @@ export async function PageAside(props: {
- resolveContentRef(ctx, ref, context),
- );
+ const sections = await getDocumentSections(document, (ref) => resolveContentRef(ref, context));
return sections.length > 1 ? : null;
}
diff --git a/packages/gitbook/src/components/PageBody/PageBody.tsx b/packages/gitbook/src/components/PageBody/PageBody.tsx
index aab553a19..699c018a2 100644
--- a/packages/gitbook/src/components/PageBody/PageBody.tsx
+++ b/packages/gitbook/src/components/PageBody/PageBody.tsx
@@ -5,14 +5,12 @@ import {
SiteCustomizationSettings,
Space,
} from '@gitbook/api';
-import { headers } from 'next/headers';
import React from 'react';
import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate';
-import { ContentTarget, SiteContentPointer } from '@/lib/api';
+import { ContentTarget, SiteContentPointer, api } from '@/lib/api';
import { hasFullWidthBlock, isNodeEmpty } from '@/lib/document';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { AncestorRevisionPage } from '@/lib/pages';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -27,7 +25,7 @@ import { TrackPageViewEvent } from '../Insights';
import { PageFeedbackForm } from '../PageFeedback';
import { DateRelative } from '../primitives';
-export async function PageBody(props: {
+export function PageBody(props: {
space: Space;
pointer: SiteContentPointer;
contentTarget: ContentTarget;
@@ -38,7 +36,6 @@ export async function PageBody(props: {
context: ContentRefContext;
withPageFeedback: boolean;
}) {
- const ctx = getGitBookContextFromHeaders(await headers());
const {
space,
contentTarget,
@@ -102,7 +99,7 @@ export async function PageBody(props: {
content: contentTarget,
contentRefContext: context,
resolveContentRef: (ref, options) =>
- resolveContentRef(ctx, ref, context, options),
+ resolveContentRef(ref, context, options),
}}
/>
@@ -143,7 +140,7 @@ export async function PageBody(props: {
) : null}
{withPageFeedback ? (
-
+
) : null}
diff --git a/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx b/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx
index 581e624b7..40f8f27dc 100644
--- a/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx
+++ b/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx
@@ -1,8 +1,6 @@
import { RevisionPage, RevisionPageDocument, RevisionPageType } from '@gitbook/api';
-import { headers } from 'next/headers';
import { Card } from '@/components/primitives';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getPageHref } from '@/lib/links';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -17,7 +15,6 @@ export async function PageBodyBlankslate(props: {
rootPages: RevisionPage[];
context: ContentRefContext;
}) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { page, rootPages, context } = props;
const pages = page.pages.filter((child) =>
@@ -38,7 +35,7 @@ export async function PageBodyBlankslate(props: {
'Unexpected computed page, it should have been computed in the API',
);
} else if (child.type === RevisionPageType.Link) {
- const resolved = await resolveContentRef(ctx, child.target, context);
+ const resolved = await resolveContentRef(child.target, context);
if (!resolved) {
return null;
}
@@ -56,7 +53,7 @@ export async function PageBodyBlankslate(props: {
/>
);
} else {
- const href = getPageHref(ctx, rootPages, child);
+ const href = await getPageHref(rootPages, child);
return ;
}
}),
diff --git a/packages/gitbook/src/components/PageBody/PageCover.tsx b/packages/gitbook/src/components/PageBody/PageCover.tsx
index 3b7f41632..0047cdf4f 100644
--- a/packages/gitbook/src/components/PageBody/PageCover.tsx
+++ b/packages/gitbook/src/components/PageBody/PageCover.tsx
@@ -1,8 +1,6 @@
import { RevisionPageDocument, RevisionPageDocumentCover } from '@gitbook/api';
-import { headers } from 'next/headers';
import { Image, ImageSize } from '@/components/utils';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -19,9 +17,8 @@ export async function PageCover(props: {
cover: RevisionPageDocumentCover;
context: ContentRefContext;
}) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { as, page, cover, context } = props;
- const resolved = cover.ref ? await resolveContentRef(ctx, cover.ref, context) : null;
+ const resolved = cover.ref ? await resolveContentRef(cover.ref, context) : null;
return (
{
- const href = await getPageHref(ctx, pages, breadcrumb);
+ const href = await getPageHref(pages, breadcrumb);
return (
diff --git a/packages/gitbook/src/components/PageFeedback/PageFeedbackForm.tsx b/packages/gitbook/src/components/PageFeedback/PageFeedbackForm.tsx
index 27b89b851..5571d4e27 100644
--- a/packages/gitbook/src/components/PageFeedback/PageFeedbackForm.tsx
+++ b/packages/gitbook/src/components/PageFeedback/PageFeedbackForm.tsx
@@ -1,12 +1,10 @@
'use client';
import { PageFeedbackRating } from '@gitbook/api';
-import { headers } from 'next/headers';
import React from 'react';
import { useLanguage } from '@/intl/client';
import { t, tString } from '@/intl/translate';
-import { getGitBookContextFromHeaders, GitBookContext } from '@/lib/gitbook-context';
import { tcls } from '@/lib/tailwind';
import { getVisitorId, useTrackEvent } from '../Insights';
@@ -16,12 +14,11 @@ import { postPageFeedback } from './server-actions';
* Form to submit feedback on a page.
*/
export function PageFeedbackForm(props: {
- ctx: GitBookContext;
orientation?: 'horizontal' | 'vertical';
pageId: string;
className?: string;
}) {
- const { ctx, orientation = 'vertical', pageId, className } = props;
+ const { orientation = 'vertical', pageId, className } = props;
const languages = useLanguage();
const trackEvent = useTrackEvent();
const [submitted, setSubmitted] = React.useState(false);
@@ -29,7 +26,7 @@ export function PageFeedbackForm(props: {
const onSubmit = async (rating: PageFeedbackRating) => {
setSubmitted(true);
const visitorId = await getVisitorId();
- await postPageFeedback(ctx, { pageId, visitorId, rating });
+ await postPageFeedback({ pageId, visitorId, rating });
trackEvent({
type: 'page_post_feedback',
diff --git a/packages/gitbook/src/components/PageFeedback/server-actions.ts b/packages/gitbook/src/components/PageFeedback/server-actions.ts
index a86193520..248e03197 100644
--- a/packages/gitbook/src/components/PageFeedback/server-actions.ts
+++ b/packages/gitbook/src/components/PageFeedback/server-actions.ts
@@ -4,25 +4,23 @@ import { PageFeedbackRating } from '@gitbook/api';
import { assert } from 'ts-essentials';
import { api } from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { getSiteContentPointer } from '@/lib/pointer';
-export async function postPageFeedback(
- ctx: GitBookContext,
- args: {
- pageId: string;
- visitorId: string;
- rating: PageFeedbackRating;
- },
-) {
- const { organizationId, siteId, siteSpaceId } = getSiteContentPointer(ctx);
+export async function postPageFeedback(args: {
+ pageId: string;
+ visitorId: string;
+ rating: PageFeedbackRating;
+}) {
+ const { organizationId, siteId, siteSpaceId } = await getSiteContentPointer();
assert(
siteSpaceId,
`No siteSpaceId in pointer. organizationId: ${organizationId}, siteId: ${siteId}, pageId: ${args.pageId}`,
);
- await api(ctx).client.orgs.createSitesPageFeedback(
+ const apiCtx = await api();
+
+ await apiCtx.client.orgs.createSitesPageFeedback(
organizationId,
siteId,
siteSpaceId,
diff --git a/packages/gitbook/src/components/Search/SearchAskAnswer.tsx b/packages/gitbook/src/components/Search/SearchAskAnswer.tsx
index 6334327a5..018575132 100644
--- a/packages/gitbook/src/components/Search/SearchAskAnswer.tsx
+++ b/packages/gitbook/src/components/Search/SearchAskAnswer.tsx
@@ -2,7 +2,6 @@
import { Icon } from '@gitbook/icons';
import React from 'react';
-import { useEventCallback } from 'usehooks-ts';
import { Loading } from '@/components/primitives';
import { useLanguage } from '@/intl/client';
@@ -10,7 +9,6 @@ import { t } from '@/intl/translate';
import { TranslationLanguage } from '@/intl/translations';
import { iterateStreamResponse } from '@/lib/actions';
import { SiteContentPointer } from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { tcls } from '@/lib/tailwind';
import { AskAnswerResult, AskAnswerSource, streamAskQuestion } from './server-actions';
@@ -34,19 +32,14 @@ export type SearchAskState =
/**
* Fetch and render the answers to a question.
*/
-export function SearchAskAnswer(props: {
- ctx: GitBookContext;
- pointer: SiteContentPointer;
- query: string;
-}) {
- const { ctx, pointer, query } = props;
+export function SearchAskAnswer(props: { pointer: SiteContentPointer; query: string }) {
+ const { pointer, query } = props;
const language = useLanguage();
const trackEvent = useTrackEvent();
const [, setSearchState] = useSearch();
const [askState, setAskState] = useSearchAskContext();
const { organizationId, siteId, siteSpaceId } = pointer;
- const getCtx = useEventCallback(() => ctx);
React.useEffect(() => {
let cancelled = false;
@@ -59,13 +52,7 @@ export function SearchAskAnswer(props: {
query,
});
- const response = streamAskQuestion(
- getCtx(),
- organizationId,
- siteId,
- siteSpaceId ?? null,
- query,
- );
+ const response = streamAskQuestion(organizationId, siteId, siteSpaceId ?? null, query);
const stream = iterateStreamResponse(response);
// When we pass in "ask" mode, the query could still be updated by the client
@@ -94,16 +81,7 @@ export function SearchAskAnswer(props: {
cancelled = true;
}
};
- }, [
- organizationId,
- siteId,
- siteSpaceId,
- query,
- setAskState,
- setSearchState,
- trackEvent,
- getCtx,
- ]);
+ }, [organizationId, siteId, siteSpaceId, query, setAskState, setSearchState, trackEvent]);
React.useEffect(() => {
return () => {
diff --git a/packages/gitbook/src/components/Search/SearchModal.tsx b/packages/gitbook/src/components/Search/SearchModal.tsx
index a887e1d83..ca165e731 100644
--- a/packages/gitbook/src/components/Search/SearchModal.tsx
+++ b/packages/gitbook/src/components/Search/SearchModal.tsx
@@ -8,7 +8,6 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { tString, useLanguage } from '@/intl/client';
import { SiteContentPointer } from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { tcls } from '@/lib/tailwind';
import { SearchAskAnswer } from './SearchAskAnswer';
@@ -19,7 +18,6 @@ import { SearchState, UpdateSearchState, useSearch } from './useSearch';
import { LoadingPane } from '../primitives/LoadingPane';
interface SearchModalProps {
- ctx: GitBookContext;
spaceId: string;
revisionId: string;
spaceTitle: string;
@@ -310,7 +308,6 @@ function SearchModalBody(
{!state.ask || !withAsk ? (
) : null}
{state.query && state.ask && withAsk ? (
-
+
) : null}
);
diff --git a/packages/gitbook/src/components/Search/SearchResults.tsx b/packages/gitbook/src/components/Search/SearchResults.tsx
index a96de2c62..2b9bf3640 100644
--- a/packages/gitbook/src/components/Search/SearchResults.tsx
+++ b/packages/gitbook/src/components/Search/SearchResults.tsx
@@ -1,12 +1,11 @@
'use client';
+
import { captureException } from '@sentry/nextjs';
import assertNever from 'assert-never';
import React from 'react';
-import { useEventCallback } from 'usehooks-ts';
import { t, useLanguage } from '@/intl/client';
import { SiteContentPointer } from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { tcls } from '@/lib/tailwind';
import { SearchPageResultItem } from './SearchPageResultItem';
@@ -40,7 +39,6 @@ type ResultType =
*/
export const SearchResults = React.forwardRef(function SearchResults(
props: {
- ctx: GitBookContext;
children?: React.ReactNode;
query: string;
spaceId: string;
@@ -52,8 +50,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
},
ref: React.Ref,
) {
- const { ctx, children, query, pointer, spaceId, revisionId, withAsk, global, onSwitchToAsk } =
- props;
+ const { children, query, pointer, spaceId, revisionId, withAsk, global, onSwitchToAsk } = props;
const language = useLanguage();
const trackEvent = useTrackEvent();
@@ -64,7 +61,6 @@ export const SearchResults = React.forwardRef(function SearchResults(
const [cursor, setCursor] = React.useState(null);
const refs = React.useRef<(null | HTMLAnchorElement)[]>([]);
const suggestedQuestionsRef = React.useRef(null);
- const getCtx = useEventCallback(() => ctx);
React.useEffect(() => {
if (!query) {
@@ -81,7 +77,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
let cancelled = false;
setResultsState({ results: [], fetching: true });
- getRecommendedQuestions(getCtx(), spaceId).then((questions) => {
+ getRecommendedQuestions(spaceId).then((questions) => {
if (!questions) {
if (!cancelled) {
setResultsState({ results: [], fetching: false });
@@ -115,8 +111,8 @@ export const SearchResults = React.forwardRef(function SearchResults(
let cancelled = false;
const timeout = setTimeout(async () => {
const results = await (global
- ? searchAllSiteContent(getCtx(), query, pointer)
- : searchSiteSpaceContent(getCtx(), query, pointer, revisionId));
+ ? searchAllSiteContent(query, pointer)
+ : searchSiteSpaceContent(query, pointer, revisionId));
if (cancelled) {
return;
@@ -146,7 +142,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
clearTimeout(timeout);
};
}
- }, [query, global, pointer, spaceId, revisionId, withAsk, trackEvent, getCtx]);
+ }, [query, global, pointer, spaceId, revisionId, withAsk, trackEvent]);
const results: ResultType[] = React.useMemo(() => {
if (!withAsk) {
diff --git a/packages/gitbook/src/components/Search/server-actions.tsx b/packages/gitbook/src/components/Search/server-actions.tsx
index 148c9d06a..c82d48bf8 100644
--- a/packages/gitbook/src/components/Search/server-actions.tsx
+++ b/packages/gitbook/src/components/Search/server-actions.tsx
@@ -7,7 +7,6 @@ import { assert } from 'ts-essentials';
import { streamResponse } from '@/lib/actions';
import * as api from '@/lib/api';
-import { GitBookContext } from '@/lib/gitbook-context';
import { getAbsoluteHref, getPageHref } from '@/lib/links';
import { resolvePageId } from '@/lib/pages';
import { filterOutNullable } from '@/lib/typescript';
@@ -50,18 +49,15 @@ export interface AskAnswerResult {
/**
* Search for content in a site by scoping the search to all content, a specific spaces or current space.
*/
-async function searchSiteContent(
- ctx: GitBookContext,
- args: {
- pointer: api.SiteContentPointer;
- query: string;
- scope:
- | { mode: 'all' }
- | { mode: 'current'; siteSpaceId: string }
- | { mode: 'specific'; siteSpaceIds: string[] };
- cacheBust?: string;
- },
-): Promise {
+async function searchSiteContent(args: {
+ pointer: api.SiteContentPointer;
+ query: string;
+ scope:
+ | { mode: 'all' }
+ | { mode: 'current'; siteSpaceId: string }
+ | { mode: 'specific'; siteSpaceIds: string[] };
+ cacheBust?: string;
+}): Promise {
const { pointer, scope, query, cacheBust } = args;
if (query.length <= 1) {
@@ -74,8 +70,8 @@ async function searchSiteContent(
(scope.mode === 'specific' && scope.siteSpaceIds.length > 1);
const [searchResults, siteData] = await Promise.all([
- api.searchSiteContent(ctx, pointer.organizationId, pointer.siteId, query, scope, cacheBust),
- needsStructure ? api.getSiteData(ctx, pointer) : null,
+ api.searchSiteContent(pointer.organizationId, pointer.siteId, query, scope, cacheBust),
+ needsStructure ? api.getSiteData(pointer) : null,
]);
const siteStructure = siteData?.structure;
@@ -98,31 +94,38 @@ async function searchSiteContent(
if (siteSpaces) {
// We are searching all of this Site's content
- return searchResults.items
- .map((spaceItem) => {
- const siteSpace = siteSpaces.find(
- (siteSpace) => siteSpace.space.id === spaceItem.id,
- );
+ return (
+ await Promise.all(
+ searchResults.items.map(async (spaceItem) => {
+ const siteSpace = siteSpaces.find(
+ (siteSpace) => siteSpace.space.id === spaceItem.id,
+ );
- return spaceItem.pages.map((item) => transformSitePageResult(ctx, item, siteSpace));
- })
- .flat(2);
+ return Promise.all(
+ spaceItem.pages.map((item) => transformSitePageResult(item, siteSpace)),
+ );
+ }),
+ )
+ ).flat(2);
}
- return searchResults.items
- .map((spaceItem) => spaceItem.pages.map((item) => transformPageResult(ctx, item)))
- .flat(2);
+ return (
+ await Promise.all(
+ searchResults.items.map((spaceItem) => {
+ return Promise.all(spaceItem.pages.map((item) => transformPageResult(item)));
+ }),
+ )
+ ).flat(2);
}
/**
* Server action to search content in the entire site.
*/
export async function searchAllSiteContent(
- ctx: GitBookContext,
query: string,
pointer: api.SiteContentPointer,
): Promise {
- return await searchSiteContent(ctx, {
+ return await searchSiteContent({
pointer,
query,
scope: { mode: 'all' },
@@ -133,7 +136,6 @@ export async function searchAllSiteContent(
* Server action to search content in a space.
*/
export async function searchSiteSpaceContent(
- ctx: GitBookContext,
query: string,
pointer: api.SiteContentPointer,
revisionId: string,
@@ -141,7 +143,7 @@ export async function searchSiteSpaceContent(
const siteSpaceId = pointer.siteSpaceId;
assert(siteSpaceId, 'Expected siteSpaceId for searchSiteSpaceContent');
- return await searchSiteContent(ctx, {
+ return await searchSiteContent({
pointer,
query,
// If we have a siteSectionId that means its a sections site use `current` mode
@@ -158,13 +160,13 @@ export async function searchSiteSpaceContent(
* Server action to ask a question in a space.
*/
export const streamAskQuestion = streamResponse(async function* (
- ctx: GitBookContext,
organizationId: string,
siteId: string,
siteSpaceId: string | null,
question: string,
) {
- const stream = api.api(ctx).client.orgs.streamAskInSite(
+ const apiCtx = await api.api();
+ const stream = apiCtx.client.orgs.streamAskInSite(
organizationId,
siteId,
{
@@ -198,9 +200,7 @@ export const streamAskQuestion = streamResponse(async function* (
if (!spacePromises.has(source.space)) {
spacePromises.set(
source.space,
- api.getRevisionPages(ctx, source.space, source.revision, {
- metadata: false,
- }),
+ api.getRevisionPages(source.space, source.revision, { metadata: false }),
);
}
@@ -222,56 +222,48 @@ export const streamAskQuestion = streamResponse(async function* (
return map;
}, new Map());
});
- yield transformAnswer(ctx, chunk.answer, pages);
+ yield await transformAnswer(chunk.answer, pages);
}
});
/**
* List suggested questions for a space.
*/
-export async function getRecommendedQuestions(
- ctx: GitBookContext,
- spaceId: string,
-): Promise {
- const data = await api.getRecommendedQuestionsInSpace(ctx, spaceId);
- if (!data.questions) {
- captureException(new Error('Expected questions in getRecommendedQuestions'), {
- extra: { data },
- });
- return [];
- }
+export async function getRecommendedQuestions(spaceId: string): Promise {
+ const data = await api.getRecommendedQuestionsInSpace(spaceId);
return data.questions;
}
-function transformAnswer(
- ctx: GitBookContext,
+async function transformAnswer(
answer: SearchAIAnswer,
spacePages: Map,
-): AskAnswerResult {
- const sources = answer.sources
- .map((source) => {
- if (source.type !== 'page') {
- return null;
- }
+): Promise {
+ const sources = (
+ await Promise.all(
+ answer.sources.map(async (source) => {
+ if (source.type !== 'page') {
+ return null;
+ }
- const pages = spacePages.get(source.space);
+ const pages = spacePages.get(source.space);
- if (!pages) {
- return null;
- }
+ if (!pages) {
+ return null;
+ }
- const page = resolvePageId(pages, source.page);
- if (!page) {
- return null;
- }
+ const page = resolvePageId(pages, source.page);
+ if (!page) {
+ return null;
+ }
- return {
- id: source.page,
- title: page.page.title,
- href: getPageHref(ctx, pages, page.page),
- };
- })
- .filter(filterOutNullable);
+ return {
+ id: source.page,
+ title: page.page.title,
+ href: await getPageHref(pages, page.page),
+ };
+ }),
+ )
+ ).filter(filterOutNullable);
return {
body:
@@ -292,19 +284,16 @@ function transformAnswer(
};
}
-function transformSectionsAndPage(
- ctx: GitBookContext,
- args: {
- item: SearchPageResult;
- space?: Space;
- spaceURL?: string;
- },
-): [ComputedPageResult, ComputedSectionResult[]] {
+async function transformSectionsAndPage(args: {
+ item: SearchPageResult;
+ space?: Space;
+ spaceURL?: string;
+}): Promise<[ComputedPageResult, ComputedSectionResult[]]> {
const { item, space, spaceURL } = args;
// Resolve a relative path to an absolute URL
// if the search result is relative to another space, we use the space URL
- const getURL = (path: string, spaceURL?: string) => {
+ const getURL = async (path: string, spaceURL?: string) => {
if (spaceURL) {
if (!spaceURL.endsWith('/')) {
spaceURL += '/';
@@ -314,36 +303,33 @@ function transformSectionsAndPage(
}
return spaceURL + path;
} else {
- return getAbsoluteHref(ctx, path);
+ return getAbsoluteHref(path);
}
};
- const sections =
- item.sections?.map((section) => ({
+ const sections = await Promise.all(
+ item.sections?.map>(async (section) => ({
type: 'section',
id: item.id + '/' + section.id,
title: section.title,
- href: getURL(section.path, spaceURL),
+ href: await getURL(section.path, spaceURL),
body: section.body,
- })) ?? [];
+ })) ?? [],
+ );
const page: ComputedPageResult = {
type: 'page',
id: item.id,
title: item.title,
- href: getURL(item.path, spaceURL),
+ href: await getURL(item.path, spaceURL),
spaceTitle: space?.title,
};
return [page, sections];
}
-function transformSitePageResult(
- ctx: GitBookContext,
- item: SearchPageResult,
- siteSpace?: SiteSpace,
-) {
- const [page, sections] = transformSectionsAndPage(ctx, {
+async function transformSitePageResult(item: SearchPageResult, siteSpace?: SiteSpace) {
+ const [page, sections] = await transformSectionsAndPage({
item,
space: siteSpace?.space,
spaceURL: siteSpace?.urls.published,
@@ -352,8 +338,8 @@ function transformSitePageResult(
return [page, ...sections];
}
-function transformPageResult(ctx: GitBookContext, item: SearchPageResult, space?: Space) {
- const [page, sections] = transformSectionsAndPage(ctx, {
+async function transformPageResult(item: SearchPageResult, space?: Space) {
+ const [page, sections] = await transformSectionsAndPage({
item,
space,
spaceURL: space?.urls.published ?? space?.urls.app,
diff --git a/packages/gitbook/src/components/Space/SpaceIcon.tsx b/packages/gitbook/src/components/Space/SpaceIcon.tsx
index 6563a148c..4bee2bad1 100644
--- a/packages/gitbook/src/components/Space/SpaceIcon.tsx
+++ b/packages/gitbook/src/components/Space/SpaceIcon.tsx
@@ -1,8 +1,6 @@
import { CustomizationThemedURL } from '@gitbook/api';
-import { headers } from 'next/headers';
import { Image } from '@/components/utils';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { getAbsoluteHref } from '@/lib/links';
import { Emoji } from '../primitives';
@@ -16,7 +14,6 @@ export async function SpaceIcon(
'sources'
>,
) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { icon, emoji, alt, ...imageProps } = props;
if (emoji && !icon) {
@@ -40,16 +37,14 @@ export async function SpaceIcon(
}
: {
light: {
- src: getAbsoluteHref(
- ctx,
+ src: await getAbsoluteHref(
'~gitbook/icon?size=medium&theme=light',
true,
),
size: { width: 256, height: 256 },
},
dark: {
- src: getAbsoluteHref(
- ctx,
+ src: await getAbsoluteHref(
'~gitbook/icon?size=medium&theme=dark',
true,
),
diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
index 68d7ddf7e..8c2027665 100644
--- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
+++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
@@ -9,7 +9,6 @@ import {
SiteCustomizationSettings,
Space,
} from '@gitbook/api';
-import { headers } from 'next/headers';
import React from 'react';
import { Footer } from '@/components/Footer';
@@ -20,10 +19,10 @@ import { TableOfContents } from '@/components/TableOfContents';
import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate';
import { api, ContentTarget, type SectionsList, SiteContentPointer } from '@/lib/api';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { ContentRefContext } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import { shouldTrackEvents } from '@/lib/tracking';
+import { getCurrentVisitorToken } from '@/lib/visitor-token';
import { SpacesDropdown } from '../Header/SpacesDropdown';
import { InsightsProvider } from '../Insights';
@@ -44,7 +43,6 @@ export async function SpaceLayout(props: {
ancestors: Array;
children: React.ReactNode;
}) {
- const ctx = getGitBookContextFromHeaders(await headers());
const {
space,
contentTarget,
@@ -76,9 +74,9 @@ export async function SpaceLayout(props: {
'sidebar' in customization.styling &&
customization.styling.sidebar.background === CustomizationSidebarBackgroundStyle.Filled,
};
- const apiHost = api(ctx).client.endpoint;
- const visitorAuthToken = ctx.visitorToken;
- const enabled = shouldTrackEvents(ctx);
+ const apiHost = (await api()).client.endpoint;
+ const visitorAuthToken = await getCurrentVisitorToken();
+ const enabled = await shouldTrackEvents();
return (
;
context: ContentRefContext;
}) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { rootPages, page, ancestors, context } = props;
- const href = await getPageHref(ctx, rootPages, page);
+ const href = await getPageHref(rootPages, page);
return (
diff --git a/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx b/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx
index d68dccac0..7a10e9f2b 100644
--- a/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx
+++ b/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx
@@ -1,19 +1,16 @@
import { RevisionPageLink } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
-import { headers } from 'next/headers';
import { Link } from '@/components/primitives';
-import { getGitBookContextFromHeaders } from '@/lib/gitbook-context';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import { TOCPageIcon } from './TOCPageIcon';
export async function PageLinkItem(props: { page: RevisionPageLink; context: ContentRefContext }) {
- const ctx = getGitBookContextFromHeaders(await headers());
const { page, context } = props;
- const resolved = await resolveContentRef(ctx, page.target, context);
+ const resolved = await resolveContentRef(page.target, context);
return (
diff --git a/packages/gitbook/src/components/utils/Image.tsx b/packages/gitbook/src/components/utils/Image.tsx
index 62190a781..3ae58ed59 100644
--- a/packages/gitbook/src/components/utils/Image.tsx
+++ b/packages/gitbook/src/components/utils/Image.tsx
@@ -1,8 +1,6 @@
/* eslint-disable @next/next/no-img-element */
-import { headers } from 'next/headers';
import ReactDOM from 'react-dom';
-import { getGitBookContextFromHeaders, GitBookContext } from '@/lib/gitbook-context';
import { checkIsHttpURL, getImageSize, getResizedImageURLFactory } from '@/lib/images';
import { ClassValue, tcls } from '@/lib/tailwind';
@@ -193,8 +191,6 @@ async function ImagePictureSized(
} & ImageCommonProps
>,
) {
- const ctx = getGitBookContextFromHeaders(await headers());
-
const {
source,
sizes,
@@ -214,7 +210,7 @@ async function ImagePictureSized(
throw new Error('You must provide at least one size for the image.');
}
- const attrs = await getImageAttributes(ctx, { sizes, source, quality, resize });
+ const attrs = await getImageAttributes({ sizes, source, quality, resize });
const canBeFetched = checkIsHttpURL(attrs.src);
const fetchPriority = canBeFetched ? getFetchPriority(priority) : undefined;
const loading = priority === 'lazy' ? 'lazy' : undefined;
@@ -247,15 +243,12 @@ async function ImagePictureSized(
* Get the attributes for an image.
* src, srcSet, sizes, width, height, etc.
*/
-async function getImageAttributes(
- ctx: GitBookContext,
- params: {
- sizes: ImageResponsiveSize[];
- source: ImageSourceSized;
- quality: number;
- resize: boolean;
- },
-): Promise<{
+async function getImageAttributes(params: {
+ sizes: ImageResponsiveSize[];
+ source: ImageSourceSized;
+ quality: number;
+ resize: boolean;
+}): Promise<{
src: string;
srcSet?: string;
sizes?: string;
@@ -265,7 +258,7 @@ async function getImageAttributes(
const { sizes, source, quality, resize } = params;
let src = source.src;
- const getURL = resize ? getResizedImageURLFactory(ctx, source.src) : null;
+ const getURL = resize ? await getResizedImageURLFactory(source.src) : null;
if (!getURL) {
return {
diff --git a/packages/gitbook/src/lib/api.ts b/packages/gitbook/src/lib/api.ts
index 856f3d334..0f2159df4 100644
--- a/packages/gitbook/src/lib/api.ts
+++ b/packages/gitbook/src/lib/api.ts
@@ -32,7 +32,6 @@ import {
noCacheFetchOptions,
parseCacheResponse,
} from './cache';
-import { GitBookContext } from './gitbook-context';
import { defaultCustomizationForSpace } from './utils';
/**
@@ -111,14 +110,16 @@ export const DEFAULT_API_ENDPOINT = process.env.GITBOOK_API_URL ?? 'https://api.
/**
* Create a new API client with a token.
*/
-export function apiWithToken(
+export async function apiWithToken(
apiToken: string,
contextId: string | undefined,
- ctx: GitBookContext,
-): GitBookAPIContext {
+): Promise {
+ const headersList = await headers();
+ const apiEndpoint = headersList.get('x-gitbook-api') ?? DEFAULT_API_ENDPOINT;
+
const gitbook = new GitBookAPI({
authToken: apiToken,
- endpoint: ctx.apiEndpoint,
+ endpoint: apiEndpoint,
userAgent: userAgent(),
});
@@ -128,19 +129,23 @@ export function apiWithToken(
/**
* Create an API client for the current request.
*/
-export function api(ctx: GitBookContext): GitBookAPIContext {
+export async function api(): Promise {
const existing = apiSyncStorage.getStore();
if (existing) {
return existing;
}
- if (!ctx.apiToken) {
+ const headersList = await headers();
+ const apiToken = headersList.get('x-gitbook-token');
+ const contextId = headersList.get('x-gitbook-token-context') ?? undefined;
+
+ if (!apiToken) {
throw new Error(
'Missing GitBook API token, please check that the request is correctly processed by the middleware',
);
}
- return apiWithToken(ctx.apiToken, ctx.apiTokenContextId ?? undefined, ctx);
+ return apiWithToken(apiToken, contextId);
}
/**
@@ -172,14 +177,15 @@ export type PublishedContentWithCache =
*/
export const getUserById = cache({
name: 'api.getUserById',
- tag: (_ctx, userId) =>
+ tag: (userId) =>
getAPICacheTag({
tag: 'user',
user: userId,
}),
- get: async (ctx: GitBookContext, userId: string, options: CacheFunctionOptions) => {
+ get: async (userId: string, options: CacheFunctionOptions) => {
try {
- const response = await api(ctx).client.users.getUserById(userId, {
+ const apiCtx = await api();
+ const response = await apiCtx.client.users.getUserById(userId, {
signal: options.signal,
...noCacheFetchOptions,
});
@@ -204,13 +210,12 @@ export const getUserById = cache({
*/
export const getPublishedContentByUrl = cache({
name: 'api.getPublishedContentByUrl.v4',
- tag: (_ctx, url) =>
+ tag: (url) =>
getAPICacheTag({
tag: 'url',
hostname: new URL(url).hostname,
}),
get: async (
- ctx: GitBookContext,
url: string,
visitorAuthToken: string | undefined,
// Prefer undefined for a better cache key.
@@ -218,7 +223,8 @@ export const getPublishedContentByUrl = cache({
options: CacheFunctionOptions,
) => {
try {
- const response = await api(ctx).client.urls.getPublishedContentByUrl(
+ const apiCtx = await api();
+ const response = await apiCtx.client.urls.getPublishedContentByUrl(
{
url,
visitorAuthToken,
@@ -266,14 +272,10 @@ export const getPublishedContentByUrl = cache({
*/
export const getSpace = cache({
name: 'api.getSpace',
- tag: (_ctx, spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
- get: async (
- ctx: GitBookContext,
- spaceId: string,
- shareKey: string | undefined,
- options: CacheFunctionOptions,
- ) => {
- const response = await api(ctx).client.spaces.getSpaceById(
+ tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
+ get: async (spaceId: string, shareKey: string | undefined, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getSpaceById(
spaceId,
{
shareKey,
@@ -294,22 +296,14 @@ export const getSpace = cache({
*/
export const getChangeRequest = cache({
name: 'api.getChangeRequest',
- tag: (_ctx, spaceId, changeRequestId) =>
+ tag: (spaceId, changeRequestId) =>
getAPICacheTag({ tag: 'change-request', space: spaceId, changeRequest: changeRequestId }),
- get: async (
- ctx: GitBookContext,
- spaceId: string,
- changeRequestId: string,
- options: CacheFunctionOptions,
- ) => {
- const response = await api(ctx).client.spaces.getChangeRequestById(
- spaceId,
- changeRequestId,
- {
- ...noCacheFetchOptions,
- signal: options.signal,
- },
- );
+ get: async (spaceId: string, changeRequestId: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getChangeRequestById(spaceId, changeRequestId, {
+ ...noCacheFetchOptions,
+ signal: options.signal,
+ });
return cacheResponse(response, {
ttl: 60 * 60,
revalidateBefore: 10 * 60,
@@ -327,22 +321,27 @@ interface GetRevisionOptions {
metadata: boolean;
}
+const getAPIContextId = async () => {
+ const apiCtx = await api();
+ return apiCtx.contextId;
+};
+
/**
* Get a revision by its ID.
*/
export const getRevision = cache({
name: 'api.getRevision.v2',
- tag: (_ctx, spaceId, revisionId) =>
+ tag: (spaceId, revisionId) =>
getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
spaceId: string,
revisionId: string,
fetchOptions: GetRevisionOptions,
options: CacheFunctionOptions,
) => {
- const response = await api(ctx).client.spaces.getRevisionById(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getRevisionById(
spaceId,
revisionId,
{
@@ -364,17 +363,17 @@ export const getRevision = cache({
*/
export const getRevisionPages = cache({
name: 'api.getRevisionPages.v4',
- tag: (_ctx, spaceId, revisionId) =>
+ tag: (spaceId, revisionId) =>
getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
spaceId: string,
revisionId: string,
fetchOptions: GetRevisionOptions,
options: CacheFunctionOptions,
) => {
- const response = await api(ctx).client.spaces.listPagesInRevisionById(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.listPagesInRevisionById(
spaceId,
revisionId,
{
@@ -399,11 +398,10 @@ export const getRevisionPages = cache({
*/
export const getRevisionPageByPath = cache({
name: 'api.getRevisionPageByPath.v3',
- tag: (_ctx, spaceId, revisionId) =>
+ tag: (spaceId, revisionId) =>
getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
spaceId: string,
revisionId: string,
pagePath: string,
@@ -412,7 +410,8 @@ export const getRevisionPageByPath = cache({
const encodedPath = encodeURIComponent(pagePath);
try {
- const response = await api(ctx).client.spaces.getPageInRevisionByPath(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getPageInRevisionByPath(
spaceId,
revisionId,
encodedPath,
@@ -445,17 +444,17 @@ export const getRevisionPageByPath = cache({
*/
const getRevisionFileById = cache({
name: 'api.getRevisionFile.v3',
- tag: (_ctx, spaceId, revisionId) =>
+ tag: (spaceId, revisionId) =>
getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
get: async (
- ctx: GitBookContext,
spaceId: string,
revisionId: string,
fileId: string,
options: CacheFunctionOptions,
) => {
try {
- const response = await api(ctx).client.spaces.getFileInRevisionById(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getFileInRevisionById(
spaceId,
revisionId,
fileId,
@@ -481,18 +480,18 @@ const getRevisionFileById = cache({
const getRevisionReusableContentById = cache({
name: 'api.getRevisionReusableContentById.v1',
- tag: (_ctx, spaceId, revisionId) =>
+ tag: (spaceId, revisionId) =>
getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
spaceId: string,
revisionId: string,
reusableContentId: string,
options: CacheFunctionOptions,
) => {
try {
- const response = await api(ctx).client.spaces.getReusableContentInRevisionById(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getReusableContentInRevisionById(
spaceId,
revisionId,
reusableContentId,
@@ -522,17 +521,13 @@ const getRevisionReusableContentById = cache({
*/
const getRevisionAllFiles = cache({
name: 'api.getRevisionAllFiles.v2',
- tag: (_ctx, spaceId, revisionId) =>
+ tag: (spaceId, revisionId) =>
getAPICacheTag({ tag: 'revision', space: spaceId, revision: revisionId }),
- get: async (
- ctx: GitBookContext,
- spaceId: string,
- revisionId: string,
- options: CacheFunctionOptions,
- ) => {
+ get: async (spaceId: string, revisionId: string, options: CacheFunctionOptions) => {
const response = await getAll(
async (params) => {
- const response = await api(ctx).client.spaces.listFilesInRevisionById(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.listFilesInRevisionById(
spaceId,
revisionId,
{
@@ -566,39 +561,35 @@ const getRevisionAllFiles = cache({
* The approach is optimized to use the entire list of files in the revision if it has been fetched
* or to use a per-file approach if not.
*/
-export const getRevisionFile = batch<[GitBookContext, string, string, string], RevisionFile | null>(
+export const getRevisionFile = batch<[string, string, string], RevisionFile | null>(
async (executions) => {
- const [ctx, spaceId, revisionId] = executions[0];
+ const [spaceId, revisionId] = executions[0];
- const hasRevisionInMemory = await getRevision.hasInMemory(ctx, spaceId, revisionId, {
+ const hasRevisionInMemory = await getRevision.hasInMemory(spaceId, revisionId, {
metadata: false,
});
- const hasRevisionFilesInMemory = await getRevisionAllFiles.hasInMemory(
- ctx,
- spaceId,
- revisionId,
- );
+ const hasRevisionFilesInMemory = await getRevisionAllFiles.hasInMemory(spaceId, revisionId);
// When fetching more than 5 files, we should bundle them all into one call to get the entire revision
if (executions.length > 5 || hasRevisionFilesInMemory || hasRevisionInMemory) {
let files: Record = {};
if (hasRevisionInMemory) {
- const revision = await getRevision(ctx, spaceId, revisionId, { metadata: false });
+ const revision = await getRevision(spaceId, revisionId, { metadata: false });
files = {};
revision.files.forEach((file) => {
files[file.id] = file;
});
} else {
- files = await getRevisionAllFiles(ctx, spaceId, revisionId);
+ files = await getRevisionAllFiles(spaceId, revisionId);
}
- return executions.map(([ctx, spaceId, revisionId, fileId]) => files[fileId] ?? null);
+ return executions.map(([spaceId, revisionId, fileId]) => files[fileId] ?? null);
} else {
// Fetch file individually
return Promise.all(
- executions.map(([ctx, spaceId, revisionId, fileId]) =>
- getRevisionFileById(ctx, spaceId, revisionId, fileId),
+ executions.map(([spaceId, revisionId, fileId]) =>
+ getRevisionFileById(spaceId, revisionId, fileId),
),
);
}
@@ -606,13 +597,13 @@ export const getRevisionFile = batch<[GitBookContext, string, string, string], R
{
delay: 20,
groupBy: (spaceId, revisionId) => spaceId + '/' + revisionId,
- skip: async (ctx, spaceId, revisionId, fileId) => {
+ skip: async (spaceId, revisionId, fileId) => {
return (
- (await getRevision.hasInMemory(ctx, spaceId, revisionId, {
+ (await getRevision.hasInMemory(spaceId, revisionId, {
metadata: false,
})) ||
- (await getRevisionAllFiles.hasInMemory(ctx, spaceId, revisionId)) ||
- (await getRevisionFileById.hasInMemory(ctx, spaceId, revisionId, fileId))
+ (await getRevisionAllFiles.hasInMemory(spaceId, revisionId)) ||
+ (await getRevisionFileById.hasInMemory(spaceId, revisionId, fileId))
);
},
},
@@ -622,24 +613,23 @@ export const getRevisionFile = batch<[GitBookContext, string, string, string], R
* Get reusable content in a revision.
*/
export const getReusableContent = async (
- ctx: GitBookContext,
spaceId: string,
revisionId: string,
reusableContentId: string,
): Promise => {
- const hasRevisionInMemory = await getRevision.hasInMemory(ctx, spaceId, revisionId, {
+ const hasRevisionInMemory = await getRevision.hasInMemory(spaceId, revisionId, {
metadata: false,
});
if (hasRevisionInMemory) {
- const revision = await getRevision(ctx, spaceId, revisionId, { metadata: false });
+ const revision = await getRevision(spaceId, revisionId, { metadata: false });
return (
revision.reusableContents.find(
(reusableContent) => reusableContent.id === reusableContentId,
) ?? null
);
} else {
- return getRevisionReusableContentById(ctx, spaceId, revisionId, reusableContentId);
+ return getRevisionReusableContentById(spaceId, revisionId, reusableContentId);
}
};
@@ -648,16 +638,12 @@ export const getReusableContent = async (
*/
export const getDocument = cache({
name: 'api.getDocument.v2',
- tag: (_ctx, spaceId, documentId) =>
+ tag: (spaceId, documentId) =>
getAPICacheTag({ tag: 'document', space: spaceId, document: documentId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
- get: async (
- ctx: GitBookContext,
- spaceId: string,
- documentId: string,
- options: CacheFunctionOptions,
- ) => {
- const response = await api(ctx).client.spaces.getDocumentById(
+ getKeySuffix: getAPIContextId,
+ get: async (spaceId: string, documentId: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getDocumentById(
spaceId,
documentId,
{
@@ -688,10 +674,9 @@ function validateSiteRedirectSource(source: string) {
*/
export const getSiteRedirectBySource = cache({
name: 'api.getSiteRedirectBySource',
- tag: (_ctx, { siteId }) => getAPICacheTag({ tag: 'site', site: siteId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ tag: ({ siteId }) => getAPICacheTag({ tag: 'site', site: siteId }),
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
args: {
organizationId: string;
siteId: string;
@@ -709,7 +694,8 @@ export const getSiteRedirectBySource = cache({
};
}
try {
- const response = await api(ctx).client.orgs.getSiteRedirectBySource(
+ const apiCtx = await api();
+ const response = await apiCtx.client.orgs.getSiteRedirectBySource(
args.organizationId,
args.siteId,
{
@@ -749,15 +735,11 @@ export const getSiteRedirectBySource = cache({
*/
export const getSite = cache({
name: 'api.getSite',
- tag: (_ctx, _organizationId, siteId) => getAPICacheTag({ tag: 'site', site: siteId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
- get: async (
- ctx: GitBookContext,
- organizationId: string,
- siteId: string,
- options: CacheFunctionOptions,
- ) => {
- const response = await api(ctx).client.orgs.getSiteById(organizationId, siteId, {
+ tag: (organizationId, siteId) => getAPICacheTag({ tag: 'site', site: siteId }),
+ getKeySuffix: getAPIContextId,
+ get: async (organizationId: string, siteId: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.orgs.getSiteById(organizationId, siteId, {
...noCacheFetchOptions,
signal: options.signal,
});
@@ -772,10 +754,9 @@ export const getSite = cache({
*/
export const getPublishedContentSite = cache({
name: 'api.getPublishedContentSite',
- tag: (_ctx, { siteId }) => getAPICacheTag({ tag: 'site', site: siteId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ tag: ({ siteId }) => getAPICacheTag({ tag: 'site', site: siteId }),
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
args: {
organizationId: string;
siteId: string /** Site share key that can be used as context to resolve site space published urls */;
@@ -783,7 +764,8 @@ export const getPublishedContentSite = cache({
},
options: CacheFunctionOptions,
) => {
- const response = await api(ctx).client.orgs.getPublishedContentSite(
+ const apiCtx = await api();
+ const response = await apiCtx.client.orgs.getPublishedContentSite(
args.organizationId,
args.siteId,
{
@@ -831,7 +813,6 @@ function parseSiteSectionsList(siteSectionId: string, sections: SiteSection[]) {
* experience for the site (structure, customizations, scripts etc)
*/
export async function getSiteData(
- ctx: GitBookContext,
pointer: Pick<
SiteContentPointer,
'organizationId' | 'siteId' | 'siteSectionId' | 'siteSpaceId' | 'siteShareKey'
@@ -842,7 +823,7 @@ export async function getSiteData(
structure: siteStructure,
customizations,
scripts,
- } = await getPublishedContentSite(ctx, {
+ } = await getPublishedContentSite({
organizationId: pointer.organizationId,
siteId: pointer.siteId,
siteShareKey: pointer.siteShareKey,
@@ -869,8 +850,7 @@ export async function getSiteData(
const spaces =
siteSpaces ?? (sections ? parseSpacesFromSiteSpaces(sections.section.siteSpaces) : []);
- const customization = getActiveCustomizationSettings(
- ctx,
+ const customization = await getActiveCustomizationSettings(
pointer.siteSpaceId ? customizations.siteSpaces[pointer.siteSpaceId] : customizations.site,
);
@@ -887,12 +867,13 @@ export async function getSiteData(
/**
* Get the customization settings for a space from the API.
*/
-export function getSpaceCustomization(ctx: GitBookContext): {
+export async function getSpaceCustomization(): Promise<{
customization: CustomizationSettings;
-} {
+}> {
+ const headersList = await headers();
const raw = defaultCustomizationForSpace();
- const extend = ctx.customization;
+ const extend = headersList.get('x-gitbook-customization');
if (extend) {
try {
const parsed = rison.decode_object>(extend);
@@ -916,9 +897,10 @@ export function getSpaceCustomization(ctx: GitBookContext): {
*/
export const getCollection = cache({
name: 'api.getCollection',
- tag: (_ctx, collectionId) => getAPICacheTag({ tag: 'collection', collection: collectionId }),
- get: async (ctx: GitBookContext, collectionId: string, options: CacheFunctionOptions) => {
- const response = await api(ctx).client.collections.getCollectionById(collectionId, {
+ tag: (collectionId) => getAPICacheTag({ tag: 'collection', collection: collectionId }),
+ get: async (collectionId: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.collections.getCollectionById(collectionId, {
...noCacheFetchOptions,
signal: options.signal,
});
@@ -933,10 +915,11 @@ export const getCollection = cache({
*/
export const getCollectionSpaces = cache({
name: 'api.getCollectionSpaces',
- tag: (_ctx, collectionId) => getAPICacheTag({ tag: 'collection', collection: collectionId }),
- get: async (ctx: GitBookContext, collectionId: string, options: CacheFunctionOptions) => {
+ tag: (collectionId) => getAPICacheTag({ tag: 'collection', collection: collectionId }),
+ get: async (collectionId: string, options: CacheFunctionOptions) => {
const response = await getAll(async (params) => {
- const response = await api(ctx).client.collections.listSpacesInCollectionById(
+ const apiCtx = await api();
+ const response = await apiCtx.client.collections.listSpacesInCollectionById(
collectionId,
params,
{
@@ -962,22 +945,19 @@ export const getCollectionSpaces = cache({
* instead of calling the individual functions.
*/
export async function getSpaceContentData(
- ctx: GitBookContext,
pointer: SpaceContentPointer,
shareKey: string | undefined,
) {
const [space, changeRequest] = await Promise.all([
- getSpace(ctx, pointer.spaceId, shareKey),
- pointer.changeRequestId
- ? getChangeRequest(ctx, pointer.spaceId, pointer.changeRequestId)
- : null,
+ getSpace(pointer.spaceId, shareKey),
+ pointer.changeRequestId ? getChangeRequest(pointer.spaceId, pointer.changeRequestId) : null,
]);
const contentTarget: ContentTarget = {
spaceId: pointer.spaceId,
revisionId: changeRequest?.revision ?? pointer.revisionId ?? space.revision,
};
- const pages = await getRevisionPages(ctx, space.id, contentTarget.revisionId, {
+ const pages = await getRevisionPages(space.id, contentTarget.revisionId, {
// We only care about the Git metadata when the Git sync is enabled
// otherwise we can optimize performance by not fetching it
metadata: !!space.gitSync,
@@ -995,17 +975,17 @@ export async function getSpaceContentData(
*/
export const searchSpaceContent = cache({
name: 'api.searchSpaceContent',
- tag: (_ctx, spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
spaceId: string,
/** The revision ID is used as a cache bust key, to avoid revalidating lot of cache entries by tags */
revisionId: string,
query: string,
options: CacheFunctionOptions,
) => {
- const response = await api(ctx).client.spaces.searchSpaceContent(
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.searchSpaceContent(
spaceId,
{ query },
{
@@ -1022,15 +1002,11 @@ export const searchSpaceContent = cache({
*/
export const searchParentContent = cache({
name: 'api.searchParentContent',
- tag: (_ctx, spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
- get: async (
- ctx: GitBookContext,
- parentId: string,
- query: string,
- options: CacheFunctionOptions,
- ) => {
- const response = await api(ctx).client.search.searchContent(
+ tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
+ getKeySuffix: getAPIContextId,
+ get: async (parentId: string, query: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.search.searchContent(
{ query },
{
...noCacheFetchOptions,
@@ -1048,10 +1024,9 @@ export const searchParentContent = cache({
*/
export const searchSiteContent = cache({
name: 'api.searchSiteContent',
- tag: (_ctx, _organizationId, siteId) => getAPICacheTag({ tag: 'site', site: siteId }),
- getKeySuffix: (ctx) => api(ctx).contextId,
+ tag: (organizationId, siteId) => getAPICacheTag({ tag: 'site', site: siteId }),
+ getKeySuffix: getAPIContextId,
get: async (
- ctx: GitBookContext,
organizationId: string,
siteId: string,
query: string,
@@ -1063,7 +1038,8 @@ export const searchSiteContent = cache({
cacheBust?: string,
options?: CacheFunctionOptions,
) => {
- const response = await api(ctx).client.orgs.searchSiteContent(
+ const apiCtx = await api();
+ const response = await apiCtx.client.orgs.searchSiteContent(
organizationId,
siteId,
{
@@ -1088,9 +1064,10 @@ export const searchSiteContent = cache({
*/
export const getRecommendedQuestionsInSpace = cache({
name: 'api.getRecommendedQuestionsInSpace',
- tag: (_ctx, spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
- get: async (ctx: GitBookContext, spaceId: string, options: CacheFunctionOptions) => {
- const response = await api(ctx).client.spaces.getRecommendedQuestionsInSpace(spaceId, {
+ tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
+ get: async (spaceId: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getRecommendedQuestionsInSpace(spaceId, {
...noCacheFetchOptions,
signal: options.signal,
});
@@ -1103,15 +1080,14 @@ export const getRecommendedQuestionsInSpace = cache({
*/
export const renderIntegrationUi = cache({
name: 'api.renderIntegrationUi',
- tag: (_ctx, integrationName) =>
- getAPICacheTag({ tag: 'integration', integration: integrationName }),
+ tag: (integrationName) => getAPICacheTag({ tag: 'integration', integration: integrationName }),
get: async (
- ctx: GitBookContext,
integrationName: string,
request: RequestRenderIntegrationUI,
options: CacheFunctionOptions,
) => {
- const response = await api(ctx).client.integrations.renderIntegrationUiWithPost(
+ const apiCtx = await api();
+ const response = await apiCtx.client.integrations.renderIntegrationUiWithPost(
integrationName,
request,
{
@@ -1129,8 +1105,9 @@ export const renderIntegrationUi = cache({
*/
export const getEmbedByUrl = cache({
name: 'api.getEmbedByUrl',
- get: async (ctx: GitBookContext, url: string, options: CacheFunctionOptions) => {
- const response = await api(ctx).client.urls.getEmbedByUrl(
+ get: async (url: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.urls.getEmbedByUrl(
{ url },
{
...noCacheFetchOptions,
@@ -1146,14 +1123,10 @@ export const getEmbedByUrl = cache({
*/
export const getEmbedByUrlInSpace = cache({
name: 'api.getEmbedByUrlInSpace',
- tag: (_ctx, spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
- get: async (
- ctx: GitBookContext,
- spaceId: string,
- url: string,
- options: CacheFunctionOptions,
- ) => {
- const response = await api(ctx).client.spaces.getEmbedByUrlInSpace(
+ tag: (spaceId) => getAPICacheTag({ tag: 'space', space: spaceId }),
+ get: async (spaceId: string, url: string, options: CacheFunctionOptions) => {
+ const apiCtx = await api();
+ const response = await apiCtx.client.spaces.getEmbedByUrlInSpace(
spaceId,
{ url },
{
@@ -1324,11 +1297,11 @@ async function getAll(
* Selects the customization settings from the x-gitbook-customization header if present,
* otherwise returns the original API-provided settings.
*/
-function getActiveCustomizationSettings(
- ctx: GitBookContext,
+async function getActiveCustomizationSettings(
settings: SiteCustomizationSettings,
-): SiteCustomizationSettings {
- const extend = ctx.customization;
+): Promise {
+ const headersList = await headers();
+ const extend = headersList.get('x-gitbook-customization');
if (extend) {
try {
const parsedSettings = rison.decode_object(extend);
diff --git a/packages/gitbook/src/lib/cache/cache.test.ts b/packages/gitbook/src/lib/cache/cache.test.ts
index 4e3a4c896..0870d66da 100644
--- a/packages/gitbook/src/lib/cache/cache.test.ts
+++ b/packages/gitbook/src/lib/cache/cache.test.ts
@@ -74,7 +74,7 @@ describe('cache', () => {
describe('cache with suffix key', () => {
const impl = mock((arg: string) => 'test-' + arg);
const getKeySuffixImpl: Mock['getKeySuffix']>> =
- mock(() => hash({ test: 1 }));
+ mock(async () => hash({ test: 1 }));
let fn: CacheFunction<[string], string>;
let testId = 0;
@@ -112,7 +112,7 @@ describe('cache with suffix key', () => {
expect(impl).toHaveBeenCalledTimes(2);
- getKeySuffixImpl.mockImplementation(() => hash({ test: 2 }));
+ getKeySuffixImpl.mockImplementation(async () => hash({ test: 2 }));
expect(await fn('a')).toEqual('test-a');
expect(impl).toHaveBeenCalledTimes(3);
@@ -120,7 +120,7 @@ describe('cache with suffix key', () => {
it('should preserve behaviour even when the returned key suffix is undefined', async () => {
// Start with the returned suffix being undefined
- getKeySuffixImpl.mockImplementation(() => undefined);
+ getKeySuffixImpl.mockImplementation(async () => undefined);
const result = await fn('a');
expect(result).toEqual('test-a');
@@ -128,19 +128,19 @@ describe('cache with suffix key', () => {
expect(impl).toHaveBeenCalledTimes(1);
// The returned suffix changes so we should get the value computed by the function
- getKeySuffixImpl.mockImplementation(() => hash({ test: 1 }));
+ getKeySuffixImpl.mockImplementation(async () => hash({ test: 1 }));
expect(await fn('a')).toEqual('test-a');
expect(impl).toHaveBeenCalledTimes(2);
// The returned suffix is undefined again so we should get the value from a previous cache entry
- getKeySuffixImpl.mockImplementation(() => undefined);
+ getKeySuffixImpl.mockImplementation(async () => undefined);
expect(await fn('a')).toEqual('test-a');
expect(impl).toHaveBeenCalledTimes(2);
// The returned suffix goes back to a previous hash so we should the value from a previous cache entry
- getKeySuffixImpl.mockImplementation(() => hash({ test: 1 }));
+ getKeySuffixImpl.mockImplementation(async () => hash({ test: 1 }));
expect(await fn('a')).toEqual('test-a');
expect(impl).toHaveBeenCalledTimes(2);
diff --git a/packages/gitbook/src/lib/cache/cache.ts b/packages/gitbook/src/lib/cache/cache.ts
index 1c6423370..cef72fe99 100644
--- a/packages/gitbook/src/lib/cache/cache.ts
+++ b/packages/gitbook/src/lib/cache/cache.ts
@@ -57,7 +57,7 @@ export interface CacheDefinition {
getKeyArgs?: (args: Args) => any[];
/** Returns a precomputed hash that is used alongside arguments to generate the cache key */
- getKeySuffix?: (...args: Args) => string | undefined;
+ getKeySuffix?: () => Promise;
/** Default ttl (in seconds) */
defaultTtl?: number;
@@ -245,7 +245,7 @@ export function cache(
const [args, { signal }] = extractCacheFunctionOptions(rawArgs);
const cacheArgs = cacheDef.getKeyArgs ? cacheDef.getKeyArgs(args) : args;
- const cacheKeySuffix = cacheDef.getKeySuffix ? cacheDef.getKeySuffix(...args) : undefined;
+ const cacheKeySuffix = cacheDef.getKeySuffix ? await cacheDef.getKeySuffix() : undefined;
const key = getCacheKey(cacheDef.name, cacheArgs, cacheKeySuffix);
return await trace(
@@ -263,7 +263,7 @@ export function cache(
cacheFn.revalidate = async (...rawArgs: Args | [...Args, CacheFunctionOptions]) => {
const [args, { signal }] = extractCacheFunctionOptions(rawArgs);
const cacheArgs = cacheDef.getKeyArgs ? cacheDef.getKeyArgs(args) : args;
- const cacheKeySuffix = cacheDef.getKeySuffix ? cacheDef.getKeySuffix(...args) : undefined;
+ const cacheKeySuffix = cacheDef.getKeySuffix ? await cacheDef.getKeySuffix() : undefined;
const key = getCacheKey(cacheDef.name, cacheArgs, cacheKeySuffix);
const result = await revalidate(key, signal, ...args);
@@ -272,7 +272,7 @@ export function cache(
cacheFn.hasInMemory = async (...args: Args) => {
const cacheArgs = cacheDef.getKeyArgs ? cacheDef.getKeyArgs(args) : args;
- const cacheKeySuffix = cacheDef.getKeySuffix ? cacheDef.getKeySuffix(...args) : undefined;
+ const cacheKeySuffix = cacheDef.getKeySuffix ? await cacheDef.getKeySuffix() : undefined;
const key = getCacheKey(cacheDef.name, cacheArgs, cacheKeySuffix);
const tag = cacheDef.tag?.(...args);
diff --git a/packages/gitbook/src/lib/csp.ts b/packages/gitbook/src/lib/csp.ts
index 49a0d6428..663a2a14a 100644
--- a/packages/gitbook/src/lib/csp.ts
+++ b/packages/gitbook/src/lib/csp.ts
@@ -3,17 +3,18 @@ import { merge } from 'content-security-policy-merger';
import { headers } from 'next/headers';
import { assetsDomain } from './assets';
-import { GitBookContext } from './gitbook-context';
import { filterOutNullable } from './typescript';
/**
* Get the current nonce for the current request.
*/
-export function getContentSecurityPolicyNonce(ctx: GitBookContext): string {
- if (!ctx.nonce) {
+export async function getContentSecurityPolicyNonce(): Promise {
+ const headersList = await headers();
+ const nonce = headersList.get('x-nonce');
+ if (!nonce) {
throw new Error('No nonce found in headers');
}
- return ctx.nonce;
+ return nonce;
}
/**
diff --git a/packages/gitbook/src/lib/gitbook-context.ts b/packages/gitbook/src/lib/gitbook-context.ts
deleted file mode 100644
index 78f57ab07..000000000
--- a/packages/gitbook/src/lib/gitbook-context.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { headers } from 'next/headers';
-
-import { DEFAULT_API_ENDPOINT } from './api';
-import { formatBasePath } from './links';
-
-export type GitBookContext = {
- theme: string | null;
- nonce: string | null;
- visitorToken: string | null;
- trackPageViews: boolean;
- apiEndpoint: string;
- apiToken: string | null;
- apiTokenContextId: string | null;
- customization: string | null;
- host: string;
- basePath: string;
- protocol: string;
- originBasePath: string;
-
- // Content pointers
- spaceId: string | null;
- siteId: string | null;
- organizationId: string | null;
- siteSpaceId: string | null;
- siteSectionId: string | null;
- siteShareKey: string | null;
- contentRevisionId: string | null;
- changeRequestId: string | null;
-
- // Indexation
- searchIndexation: boolean;
-};
-
-/**
- * Extract the gitbook context from the headers.
- */
-export function getGitBookContextFromHeaders(headers: Headers): GitBookContext {
- return {
- theme: headers.get('x-gitbook-theme'),
- nonce: headers.get('x-nonce'),
- visitorToken: headers.get('x-gitbook-visitor-token'),
- trackPageViews: headers.has('x-gitbook-track-page-views'),
- apiEndpoint: headers.get('x-gitbook-api') ?? DEFAULT_API_ENDPOINT,
- apiToken: headers.get('x-gitbook-token'),
- apiTokenContextId: headers.get('x-gitbook-token-context'),
- customization: headers.get('x-gitbook-customization'),
- basePath: formatBasePath(headers.get('x-gitbook-basepath')),
- host: headers.get('x-gitbook-host') ?? headers.get('host') ?? '',
- protocol: headers.get('x-forwarded-proto') ?? 'https',
- originBasePath: headers.get('x-gitbook-origin-basepath') ?? '/',
- spaceId: headers.get('x-gitbook-content-space'),
- siteId: headers.get('x-gitbook-content-site'),
- organizationId: headers.get('x-gitbook-content-organization'),
- siteSpaceId: headers.get('x-gitbook-content-site-space'),
- siteSectionId: headers.get('x-gitbook-content-site-section'),
- siteShareKey: headers.get('x-gitbook-content-site-share-key'),
- contentRevisionId: headers.get('x-gitbook-content-revision'),
- changeRequestId: headers.get('x-gitbook-content-changerequest'),
- searchIndexation: headers.has('x-gitbook-search-indexation'),
- };
-}
-
-export type IpAndUserAgent = {
- ip: string;
- userAgent: string;
-};
-
-/**
- * Read the IP and User-Agent from the headers.
- * This function can only be called at the top level of a component or route.
- */
-export function getIpAndUserAgentFromHeaders(headers: Headers): IpAndUserAgent {
- const ip =
- headers.get('x-gitbook-ipv4') ??
- headers.get('x-gitbook-ip') ??
- headers.get('cf-pseudo-ipv4') ??
- headers.get('cf-connecting-ip') ??
- headers.get('x-forwarded-for') ??
- '';
- const userAgent = headers.get('user-agent') ?? '';
-
- return { ip, userAgent };
-}
diff --git a/packages/gitbook/src/lib/image-signatures.ts b/packages/gitbook/src/lib/image-signatures.ts
index 1caee65a3..08e042e85 100644
--- a/packages/gitbook/src/lib/image-signatures.ts
+++ b/packages/gitbook/src/lib/image-signatures.ts
@@ -3,7 +3,7 @@ import 'server-only';
import fnv1a from '@sindresorhus/fnv1a';
import type { MaybePromise } from 'p-map';
-import { GitBookContext } from './gitbook-context';
+import { getHost } from './links';
/**
* GitBook has supported different version of image signing in the past. To maintain backwards
@@ -19,14 +19,12 @@ export const CURRENT_SIGNATURE_VERSION: SignatureVersion = '2';
/**
* A mapping of signature versions to signature functions.
*/
-const IMAGE_SIGNATURE_FUNCTIONS: Record<
- SignatureVersion,
- (ctx: GitBookContext, input: string) => MaybePromise
-> = {
- '0': generateSignatureV0,
- '1': generateSignatureV1,
- '2': generateSignatureV2,
-};
+const IMAGE_SIGNATURE_FUNCTIONS: Record MaybePromise> =
+ {
+ '0': generateSignatureV0,
+ '1': generateSignatureV1,
+ '2': generateSignatureV2,
+ };
export function isSignatureVersion(input: string): input is SignatureVersion {
return Object.keys(IMAGE_SIGNATURE_FUNCTIONS).includes(input);
@@ -36,12 +34,11 @@ export function isSignatureVersion(input: string): input is SignatureVersion {
* Verify a signature of an image URL
*/
export async function verifyImageSignature(
- ctx: GitBookContext,
input: string,
{ signature, version }: { signature: string; version: SignatureVersion },
): Promise {
const generator = IMAGE_SIGNATURE_FUNCTIONS[version];
- const generated = await generator(ctx, input);
+ const generated = await generator(input);
return generated === signature;
}
@@ -51,14 +48,11 @@ export async function verifyImageSignature(
* This function is sync. If you need to implement an async version of image signing, you'll need to change
* ths signature of this fn and where it's used.
*/
-export function generateImageSignature(
- ctx: GitBookContext,
- input: string,
-): {
+export async function generateImageSignature(input: string): Promise<{
signature: string;
version: SignatureVersion;
-} {
- const result = generateSignatureV2(ctx, input);
+}> {
+ const result = await generateSignatureV2(input);
return { signature: result, version: CURRENT_SIGNATURE_VERSION };
}
@@ -69,8 +63,8 @@ const fnv1aUtf8Buffer = new Uint8Array(512);
* Generate a signature for an image.
* The signature is relative to the current site being rendered to avoid serving images from other sites on the same domain.
*/
-function generateSignatureV2(ctx: GitBookContext, input: string): string {
- const hostName = ctx.host;
+async function generateSignatureV2(input: string): Promise {
+ const hostName = await getHost();
const all = [
input,
hostName, // The hostname is used to avoid serving images from other sites on the same domain
@@ -89,7 +83,7 @@ const fnv1aUtf8BufferV1 = new Uint8Array(512);
* When setting it in a URL, we use version '1' for the 'sv' querystring parameneter
* to know that it was the algorithm that was used.
*/
-function generateSignatureV1(ctx: GitBookContext, input: string): string {
+function generateSignatureV1(input: string): string {
const all = [input, process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY].filter(Boolean).join(':');
return fnv1a(all, { utf8Buffer: fnv1aUtf8BufferV1 }).toString(16);
}
@@ -99,7 +93,7 @@ function generateSignatureV1(ctx: GitBookContext, input: string): string {
* We still need it to validate older signatures that were generated without versioning
* but still exist in previously generated and cached content.
*/
-async function generateSignatureV0(ctx: GitBookContext, input: string): Promise {
+async function generateSignatureV0(input: string): Promise {
const all = [input, process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY].filter(Boolean).join(':');
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(all));
diff --git a/packages/gitbook/src/lib/images.ts b/packages/gitbook/src/lib/images.ts
index c911c5309..83406cf33 100644
--- a/packages/gitbook/src/lib/images.ts
+++ b/packages/gitbook/src/lib/images.ts
@@ -2,7 +2,6 @@ import 'server-only';
import { noCacheFetchOptions } from '@/lib/cache/http';
-import { GitBookContext } from './gitbook-context';
import { generateImageSignature } from './image-signatures';
import { getRootUrl } from './links';
import { getImageAPIUrl } from './urls';
@@ -85,16 +84,17 @@ interface ResizeImageOptions {
/**
* Create a function to get resized image URLs for a given image URL.
*/
-export function getResizedImageURLFactory(
- ctx: GitBookContext,
+export async function getResizedImageURLFactory(
input: string,
-): ((options: ResizeImageOptions) => string) | null {
+): Promise<((options: ResizeImageOptions) => string) | null> {
if (!checkIsSizableImageURL(input)) {
return null;
}
- const { signature, version } = generateImageSignature(ctx, input);
- const rootUrl = getRootUrl(ctx);
+ const [{ signature, version }, rootUrl] = await Promise.all([
+ generateImageSignature(input),
+ getRootUrl(),
+ ]);
return (options) => {
const url = new URL('/~gitbook/image', rootUrl);
@@ -124,12 +124,11 @@ export function getResizedImageURLFactory(
* Create a new URL for an image with resized parameters.
* The URL is signed and verified by the server.
*/
-export function getResizedImageURL(
- ctx: GitBookContext,
+export async function getResizedImageURL(
input: string,
options: ResizeImageOptions,
-): string {
- const factory = getResizedImageURLFactory(ctx, input);
+): Promise {
+ const factory = await getResizedImageURLFactory(input);
return factory?.(options) ?? input;
}
diff --git a/packages/gitbook/src/lib/links.ts b/packages/gitbook/src/lib/links.ts
index 7eebdba77..5bbf04f55 100644
--- a/packages/gitbook/src/lib/links.ts
+++ b/packages/gitbook/src/lib/links.ts
@@ -8,7 +8,6 @@ import {
} from '@gitbook/api';
import { headers } from 'next/headers';
-import { GitBookContext } from './gitbook-context';
import { getPagePath } from './pages';
export interface PageHrefContext {
@@ -23,8 +22,9 @@ export interface PageHrefContext {
* Return the base path for the current request.
* The value will start and finish with /
*/
-export function formatBasePath(headerBasePath: string | null): string {
- let path = headerBasePath ?? '/';
+export async function getBasePath(): Promise {
+ const headersList = await headers();
+ let path = headersList.get('x-gitbook-basepath') ?? '/';
if (!path.startsWith('/')) {
path = '/' + path;
@@ -37,15 +37,24 @@ export function formatBasePath(headerBasePath: string | null): string {
return path;
}
+/**
+ * Return the current host for the current request.
+ */
+export async function getHost(): Promise {
+ const headersList = await headers();
+ return headersList.get('x-gitbook-host') ?? headersList.get('host') ?? '';
+}
+
/**
* Return the root URL for the GitBook Open instance (not the content).
* Use `baseUrl` to get the base URL for the current content.
*
* The URL will end with "/".
*/
-export function getRootUrl(ctx: GitBookContext): string {
- const protocol = ctx.protocol;
- let path = ctx.originBasePath;
+export async function getRootUrl(): Promise {
+ const [headersList, host] = await Promise.all([headers(), getHost()]);
+ const protocol = headersList.get('x-forwarded-proto') ?? 'https';
+ let path = headersList.get('x-gitbook-origin-basepath') ?? '/';
if (!path.startsWith('/')) {
path = '/' + path;
@@ -55,26 +64,24 @@ export function getRootUrl(ctx: GitBookContext): string {
path = path + '/';
}
- return `${protocol}://${ctx.host}${path}`;
+ return `${protocol}://${host}${path}`;
}
/**
* Return the base URL for the current content.
* The URL will end with "/".
*/
-export function getBaseUrl(ctx: GitBookContext): string {
- return `${ctx.protocol}://${ctx.host}${ctx.basePath}`;
+export async function getBaseUrl(): Promise {
+ const [headersList, host, basePath] = await Promise.all([headers(), getHost(), getBasePath()]);
+ const protocol = headersList.get('x-forwarded-proto') ?? 'https';
+ return `${protocol}://${host}${basePath}`;
}
/**
* Create an absolute href in the current content.
*/
-export function getAbsoluteHref(
- ctx: GitBookContext,
- href: string,
- withHost: boolean = false,
-): string {
- const base = withHost ? getBaseUrl(ctx) : ctx.basePath;
+export async function getAbsoluteHref(href: string, withHost: boolean = false): Promise {
+ const base = withHost ? await getBaseUrl() : await getBasePath();
return `${base}${href.startsWith('/') ? href.slice(1) : href}`;
}
@@ -91,14 +98,13 @@ export function getGitbookAppHref(pathname: string): string {
/**
* Create a link to a page path in the current space.
*/
-export function getPageHref(
- ctx: GitBookContext,
+export async function getPageHref(
rootPages: RevisionPage[],
page: RevisionPageDocument | RevisionPageGroup,
context: PageHrefContext = {},
/** Anchor to link to in the page. */
anchor?: string,
-): string {
+): Promise {
const { pdf } = context;
if (pdf) {
@@ -114,7 +120,9 @@ export function getPageHref(
}
}
- return getAbsoluteHref(ctx, getPagePath(rootPages, page)) + (anchor ? '#' + anchor : '');
+ const href =
+ (await getAbsoluteHref(getPagePath(rootPages, page))) + (anchor ? '#' + anchor : '');
+ return href;
}
/**
diff --git a/packages/gitbook/src/lib/pointer.ts b/packages/gitbook/src/lib/pointer.ts
index 53b8ffbac..400fdd51d 100644
--- a/packages/gitbook/src/lib/pointer.ts
+++ b/packages/gitbook/src/lib/pointer.ts
@@ -1,11 +1,18 @@
+import { headers } from 'next/headers';
+
import { SiteContentPointer, SpaceContentPointer } from './api';
-import { GitBookContext } from './gitbook-context';
/**
* Get the current site content pointer from the headers
*/
-export function getSiteContentPointer(ctx: GitBookContext): SiteContentPointer {
- const { siteId, spaceId, organizationId, siteSectionId, siteSpaceId, siteShareKey } = ctx;
+export async function getSiteContentPointer(): Promise {
+ const headersList = await headers();
+ const spaceId = headersList.get('x-gitbook-content-space');
+ const siteId = headersList.get('x-gitbook-content-site');
+ 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');
if (!spaceId || !siteId || !organizationId) {
throw new Error(
@@ -20,8 +27,8 @@ export function getSiteContentPointer(ctx: GitBookContext): SiteContentPointer {
siteSpaceId: siteSpaceId ?? undefined,
siteShareKey: siteShareKey ?? undefined,
organizationId,
- revisionId: ctx.contentRevisionId ?? undefined,
- changeRequestId: ctx.changeRequestId ?? undefined,
+ revisionId: headersList.get('x-gitbook-content-revision') ?? undefined,
+ changeRequestId: headersList.get('x-gitbook-content-changerequest') ?? undefined,
};
return pointer;
@@ -31,8 +38,9 @@ export function getSiteContentPointer(ctx: GitBookContext): SiteContentPointer {
* Get the current space pointer from the headers. This should be used when rendering
* the space in an isolated context (e.g. PDF generation).
*/
-export function getSpacePointer(ctx: GitBookContext): SpaceContentPointer {
- const spaceId = ctx.spaceId;
+export async function getSpacePointer(): Promise {
+ 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',
@@ -41,8 +49,8 @@ export function getSpacePointer(ctx: GitBookContext): SpaceContentPointer {
const pointer: SpaceContentPointer = {
spaceId,
- revisionId: ctx.contentRevisionId ?? undefined,
- changeRequestId: ctx.changeRequestId ?? undefined,
+ revisionId: headersList.get('x-gitbook-content-revision') ?? undefined,
+ changeRequestId: headersList.get('x-gitbook-content-changerequest') ?? undefined,
};
return pointer;
diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx
index c62468f5c..b71ffa430 100644
--- a/packages/gitbook/src/lib/references.tsx
+++ b/packages/gitbook/src/lib/references.tsx
@@ -28,7 +28,6 @@ import {
parseSpacesFromSiteSpaces,
} from './api';
import { getBlockById, getBlockTitle } from './document';
-import { GitBookContext } from './gitbook-context';
import { getGitbookAppHref, getPageHref, PageHrefContext } from './links';
import { getPagePath, resolvePageId } from './pages';
import { ClassValue } from './tailwind';
@@ -101,7 +100,6 @@ export interface ResolveContentRefOptions {
* Resolve a content reference to be rendered.
*/
export async function resolveContentRef(
- ctx: GitBookContext,
contentRef: ContentRef,
context: ContentRefContext,
options: ResolveContentRefOptions = {},
@@ -119,7 +117,7 @@ export async function resolveContentRef(
}
case 'file': {
- const file = await getRevisionFile(ctx, space.id, revisionId, contentRef.file);
+ const file = await getRevisionFile(space.id, revisionId, contentRef.file);
if (file) {
return {
href: file.downloadURL,
@@ -135,7 +133,7 @@ export async function resolveContentRef(
case 'anchor':
case 'page': {
if (contentRef.space && contentRef.space !== space.id) {
- return resolveContentRefInSpace(ctx, contentRef.space, siteContext, contentRef);
+ return resolveContentRefInSpace(contentRef.space, siteContext, contentRef);
}
const resolvePageResult =
@@ -164,7 +162,7 @@ export async function resolveContentRef(
if (resolveAnchorText) {
const document = page.documentId
- ? await getDocument(ctx, space.id, page.documentId)
+ ? await getDocument(space.id, page.documentId)
: null;
if (document) {
const block = getBlockById(document, anchor);
@@ -195,7 +193,7 @@ export async function resolveContentRef(
}
} else {
// Page in the current content
- href = await getPageHref(ctx, pages, page, linksContext, anchor);
+ href = await getPageHref(pages, page, linksContext, anchor);
}
return {
@@ -211,7 +209,7 @@ export async function resolveContentRef(
const targetSpace =
contentRef.space === space.id
? space
- : await getBestTargetSpace(ctx, contentRef.space, siteContext);
+ : await getBestTargetSpace(contentRef.space, siteContext);
if (!targetSpace) {
return {
@@ -229,7 +227,7 @@ export async function resolveContentRef(
}
case 'user': {
- const user = await getUserById(ctx, contentRef.user);
+ const user = await getUserById(contentRef.user);
if (user) {
return {
href: `mailto:${user.email}`,
@@ -252,7 +250,7 @@ export async function resolveContentRef(
}
case 'collection': {
- const collection = await ignoreAPIError(getCollection(ctx, contentRef.collection));
+ const collection = await ignoreAPIError(getCollection(contentRef.collection));
if (!collection) {
return {
href: getGitbookAppHref(`/s/${contentRef.collection}`),
@@ -270,7 +268,6 @@ export async function resolveContentRef(
case 'reusable-content': {
const reusableContent = await getReusableContent(
- ctx,
space.id,
revisionId,
contentRef.reusableContent,
@@ -296,21 +293,16 @@ export async function resolveContentRef(
* It will try to return the space in the site context if it exists to avoid cross-site links.
*/
async function getBestTargetSpace(
- ctx: GitBookContext,
spaceId: string,
siteContext: SiteContentPointer | null,
): Promise {
const [fetchedSpace, publishedContentSite] = await Promise.all([
ignoreAPIError(
- getSpace(
- ctx,
- spaceId,
- siteContext?.siteShareKey ? siteContext.siteShareKey : undefined,
- ),
+ getSpace(spaceId, siteContext?.siteShareKey ? siteContext.siteShareKey : undefined),
),
siteContext
? ignoreAPIError(
- getPublishedContentSite(ctx, {
+ getPublishedContentSite({
organizationId: siteContext.organizationId,
siteId: siteContext.siteId,
siteShareKey: siteContext.siteShareKey,
@@ -341,7 +333,6 @@ async function getBestTargetSpace(
}
async function resolveContentRefInSpace(
- ctx: GitBookContext,
spaceId: string,
siteContext: SiteContentPointer | null,
contentRef: ContentRef,
@@ -351,8 +342,8 @@ async function resolveContentRefInSpace(
};
const [result, bestTargetSpace] = await Promise.all([
- ignoreAPIError(getSpaceContentData(ctx, pointer, siteContext?.siteShareKey)),
- getBestTargetSpace(ctx, spaceId, siteContext),
+ ignoreAPIError(getSpaceContentData(pointer, siteContext?.siteShareKey)),
+ getBestTargetSpace(spaceId, siteContext),
]);
if (!result) {
return null;
@@ -367,7 +358,7 @@ async function resolveContentRefInSpace(
baseUrl += '/';
}
- const resolved = await resolveContentRef(ctx, contentRef, {
+ const resolved = await resolveContentRef(contentRef, {
siteContext,
space,
revisionId: space.revision,
diff --git a/packages/gitbook/src/lib/seo.ts b/packages/gitbook/src/lib/seo.ts
index f979207c9..a7db11b8f 100644
--- a/packages/gitbook/src/lib/seo.ts
+++ b/packages/gitbook/src/lib/seo.ts
@@ -1,4 +1,5 @@
import {
+ Collection,
ContentVisibility,
RevisionPageDocument,
RevisionPageGroup,
@@ -6,8 +7,7 @@ import {
SiteVisibility,
Space,
} from '@gitbook/api';
-
-import { GitBookContext } from './gitbook-context';
+import { headers } from 'next/headers';
/**
* Return true if a page is indexable in search.
@@ -31,16 +31,21 @@ export function isPageIndexable(
/**
* Return true if a space should be indexed by search engines.
*/
-export function isSpaceIndexable(
- ctx: GitBookContext,
- { space, site }: { space: Space; site: Site | null },
-) {
- if (process.env.GITBOOK_BLOCK_SEARCH_INDEXATION && !ctx.searchIndexation) {
+export async function isSpaceIndexable({ space, site }: { space: Space; site: Site | null }) {
+ const headersList = await headers();
+
+ if (
+ process.env.GITBOOK_BLOCK_SEARCH_INDEXATION &&
+ !headersList.has('x-gitbook-search-indexation')
+ ) {
return false;
}
// Prevent indexation of preview of revisions / change-requests
- if (ctx.contentRevisionId || ctx.changeRequestId) {
+ if (
+ headersList.get('x-gitbook-content-revision') ||
+ headersList.get('x-gitbook-content-changerequest')
+ ) {
return false;
}
diff --git a/packages/gitbook/src/lib/tracking.ts b/packages/gitbook/src/lib/tracking.ts
index edd106d72..7c52a01d0 100644
--- a/packages/gitbook/src/lib/tracking.ts
+++ b/packages/gitbook/src/lib/tracking.ts
@@ -1,12 +1,15 @@
-import { GitBookContext } from './gitbook-context';
+import { headers } from 'next/headers';
/**
* Return true if events should be tracked on the site.
*/
-export function shouldTrackEvents(ctx: GitBookContext): boolean {
+export async function shouldTrackEvents(): Promise {
+ const headersList = await headers();
+
if (
process.env.NODE_ENV === 'development' ||
- (process.env.GITBOOK_BLOCK_PAGE_VIEWS_TRACKING && !ctx.trackPageViews)
+ (process.env.GITBOOK_BLOCK_PAGE_VIEWS_TRACKING &&
+ !headersList.has('x-gitbook-track-page-views'))
) {
return false;
}
diff --git a/packages/gitbook/src/lib/visitor-token.ts b/packages/gitbook/src/lib/visitor-token.ts
index 3656aa606..50987f8fd 100644
--- a/packages/gitbook/src/lib/visitor-token.ts
+++ b/packages/gitbook/src/lib/visitor-token.ts
@@ -90,6 +90,15 @@ export function normalizeVisitorAuthURL(url: URL): URL {
return withoutVAParam;
}
+/**
+ * Get the visitor token from the request context.
+ */
+export async function getCurrentVisitorToken(): Promise {
+ const headersList = await headers();
+ const visitorToken = headersList.get('x-gitbook-visitor-token');
+ return visitorToken;
+}
+
/**
* Get all possible basePaths for a given URL. This is used to find the visitor
* authentication cookie token.
diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts
index 84df0da31..b17659d77 100644
--- a/packages/gitbook/src/middleware.ts
+++ b/packages/gitbook/src/middleware.ts
@@ -30,7 +30,6 @@ import {
normalizeVisitorAuthURL,
} from '@/lib/visitor-token';
-import { getGitBookContextFromHeaders, GitBookContext } from './lib/gitbook-context';
import { waitUntil } from './lib/waitUntil';
export const config = {
@@ -97,7 +96,6 @@ export type LookupResult = PublishedContentWithCache & {
* The middleware also takes care of persisting the visitor authentication state.
*/
export async function middleware(request: NextRequest) {
- const ctx = getGitBookContextFromHeaders(request.headers);
const { url, mode } = getInputURL(request);
setTag('url', url.toString());
@@ -130,7 +128,7 @@ export async function middleware(request: NextRequest) {
}),
contextId: undefined,
},
- () => lookupSiteForURL(ctx, mode, request, inputURL),
+ () => lookupSiteForURL(mode, request, inputURL),
);
if ('error' in resolved) {
return new NextResponse(resolved.error.message, {
@@ -184,7 +182,7 @@ export async function middleware(request: NextRequest) {
async () => {
const [siteData] = await Promise.all([
'site' in resolved
- ? getSiteData(ctx, {
+ ? getSiteData({
organizationId: resolved.organization,
siteId: resolved.site,
siteSectionId: resolved.siteSection,
@@ -196,7 +194,6 @@ export async function middleware(request: NextRequest) {
// the cache will handle concurrent calls
waitUntil(
getSpaceContentData(
- ctx,
{
spaceId: resolved.space,
changeRequestId: resolved.changeRequest,
@@ -377,26 +374,25 @@ function getInputURL(request: NextRequest): {
}
async function lookupSiteForURL(
- ctx: GitBookContext,
mode: URLLookupMode,
request: NextRequest,
url: URL,
): Promise {
switch (mode) {
case 'single': {
- return lookupSiteInSingleMode(ctx, url);
+ return await lookupSiteInSingleMode(url);
}
case 'multi': {
- return await lookupSiteInMultiMode(ctx, request, url);
+ return await lookupSiteInMultiMode(request, url);
}
case 'multi-path': {
- return await lookupSiteInMultiPathMode(ctx, request, url);
+ return await lookupSiteInMultiPathMode(request, url);
}
case 'multi-id': {
- return await lookupSiteOrSpaceInMultiIdMode(ctx, request, url);
+ return await lookupSiteOrSpaceInMultiIdMode(request, url);
}
case 'proxy':
- return await lookupSiteInProxy(ctx, request, url);
+ return await lookupSiteInProxy(request, url);
default:
assertNever(mode);
}
@@ -406,7 +402,7 @@ async function lookupSiteForURL(
* GITBOOK_MODE=single
* When serving a single space, configured using GITBOOK_SPACE_ID and GITBOOK_TOKEN.
*/
-function lookupSiteInSingleMode(ctx: GitBookContext, url: URL): LookupResult {
+async function lookupSiteInSingleMode(url: URL): Promise {
const spaceId = process.env.GITBOOK_SPACE_ID;
if (!spaceId) {
throw new Error(
@@ -414,7 +410,8 @@ function lookupSiteInSingleMode(ctx: GitBookContext, url: URL): LookupResult {
);
}
- const apiToken = getDefaultAPIToken(api(ctx).client.endpoint);
+ const apiCtx = await api();
+ const apiToken = getDefaultAPIToken(apiCtx.client.endpoint);
if (!apiToken) {
throw new Error(
`Missing GITBOOK_TOKEN environment variable. It should be passed when using GITBOOK_MODE=single.`,
@@ -435,11 +432,7 @@ function lookupSiteInSingleMode(ctx: GitBookContext, url: URL): LookupResult {
* GITBOOK_MODE=proxy
* When proxying a site on a different base URL.
*/
-async function lookupSiteInProxy(
- ctx: GitBookContext,
- request: NextRequest,
- url: URL,
-): Promise {
+async function lookupSiteInProxy(request: NextRequest, url: URL): Promise {
const rawSiteUrl = request.headers.get('x-gitbook-site-url');
if (!rawSiteUrl) {
throw new Error(
@@ -450,20 +443,16 @@ async function lookupSiteInProxy(
const siteUrl = new URL(rawSiteUrl);
siteUrl.pathname = joinPath(siteUrl.pathname, url.pathname);
- return await lookupSiteInMultiMode(ctx, request, siteUrl);
+ return await lookupSiteInMultiMode(request, siteUrl);
}
/**
* GITBOOK_MODE=multi
* When serving multi spaces based on the current URL.
*/
-async function lookupSiteInMultiMode(
- ctx: GitBookContext,
- request: NextRequest,
- url: URL,
-): Promise {
+async function lookupSiteInMultiMode(request: NextRequest, url: URL): Promise {
const visitorAuthToken = getVisitorToken(request, url);
- const lookup = await lookupSiteByAPI(ctx, url, visitorAuthToken);
+ const lookup = await lookupSiteByAPI(url, visitorAuthToken);
return {
...lookup,
...('basePath' in lookup && visitorAuthToken
@@ -483,7 +472,6 @@ async function lookupSiteInMultiMode(
* - /~space|~site/:id/~revisions/:revisionId/:path
*/
async function lookupSiteOrSpaceInMultiIdMode(
- ctx: GitBookContext,
request: NextRequest,
url: URL,
): Promise {
@@ -562,8 +550,9 @@ async function lookupSiteOrSpaceInMultiIdMode(
// invalidated when trying to preview the site with different visitor
// attributes.
const contextId = decoded.claims ? hash(decoded.claims) : undefined;
+ const apiCtx = await api();
const gitbookAPI = new GitBookAPI({
- endpoint: apiEndpoint ?? api(ctx).client.endpoint,
+ endpoint: apiEndpoint ?? apiCtx.client.endpoint,
authToken: apiToken,
userAgent: userAgent(),
});
@@ -572,7 +561,7 @@ async function lookupSiteOrSpaceInMultiIdMode(
// (the cache is not dependend on the auth token, so it could leak data)
if (source.kind === 'space') {
await withAPI({ client: gitbookAPI, contextId }, () =>
- getSpace.revalidate(ctx, source.id, undefined),
+ getSpace.revalidate(source.id, undefined),
);
}
@@ -580,7 +569,7 @@ async function lookupSiteOrSpaceInMultiIdMode(
// (the cache is not dependend on the auth token, so it could leak data)
if (source.kind === 'site') {
await withAPI({ client: gitbookAPI, contextId }, () =>
- getPublishedContentSite.revalidate(ctx, {
+ getPublishedContentSite.revalidate({
organizationId: decoded.organization,
siteId: source.id,
siteShareKey: undefined,
@@ -630,11 +619,7 @@ async function lookupSiteOrSpaceInMultiIdMode(
* GITBOOK_MODE=multi-path
* When serving multi spaces with the url passed in the path.
*/
-async function lookupSiteInMultiPathMode(
- ctx: GitBookContext,
- request: NextRequest,
- url: URL,
-): Promise {
+async function lookupSiteInMultiPathMode(request: NextRequest, url: URL): Promise {
// Skip useless requests
if (
url.pathname === '/favicon.ico' ||
@@ -673,7 +658,7 @@ async function lookupSiteInMultiPathMode(
const visitorAuthToken = getVisitorToken(request, target);
- const lookup = await lookupSiteByAPI(ctx, target, visitorAuthToken);
+ const lookup = await lookupSiteByAPI(target, visitorAuthToken);
if ('error' in lookup) {
return lookup;
}
@@ -711,7 +696,6 @@ async function lookupSiteInMultiPathMode(
* To optimize caching, we try multiple lookup alternatives and return the first one that matches.
*/
async function lookupSiteByAPI(
- ctx: GitBookContext,
lookupURL: URL,
visitorTokenLookup: VisitorTokenLookup,
): Promise {
@@ -729,7 +713,6 @@ async function lookupSiteByAPI(
const result = await race(lookup.urls, async (alternative, { signal }) => {
const data = await getPublishedContentByUrl(
- ctx,
alternative.url,
visitorTokenLookup?.token,
redirectOnError || undefined,