From 4b90635d66c0121ddb763ca292c3f2e8a4fd8493 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Fri, 18 Apr 2025 17:57:01 +0100 Subject: [PATCH 01/15] First draft of using content token to fetch and render reusable content across spaces. --- packages/gitbook-v2/src/lib/data/api.ts | 19 +++++++++++-------- packages/gitbook-v2/src/lib/data/types.ts | 11 ++++++++--- .../DocumentView/ReusableContent.tsx | 6 +++++- packages/gitbook/src/lib/api.ts | 4 +++- packages/gitbook/src/lib/references.tsx | 8 +++++++- packages/gitbook/src/lib/v1.ts | 3 ++- 6 files changed, 36 insertions(+), 15 deletions(-) diff --git a/packages/gitbook-v2/src/lib/data/api.ts b/packages/gitbook-v2/src/lib/data/api.ts index 20e495656e..33266bba1e 100644 --- a/packages/gitbook-v2/src/lib/data/api.ts +++ b/packages/gitbook-v2/src/lib/data/api.ts @@ -125,13 +125,14 @@ export function createDataFetcher( ); }, getReusableContent(params) { - return trace('getReusableContent', () => - getReusableContent(input, { + return trace('getReusableContent', () => { + const withToken = params.apiToken ? { ...input, apiToken: params.apiToken } : input; + return getReusableContent(withToken, { spaceId: params.spaceId, revisionId: params.revisionId, reusableContentId: params.reusableContentId, - }) - ); + }); + }); }, getLatestOpenAPISpecVersionContent(params) { return trace('getLatestOpenAPISpecVersionContent', () => @@ -158,12 +159,14 @@ export function createDataFetcher( ); }, getDocument(params) { - return trace('getDocument', () => - getDocument(input, { + return trace('getDocument', () => { + const withToken = params.apiToken ? { ...input, apiToken: params.apiToken } : input; + + return getDocument(withToken, { spaceId: params.spaceId, documentId: params.documentId, - }) - ); + }); + }); }, getComputedDocument(params) { return trace('getComputedDocument', () => diff --git a/packages/gitbook-v2/src/lib/data/types.ts b/packages/gitbook-v2/src/lib/data/types.ts index 178a0ba77d..6d9076206f 100644 --- a/packages/gitbook-v2/src/lib/data/types.ts +++ b/packages/gitbook-v2/src/lib/data/types.ts @@ -109,9 +109,12 @@ export interface GitBookDataFetcher { /** * Get a document by its space ID and document ID. */ - getDocument(params: { spaceId: string; documentId: string }): Promise< - DataFetcherResponse - >; + getDocument(params: { + spaceId: string; + documentId: string; + /** Optionally override the API token used to fetch the content. */ + apiToken?: string; + }): Promise>; /** * Get a computed document by its space ID and computed source. @@ -130,6 +133,8 @@ export interface GitBookDataFetcher { spaceId: string; revisionId: string; reusableContentId: string; + /** Optionally override the API token used to fetch the content. */ + apiToken?: string; }): Promise>; /** diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx index e95ffc7545..97a2fe868e 100644 --- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -13,7 +13,10 @@ export async function ReusableContent(props: BlockProps => { + ishouldbeimplmeneted; const hasRevisionInMemory = await getRevision.hasInMemory(spaceId, revisionId, { metadata: false, }); diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index ab941e43c5..078033e1dc 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -64,6 +64,11 @@ export interface ResolveContentRefOptions { * @default false */ resolveAsAbsoluteURL?: boolean; + + /** + * Override the API token used to fetch any content. + */ + apiToken?: string; } /** @@ -233,9 +238,10 @@ export async function resolveContentRef( case 'reusable-content': { const reusableContent = await getDataOrNull( dataFetcher.getReusableContent({ - spaceId: space.id, + spaceId: contentRef.space ?? space.id, revisionId, reusableContentId: contentRef.reusableContent, + apiToken: options.apiToken, }) ); if (!reusableContent) { diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index ea5219816b..51e50642b4 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -210,7 +210,8 @@ async function getDataFetcherV1(): Promise { const reusableContent = await getReusableContent( params.spaceId, params.revisionId, - params.reusableContentId + params.reusableContentId, + params.apiToken ); if (!reusableContent) { From 1e42d9e46935856a6bb2e4ab8f0be3530587783a Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Wed, 23 Apr 2025 10:59:21 +0100 Subject: [PATCH 02/15] Fix ref --- packages/gitbook/src/lib/references.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index 078033e1dc..a226aa3036 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -236,9 +236,10 @@ export async function resolveContentRef( } case 'reusable-content': { + const spaceId = contentRef.space ?? space.id; const reusableContent = await getDataOrNull( dataFetcher.getReusableContent({ - spaceId: contentRef.space ?? space.id, + spaceId, revisionId, reusableContentId: contentRef.reusableContent, apiToken: options.apiToken, @@ -248,7 +249,7 @@ export async function resolveContentRef( return null; } return { - href: getGitBookAppHref(`/s/${space.id}`), + href: getGitBookAppHref(`/s/${spaceId}/~/reusable/${reusableContent.id}`), text: reusableContent.title, active: false, reusableContent, From cfb97dabcee19c92a4c3df0d39f2c4d01292026a Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Wed, 23 Apr 2025 16:46:01 +0100 Subject: [PATCH 03/15] Bump API --- bun.lock | 34 +++++++++++++++------------------- package.json | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index 323725884f..93788c7b65 100644 --- a/bun.lock +++ b/bun.lock @@ -49,7 +49,7 @@ }, "packages/gitbook": { "name": "gitbook", - "version": "0.10.1", + "version": "0.11.1", "dependencies": { "@gitbook/api": "*", "@gitbook/cache-do": "workspace:*", @@ -231,7 +231,7 @@ }, "packages/react-openapi": { "name": "@gitbook/react-openapi", - "version": "1.1.10", + "version": "1.2.1", "dependencies": { "@gitbook/openapi-parser": "workspace:*", "@scalar/api-client-react": "^1.2.19", @@ -259,7 +259,7 @@ }, "overrides": { "@codemirror/state": "6.4.1", - "@gitbook/api": "0.111.0", + "@gitbook/api": "0.113.0", "react": "18.3.1", "react-dom": "18.3.1", }, @@ -628,7 +628,7 @@ "@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="], - "@gitbook/api": ["@gitbook/api@0.111.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-E5Pk28kPD4p6XNWdwFM9pgDijdByseIZQqcFK+/hoW5tEZa5Yw/plRKJyN1hmwfPL6SKq6Maf0fbIzTQiVXyQQ=="], + "@gitbook/api": ["@gitbook/api@0.113.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-PWMeAkdm4bHSl3b5OmtcmskZ6qRkkDhauCPybo8sGnjS03O14YAUtubAQiNCKX/uwbs+yiQ8KRPyeIwn+g42yw=="], "@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"], @@ -1094,7 +1094,7 @@ "@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="], - "@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.10", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-6MSgvpNKu/anZy96dn8tXQZo1PuDCoeB4m2ZLLDS4vC2zaTnuNBvvQHx+gjwXNKWhTbIVy8bQpYBzlMAYnFNcQ=="], + "@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="], "@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="], @@ -3714,14 +3714,10 @@ "@rollup/pluginutils/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], - "@scalar/api-client/@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="], - "@scalar/api-client/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], "@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="], - "@scalar/import/@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="], - "@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], "@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="], @@ -4032,7 +4028,7 @@ "gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "gitbook-v2/next": ["next@15.3.1-canary.8", "", { "dependencies": { "@next/env": "15.3.1-canary.8", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.1-canary.8", "@next/swc-darwin-x64": "15.3.1-canary.8", "@next/swc-linux-arm64-gnu": "15.3.1-canary.8", "@next/swc-linux-arm64-musl": "15.3.1-canary.8", "@next/swc-linux-x64-gnu": "15.3.1-canary.8", "@next/swc-linux-x64-musl": "15.3.1-canary.8", "@next/swc-win32-arm64-msvc": "15.3.1-canary.8", "@next/swc-win32-x64-msvc": "15.3.1-canary.8", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Of5a3BTTIl/iUvL2a9Jh7m7G/H8z4Pj5Vs54CLvcdadokxSNgLOpjzbDgFR8J4PawLx6+MOMy19m9Cvr6EPGug=="], + "gitbook-v2/next": ["next@15.4.0-canary.5", "", { "dependencies": { "@next/env": "15.4.0-canary.5", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.0-canary.5", "@next/swc-darwin-x64": "15.4.0-canary.5", "@next/swc-linux-arm64-gnu": "15.4.0-canary.5", "@next/swc-linux-arm64-musl": "15.4.0-canary.5", "@next/swc-linux-x64-gnu": "15.4.0-canary.5", "@next/swc-linux-x64-musl": "15.4.0-canary.5", "@next/swc-win32-arm64-msvc": "15.4.0-canary.5", "@next/swc-win32-x64-msvc": "15.4.0-canary.5", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Qpv129bewAf84lzjvGNbvjGIgkWVe/wPi4B615ltNSEsvb4K+sroa24WVggdDot1rnmfs+uSirvUhETPv1h0gA=="], "global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="], @@ -4908,23 +4904,23 @@ "gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], - "gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.8", "", {}, "sha512-ShZTo0hNhbTRrp7k6oUDSck4Xx4hhfSeLBp35jvGaw1QMZzWYr5v/oc0kEt0bfMdl+833flwKV7kFR3BnrULfg=="], + "gitbook-v2/next/@next/env": ["@next/env@15.4.0-canary.5", "", {}, "sha512-4z6OcPCFze1RKmQ0LraHZ5EhA4UHvmqvzvlrAxcfT9JUDQiRZgbWDGn8CrEAG32gtIDG8jyA9/i4YprNbam1Uw=="], - "gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZaDynM+pbrnLLlBAxH/CDGp9KN79OFrLcT1ejlWyo86V3SS9Gyqr4nmTuvTevByTTpr1VHReQel8Zbq0Pttu7Q=="], + "gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.0-canary.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JvgdecekiZP9u1CrLt/why7ZhEfq5Q81eXhqdt2GLgQ9orYhl1mPLQRyKgoK2scpYB22vkis32v9to3CEQ2tpA=="], - "gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-BsMR8WqeCDAX8C9RYAO8TI4ttpuqKk2oYMb1+bCrOYi857SMfB4vWD4PWmMvj7mwFXGqrxly4W6CBQlD66A+fg=="], + "gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.0-canary.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-0l81meyl5mmEu+seBJ+EO4tkHAarRZHo9XmMhBaSEyRmq2GVh1snGg+b5/05FDgsWcdPFcP5CrnN49Qmhwkm+Q=="], - "gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-xL+K2SW+/46j/KnKNf1gizM1bxwcEaE56eCEG9RPoYS/lfxHLuHcR9O2MlcPI90g/rIN2HXmHeuKRbXbnVy15g=="], + "gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.0-canary.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-YA0ENPjHv7SeiZ3thEKHxKY1tMP8u+tXgpignt8guAPKEXIgXWKe970EbByRwvu3DYRPVf4MZtS038m164UrNA=="], - "gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-bzXlCUXkjIRsMTb6rr7OsWEQmdO2rZgKijnMGBJzEpa9ROq95VJTF+rdyrLmHuc4fPsAGDn/C18V3E1YOi4ipQ=="], + "gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.0-canary.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZbdHlsKy2FlCt2Kwovfy3ziJO+hTtNnjW4Kh3TiUe0PrHrUThk6OJ2kRSwOIbyJY1Q93vKc4KVkmwhjjzym+VQ=="], - "gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.8", "", { "os": "linux", "cpu": "x64" }, "sha512-snbPQ9th7eoYYMZpNGhEvX4EbqGjjkiXzTEm2F/oDadlZR2wI6egtfQHiZqeczL7XwG3M66hBJ1/U20wdTDtvA=="], + "gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.0-canary.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uU2iEFTqvtuVaYX0D5URsEOMpvXR0AakqPfhvIVBjgSB9WSow8NVcUxhFzQzPwJEzOoWekoQaHuMNiMk8bwQSA=="], - "gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.8", "", { "os": "linux", "cpu": "x64" }, "sha512-5hmcaGazc3w6rg/gbQOrmuw0kKShf4Egs0JlUPop/ZiRDfZO5KlyrGY0hUD+2pLG7Yx3B/fTbj86cHKfQZDMBw=="], + "gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.0-canary.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FAh6fHKSxzHh+LuLWzEeA3oMWFeNkgGYH6eJGoiKjlfavbr3nUr581Gafja2sjD2QyP7fQ2BX3f1RNuH4Vl/Sw=="], - "gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-t6uwWC/UbQ8CQGyBVbEmpcJC41yAvz2eZZGec9EoO0ZyQ/fbvjOkqfPnf+WcjWifWNeuiWogjN1UBg7YK9IaVA=="], + "gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.0-canary.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Bk9IFKOKjN6nj3wx05sLfMfMQqk7+xT5FaFtOFD+nN0iqwTiW1nGXBvyHJwCDty6dSIGq8RDqVz/dEFZbw0Egg=="], - "gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.8", "", { "os": "win32", "cpu": "x64" }, "sha512-93gNqVYwlr9bz6Pm5dQnqjpuOEQyvDre0+H39UqS7h2KuFh3sf2MejhAEr2uFEux4mb7TN0PlohaihO7YxJ3fw=="], + "gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.0-canary.5", "", { "os": "win32", "cpu": "x64" }, "sha512-I8e5tOYWh0YTNCarU24Qfy9PBp1pv4iAp5KMcrsGv2BWXmqgN7PacBKmM/B0BUqDRFr5H5JJHOZgV08SnixnQg=="], "gitbook-v2/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], diff --git a/package.json b/package.json index 736eabe411..4c6eb519fd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@codemirror/state": "6.4.1", "react": "18.3.1", "react-dom": "18.3.1", - "@gitbook/api": "0.111.0" + "@gitbook/api": "0.113.0" }, "private": true, "scripts": { From b83e61d67722b78f65ae31b6c5ab7f7030e8ee76 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:22:56 +0100 Subject: [PATCH 04/15] WIP --- packages/gitbook-v2/src/lib/data/types.ts | 2 +- .../DocumentView/ReusableContent.tsx | 3 +- packages/gitbook/src/lib/api.ts | 24 ++++++----- packages/gitbook/src/lib/cache/cache.ts | 2 + packages/gitbook/src/lib/references.tsx | 40 ++++++++++++++++--- packages/gitbook/src/lib/v1.ts | 4 +- 6 files changed, 54 insertions(+), 21 deletions(-) diff --git a/packages/gitbook-v2/src/lib/data/types.ts b/packages/gitbook-v2/src/lib/data/types.ts index 6d9076206f..ee137a52dd 100644 --- a/packages/gitbook-v2/src/lib/data/types.ts +++ b/packages/gitbook-v2/src/lib/data/types.ts @@ -49,7 +49,7 @@ export interface GitBookDataFetcher { /** * Get a space by its ID. */ - getSpace(params: { spaceId: string; shareKey: string | undefined }): Promise< + getSpace(params: { spaceId: string; shareKey: string | undefined; apiToken?: string | undefined }): Promise< DataFetcherResponse >; diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx index 97a2fe868e..ac9521acdd 100644 --- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -21,9 +21,10 @@ export async function ReusableContent(props: BlockProps { +export async function api(tokenOverride?: string): Promise { const existing = apiSyncStorage.getStore(); - if (existing) { + if (existing && (!tokenOverride || existing.client.authToken === tokenOverride)) { return existing; } const headersList = await headers(); - const apiToken = headersList.get('x-gitbook-token'); + const apiToken = tokenOverride ?? headersList.get('x-gitbook-token'); const contextId = headersList.get('x-gitbook-token-context') ?? undefined; if (!apiToken) { @@ -266,7 +266,8 @@ export const getPublishedContentByUrl = cache({ ); const parsed = parseCacheResponse(response); - + + // biome-ignore lint/suspicious/noConsole: log the ttl of the token console.log( `Parsed ttl: ${parsed.ttl} at ${Date.now()}, for ${'apiToken' in response.data ? response.data.apiToken : ''}` ); @@ -307,7 +308,7 @@ export const getSpace = cache({ name: 'api.getSpace', tag: (spaceId) => getCacheTag({ tag: 'space', space: spaceId }), get: async (spaceId: string, shareKey: string | undefined, options: CacheFunctionOptions) => { - const apiCtx = await api(); + const apiCtx = await api(options.apiToken); const response = await apiCtx.client.spaces.getSpaceById( spaceId, { @@ -370,6 +371,8 @@ interface GetRevisionOptions { * These options don't impact the cache key and it means revisions can be shared between different fetches with different metadata options. */ metadata: boolean; + + apiToken?: string; } const getAPIContextId = async () => { @@ -392,7 +395,7 @@ export const getRevision = cache({ fetchOptions: GetRevisionOptions, options: CacheFunctionOptions ) => { - const apiCtx = await api(); + const apiCtx = await api(fetchOptions.apiToken); const response = await apiCtx.client.spaces.getRevisionById( spaceId, revisionId, @@ -546,7 +549,7 @@ const getRevisionReusableContentById = cache({ options: CacheFunctionOptions ) => { try { - const apiCtx = await api(); + const apiCtx = await api(options.apiToken); const response = await apiCtx.client.spaces.getReusableContentInRevisionById( spaceId, revisionId, @@ -674,20 +677,19 @@ export const getReusableContent = async ( reusableContentId: string, apiToken?: string ): Promise => { - ishouldbeimplmeneted; const hasRevisionInMemory = await getRevision.hasInMemory(spaceId, revisionId, { metadata: false, }); if (hasRevisionInMemory) { - const revision = await getRevision(spaceId, revisionId, { metadata: false }); + const revision = await getRevision(spaceId, revisionId, { metadata: false, apiToken }); return ( revision.reusableContents.find( (reusableContent) => reusableContent.id === reusableContentId ) ?? null ); } - return getRevisionReusableContentById(spaceId, revisionId, reusableContentId); + return getRevisionReusableContentById(spaceId, revisionId, reusableContentId, { signal: undefined, apiToken }); }; /** @@ -700,7 +702,7 @@ export const getDocument = cache({ tagImmutable: true, getKeySuffix: getAPIContextId, get: async (spaceId: string, documentId: string, options: CacheFunctionOptions) => { - const apiCtx = await api(); + const apiCtx = await api(options.apiToken); const response = await apiCtx.client.spaces.getDocumentById( spaceId, documentId, diff --git a/packages/gitbook/src/lib/cache/cache.ts b/packages/gitbook/src/lib/cache/cache.ts index 065368e400..152c0bf60f 100644 --- a/packages/gitbook/src/lib/cache/cache.ts +++ b/packages/gitbook/src/lib/cache/cache.ts @@ -11,6 +11,8 @@ import type { CacheBackend, CacheEntry } from './types'; export type CacheFunctionOptions = { signal: AbortSignal | undefined; + + apiToken?: string }; export type CacheFunction = (( diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index a226aa3036..89ad3e6711 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -42,7 +42,7 @@ export interface ResolvedContentRef { /** Page document resolved from the content ref */ page?: RevisionPageDocument; /** Resolved reusable content, if the ref points to reusable content on a revision. */ - reusableContent?: RevisionReusableContent; + reusableContent?: RevisionReusableContent & { space: string }; /** Resolve OpenAPI spec filesystem. */ openAPIFilesystem?: Filesystem; } @@ -236,23 +236,51 @@ export async function resolveContentRef( } case 'reusable-content': { - const spaceId = contentRef.space ?? space.id; + const resolvedSpace = await (async () => { + if (!contentRef.space || contentRef.space === context.space.id) { + return { spaceId: context.space.id, revisionId }; + } + + const space = await getDataOrNull( + dataFetcher.getSpace({ + spaceId: contentRef.space, + shareKey: undefined, + apiToken: options.apiToken, + }) + ); + + if (!space) { + return null; + } + + return { spaceId: space.id, revisionId: space.revision }; + })(); + + if (!resolvedSpace) { + return null; + } + + const reusableContent = await getDataOrNull( dataFetcher.getReusableContent({ - spaceId, - revisionId, + spaceId: resolvedSpace.spaceId, + revisionId: resolvedSpace.revisionId, reusableContentId: contentRef.reusableContent, apiToken: options.apiToken, }) ); + if (!reusableContent) { return null; } return { - href: getGitBookAppHref(`/s/${spaceId}/~/reusable/${reusableContent.id}`), + href: getGitBookAppHref(`/s/${resolvedSpace.spaceId}/~/reusable/${reusableContent.id}`), text: reusableContent.title, active: false, - reusableContent, + reusableContent: { + ...reusableContent, + space: resolvedSpace.spaceId, + }, }; } diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index 51e50642b4..d2e10b2dff 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -114,7 +114,7 @@ async function getDataFetcherV1(): Promise { getSpace(params) { return wrapDataFetcherError(async () => { - return getSpace(params.spaceId, params.shareKey); + return getSpace(params.spaceId, params.shareKey, { signal: undefined, apiToken: params.apiToken }); }); }, @@ -161,7 +161,7 @@ async function getDataFetcherV1(): Promise { getDocument(params) { return wrapDataFetcherError(async () => { - const document = await getDocument(params.spaceId, params.documentId); + const document = await getDocument(params.spaceId, params.documentId, { signal: undefined, apiToken: params.apiToken }); if (!document) { throw new DataFetcherError('Document not found', 404); } From 733b8a27dc84e5ac528dce504a2bbe41a763c1e7 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:30:52 +0100 Subject: [PATCH 05/15] Self review --- packages/gitbook-v2/src/lib/data/types.ts | 8 +++-- .../DocumentView/ReusableContent.tsx | 1 - packages/gitbook/src/lib/api.ts | 7 +++-- packages/gitbook/src/lib/cache/cache.ts | 2 +- packages/gitbook/src/lib/references.tsx | 31 ++++++++++--------- packages/gitbook/src/lib/v1.ts | 10 ++++-- 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/gitbook-v2/src/lib/data/types.ts b/packages/gitbook-v2/src/lib/data/types.ts index ee137a52dd..cb48b31f7f 100644 --- a/packages/gitbook-v2/src/lib/data/types.ts +++ b/packages/gitbook-v2/src/lib/data/types.ts @@ -49,9 +49,11 @@ export interface GitBookDataFetcher { /** * Get a space by its ID. */ - getSpace(params: { spaceId: string; shareKey: string | undefined; apiToken?: string | undefined }): Promise< - DataFetcherResponse - >; + getSpace(params: { + spaceId: string; + shareKey: string | undefined; + apiToken?: string | undefined; + }): Promise>; /** * Get a change request by its space ID and change request ID. diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx index ac9521acdd..e1ac2605fe 100644 --- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -21,7 +21,6 @@ export async function ReusableContent(props: BlockProps'}` @@ -689,7 +689,10 @@ export const getReusableContent = async ( ) ?? null ); } - return getRevisionReusableContentById(spaceId, revisionId, reusableContentId, { signal: undefined, apiToken }); + return getRevisionReusableContentById(spaceId, revisionId, reusableContentId, { + signal: undefined, + apiToken, + }); }; /** diff --git a/packages/gitbook/src/lib/cache/cache.ts b/packages/gitbook/src/lib/cache/cache.ts index 152c0bf60f..e7f9a12f77 100644 --- a/packages/gitbook/src/lib/cache/cache.ts +++ b/packages/gitbook/src/lib/cache/cache.ts @@ -12,7 +12,7 @@ import type { CacheBackend, CacheEntry } from './types'; export type CacheFunctionOptions = { signal: AbortSignal | undefined; - apiToken?: string + apiToken?: string; }; export type CacheFunction = (( diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index 89ad3e6711..216a5c3ca3 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -41,8 +41,8 @@ export interface ResolvedContentRef { file?: RevisionFile; /** Page document resolved from the content ref */ page?: RevisionPageDocument; - /** Resolved reusable content, if the ref points to reusable content on a revision. */ - reusableContent?: RevisionReusableContent & { space: string }; + /** Resolved reusable content, if the ref points to reusable content on a revision. Also contains the space and revision used for resolution. */ + reusableContent?: RevisionReusableContent & { space: string; revision: string }; /** Resolve OpenAPI spec filesystem. */ openAPIFilesystem?: Filesystem; } @@ -236,9 +236,11 @@ export async function resolveContentRef( } case 'reusable-content': { - const resolvedSpace = await (async () => { + // Figure out which space and revision the reusable content is in. + const container: { space: string; revision: string } | null = await (async () => { + // without a space on the content ref, or if the space is the same as the current one, we can use the current revision. if (!contentRef.space || contentRef.space === context.space.id) { - return { spaceId: context.space.id, revisionId }; + return { space: context.space.id, revision: revisionId }; } const space = await getDataOrNull( @@ -248,38 +250,39 @@ export async function resolveContentRef( apiToken: options.apiToken, }) ); - + if (!space) { return null; } - return { spaceId: space.id, revisionId: space.revision }; + return { space: space.id, revision: space.revision }; })(); - if (!resolvedSpace) { + if (!container) { return null; } - - + const reusableContent = await getDataOrNull( dataFetcher.getReusableContent({ - spaceId: resolvedSpace.spaceId, - revisionId: resolvedSpace.revisionId, + spaceId: container.space, + revisionId: container.revision, reusableContentId: contentRef.reusableContent, apiToken: options.apiToken, }) ); - + if (!reusableContent) { return null; } + return { - href: getGitBookAppHref(`/s/${resolvedSpace.spaceId}/~/reusable/${reusableContent.id}`), + href: getGitBookAppHref(`/s/${container.space}/~/reusable/${reusableContent.id}`), text: reusableContent.title, active: false, reusableContent: { ...reusableContent, - space: resolvedSpace.spaceId, + space: container.space, + revision: container.revision, }, }; } diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index d2e10b2dff..b2ec6af0f0 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -114,7 +114,10 @@ async function getDataFetcherV1(): Promise { getSpace(params) { return wrapDataFetcherError(async () => { - return getSpace(params.spaceId, params.shareKey, { signal: undefined, apiToken: params.apiToken }); + return getSpace(params.spaceId, params.shareKey, { + signal: undefined, + apiToken: params.apiToken, + }); }); }, @@ -161,7 +164,10 @@ async function getDataFetcherV1(): Promise { getDocument(params) { return wrapDataFetcherError(async () => { - const document = await getDocument(params.spaceId, params.documentId, { signal: undefined, apiToken: params.apiToken }); + const document = await getDocument(params.spaceId, params.documentId, { + signal: undefined, + apiToken: params.apiToken, + }); if (!document) { throw new DataFetcherError('Document not found', 404); } From 0166e231ae317b7d1b5efccac42461ee217adfdd Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:35:58 +0100 Subject: [PATCH 06/15] self review --- packages/gitbook-v2/src/lib/data/types.ts | 19 ++++++------------- .../DocumentView/ReusableContent.tsx | 7 +++++-- packages/gitbook/src/lib/references.tsx | 10 ++++++---- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/gitbook-v2/src/lib/data/types.ts b/packages/gitbook-v2/src/lib/data/types.ts index cb48b31f7f..178a0ba77d 100644 --- a/packages/gitbook-v2/src/lib/data/types.ts +++ b/packages/gitbook-v2/src/lib/data/types.ts @@ -49,11 +49,9 @@ export interface GitBookDataFetcher { /** * Get a space by its ID. */ - getSpace(params: { - spaceId: string; - shareKey: string | undefined; - apiToken?: string | undefined; - }): Promise>; + getSpace(params: { spaceId: string; shareKey: string | undefined }): Promise< + DataFetcherResponse + >; /** * Get a change request by its space ID and change request ID. @@ -111,12 +109,9 @@ export interface GitBookDataFetcher { /** * Get a document by its space ID and document ID. */ - getDocument(params: { - spaceId: string; - documentId: string; - /** Optionally override the API token used to fetch the content. */ - apiToken?: string; - }): Promise>; + getDocument(params: { spaceId: string; documentId: string }): Promise< + DataFetcherResponse + >; /** * Get a computed document by its space ID and computed source. @@ -135,8 +130,6 @@ export interface GitBookDataFetcher { spaceId: string; revisionId: string; reusableContentId: string; - /** Optionally override the API token used to fetch the content. */ - apiToken?: string; }): Promise>; /** diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx index e1ac2605fe..e7dfb4a5ef 100644 --- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -21,11 +21,14 @@ export async function ReusableContent(props: BlockProps { // without a space on the content ref, or if the space is the same as the current one, we can use the current revision. @@ -244,10 +248,9 @@ export async function resolveContentRef( } const space = await getDataOrNull( - dataFetcher.getSpace({ + fetcher.getSpace({ spaceId: contentRef.space, shareKey: undefined, - apiToken: options.apiToken, }) ); @@ -263,11 +266,10 @@ export async function resolveContentRef( } const reusableContent = await getDataOrNull( - dataFetcher.getReusableContent({ + fetcher.getReusableContent({ spaceId: container.space, revisionId: container.revision, reusableContentId: contentRef.reusableContent, - apiToken: options.apiToken, }) ); From 419f952198ac312a50ee6daf2be921fc602c0c1a Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:36:28 +0100 Subject: [PATCH 07/15] Revert --- packages/gitbook-v2/src/lib/data/api.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/gitbook-v2/src/lib/data/api.ts b/packages/gitbook-v2/src/lib/data/api.ts index 33266bba1e..20e495656e 100644 --- a/packages/gitbook-v2/src/lib/data/api.ts +++ b/packages/gitbook-v2/src/lib/data/api.ts @@ -125,14 +125,13 @@ export function createDataFetcher( ); }, getReusableContent(params) { - return trace('getReusableContent', () => { - const withToken = params.apiToken ? { ...input, apiToken: params.apiToken } : input; - return getReusableContent(withToken, { + return trace('getReusableContent', () => + getReusableContent(input, { spaceId: params.spaceId, revisionId: params.revisionId, reusableContentId: params.reusableContentId, - }); - }); + }) + ); }, getLatestOpenAPISpecVersionContent(params) { return trace('getLatestOpenAPISpecVersionContent', () => @@ -159,14 +158,12 @@ export function createDataFetcher( ); }, getDocument(params) { - return trace('getDocument', () => { - const withToken = params.apiToken ? { ...input, apiToken: params.apiToken } : input; - - return getDocument(withToken, { + return trace('getDocument', () => + getDocument(input, { spaceId: params.spaceId, documentId: params.documentId, - }); - }); + }) + ); }, getComputedDocument(params) { return trace('getComputedDocument', () => From 221c2f2596c81c01a72d7ffc43f11b3c2c19849b Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:39:08 +0100 Subject: [PATCH 08/15] Fix interface --- .../src/components/DocumentView/ReusableContent.tsx | 13 +++++++------ packages/gitbook/src/lib/references.tsx | 13 ++----------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx index e7dfb4a5ef..eec2907a8c 100644 --- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -13,18 +13,19 @@ export async function ReusableContent(props: BlockProps { // without a space on the content ref, or if the space is the same as the current one, we can use the current revision. @@ -248,7 +239,7 @@ export async function resolveContentRef( } const space = await getDataOrNull( - fetcher.getSpace({ + dataFetcher.getSpace({ spaceId: contentRef.space, shareKey: undefined, }) @@ -266,7 +257,7 @@ export async function resolveContentRef( } const reusableContent = await getDataOrNull( - fetcher.getReusableContent({ + dataFetcher.getReusableContent({ spaceId: container.space, revisionId: container.revision, reusableContentId: contentRef.reusableContent, From 585d32a784fcad5e472d8b8e17e362f40dfa8328 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:40:47 +0100 Subject: [PATCH 09/15] Revert unwanted --- packages/gitbook/src/lib/api.ts | 26 ++++++++++--------------- packages/gitbook/src/lib/cache/cache.ts | 2 -- packages/gitbook/src/lib/v1.ts | 13 +++---------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/packages/gitbook/src/lib/api.ts b/packages/gitbook/src/lib/api.ts index 412cdc0a2e..ca3ce3b1f8 100644 --- a/packages/gitbook/src/lib/api.ts +++ b/packages/gitbook/src/lib/api.ts @@ -125,14 +125,14 @@ export async function apiWithToken( /** * Create an API client for the current request. */ -export async function api(tokenOverride?: string): Promise { +export async function api(): Promise { const existing = apiSyncStorage.getStore(); - if (existing && (!tokenOverride || existing.client.authToken === tokenOverride)) { + if (existing) { return existing; } const headersList = await headers(); - const apiToken = tokenOverride ?? headersList.get('x-gitbook-token'); + const apiToken = headersList.get('x-gitbook-token'); const contextId = headersList.get('x-gitbook-token-context') ?? undefined; if (!apiToken) { @@ -308,7 +308,7 @@ export const getSpace = cache({ name: 'api.getSpace', tag: (spaceId) => getCacheTag({ tag: 'space', space: spaceId }), get: async (spaceId: string, shareKey: string | undefined, options: CacheFunctionOptions) => { - const apiCtx = await api(options.apiToken); + const apiCtx = await api(); const response = await apiCtx.client.spaces.getSpaceById( spaceId, { @@ -371,8 +371,6 @@ interface GetRevisionOptions { * These options don't impact the cache key and it means revisions can be shared between different fetches with different metadata options. */ metadata: boolean; - - apiToken?: string; } const getAPIContextId = async () => { @@ -395,7 +393,7 @@ export const getRevision = cache({ fetchOptions: GetRevisionOptions, options: CacheFunctionOptions ) => { - const apiCtx = await api(fetchOptions.apiToken); + const apiCtx = await api(); const response = await apiCtx.client.spaces.getRevisionById( spaceId, revisionId, @@ -549,7 +547,7 @@ const getRevisionReusableContentById = cache({ options: CacheFunctionOptions ) => { try { - const apiCtx = await api(options.apiToken); + const apiCtx = await api(); const response = await apiCtx.client.spaces.getReusableContentInRevisionById( spaceId, revisionId, @@ -674,25 +672,21 @@ export const getRevisionFile = batch<[string, string, string], RevisionFile | nu export const getReusableContent = async ( spaceId: string, revisionId: string, - reusableContentId: string, - apiToken?: string + reusableContentId: string ): Promise => { const hasRevisionInMemory = await getRevision.hasInMemory(spaceId, revisionId, { metadata: false, }); if (hasRevisionInMemory) { - const revision = await getRevision(spaceId, revisionId, { metadata: false, apiToken }); + const revision = await getRevision(spaceId, revisionId, { metadata: false }); return ( revision.reusableContents.find( (reusableContent) => reusableContent.id === reusableContentId ) ?? null ); } - return getRevisionReusableContentById(spaceId, revisionId, reusableContentId, { - signal: undefined, - apiToken, - }); + return getRevisionReusableContentById(spaceId, revisionId, reusableContentId); }; /** @@ -705,7 +699,7 @@ export const getDocument = cache({ tagImmutable: true, getKeySuffix: getAPIContextId, get: async (spaceId: string, documentId: string, options: CacheFunctionOptions) => { - const apiCtx = await api(options.apiToken); + const apiCtx = await api(); const response = await apiCtx.client.spaces.getDocumentById( spaceId, documentId, diff --git a/packages/gitbook/src/lib/cache/cache.ts b/packages/gitbook/src/lib/cache/cache.ts index e7f9a12f77..065368e400 100644 --- a/packages/gitbook/src/lib/cache/cache.ts +++ b/packages/gitbook/src/lib/cache/cache.ts @@ -11,8 +11,6 @@ import type { CacheBackend, CacheEntry } from './types'; export type CacheFunctionOptions = { signal: AbortSignal | undefined; - - apiToken?: string; }; export type CacheFunction = (( diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index b2ec6af0f0..ea5219816b 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -114,10 +114,7 @@ async function getDataFetcherV1(): Promise { getSpace(params) { return wrapDataFetcherError(async () => { - return getSpace(params.spaceId, params.shareKey, { - signal: undefined, - apiToken: params.apiToken, - }); + return getSpace(params.spaceId, params.shareKey); }); }, @@ -164,10 +161,7 @@ async function getDataFetcherV1(): Promise { getDocument(params) { return wrapDataFetcherError(async () => { - const document = await getDocument(params.spaceId, params.documentId, { - signal: undefined, - apiToken: params.apiToken, - }); + const document = await getDocument(params.spaceId, params.documentId); if (!document) { throw new DataFetcherError('Document not found', 404); } @@ -216,8 +210,7 @@ async function getDataFetcherV1(): Promise { const reusableContent = await getReusableContent( params.spaceId, params.revisionId, - params.reusableContentId, - params.apiToken + params.reusableContentId ); if (!reusableContent) { From 6242024bb35ae9622412888060dba0f3a4ed5071 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:46:26 +0100 Subject: [PATCH 10/15] Fix types --- .../DocumentView/Integration/contentkit.tsx | 2 ++ packages/gitbook/src/fonts/custom.test.ts | 24 +++++++++++++++++++ packages/gitbook/src/fonts/custom.ts | 6 ++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx b/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx index fb692c6824..d6e6474177 100644 --- a/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx +++ b/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx @@ -20,6 +20,8 @@ export const contentKitServerContext: ContentKitServerContext = { 'link-external': (props) => , eye: (props) => , lock: (props) => , + check: (props) => , + "check-circle" : (props) => , }, codeBlock: (props) => { return ; diff --git a/packages/gitbook/src/fonts/custom.test.ts b/packages/gitbook/src/fonts/custom.test.ts index ce2616187e..747124e10c 100644 --- a/packages/gitbook/src/fonts/custom.test.ts +++ b/packages/gitbook/src/fonts/custom.test.ts @@ -28,6 +28,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], }, ], + permissions: { + edit: false, + } }, multiWeight: { @@ -81,6 +84,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], }, ], + permissions: { + edit: false, + } }, multiSource: { @@ -99,6 +105,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], }, ], + permissions: { + edit: false, + } }, missingFormat: { @@ -117,6 +126,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], }, ], + permissions: { + edit: false, + } }, empty: { @@ -124,6 +136,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { custom: true, fontFamily: 'Empty Font', fontFaces: [], + permissions: { + edit: false, + } }, specialChars: { @@ -136,6 +151,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { sources: [{ url: 'https://example.com/fonts/special.woff2', format: 'woff2' }], }, ], + permissions: { + edit: false, + } }, complex: { @@ -158,6 +176,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], }, ], + permissions: { + edit: false, + } }, variousURLs: { @@ -174,6 +195,9 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], }, ], + permissions: { + edit: false, + } }, }; diff --git a/packages/gitbook/src/fonts/custom.ts b/packages/gitbook/src/fonts/custom.ts index cb31f771ed..84abdf8b27 100644 --- a/packages/gitbook/src/fonts/custom.ts +++ b/packages/gitbook/src/fonts/custom.ts @@ -1,9 +1,9 @@ -import type { CustomizationFontDefinition } from '@gitbook/api'; +import type { CustomizationFontDefinitionInput } from '@gitbook/api'; /** * Define the custom font faces and set the --font-custom to the custom font name */ -export function generateFontFacesCSS(customFont: CustomizationFontDefinition): string { +export function generateFontFacesCSS(customFont: CustomizationFontDefinitionInput): string { const { fontFaces } = customFont; // Generate font face declarations for all weights @@ -45,7 +45,7 @@ export function generateFontFacesCSS(customFont: CustomizationFontDefinition): s /** * Get a list of font sources to preload (only 400 and 700 weights) */ -export function getFontSourcesToPreload(customFont: CustomizationFontDefinition) { +export function getFontSourcesToPreload(customFont: CustomizationFontDefinitionInput) { return customFont.fontFaces.filter( (face): face is typeof face & { weight: 400 | 700 } => face.weight === 400 || face.weight === 700 From 02a6421fe36b13c86944f95b8d3cc88ce1ed722a Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:46:31 +0100 Subject: [PATCH 11/15] Format --- .../DocumentView/Integration/contentkit.tsx | 2 +- packages/gitbook/src/fonts/custom.test.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx b/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx index d6e6474177..155077ca25 100644 --- a/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx +++ b/packages/gitbook/src/components/DocumentView/Integration/contentkit.tsx @@ -21,7 +21,7 @@ export const contentKitServerContext: ContentKitServerContext = { eye: (props) => , lock: (props) => , check: (props) => , - "check-circle" : (props) => , + 'check-circle': (props) => , }, codeBlock: (props) => { return ; diff --git a/packages/gitbook/src/fonts/custom.test.ts b/packages/gitbook/src/fonts/custom.test.ts index 747124e10c..b430b76241 100644 --- a/packages/gitbook/src/fonts/custom.test.ts +++ b/packages/gitbook/src/fonts/custom.test.ts @@ -30,7 +30,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, multiWeight: { @@ -86,7 +86,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, multiSource: { @@ -107,7 +107,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, missingFormat: { @@ -128,7 +128,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, empty: { @@ -138,7 +138,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { fontFaces: [], permissions: { edit: false, - } + }, }, specialChars: { @@ -153,7 +153,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, complex: { @@ -178,7 +178,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, variousURLs: { @@ -197,7 +197,7 @@ const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = { ], permissions: { edit: false, - } + }, }, }; From 139219c64452ccbd4fb0d0109203cab811daa684 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 11:48:24 +0100 Subject: [PATCH 12/15] changeset --- .changeset/polite-falcons-agree.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/polite-falcons-agree.md diff --git a/.changeset/polite-falcons-agree.md b/.changeset/polite-falcons-agree.md new file mode 100644 index 0000000000..7743ab7095 --- /dev/null +++ b/.changeset/polite-falcons-agree.md @@ -0,0 +1,6 @@ +--- +"gitbook-v2": minor +"gitbook": minor +--- + +Add support for reusable content across spaces. From f0d2a3af71bd030734fb97c55bf3b87aab5bdc15 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 13:11:29 +0100 Subject: [PATCH 13/15] Adapt data fetcher V1 to work with a custom token. --- packages/gitbook/src/lib/v1.ts | 339 ++++++++++++++++++++------------- 1 file changed, 204 insertions(+), 135 deletions(-) diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index ea5219816b..5168a94797 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -8,6 +8,7 @@ import type { GitBookDataFetcher } from '@v2/lib/data/types'; import { createImageResizer } from '@v2/lib/images'; import { createLinker } from '@v2/lib/links'; +import { GitBookAPI } from '@gitbook/api'; import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data'; import { headers } from 'next/headers'; import { @@ -30,6 +31,7 @@ import { getUserById, renderIntegrationUi, searchSiteContent, + withAPI as withAPIV1, } from './api'; import { getDynamicCustomizationSettings } from './customization'; import { withLeadingSlash, withTrailingSlash } from './paths'; @@ -58,7 +60,7 @@ export async function getV1BaseContext(): Promise { return url; }; - const dataFetcher = await getDataFetcherV1(); + const dataFetcher = getDataFetcherV1(); const imageResizer = createImageResizer({ imagesContextId: host, @@ -82,77 +84,121 @@ export async function getV1BaseContext(): Promise { * Try not to use this as much as possible, and instead take the data fetcher from the props. * This data fetcher should only be used at the top of the tree. */ -async function getDataFetcherV1(): Promise { +function getDataFetcherV1(apiTokenOverride?: string): GitBookDataFetcher { + let apiClient: GitBookAPI | undefined; + + /** + * Run a function with the correct API client. If an API token is provided, we + * create a new API client with the token. Otherwise, we use the default API client. + */ + async function withAPI(fn: () => Promise): Promise { + // No token override - we can use the default API client. + if (!apiTokenOverride) { + return fn(); + } + + const client = await api(); + + if (!apiClient) { + // New client uses same endpoint and user agent as the default client. + apiClient = new GitBookAPI({ + endpoint: client.client.endpoint, + authToken: apiTokenOverride, + userAgent: client.client.userAgent, + }); + } + + return withAPIV1( + { + client: apiClient, + contextId: client.contextId, + }, + fn + ); + } + const dataFetcher: GitBookDataFetcher = { async api() { - const result = await api(); - return result.client; + return withAPI(async () => { + const result = await api(); + return result.client; + }); }, - withToken() { - // In v1, the token is global and controlled by the middleware. - // We don't need to do anything special here. - return dataFetcher; + withToken({ apiToken }) { + return getDataFetcherV1(apiToken); }, getUserById(userId) { - return wrapDataFetcherError(async () => { - const user = await getUserById(userId); - if (!user) { - throw new DataFetcherError('User not found', 404); - } - - return user; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const user = await getUserById(userId); + if (!user) { + throw new DataFetcherError('User not found', 404); + } + + return user; + }) + ); }, getPublishedContentSite(params) { - return wrapDataFetcherError(async () => { - return getPublishedContentSite(params); - }); + return withAPI(() => + wrapDataFetcherError(async () => { + return getPublishedContentSite(params); + }) + ); }, getSpace(params) { - return wrapDataFetcherError(async () => { - return getSpace(params.spaceId, params.shareKey); - }); + return withAPI(() => + wrapDataFetcherError(async () => { + return getSpace(params.spaceId, params.shareKey); + }) + ); }, getChangeRequest(params) { - return wrapDataFetcherError(async () => { - const changeRequest = await getChangeRequest( - params.spaceId, - params.changeRequestId - ); - if (!changeRequest) { - throw new DataFetcherError('Change request not found', 404); - } - - return changeRequest; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const changeRequest = await getChangeRequest( + params.spaceId, + params.changeRequestId + ); + if (!changeRequest) { + throw new DataFetcherError('Change request not found', 404); + } + + return changeRequest; + }) + ); }, getRevision(params) { - return wrapDataFetcherError(async () => { - return getRevision(params.spaceId, params.revisionId, { - metadata: params.metadata, - }); - }); + return withAPI(() => + wrapDataFetcherError(async () => { + return getRevision(params.spaceId, params.revisionId, { + metadata: params.metadata, + }); + }) + ); }, getRevisionFile(params) { - return wrapDataFetcherError(async () => { - const revisionFile = await getRevisionFile( - params.spaceId, - params.revisionId, - params.fileId - ); - if (!revisionFile) { - throw new DataFetcherError('Revision file not found', 404); - } - - return revisionFile; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const revisionFile = await getRevisionFile( + params.spaceId, + params.revisionId, + params.fileId + ); + if (!revisionFile) { + throw new DataFetcherError('Revision file not found', 404); + } + + return revisionFile; + }) + ); }, getRevisionPageMarkdown() { @@ -160,117 +206,140 @@ async function getDataFetcherV1(): Promise { }, getDocument(params) { - return wrapDataFetcherError(async () => { - const document = await getDocument(params.spaceId, params.documentId); - if (!document) { - throw new DataFetcherError('Document not found', 404); - } - - return document; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const document = await getDocument(params.spaceId, params.documentId); + if (!document) { + throw new DataFetcherError('Document not found', 404); + } + + return document; + }) + ); }, getComputedDocument(params) { - return wrapDataFetcherError(() => { - return getComputedDocument( - params.organizationId, - params.spaceId, - params.source, - params.seed - ); - }); + return withAPI(() => + wrapDataFetcherError(() => { + return getComputedDocument( + params.organizationId, + params.spaceId, + params.source, + params.seed + ); + }) + ); }, getRevisionPages(params) { - return wrapDataFetcherError(async () => { - return getRevisionPages(params.spaceId, params.revisionId, { - metadata: params.metadata, - }); - }); + return withAPI(() => + wrapDataFetcherError(async () => { + return getRevisionPages(params.spaceId, params.revisionId, { + metadata: params.metadata, + }); + }) + ); }, getRevisionPageByPath(params) { - return wrapDataFetcherError(async () => { - const revisionPage = await getRevisionPageByPath( - params.spaceId, - params.revisionId, - params.path - ); - - if (!revisionPage) { - throw new DataFetcherError('Revision page not found', 404); - } - - return revisionPage; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const revisionPage = await getRevisionPageByPath( + params.spaceId, + params.revisionId, + params.path + ); + + if (!revisionPage) { + throw new DataFetcherError('Revision page not found', 404); + } + + return revisionPage; + }) + ); }, getReusableContent(params) { - return wrapDataFetcherError(async () => { - const reusableContent = await getReusableContent( - params.spaceId, - params.revisionId, - params.reusableContentId - ); - - if (!reusableContent) { - throw new DataFetcherError('Reusable content not found', 404); - } - - return reusableContent; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const reusableContent = await getReusableContent( + params.spaceId, + params.revisionId, + params.reusableContentId + ); + + if (!reusableContent) { + throw new DataFetcherError('Reusable content not found', 404); + } + + return reusableContent; + }) + ); }, getLatestOpenAPISpecVersionContent(params) { - return wrapDataFetcherError(async () => { - const openAPISpecVersionContent = await getLatestOpenAPISpecVersionContent( - params.organizationId, - params.slug - ); - - if (!openAPISpecVersionContent) { - throw new DataFetcherError('OpenAPI spec version content not found', 404); - } - - return openAPISpecVersionContent; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const openAPISpecVersionContent = await getLatestOpenAPISpecVersionContent( + params.organizationId, + params.slug + ); + + if (!openAPISpecVersionContent) { + throw new DataFetcherError('OpenAPI spec version content not found', 404); + } + + return openAPISpecVersionContent; + }) + ); }, getSiteRedirectBySource(params) { - return wrapDataFetcherError(async () => { - const siteRedirect = await getSiteRedirectBySource(params); - if (!siteRedirect) { - throw new DataFetcherError('Site redirect not found', 404); - } - - return siteRedirect; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const siteRedirect = await getSiteRedirectBySource(params); + if (!siteRedirect) { + throw new DataFetcherError('Site redirect not found', 404); + } + + return siteRedirect; + }) + ); }, getEmbedByUrl(params) { - return wrapDataFetcherError(() => { - return getEmbedByUrlInSpace(params.spaceId, params.url); - }); + return withAPI(() => + wrapDataFetcherError(() => { + return getEmbedByUrlInSpace(params.spaceId, params.url); + }) + ); }, searchSiteContent(params) { - return wrapDataFetcherError(async () => { - const { organizationId, siteId, query, cacheBust, scope } = params; - const result = await searchSiteContent( - organizationId, - siteId, - query, - scope, - cacheBust - ); - return result.items; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const { organizationId, siteId, query, cacheBust, scope } = params; + const result = await searchSiteContent( + organizationId, + siteId, + query, + scope, + cacheBust + ); + return result.items; + }) + ); }, renderIntegrationUi(params) { - return wrapDataFetcherError(async () => { - const result = await renderIntegrationUi(params.integrationName, params.request); - return result; - }); + return withAPI(() => + wrapDataFetcherError(async () => { + const result = await renderIntegrationUi( + params.integrationName, + params.request + ); + return result; + }) + ); }, streamAIResponse() { From 8de9d8ae622d987cb2814178edc1fe8ebf4d64cb Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 13:13:48 +0100 Subject: [PATCH 14/15] Format --- packages/gitbook/src/lib/v1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index 5168a94797..eb2accdc24 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -96,7 +96,7 @@ function getDataFetcherV1(apiTokenOverride?: string): GitBookDataFetcher { if (!apiTokenOverride) { return fn(); } - + const client = await api(); if (!apiClient) { From 486f1118f4fd5a4a6856696b21896d5866241b72 Mon Sep 17 00:00:00 2001 From: Steven Hall Date: Thu, 24 Apr 2025 16:24:22 +0100 Subject: [PATCH 15/15] Fix props --- .../src/components/DocumentView/ReusableContent.tsx | 9 +++++++-- packages/gitbook/src/lib/references.tsx | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx index eec2907a8c..7e2d28d8d2 100644 --- a/packages/gitbook/src/components/DocumentView/ReusableContent.tsx +++ b/packages/gitbook/src/components/DocumentView/ReusableContent.tsx @@ -22,14 +22,19 @@ export async function ReusableContent(props: BlockProps