Enable updates for `production` behind `receive_updates` gate (#3496)
* add gate type * gate the updates * enable updates in `production` * web placeholder for `useOTAUpdates()` * update commentzio/stable
parent
1f587ea4b6
commit
f91aa37c6b
|
@ -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',
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export function useOTAUpdates() {}
|
|
@ -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'
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue