feat: auto-activate biometrics prompt when mobile regains focus (#1891)
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
TransferPayload,
|
||||
UuidString,
|
||||
} from '@standardnotes/snjs'
|
||||
import { Alert, Linking, PermissionsAndroid, Platform, StatusBar } from 'react-native'
|
||||
import { Alert, AppState, AppStateStatus, Linking, PermissionsAndroid, Platform, StatusBar } from 'react-native'
|
||||
import FileViewer from 'react-native-file-viewer'
|
||||
import FingerprintScanner from 'react-native-fingerprint-scanner'
|
||||
import FlagSecure from 'react-native-flag-secure-android'
|
||||
@@ -610,4 +610,8 @@ export class MobileDevice implements MobileDeviceInterface {
|
||||
isUrlComponentUrl(url: string): boolean {
|
||||
return Array.from(this.componentUrls.values()).includes(url)
|
||||
}
|
||||
|
||||
async getAppState(): Promise<AppStateStatus> {
|
||||
return AppState.currentState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ export interface MobileDeviceInterface extends DeviceInterface {
|
||||
addComponentUrl(componentUuid: string, componentUrl: string): void
|
||||
removeComponentUrl(componentUuid: string): void
|
||||
isUrlComponentUrl(url: string): boolean
|
||||
getAppState(): Promise<'active' | 'background' | 'inactive' | 'unknown' | 'extension'>
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
ThemeManager,
|
||||
WebAlertService,
|
||||
} from '@standardnotes/ui-services'
|
||||
import { MobileWebReceiver } from '../NativeMobileWeb/MobileWebReceiver'
|
||||
import { MobileWebReceiver, NativeMobileEventListener } from '../NativeMobileWeb/MobileWebReceiver'
|
||||
import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
|
||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||
import { setViewportHeightWithFallback } from '@/setViewportHeightWithFallback'
|
||||
@@ -330,4 +330,12 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
openPurchaseFlow(): void {
|
||||
this.getViewControllerManager().purchaseFlowController.openPurchaseFlow()
|
||||
}
|
||||
|
||||
addNativeMobileEventListener = (listener: NativeMobileEventListener) => {
|
||||
if (!this.mobileWebReceiver) {
|
||||
return
|
||||
}
|
||||
|
||||
return this.mobileWebReceiver.addReactListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { ChallengePrompt, ChallengeValidation, ProtectionSessionDurations } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useEffect, useRef } from 'react'
|
||||
import {
|
||||
ChallengePrompt,
|
||||
ChallengeValidation,
|
||||
ProtectionSessionDurations,
|
||||
ReactNativeToWebEvent,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useEffect, useRef } from 'react'
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import DecoratedPasswordInput from '@/Components/Input/DecoratedPasswordInput'
|
||||
import { ChallengeModalValues } from './ChallengeModalValues'
|
||||
@@ -27,6 +32,40 @@ const ChallengeModalPrompt: FunctionComponent<Props> = ({
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const biometricsButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const activatePrompt = useCallback(async () => {
|
||||
if (prompt.validation === ChallengeValidation.Biometric) {
|
||||
if (application.isNativeMobileWeb()) {
|
||||
const appState = await application.mobileDevice().getAppState()
|
||||
|
||||
if (appState !== 'active') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
biometricsButtonRef.current?.click()
|
||||
} else {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [application, prompt.validation])
|
||||
|
||||
useEffect(() => {
|
||||
if (!application.isNativeMobileWeb()) {
|
||||
return
|
||||
}
|
||||
|
||||
const disposeListener = application.addNativeMobileEventListener((event: ReactNativeToWebEvent) => {
|
||||
if (event === ReactNativeToWebEvent.GainingFocus) {
|
||||
void activatePrompt()
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
if (disposeListener) {
|
||||
disposeListener()
|
||||
}
|
||||
}
|
||||
}, [activatePrompt, application])
|
||||
|
||||
useEffect(() => {
|
||||
const isNotFirstPrompt = index !== 0
|
||||
|
||||
@@ -34,12 +73,8 @@ const ChallengeModalPrompt: FunctionComponent<Props> = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (prompt.validation === ChallengeValidation.Biometric) {
|
||||
biometricsButtonRef.current?.click()
|
||||
} else {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [index, prompt.validation])
|
||||
void activatePrompt()
|
||||
}, [activatePrompt, index])
|
||||
|
||||
useEffect(() => {
|
||||
if (isInvalid) {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { ReactNativeToWebEvent, WebApplicationInterface } from '@standardnotes/snjs'
|
||||
|
||||
export type NativeMobileEventListener = (event: ReactNativeToWebEvent) => void
|
||||
|
||||
export class MobileWebReceiver {
|
||||
private listeners: Set<NativeMobileEventListener> = new Set()
|
||||
|
||||
constructor(private application: WebApplicationInterface) {
|
||||
this.listenForNativeMobileEvents()
|
||||
}
|
||||
@@ -39,6 +43,14 @@ export class MobileWebReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
addReactListener = (listener: NativeMobileEventListener) => {
|
||||
this.listeners.add(listener)
|
||||
|
||||
return () => {
|
||||
this.listeners.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
handleNativeEvent(event: ReactNativeToWebEvent) {
|
||||
switch (event) {
|
||||
case ReactNativeToWebEvent.EnteringBackground:
|
||||
@@ -60,5 +72,7 @@ export class MobileWebReceiver {
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
this.listeners.forEach((listener) => listener(event))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user