Skip to content

Commit 1211ebe

Browse files
authored
Proxy analytics events (#3570)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
1 parent 2e6e28e commit 1211ebe

File tree

5 files changed

+49
-19
lines changed

5 files changed

+49
-19
lines changed

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

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ interface InsightsProviderProps {
5555
/** If true, the visitor cookie tracking will be used */
5656
visitorCookieTrackingEnabled: boolean;
5757

58-
/** The URL of the app. */
58+
/** The application URL. */
5959
appURL: string;
6060

61-
/** The host of the API. */
62-
apiHost: string;
61+
/** The url of the endpoint to send events to */
62+
eventUrl: string;
6363

6464
/** The children of the provider. */
6565
children: React.ReactNode;
@@ -69,7 +69,7 @@ interface InsightsProviderProps {
6969
* Wrap the content of the app with the InsightsProvider to track events.
7070
*/
7171
export function InsightsProvider(props: InsightsProviderProps) {
72-
const { enabled, appURL, apiHost, children, visitorCookieTrackingEnabled } = props;
72+
const { enabled, children, visitorCookieTrackingEnabled, eventUrl, appURL } = props;
7373

7474
const currentContent = useCurrentContent();
7575
const visitorIdRef = React.useRef<string | null>(null);
@@ -126,9 +126,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
126126
if (allEvents.length > 0) {
127127
if (enabled) {
128128
sendEvents({
129-
apiHost,
130-
organizationId: currentContent.organizationId,
131-
siteId: currentContent.siteId,
129+
eventUrl,
132130
events: allEvents,
133131
});
134132
} else {
@@ -190,7 +188,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
190188
return () => {
191189
window.removeEventListener('beforeunload', flushEventsSync);
192190
};
193-
}, [flushEventsSync, appURL, visitorCookieTrackingEnabled]);
191+
}, [flushEventsSync, visitorCookieTrackingEnabled, appURL]);
194192

195193
return (
196194
<InsightsContext.Provider value={trackEvent}>
@@ -216,16 +214,12 @@ export function useTrackEvent(): TrackEventCallback {
216214
* Post the events to the server.
217215
*/
218216
function sendEvents(args: {
219-
apiHost: string;
220-
organizationId: string;
221-
siteId: string;
217+
eventUrl: string;
222218
events: api.SiteInsightsEvent[];
223219
}) {
224-
const { apiHost, organizationId, siteId, events } = args;
225-
const url = new URL(apiHost);
226-
url.pathname = `/v1/orgs/${organizationId}/sites/${siteId}/insights/events`;
220+
const { eventUrl, events } = args;
227221

228-
fetch(url.toString(), {
222+
fetch(eventUrl, {
229223
method: 'POST',
230224
headers: {
231225
'Content-Type': 'application/json',

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async function fetchVisitorID(
5353
const proposed = generateRandomId();
5454

5555
const url = new URL(appURL);
56-
url.pathname = '/__session';
56+
url.pathname = '/__session/2/';
5757
url.searchParams.set('proposed', proposed);
5858

5959
try {

packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { CONTAINER_STYLE } from '@/components/layout';
1313
import { tcls } from '@/lib/tailwind';
1414

1515
import type { VisitorAuthClaims } from '@/lib/adaptive';
16-
import { GITBOOK_API_PUBLIC_URL, GITBOOK_APP_URL } from '@/lib/env';
16+
import { GITBOOK_APP_URL } from '@/lib/env';
1717
import { AIChat } from '../AIChat';
1818
import { Announcement } from '../Announcement';
1919
import { SpacesDropdown } from '../Header/SpacesDropdown';
@@ -69,6 +69,12 @@ export function SpaceLayout(props: {
6969
</div>
7070
);
7171

72+
const eventUrl = new URL(
73+
context.linker.toAbsoluteURL(context.linker.toPathInSite('/~gitbook/__evt'))
74+
);
75+
eventUrl.searchParams.set('o', context.organizationId);
76+
eventUrl.searchParams.set('s', context.site.id);
77+
7278
return (
7379
<SpaceLayoutContextProvider basePath={context.linker.toPathInSpace('')}>
7480
<CurrentContentProvider
@@ -84,7 +90,7 @@ export function SpaceLayout(props: {
8490
<InsightsProvider
8591
enabled={withTracking}
8692
appURL={GITBOOK_APP_URL}
87-
apiHost={GITBOOK_API_PUBLIC_URL}
93+
eventUrl={eventUrl.toString()}
8894
visitorCookieTrackingEnabled={customization.insights?.trackingCookie}
8995
>
9096
<Announcement context={context} />

packages/gitbook/src/lib/tracking.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { headers as nextHeaders } from 'next/headers';
2-
import { GITBOOK_DISABLE_TRACKING } from './env';
2+
import { GITBOOK_API_URL, GITBOOK_DISABLE_TRACKING } from './env';
33

44
/**
55
* Return true if events should be tracked on the site.
@@ -21,3 +21,27 @@ export function shouldTrackEvents(headers?: Awaited<ReturnType<typeof nextHeader
2121

2222
return true;
2323
}
24+
25+
/**
26+
* Serve as a proxy to the analytics endpoint, forwarding the request body and required parameters.
27+
*/
28+
export async function serveProxyAnalyticsEvent(req: Request) {
29+
const requestURL = new URL(req.url);
30+
31+
const org = requestURL.searchParams.get('o');
32+
const site = requestURL.searchParams.get('s');
33+
if (!org || !site) {
34+
return new Response('Missing required query parameters: o (org) and s (site)', {
35+
status: 400,
36+
headers: { 'content-type': 'text/plain' },
37+
});
38+
}
39+
const url = new URL(`${GITBOOK_API_URL}/v1/orgs/${org}/sites/${site}/insights/events`);
40+
return await fetch(url.toString(), {
41+
method: 'POST',
42+
headers: {
43+
'Content-Type': 'application/json',
44+
},
45+
body: req.body,
46+
});
47+
}

packages/gitbook/src/middleware.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import { serveResizedImage } from '@/routes/image';
2727
import { cookies } from 'next/headers';
2828
import type { SiteURLData } from './lib/context';
29+
import { serveProxyAnalyticsEvent } from './lib/tracking';
2930
export const config = {
3031
matcher: [
3132
'/((?!_next/static|_next/image|~gitbook/static|~gitbook/revalidate|~gitbook/monitoring|~scalar/proxy).*)',
@@ -140,6 +141,11 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
140141
});
141142
}
142143

144+
//Forwards analytics events
145+
if (siteRequestURL.pathname.endsWith('/~gitbook/__evt')) {
146+
return await serveProxyAnalyticsEvent(request);
147+
}
148+
143149
// We want to filter hostnames that contains a port here as this is likely a malicious request.
144150
if (shouldFilterMaliciousRequests(siteRequestURL)) {
145151
return new Response('Invalid request', {

0 commit comments

Comments
 (0)