Skip to content

Commit

Permalink
[Statsig] Prefetch configs for other accounts (#3607)
Browse files Browse the repository at this point in the history
* Poll both current and other accounts

* Make createStatsigOptions a function

* Pass prefetchUsers with the initial request

* Add initializeCalled check

* Be resilient to object identity changes

* Decrease poll interval to 1 minute
  • Loading branch information
gaearon authored Apr 18, 2024
1 parent 41b5b5b commit 6101c32
Showing 1 changed file with 46 additions and 21 deletions.
67 changes: 46 additions & 21 deletions src/lib/statsig/statsig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
import {LogEvents} from './events'
import {Gate} from './gates'

Expand All @@ -34,19 +35,23 @@ if (isWeb && typeof window !== 'undefined') {

export type {LogEvents}

const statsigOptions = {
environment: {
tier:
process.env.NODE_ENV === 'development'
? 'development'
: IS_TESTFLIGHT
? 'staging'
: 'production',
},
// Don't block on waiting for network. The fetched config will kick in on next load.
// This ensures the UI is always consistent and doesn't update mid-session.
// Note this makes cold load (no local storage) and private mode return `false` for all gates.
initTimeoutMs: 1,
function createStatsigOptions(prefetchUsers: StatsigUser[]) {
return {
environment: {
tier:
process.env.NODE_ENV === 'development'
? 'development'
: IS_TESTFLIGHT
? 'staging'
: 'production',
},
// Don't block on waiting for network. The fetched config will kick in on next load.
// This ensures the UI is always consistent and doesn't update mid-session.
// Note this makes cold load (no local storage) and private mode return `false` for all gates.
initTimeoutMs: 1,
// Get fresh flags for other accounts as well, if any.
prefetchUsers,
}
}

type FlatJSONRecord = Record<
Expand Down Expand Up @@ -160,25 +165,45 @@ AppState.addEventListener('change', (state: AppStateStatus) => {
})

export function Provider({children}: {children: React.ReactNode}) {
const {currentAccount} = useSession()
const {currentAccount, accounts} = useSession()
const did = currentAccount?.did
const currentStatsigUser = React.useMemo(() => toStatsigUser(did), [did])

const otherDidsConcatenated = accounts
.map(account => account.did)
.filter(accountDid => accountDid !== did)
.join(' ') // We're only interested in DID changes.
const otherStatsigUsers = React.useMemo(
() => otherDidsConcatenated.split(' ').map(toStatsigUser),
[otherDidsConcatenated],
)
const statsigOptions = React.useMemo(
() => createStatsigOptions(otherStatsigUsers),
[otherStatsigUsers],
)

// Have our own cache in front of Statsig.
// This ensures the results remain stable until the active DID changes.
const [gateCache, setGateCache] = React.useState(() => new Map())
const [prevDid, setPrevDid] = React.useState(did)
if (did !== prevDid) {
setPrevDid(did)
setGateCache(new Map())
}

React.useEffect(() => {
function refresh() {
// This will not affect the current session.
// Statsig will put the results into local storage and we'll pick it up on next load.
Statsig.updateUser(currentStatsigUser)
// Periodically poll Statsig to get the current rule evaluations for all stored accounts.
// These changes are prefetched and stored, but don't get applied until the active DID changes.
// This ensures that when you switch an account, it already has fresh results by then.
const handleIntervalTick = useNonReactiveCallback(() => {
if (Statsig.initializeCalled()) {
// Note: Only first five will be taken into account by Statsig.
Statsig.prefetchUsers([currentStatsigUser, ...otherStatsigUsers])
}
const id = setInterval(refresh, 3 * 60e3 /* 3 min */)
})
React.useEffect(() => {
const id = setInterval(handleIntervalTick, 60e3 /* 1 min */)
return () => clearInterval(id)
}, [currentStatsigUser])
}, [handleIntervalTick])

return (
<GateCache.Provider value={gateCache}>
Expand Down

0 comments on commit 6101c32

Please sign in to comment.