forked from NWACus/avy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFeatureFlags.tsx
128 lines (105 loc) · 5.01 KB
/
FeatureFlags.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Create an object that stores feature flags as resolved by the PostHog server and as set by the user,
// storing state in memory and exposing hooks to consume and edit them.
import React, {createContext, ReactNode, useContext, useEffect, useState} from 'react';
import _ from 'lodash';
import * as Application from 'expo-application';
import * as Updates from 'expo-updates';
import {useNetInfo} from '@react-native-community/netinfo';
import PostHog, {useFeatureFlags, usePostHog} from 'posthog-react-native';
import {PostHogCore} from 'posthog-react-native/lib/posthog-core/src';
import {useAppState} from 'hooks/useAppState';
import {getUpdateGroupId} from 'hooks/useEASUpdateStatus';
import {logger} from 'logger';
import {usePreferences} from 'Preferences';
export type FeatureFlagsReturn = ReturnType<PostHogCore['getFeatureFlags']>;
export type FeatureFlags = Exclude<FeatureFlagsReturn, undefined>;
export type FeatureFlagKey = keyof FeatureFlags;
export type FeatureFlagValue = FeatureFlags[keyof FeatureFlags];
const defaultFeatureFlags: FeatureFlags = {};
interface FeatureFlagsContextType {
featureFlags: FeatureFlags;
clientSideFeatureFlagOverrides: FeatureFlags;
setClientSideFeatureFlagOverrides: React.Dispatch<React.SetStateAction<FeatureFlags>>;
}
const FeatureFlagsContext = createContext<FeatureFlagsContextType>({
featureFlags: defaultFeatureFlags,
clientSideFeatureFlagOverrides: defaultFeatureFlags,
setClientSideFeatureFlagOverrides: () => undefined,
});
interface FeatureFlagsProviderProps {
children?: ReactNode;
}
const tryReloadFeatureFlags = (posthog: PostHog) => {
logger.debug('fetching feature flags');
void posthog.reloadFeatureFlagsAsync();
};
// In release and preview mode, When returning to foreground, don't fetch feature flags more frequently than every 30 minutes
// In development mode, refresh every time we return from foreground
const FEATURE_FLAG_REFRESH_INTERVAL_MS = Updates.channel ? 30 * 60 * 1000 : 0;
const tryReloadFeatureFlagsWithDebounce = _.debounce(tryReloadFeatureFlags, FEATURE_FLAG_REFRESH_INTERVAL_MS);
export const FeatureFlagsProvider: React.FC<FeatureFlagsProviderProps> = ({children}) => {
const postHog = usePostHog();
useEffect(() => {
if (postHog) {
postHog.register({
// Posthog automatically captures `Application.nativeBuildVersion` as `App Build`, but stores it as a string.
// We additionally capture it as a number here, so that we can use < and > in feature flag rules.
buildNumber: Number.parseInt(Application.nativeBuildVersion || '0'),
updateGroupId: getUpdateGroupId(),
updateBuildTime: process.env.EXPO_PUBLIC_GIT_REVISION as string,
channel: Updates.channel || 'development',
});
}
}, [postHog]);
// We use the mixpanel user id (a unique UUID generated for each install of the app) as the posthog distinct id as well.
const {
preferences: {mixpanelUserId: distinctUserId},
} = usePreferences();
const [userIdentified, setUserIdentified] = useState(false);
useEffect(() => {
if (postHog && distinctUserId && !userIdentified) {
postHog.identify(distinctUserId);
setUserIdentified(true);
logger.debug('identified user, reloading feature flags', {distinctUserId});
tryReloadFeatureFlagsWithDebounce(postHog);
}
}, [postHog, distinctUserId, userIdentified]);
const appState = useAppState();
useEffect(() => {
if (postHog && appState === 'active') {
logger.debug('appState changed to active, reloading feature flags');
tryReloadFeatureFlagsWithDebounce(postHog);
}
}, [appState, postHog]);
const netInfo = useNetInfo();
useEffect(() => {
if (postHog && netInfo.isConnected && netInfo.isInternetReachable) {
logger.debug('network online, reloading feature flags');
tryReloadFeatureFlagsWithDebounce(postHog);
}
}, [netInfo, postHog]);
// TODO: why does TS thing useFeatureFlags() returns an any? it has a concrete return type ...
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const featureFlags: FeatureFlags = useFeatureFlags();
const [clientSideFeatureFlagOverrides, setClientSideFeatureFlagOverrides] = useState<FeatureFlags>({});
return (
<FeatureFlagsContext.Provider
value={{
featureFlags: featureFlags,
clientSideFeatureFlagOverrides: clientSideFeatureFlagOverrides,
setClientSideFeatureFlagOverrides: setClientSideFeatureFlagOverrides,
}}>
{children}
</FeatureFlagsContext.Provider>
);
};
export const useAllFeatureFlags = (): FeatureFlags | undefined => {
const flags = useContext(FeatureFlagsContext);
return _.merge({}, flags.featureFlags, flags.clientSideFeatureFlagOverrides);
};
export const useOneFeatureFlag = (key: FeatureFlagKey): FeatureFlagValue | undefined => {
const flags = useContext(FeatureFlagsContext);
const resolved: FeatureFlags = _.merge({}, flags.featureFlags, flags.clientSideFeatureFlagOverrides);
return resolved && resolved[key];
};
export const useDebugFeatureFlags = () => useContext(FeatureFlagsContext);