Skip to content

Commit 3e1592e

Browse files
authored
Fix configuration for v2 staging (#2940)
1 parent 90604c4 commit 3e1592e

File tree

11 files changed

+167
-133
lines changed

11 files changed

+167
-133
lines changed

packages/gitbook-v2/src/app/~gitbook/env/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
GITBOOK_APP_URL,
66
GITBOOK_ASSETS_URL,
77
GITBOOK_DISABLE_TRACKING,
8+
GITBOOK_ICONS_URL,
89
GITBOOK_INTEGRATIONS_HOST,
910
GITBOOK_URL,
1011
GITBOOK_USER_AGENT,
@@ -19,6 +20,7 @@ export async function GET(_req: NextRequest) {
1920
GITBOOK_APP_URL,
2021
GITBOOK_API_URL,
2122
GITBOOK_ASSETS_URL,
23+
GITBOOK_ICONS_URL,
2224
GITBOOK_USER_AGENT,
2325
GITBOOK_INTEGRATIONS_HOST,
2426
GITBOOK_DISABLE_TRACKING,

packages/gitbook-v2/src/lib/data/index.ts

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

packages/gitbook-v2/src/lib/data/urls.ts

Lines changed: 0 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,3 @@
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-
1191
/**
1202
* For a given GitBook URL, return a list of alternative URLs that could be matched against to lookup the content.
1213
* The approach is optimized to aim at reusing cached lookup results as much as possible.

packages/gitbook-v2/src/lib/env/globals.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import 'server-only';
2+
3+
/*
4+
* Support both Cloudflare and Vercel, environment variables can be bundled.
5+
* To avoid leaking them on the client-side, they should be accessed from this file
6+
* and not from the `process.env` object.
7+
*/
8+
19
/**
210
* Main host on which GitBook is running.
311
*/
@@ -19,7 +27,7 @@ export const GITBOOK_ASSETS_URL =
1927
/**
2028
* GitBook app URL.
2129
*/
22-
export const GITBOOK_APP_URL = process.env.NEXT_PUBLIC_GITBOOK_APP_URL || 'https://app.gitbook.com';
30+
export const GITBOOK_APP_URL = process.env.GITBOOK_APP_URL || 'https://app.gitbook.com';
2331

2432
/**
2533
* Default GitBook API URL endpoint.
@@ -59,6 +67,17 @@ export const GITBOOK_IMAGE_RESIZE_URL = process.env.GITBOOK_IMAGE_RESIZE_URL ??
5967
export const GITBOOK_IMAGE_RESIZE_SIGNING_KEY =
6068
process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY ?? null;
6169

70+
/**
71+
* Endpoint where icons are served.
72+
*/
73+
export const GITBOOK_ICONS_URL =
74+
process.env.GITBOOK_ICONS_URL || `${GITBOOK_ASSETS_URL}/~gitbook/static/icons`;
75+
76+
/**
77+
* Token passed to the icons endpoint.
78+
*/
79+
export const GITBOOK_ICONS_TOKEN = process.env.GITBOOK_ICONS_TOKEN;
80+
6281
/**
6382
* Secret used to validate requests from the GitBook app.
6483
*/

packages/gitbook/src/components/Insights/InsightsProvider.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const InsightsContext = React.createContext<TrackEventCallback>(() => {});
6363

6464
interface InsightsProviderProps extends InsightsEventContext {
6565
enabled: boolean;
66+
appURL: string;
6667
apiHost: string;
6768
visitorAuthToken: string | null;
6869
children: React.ReactNode;
@@ -72,7 +73,7 @@ interface InsightsProviderProps extends InsightsEventContext {
7273
* Wrap the content of the app with the InsightsProvider to track events.
7374
*/
7475
export function InsightsProvider(props: InsightsProviderProps) {
75-
const { enabled, apiHost, visitorAuthToken, children, ...context } = props;
76+
const { enabled, appURL, apiHost, visitorAuthToken, children, ...context } = props;
7677

7778
const visitorIdRef = React.useRef<string | null>(null);
7879
const eventsRef = React.useRef<{
@@ -140,7 +141,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
140141
});
141142

142143
const flushBatchedEvents = useDebounceCallback(async () => {
143-
const visitorId = visitorIdRef.current ?? (await getVisitorId());
144+
const visitorId = visitorIdRef.current ?? (await getVisitorId(appURL));
144145
visitorIdRef.current = visitorId;
145146

146147
flushEventsSync();
@@ -184,15 +185,15 @@ export function InsightsProvider(props: InsightsProviderProps) {
184185
* Get the visitor ID and store it in a ref.
185186
*/
186187
React.useEffect(() => {
187-
getVisitorId().then((visitorId) => {
188+
getVisitorId(appURL).then((visitorId) => {
188189
visitorIdRef.current = visitorId;
189190
// When the page is unloaded, flush all events, but only if the visitor ID is set
190191
window.addEventListener('beforeunload', flushEventsSync);
191192
});
192193
return () => {
193194
window.removeEventListener('beforeunload', flushEventsSync);
194195
};
195-
}, [flushEventsSync]);
196+
}, [flushEventsSync, appURL]);
196197

197198
return (
198199
<InsightsContext.Provider value={trackEvent}>

packages/gitbook/src/components/Insights/visitorId.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ let pendingVisitorId: Promise<string> | null = null;
1313
/**
1414
* Return the current visitor identifier.
1515
*/
16-
export async function getVisitorId(): Promise<string> {
16+
export async function getVisitorId(appURL: string): Promise<string> {
1717
if (!visitorId) {
1818
if (!pendingVisitorId) {
19-
pendingVisitorId = fetchVisitorID().finally(() => {
19+
pendingVisitorId = fetchVisitorID(appURL).finally(() => {
2020
pendingVisitorId = null;
2121
});
2222
}
@@ -30,7 +30,7 @@ export async function getVisitorId(): Promise<string> {
3030
/**
3131
* Propose a visitor identifier to the GitBook.com server and get the devideId back.
3232
*/
33-
async function fetchVisitorID(): Promise<string> {
33+
async function fetchVisitorID(appURL: string): Promise<string> {
3434
const withoutCookies = isCookiesTrackingDisabled();
3535

3636
if (withoutCookies) {
@@ -46,7 +46,7 @@ async function fetchVisitorID(): Promise<string> {
4646
// No tracking deviceId set, we'll need to consolidate with the server.
4747
const proposed = generateRandomId();
4848

49-
const url = new URL(process.env.NEXT_PUBLIC_GITBOOK_APP_URL ?? 'https://app.gitbook.com');
49+
const url = new URL(appURL);
5050
url.pathname = '/__session';
5151
url.searchParams.set('proposed', proposed);
5252

packages/gitbook/src/components/RootLayout/CustomizationRootLayout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ClientContexts } from './ClientContexts';
3131

3232
import '@gitbook/icons/style.css';
3333
import './globals.css';
34+
import { GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@v2/lib/env';
3435

3536
/**
3637
* Layout shared between the content and the PDF renderer.
@@ -127,8 +128,8 @@ export async function CustomizationRootLayout(props: {
127128
)}
128129
>
129130
<IconsProvider
130-
assetsURL={process.env.GITBOOK_ICONS_URL ?? getAssetURL('icons')}
131-
assetsURLToken={process.env.GITBOOK_ICONS_TOKEN}
131+
assetsURL={GITBOOK_ICONS_URL}
132+
assetsURLToken={GITBOOK_ICONS_TOKEN}
132133
assetsByStyles={{
133134
'custom-icons': {
134135
assetsURL: getAssetURL('icons'),

0 commit comments

Comments
 (0)