From af8fada3c1e1c17ff260dc6f3bef0cf5cf0211c1 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 20 Jan 2026 17:44:21 -0600 Subject: [PATCH] Shared metadata cache --- src/App.native.tsx | 6 +- src/App.web.tsx | 6 +- src/components/PostControls/DiscoverDebug.tsx | 2 - src/geolocation/index.tsx | 4 + src/lib/appState.ts | 1 + src/logger/README.md | 20 ++- src/logger/growthbook/identifiers/common.ts | 24 --- src/logger/growthbook/identifiers/index.ts | 10 -- .../growthbook/identifiers/index.web.ts | 15 -- src/logger/growthbook/index.tsx | 151 +++--------------- src/logger/growthbook/util/referrer.ts | 2 - src/logger/growthbook/util/referrer.web.ts | 3 - src/logger/metadata/deviceId.ts | 26 +++ src/logger/metadata/index.ts | 104 ++++++++++++ src/logger/metadata/sessionId.ts | 34 ++++ src/logger/metadata/sessionId.web.ts | 38 +++++ src/logger/metrics/client.ts | 11 +- src/logger/metrics/events.ts | 5 + src/state/session/agent.ts | 9 +- src/storage/schema.ts | 7 +- 20 files changed, 275 insertions(+), 203 deletions(-) delete mode 100644 src/logger/growthbook/identifiers/common.ts delete mode 100644 src/logger/growthbook/identifiers/index.ts delete mode 100644 src/logger/growthbook/identifiers/index.web.ts delete mode 100644 src/logger/growthbook/util/referrer.ts delete mode 100644 src/logger/growthbook/util/referrer.web.ts create mode 100644 src/logger/metadata/deviceId.ts create mode 100644 src/logger/metadata/index.ts create mode 100644 src/logger/metadata/sessionId.ts create mode 100644 src/logger/metadata/sessionId.web.ts diff --git a/src/App.native.tsx b/src/App.native.tsx index 2d4f49f25d..89714d0b11 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -23,6 +23,7 @@ import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' import {initializer as growthbookInitializer} from '#/logger/growthbook' +import {setupDeviceId} from '#/logger/metadata' import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' @@ -112,9 +113,10 @@ function InnerApp() { useEffect(() => { async function onLaunch(account?: SessionAccount) { try { - await growthbookInitializer if (account) { await resumeSession(account) + } else { + await growthbookInitializer } } catch (e) { logger.error(`session: resume failed`, {message: e}) @@ -208,7 +210,7 @@ function App() { const [isReady, setReady] = useState(false) React.useEffect(() => { - Promise.all([initPersistedState(), Geo.resolve()]).then(() => + Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() => setReady(true), ) }, []) diff --git a/src/App.web.tsx b/src/App.web.tsx index 34205a0bf3..dfe4c3cb18 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -14,6 +14,7 @@ import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' import {initializer as growthbookInitializer} from '#/logger/growthbook' +import {setupDeviceId} from '#/logger/metadata' import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' @@ -88,9 +89,10 @@ function InnerApp() { useEffect(() => { async function onLaunch(account?: SessionAccount) { try { - await growthbookInitializer if (account) { await resumeSession(account) + } else { + await growthbookInitializer } } catch (e) { logger.error(`session: resumeSession failed`, {message: e}) @@ -183,7 +185,7 @@ function App() { const [isReady, setReady] = useState(false) React.useEffect(() => { - Promise.all([initPersistedState(), Geo.resolve()]).then(() => + Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() => setReady(true), ) }, []) diff --git a/src/components/PostControls/DiscoverDebug.tsx b/src/components/PostControls/DiscoverDebug.tsx index 88aedae07d..93d6126d99 100644 --- a/src/components/PostControls/DiscoverDebug.tsx +++ b/src/components/PostControls/DiscoverDebug.tsx @@ -4,7 +4,6 @@ import {t} from '@lingui/macro' import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' import {useGate} from '#/lib/statsig/statsig' -import {logger} from '#/logger' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import * as Toast from '#/components/Toast' @@ -33,7 +32,6 @@ export function DiscoverDebug({ style={[a.absolute, {zIndex: 1000, maxWidth: 65, bottom: -4}, a.left_0]} onPress={e => { e.stopPropagation() - logger.metric('debug', {feedContext}) Clipboard.setStringAsync(feedContext) Toast.show(t`Copied to clipboard`) }}> diff --git a/src/geolocation/index.tsx b/src/geolocation/index.tsx index c9dc0cb1ed..6c04020ea0 100644 --- a/src/geolocation/index.tsx +++ b/src/geolocation/index.tsx @@ -6,6 +6,7 @@ import { useMemo, } from 'react' +import {updateBaseMetadata} from '#/logger/metadata' import {useSyncDeviceGeolocationOnStartup} from '#/geolocation/device' import {useGeolocationServiceResponse} from '#/geolocation/service' import {type Geolocation} from '#/geolocation/types' @@ -53,6 +54,9 @@ export function Provider({children}: {children: ReactNode}) { * Needs to be available for the data prefetching we do on boot. */ device.set(['mergedGeolocation'], geolocation) + updateBaseMetadata({ + country: geolocation.countryCode || 'unknown', + }) }, [geolocation]) useSyncDeviceGeolocationOnStartup(setDeviceGeolocation) diff --git a/src/lib/appState.ts b/src/lib/appState.ts index 1c363c57c0..042e2d480d 100644 --- a/src/lib/appState.ts +++ b/src/lib/appState.ts @@ -7,6 +7,7 @@ export function onAppStateChange(cb: (state: AppStateStatus) => void) { let prev = AppState.currentState return AppState.addEventListener('change', next => { if (next === prev) return + prev = next cb(next) }) } diff --git a/src/logger/README.md b/src/logger/README.md index e3476efdf6..e77d24be94 100644 --- a/src/logger/README.md +++ b/src/logger/README.md @@ -1,8 +1,6 @@ -# Logger +# Logging & Metrics -Simple logger for Bluesky. - -## At a Glance +## Logging ```typescript import { logger, Logger } from '#/logger' @@ -43,3 +41,17 @@ Debug logs are dev-only, and not enabled by default. Once enabled, they can get noisy. So you can filter them by setting the `EXPO_PUBLIC_LOG_DEBUG` env var e.g. `EXPO_PUBLIC_LOG_DEBUG=notifications`. These values can be comma-separated and include wildcards. + +## Metrics + +Metrics are emit using `logger.metric(event, payload)`. + +## Metadata + +We've implemented a shared metadata cache, which is used by the logger and by +our feature-flagging system, GrowthBook. + +## Initialization + +We manage our own device and session IDs, which are initialized at app startup +via `await setupDeviceId`. diff --git a/src/logger/growthbook/identifiers/common.ts b/src/logger/growthbook/identifiers/common.ts deleted file mode 100644 index 1941ce2686..0000000000 --- a/src/logger/growthbook/identifiers/common.ts +++ /dev/null @@ -1,24 +0,0 @@ -import uuid from 'react-native-uuid' -import AsyncStorage from '@react-native-async-storage/async-storage' - -import {device} from '#/storage' - -const LEGACY_STABLE_ID = 'STATSIG_LOCAL_STORAGE_STABLE_ID' - -export async function getAndMigrateStableId() { - const id = (await AsyncStorage.getItem(LEGACY_STABLE_ID)) || uuid.v4() - device.set(['stableId'], id) - return id -} - -export function getStableId() { - return device.get(['stableId']) -} - -export function getStableIdOrThrow() { - const id = device.get(['stableId']) - if (!id) { - throw new Error(`stableId is not set, call getAndMigrateStableId first`) - } - return id -} diff --git a/src/logger/growthbook/identifiers/index.ts b/src/logger/growthbook/identifiers/index.ts deleted file mode 100644 index b9f1708519..0000000000 --- a/src/logger/growthbook/identifiers/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import uuid from 'react-native-uuid' - -export * from '#/logger/growthbook/identifiers/common' - -// TODO probably want to clear this -const sessionId = uuid.v4() -/** - * Stable session ID, persisted for the duration of the user's session - */ -export const getSessionId = () => sessionId diff --git a/src/logger/growthbook/identifiers/index.web.ts b/src/logger/growthbook/identifiers/index.web.ts deleted file mode 100644 index 1ea2da5157..0000000000 --- a/src/logger/growthbook/identifiers/index.web.ts +++ /dev/null @@ -1,15 +0,0 @@ -import uuid from 'react-native-uuid' - -export * from '#/logger/growthbook/identifiers/common' - -/** - * Stable session ID, persisted for the duration of the user's session - */ -export const getSessionId = () => { - let id = sessionStorage.getItem('BSKY_SESSION_ID') - if (!id) { - id = uuid.v4() - sessionStorage.setItem('BSKY_SESSION_ID', id) - } - return id -} diff --git a/src/logger/growthbook/index.tsx b/src/logger/growthbook/index.tsx index dea3a70eb9..3dca39357a 100644 --- a/src/logger/growthbook/index.tsx +++ b/src/logger/growthbook/index.tsx @@ -1,18 +1,9 @@ import {useCallback} from 'react' -import {Platform} from 'react-native' import {GrowthBook} from '@growthbook/growthbook-react' -import {BSKY_SERVICE} from '#/lib/constants' -import { - getAndMigrateStableId, - getSessionId, - getStableId, -} from '#/logger/growthbook/identifiers' -import * as referrer from '#/logger/growthbook/util/referrer' -import * as persisted from '#/state/persisted' -import {type SessionAccount} from '#/state/session' +import {type Metadata} from '#/logger/metadata' +import {metrics} from '#/logger/metrics' import * as env from '#/env' -import {device} from '#/storage' const debugEnabled = env.IS_DEV && true const debug = (message: string, attributes?: Record) => { @@ -28,57 +19,22 @@ const TIMEOUT_PREFER_FRESH_GATES = 1500 /** * We vary the amount of time we wait for GrowthBook to fetch feature * gates based on the strategy specified. - * - * TODO examples */ type FeatureFetchStrategy = 'prefer-low-latency' | 'prefer-fresh-gates' -/** - * These are fields that are handled specially by GrowthBook - */ -type GrowthBookDefaultAttributes = { - /** Special GrowthBook field */ - device_id: string - /** Special GrowthBook field */ - session_id: string -} -/** - * These are user fields that are handled specially by GrowthBook - */ -type GrowthBookDefaultUserAttributes = { - /** Special GrowthBook field */ - user_id: string -} -type DefaultAttributes = GrowthBookDefaultAttributes & { - /** Custom field provided by our Geolocation context */ - country: string -} -type UserAttributes = GrowthBookDefaultUserAttributes & { - // do not use `id`, GrowthBook will think it's the same as `device_id` - did: string - isBskyPds: boolean - platform: string - appVersion: string - bundleIdentifier: string - bundleDate: number - refSrc: string - refUrl: string - appLanguage: string - contentLanguages: string[] -} -export type Attributes = DefaultAttributes & Partial - const gb = new GrowthBook({ apiHost: env.GROWTHBOOK_API_HOST, clientKey: env.GROWTHBOOK_CLIENT_KEY, trackingCallback: (experiment, result) => { - debug(`Experiment Viewed`, { + metrics.track('experiment:viewed', { experimentId: experiment.key, variationId: result.key, }) - // TODO }, - attributes: getDefaultAttributes(), + /** + * Initial values are set on startup in `#/logger/metdata/index.ts` + */ + attributes: {}, }) /** @@ -89,16 +45,6 @@ const gb = new GrowthBook({ * completes. */ export const initializer = new Promise(async y => { - /* - * This _must_ happen first to ensure continuity of the device ID from - * StatSig to GrowthBook - */ - const id = await getAndMigrateStableId() - const attr: GrowthBookDefaultAttributes = { - device_id: id, - session_id: getSessionId(), - } - gb.setAttributes(attr) await gb.init({timeout: TIMEOUT_INIT}) y() }) @@ -107,23 +53,28 @@ export function getGrowthBook() { return gb } -export function getGrowthBookAttributes(): Attributes { - return gb.getAttributes() as Attributes +/** + * Converts our metadata into GrowthBook attributes and sets them. + */ +export function setGrowthBookAttributes({ + deviceId: device_id, + sessionId: session_id, + ...metadata +}: Metadata) { + gb.setAttributes({ + device_id, // GrowthBook special field + session_id, // GrowthBook special field + user_id: metadata.did, // GrowthBook special field + ...metadata, + }) } /** * Refresh feature gates from GrowthBook. Updates attributes based on the * provided account, if any. */ -export async function refresh({ - account, - strategy, -}: { - account?: SessionAccount - strategy: FeatureFetchStrategy -}) { - debug(`refresh`, {account: !!account, strategy}) - setAttributesForAccount(account) +export async function refresh({strategy}: {strategy: FeatureFetchStrategy}) { + debug(`refresh`, {strategy}) await gb.refreshFeatures({ timeout: strategy === 'prefer-low-latency' @@ -140,59 +91,3 @@ export function useGate() { return gb.isOn(gate) }, []) } - -/** - * Get the default attributes that should always be set - * on the GrowthBook instance - */ -function getDefaultAttributes() { - return { - device_id: getStableId() || 'unset', - session_id: getSessionId(), - country: device.get(['mergedGeolocation'])?.countryCode || 'unknown', - } -} - -/** - * Set attributes on the global GrowthBook instance. If an account is provided, - * set user attributes as well. Otherwise, clear user attributes. - */ -function setAttributesForAccount(account?: SessionAccount) { - if (account) { - const attr: Attributes = { - ...getDefaultAttributes(), - ...(getUserAttributes(account) || {}), - } - gb.setAttributes(attr) - debug(`setAttributesForAccount: has account`, {attributes: attr}) - } else { - const attr = getDefaultAttributes() - gb.setAttributes(attr) - debug(`setAttributesForAccount: no account`, {attributes: attr}) - } -} - -/** - * Converts a SessionAccount into user attributes for GrowthBook - */ -export function getUserAttributes(account: SessionAccount): UserAttributes -export function getUserAttributes(account: undefined): null -export function getUserAttributes( - account?: SessionAccount, -): UserAttributes | null { - if (!account) return null - const languagePrefs = persisted.get('languagePrefs') - return { - user_id: account.did, - did: account.did, - isBskyPds: account.service.startsWith(BSKY_SERVICE), - platform: Platform.OS, - appVersion: env.RELEASE_VERSION, - bundleIdentifier: env.BUNDLE_IDENTIFIER, - bundleDate: env.BUNDLE_DATE, - appLanguage: languagePrefs.appLanguage, - contentLanguages: languagePrefs.contentLanguages, - refSrc: referrer.src, - refUrl: referrer.url, - } -} diff --git a/src/logger/growthbook/util/referrer.ts b/src/logger/growthbook/util/referrer.ts deleted file mode 100644 index d2ccdc47a4..0000000000 --- a/src/logger/growthbook/util/referrer.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const src = '' -export const url = '' diff --git a/src/logger/growthbook/util/referrer.web.ts b/src/logger/growthbook/util/referrer.web.ts deleted file mode 100644 index 0105ccd476..0000000000 --- a/src/logger/growthbook/util/referrer.web.ts +++ /dev/null @@ -1,3 +0,0 @@ -const params = new URLSearchParams(window.location.search) -export const src = params.get('ref_src') ?? '' -export const url = decodeURIComponent(params.get('ref_url') ?? '') diff --git a/src/logger/metadata/deviceId.ts b/src/logger/metadata/deviceId.ts new file mode 100644 index 0000000000..ac50143548 --- /dev/null +++ b/src/logger/metadata/deviceId.ts @@ -0,0 +1,26 @@ +import uuid from 'react-native-uuid' +import AsyncStorage from '@react-native-async-storage/async-storage' + +import {device} from '#/storage' + +const LEGACY_STABLE_ID = 'STATSIG_LOCAL_STORAGE_STABLE_ID' + +export async function getAndMigrateDeviceId() { + const migrated = getDeviceId() + if (migrated) return migrated + const id = (await AsyncStorage.getItem(LEGACY_STABLE_ID)) || uuid.v4() + device.set(['deviceId'], id) + return id +} + +export function getDeviceId() { + return device.get(['deviceId']) +} + +export function getDeviceIdOrThrow() { + const id = device.get(['deviceId']) + if (!id) { + throw new Error(`deviceId is not set, call getAndMigrateDeviceId first`) + } + return id +} diff --git a/src/logger/metadata/index.ts b/src/logger/metadata/index.ts new file mode 100644 index 0000000000..66027eb104 --- /dev/null +++ b/src/logger/metadata/index.ts @@ -0,0 +1,104 @@ +import {Platform} from 'react-native' + +import {BSKY_SERVICE} from '#/lib/constants' +import {setGrowthBookAttributes} from '#/logger/growthbook' +import {getAndMigrateDeviceId, getDeviceId} from '#/logger/metadata/deviceId' +import {getSessionId} from '#/logger/metadata/sessionId' +import * as persisted from '#/state/persisted' +import * as env from '#/env' +import {device} from '#/storage' + +export type BaseMetadata = { + deviceId: string + sessionId: string + country: string +} + +export type UserMetadata = { + did: string + isBskyPds: boolean + platform: string + appVersion: string + bundleIdentifier: string + bundleDate: number + refSrc: string + refUrl: string + appLanguage: string + contentLanguages: string[] +} + +export type Metadata = BaseMetadata & Partial + +/** + * Ensures that deviceId is set and migrated from legacy storage. Handled on + * startup in `App..tsx` + */ +export const setupDeviceId = getAndMigrateDeviceId() + +let baseMetadata: BaseMetadata = { + deviceId: getDeviceId() || 'unknown', + sessionId: getSessionId(), + country: device.get(['mergedGeolocation'])?.countryCode || 'unknown', +} +export function updateBaseMetadata( + metadata: Omit, +) { + baseMetadata = { + deviceId: getDeviceId() || 'unknown', + sessionId: getSessionId(), + ...metadata, + } + __onMetadataChange() +} +export function getBaseMetadata() { + return { + ...baseMetadata, + sessionId: getSessionId(), // may have changed + } +} + +let refSrc = '' +let refUrl = '' +if (env.IS_WEB) { + const params = new URLSearchParams(window.location.search) + refSrc = params.get('ref_src') ?? '' + refUrl = decodeURIComponent(params.get('ref_url') ?? '') +} + +let userMetadata: UserMetadata | null = null +export function updateUserMetadata(account: persisted.PersistedAccount | null) { + if (account === null) { + userMetadata = null + } else { + const languagePrefs = persisted.get('languagePrefs') + userMetadata = { + did: account.did, + isBskyPds: account.service.startsWith(BSKY_SERVICE), + platform: Platform.OS, + appVersion: env.RELEASE_VERSION, + bundleIdentifier: env.BUNDLE_IDENTIFIER, + bundleDate: env.BUNDLE_DATE, + appLanguage: languagePrefs.appLanguage, + contentLanguages: languagePrefs.contentLanguages, + refSrc, + refUrl, + } + } + __onMetadataChange() +} +export function getUserMetadata() { + return userMetadata +} + +export function getMetadata(): Metadata { + return { + ...getBaseMetadata(), + ...(getUserMetadata() || {}), + } +} + +function __onMetadataChange() { + const metadata = getMetadata() + setGrowthBookAttributes(metadata) +} +__onMetadataChange() diff --git a/src/logger/metadata/sessionId.ts b/src/logger/metadata/sessionId.ts new file mode 100644 index 0000000000..3a7d3bf012 --- /dev/null +++ b/src/logger/metadata/sessionId.ts @@ -0,0 +1,34 @@ +import uuid from 'react-native-uuid' + +import {onAppStateChange} from '#/lib/appState' +import {device} from '#/storage' + +const TTL = 5 * 60 * 1e3 // 5 min on native +function expired(since: number | undefined) { + if (since === undefined) return false + return Date.now() - since >= TTL +} + +let sessionId = (() => { + const existing = device.get(['nativeSessionId']) + const lastEvent = device.get(['nativeSessionIdLastEventAt']) + const id = existing && !expired(lastEvent) ? existing : uuid.v4() + device.set(['nativeSessionId'], id) + return id +})() + +onAppStateChange(state => { + if (state === 'active') { + const lastEvent = device.get(['nativeSessionIdLastEventAt']) + if (expired(lastEvent)) { + sessionId = uuid.v4() + device.set(['nativeSessionId'], sessionId) + } + } else { + device.set(['nativeSessionIdLastEventAt'], Date.now()) + } +}) + +export function getSessionId() { + return sessionId +} diff --git a/src/logger/metadata/sessionId.web.ts b/src/logger/metadata/sessionId.web.ts new file mode 100644 index 0000000000..60702539c8 --- /dev/null +++ b/src/logger/metadata/sessionId.web.ts @@ -0,0 +1,38 @@ +import uuid from 'react-native-uuid' + +import {onAppStateChange} from '#/lib/appState' + +const TTL = 30 * 60 * 1e3 // 30 min on web +const SESSION_ID_KEY = 'bsky_session_id' +const LAST_EVENT_KEY = 'bsky_session_id_last_event_at' + +function expired(since: number | undefined) { + if (since === undefined) return false + return Date.now() - since >= TTL +} + +let sessionId = (() => { + const existing = window.sessionStorage.getItem(SESSION_ID_KEY) + const lastEventStr = window.sessionStorage.getItem(LAST_EVENT_KEY) + const lastEvent = lastEventStr ? Number(lastEventStr) : undefined + const id = existing && !expired(lastEvent) ? existing : uuid.v4() + window.sessionStorage.setItem(SESSION_ID_KEY, id) + return id +})() + +onAppStateChange(state => { + if (state === 'active') { + const lastEventStr = window.sessionStorage.getItem(LAST_EVENT_KEY) + const lastEvent = lastEventStr ? Number(lastEventStr) : undefined + if (expired(lastEvent)) { + sessionId = uuid.v4() + window.sessionStorage.setItem(SESSION_ID_KEY, sessionId) + } + } else { + window.sessionStorage.setItem(LAST_EVENT_KEY, String(Date.now())) + } +}) + +export function getSessionId() { + return sessionId +} diff --git a/src/logger/metrics/client.ts b/src/logger/metrics/client.ts index 86f984ecb0..911a158e85 100644 --- a/src/logger/metrics/client.ts +++ b/src/logger/metrics/client.ts @@ -1,10 +1,7 @@ import {onAppStateChange} from '#/lib/appState' import {isNetworkError} from '#/lib/strings/errors' -import { - type Attributes, - getGrowthBook, - getGrowthBookAttributes, -} from '#/logger/growthbook' +import {getGrowthBook} from '#/logger/growthbook' +import {getMetadata, type Metadata} from '#/logger/metadata' import {type Metrics} from '#/logger/metrics/events' import {Sentry} from '#/logger/sentry/lib' import * as env from '#/env' @@ -13,7 +10,7 @@ type Event = { time: number event: keyof M payload: M[keyof M] - metadata: Attributes + metadata: Metadata } const TRACKING_ENDPOINT = env.METRICS_API_HOST + '/track' @@ -47,7 +44,7 @@ export class MetricsClient { time: Date.now(), event, payload, - metadata: getGrowthBookAttributes(), + metadata: getMetadata(), }) if (this.queue.length > 100) { diff --git a/src/logger/metrics/events.ts b/src/logger/metrics/events.ts index 864e07ec89..b1c5b11fcf 100644 --- a/src/logger/metrics/events.ts +++ b/src/logger/metrics/events.ts @@ -7,6 +7,11 @@ export type Metrics = { init: { initMs: number } + 'experiment:viewed': { + experimentId: string + variationId: string + } + 'account:loggedIn': { logContext: | 'LoginForm' diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 5f99b9a4ef..b323be2640 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -25,6 +25,7 @@ import { import {getAge} from '#/lib/strings/time' import {logger} from '#/logger' import {refresh as refreshGates} from '#/logger/growthbook' +import {updateUserMetadata} from '#/logger/metadata' import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate' import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders' import { @@ -63,8 +64,8 @@ export async function createAgentAndResume( if (storedAccount.pdsUrl) { agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl) } + updateUserMetadata(storedAccount) const gates = refreshGates({ - account: storedAccount, strategy: 'prefer-low-latency', }) const moderation = configureModerationForAccount(agent, storedAccount) @@ -126,7 +127,8 @@ export async function createAgentAndLogin( }) const account = agentToSessionAccountOrThrow(agent) - const gates = refreshGates({account, strategy: 'prefer-fresh-gates'}) + updateUserMetadata(account) + const gates = refreshGates({strategy: 'prefer-fresh-gates'}) const moderation = configureModerationForAccount(agent, account) const aa = prefetchAgeAssuranceData({agent}) @@ -174,7 +176,8 @@ export async function createAgentAndCreateAccount( verificationCode, }) const account = agentToSessionAccountOrThrow(agent) - const gates = refreshGates({account, strategy: 'prefer-fresh-gates'}) + updateUserMetadata(account) + const gates = refreshGates({strategy: 'prefer-fresh-gates'}) const moderation = configureModerationForAccount(agent, account) const createdAt = new Date().toISOString() diff --git a/src/storage/schema.ts b/src/storage/schema.ts index 21abc60ed8..d2baec1cfc 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -9,7 +9,12 @@ export type Device = { * Formerly managed by StatSig, this is the migrated stable ID for the * device, used with our logging and metrics tracking. */ - stableId: string | undefined + deviceId?: string + /** + * Session ID storage for _native only_. On web, use we `sessionStorage` + */ + nativeSessionId?: string + nativeSessionIdLastEventAt?: number fontScale: '-2' | '-1' | '0' | '1' | '2' fontFamily: 'system' | 'theme'