Fix previews for sites in multi-id mode (#2289)

This commit is contained in:
Taran Vohra
2024-04-18 18:19:52 +05:30
committed by GitHub
parent bf450e5cc7
commit 4005895337
7 changed files with 123 additions and 33 deletions
@@ -1,5 +1,5 @@
import { getSpaceLanguage, t } from '@/intl/server';
import { getSiteSpaceLayoutData, getSpaceLayoutData } from '@/lib/api';
import { getCurrentSiteLayoutData, getSpaceLayoutData } from '@/lib/api';
import { tcls } from '@/lib/tailwind';
import { getContentPointer } from '../../fetch';
@@ -7,7 +7,7 @@ import { getContentPointer } from '../../fetch';
export default async function NotFound() {
const pointer = getContentPointer();
const { customization } = await ('siteId' in pointer
? getSiteSpaceLayoutData(pointer)
? getCurrentSiteLayoutData(pointer)
: getSpaceLayoutData(pointer.spaceId));
const language = getSpaceLanguage(customization);
@@ -7,8 +7,8 @@ import React from 'react';
import { getContentPointer } from '@/app/(space)/fetch';
import {
getCollection,
getCurrentSiteCustomization,
getSite,
getSiteSpaceCustomization,
getSpace,
getSpaceCustomization,
} from '@/lib/api';
@@ -46,7 +46,7 @@ export async function GET(req: NextRequest) {
const [space, customization] = await Promise.all([
getSpace(spaceId),
'siteId' in pointer ? getSiteSpaceCustomization(pointer) : getSpaceCustomization(spaceId),
'siteId' in pointer ? getCurrentSiteCustomization(pointer) : getSpaceCustomization(spaceId),
]);
const parent =
'siteId' in pointer
+3 -3
View File
@@ -22,7 +22,7 @@ import {
getSpace,
getSpaceCustomization,
getSpaceContentData,
getSiteSpaceCustomization,
getCurrentSiteCustomization,
} from '@/lib/api';
import { pagePDFContainerId, PageHrefContext, absoluteHref } from '@/lib/links';
import { resolvePageId } from '@/lib/pages';
@@ -43,7 +43,7 @@ export async function generateMetadata(): Promise<Metadata> {
const [space, customization] = await Promise.all([
getSpace(contentPointer.spaceId),
'siteId' in contentPointer
? getSiteSpaceCustomization(contentPointer)
? getCurrentSiteCustomization(contentPointer)
: getSpaceCustomization(contentPointer.spaceId),
]);
@@ -69,7 +69,7 @@ export default async function PDFHTMLOutput(props: { searchParams: { [key: strin
// Load the content,
const [customization, { space, contentTarget, pages: rootPages }] = await Promise.all([
'siteId' in contentPointer
? getSiteSpaceCustomization(contentPointer)
? getCurrentSiteCustomization(contentPointer)
: getSpaceCustomization(contentPointer.spaceId),
getSpaceContentData(contentPointer),
]);
+5 -5
View File
@@ -10,7 +10,7 @@ import {
getSpaceData,
ContentTarget,
SiteContentPointer,
getSiteSpaceData,
getCurrentSiteData,
getSite,
getSiteSpaces,
} from '@/lib/api';
@@ -40,14 +40,14 @@ export function getContentPointer(): ContentPointer | SiteContentPointer {
if (siteId) {
const organizationId = headerSet.get('x-gitbook-content-organization');
const siteSpaceId = headerSet.get('x-gitbook-content-site-space');
if (!organizationId || !siteSpaceId) {
if (!organizationId) {
throw new Error('Missing site content headers');
}
const siteContent: SiteContentPointer = {
siteId,
spaceId,
siteSpaceId,
siteSpaceId: siteSpaceId ?? undefined,
organizationId,
revisionId: headerSet.get('x-gitbook-content-revision') ?? undefined,
changeRequestId: headerSet.get('x-gitbook-content-changerequest') ?? undefined,
@@ -71,7 +71,7 @@ export async function fetchSpaceData() {
const [{ space, contentTarget, pages, customization, scripts }, parentSite] = await Promise.all(
'siteId' in content
? [getSiteSpaceData(content), fetchParentSite(content.organizationId, content.siteId)]
? [getCurrentSiteData(content), fetchParentSite(content.organizationId, content.siteId)]
: [getSpaceData(content)],
);
@@ -96,7 +96,7 @@ export async function fetchSpaceData() {
export async function fetchPageData(params: PagePathParams | PageIdParams) {
const content = getContentPointer();
const { space, contentTarget, pages, customization, scripts } = await ('siteId' in content
? getSiteSpaceData(content)
? getCurrentSiteData(content)
: getSpaceData(content));
const page = await resolvePage(contentTarget, pages, params);
+2 -2
View File
@@ -11,7 +11,7 @@ import colors from 'tailwindcss/colors';
import { emojiFontClassName } from '@/components/primitives';
import { fonts, ibmPlexMono } from '@/fonts';
import { getSpaceLanguage } from '@/intl/server';
import { getSiteSpaceLayoutData, getSpaceLayoutData } from '@/lib/api';
import { getCurrentSiteLayoutData, getSpaceLayoutData } from '@/lib/api';
import { hexToRgb, shadesOfColor } from '@/lib/colors';
import { tcls } from '@/lib/tailwind';
@@ -28,7 +28,7 @@ export default async function SpaceRootLayout(props: { children: React.ReactNode
const pointer = getContentPointer();
const { customization } = await ('siteId' in pointer
? getSiteSpaceLayoutData(pointer)
? getCurrentSiteLayoutData(pointer)
: getSpaceLayoutData(pointer.spaceId));
const headerTheme = generateHeaderTheme(customization);
const language = getSpaceLanguage(customization);
+92 -15
View File
@@ -43,7 +43,10 @@ export interface ContentPointer {
export interface SiteContentPointer extends ContentPointer {
organizationId: string;
siteId: string;
siteSpaceId: string;
/**
* ID of the siteSpace can be undefined when rendering in multi-id mode (for site previews)
*/
siteSpaceId: string | undefined;
}
/**
@@ -595,7 +598,7 @@ export const getDocument = cache(
/**
* Get the customization settings for a site-space from the API.
*/
export const getSiteSpaceCustomizationFromAPI = cache(
const getSiteSpaceCustomizationFromAPI = cache(
'api.getSiteSpaceCustomizationById',
async (
organizationId: string,
@@ -625,10 +628,33 @@ export const getSiteSpaceCustomizationFromAPI = cache(
},
);
/**
* Get the customization settings for a site from the API.
*/
const getSiteCustomizationFromAPI = cache(
'api.getSiteCustomizationById',
async (organizationId: string, siteId: string, options: CacheFunctionOptions) => {
const response = await api().orgs.getSiteCustomizationById(organizationId, siteId, {
signal: options.signal,
...noCacheFetchOptions,
});
return cacheResponse(response, {
revalidateBefore: 60 * 60,
tags: [
getAPICacheTag({
tag: 'site',
organization: organizationId,
site: siteId,
}),
],
});
},
);
/**
* Get the customization settings for a site space from the API.
*/
export async function getSiteSpaceCustomization(args: {
async function getSiteSpaceCustomization(args: {
organizationId: string;
siteId: string;
siteSpaceId: string;
@@ -657,6 +683,33 @@ export async function getSiteSpaceCustomization(args: {
return raw;
}
/**
* Get the customization settings for a site space from the API.
*/
async function getSiteCustomization(args: {
organizationId: string;
siteId: string;
}): Promise<SiteCustomizationSettings> {
const headersList = headers();
const raw = await getSiteCustomizationFromAPI(args.organizationId, args.siteId);
const extend = headersList.get('x-gitbook-customization');
if (extend) {
try {
const parsed = rison.decode_object<Partial<SiteCustomizationSettings>>(extend);
return { ...raw, ...parsed };
} catch (error) {
console.error(
`Failed to parse x-gitbook-customization header (ignored): ${
(error as Error).stack ?? (error as Error).message ?? error
}`,
);
}
}
return raw;
}
/**
* Get the infos about a site by its ID.
*/
@@ -713,12 +766,12 @@ export const getSiteIntegrationScripts = cache(
);
/**
* Fetch all the data to render a site-space at once.
* Fetch all the data to render the current site at once.
*/
export async function getSiteSpaceData(pointer: SiteContentPointer) {
export async function getCurrentSiteData(pointer: SiteContentPointer) {
const [{ space, pages, contentTarget }, { customization, scripts }] = await Promise.all([
getSpaceData(pointer),
getSiteSpaceLayoutData(pointer),
getCurrentSiteLayoutData(pointer),
]);
return {
@@ -731,20 +784,24 @@ export async function getSiteSpaceData(pointer: SiteContentPointer) {
}
/**
* Fetch all the layout data about a site-space at once.
* Fetch all the layout data about the current site at once.
*/
export async function getSiteSpaceLayoutData(args: {
export async function getCurrentSiteLayoutData(args: {
organizationId: string;
siteId: string;
siteSpaceId: string;
spaceId: string;
siteSpaceId: string | undefined;
}) {
const [customization, scripts] = await Promise.all([
getSiteSpaceCustomization({
organizationId: args.organizationId,
siteId: args.siteId,
siteSpaceId: args.siteSpaceId,
}),
args.siteSpaceId
? getSiteSpaceCustomization({
organizationId: args.organizationId,
siteId: args.siteId,
siteSpaceId: args.siteSpaceId,
})
: getSiteCustomization({
organizationId: args.organizationId,
siteId: args.siteId,
}),
getSiteIntegrationScripts(args.organizationId, args.siteId),
]);
@@ -754,6 +811,26 @@ export async function getSiteSpaceLayoutData(args: {
};
}
/**
* Get the customization settings for the current site from the API.
*/
export async function getCurrentSiteCustomization(args: {
organizationId: string;
siteId: string;
siteSpaceId: string | undefined;
}): Promise<SiteCustomizationSettings> {
return args.siteSpaceId
? getSiteSpaceCustomization({
organizationId: args.organizationId,
siteId: args.siteId,
siteSpaceId: args.siteSpaceId,
})
: getSiteCustomization({
organizationId: args.organizationId,
siteId: args.siteId,
});
}
/**
* Get the customization settings for a space from the API.
*/
+17 -4
View File
@@ -1,6 +1,7 @@
import { GitBookAPI } from '@gitbook/api';
import * as Sentry from '@sentry/nextjs';
import assertNever from 'assert-never';
import jwt from 'jsonwebtoken';
import type { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
import { NextResponse, NextRequest } from 'next/server';
@@ -14,7 +15,7 @@ import {
withAPI,
getSpaceLayoutData,
DEFAULT_API_ENDPOINT,
getSiteSpaceLayoutData,
getCurrentSiteLayoutData,
} from '@/lib/api';
import { race } from '@/lib/async';
import { buildVersion } from '@/lib/build';
@@ -73,6 +74,13 @@ export type LookupResult = PublishedContentWithCache & {
cookies?: LookupCookies;
};
interface ContentAPITokenPayload {
organization: string;
spaces: string[];
collection?: string;
site?: string;
}
/**
* Middleware to lookup the space to render.
* It takes as input a request with an URL, and a set of headers:
@@ -170,11 +178,10 @@ export async function middleware(request: NextRequest) {
);
const { scripts } = await ('site' in resolved
? getSiteSpaceLayoutData({
? getCurrentSiteLayoutData({
organizationId: resolved.organization,
siteId: resolved.site,
siteSpaceId: resolved.siteSpace,
spaceId: resolved.space,
})
: getSpaceLayoutData(resolved.space));
return getContentSecurityPolicy(scripts, nonce);
@@ -196,7 +203,9 @@ export async function middleware(request: NextRequest) {
if ('site' in resolved) {
headers.set('x-gitbook-content-organization', resolved.organization);
headers.set('x-gitbook-content-site', resolved.site);
headers.set('x-gitbook-content-site-space', resolved.siteSpace);
if (resolved.siteSpace) {
headers.set('x-gitbook-content-site-space', resolved.siteSpace);
}
}
if (resolved.revision) {
headers.set('x-gitbook-content-revision', resolved.revision);
@@ -475,10 +484,14 @@ async function lookupSpaceInMultiIdMode(request: NextRequest, url: URL): Promise
};
}
const decoded = jwt.decode(apiToken) as ContentAPITokenPayload;
return {
space: spaceId,
changeRequest: changeRequestId,
revision: revisionId,
site: decoded.site,
organization: decoded.organization,
basePath: normalizePathname(basePathParts.join('/')),
pathname: normalizePathname(pathSegments.join('/')),
apiToken,