mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 02:53:29 +00:00
Request insights and visitor endpoints relative to the served origin (#4603)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix analytics and adaptive content silently breaking on sites served from a different host than the one configured (apex vs www, domain alias, CDN). The insights and visitor-claims endpoints are now requested relative to the page's own origin instead of the configured host, which a prerendered page cannot know.
|
||||
|
||||
Keep the traffic of GitBook's own preview deployments out of the sites' analytics, now that those requests reach the events endpoint.
|
||||
@@ -69,6 +69,7 @@ runs:
|
||||
GITBOOK_RUNTIME: cloudflare
|
||||
GITBOOK_BLOCK_SEARCH_INDEXATION: ${{ inputs.environment == 'preview' && 'true' || '' }}
|
||||
GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: ${{ inputs.environment == 'preview' && 'true' || '' }}
|
||||
GITBOOK_DISABLE_INSIGHTS: ${{ inputs.environment == 'preview' && 'true' || '' }}
|
||||
shell: bash
|
||||
|
||||
- name: Upload the DO worker
|
||||
|
||||
@@ -75,6 +75,7 @@ runs:
|
||||
echo "GITBOOK_RUNTIME=vercel" >> .vercel/.env.${{ inputs.environment }}.local
|
||||
echo "GITBOOK_BLOCK_SEARCH_INDEXATION=true" >> .vercel/.env.${{ inputs.environment }}.local
|
||||
echo "GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE=true" >> .vercel/.env.${{ inputs.environment }}.local
|
||||
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: Build Project Artifacts
|
||||
|
||||
@@ -29,3 +29,6 @@ yarn-error.log*
|
||||
|
||||
# Bun pack artifacts
|
||||
packages/*/*.tgz
|
||||
|
||||
# Playwright MCP artifacts
|
||||
.playwright-mcp/
|
||||
|
||||
@@ -94,6 +94,7 @@ const nextConfig = {
|
||||
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
|
||||
GITBOOK_BLOCK_SEARCH_INDEXATION: process.env.GITBOOK_BLOCK_SEARCH_INDEXATION,
|
||||
GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE: process.env.GITBOOK_ALLOW_CUSTOMIZATION_OVERRIDE,
|
||||
GITBOOK_DISABLE_INSIGHTS: process.env.GITBOOK_DISABLE_INSIGHTS,
|
||||
|
||||
// Next.js envs
|
||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
GITBOOK_API_URL,
|
||||
GITBOOK_APP_URL,
|
||||
GITBOOK_ASSETS_URL,
|
||||
GITBOOK_DISABLE_INSIGHTS,
|
||||
GITBOOK_DISABLE_TRACKING,
|
||||
GITBOOK_FONTS_URL,
|
||||
GITBOOK_ICONS_URL,
|
||||
@@ -35,6 +36,7 @@ export async function GET(_req: NextRequest) {
|
||||
GITBOOK_INTEGRATIONS_HOST,
|
||||
GITBOOK_INTEGRATIONS_CONTENT_HOST,
|
||||
GITBOOK_DISABLE_TRACKING,
|
||||
GITBOOK_DISABLE_INSIGHTS,
|
||||
|
||||
// Secret envs
|
||||
GITBOOK_SECRET: !!GITBOOK_SECRET,
|
||||
|
||||
@@ -58,15 +58,15 @@ export function SpaceLayoutServerContext(props: SpaceLayoutProps) {
|
||||
? context.linker.toPathInSite('~gitbook/auth/login')
|
||||
: null;
|
||||
|
||||
const eventUrl = new URL(
|
||||
context.linker.toAbsoluteURL(context.linker.toPathInSite('/~gitbook/__evt'))
|
||||
);
|
||||
eventUrl.searchParams.set('o', context.organizationId);
|
||||
eventUrl.searchParams.set('s', context.site.id);
|
||||
// Kept relative: a prerendered page has no request to read the host from, so an absolute URL
|
||||
// pins the configured host and turns these fetches cross-origin when it differs (apex vs www).
|
||||
const eventParams = new URLSearchParams({
|
||||
o: context.organizationId,
|
||||
s: context.site.id,
|
||||
});
|
||||
const eventUrl = `${context.linker.toPathInSite('/~gitbook/__evt')}?${eventParams}`;
|
||||
|
||||
const getVisitorClaimsUrl = context.linker.toAbsoluteURL(
|
||||
context.linker.toPathInSite('/~gitbook/visitor')
|
||||
);
|
||||
const getVisitorClaimsUrl = context.linker.toPathInSite('/~gitbook/visitor');
|
||||
|
||||
return (
|
||||
<SpaceLayoutContextProvider
|
||||
@@ -92,7 +92,7 @@ export function SpaceLayoutServerContext(props: SpaceLayoutProps) {
|
||||
appURL={GITBOOK_APP_URL}
|
||||
visitorCookieTrackingEnabled={customization.insights?.trackingCookie}
|
||||
>
|
||||
<InsightsProvider enabled={withTracking} eventUrl={eventUrl.toString()}>
|
||||
<InsightsProvider enabled={withTracking} eventUrl={eventUrl}>
|
||||
<AIChatProvider
|
||||
renderMessageOptions={aiChatRenderMessageOptions}
|
||||
withPageFeedback={customization.feedback.enabled}
|
||||
|
||||
+6
@@ -78,6 +78,12 @@ export const GITBOOK_DISABLE_TRACKING = Boolean(
|
||||
!!process.env.GITBOOK_DISABLE_TRACKING || process.env.NODE_ENV !== 'production'
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether insights events must not reach the sites' analytics, while the pages otherwise behave
|
||||
* like production. Set on the preview deployments, whose traffic is e2e runs, not the sites'.
|
||||
*/
|
||||
export const GITBOOK_DISABLE_INSIGHTS = process.env.GITBOOK_DISABLE_INSIGHTS === 'true';
|
||||
|
||||
/**
|
||||
* Hostname serving the integrations.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { headers as nextHeaders } from 'next/headers';
|
||||
import type * as api from '@gitbook/api';
|
||||
|
||||
import { apiClient } from './data/api';
|
||||
import { GITBOOK_DISABLE_TRACKING } from './env';
|
||||
import { GITBOOK_DISABLE_INSIGHTS, GITBOOK_DISABLE_TRACKING } from './env';
|
||||
import { getLogger } from './logger';
|
||||
|
||||
/**
|
||||
@@ -18,9 +18,7 @@ export function shouldTrackEvents(headers?: Awaited<ReturnType<typeof nextHeader
|
||||
return false;
|
||||
}
|
||||
|
||||
const disableTrackingHeader = headers?.get('x-gitbook-disable-tracking');
|
||||
|
||||
if (disableTrackingHeader === 'true') {
|
||||
if (headers?.get('x-gitbook-disable-tracking') === 'true') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -85,7 +83,7 @@ export async function trackServerInsightsEvents(args: {
|
||||
`Tracking ${args.events.length} events at ${request.url} for site ${args.siteId} (enabled=${!GITBOOK_DISABLE_TRACKING})`
|
||||
);
|
||||
|
||||
if (GITBOOK_DISABLE_TRACKING) {
|
||||
if (GITBOOK_DISABLE_TRACKING || GITBOOK_DISABLE_INSIGHTS) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
normalizeRequestURL,
|
||||
throwIfDataError,
|
||||
} from '@/lib/data';
|
||||
import { isGitBookAssetsHostURL, isGitBookHostURL } from '@/lib/env';
|
||||
import { GITBOOK_DISABLE_INSIGHTS, isGitBookAssetsHostURL, isGitBookHostURL } from '@/lib/env';
|
||||
import { getImageResizingContextId } from '@/lib/images';
|
||||
import { isAITrainingOrIndexingRequest } from '@/lib/indexing-crawlers';
|
||||
import { MiddlewareHeaders } from '@/lib/middleware';
|
||||
@@ -185,6 +185,9 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
||||
|
||||
//Forwards analytics events
|
||||
if (siteRequestURL.pathname.endsWith('/~gitbook/__evt')) {
|
||||
if (GITBOOK_DISABLE_INSIGHTS) {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
return await serveProxyAnalyticsEvent(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { getContentTestURL } from './utils';
|
||||
|
||||
describe('Insights', () => {
|
||||
// The harness serves the site from BASE_URL while it is configured on gitbook.gitbook.io,
|
||||
// reproducing the split (apex vs www, alias, CDN) that turns an absolute URL cross-origin.
|
||||
const TEST_URL = getContentTestURL('https://gitbook.gitbook.io/test-gitbook-open');
|
||||
|
||||
it('should reference the insights endpoints relative to the served origin', async () => {
|
||||
const response = await fetch(TEST_URL);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const html = await response.text();
|
||||
|
||||
for (const endpoint of ['__evt', 'visitor']) {
|
||||
expect(html).toContain(`~gitbook/${endpoint}`);
|
||||
expect(html).not.toMatch(new RegExp(`https?://[^"'\\\\\\s]*~gitbook/${endpoint}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user