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
zio/stable
Hailey 2024-04-12 14:51:53 -07:00 committed by GitHub
parent 1f587ea4b6
commit f91aa37c6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 49 additions and 40 deletions

View File

@ -42,8 +42,14 @@ module.exports = function (config) {
const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development' const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development'
const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight' const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight'
const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production'
const UPDATES_CHANNEL = IS_TESTFLIGHT ? 'testflight' : 'production' const UPDATES_CHANNEL = IS_TESTFLIGHT
? 'testflight'
: IS_PRODUCTION
? 'production'
: undefined
const UPDATES_ENABLED = !!UPDATES_CHANNEL
return { return {
expo: { expo: {
@ -126,14 +132,12 @@ module.exports = function (config) {
}, },
updates: { updates: {
url: 'https://updates.bsky.app/manifest', url: 'https://updates.bsky.app/manifest',
// TODO Eventually we want to enable this for all environments, but for now it will only be used for enabled: UPDATES_ENABLED,
// TestFlight builds
enabled: IS_TESTFLIGHT,
fallbackToCacheTimeout: 30000, fallbackToCacheTimeout: 30000,
codeSigningCertificate: IS_TESTFLIGHT codeSigningCertificate: UPDATES_ENABLED
? './code-signing/certificate.pem' ? './code-signing/certificate.pem'
: undefined, : undefined,
codeSigningMetadata: IS_TESTFLIGHT codeSigningMetadata: UPDATES_ENABLED
? { ? {
keyid: 'main', keyid: 'main',
alg: 'rsa-v1_5-sha256', alg: 'rsa-v1_5-sha256',

View File

@ -19,7 +19,6 @@ import {init as initPersistedState} from '#/state/persisted'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {useIntentHandler} from 'lib/hooks/useIntentHandler' import {useIntentHandler} from 'lib/hooks/useIntentHandler'
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
import {useNotificationsListener} from 'lib/notifications/notifications' import {useNotificationsListener} from 'lib/notifications/notifications'
import {QueryProvider} from 'lib/react-query' import {QueryProvider} from 'lib/react-query'
import {s} from 'lib/styles' import {s} from 'lib/styles'
@ -58,7 +57,6 @@ function InnerApp() {
const {_} = useLingui() const {_} = useLingui()
useIntentHandler() useIntentHandler()
useOTAUpdates()
// init // init
useEffect(() => { useEffect(() => {

View File

@ -12,6 +12,7 @@ import {
import {logger} from '#/logger' import {logger} from '#/logger'
import {IS_TESTFLIGHT} from 'lib/app-info' import {IS_TESTFLIGHT} from 'lib/app-info'
import {useGate} from 'lib/statsig/statsig'
import {isIOS} from 'platform/detection' import {isIOS} from 'platform/detection'
const MINIMUM_MINIMIZE_TIME = 15 * 60e3 const MINIMUM_MINIMIZE_TIME = 15 * 60e3
@ -30,6 +31,9 @@ async function setExtraParams() {
} }
export function useOTAUpdates() { export function useOTAUpdates() {
const shouldReceiveUpdates =
useGate('receive_updates') && isEnabled && !__DEV__
const appState = React.useRef<AppStateStatus>('active') const appState = React.useRef<AppStateStatus>('active')
const lastMinimize = React.useRef(0) const lastMinimize = React.useRef(0)
const ranInitialCheck = React.useRef(false) const ranInitialCheck = React.useRef(false)
@ -51,61 +55,59 @@ export function useOTAUpdates() {
logger.debug('No update available.') logger.debug('No update available.')
} }
} catch (e) { } catch (e) {
logger.warn('OTA Update Error', {error: `${e}`}) logger.error('OTA Update Error', {error: `${e}`})
} }
}, 10e3) }, 10e3)
}, []) }, [])
const onIsTestFlight = React.useCallback(() => { const onIsTestFlight = React.useCallback(async () => {
setTimeout(async () => { try {
try { await setExtraParams()
await setExtraParams()
const res = await checkForUpdateAsync() const res = await checkForUpdateAsync()
if (res.isAvailable) { if (res.isAvailable) {
await fetchUpdateAsync() await fetchUpdateAsync()
Alert.alert( Alert.alert(
'Update Available', 'Update Available',
'A new version of the app is available. Relaunch now?', 'A new version of the app is available. Relaunch now?',
[ [
{ {
text: 'No', text: 'No',
style: 'cancel', style: 'cancel',
},
{
text: 'Relaunch',
style: 'default',
onPress: async () => {
await reloadAsync()
}, },
{ },
text: 'Relaunch', ],
style: 'default', )
onPress: async () => {
await reloadAsync()
},
},
],
)
}
} catch (e: any) {
// No need to handle
} }
}, 3e3) } catch (e: any) {
logger.error('Internal OTA Update Error', {error: `${e}`})
}
}, []) }, [])
React.useEffect(() => { React.useEffect(() => {
// We use this setTimeout to allow Statsig to initialize before we check for an update
// For Testflight users, we can prompt the user to update immediately whenever there's an available update. This // For Testflight users, we can prompt the user to update immediately whenever there's an available update. This
// is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update // is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update
// immediately. // immediately.
if (IS_TESTFLIGHT) { if (IS_TESTFLIGHT) {
onIsTestFlight() onIsTestFlight()
return return
} else if (!isEnabled || __DEV__ || ranInitialCheck.current) { } else if (!shouldReceiveUpdates || ranInitialCheck.current) {
// Development client shouldn't check for updates at all, so we skip that here.
return return
} }
setCheckTimeout() setCheckTimeout()
ranInitialCheck.current = true ranInitialCheck.current = true
}, [onIsTestFlight, setCheckTimeout]) }, [onIsTestFlight, setCheckTimeout, shouldReceiveUpdates])
// After the app has been minimized for 30 minutes, we want to either A. install an update if one has become available // After the app has been minimized for 15 minutes, we want to either A. install an update if one has become available
// or B check for an update again. // or B check for an update again.
React.useEffect(() => { React.useEffect(() => {
if (!isEnabled) return if (!isEnabled) return

View File

@ -0,0 +1 @@
export function useOTAUpdates() {}

View File

@ -5,6 +5,7 @@ export type Gate =
| 'disable_poll_on_discover' | 'disable_poll_on_discover'
| 'new_profile_scroll_component' | 'new_profile_scroll_component'
| 'new_search' | 'new_search'
| 'receive_updates'
| 'show_follow_back_label' | 'show_follow_back_label'
| 'start_session_with_following' | 'start_session_with_following'
| 'use_new_suggestions_endpoint' | 'use_new_suggestions_endpoint'

View File

@ -14,6 +14,7 @@ import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
import {useOTAUpdates} from 'lib/hooks/useOTAUpdates'
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {FeedPage} from 'view/com/feeds/FeedPage' import {FeedPage} from 'view/com/feeds/FeedPage'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
@ -51,6 +52,8 @@ function HomeScreenReady({
preferences: UsePreferencesQueryResponse preferences: UsePreferencesQueryResponse
pinnedFeedInfos: FeedSourceInfo[] pinnedFeedInfos: FeedSourceInfo[]
}) { }) {
useOTAUpdates()
const allFeeds = React.useMemo(() => { const allFeeds = React.useMemo(() => {
const feeds: FeedDescriptor[] = [] const feeds: FeedDescriptor[] = []
feeds.push('home') feeds.push('home')