From ba4bb46c3fc3d670e565c69bdf71dbb4510b51f0 Mon Sep 17 00:00:00 2001 From: Ansh Date: Fri, 2 Jun 2023 13:27:59 -0700 Subject: [APP-107] OTA updates (#587) * add 1000ms fallbackToCacheTimeout * add listener via useOTAUpdate hook and show modal if update is available * finish expo-updates setup * setup useOTAUpdate hook * add 1000ms fallbackToCacheTimeout * add listener via useOTAUpdate hook and show modal if update is available * finish expo-updates setup * setup useOTAUpdate hook * add OTA updates * Update build.md * temporarily disable ota updates * refactor useOTAUpdate code --- src/lib/app-info.ts | 3 ++ src/lib/hooks/useOTAUpdate.ts | 74 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/lib/hooks/useOTAUpdate.ts (limited to 'src/lib') diff --git a/src/lib/app-info.ts b/src/lib/app-info.ts index a365e7e9f..3f026d3fe 100644 --- a/src/lib/app-info.ts +++ b/src/lib/app-info.ts @@ -1,2 +1,5 @@ import VersionNumber from 'react-native-version-number' +import * as Updates from 'expo-updates' +export const updateChannel = Updates.channel + export const appVersion = `${VersionNumber.appVersion} (${VersionNumber.buildVersion})` diff --git a/src/lib/hooks/useOTAUpdate.ts b/src/lib/hooks/useOTAUpdate.ts new file mode 100644 index 000000000..ae6035223 --- /dev/null +++ b/src/lib/hooks/useOTAUpdate.ts @@ -0,0 +1,74 @@ +import * as Updates from 'expo-updates' +import {useCallback, useEffect} from 'react' +import {AppState} from 'react-native' +import {useStores} from 'state/index' + +export function useOTAUpdate() { + const store = useStores() + + // HELPER FUNCTIONS + const showUpdatePopup = useCallback(() => { + store.shell.openModal({ + name: 'confirm', + title: 'Update Available', + message: + 'A new version of the app is available. Please update to continue using the app.', + onPressConfirm: async () => { + Updates.reloadAsync().catch(err => { + throw err + }) + }, + }) + }, [store.shell]) + const checkForUpdate = useCallback(async () => { + store.log.debug('useOTAUpdate: Checking for update...') + try { + // Check if new OTA update is available + const update = await Updates.checkForUpdateAsync() + // If updates aren't available stop the function execution + if (!update.isAvailable) { + return + } + // Otherwise fetch the update in the background, so even if the user rejects switching to latest version it will be done automatically on next relaunch. + await Updates.fetchUpdateAsync() + // show a popup modal + showUpdatePopup() + } catch (e) { + console.error('useOTAUpdate: Error while checking for update', e) + store.log.error('useOTAUpdate: Error while checking for update', e) + } + }, [showUpdatePopup, store.log]) + const updateEventListener = useCallback( + (event: Updates.UpdateEvent) => { + store.log.debug('useOTAUpdate: Listening for update...') + if (event.type === Updates.UpdateEventType.ERROR) { + throw new Error(event.message) + } else if (event.type === Updates.UpdateEventType.NO_UPDATE_AVAILABLE) { + // Handle no update available + // do nothing + } else if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) { + // Handle update available + // open modal, ask for user confirmation, and reload the app + showUpdatePopup() + } + }, + [showUpdatePopup, store.log], + ) + + useEffect(() => { + // ADD EVENT LISTENERS + const updateEventSubscription = Updates.addListener(updateEventListener) + const appStateSubscription = AppState.addEventListener('change', state => { + if (state === 'active' && !__DEV__) { + checkForUpdate() + } + }) + + // REMOVE EVENT LISTENERS (CLEANUP) + return () => { + updateEventSubscription.remove() + appStateSubscription.remove() + } + }, []) // eslint-disable-line react-hooks/exhaustive-deps + // disable exhaustive deps because we don't want to run this effect again +} -- cgit 1.4.1