|
1 |
| -import { race, tryCatch } from '@/lib/async'; |
2 |
| -import { joinPath } from '@/lib/paths'; |
3 |
| -import { GitBookAPI, type GitBookAPIError, type PublishedSiteContentLookup } from '@gitbook/api'; |
4 |
| -import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env'; |
5 |
| - |
6 |
| -/** |
7 |
| - * Lookup a content by its URL using the GitBook API. |
8 |
| - * To optimize caching, we try multiple lookup alternatives and return the first one that matches. |
9 |
| - */ |
10 |
| -export async function getPublishedContentByURL(input: { |
11 |
| - url: string; |
12 |
| - visitorAuthToken: string | null; |
13 |
| - redirectOnError: boolean; |
14 |
| -}): Promise< |
15 |
| - | { data: PublishedSiteContentLookup; error?: undefined } |
16 |
| - | { data?: undefined; error: Error | GitBookAPIError } |
17 |
| -> { |
18 |
| - const lookupURL = new URL(input.url); |
19 |
| - const url = stripURLSearch(lookupURL); |
20 |
| - const lookup = getURLLookupAlternatives(url); |
21 |
| - |
22 |
| - const result = await race(lookup.urls, async (alternative, { signal }) => { |
23 |
| - const api = new GitBookAPI({ |
24 |
| - authToken: GITBOOK_API_TOKEN ?? undefined, |
25 |
| - endpoint: GITBOOK_API_URL, |
26 |
| - userAgent: GITBOOK_USER_AGENT, |
27 |
| - }); |
28 |
| - |
29 |
| - const callResult = await tryCatch( |
30 |
| - api.urls.getPublishedContentByUrl( |
31 |
| - { |
32 |
| - url: alternative.url, |
33 |
| - visitorAuthToken: input.visitorAuthToken ?? undefined, |
34 |
| - redirectOnError: input.redirectOnError, |
35 |
| - cache: true, |
36 |
| - }, |
37 |
| - { |
38 |
| - signal, |
39 |
| - headers: { |
40 |
| - 'x-gitbook-force-cache': 'true', |
41 |
| - }, |
42 |
| - } |
43 |
| - ) |
44 |
| - ); |
45 |
| - |
46 |
| - if (callResult.error) { |
47 |
| - if (alternative.primary) { |
48 |
| - // We only return an error for the primary alternative (full URL), |
49 |
| - // as other parts could result in errors due to the URL being incomplete (share links, etc). |
50 |
| - return { error: callResult.error }; |
51 |
| - } |
52 |
| - return null; |
53 |
| - } |
54 |
| - |
55 |
| - const { |
56 |
| - data: { data }, |
57 |
| - } = callResult; |
58 |
| - |
59 |
| - if ('redirect' in data) { |
60 |
| - if (alternative.primary) { |
61 |
| - // Append the path to the redirect URL |
62 |
| - // because we might have matched a shorter path and the redirect is relative to it |
63 |
| - if (alternative.extraPath) { |
64 |
| - if (data.target === 'content') { |
65 |
| - const redirect = new URL(data.redirect); |
66 |
| - redirect.pathname = joinPath(redirect.pathname, alternative.extraPath); |
67 |
| - data.redirect = redirect.toString(); |
68 |
| - } else { |
69 |
| - const redirect = new URL(data.redirect); |
70 |
| - if (redirect.searchParams.has('location')) { |
71 |
| - redirect.searchParams.set( |
72 |
| - 'location', |
73 |
| - joinPath( |
74 |
| - redirect.searchParams.get('location') ?? '', |
75 |
| - alternative.extraPath |
76 |
| - ) |
77 |
| - ); |
78 |
| - data.redirect = redirect.toString(); |
79 |
| - } |
80 |
| - } |
81 |
| - } |
82 |
| - |
83 |
| - return { data }; |
84 |
| - } |
85 |
| - |
86 |
| - return null; |
87 |
| - } |
88 |
| - |
89 |
| - /** |
90 |
| - * We use the following criteria to determine if the lookup result is the right one: |
91 |
| - * - the primary alternative was resolved (because that's the longest or most inclusive path) |
92 |
| - * - the resolution of the site URL is complete (because we want to resolve the deepest path possible) |
93 |
| - * |
94 |
| - * In both cases, the idea is to use the deepest/longest/most inclusive path to resolve the content. |
95 |
| - */ |
96 |
| - if (alternative.primary || ('site' in data && data.complete)) { |
97 |
| - const siteResult: PublishedSiteContentLookup = { |
98 |
| - ...data, |
99 |
| - changeRequest: data.changeRequest ?? lookup.changeRequest, |
100 |
| - revision: data.revision ?? lookup.revision, |
101 |
| - basePath: joinPath(data.basePath, lookup.basePath ?? ''), |
102 |
| - pathname: joinPath(data.pathname, alternative.extraPath), |
103 |
| - }; |
104 |
| - return { data: siteResult }; |
105 |
| - } |
106 |
| - |
107 |
| - return null; |
108 |
| - }); |
109 |
| - |
110 |
| - if (!result) { |
111 |
| - return { |
112 |
| - error: new Error('No content found'), |
113 |
| - }; |
114 |
| - } |
115 |
| - |
116 |
| - return result; |
117 |
| -} |
118 |
| - |
119 | 1 | /**
|
120 | 2 | * For a given GitBook URL, return a list of alternative URLs that could be matched against to lookup the content.
|
121 | 3 | * The approach is optimized to aim at reusing cached lookup results as much as possible.
|
|
0 commit comments