This commit is contained in:
Eric Bailey
2026-01-19 17:32:59 -06:00
parent b4f57b94aa
commit c005e02713
10 changed files with 172 additions and 4 deletions
+114
View File
@@ -0,0 +1,114 @@
import {useMemo, useEffect} from 'react'
import {Platform} from 'react-native'
import {
GrowthBook,
GrowthBookProvider,
useFeatureIsOn,
} from '@growthbook/growthbook-react'
import * as env from '#/env'
import {useSession, SessionAccount} from '#/state/session'
import {useGeolocation} from '#/geolocation'
import * as persisted from '#/state/persisted'
import * as referrer from '#/logger/growthbook/util/referrer'
type DefaultAttributes = {
country: string
}
type UserAttributes = {
id: string
pds: string | undefined
platform: string
appVersion: string
bundleIdentifier: string
bundleDate: number
refSrc: string
refUrl: string
appLanguage: string
contentLanguages: string[]
}
const gb = new GrowthBook({
apiHost: env.GROWTHBOOK_API_HOST,
clientKey: env.GROWTHBOOK_CLIENT_KEY,
trackingCallback: (experiment, result) => {
// TODO
console.log('Experiment Viewed', {
experimentId: experiment.key,
variationId: result.key,
})
},
})
export const initializer = gb.init({
timeout: 1e3,
})
export function useGate(gate: string): boolean {
return useFeatureIsOn(gate)
}
export function Provider({children}: {children: React.ReactNode}) {
const geo = useGeolocation()
const {currentAccount} = useSession()
const defaultAttributes = useMemo<DefaultAttributes>(
() => ({
country: geo.countryCode || 'unknown',
}),
[geo],
)
/**
* Decorate existing attributes with any new default attributes
*/
useEffect(() => {
const attr = {
...gb.getAttributes(),
...defaultAttributes,
}
gb.setAttributes(attr)
console.debug(`update attributes`, {attributes: attr})
}, [defaultAttributes])
/**
* Update user attributes on session change, and clear them on logout
*/
useEffect(() => {
if (currentAccount) {
const attr = getUserAttributes(currentAccount)
gb.setAttributes({
...defaultAttributes,
...getUserAttributes(currentAccount),
})
console.debug(`has session, set attributes`, {attributes: attr})
} else {
gb.setAttributes(defaultAttributes)
console.debug(`no session, reset attributes`, {
attributes: defaultAttributes,
})
}
}, [defaultAttributes, currentAccount])
return <GrowthBookProvider growthbook={gb}>{children}</GrowthBookProvider>
}
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 {
id: account.did,
pds: account.pdsUrl,
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,
}
}
+2
View File
@@ -0,0 +1,2 @@
export const src = ''
export const url = ''
@@ -0,0 +1,3 @@
const params = new URLSearchParams(window.location.search)
export const src = params.get('ref_src') ?? ''
export const url = decodeURIComponent(params.get('ref_url') ?? '')
@@ -1,5 +1,6 @@
import {nanoid} from 'nanoid/non-secure'
import {Provider as GrowthbookProvider} from '#/logger/growthbook/context'
import {logEvent} from '#/lib/statsig/statsig'
import {add} from '#/logger/logDump'
import {type MetricEvents} from '#/logger/metrics'
@@ -171,3 +172,10 @@ export class Logger {
* `logger.error(error[, metadata])`
*/
export const logger = Logger.create(Logger.Context.Default)
/**
* Logger context provider
*/
export function Provider({children}: {children: React.ReactNode}) {
return <GrowthbookProvider>{children}</GrowthbookProvider>
}