mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 02:53:29 +00:00
Implement bypass for URL lookup alternatives (#4620)
This commit is contained in:
@@ -70,6 +70,7 @@ runs:
|
||||
GITBOOK_BLOCK_SEARCH_INDEXATION: ${{ inputs.environment == 'preview' && 'true' || '' }}
|
||||
GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: ${{ inputs.environment == 'preview' && 'true' || '' }}
|
||||
GITBOOK_DISABLE_INSIGHTS: ${{ inputs.environment == 'preview' && 'true' || '' }}
|
||||
GITBOOK_DISABLE_LOOKUP_ALTERNATIVES: ${{ inputs.environment == 'staging' && 'true' || '' }}
|
||||
shell: bash
|
||||
|
||||
- name: Upload the DO worker
|
||||
|
||||
@@ -78,6 +78,11 @@ runs:
|
||||
echo "GITBOOK_DISABLE_INSIGHTS=true" >> .vercel/.env.${{ inputs.environment }}.local
|
||||
echo "--- .vercel/.env.${{ inputs.environment }}.local after inject ---"
|
||||
cat .vercel/.env.${{ inputs.environment }}.local
|
||||
- name: Inject staging build env vars
|
||||
if: ${{ inputs.environment == 'staging' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "GITBOOK_DISABLE_LOOKUP_ALTERNATIVES=true" >> .vercel/.env.${{ inputs.environment }}.local
|
||||
- name: Build Project Artifacts
|
||||
run: bun run vercel build --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }}
|
||||
shell: bash
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
GITBOOK_APP_URL,
|
||||
GITBOOK_ASSETS_URL,
|
||||
GITBOOK_DISABLE_INSIGHTS,
|
||||
GITBOOK_DISABLE_LOOKUP_ALTERNATIVES,
|
||||
GITBOOK_DISABLE_TRACKING,
|
||||
GITBOOK_FONTS_URL,
|
||||
GITBOOK_ICONS_URL,
|
||||
@@ -37,6 +38,7 @@ export async function GET(_req: NextRequest) {
|
||||
GITBOOK_INTEGRATIONS_CONTENT_HOST,
|
||||
GITBOOK_DISABLE_TRACKING,
|
||||
GITBOOK_DISABLE_INSIGHTS,
|
||||
GITBOOK_DISABLE_LOOKUP_ALTERNATIVES,
|
||||
|
||||
// Secret envs
|
||||
GITBOOK_SECRET: !!GITBOOK_SECRET,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { getURLLookupAlternatives, getURLLookupPathname, normalizeURL } from './urls';
|
||||
import {
|
||||
getURLLookupAlternatives,
|
||||
getURLLookupPathname,
|
||||
normalizeURL,
|
||||
shouldBypassLookupAlternatives,
|
||||
} from './urls';
|
||||
|
||||
describe('getURLLookupPathname', () => {
|
||||
const previewRoot = 'https://sites.gitbook.com/preview/site_example/section';
|
||||
@@ -832,3 +837,54 @@ describe('normalizeURL with encoded paths', () => {
|
||||
expect(result.searchParams.get('filter')).toBe(risonValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getURLLookupAlternatives with bypass', () => {
|
||||
it('only looks up the full URL', () => {
|
||||
expect(
|
||||
getURLLookupAlternatives(new URL('https://docs.mycompany.com/a/b/c'), { bypass: true })
|
||||
).toEqual({
|
||||
revision: undefined,
|
||||
changeRequest: undefined,
|
||||
basePath: undefined,
|
||||
urls: [{ url: 'https://docs.mycompany.com/a/b/c', extraPath: '', primary: true }],
|
||||
});
|
||||
});
|
||||
|
||||
it('only looks up the full URL for a variant', () => {
|
||||
expect(
|
||||
getURLLookupAlternatives(new URL('https://test.gitbook.io/v/variant/space'), {
|
||||
bypass: true,
|
||||
}).urls
|
||||
).toEqual([
|
||||
{ url: 'https://test.gitbook.io/v/variant/space', extraPath: '', primary: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(['revisions', 'changes'])('keeps the alternatives for %s', (kind) => {
|
||||
const url = new URL(`https://docs.mycompany.com/a/~/${kind}/id/page`);
|
||||
expect(getURLLookupAlternatives(url, { bypass: true })).toEqual(
|
||||
getURLLookupAlternatives(url, { bypass: false })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldBypassLookupAlternatives', () => {
|
||||
const bypassURLs = ['https://docs.mycompany.com/section', 'https://other.mycompany.com/'];
|
||||
|
||||
it.each([
|
||||
'https://docs.mycompany.com/section',
|
||||
'https://docs.mycompany.com/section/page',
|
||||
'https://other.mycompany.com/',
|
||||
'https://other.mycompany.com/page',
|
||||
])('matches %s', (url) => {
|
||||
expect(shouldBypassLookupAlternatives(normalizeURL(new URL(url)), bypassURLs)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'https://docs.mycompany.com/sectionpage',
|
||||
'https://docs.mycompany.com/',
|
||||
'https://unknown.mycompany.com/section',
|
||||
])('does not match %s', (url) => {
|
||||
expect(shouldBypassLookupAlternatives(normalizeURL(new URL(url)), bypassURLs)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { GITBOOK_DISABLE_LOOKUP_ALTERNATIVES } from '../env';
|
||||
import { joinPath, removeTrailingSlash } from '../paths';
|
||||
import { isProxyRootRequest } from '../proxy';
|
||||
import { DataFetcherError, getExposableError } from './errors';
|
||||
@@ -42,6 +43,34 @@ function getContentPathSegments(pathSegments: string[]): string[] {
|
||||
return pathSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Site URL prefixes resolved with the full URL only, for sites where a shorter alternative
|
||||
* would resolve to the wrong content.
|
||||
*/
|
||||
const LOOKUP_ALTERNATIVES_BYPASS_URLS: string[] = ['https://proxy.gitbook.site/sites/site_p4Xo4'];
|
||||
|
||||
/**
|
||||
* Whether the lookup of this (normalized) URL should skip the shorter alternatives.
|
||||
*/
|
||||
export function shouldBypassLookupAlternatives(
|
||||
url: URL,
|
||||
bypassURLs: string[] = LOOKUP_ALTERNATIVES_BYPASS_URLS
|
||||
): boolean {
|
||||
if (GITBOOK_DISABLE_LOOKUP_ALTERNATIVES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return bypassURLs.some((bypassURL) => {
|
||||
const prefix = normalizeURL(new URL(bypassURL));
|
||||
const prefixPath = removeTrailingSlash(prefix.pathname);
|
||||
return (
|
||||
prefix.origin === url.origin &&
|
||||
(removeTrailingSlash(url.pathname) === prefixPath ||
|
||||
url.pathname.startsWith(`${prefixPath}/`))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given GitBook URL, return a list of alternative URLs that could be matched against to lookup the content.
|
||||
* The approach is optimized to aim at reusing cached lookup results as much as possible.
|
||||
@@ -62,8 +91,9 @@ function getContentPathSegments(pathSegments: string[]): string[] {
|
||||
* - Public content has a custom hostname in the organization with a variant: docs.company.com/<space>/v/<variant>/<path>
|
||||
* - Public content has a custom hostname in the organization with a variant and a share-link: docs.company.com/<space>/<link>/v/<variant>/<path>
|
||||
*/
|
||||
export function getURLLookupAlternatives(input: URL) {
|
||||
export function getURLLookupAlternatives(input: URL, options: { bypass?: boolean } = {}) {
|
||||
const url = normalizeURL(input);
|
||||
const bypass = options.bypass ?? shouldBypassLookupAlternatives(url);
|
||||
|
||||
let basePath: string | undefined = undefined;
|
||||
let changeRequest: string | undefined = undefined;
|
||||
@@ -123,6 +153,11 @@ export function getURLLookupAlternatives(input: URL) {
|
||||
pushAlternative(contentURL, pathSegments.slice(revisionOrChangeIdIndex + 1).join('/'));
|
||||
}
|
||||
|
||||
// Revisions and changes above still need their alternatives to extract the base path.
|
||||
else if (bypass) {
|
||||
pushAlternative(url, '');
|
||||
}
|
||||
|
||||
// URL looks like a collection url (with /v/ in the path)
|
||||
// We only start matching after the /v/ segment and we ignore everything before it
|
||||
// to avoid potentially matching as a page not found under the default space in the collection
|
||||
|
||||
+6
@@ -84,6 +84,12 @@ export const GITBOOK_DISABLE_TRACKING = Boolean(
|
||||
*/
|
||||
export const GITBOOK_DISABLE_INSIGHTS = process.env.GITBOOK_DISABLE_INSIGHTS === 'true';
|
||||
|
||||
/**
|
||||
* Whether to resolve site content with the full URL only, skipping the shorter lookup alternatives.
|
||||
*/
|
||||
export const GITBOOK_DISABLE_LOOKUP_ALTERNATIVES =
|
||||
process.env.GITBOOK_DISABLE_LOOKUP_ALTERNATIVES === 'true';
|
||||
|
||||
/**
|
||||
* Hostname serving the integrations.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user