···42424343 const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development'
4444 const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight'
4545+ const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production'
45464646- const UPDATES_CHANNEL = IS_TESTFLIGHT ? 'testflight' : 'production'
4747+ const UPDATES_CHANNEL = IS_TESTFLIGHT
4848+ ? 'testflight'
4949+ : IS_PRODUCTION
5050+ ? 'production'
5151+ : undefined
5252+ const UPDATES_ENABLED = !!UPDATES_CHANNEL
47534854 return {
4955 expo: {
···126132 },
127133 updates: {
128134 url: 'https://updates.bsky.app/manifest',
129129- // TODO Eventually we want to enable this for all environments, but for now it will only be used for
130130- // TestFlight builds
131131- enabled: IS_TESTFLIGHT,
135135+ enabled: UPDATES_ENABLED,
132136 fallbackToCacheTimeout: 30000,
133133- codeSigningCertificate: IS_TESTFLIGHT
137137+ codeSigningCertificate: UPDATES_ENABLED
134138 ? './code-signing/certificate.pem'
135139 : undefined,
136136- codeSigningMetadata: IS_TESTFLIGHT
140140+ codeSigningMetadata: UPDATES_ENABLED
137141 ? {
138142 keyid: 'main',
139143 alg: 'rsa-v1_5-sha256',
-2
src/App.native.tsx
···1919import * as persisted from '#/state/persisted'
2020import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
2121import {useIntentHandler} from 'lib/hooks/useIntentHandler'
2222-import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
2322import {useNotificationsListener} from 'lib/notifications/notifications'
2423import {QueryProvider} from 'lib/react-query'
2524import {s} from 'lib/styles'
···5857 const {_} = useLingui()
59586059 useIntentHandler()
6161- useOTAUpdates()
62606361 // init
6462 useEffect(() => {
+34-32
src/lib/hooks/useOTAUpdates.ts
···12121313import {logger} from '#/logger'
1414import {IS_TESTFLIGHT} from 'lib/app-info'
1515+import {useGate} from 'lib/statsig/statsig'
1516import {isIOS} from 'platform/detection'
16171718const MINIMUM_MINIMIZE_TIME = 15 * 60e3
···3031}
31323233export function useOTAUpdates() {
3434+ const shouldReceiveUpdates =
3535+ useGate('receive_updates') && isEnabled && !__DEV__
3636+3337 const appState = React.useRef<AppStateStatus>('active')
3438 const lastMinimize = React.useRef(0)
3539 const ranInitialCheck = React.useRef(false)
···5155 logger.debug('No update available.')
5256 }
5357 } catch (e) {
5454- logger.warn('OTA Update Error', {error: `${e}`})
5858+ logger.error('OTA Update Error', {error: `${e}`})
5559 }
5660 }, 10e3)
5761 }, [])
58625959- const onIsTestFlight = React.useCallback(() => {
6060- setTimeout(async () => {
6161- try {
6262- await setExtraParams()
6363+ const onIsTestFlight = React.useCallback(async () => {
6464+ try {
6565+ await setExtraParams()
63666464- const res = await checkForUpdateAsync()
6565- if (res.isAvailable) {
6666- await fetchUpdateAsync()
6767+ const res = await checkForUpdateAsync()
6868+ if (res.isAvailable) {
6969+ await fetchUpdateAsync()
67706868- Alert.alert(
6969- 'Update Available',
7070- 'A new version of the app is available. Relaunch now?',
7171- [
7272- {
7373- text: 'No',
7474- style: 'cancel',
7575- },
7676- {
7777- text: 'Relaunch',
7878- style: 'default',
7979- onPress: async () => {
8080- await reloadAsync()
8181- },
7171+ Alert.alert(
7272+ 'Update Available',
7373+ 'A new version of the app is available. Relaunch now?',
7474+ [
7575+ {
7676+ text: 'No',
7777+ style: 'cancel',
7878+ },
7979+ {
8080+ text: 'Relaunch',
8181+ style: 'default',
8282+ onPress: async () => {
8383+ await reloadAsync()
8284 },
8383- ],
8484- )
8585- }
8686- } catch (e: any) {
8787- // No need to handle
8585+ },
8686+ ],
8787+ )
8888 }
8989- }, 3e3)
8989+ } catch (e: any) {
9090+ logger.error('Internal OTA Update Error', {error: `${e}`})
9191+ }
9092 }, [])
91939294 React.useEffect(() => {
9595+ // We use this setTimeout to allow Statsig to initialize before we check for an update
9396 // For Testflight users, we can prompt the user to update immediately whenever there's an available update. This
9497 // is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update
9598 // immediately.
9699 if (IS_TESTFLIGHT) {
97100 onIsTestFlight()
98101 return
9999- } else if (!isEnabled || __DEV__ || ranInitialCheck.current) {
100100- // Development client shouldn't check for updates at all, so we skip that here.
102102+ } else if (!shouldReceiveUpdates || ranInitialCheck.current) {
101103 return
102104 }
103105104106 setCheckTimeout()
105107 ranInitialCheck.current = true
106106- }, [onIsTestFlight, setCheckTimeout])
108108+ }, [onIsTestFlight, setCheckTimeout, shouldReceiveUpdates])
107109108108- // After the app has been minimized for 30 minutes, we want to either A. install an update if one has become available
110110+ // After the app has been minimized for 15 minutes, we want to either A. install an update if one has become available
109111 // or B check for an update again.
110112 React.useEffect(() => {
111113 if (!isEnabled) return