Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Enable updates for `production` behind `receive_updates` gate (#3496)

* add gate type

* gate the updates

* enable updates in `production`

* web placeholder for `useOTAUpdates()`

* update comment

authored by

Hailey and committed by
GitHub
f91aa37c 1f587ea4

+49 -40
+10 -6
app.config.js
··· 42 42 43 43 const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development' 44 44 const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight' 45 + const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production' 45 46 46 - const UPDATES_CHANNEL = IS_TESTFLIGHT ? 'testflight' : 'production' 47 + const UPDATES_CHANNEL = IS_TESTFLIGHT 48 + ? 'testflight' 49 + : IS_PRODUCTION 50 + ? 'production' 51 + : undefined 52 + const UPDATES_ENABLED = !!UPDATES_CHANNEL 47 53 48 54 return { 49 55 expo: { ··· 126 132 }, 127 133 updates: { 128 134 url: 'https://updates.bsky.app/manifest', 129 - // TODO Eventually we want to enable this for all environments, but for now it will only be used for 130 - // TestFlight builds 131 - enabled: IS_TESTFLIGHT, 135 + enabled: UPDATES_ENABLED, 132 136 fallbackToCacheTimeout: 30000, 133 - codeSigningCertificate: IS_TESTFLIGHT 137 + codeSigningCertificate: UPDATES_ENABLED 134 138 ? './code-signing/certificate.pem' 135 139 : undefined, 136 - codeSigningMetadata: IS_TESTFLIGHT 140 + codeSigningMetadata: UPDATES_ENABLED 137 141 ? { 138 142 keyid: 'main', 139 143 alg: 'rsa-v1_5-sha256',
-2
src/App.native.tsx
··· 19 19 import * as persisted from '#/state/persisted' 20 20 import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' 21 21 import {useIntentHandler} from 'lib/hooks/useIntentHandler' 22 - import {useOTAUpdates} from 'lib/hooks/useOTAUpdates' 23 22 import {useNotificationsListener} from 'lib/notifications/notifications' 24 23 import {QueryProvider} from 'lib/react-query' 25 24 import {s} from 'lib/styles' ··· 58 57 const {_} = useLingui() 59 58 60 59 useIntentHandler() 61 - useOTAUpdates() 62 60 63 61 // init 64 62 useEffect(() => {
+34 -32
src/lib/hooks/useOTAUpdates.ts
··· 12 12 13 13 import {logger} from '#/logger' 14 14 import {IS_TESTFLIGHT} from 'lib/app-info' 15 + import {useGate} from 'lib/statsig/statsig' 15 16 import {isIOS} from 'platform/detection' 16 17 17 18 const MINIMUM_MINIMIZE_TIME = 15 * 60e3 ··· 30 31 } 31 32 32 33 export function useOTAUpdates() { 34 + const shouldReceiveUpdates = 35 + useGate('receive_updates') && isEnabled && !__DEV__ 36 + 33 37 const appState = React.useRef<AppStateStatus>('active') 34 38 const lastMinimize = React.useRef(0) 35 39 const ranInitialCheck = React.useRef(false) ··· 51 55 logger.debug('No update available.') 52 56 } 53 57 } catch (e) { 54 - logger.warn('OTA Update Error', {error: `${e}`}) 58 + logger.error('OTA Update Error', {error: `${e}`}) 55 59 } 56 60 }, 10e3) 57 61 }, []) 58 62 59 - const onIsTestFlight = React.useCallback(() => { 60 - setTimeout(async () => { 61 - try { 62 - await setExtraParams() 63 + const onIsTestFlight = React.useCallback(async () => { 64 + try { 65 + await setExtraParams() 63 66 64 - const res = await checkForUpdateAsync() 65 - if (res.isAvailable) { 66 - await fetchUpdateAsync() 67 + const res = await checkForUpdateAsync() 68 + if (res.isAvailable) { 69 + await fetchUpdateAsync() 67 70 68 - Alert.alert( 69 - 'Update Available', 70 - 'A new version of the app is available. Relaunch now?', 71 - [ 72 - { 73 - text: 'No', 74 - style: 'cancel', 75 - }, 76 - { 77 - text: 'Relaunch', 78 - style: 'default', 79 - onPress: async () => { 80 - await reloadAsync() 81 - }, 71 + Alert.alert( 72 + 'Update Available', 73 + 'A new version of the app is available. Relaunch now?', 74 + [ 75 + { 76 + text: 'No', 77 + style: 'cancel', 78 + }, 79 + { 80 + text: 'Relaunch', 81 + style: 'default', 82 + onPress: async () => { 83 + await reloadAsync() 82 84 }, 83 - ], 84 - ) 85 - } 86 - } catch (e: any) { 87 - // No need to handle 85 + }, 86 + ], 87 + ) 88 88 } 89 - }, 3e3) 89 + } catch (e: any) { 90 + logger.error('Internal OTA Update Error', {error: `${e}`}) 91 + } 90 92 }, []) 91 93 92 94 React.useEffect(() => { 95 + // We use this setTimeout to allow Statsig to initialize before we check for an update 93 96 // For Testflight users, we can prompt the user to update immediately whenever there's an available update. This 94 97 // is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update 95 98 // immediately. 96 99 if (IS_TESTFLIGHT) { 97 100 onIsTestFlight() 98 101 return 99 - } else if (!isEnabled || __DEV__ || ranInitialCheck.current) { 100 - // Development client shouldn't check for updates at all, so we skip that here. 102 + } else if (!shouldReceiveUpdates || ranInitialCheck.current) { 101 103 return 102 104 } 103 105 104 106 setCheckTimeout() 105 107 ranInitialCheck.current = true 106 - }, [onIsTestFlight, setCheckTimeout]) 108 + }, [onIsTestFlight, setCheckTimeout, shouldReceiveUpdates]) 107 109 108 - // After the app has been minimized for 30 minutes, we want to either A. install an update if one has become available 110 + // After the app has been minimized for 15 minutes, we want to either A. install an update if one has become available 109 111 // or B check for an update again. 110 112 React.useEffect(() => { 111 113 if (!isEnabled) return
+1
src/lib/hooks/useOTAUpdates.web.ts
··· 1 + export function useOTAUpdates() {}
+1
src/lib/statsig/gates.ts
··· 5 5 | 'disable_poll_on_discover' 6 6 | 'new_profile_scroll_component' 7 7 | 'new_search' 8 + | 'receive_updates' 8 9 | 'show_follow_back_label' 9 10 | 'start_session_with_following' 10 11 | 'use_new_suggestions_endpoint'
+3
src/view/screens/Home.tsx
··· 14 14 import {useSession} from '#/state/session' 15 15 import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' 16 16 import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' 17 + import {useOTAUpdates} from 'lib/hooks/useOTAUpdates' 17 18 import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' 18 19 import {FeedPage} from 'view/com/feeds/FeedPage' 19 20 import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' ··· 51 52 preferences: UsePreferencesQueryResponse 52 53 pinnedFeedInfos: FeedSourceInfo[] 53 54 }) { 55 + useOTAUpdates() 56 + 54 57 const allFeeds = React.useMemo(() => { 55 58 const feeds: FeedDescriptor[] = [] 56 59 feeds.push('home')