Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {MMKV} from '@bsky.app/react-native-mmkv'
2import {setPolyfills} from '@growthbook/growthbook'
3import {GrowthBook} from '@growthbook/growthbook-react'
4
5import {getNavigationMetadata, type Metadata} from '#/analytics/metadata'
6import * as env from '#/env'
7
8export {Features} from '#/analytics/features/types'
9
10const CACHE = new MMKV({id: 'bsky_features_cache'})
11
12setPolyfills({
13 localStorage: {
14 getItem: key => {
15 const value = CACHE.getString(key)
16 return value != null ? JSON.parse(value) : null
17 },
18 setItem: async (key, value) => {
19 CACHE.set(key, value)
20 },
21 },
22})
23
24/**
25 * We vary the amount of time we wait for GrowthBook to fetch feature
26 * gates based on the strategy specified.
27 */
28export type FeatureFetchStrategy = 'prefer-low-latency' | 'prefer-fresh-gates'
29
30const TIMEOUT_INIT = 500 // TODO should base on p99 or something
31const TIMEOUT_PREFER_LOW_LATENCY = 250
32const TIMEOUT_PREFER_FRESH_GATES = 1500
33
34export const features = new GrowthBook({
35 apiHost: env.GROWTHBOOK_API_HOST,
36 clientKey: env.GROWTHBOOK_CLIENT_KEY,
37})
38
39/**
40 * Initializer promise that must be awaited before using the GrowthBook
41 * instance or rendering the `AnalyticsFeaturesContext`. Note: this may not be
42 * fully initialized if it takes longer than `TIMEOUT_INIT` to initialize. In
43 * that case, we may see a flash of uncustomized content until the
44 * initialization completes.
45 */
46export const init = new Promise<void>(async y => {
47 await features.init({timeout: TIMEOUT_INIT})
48 y()
49})
50
51/**
52 * Refresh feature gates from GrowthBook. Updates attributes based on the
53 * provided account, if any.
54 */
55export async function refresh({strategy}: {strategy: FeatureFetchStrategy}) {
56 await features.refreshFeatures({
57 timeout:
58 strategy === 'prefer-low-latency'
59 ? TIMEOUT_PREFER_LOW_LATENCY
60 : TIMEOUT_PREFER_FRESH_GATES,
61 })
62}
63
64/**
65 * Converts our metadata into GrowthBook attributes and sets them. GrowthBook
66 * attributes are manually configured in the GrowthBook dashboard. So these
67 * values need to match exactly. Therefore, let's add them here manually to and
68 * not spread them to avoid mistakes.
69 */
70export function setAttributes({
71 base,
72 geolocation,
73 session,
74 preferences,
75}: Metadata) {
76 features.setAttributes({
77 deviceId: base.deviceId,
78 sessionId: base.sessionId,
79 platform: base.platform,
80 appVersion: base.appVersion,
81 countryCode: geolocation.countryCode,
82 regionCode: geolocation.regionCode,
83 did: session?.did,
84 isBskyPds: session?.isBskyPds,
85 appLanguage: preferences?.appLanguage,
86 contentLanguages: preferences?.contentLanguages,
87 currentScreen: getNavigationMetadata()?.currentScreen,
88 })
89}