diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index 151b365d..c43d2bf8 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -8,6 +8,7 @@ import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {IS_TESTFLIGHT} from 'lib/app-info' import {useSession} from '../../state/session' +import {timeout} from '../async/timeout' import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback' import {LogEvents} from './events' import {Gate} from './gates' @@ -164,6 +165,31 @@ AppState.addEventListener('change', (state: AppStateStatus) => { } }) +export async function tryFetchGates( + did: string, + strategy: 'prefer-low-latency' | 'prefer-fresh-gates', +) { + try { + let timeoutMs = 250 // Don't block the UI if we can't do this fast. + if (strategy === 'prefer-fresh-gates') { + // Use this for less common operations where the user would be OK with a delay. + timeoutMs = 1500 + } + // Note: This condition is currently false the very first render because + // Statsig has not initialized yet. In the future, we can fix this by + // doing the initialization ourselves instead of relying on the provider. + if (Statsig.initializeCalled()) { + await Promise.race([ + timeout(timeoutMs), + Statsig.prefetchUsers([toStatsigUser(did)]), + ]) + } + } catch (e) { + // Don't leak errors to the calling code, this is meant to be always safe. + console.error(e) + } +} + export function Provider({children}: {children: React.ReactNode}) { const {currentAccount, accounts} = useSession() const did = currentAccount?.did diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index b88181eb..1d60eaf8 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -9,7 +9,7 @@ import {jwtDecode} from 'jwt-decode' import {track} from '#/lib/analytics/analytics' import {networkRetry} from '#/lib/async/retry' import {IS_TEST_USER} from '#/lib/constants' -import {logEvent, LogEvents} from '#/lib/statsig/statsig' +import {logEvent, LogEvents, tryFetchGates} from '#/lib/statsig/statsig' import {hasProp} from '#/lib/type-guards' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' @@ -243,6 +243,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) { if (!agent.session) { throw new Error(`session: createAccount failed to establish a session`) } + const fetchingGates = tryFetchGates( + agent.session.did, + 'prefer-fresh-gates', + ) const deactivated = isSessionDeactivated(agent.session.accessJwt) if (!deactivated) { @@ -283,6 +287,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { ) __globalAgent = agent + await fetchingGates upsertAccount(account) logger.debug(`session: created account`, {}, logger.DebugContext.session) @@ -303,6 +308,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) { if (!agent.session) { throw new Error(`session: login failed to establish a session`) } + const fetchingGates = tryFetchGates( + agent.session.did, + 'prefer-fresh-gates', + ) const account: SessionAccount = { service: agent.service.toString(), @@ -330,6 +339,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { __globalAgent = agent // @ts-ignore if (IS_DEV && isWeb) window.agent = agent + await fetchingGates upsertAccount(account) logger.debug(`session: logged in`, {}, logger.DebugContext.session) @@ -362,6 +372,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const initSession = React.useCallback( async account => { logger.debug(`session: initSession`, {}, logger.DebugContext.session) + const fetchingGates = tryFetchGates(account.did, 'prefer-low-latency') const agent = new BskyAgent({ service: account.service, @@ -406,6 +417,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { agent.session = prevSession __globalAgent = agent + await fetchingGates upsertAccount(account) if (prevSession.deactivated) { @@ -442,6 +454,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { try { const freshAccount = await resumeSessionWithFreshAccount() __globalAgent = agent + await fetchingGates upsertAccount(freshAccount) } catch (e) { /*