Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at bwc9876/pdsls-in-profile 238 lines 6.7 kB view raw
1import {createContext, useContext, useMemo} from 'react' 2import {Platform} from 'react-native' 3 4import {Logger} from '#/logger' 5import { 6 Features, 7 features as feats, 8 init, 9 refresh, 10 setAttributes, 11} from '#/analytics/features' 12import { 13 getAndMigrateDeviceId, 14 getDeviceId, 15 getInitialSessionId, 16 useSessionId, 17} from '#/analytics/identifiers' 18import { 19 getMetadataForLogger, 20 getNavigationMetadata, 21 type MergeableMetadata, 22 type Metadata, 23} from '#/analytics/metadata' 24import {type Metrics, metrics} from '#/analytics/metrics' 25import * as refParams from '#/analytics/misc/refParams' 26import * as env from '#/env' 27import {useGeolocationServiceResponse} from '#/geolocation/service' 28import {device} from '#/storage' 29 30export * as utils from '#/analytics/utils' 31export const features = {init, refresh} 32export {Features} from '#/analytics/features' 33export {type Metrics} from '#/analytics/metrics' 34 35type LoggerType = { 36 debug: Logger['debug'] 37 info: Logger['info'] 38 log: Logger['log'] 39 warn: Logger['warn'] 40 error: Logger['error'] 41 /** 42 * Clones the existing logger and overrides the `context` value. Existing 43 * metadata is inherited. 44 * 45 * ```ts 46 * const ax = useAnalytics() 47 * const logger = ax.logger.useChild(ax.logger.Context.Notifications) 48 * ``` 49 */ 50 useChild: (context: Exclude<Logger['context'], undefined>) => LoggerType 51 Context: typeof Logger.Context 52} 53export type AnalyticsContextType = { 54 metadata: Metadata 55 logger: LoggerType 56 metric: <E extends keyof Metrics>( 57 event: E, 58 payload: Metrics[E], 59 metadata?: MergeableMetadata, 60 ) => void 61 features: typeof Features & { 62 enabled(feature: Features): boolean 63 } 64} 65export type AnalyticsBaseContextType = Omit<AnalyticsContextType, 'features'> 66 67function createLogger( 68 context: Logger['context'], 69 metadata: Partial<Metadata>, 70): LoggerType { 71 const logger = Logger.create(context, metadata) 72 return { 73 debug: logger.debug.bind(logger), 74 info: logger.info.bind(logger), 75 log: logger.log.bind(logger), 76 warn: logger.warn.bind(logger), 77 error: logger.error.bind(logger), 78 useChild: (context: Exclude<Logger['context'], undefined>) => { 79 return useMemo(() => createLogger(context, metadata), [context, metadata]) 80 }, 81 Context: Logger.Context, 82 } 83} 84 85const Context = createContext<AnalyticsBaseContextType>({ 86 logger: createLogger(Logger.Context.Default, {}), 87 metric: (event, payload, metadata) => { 88 if (metadata && '__meta' in metadata) { 89 delete metadata.__meta 90 } 91 metrics.track(event, payload, { 92 ...metadata, 93 navigation: getNavigationMetadata(), 94 }) 95 }, 96 metadata: { 97 base: { 98 deviceId: getDeviceId() ?? 'unknown', 99 sessionId: getInitialSessionId(), 100 platform: Platform.OS, 101 appVersion: env.APP_VERSION, 102 bundleIdentifier: env.BUNDLE_IDENTIFIER, 103 bundleDate: env.BUNDLE_DATE, 104 referrerSrc: refParams.src, 105 referrerUrl: refParams.url, 106 }, 107 geolocation: device.get(['geolocationServiceResponse']) || { 108 countryCode: '', 109 regionCode: '', 110 }, 111 }, 112}) 113Context.displayName = 'AnalyticsContext' 114 115/** 116 * Ensures that deviceId is set and migrated from legacy storage. Handled on 117 * startup in `App.<platform>.tsx`. This must be awaited prior to the app 118 * booting up. 119 */ 120export const setupDeviceId = getAndMigrateDeviceId() 121 122/** 123 * Analytics context provider. Decorates the parent analytics context with 124 * additional metadata. Nesting should be done carefully and sparingly. 125 */ 126export function AnalyticsContext({ 127 children, 128 metadata, 129}: { 130 children: React.ReactNode 131 metadata?: MergeableMetadata 132}) { 133 if (metadata) { 134 if (!('__meta' in metadata)) { 135 throw new Error( 136 'Use the useMeta() helper when passing metadata to AnalyticsContext', 137 ) 138 } 139 } 140 const sessionId = useSessionId() 141 const geolocation = useGeolocationServiceResponse() 142 const parentContext = useContext(Context) 143 const childContext = useMemo(() => { 144 const combinedMetadata = { 145 ...parentContext.metadata, 146 ...metadata, 147 base: { 148 ...parentContext.metadata.base, 149 sessionId, 150 }, 151 geolocation, 152 } 153 const context: AnalyticsBaseContextType = { 154 ...parentContext, 155 logger: createLogger( 156 Logger.Context.Default, 157 getMetadataForLogger(combinedMetadata), 158 ), 159 metadata: combinedMetadata, 160 metric: (event, payload, extraMetadata) => { 161 parentContext.metric(event, payload, { 162 ...combinedMetadata, 163 ...extraMetadata, 164 }) 165 }, 166 } 167 return context 168 }, [sessionId, geolocation, parentContext, metadata]) 169 return <Context.Provider value={childContext}>{children}</Context.Provider> 170} 171 172/** 173 * Feature gates provider. Decorates the parent analytics context with 174 * feature gate capabilities. Should be mounted within `AnalyticsContext`, 175 * and below the `<Fragment key={did} />` breaker in `App.<platform>.tsx`. 176 */ 177export function AnalyticsFeaturesContext({ 178 children, 179}: { 180 children: React.ReactNode 181}) { 182 const parentContext = useContext(Context) 183 184 /** 185 * Side-effects: we need to synchronously set these during the same render 186 * cycle. These calls do not trigger re-renders, they just set properties on 187 * the singleton GrowthBook instance. 188 */ 189 setAttributes(parentContext.metadata) 190 feats.setTrackingCallback((experiment, result) => { 191 parentContext.metric('experiment:viewed', { 192 experimentId: experiment.key, 193 variationId: result.key, 194 }) 195 }) 196 feats.setFeatureUsageCallback((feature, result) => { 197 parentContext.metric('feature:viewed', { 198 featureId: feature, 199 featureResultValue: result.value, 200 experimentId: result.experiment?.key, 201 variationId: result.experimentResult?.key, 202 }) 203 }) 204 205 const childContext = useMemo<AnalyticsContextType>(() => { 206 return { 207 ...parentContext, 208 features: { 209 enabled: feats.isOn.bind(feats), 210 ...Features, 211 }, 212 } 213 }, [parentContext]) 214 215 return <Context.Provider value={childContext}>{children}</Context.Provider> 216} 217 218/** 219 * Basic analytics context without feature gates. Should really only be used 220 * above the `AnalyticsFeaturesContext` provider. 221 */ 222export function useAnalyticsBase() { 223 return useContext(Context) 224} 225 226/** 227 * The main analytics context, including feature gates. Use this everywhere you 228 * need metrics, features, or logging within the React tree. 229 */ 230export function useAnalytics() { 231 const ctx = useContext(Context) 232 if (!('features' in ctx)) { 233 throw new Error( 234 'useAnalytics must be used within an AnalyticsFeaturesContext', 235 ) 236 } 237 return ctx as AnalyticsContextType 238}