diff --git a/apps/desktop/src/auth/billing.tsx b/apps/desktop/src/auth/billing.tsx index 132cd6f914..6466e9dbc7 100644 --- a/apps/desktop/src/auth/billing.tsx +++ b/apps/desktop/src/auth/billing.tsx @@ -63,7 +63,7 @@ export function BillingProvider({ children }: { children: ReactNode }) { enabled: !!auth?.session && !billing.isPro, queryKey: [auth?.session?.user.id ?? "", "canStartTrial"], queryFn: async () => { - const headers = auth?.getHeaders(); + const headers = await auth?.getHeadersWithFingerprint(); if (!headers) { return false; } diff --git a/apps/desktop/src/auth/context.tsx b/apps/desktop/src/auth/context.tsx index 4771086208..7385024b7e 100644 --- a/apps/desktop/src/auth/context.tsx +++ b/apps/desktop/src/auth/context.tsx @@ -50,6 +50,7 @@ type AuthTokenHandlers = { type AuthUtils = { getHeaders: () => Record | null; + getHeadersWithFingerprint: () => Promise | null>; getAvatarUrl: () => Promise; }; @@ -105,37 +106,41 @@ async function initSession( } let trackedUserId: string | null = null; +let trackedSignedInEventUserId: string | null = null; async function trackAuthEvent( event: AuthChangeEvent, session: Session | null, ): Promise { if ((event === "SIGNED_IN" || event === "INITIAL_SESSION") && session) { - if (session.user.id === trackedUserId) { - return; + if (session.user.id !== trackedUserId) { + trackedUserId = session.user.id; + + const appVersion = await getVersion(); + await analyticsCommands.identify(session.user.id, { + email: session.user.email, + set: { + account_created_date: session.user.created_at, + is_signed_up: true, + app_version: appVersion, + os_version: osVersion(), + platform: platform(), + }, + }); } - trackedUserId = session.user.id; - - const appVersion = await getVersion(); - void analyticsCommands.identify(session.user.id, { - email: session.user.email, - set: { - account_created_date: session.user.created_at, - is_signed_up: true, - app_version: appVersion, - os_version: osVersion(), - platform: platform(), - }, - }); - - if (event === "SIGNED_IN") { - void analyticsCommands.event({ event: "user_signed_in" }); + if ( + event === "SIGNED_IN" && + session.user.id !== trackedSignedInEventUserId + ) { + trackedSignedInEventUserId = session.user.id; + await analyticsCommands.event({ event: "user_signed_in" }); } } if (event === "SIGNED_OUT") { trackedUserId = null; + trackedSignedInEventUserId = null; } } @@ -338,6 +343,32 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { return headers; }, [session, fingerprint]); + const getHeadersWithFingerprint = useCallback(async () => { + if (!session) { + return null; + } + + let resolvedFingerprint = fingerprint; + + if (!resolvedFingerprint) { + const result = await miscCommands.getFingerprint(); + if (result.status === "ok") { + resolvedFingerprint = result.data; + setFingerprint((prev) => prev ?? result.data); + } + } + + const headers: Record = { + Authorization: `${session.token_type} ${session.access_token}`, + }; + + if (resolvedFingerprint) { + headers[DEVICE_FINGERPRINT_HEADER] = resolvedFingerprint; + } + + return headers; + }, [session, fingerprint]); + const getAvatarUrl = useCallback(async () => { const email = session?.user.email; @@ -366,6 +397,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { handleAuthCallback, setSessionFromTokens, getHeaders, + getHeadersWithFingerprint, getAvatarUrl, }), [ @@ -377,6 +409,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { handleAuthCallback, setSessionFromTokens, getHeaders, + getHeadersWithFingerprint, getAvatarUrl, ], ); diff --git a/apps/desktop/src/auth/useConnections.ts b/apps/desktop/src/auth/useConnections.ts index 7c8da3a7c8..1e122d4471 100644 --- a/apps/desktop/src/auth/useConnections.ts +++ b/apps/desktop/src/auth/useConnections.ts @@ -14,7 +14,7 @@ export function useConnections(enabled = true) { return useQuery({ queryKey: ["integration-status", userId], queryFn: async () => { - const headers = auth?.getHeaders(); + const headers = await auth?.getHeadersWithFingerprint(); if (!headers) { return []; } diff --git a/apps/desktop/src/calendar/components/oauth/calendar-selection.tsx b/apps/desktop/src/calendar/components/oauth/calendar-selection.tsx index 8b083f8c66..c8f641cba3 100644 --- a/apps/desktop/src/calendar/components/oauth/calendar-selection.tsx +++ b/apps/desktop/src/calendar/components/oauth/calendar-selection.tsx @@ -46,7 +46,7 @@ export function useOAuthCalendarSelection(config: CalendarProvider) { } = useQuery({ queryKey: ["oauthCalendars", config.id], queryFn: async () => { - const headers = auth?.getHeaders(); + const headers = await auth?.getHeadersWithFingerprint(); if (!headers) return []; const client = createClient({ baseUrl: env.VITE_API_URL, headers }); const { data, error } = await googleListCalendars({ client }); diff --git a/apps/desktop/src/onboarding/account/trial.tsx b/apps/desktop/src/onboarding/account/trial.tsx index aca2108268..b346a6adea 100644 --- a/apps/desktop/src/onboarding/account/trial.tsx +++ b/apps/desktop/src/onboarding/account/trial.tsx @@ -28,7 +28,7 @@ export function useTrialFlow(onContinue: () => void) { const mutation = useMutation({ mutationFn: async () => { - const headers = auth.getHeaders(); + const headers = await auth.getHeadersWithFingerprint(); if (!headers) throw new Error("no headers"); const client = createClient({ baseUrl: env.VITE_API_URL, headers }); const { data, error } = await startTrial({ diff --git a/apps/desktop/src/settings/general/account.tsx b/apps/desktop/src/settings/general/account.tsx index 07b3f638f5..af731f60f0 100644 --- a/apps/desktop/src/settings/general/account.tsx +++ b/apps/desktop/src/settings/general/account.tsx @@ -306,7 +306,7 @@ function BillingButton() { enabled: !!auth?.session && !isPro, queryKey: [auth?.session?.user.id ?? "", "canStartTrial"], queryFn: async () => { - const headers = auth?.getHeaders(); + const headers = await auth?.getHeadersWithFingerprint(); if (!headers) { return false; } @@ -322,7 +322,7 @@ function BillingButton() { const startTrialMutation = useMutation({ mutationFn: async () => { - const headers = auth?.getHeaders(); + const headers = await auth?.getHeadersWithFingerprint(); if (!headers) { throw new Error("Not authenticated"); }