Integrate feature gates into analytics context
This commit is contained in:
+60
-54
@@ -22,7 +22,6 @@ import {s} from '#/lib/styles'
|
|||||||
import {ThemeProvider} from '#/lib/ThemeContext'
|
import {ThemeProvider} from '#/lib/ThemeContext'
|
||||||
import I18nProvider from '#/locale/i18nProvider'
|
import I18nProvider from '#/locale/i18nProvider'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {initializer as growthbookInitializer} from '#/logger/growthbook'
|
|
||||||
import {Provider as A11yProvider} from '#/state/a11y'
|
import {Provider as A11yProvider} from '#/state/a11y'
|
||||||
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
||||||
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
||||||
@@ -70,7 +69,12 @@ import {
|
|||||||
prefetchAgeAssuranceConfig,
|
prefetchAgeAssuranceConfig,
|
||||||
Provider as AgeAssuranceV2Provider,
|
Provider as AgeAssuranceV2Provider,
|
||||||
} from '#/ageAssurance'
|
} from '#/ageAssurance'
|
||||||
import {AnalyticsContext, setupDeviceId} from '#/analytics'
|
import {
|
||||||
|
AnalyticsContext,
|
||||||
|
AnalyticsFeaturesContext,
|
||||||
|
features,
|
||||||
|
setupDeviceId,
|
||||||
|
} from '#/analytics'
|
||||||
import {IS_ANDROID, IS_IOS} from '#/env'
|
import {IS_ANDROID, IS_IOS} from '#/env'
|
||||||
import {
|
import {
|
||||||
prefetchLiveEvents,
|
prefetchLiveEvents,
|
||||||
@@ -116,7 +120,7 @@ function InnerApp() {
|
|||||||
if (account) {
|
if (account) {
|
||||||
await resumeSession(account)
|
await resumeSession(account)
|
||||||
} else {
|
} else {
|
||||||
await growthbookInitializer
|
await features.init
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`session: resume failed`, {message: e})
|
logger.error(`session: resume failed`, {message: e})
|
||||||
@@ -146,57 +150,59 @@ function InnerApp() {
|
|||||||
<React.Fragment
|
<React.Fragment
|
||||||
// Resets the entire tree below when it changes:
|
// Resets the entire tree below when it changes:
|
||||||
key={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
<AnalyticsFeaturesContext>
|
||||||
<PolicyUpdateOverlayProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<PolicyUpdateOverlayProvider>
|
||||||
<LiveEventsProvider>
|
<StatsigProvider>
|
||||||
<AgeAssuranceV2Provider>
|
<LiveEventsProvider>
|
||||||
<ComposerProvider>
|
<AgeAssuranceV2Provider>
|
||||||
<MessagesProvider>
|
<ComposerProvider>
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<MessagesProvider>
|
||||||
<LabelDefsProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
<ModerationOptsProvider>
|
<LabelDefsProvider>
|
||||||
<LoggedOutViewProvider>
|
<ModerationOptsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<HiddenRepliesProvider>
|
<SelectedFeedProvider>
|
||||||
<HomeBadgeProvider>
|
<HiddenRepliesProvider>
|
||||||
<UnreadNotifsProvider>
|
<HomeBadgeProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<UnreadNotifsProvider>
|
||||||
<MutedThreadsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<ProgressGuideProvider>
|
<MutedThreadsProvider>
|
||||||
<ServiceAccountManager>
|
<ProgressGuideProvider>
|
||||||
<EmailVerificationProvider>
|
<ServiceAccountManager>
|
||||||
<HideBottomBarBorderProvider>
|
<EmailVerificationProvider>
|
||||||
<GestureHandlerRootView
|
<HideBottomBarBorderProvider>
|
||||||
style={s.h100pct}>
|
<GestureHandlerRootView
|
||||||
<GlobalGestureEventsProvider>
|
style={s.h100pct}>
|
||||||
<IntentDialogProvider>
|
<GlobalGestureEventsProvider>
|
||||||
<TestCtrls />
|
<IntentDialogProvider>
|
||||||
<Shell />
|
<TestCtrls />
|
||||||
<ToastOutlet />
|
<Shell />
|
||||||
</IntentDialogProvider>
|
<ToastOutlet />
|
||||||
</GlobalGestureEventsProvider>
|
</IntentDialogProvider>
|
||||||
</GestureHandlerRootView>
|
</GlobalGestureEventsProvider>
|
||||||
</HideBottomBarBorderProvider>
|
</GestureHandlerRootView>
|
||||||
</EmailVerificationProvider>
|
</HideBottomBarBorderProvider>
|
||||||
</ServiceAccountManager>
|
</EmailVerificationProvider>
|
||||||
</ProgressGuideProvider>
|
</ServiceAccountManager>
|
||||||
</MutedThreadsProvider>
|
</ProgressGuideProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</MutedThreadsProvider>
|
||||||
</UnreadNotifsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</HomeBadgeProvider>
|
</UnreadNotifsProvider>
|
||||||
</HiddenRepliesProvider>
|
</HomeBadgeProvider>
|
||||||
</SelectedFeedProvider>
|
</HiddenRepliesProvider>
|
||||||
</LoggedOutViewProvider>
|
</SelectedFeedProvider>
|
||||||
</ModerationOptsProvider>
|
</LoggedOutViewProvider>
|
||||||
</LabelDefsProvider>
|
</ModerationOptsProvider>
|
||||||
</MessagesProvider>
|
</LabelDefsProvider>
|
||||||
</ComposerProvider>
|
</MessagesProvider>
|
||||||
</AgeAssuranceV2Provider>
|
</ComposerProvider>
|
||||||
</LiveEventsProvider>
|
</AgeAssuranceV2Provider>
|
||||||
</StatsigProvider>
|
</LiveEventsProvider>
|
||||||
</PolicyUpdateOverlayProvider>
|
</StatsigProvider>
|
||||||
</QueryProvider>
|
</PolicyUpdateOverlayProvider>
|
||||||
|
</QueryProvider>
|
||||||
|
</AnalyticsFeaturesContext>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</VideoVolumeProvider>
|
</VideoVolumeProvider>
|
||||||
</Splash>
|
</Splash>
|
||||||
|
|||||||
+56
-50
@@ -13,7 +13,6 @@ import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
|||||||
import {ThemeProvider} from '#/lib/ThemeContext'
|
import {ThemeProvider} from '#/lib/ThemeContext'
|
||||||
import I18nProvider from '#/locale/i18nProvider'
|
import I18nProvider from '#/locale/i18nProvider'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {initializer as growthbookInitializer} from '#/logger/growthbook'
|
|
||||||
import {Provider as A11yProvider} from '#/state/a11y'
|
import {Provider as A11yProvider} from '#/state/a11y'
|
||||||
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
||||||
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
||||||
@@ -60,7 +59,12 @@ import {
|
|||||||
prefetchAgeAssuranceConfig,
|
prefetchAgeAssuranceConfig,
|
||||||
Provider as AgeAssuranceV2Provider,
|
Provider as AgeAssuranceV2Provider,
|
||||||
} from '#/ageAssurance'
|
} from '#/ageAssurance'
|
||||||
import {AnalyticsContext, setupDeviceId} from '#/analytics'
|
import {
|
||||||
|
AnalyticsContext,
|
||||||
|
AnalyticsFeaturesContext,
|
||||||
|
features,
|
||||||
|
setupDeviceId,
|
||||||
|
} from '#/analytics'
|
||||||
import {
|
import {
|
||||||
prefetchLiveEvents,
|
prefetchLiveEvents,
|
||||||
Provider as LiveEventsProvider,
|
Provider as LiveEventsProvider,
|
||||||
@@ -92,7 +96,7 @@ function InnerApp() {
|
|||||||
if (account) {
|
if (account) {
|
||||||
await resumeSession(account)
|
await resumeSession(account)
|
||||||
} else {
|
} else {
|
||||||
await growthbookInitializer
|
await features.init
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`session: resumeSession failed`, {message: e})
|
logger.error(`session: resumeSession failed`, {message: e})
|
||||||
@@ -125,53 +129,55 @@ function InnerApp() {
|
|||||||
<React.Fragment
|
<React.Fragment
|
||||||
// Resets the entire tree below when it changes:
|
// Resets the entire tree below when it changes:
|
||||||
key={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
<AnalyticsFeaturesContext>
|
||||||
<PolicyUpdateOverlayProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<PolicyUpdateOverlayProvider>
|
||||||
<LiveEventsProvider>
|
<StatsigProvider>
|
||||||
<AgeAssuranceV2Provider>
|
<LiveEventsProvider>
|
||||||
<ComposerProvider>
|
<AgeAssuranceV2Provider>
|
||||||
<MessagesProvider>
|
<ComposerProvider>
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<MessagesProvider>
|
||||||
<LabelDefsProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
<ModerationOptsProvider>
|
<LabelDefsProvider>
|
||||||
<LoggedOutViewProvider>
|
<ModerationOptsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<HiddenRepliesProvider>
|
<SelectedFeedProvider>
|
||||||
<HomeBadgeProvider>
|
<HiddenRepliesProvider>
|
||||||
<UnreadNotifsProvider>
|
<HomeBadgeProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<UnreadNotifsProvider>
|
||||||
<MutedThreadsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<SafeAreaProvider>
|
<MutedThreadsProvider>
|
||||||
<ProgressGuideProvider>
|
<SafeAreaProvider>
|
||||||
<ServiceConfigProvider>
|
<ProgressGuideProvider>
|
||||||
<EmailVerificationProvider>
|
<ServiceConfigProvider>
|
||||||
<HideBottomBarBorderProvider>
|
<EmailVerificationProvider>
|
||||||
<IntentDialogProvider>
|
<HideBottomBarBorderProvider>
|
||||||
<Shell />
|
<IntentDialogProvider>
|
||||||
<ToastOutlet />
|
<Shell />
|
||||||
</IntentDialogProvider>
|
<ToastOutlet />
|
||||||
</HideBottomBarBorderProvider>
|
</IntentDialogProvider>
|
||||||
</EmailVerificationProvider>
|
</HideBottomBarBorderProvider>
|
||||||
</ServiceConfigProvider>
|
</EmailVerificationProvider>
|
||||||
</ProgressGuideProvider>
|
</ServiceConfigProvider>
|
||||||
</SafeAreaProvider>
|
</ProgressGuideProvider>
|
||||||
</MutedThreadsProvider>
|
</SafeAreaProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</MutedThreadsProvider>
|
||||||
</UnreadNotifsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</HomeBadgeProvider>
|
</UnreadNotifsProvider>
|
||||||
</HiddenRepliesProvider>
|
</HomeBadgeProvider>
|
||||||
</SelectedFeedProvider>
|
</HiddenRepliesProvider>
|
||||||
</LoggedOutViewProvider>
|
</SelectedFeedProvider>
|
||||||
</ModerationOptsProvider>
|
</LoggedOutViewProvider>
|
||||||
</LabelDefsProvider>
|
</ModerationOptsProvider>
|
||||||
</MessagesProvider>
|
</LabelDefsProvider>
|
||||||
</ComposerProvider>
|
</MessagesProvider>
|
||||||
</AgeAssuranceV2Provider>
|
</ComposerProvider>
|
||||||
</LiveEventsProvider>
|
</AgeAssuranceV2Provider>
|
||||||
</StatsigProvider>
|
</LiveEventsProvider>
|
||||||
</PolicyUpdateOverlayProvider>
|
</StatsigProvider>
|
||||||
</QueryProvider>
|
</PolicyUpdateOverlayProvider>
|
||||||
|
</QueryProvider>
|
||||||
|
</AnalyticsFeaturesContext>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
</ActiveVideoProvider>
|
</ActiveVideoProvider>
|
||||||
</VideoVolumeProvider>
|
</VideoVolumeProvider>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import {GrowthBook} from '@growthbook/growthbook-react'
|
||||||
|
|
||||||
|
import {type Metadata} from '#/analytics/types'
|
||||||
|
import * as env from '#/env'
|
||||||
|
|
||||||
|
export {Features} from '#/analytics/features/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We vary the amount of time we wait for GrowthBook to fetch feature
|
||||||
|
* gates based on the strategy specified.
|
||||||
|
*/
|
||||||
|
export type FeatureFetchStrategy = 'prefer-low-latency' | 'prefer-fresh-gates'
|
||||||
|
|
||||||
|
const TIMEOUT_INIT = 500 // TODO should base on p99 or something
|
||||||
|
const TIMEOUT_PREFER_LOW_LATENCY = 250
|
||||||
|
const TIMEOUT_PREFER_FRESH_GATES = 1500
|
||||||
|
|
||||||
|
export const features = new GrowthBook({
|
||||||
|
apiHost: env.GROWTHBOOK_API_HOST,
|
||||||
|
clientKey: env.GROWTHBOOK_CLIENT_KEY,
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializer promise that must be awaited before using the GrowthBook
|
||||||
|
* instance or rendering the `AnalyticsFeaturesContext`. Note: this may not be
|
||||||
|
* fully initialized if it takes longer than `TIMEOUT_INIT` to initialize. In
|
||||||
|
* that case, we may see a flash of uncustomized content until the
|
||||||
|
* initialization completes.
|
||||||
|
*/
|
||||||
|
export const init = new Promise<void>(async y => {
|
||||||
|
await features.init({timeout: TIMEOUT_INIT})
|
||||||
|
y()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh feature gates from GrowthBook. Updates attributes based on the
|
||||||
|
* provided account, if any.
|
||||||
|
*/
|
||||||
|
export async function refresh({strategy}: {strategy: FeatureFetchStrategy}) {
|
||||||
|
await features.refreshFeatures({
|
||||||
|
timeout:
|
||||||
|
strategy === 'prefer-low-latency'
|
||||||
|
? TIMEOUT_PREFER_LOW_LATENCY
|
||||||
|
: TIMEOUT_PREFER_FRESH_GATES,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts our metadata into GrowthBook attributes and sets them.
|
||||||
|
*/
|
||||||
|
export function setAttributes({base, session, preferences}: Metadata) {
|
||||||
|
const {deviceId, sessionId, ...br} = base
|
||||||
|
features.setAttributes({
|
||||||
|
device_id: deviceId, // GrowthBook special field
|
||||||
|
session_id: sessionId, // GrowthBook special field
|
||||||
|
user_id: session?.did, // GrowthBook special field
|
||||||
|
id: session?.did, // GrowthBook special field
|
||||||
|
...br,
|
||||||
|
...(session || {}),
|
||||||
|
...(preferences || {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export enum Features {
|
||||||
|
DebugFeedContext = 'debug_show_feedcontext',
|
||||||
|
IsBskyTeam = 'is_bsky_team_member',
|
||||||
|
DisableOnboardingFindContacts = 'disable_onboarding_find_contacts',
|
||||||
|
DisableSettingsFindContacts = 'disable_settings_find_contacts',
|
||||||
|
DisableLiveNowBeta = 'disable_live_now_beta',
|
||||||
|
}
|
||||||
+61
-6
@@ -1,6 +1,13 @@
|
|||||||
import {createContext, useContext, useMemo} from 'react'
|
import {createContext, useContext, useEffect, useMemo} from 'react'
|
||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Features,
|
||||||
|
features as feats,
|
||||||
|
init,
|
||||||
|
refresh,
|
||||||
|
setAttributes,
|
||||||
|
} from '#/analytics/features'
|
||||||
import {
|
import {
|
||||||
getAndMigrateDeviceId,
|
getAndMigrateDeviceId,
|
||||||
getDeviceIdOrThrow,
|
getDeviceIdOrThrow,
|
||||||
@@ -15,17 +22,24 @@ import {useGeolocation} from '#/geolocation'
|
|||||||
import {device} from '#/storage'
|
import {device} from '#/storage'
|
||||||
|
|
||||||
export * as utils from '#/analytics/utils'
|
export * as utils from '#/analytics/utils'
|
||||||
|
export const features = {init, refresh}
|
||||||
|
|
||||||
type ContextType = {
|
type AnalyticsContextType = {
|
||||||
|
metadata: Metadata
|
||||||
metric: <E extends keyof Metrics>(
|
metric: <E extends keyof Metrics>(
|
||||||
event: E,
|
event: E,
|
||||||
payload: Metrics[E],
|
payload: Metrics[E],
|
||||||
metadata?: MergeableMetadata,
|
metadata?: MergeableMetadata,
|
||||||
) => void
|
) => void
|
||||||
metadata: Metadata
|
feature: (feature: Features) => boolean
|
||||||
|
Features: typeof Features
|
||||||
}
|
}
|
||||||
|
type AnalyticsBaseContextType = Omit<
|
||||||
|
AnalyticsContextType,
|
||||||
|
'feature' | 'Features'
|
||||||
|
>
|
||||||
|
|
||||||
const Context = createContext<ContextType>({
|
const Context = createContext<AnalyticsBaseContextType>({
|
||||||
metric: (event, payload, metadata) => {
|
metric: (event, payload, metadata) => {
|
||||||
metrics.track(event, payload, metadata)
|
metrics.track(event, payload, metadata)
|
||||||
},
|
},
|
||||||
@@ -74,7 +88,7 @@ export function AnalyticsContext({
|
|||||||
}
|
}
|
||||||
combinedMetadata.base.sessionId = sessionId
|
combinedMetadata.base.sessionId = sessionId
|
||||||
combinedMetadata.geolocation = geolocation
|
combinedMetadata.geolocation = geolocation
|
||||||
const context: ContextType = {
|
const context: AnalyticsBaseContextType = {
|
||||||
metadata: combinedMetadata,
|
metadata: combinedMetadata,
|
||||||
metric: (event, payload, extraMetadata) => {
|
metric: (event, payload, extraMetadata) => {
|
||||||
parentContext.metric(event, payload, {
|
parentContext.metric(event, payload, {
|
||||||
@@ -88,6 +102,47 @@ export function AnalyticsContext({
|
|||||||
return <Context.Provider value={childContext}>{children}</Context.Provider>
|
return <Context.Provider value={childContext}>{children}</Context.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAnalytics() {
|
export function AnalyticsFeaturesContext({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const parentContext = useContext(Context)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
feats.setTrackingCallback((experiment, result) => {
|
||||||
|
parentContext.metric('experiment:viewed', {
|
||||||
|
experimentId: experiment.key,
|
||||||
|
variationId: result.key,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, [parentContext.metric])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAttributes(parentContext.metadata)
|
||||||
|
}, [parentContext.metadata])
|
||||||
|
|
||||||
|
const childContext = useMemo<AnalyticsContextType>(() => {
|
||||||
|
return {
|
||||||
|
...parentContext,
|
||||||
|
feature: feats.isOn.bind(feats),
|
||||||
|
Features,
|
||||||
|
}
|
||||||
|
}, [parentContext])
|
||||||
|
|
||||||
|
return <Context.Provider value={childContext}>{children}</Context.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAnalyticsBase() {
|
||||||
return useContext(Context)
|
return useContext(Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAnalytics() {
|
||||||
|
const ctx = useContext(Context)
|
||||||
|
if (!('feature' in ctx) || !('Features' in ctx)) {
|
||||||
|
throw new Error(
|
||||||
|
'useAnalytics must be used within an AnalyticsFeaturesContext',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return ctx as AnalyticsContextType
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {type SessionAccount} from '#/state/session'
|
|||||||
import {type MergeableMetadata, type SessionMetadata} from '#/analytics/types'
|
import {type MergeableMetadata, type SessionMetadata} from '#/analytics/types'
|
||||||
|
|
||||||
export function meta(metadata: MergeableMetadata) {
|
export function meta(metadata: MergeableMetadata) {
|
||||||
|
// I don't care
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const m = useMemo(() => metadata, [metadata])
|
const m = useMemo(() => metadata, [metadata])
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
} from '#/lib/constants'
|
} from '#/lib/constants'
|
||||||
import {getAge} from '#/lib/strings/time'
|
import {getAge} from '#/lib/strings/time'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {refresh as refreshGates} from '#/logger/growthbook'
|
|
||||||
import {setUserMetadata} from '#/logger/metadata'
|
import {setUserMetadata} from '#/logger/metadata'
|
||||||
import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate'
|
import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate'
|
||||||
import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders'
|
import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders'
|
||||||
@@ -33,6 +32,7 @@ import {
|
|||||||
setBirthdateForDid,
|
setBirthdateForDid,
|
||||||
setCreatedAtForDid,
|
setCreatedAtForDid,
|
||||||
} from '#/ageAssurance/data'
|
} from '#/ageAssurance/data'
|
||||||
|
import {features} from '#/analytics'
|
||||||
import {emitNetworkConfirmed, emitNetworkLost} from '../events'
|
import {emitNetworkConfirmed, emitNetworkLost} from '../events'
|
||||||
import {addSessionErrorLog} from './logging'
|
import {addSessionErrorLog} from './logging'
|
||||||
import {
|
import {
|
||||||
@@ -65,7 +65,7 @@ export async function createAgentAndResume(
|
|||||||
agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl)
|
agent.sessionManager.pdsUrl = new URL(storedAccount.pdsUrl)
|
||||||
}
|
}
|
||||||
setUserMetadata(storedAccount)
|
setUserMetadata(storedAccount)
|
||||||
const gates = refreshGates({
|
const gates = features.refresh({
|
||||||
strategy: 'prefer-low-latency',
|
strategy: 'prefer-low-latency',
|
||||||
})
|
})
|
||||||
const moderation = configureModerationForAccount(agent, storedAccount)
|
const moderation = configureModerationForAccount(agent, storedAccount)
|
||||||
@@ -128,7 +128,7 @@ export async function createAgentAndLogin(
|
|||||||
|
|
||||||
const account = agentToSessionAccountOrThrow(agent)
|
const account = agentToSessionAccountOrThrow(agent)
|
||||||
setUserMetadata(account)
|
setUserMetadata(account)
|
||||||
const gates = refreshGates({strategy: 'prefer-fresh-gates'})
|
const gates = features.refresh({strategy: 'prefer-fresh-gates'})
|
||||||
const moderation = configureModerationForAccount(agent, account)
|
const moderation = configureModerationForAccount(agent, account)
|
||||||
const aa = prefetchAgeAssuranceData({agent})
|
const aa = prefetchAgeAssuranceData({agent})
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ export async function createAgentAndCreateAccount(
|
|||||||
})
|
})
|
||||||
const account = agentToSessionAccountOrThrow(agent)
|
const account = agentToSessionAccountOrThrow(agent)
|
||||||
setUserMetadata(account)
|
setUserMetadata(account)
|
||||||
const gates = refreshGates({strategy: 'prefer-fresh-gates'})
|
const gates = features.refresh({strategy: 'prefer-fresh-gates'})
|
||||||
const moderation = configureModerationForAccount(agent, account)
|
const moderation = configureModerationForAccount(agent, account)
|
||||||
|
|
||||||
const createdAt = new Date().toISOString()
|
const createdAt = new Date().toISOString()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {type AtpSessionEvent, type BskyAgent} from '@atproto/api'
|
|||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {useCloseAllActiveElements} from '#/state/util'
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
import {AnalyticsContext, useAnalytics, utils} from '#/analytics'
|
import {AnalyticsContext, useAnalyticsBase, utils} from '#/analytics'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
import {emitSessionDropped} from '../events'
|
import {emitSessionDropped} from '../events'
|
||||||
import {
|
import {
|
||||||
@@ -92,7 +92,7 @@ class SessionStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const ax = useAnalytics()
|
const ax = useAnalyticsBase()
|
||||||
const cancelPendingTask = useOneTaskAtATime()
|
const cancelPendingTask = useOneTaskAtATime()
|
||||||
const [store] = React.useState(() => new SessionStore())
|
const [store] = React.useState(() => new SessionStore())
|
||||||
const state = React.useSyncExternalStore(store.subscribe, store.getState)
|
const state = React.useSyncExternalStore(store.subscribe, store.getState)
|
||||||
|
|||||||
Reference in New Issue
Block a user