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
+4 -2
View File
@@ -21,7 +21,7 @@ import {Provider as StatsigProvider, tryFetchGates} from '#/lib/statsig/statsig'
import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {logger, Provider as LoggingProvider} from '#/logger'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
@@ -238,7 +238,9 @@ function App() {
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
<InnerApp />
<LoggingProvider>
<InnerApp />
</LoggingProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
+6 -2
View File
@@ -12,7 +12,8 @@ import {QueryProvider} from '#/lib/react-query'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {logger, Provider as LoggingProvider} from '#/logger'
import {initializer as growthbookInitializer} from '#/logger/growthbook/context'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
@@ -85,6 +86,7 @@ function InnerApp() {
useEffect(() => {
async function onLaunch(account?: SessionAccount) {
try {
await growthbookInitializer
if (account) {
await resumeSession(account)
}
@@ -205,7 +207,9 @@ function App() {
<LightboxStateProvider>
<PortalProvider>
<StarterPackProvider>
<InnerApp />
<LoggingProvider>
<InnerApp />
</LoggingProvider>
</StarterPackProvider>
</PortalProvider>
</LightboxStateProvider>
+11
View File
@@ -84,6 +84,17 @@ export const BLUESKY_PROXY_DID: Did =
export const CHAT_PROXY_DID: Did =
process.env.EXPO_PUBLIC_CHAT_PROXY_DID || 'did:web:api.bsky.chat'
/**
* Growthbook API host
*/
export const GROWTHBOOK_API_HOST: string | undefined = process.env.EXPO_PUBLIC_GROWTHBOOK_API_HOST
/**
* Growthbook client key
*/
export const GROWTHBOOK_CLIENT_KEY: string | undefined = process.env.EXPO_PUBLIC_GROWTHBOOK_CLIENT_KEY
/**
* Sentry DSN for telemetry
*/
+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>
}