@nevis-security/nevis-mobile-authentication-sdk-react
Version:
React Native plugin for Nevis Mobile Authentication SDK. Supports only mobile.
166 lines (154 loc) • 6.65 kB
text/typescript
/**
* Copyright © 2023 Nevis Security AG. All rights reserved.
*/
import { UserInteractionPlatformOperation } from '../../cache/operation/UserInteractionPlatformOperation';
import { PlatformOperationCache } from '../../cache/PlatformOperationCache';
import NevisMobileAuthenticationSdkReact from '../../MobileAuthenticationSdk';
import { OperationIdMessage } from '../../model/messages/out/OperationIdMessage';
/**
* An object that can be used to pause or resume listening for OS credentials (i.e. fingerprint, face
* recognition) and to cancel the whole operation while listening for credentials.
*
* **IMPORTANT** \
* The {@link OsAuthenticationListenHandler} class is Android specific.
*
* This is used with {@link Aaid.BIOMETRIC}, {@link Aaid.DEVICE_PASSCODE} and {@link Aaid.FINGERPRINT}
* authenticator attestation identifiers.
*
* @see
* - {@link BiometricUserVerificationHandler.listenForOsCredentials}
* - {@link DevicePasscodeUserVerificationHandler.listenForOsCredentials}
* - {@link FingerprintUserVerificationHandler.listenForOsCredentials}
*/
export abstract class OsAuthenticationListenHandler {
/**
* Cancels the authentication operation.
*
* This will result in the operation being canceled and an {@link OperationError} or
* an {@link OperationFidoError} with a {@link FidoErrorCodeType.UserCanceled} will be returned.
*/
abstract cancelAuthentication(): Promise<void>;
/**
* Pauses listening for OS credentials.
*
* If the application is listening for OS credentials, and it is brought to the background, then
* the operating system will cancel automatically listening for credentials and will send an error.
* This method must be invoked when the application is brought to the background before the OS
* cancels the authentication and the SDK sends an error.
*
* Invoking this method will have effect only if {@link cancelAuthentication} was not previously
* invoked.
*
* The method can be invoked from the [AppState](https://reactnative.dev/docs/appstate) event listener
* when the new state is `inactive` or `background` and the current state is `active`.
* Note that this approach does not work in some devices (in these devices the event listener is
* invoked after the OS cancels the authentication).
*
* For example:
* ```ts
* const appState = useRef(AppState.currentState);
* const [listenHandler, setListenHandler] = useState<OsAuthenticationListenHandler>();
*
* useCallback(() => {
* const onStateChange = async (nextAppState: AppStateStatus) => {
* if (appState.current === 'active' && nextAppState.match(/inactive|background/)) {
* if (listenHandler !== undefined) {
* await listenHandler.pauseListening()
* .then((newListenHandler) => {
* setListenHandler(newListenHandler);
* })
* .catch(ErrorHandler.handle.bind(null, OperationType.unknown));
* }
* }
*
* appState.current = nextAppState;
* };
*
* const subscription = AppState.addEventListener('change', onStateChange);
* return () => subscription.remove();
* }, [appState, listenHandler]);
* ```
*
* @returns the {@link OsAuthenticationListenHandler} to handle the new listening.
*/
abstract pauseListening(): Promise<OsAuthenticationListenHandler>;
/**
* Resumes listening for OS credentials.
*
* If the application is listening for OS credentials and it is brought to the background, then
* the operating system will cancel automatically listening for credentials.
* This method must be invoked when the application is brought to the foreground again to resume
* listening for credentials.
*
* Invoking this method will have effect only if {@link cancelAuthentication} was not previously
* invoked.
*
* The method is typically invoked from the [AppState](https://reactnative.dev/docs/appstate) event
* listener when the new state is `active` and the current state is `inactive` or `background`.
*
* For example:
* ```ts
* const appState = useRef(AppState.currentState);
* const [listenHandler, setListenHandler] = useState<OsAuthenticationListenHandler>();
*
* useCallback(() => {
* const onStateChange = async (nextAppState: AppStateStatus) => {
* if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
* if (listenHandler !== undefined) {
* await listenHandler.resumeListening()
* .then((newListenHandler) => {
* setListenHandler(newListenHandler);
* })
* .catch(ErrorHandler.handle.bind(null, OperationType.unknown));
* }
* }
*
* appState.current = nextAppState;
* };
*
* const subscription = AppState.addEventListener('change', onStateChange);
* return () => subscription.remove();
* }, [appState, listenHandler]);
* ```
*
* @returns the {@link OsAuthenticationListenHandler} to handle the new listening.
*/
abstract resumeListening(): Promise<OsAuthenticationListenHandler>;
}
export class OsAuthenticationListenHandlerImpl extends OsAuthenticationListenHandler {
private readonly _operationId: string;
constructor(operationId: string) {
super();
this._operationId = operationId;
}
async cancelAuthentication(): Promise<void> {
const message = new OperationIdMessage(this._operationId);
return NevisMobileAuthenticationSdkReact.cancelAuthentication(message);
}
async pauseListening(): Promise<OsAuthenticationListenHandler> {
const operation = PlatformOperationCache.getInstance().read(this._operationId);
if (
!(operation instanceof UserInteractionPlatformOperation) ||
operation.userVerificationHandler === undefined
) {
return this;
}
const message = new OperationIdMessage(this._operationId);
return NevisMobileAuthenticationSdkReact.pauseListening(message).then(() => {
return this;
});
}
async resumeListening(): Promise<OsAuthenticationListenHandler> {
const operation = PlatformOperationCache.getInstance().read(this._operationId);
if (
!(operation instanceof UserInteractionPlatformOperation) ||
operation.userVerificationHandler === undefined
) {
return this;
}
const message = new OperationIdMessage(this._operationId);
return NevisMobileAuthenticationSdkReact.resumeListening(message).then(() => {
return this;
});
}
}