From 9435490c4375c9a65b07b4130ea512df555454f7 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Thu, 21 May 2026 15:08:40 +0100 Subject: [PATCH] chore: improve posthog session identificaion --- .../src/lib/features/app/init.svelte.ts | 4 ++- .../features/settings/pages/ai-credits.svelte | 1 - .../src/lib/utils/functions/appSetup.ts | 14 ++++---- .../src/lib/utils/services/posthog/index.ts | 34 +++++++++++++++++-- apps/dashboard/src/routes/+layout.svelte | 5 ++- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/apps/dashboard/src/lib/features/app/init.svelte.ts b/apps/dashboard/src/lib/features/app/init.svelte.ts index e7844cb8..5f1f5333 100644 --- a/apps/dashboard/src/lib/features/app/init.svelte.ts +++ b/apps/dashboard/src/lib/features/app/init.svelte.ts @@ -66,7 +66,9 @@ class AppInitApi extends BaseApi { onSuccess: (data) => { this.data = data; licenseApi.setFeatures(data.licenseFeatures); - setupAnalyticsBasedOnLicense(); + setupAnalyticsBasedOnLicense( + data.profile?.id ? { id: data.profile.id, email: data.profile.email, name: data.profile.fullname } : undefined + ); this.setupStores(params); this.setUserAnalytics(); this.routeUserToNextPage(params); diff --git a/apps/dashboard/src/lib/features/settings/pages/ai-credits.svelte b/apps/dashboard/src/lib/features/settings/pages/ai-credits.svelte index 96acdb37..7350e99b 100644 --- a/apps/dashboard/src/lib/features/settings/pages/ai-credits.svelte +++ b/apps/dashboard/src/lib/features/settings/pages/ai-credits.svelte @@ -26,7 +26,6 @@ onMount(() => { if (page.url.searchParams.get('tokens') === 'success') { snackbar.success('snackbar.ai_credits.purchase_success'); - void aiCreditsApi.fetchAll(); } }); diff --git a/apps/dashboard/src/lib/utils/functions/appSetup.ts b/apps/dashboard/src/lib/utils/functions/appSetup.ts index 5f6c0d7a..ce50fec2 100644 --- a/apps/dashboard/src/lib/utils/functions/appSetup.ts +++ b/apps/dashboard/src/lib/utils/functions/appSetup.ts @@ -1,31 +1,31 @@ import { PUBLIC_IS_SELFHOSTED } from '$env/static/public'; -import { initPosthog } from '$lib/utils/services/posthog'; +import { initPosthog, type PosthogBootstrapUser } from '$lib/utils/services/posthog'; import { initSentry } from '$lib/utils/services/sentry'; import { initUmami } from '$lib/utils/services/umami'; import { licenseApi } from '$features/license/api/license.svelte'; let isInitialized = false; -export function setupAnalytics() { +export function setupAnalytics(user?: PosthogBootstrapUser) { if (isInitialized) return; isInitialized = true; initSentry(); - initPosthog(); + initPosthog(user); initUmami(); } /** Checks if this is cloud deployment and initializes analytics */ -export function setupCloudAnalytics() { +export function setupCloudAnalytics(user?: PosthogBootstrapUser) { if (PUBLIC_IS_SELFHOSTED !== 'true') { - setupAnalytics(); + setupAnalytics(user); } } -export function setupAnalyticsBasedOnLicense() { +export function setupAnalyticsBasedOnLicense(user?: PosthogBootstrapUser) { if (licenseApi.hasAccess('no-tracking')) { return; } - setupAnalytics(); + setupAnalytics(user); } diff --git a/apps/dashboard/src/lib/utils/services/posthog/index.ts b/apps/dashboard/src/lib/utils/services/posthog/index.ts index bd665d5a..2ac744c5 100644 --- a/apps/dashboard/src/lib/utils/services/posthog/index.ts +++ b/apps/dashboard/src/lib/utils/services/posthog/index.ts @@ -2,7 +2,11 @@ import { dev } from '$app/environment'; import { licenseApi } from '$features/license/api/license.svelte'; import posthog from 'posthog-js'; -const hasNoTracking = () => licenseApi.hasAccess('no-tracking'); +const hasNoTracking = () => { + const noTracking = licenseApi.hasAccess('no-tracking'); + console.log('license_no_tracking', noTracking); + return noTracking; +}; export const capturePosthogEvent = (event: string, properties?: Record): void => { if (dev || hasNoTracking()) return; @@ -16,10 +20,34 @@ export const identifyPosthogUser = (id: string, properties?: Record { +export type PosthogBootstrapUser = { + id: string; + email?: string | null; + name?: string | null; +}; + +/** + * When `user` is supplied, PostHog initializes as the identified user from its + * very first event. Without bootstrap the first pageview / autocapture frames / + * session-replay attribute to a fresh anonymous UUID and the later `identify` + * call only aliases — session replays and initial events stay on the anon person. + * The follow-up `setPersonProperties` attaches email/name in the same tick so + * autocapture events fire with the user's properties already on the person. + */ +export const initPosthog = (user?: PosthogBootstrapUser): void => { if (dev || hasNoTracking()) return; posthog.init('phc_JfdHOZ6v0cVlGELBYx1Tmoen2nxNOrAzvgvrPA6Ksov', { - api_host: 'https://eu.posthog.com' + api_host: 'https://eu.posthog.com', + ...(user && { + bootstrap: { distinctID: user.id, isIdentifiedID: true } + }) }); + + if (user) { + posthog.setPersonProperties({ + ...(user.email && { email: user.email }), + ...(user.name && { name: user.name }) + }); + } }; diff --git a/apps/dashboard/src/routes/+layout.svelte b/apps/dashboard/src/routes/+layout.svelte index f0c9d141..f80e426f 100644 --- a/apps/dashboard/src/routes/+layout.svelte +++ b/apps/dashboard/src/routes/+layout.svelte @@ -28,7 +28,10 @@ loadingIndicator.style.display = 'none'; } - setupCloudAnalytics(); + const sessionUser = data?.locals?.user; + setupCloudAnalytics( + sessionUser ? { id: sessionUser.id, email: sessionUser.email, name: sessionUser.name } : undefined + ); if (data?.locals?.user) { user.set({