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
12 changes: 10 additions & 2 deletions src/app/(space)/(content)/[[...pathname]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ export default async function Page(props: { params: PagePathParams }) {
const { params } = props;

const rawPathname = getPathnameParam(params);
const { contentTarget, space, customization, pages, page, document } =
await fetchPageData(params);
const {
content: contentPointer,
contentTarget,
space,
customization,
pages,
page,
document,
} = await fetchPageData(params);
const linksContext: PageHrefContext = {};

if (!page) {
Expand Down Expand Up @@ -63,6 +70,7 @@ export default async function Page(props: { params: PagePathParams }) {
<div className={tcls('flex', 'flex-row')}>
<PageBody
space={space}
contentPointer={contentPointer}
contentTarget={contentTarget}
customization={customization}
context={contentRefContext}
Expand Down
26 changes: 22 additions & 4 deletions src/components/PageBody/PageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from 'react';

import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate';
import { ContentTarget, api } from '@/lib/api';
import { ContentPointer, ContentTarget, SiteContentPointer, api } from '@/lib/api';
import { hasFullWidthBlock, isNodeEmpty } from '@/lib/document';
import { ContentRefContext, resolveContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
Expand All @@ -25,20 +25,33 @@ import { DateRelative } from '../primitives';

export function PageBody(props: {
space: Space;
contentPointer: ContentPointer | SiteContentPointer;
contentTarget: ContentTarget;
customization: CustomizationSettings | SiteCustomizationSettings;
page: RevisionPageDocument;
document: JSONDocument | null;
context: ContentRefContext;
withPageFeedback: boolean;
}) {
const { space, contentTarget, customization, context, page, document, withPageFeedback } =
props;
const {
space,
contentPointer,
contentTarget,
customization,
context,
page,
document,
withPageFeedback,
} = props;

const asFullWidth = document ? hasFullWidthBlock(document) : false;
const language = getSpaceLanguage(customization);
const updatedAt = page.updatedAt ?? page.createdAt;
const shouldHighlightCode = createHighlightingContext();
const sitePointer =
'siteId' in contentPointer
? { organizationId: contentPointer.organizationId, siteId: contentPointer.siteId }
: undefined;

return (
<>
Expand Down Expand Up @@ -129,7 +142,12 @@ export function PageBody(props: {
</div>
</main>
<React.Suspense fallback={null}>
<TrackPageView spaceId={space.id} pageId={page.id} apiHost={api().endpoint} />
<TrackPageView
sitePointer={sitePointer}
spaceId={space.id}
pageId={page.id}
apiHost={api().endpoint}
/>
</React.Suspense>
</>
);
Expand Down
76 changes: 60 additions & 16 deletions src/components/PageBody/TrackPageView.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,80 @@
'use client';

import type { RequestSpaceTrackPageView } from '@gitbook/api';
import type { RequestSiteTrackPageView, RequestSpaceTrackPageView } from '@gitbook/api';
import cookies from 'js-cookie';
import * as React from 'react';

import { getVisitorId } from '@/lib/analytics';
import { SiteContentPointer } from '@/lib/api';

/**
* Track the page view for the current page to integrations.
*/
export function TrackPageView(props: {
apiHost: string;
sitePointer?: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
spaceId: string;
pageId: string | undefined;
}) {
const { apiHost, spaceId, pageId } = props;
const { apiHost, sitePointer, spaceId, pageId } = props;

React.useEffect(() => {
trackPageView(apiHost, spaceId, pageId);
}, [apiHost, spaceId, pageId]);
trackPageView({ apiHost, sitePointer, spaceId, pageId });
}, [apiHost, spaceId, pageId, sitePointer]);

return null;
}

async function sendSpaceTrackPageViewRequest(args: {
apiHost: string;
spaceId: string;
body: RequestSpaceTrackPageView;
}) {
const { apiHost, spaceId, body } = args;
const url = new URL(apiHost);
url.pathname = `/v1/spaces/${spaceId}/insights/track_view`;

await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
}

async function sendSiteTrackPageViewRequest(args: {
apiHost: string;
sitePointer: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
body: RequestSiteTrackPageView;
}) {
const { apiHost, sitePointer, body } = args;
const url = new URL(apiHost);
url.pathname = `/v1/orgs/${sitePointer.organizationId}/sites/${sitePointer.siteId}/insights/track_view`;

await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
}

let latestPageId: string | undefined | null = null;

/**
* Track the page view for the current page to GitBook.
* We don't use the API client to avoid shipping 80kb of JS to the client.
* And instead use a simple fetch.
*/
async function trackPageView(apiHost: string, spaceId: string, pageId: string | undefined) {
async function trackPageView(args: {
apiHost: string;
sitePointer?: Pick<SiteContentPointer, 'siteId' | 'organizationId'>;
spaceId: string;
pageId: string | undefined;
}) {
const { apiHost, sitePointer, pageId, spaceId } = args;
if (pageId === latestPageId) {
// The hook can be called multiple times, we only want to track once.
return;
Expand All @@ -39,7 +83,7 @@ async function trackPageView(apiHost: string, spaceId: string, pageId: string |
latestPageId = pageId;

const visitorId = await getVisitorId();
const body: RequestSpaceTrackPageView = {
const sharedTrackedProps = {
url: window.location.href,
pageId,
visitor: {
Expand All @@ -51,17 +95,17 @@ async function trackPageView(apiHost: string, spaceId: string, pageId: string |
referrer: document.referrer,
};

const url = new URL(apiHost);
url.pathname = `/v1/spaces/${spaceId}/insights/track_view`;

try {
await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
sitePointer
? await sendSiteTrackPageViewRequest({
apiHost,
sitePointer,
body: {
...sharedTrackedProps,
spaceId,
},
})
: await sendSpaceTrackPageViewRequest({ apiHost, spaceId, body: sharedTrackedProps });
} catch (error) {
console.error('Failed to track page view', error);
}
Expand Down