Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions packages/gitbook/src/components/Insights/InsightsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ interface InsightsProviderProps {
/** If true, the visitor cookie tracking will be used */
visitorCookieTrackingEnabled: boolean;

/** The URL of the app. */
/** The application URL. */
appURL: string;

/** The host of the API. */
apiHost: string;
/** The url of the endpoint to send events to */
eventUrl: string;

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

const currentContent = useCurrentContent();
const visitorIdRef = React.useRef<string | null>(null);
Expand Down Expand Up @@ -126,9 +126,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
if (allEvents.length > 0) {
if (enabled) {
sendEvents({
apiHost,
organizationId: currentContent.organizationId,
siteId: currentContent.siteId,
eventUrl,
events: allEvents,
});
} else {
Expand Down Expand Up @@ -190,7 +188,7 @@ export function InsightsProvider(props: InsightsProviderProps) {
return () => {
window.removeEventListener('beforeunload', flushEventsSync);
};
}, [flushEventsSync, appURL, visitorCookieTrackingEnabled]);
}, [flushEventsSync, visitorCookieTrackingEnabled, appURL]);

return (
<InsightsContext.Provider value={trackEvent}>
Expand All @@ -216,16 +214,12 @@ export function useTrackEvent(): TrackEventCallback {
* Post the events to the server.
*/
function sendEvents(args: {
apiHost: string;
organizationId: string;
siteId: string;
eventUrl: string;
events: api.SiteInsightsEvent[];
}) {
const { apiHost, organizationId, siteId, events } = args;
const url = new URL(apiHost);
url.pathname = `/v1/orgs/${organizationId}/sites/${siteId}/insights/events`;
const { eventUrl, events } = args;

fetch(url.toString(), {
fetch(eventUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
2 changes: 1 addition & 1 deletion packages/gitbook/src/components/Insights/visitorId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function fetchVisitorID(
const proposed = generateRandomId();

const url = new URL(appURL);
url.pathname = '/__session';
url.pathname = '/__session/2/';
url.searchParams.set('proposed', proposed);

try {
Expand Down
10 changes: 8 additions & 2 deletions packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { CONTAINER_STYLE } from '@/components/layout';
import { tcls } from '@/lib/tailwind';

import type { VisitorAuthClaims } from '@/lib/adaptive';
import { GITBOOK_API_PUBLIC_URL, GITBOOK_APP_URL } from '@/lib/env';
import { GITBOOK_APP_URL } from '@/lib/env';
import { AIChat } from '../AIChat';
import { Announcement } from '../Announcement';
import { SpacesDropdown } from '../Header/SpacesDropdown';
Expand Down Expand Up @@ -69,6 +69,12 @@ export function SpaceLayout(props: {
</div>
);

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);

return (
<SpaceLayoutContextProvider basePath={context.linker.toPathInSpace('')}>
<CurrentContentProvider
Expand All @@ -84,7 +90,7 @@ export function SpaceLayout(props: {
<InsightsProvider
enabled={withTracking}
appURL={GITBOOK_APP_URL}
apiHost={GITBOOK_API_PUBLIC_URL}
eventUrl={eventUrl.toString()}
visitorCookieTrackingEnabled={customization.insights?.trackingCookie}
>
<Announcement context={context} />
Expand Down
26 changes: 25 additions & 1 deletion packages/gitbook/src/lib/tracking.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { headers as nextHeaders } from 'next/headers';
import { GITBOOK_DISABLE_TRACKING } from './env';
import { GITBOOK_API_URL, GITBOOK_DISABLE_TRACKING } from './env';

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

return true;
}

/**
* Serve as a proxy to the analytics endpoint, forwarding the request body and required parameters.
*/
export async function serveProxyAnalyticsEvent(req: Request) {
const requestURL = new URL(req.url);

const org = requestURL.searchParams.get('o');
const site = requestURL.searchParams.get('s');
if (!org || !site) {
return new Response('Missing required query parameters: o (org) and s (site)', {
status: 400,
headers: { 'content-type': 'text/plain' },
});
}
const url = new URL(`${GITBOOK_API_URL}/v1/orgs/${org}/sites/${site}/insights/events`);
return await fetch(url.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: req.body,
});
}
6 changes: 6 additions & 0 deletions packages/gitbook/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { serveResizedImage } from '@/routes/image';
import { cookies } from 'next/headers';
import type { SiteURLData } from './lib/context';
import { serveProxyAnalyticsEvent } from './lib/tracking';
export const config = {
matcher: [
'/((?!_next/static|_next/image|~gitbook/static|~gitbook/revalidate|~gitbook/monitoring|~scalar/proxy).*)',
Expand Down Expand Up @@ -140,6 +141,11 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
});
}

//Forwards analytics events
if (siteRequestURL.pathname.endsWith('/~gitbook/__evt')) {
return await serveProxyAnalyticsEvent(request);
}

// We want to filter hostnames that contains a port here as this is likely a malicious request.
if (shouldFilterMaliciousRequests(siteRequestURL)) {
return new Response('Invalid request', {
Expand Down