diff options
Diffstat (limited to 'src/lib/statsig')
-rw-r--r-- | src/lib/statsig/events.ts | 63 | ||||
-rw-r--r-- | src/lib/statsig/statsig.tsx | 58 | ||||
-rw-r--r-- | src/lib/statsig/statsig.web.tsx | 75 |
3 files changed, 115 insertions, 81 deletions
diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts new file mode 100644 index 000000000..b83095976 --- /dev/null +++ b/src/lib/statsig/events.ts @@ -0,0 +1,63 @@ +export type LogEvents = { + init: { + initMs: number + } + 'account:loggedIn': { + logContext: 'LoginForm' | 'SwitchAccount' | 'ChooseAccountForm' | 'Settings' + withPassword: boolean + } + 'account:loggedOut': { + logContext: 'SwitchAccount' | 'Settings' | 'Deactivated' + } + 'notifications:openApp': {} + 'state:background': { + secondsActive: number + } + 'state:foreground': {} + 'feed:endReached': { + feedType: string + itemCount: number + } + 'feed:refresh': { + feedType: string + reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest' + } + 'post:create': { + imageCount: number + isReply: boolean + hasLink: boolean + hasQuote: boolean + langs: string + logContext: 'Composer' + } + 'post:like': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:repost': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:unlike': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'post:unrepost': { + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' + } + 'profile:follow': { + logContext: + | 'RecommendedFollowsItem' + | 'PostThreadItem' + | 'ProfileCard' + | 'ProfileHeader' + | 'ProfileHeaderSuggestedFollows' + | 'ProfileMenu' + } + 'profile:unfollow': { + logContext: + | 'RecommendedFollowsItem' + | 'PostThreadItem' + | 'ProfileCard' + | 'ProfileHeader' + | 'ProfileHeaderSuggestedFollows' + | 'ProfileMenu' + } +} diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index 6d9ebeb09..9fa6cce2d 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -1,11 +1,16 @@ import React from 'react' +import {Platform} from 'react-native' import { Statsig, StatsigProvider, useGate as useStatsigGate, } from 'statsig-react-native-expo' +import {AppState, AppStateStatus} from 'react-native' import {useSession} from '../../state/session' import {sha256} from 'js-sha256' +import {LogEvents} from './events' + +export type {LogEvents} const statsigOptions = { environment: { @@ -17,12 +22,28 @@ const statsigOptions = { initTimeoutMs: 1, } -export function logEvent( - eventName: string, - value?: string | number | null, - metadata?: Record<string, string> | null, +type FlatJSONRecord = Record< + string, + string | number | boolean | null | undefined +> + +let getCurrentRouteName: () => string | null | undefined = () => null + +export function attachRouteToLogEvents( + getRouteName: () => string | null | undefined, +) { + getCurrentRouteName = getRouteName +} + +export function logEvent<E extends keyof LogEvents>( + eventName: E & string, + rawMetadata: LogEvents[E] & FlatJSONRecord, ) { - Statsig.logEvent(eventName, value, metadata) + const fullMetadata = { + ...rawMetadata, + } as Record<string, string> // Statsig typings are unnecessarily strict here. + fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' + Statsig.logEvent(eventName, null, fullMetadata) } export function useGate(gateName: string) { @@ -39,9 +60,34 @@ function toStatsigUser(did: string | undefined) { if (did) { userID = sha256(did) } - return {userID} + return { + userID, + platform: Platform.OS, + } } +let lastState: AppStateStatus = AppState.currentState +let lastActive = lastState === 'active' ? performance.now() : null +AppState.addEventListener('change', (state: AppStateStatus) => { + if (state === lastState) { + return + } + lastState = state + if (state === 'active') { + lastActive = performance.now() + logEvent('state:foreground', {}) + } else { + let secondsActive = 0 + if (lastActive != null) { + secondsActive = Math.round((performance.now() - lastActive) / 1e3) + } + lastActive = null + logEvent('state:background', { + secondsActive, + }) + } +}) + export function Provider({children}: {children: React.ReactNode}) { const {currentAccount} = useSession() const currentStatsigUser = React.useMemo( diff --git a/src/lib/statsig/statsig.web.tsx b/src/lib/statsig/statsig.web.tsx deleted file mode 100644 index d1c912019..000000000 --- a/src/lib/statsig/statsig.web.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react' -import { - Statsig, - StatsigProvider, - useGate as useStatsigGate, -} from 'statsig-react' -import {useSession} from '../../state/session' -import {sha256} from 'js-sha256' - -const statsigOptions = { - environment: { - tier: process.env.NODE_ENV === 'development' ? 'development' : '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. - // Note this makes cold load (no local storage) and private mode return `false` for all gates. - initTimeoutMs: 1, -} - -export function logEvent( - eventName: string, - value?: string | number | null, - metadata?: Record<string, string> | null, -) { - Statsig.logEvent(eventName, value, metadata) -} - -export function useGate(gateName: string) { - const {isLoading, value} = useStatsigGate(gateName) - if (isLoading) { - // This should not happen because of waitForInitialization={true}. - console.error('Did not expected isLoading to ever be true.') - } - return value -} - -function toStatsigUser(did: string | undefined) { - let userID: string | undefined - if (did) { - userID = sha256(did) - } - return {userID} -} - -export function Provider({children}: {children: React.ReactNode}) { - const {currentAccount} = useSession() - const currentStatsigUser = React.useMemo( - () => toStatsigUser(currentAccount?.did), - [currentAccount?.did], - ) - - React.useEffect(() => { - function refresh() { - // Intentionally refetching the config using the JS SDK rather than React SDK - // so that the new config is stored in cache but isn't used during this session. - // It will kick in for the next reload. - Statsig.updateUser(currentStatsigUser) - } - const id = setInterval(refresh, 3 * 60e3 /* 3 min */) - return () => clearInterval(id) - }, [currentStatsigUser]) - - return ( - <StatsigProvider - sdkKey="client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV" - mountKey={currentStatsigUser.userID} - user={currentStatsigUser} - // This isn't really blocking due to short initTimeoutMs above. - // However, it ensures `isLoading` is always `false`. - waitForInitialization={true} - options={statsigOptions}> - {children} - </StatsigProvider> - ) -} |