[Statsig] Prefetch configs for other accounts (#3607)
* 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
This commit is contained in:
parent
41b5b5b283
commit
6101c32bd9
1 changed files with 46 additions and 21 deletions
|
@ -8,6 +8,7 @@ import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {IS_TESTFLIGHT} from 'lib/app-info'
|
import {IS_TESTFLIGHT} from 'lib/app-info'
|
||||||
import {useSession} from '../../state/session'
|
import {useSession} from '../../state/session'
|
||||||
|
import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
|
||||||
import {LogEvents} from './events'
|
import {LogEvents} from './events'
|
||||||
import {Gate} from './gates'
|
import {Gate} from './gates'
|
||||||
|
|
||||||
|
@ -34,19 +35,23 @@ if (isWeb && typeof window !== 'undefined') {
|
||||||
|
|
||||||
export type {LogEvents}
|
export type {LogEvents}
|
||||||
|
|
||||||
const statsigOptions = {
|
function createStatsigOptions(prefetchUsers: StatsigUser[]) {
|
||||||
environment: {
|
return {
|
||||||
tier:
|
environment: {
|
||||||
process.env.NODE_ENV === 'development'
|
tier:
|
||||||
? 'development'
|
process.env.NODE_ENV === 'development'
|
||||||
: IS_TESTFLIGHT
|
? 'development'
|
||||||
? 'staging'
|
: IS_TESTFLIGHT
|
||||||
: 'production',
|
? '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.
|
// Don't block on waiting for network. The fetched config will kick in on next load.
|
||||||
// Note this makes cold load (no local storage) and private mode return `false` for all gates.
|
// This ensures the UI is always consistent and doesn't update mid-session.
|
||||||
initTimeoutMs: 1,
|
// 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<
|
type FlatJSONRecord = Record<
|
||||||
|
@ -160,9 +165,25 @@ AppState.addEventListener('change', (state: AppStateStatus) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
export function Provider({children}: {children: React.ReactNode}) {
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount, accounts} = useSession()
|
||||||
const did = currentAccount?.did
|
const did = currentAccount?.did
|
||||||
const currentStatsigUser = React.useMemo(() => toStatsigUser(did), [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 [gateCache, setGateCache] = React.useState(() => new Map())
|
||||||
const [prevDid, setPrevDid] = React.useState(did)
|
const [prevDid, setPrevDid] = React.useState(did)
|
||||||
if (did !== prevDid) {
|
if (did !== prevDid) {
|
||||||
|
@ -170,15 +191,19 @@ export function Provider({children}: {children: React.ReactNode}) {
|
||||||
setGateCache(new Map())
|
setGateCache(new Map())
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
// Periodically poll Statsig to get the current rule evaluations for all stored accounts.
|
||||||
function refresh() {
|
// These changes are prefetched and stored, but don't get applied until the active DID changes.
|
||||||
// This will not affect the current session.
|
// This ensures that when you switch an account, it already has fresh results by then.
|
||||||
// Statsig will put the results into local storage and we'll pick it up on next load.
|
const handleIntervalTick = useNonReactiveCallback(() => {
|
||||||
Statsig.updateUser(currentStatsigUser)
|
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)
|
return () => clearInterval(id)
|
||||||
}, [currentStatsigUser])
|
}, [handleIntervalTick])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GateCache.Provider value={gateCache}>
|
<GateCache.Provider value={gateCache}>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue