mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at samuel/exp-cli 142 lines 4.2 kB view raw
1import React from 'react' 2import {Alert, AppState, AppStateStatus} from 'react-native' 3import {nativeBuildVersion} from 'expo-application' 4import { 5 checkForUpdateAsync, 6 fetchUpdateAsync, 7 isEnabled, 8 reloadAsync, 9 setExtraParamAsync, 10 useUpdates, 11} from 'expo-updates' 12 13import {IS_TESTFLIGHT} from '#/lib/app-info' 14import {logger} from '#/logger' 15import {isIOS} from '#/platform/detection' 16 17const MINIMUM_MINIMIZE_TIME = 15 * 60e3 18 19async function setExtraParams() { 20 await setExtraParamAsync( 21 isIOS ? 'ios-build-number' : 'android-build-number', 22 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 23 // This just ensures it gets passed as a string 24 `${nativeBuildVersion}`, 25 ) 26 await setExtraParamAsync( 27 'channel', 28 IS_TESTFLIGHT ? 'testflight' : 'production', 29 ) 30} 31 32export function useOTAUpdates() { 33 const shouldReceiveUpdates = isEnabled && !__DEV__ 34 35 const appState = React.useRef<AppStateStatus>('active') 36 const lastMinimize = React.useRef(0) 37 const ranInitialCheck = React.useRef(false) 38 const timeout = React.useRef<NodeJS.Timeout>() 39 const {isUpdatePending} = useUpdates() 40 41 const setCheckTimeout = React.useCallback(() => { 42 timeout.current = setTimeout(async () => { 43 try { 44 await setExtraParams() 45 46 logger.debug('Checking for update...') 47 const res = await checkForUpdateAsync() 48 49 if (res.isAvailable) { 50 logger.debug('Attempting to fetch update...') 51 await fetchUpdateAsync() 52 } else { 53 logger.debug('No update available.') 54 } 55 } catch (e) { 56 logger.error('OTA Update Error', {error: `${e}`}) 57 } 58 }, 10e3) 59 }, []) 60 61 const onIsTestFlight = React.useCallback(async () => { 62 try { 63 await setExtraParams() 64 65 const res = await checkForUpdateAsync() 66 if (res.isAvailable) { 67 await fetchUpdateAsync() 68 69 Alert.alert( 70 'Update Available', 71 'A new version of the app is available. Relaunch now?', 72 [ 73 { 74 text: 'No', 75 style: 'cancel', 76 }, 77 { 78 text: 'Relaunch', 79 style: 'default', 80 onPress: async () => { 81 await reloadAsync() 82 }, 83 }, 84 ], 85 ) 86 } 87 } catch (e: any) { 88 logger.error('Internal OTA Update Error', {error: `${e}`}) 89 } 90 }, []) 91 92 React.useEffect(() => { 93 // We use this setTimeout to allow Statsig to initialize before we check for an update 94 // For Testflight users, we can prompt the user to update immediately whenever there's an available update. This 95 // is suspect however with the Apple App Store guidelines, so we don't want to prompt production users to update 96 // immediately. 97 if (IS_TESTFLIGHT) { 98 onIsTestFlight() 99 return 100 } else if (!shouldReceiveUpdates || ranInitialCheck.current) { 101 return 102 } 103 104 setCheckTimeout() 105 ranInitialCheck.current = true 106 }, [onIsTestFlight, setCheckTimeout, shouldReceiveUpdates]) 107 108 // After the app has been minimized for 15 minutes, we want to either A. install an update if one has become available 109 // or B check for an update again. 110 React.useEffect(() => { 111 if (!isEnabled) return 112 113 const subscription = AppState.addEventListener( 114 'change', 115 async nextAppState => { 116 if ( 117 appState.current.match(/inactive|background/) && 118 nextAppState === 'active' 119 ) { 120 // If it's been 15 minutes since the last "minimize", we should feel comfortable updating the client since 121 // chances are that there isn't anything important going on in the current session. 122 if (lastMinimize.current <= Date.now() - MINIMUM_MINIMIZE_TIME) { 123 if (isUpdatePending) { 124 await reloadAsync() 125 } else { 126 setCheckTimeout() 127 } 128 } 129 } else { 130 lastMinimize.current = Date.now() 131 } 132 133 appState.current = nextAppState 134 }, 135 ) 136 137 return () => { 138 clearTimeout(timeout.current) 139 subscription.remove() 140 } 141 }, [isUpdatePending, setCheckTimeout]) 142}