diff --git a/.changelog/1933.feature.md b/.changelog/1933.feature.md new file mode 100644 index 0000000000..91f0897cbd --- /dev/null +++ b/.changelog/1933.feature.md @@ -0,0 +1 @@ +Lock profile when user leaves app on Ionic platform diff --git a/package.json b/package.json index e13c4baeb1..71fa42ca13 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "extract-messages": "rm src/locales/en/translation.json && i18next-scanner --config=internals/extractMessages/i18next-scanner.config.js && node ./internals/scripts/normalize-translations.js ./src/locales/*/translation.json", "fix-grommet-icons-types": "node ./internals/scripts/fix-grommet-icons-types.js", "print-extension-dev-csp": "node ./internals/scripts/print-extension-dev-csp.js", - "android": "yarn build && yarn cap run android -l --external", - "ios": "yarn build && yarn cap run ios -l --external" + "android": "yarn build && yarn cap sync android && yarn cap run android -l --external", + "ios": "yarn build && yarn cap sync ios && yarn cap run ios -l --external" }, "browserslist": { "production": [ diff --git a/src/app/components/Ionic/IonicProvider.tsx b/src/app/components/Ionic/IonicProvider.tsx index c270d3ee00..7e7b538230 100644 --- a/src/app/components/Ionic/IonicProvider.tsx +++ b/src/app/components/Ionic/IonicProvider.tsx @@ -1,30 +1,13 @@ -import { createContext, FC, PropsWithChildren, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' +import { createContext, FC, PropsWithChildren } from 'react' import { Capacitor } from '@capacitor/core' -import { App } from '@capacitor/app' +import { useIonicBackButtonListener } from './hooks/useIonicBackButtonListener' +import { useIonicAppStateChangeListener } from './hooks/useIonicAppStateChangeListener' const IonicContext = createContext(undefined) const IonicContextProvider: FC = ({ children }) => { - const navigate = useNavigate() - - useEffect(() => { - /** - * The back button refers to the physical back button on an Android device and should not be confused - * with either the browser back button or ion-back-button. - */ - const backButtonListenerHandle = App.addListener('backButton', ({ canGoBack }) => { - if (!canGoBack) { - App.exitApp() - } else { - navigate(-1) - } - }) - - return () => { - backButtonListenerHandle.remove() - } - }, [navigate]) + useIonicBackButtonListener() + useIonicAppStateChangeListener() return {children} } diff --git a/src/app/components/Ionic/hooks/useIonicAppStateChangeListener.tsx b/src/app/components/Ionic/hooks/useIonicAppStateChangeListener.tsx new file mode 100644 index 0000000000..c8ff8f9e31 --- /dev/null +++ b/src/app/components/Ionic/hooks/useIonicAppStateChangeListener.tsx @@ -0,0 +1,29 @@ +import { useEffect, useRef } from 'react' +import { App } from '@capacitor/app' +import { deltaMsToLockProfile } from '../../../../ionicConfig' +import { persistActions } from '../../../state/persist' +import { useDispatch } from 'react-redux' + +export const useIonicAppStateChangeListener = () => { + const dispatch = useDispatch() + const lockTimestamp = useRef() + + useEffect(() => { + const appStateChangeListenerHandle = App.addListener('appStateChange', ({ isActive }) => { + const shouldLock = lockTimestamp.current && Date.now() - lockTimestamp.current > deltaMsToLockProfile + if (isActive && shouldLock) { + dispatch(persistActions.lockAsync()) + } else if (isActive && !shouldLock) { + lockTimestamp.current = undefined + } + + if (!isActive) { + lockTimestamp.current = Date.now() + } + }) + + return () => { + appStateChangeListenerHandle.then(pluginListenerHandle => pluginListenerHandle.remove()) + } + }, [dispatch]) +} diff --git a/src/app/components/Ionic/hooks/useIonicBackButtonListener.ts b/src/app/components/Ionic/hooks/useIonicBackButtonListener.ts new file mode 100644 index 0000000000..aa574fd96a --- /dev/null +++ b/src/app/components/Ionic/hooks/useIonicBackButtonListener.ts @@ -0,0 +1,25 @@ +import { useEffect } from 'react' +import { App } from '@capacitor/app' +import { useNavigate } from 'react-router-dom' + +export const useIonicBackButtonListener = () => { + const navigate = useNavigate() + + useEffect(() => { + /** + * The back button refers to the physical back button on an Android device and should not be confused + * with either the browser back button or ion-back-button. + */ + const backButtonListenerHandle = App.addListener('backButton', ({ canGoBack }) => { + if (!canGoBack) { + App.exitApp() + } else { + navigate(-1) + } + }) + + return () => { + backButtonListenerHandle.then(pluginListenerHandle => pluginListenerHandle.remove()) + } + }, [navigate]) +} diff --git a/src/ionicConfig.ts b/src/ionicConfig.ts new file mode 100644 index 0000000000..041dc7b44f --- /dev/null +++ b/src/ionicConfig.ts @@ -0,0 +1,6 @@ +/** + * Represents the duration in milliseconds to lock a profile. + * + * @type {number} + */ +export const deltaMsToLockProfile = 30 * 1000