mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at schema-errors 158 lines 4.6 kB view raw
1import React from 'react' 2import {AppState, AppStateStatus} from 'react-native' 3import AsyncStorage from '@react-native-async-storage/async-storage' 4import {createClient, SegmentClient} from '@segment/analytics-react-native' 5import {sha256} from 'js-sha256' 6import {Native} from 'sentry-expo' 7 8import {useSession, SessionAccount} from '#/state/session' 9import {ScreenPropertiesMap, TrackPropertiesMap} from './types' 10import {logger} from '#/logger' 11 12type AppInfo = { 13 build?: string | undefined 14 name?: string | undefined 15 namespace?: string | undefined 16 version?: string | undefined 17} 18 19// Delay creating until first actual use. 20let segmentClient: SegmentClient | null = null 21function getClient(): SegmentClient { 22 if (!segmentClient) { 23 segmentClient = createClient({ 24 writeKey: '8I6DsgfiSLuoONyaunGoiQM7A6y2ybdI', 25 trackAppLifecycleEvents: false, 26 proxy: 'https://api.events.bsky.app/v1', 27 }) 28 } 29 return segmentClient 30} 31 32export const track = async <E extends keyof TrackPropertiesMap>( 33 event: E, 34 properties?: TrackPropertiesMap[E], 35) => { 36 await getClient().track(event, properties) 37} 38 39export function useAnalytics() { 40 const {hasSession} = useSession() 41 42 return React.useMemo(() => { 43 if (hasSession) { 44 return { 45 async screen<E extends keyof ScreenPropertiesMap>( 46 event: E, 47 properties?: ScreenPropertiesMap[E], 48 ) { 49 await getClient().screen(event, properties) 50 }, 51 async track<E extends keyof TrackPropertiesMap>( 52 event: E, 53 properties?: TrackPropertiesMap[E], 54 ) { 55 await getClient().track(event, properties) 56 }, 57 } 58 } 59 // dont send analytics pings for anonymous users 60 return { 61 screen: async () => {}, 62 track: async () => {}, 63 } 64 }, [hasSession]) 65} 66 67export function init(account: SessionAccount | undefined) { 68 setupListenersOnce() 69 70 if (account) { 71 const client = getClient() 72 if (account.did) { 73 const did_hashed = sha256(account.did) 74 client.identify(did_hashed, {did_hashed}) 75 Native.setUser({id: did_hashed}) 76 logger.debug('Ping w/hash') 77 } else { 78 logger.debug('Ping w/o hash') 79 client.identify() 80 } 81 } 82} 83 84let didSetupListeners = false 85function setupListenersOnce() { 86 if (didSetupListeners) { 87 return 88 } 89 didSetupListeners = true 90 // NOTE 91 // this is a copy of segment's own lifecycle event tracking 92 // we handle it manually to ensure that it never fires while the app is backgrounded 93 // -prf 94 const client = getClient() 95 client.isReady.onChange(async () => { 96 if (AppState.currentState !== 'active') { 97 logger.debug('Prevented a metrics ping while the app was backgrounded') 98 return 99 } 100 const context = client.context.get() 101 if (typeof context?.app === 'undefined') { 102 logger.debug('Aborted metrics ping due to unavailable context') 103 return 104 } 105 106 const oldAppInfo = await readAppInfo() 107 const newAppInfo = context.app as AppInfo 108 writeAppInfo(newAppInfo) 109 logger.debug('Recording app info', {new: newAppInfo, old: oldAppInfo}) 110 111 if (typeof oldAppInfo === 'undefined') { 112 client.track('Application Installed', { 113 version: newAppInfo.version, 114 build: newAppInfo.build, 115 }) 116 } else if (newAppInfo.version !== oldAppInfo.version) { 117 client.track('Application Updated', { 118 version: newAppInfo.version, 119 build: newAppInfo.build, 120 previous_version: oldAppInfo.version, 121 previous_build: oldAppInfo.build, 122 }) 123 } 124 client.track('Application Opened', { 125 from_background: false, 126 version: newAppInfo.version, 127 build: newAppInfo.build, 128 }) 129 }) 130 131 let lastState: AppStateStatus = AppState.currentState 132 AppState.addEventListener('change', (state: AppStateStatus) => { 133 if (state === 'active' && lastState !== 'active') { 134 const context = client.context.get() 135 client.track('Application Opened', { 136 from_background: true, 137 version: context?.app?.version, 138 build: context?.app?.build, 139 }) 140 } else if (state !== 'active' && lastState === 'active') { 141 client.track('Application Backgrounded') 142 } 143 lastState = state 144 }) 145} 146 147async function writeAppInfo(value: AppInfo) { 148 await AsyncStorage.setItem('BSKY_APP_INFO', JSON.stringify(value)) 149} 150 151async function readAppInfo(): Promise<AppInfo | undefined> { 152 const rawData = await AsyncStorage.getItem('BSKY_APP_INFO') 153 const obj = rawData ? JSON.parse(rawData) : undefined 154 if (!obj || typeof obj !== 'object') { 155 return undefined 156 } 157 return obj 158}