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
View File
@@ -28,6 +28,10 @@ EXPO_PUBLIC_CHAT_PROXY_DID=
#
#
# Growthbook config
EXPO_PUBLIC_GROWTHBOOK_API_HOST=
EXPO_PUBLIC_GROWTHBOOK_CLIENT_KEY=
# Sentry DSN for telemetry
EXPO_PUBLIC_SENTRY_DSN=
+1
View File
@@ -93,6 +93,7 @@
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-native-fontawesome": "^0.3.2",
"@growthbook/growthbook-react": "^1.6.2",
"@haileyok/bluesky-video": "0.3.2",
"@ipld/dag-cbor": "^9.2.0",
"@lingui/react": "^4.14.1",
+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>
}
+19
View File
@@ -4678,6 +4678,20 @@
dependencies:
nanoid "^3.3.1"
"@growthbook/growthbook-react@^1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@growthbook/growthbook-react/-/growthbook-react-1.6.2.tgz#847135be0c46b167f980dbe6015e5f4ed010475c"
integrity sha512-96Bo2Jwd4NBn/kBLN4ceF299PhTw8fQltRykD32hu2xMW8/LXhB8swxbPshGK+Xfa2gjgt24kpZ5oSvaVLLT7w==
dependencies:
"@growthbook/growthbook" "^1.6.2"
"@growthbook/growthbook@^1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@growthbook/growthbook/-/growthbook-1.6.2.tgz#6a25122deac8a09955f6bddeb134af62b209809b"
integrity sha512-x3sK6Lff4BVusIzcdBeHZqA3B4kLs3kM/pJ7vvLTJwb6N/+Yn99EF1yc0XU6cfDVqFq6uFkvIFhMwDWUaKD73g==
dependencies:
dom-mutator "^0.6.0"
"@grpc/grpc-js@^1.8.20":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.13.3.tgz#6ad08d186c2a8651697085f790c5c68eaca45904"
@@ -10522,6 +10536,11 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"
dom-mutator@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/dom-mutator/-/dom-mutator-0.6.0.tgz#079d7a4b3e8981a562cd777548b99baab51d65c5"
integrity sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg==
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"