improvements to identify

This commit is contained in:
bkellam 2025-12-11 12:15:49 -08:00
parent 36338dc8d2
commit eac7ea0555
5 changed files with 68 additions and 21 deletions

View file

@ -7,6 +7,7 @@ import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { SessionProvider } from "next-auth/react";
import { env } from "@sourcebot/shared";
import { env as clientEnv } from "@sourcebot/shared/client";
import { PlanProvider } from "@/features/entitlements/planProvider";
import { getEntitlements } from "@sourcebot/shared";
@ -42,6 +43,8 @@ export default function RootLayout({
// @note: the posthog api key doesn't need to be kept secret,
// so we are safe to send it to the client.
posthogApiKey={env.POSTHOG_PAPIK}
sourcebotVersion={clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION}
sourcebotInstallId={env.SOURCEBOT_INSTALL_ID}
>
<ThemeProvider
attribute="class"

View file

@ -34,9 +34,17 @@ interface PostHogProviderProps {
children: React.ReactNode
isDisabled: boolean
posthogApiKey: string
sourcebotVersion: string
sourcebotInstallId: string
}
export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHogProviderProps) {
export function PostHogProvider({
children,
isDisabled,
posthogApiKey,
sourcebotVersion,
sourcebotInstallId,
}: PostHogProviderProps) {
const { data: session } = useSession();
useEffect(() => {
@ -61,27 +69,33 @@ export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHog
'$referrer',
'$referring_domain',
'$ip',
] : []
] : [],
loaded: (posthog) => {
// Include install id & version in all events.
posthog.register({
sourcebot_version: sourcebotVersion,
install_id: sourcebotInstallId,
});
}
});
} else {
console.debug("PostHog telemetry disabled");
}
}, [isDisabled, posthogApiKey]);
}, [isDisabled, posthogApiKey, sourcebotInstallId, sourcebotVersion]);
useEffect(() => {
if (!session) {
return;
}
// Only identify the user if we are running in a cloud environment.
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
posthog.identify(session.user.id, {
posthog.identify(
session.user.id,
// Only include email & name when running in a cloud environment.
env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined ? {
email: session.user.email,
name: session.user.name,
});
} else {
console.debug("PostHog identify skipped");
}
} : undefined
);
}, [session]);
return (

View file

@ -3,17 +3,13 @@
import { CaptureOptions } from "posthog-js";
import posthog from "posthog-js";
import { PosthogEvent, PosthogEventMap } from "../lib/posthogEvents";
import { env } from "@sourcebot/shared/client";
export function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E], options?: CaptureOptions) {
if(!options) {
options = {};
}
options.send_instantly = true;
posthog.capture(event, {
...properties,
sourcebot_version: env.NEXT_PUBLIC_SOURCEBOT_VERSION,
}, options);
posthog.capture(event, properties, options);
}
/**

View file

@ -1,9 +1,12 @@
import { PostHog } from 'posthog-node'
import { env } from '@sourcebot/shared'
import { env as clientEnv } from '@sourcebot/shared/client';
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import * as Sentry from "@sentry/nextjs";
import { PosthogEvent, PosthogEventMap } from './posthogEvents';
import { cookies } from 'next/headers';
import { cookies, headers } from 'next/headers';
import { auth } from '@/auth';
import { getVerifiedApiObject } from '@/withAuthV2';
/**
* @note: This is a subset of the properties stored in the
@ -47,13 +50,40 @@ const getPostHogCookie = (cookieStore: Pick<RequestCookies, 'get'>): PostHogCook
return undefined;
}
/**
* Attempts to retrieve the distinct id of the current user.
*/
const tryGetDistinctId = async () => {
// First, attempt to retrieve the distinct id from the cookie.
const cookieStore = await cookies();
const cookie = getPostHogCookie(cookieStore);
if (cookie) {
return cookie.distinct_id;
}
// Next, from the session.
const session = await auth();
if (session) {
return session.user.id;
}
// Finally, from the api key.
const headersList = await headers();
const apiKeyString = headersList.get("X-Sourcebot-Api-Key") ?? undefined;
if (!apiKeyString) {
return undefined;
}
const apiKey = await getVerifiedApiObject(apiKeyString);
return apiKey?.createdById;
}
export async function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
if (env.SOURCEBOT_TELEMETRY_DISABLED === 'true') {
return;
}
const cookieStore = await cookies();
const cookie = getPostHogCookie(cookieStore);
const distinctId = await tryGetDistinctId();
const posthog = new PostHog(env.POSTHOG_PAPIK, {
host: 'https://us.i.posthog.com',
@ -63,7 +93,11 @@ export async function captureEvent<E extends PosthogEvent>(event: E, properties:
posthog.capture({
event,
properties,
distinctId: cookie?.distinct_id ?? '',
properties: {
...properties,
sourcebot_version: clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION,
install_id: env.SOURCEBOT_INSTALL_ID,
},
distinctId,
});
}

View file

@ -156,7 +156,7 @@ export const getAuthenticatedUser = async () => {
/**
* Returns a API key object if the API key string is valid, otherwise returns undefined.
*/
const getVerifiedApiObject = async (apiKeyString: string): Promise<ApiKey | undefined> => {
export const getVerifiedApiObject = async (apiKeyString: string): Promise<ApiKey | undefined> => {
const parts = apiKeyString.split("-");
if (parts.length !== 2 || parts[0] !== "sourcebot") {
return undefined;