From a9565c16451a63a71bd6cb1caf361ef5fcc1fb1a Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Fri, 26 Jun 2026 18:25:52 +0300 Subject: [PATCH] refactor: Optimize Sentry user ids (#47342) ## Summary by CodeRabbit * **Bug Fixes** * Improved signed-in user identification in error tracking to use the current authenticated user ID directly. * Removed reliance on previously stored anonymous identifiers, reducing inconsistent user matching across sessions. * **Tests** * Added coverage to confirm error tracking user identity is set when a user is signed in and not set when signed out. --- apps/studio/lib/telemetry.test.tsx | 33 +++++++++++++++++++++- apps/studio/lib/telemetry.tsx | 24 ++-------------- packages/common/constants/local-storage.ts | 2 -- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/apps/studio/lib/telemetry.test.tsx b/apps/studio/lib/telemetry.test.tsx index 4d2d1656a78..5fba56af9bf 100644 --- a/apps/studio/lib/telemetry.test.tsx +++ b/apps/studio/lib/telemetry.test.tsx @@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({ identify: vi.fn(), useUser: vi.fn(), useOrganizationsQuery: vi.fn(), + setUser: vi.fn(), })) vi.mock('common', async (importOriginal) => { @@ -34,7 +35,7 @@ vi.mock('@/hooks/misc/useSelectedOrganization', () => ({ })) vi.mock('@sentry/nextjs', () => ({ - setUser: vi.fn(), + setUser: (...args: unknown[]) => mocks.setUser(...args), })) const USER_ID = 'user-abc-123' @@ -48,6 +49,7 @@ describe('Telemetry — posthog identify mirroring', () => { mocks.identify.mockReset() mocks.useUser.mockReset() mocks.useOrganizationsQuery.mockReset() + mocks.setUser.mockReset() }) it('fires identify with both org_count and signup_timestamp when user and orgs are loaded', async () => { @@ -146,3 +148,32 @@ describe('Telemetry — posthog identify mirroring', () => { expect(mocks.identify).not.toHaveBeenCalled() }) }) + +describe('Telemetry — Sentry user identification', () => { + beforeEach(() => { + mocks.identify.mockReset() + mocks.useUser.mockReset() + mocks.useOrganizationsQuery.mockReset() + mocks.setUser.mockReset() + }) + + it('sets the raw (un-hashed) user id on Sentry', async () => { + mocks.useUser.mockReturnValue({ id: USER_ID, created_at: CREATED_AT }) + mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) }) + + render() + + await waitFor(() => { + expect(mocks.setUser).toHaveBeenCalledWith({ id: USER_ID }) + }) + }) + + it('does not set a Sentry user when the user is not logged in', async () => { + mocks.useUser.mockReturnValue(null) + mocks.useOrganizationsQuery.mockReturnValue({ data: orgs(1) }) + + render() + await new Promise((r) => setTimeout(r, 10)) + expect(mocks.setUser).not.toHaveBeenCalled() + }) +}) diff --git a/apps/studio/lib/telemetry.tsx b/apps/studio/lib/telemetry.tsx index 0b0cbb9358b..ebad3e51def 100644 --- a/apps/studio/lib/telemetry.tsx +++ b/apps/studio/lib/telemetry.tsx @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/nextjs' -import { LOCAL_STORAGE_KEYS, PageTelemetry, posthogClient, safeLocalStorage, useUser } from 'common' +import { PageTelemetry, posthogClient, useUser } from 'common' import { useEffect, useRef } from 'react' import { useConsentToast } from 'ui-patterns/consent' @@ -7,16 +7,6 @@ import { useOrganizationsQuery } from '@/data/organizations/organizations-query' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { API_URL, IS_PLATFORM } from '@/lib/constants' -const getAnonId = async (id: string) => { - const encoder = new TextEncoder() - const data = encoder.encode(id) - const hashBuffer = await crypto.subtle.digest('SHA-256', data as BufferSource) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - const base64String = btoa(hashArray.map((byte) => String.fromCharCode(byte)).join('')) - - return base64String -} - export function Telemetry() { // Although this is "technically" breaking the rules of hooks // IS_PLATFORM never changes within a session, so this won't cause any issues @@ -64,17 +54,7 @@ export function Telemetry() { return } - const setSentryId = async () => { - let sentryUserId = safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.SENTRY_USER_ID) - if (!sentryUserId) { - sentryUserId = await getAnonId(user?.id) - safeLocalStorage.setItem(LOCAL_STORAGE_KEYS.SENTRY_USER_ID, sentryUserId) - } - Sentry.setUser({ id: sentryUserId }) - } - - // if an error happens, continue without setting a sentry id - setSentryId().catch((e) => console.error(e)) + Sentry.setUser({ id: user.id }) }, [user?.id]) return ( diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index 40ac85123ab..b31810f31eb 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -58,8 +58,6 @@ export const LOCAL_STORAGE_KEYS = { LINTER_SHOW_FOOTER: 'supabase-linter-show-footer', // Key to track account deletion requests ACCOUNT_DELETION_REQUEST: 'supabase-account-deletion-request', - // Used for storing a user id when sending reports to Sentry. The id is hashed for anonymity. - SENTRY_USER_ID: 'supabase-sentry-user-id', // Used for storing the last sign in method used by the user LAST_SIGN_IN_METHOD: 'supabase-last-sign-in-method', // Key to track the last selected schema. The project ref is intentionally put at the end for easier search in the browser console.