Files
inji-wallet/machines/biometrics.ts
Swati Goel 142228b0db INJI-473) Code clean up and fixing metro errors/warnings (#947)
* feat(INJI-473) - Removed unused injiTourGuide action.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Removed unused logKey action.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Removed unused backendInfo api and state.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - simplify isPasscodeSet logic.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Move logState to commonUtil to remove cyclic dependency.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Delete unused code.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Refactor code to use util function for iOS or isAndroid.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Move Issuers_Key_Ref into utils.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Remove profile related resource from setting screen

Signed-off-by: Swati Goel <meet2swati@gmail.com>

* feat(INJI-473) - Remove unused code for locales.

Signed-off-by: Swati Goel <meet2swati@gmail.com>

---------

Signed-off-by: Swati Goel <meet2swati@gmail.com>
2023-10-20 12:50:39 +05:30

313 lines
8.0 KiB
TypeScript

import {createModel} from 'xstate/lib/model';
import * as LocalAuthentication from 'expo-local-authentication';
import {EventFrom, MetaObject, StateFrom} from 'xstate';
import {Platform} from 'react-native';
import {isAndroid} from '../shared/constants';
// ----- CREATE MODEL ---------------------------------------------------------
const model = createModel(
{
isAvailable: false,
authTypes: [],
isEnrolled: false,
status: null,
retry: false,
error: {},
},
{
events: {
SET_IS_AVAILABLE: (data: boolean) => ({data}),
SET_AUTH: (data: unknown[]) => ({data}),
SET_IS_ENROLLED: (data: boolean) => ({data}),
SET_STATUS: (data: boolean) => ({data}),
AUTHENTICATE: () => ({}),
RETRY_AUTHENTICATE: () => ({}),
},
},
);
// ----------------------------------------------------------------------------
// ----- CREATE MACHINE -------------------------------------------------------
export const biometricsMachine = model.createMachine(
{
predictableActionArguments: true,
preserveActionOrder: true,
id: 'biometrics',
context: model.initialContext,
initial: 'init',
states: {
// Initializing biometrics states
init: {
invoke: {
src: LocalAuthentication.hasHardwareAsync,
onError: 'failure.unavailable',
onDone: {
target: 'initAuthTypes',
actions: ['setIsAvailable'],
},
},
},
initAuthTypes: {
invoke: {
src: LocalAuthentication.supportedAuthenticationTypesAsync,
onError: 'failure',
onDone: {
target: 'initEnrolled',
actions: ['setAuthTypes'],
},
},
},
initEnrolled: {
invoke: {
src: LocalAuthentication.isEnrolledAsync,
onError: 'failure',
onDone: {
target: 'checking',
actions: ['setIsEnrolled'],
},
},
},
// Checks whether we need to proceed if its available otherwise it gets to failure
checking: {
always: [
{
target: 'available',
cond: 'checkIfAvailable',
},
{
target: 'failure.unavailable',
cond: 'checkIfUnavailable',
},
{
target: 'failure.unenrolled',
cond: 'checkIfUnenrolled',
},
],
},
// if available then wait for any event
available: {
on: {
AUTHENTICATE: 'authenticating',
},
},
// authenticating biometrics
authenticating: {
invoke: {
src: () => async () => {
if (isAndroid()) {
await LocalAuthentication.cancelAuthenticate();
}
const res = await LocalAuthentication.authenticateAsync({
promptMessage: 'Biometric Authentication',
// below can only works for IOS not android
// disableDeviceFallback: true,
// fallbackLabel: 'Invalid fingerprint attempts, Please try again.'
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return res.success;
},
onError: [
{
target: 'failure',
actions: ['sendFailedEndEvent'],
},
],
onDone: {
target: 'authentication',
actions: ['setStatus'],
},
},
on: {
AUTHENTICATE: 'authenticating',
},
},
reauthenticating: {
always: [
{
target: 'authenticating',
cond: 'checkIfAvailable',
},
{
target: 'failure.unenrolled',
},
],
},
// checks authentication status
authentication: {
always: [
{
target: 'success',
cond: 'isStatusSuccess',
},
{
target: 'failure.failed',
cond: 'isStatusFail',
},
],
},
success: {
on: {
SET_IS_AVAILABLE: {
target: '#biometrics.available',
},
AUTHENTICATE: 'authenticating',
},
},
failure: {
initial: 'error',
states: {
unavailable: {
meta: {
message: 'errors.unavailable',
},
},
unenrolled: {
meta: {
message: 'errors.unenrolled',
},
on: {
RETRY_AUTHENTICATE: {
//actions: assign({retry: (context, event) => true}),
actions: ['setRetry'],
target: [
'#biometrics.initEnrolled',
'#biometrics.reauthenticating',
],
},
},
},
failed: {
after: {
// after 1 seconds, transition to available
1000: '#biometrics.available',
},
meta: {
message: 'errors.failed',
},
},
error: {
meta: {
message: 'errors.generic',
},
exit: 'resetError',
},
},
on: {
AUTHENTICATE: 'authenticating',
},
},
},
},
{
actions: {
setIsAvailable: model.assign({
isAvailable: (_, event: SetIsAvailableEvent) => event.data,
}),
setAuthTypes: model.assign({
authTypes: (_, event: SetAuthTypesEvent) => event.data,
}),
setIsEnrolled: model.assign({
isEnrolled: (_, event: SetIsEnrolledEvent) => event.data,
}),
setStatus: model.assign({
status: (_, event: SetStatusEvent) => event.data,
}),
setRetry: model.assign({
retry: () => true,
}),
sendFailedEndEvent: model.assign({
error: (_context, event) => {
const res = JSON.parse((event.data as Error).message);
return {res: res, stacktrace: event};
},
}),
resetError: model.assign({
error: () => null,
}),
},
guards: {
isStatusSuccess: ctx => ctx.status,
isStatusFail: ctx => !ctx.status,
checkIfAvailable: ctx => ctx.isAvailable && ctx.isEnrolled,
checkIfUnavailable: ctx => !ctx.isAvailable,
checkIfUnenrolled: ctx => !ctx.isEnrolled,
},
},
);
// ----------------------------------------------------------------------------
// ----- TYPES ----------------------------------------------------------------
type SetStatusEvent = EventFrom<typeof model, 'SET_STATUS'>;
type SetIsAvailableEvent = EventFrom<typeof model, 'SET_IS_AVAILABLE'>;
type SetAuthTypesEvent = EventFrom<typeof model, 'SET_AUTH'>;
type SetIsEnrolledEvent = EventFrom<typeof model, 'SET_IS_ENROLLED'>;
type State = StateFrom<typeof biometricsMachine>;
// ----- OTHER EXPORTS --------------------------------------------------------
export const BiometricsEvents = model.events;
export function selectFailMessage(state: State) {
return Object.values(state.meta)
.map((m: MetaObject) => m.message)
.join(', ');
}
export function selectIsEnabled(state: State) {
return state.matches('available') || state.matches({failure: 'unenrolled'});
}
export function selectIsAvailable(state: State) {
return state.matches('available');
}
export function selectIsUnvailable(state: State) {
return state.matches({failure: 'unavailable'});
}
export function selectIsUnenrolled(state: State) {
return state.matches({failure: 'unenrolled'});
}
export function selectIsSuccess(state: State) {
return state.matches('success');
}
export function selectError(state: State) {
return state.matches({failure: 'error'}) ? selectFailMessage(state) : null;
}
export function selectUnenrolledNotice(state: State) {
return state.matches({failure: 'unenrolled'}) && state.context.retry
? selectFailMessage(state)
: null;
}
export function selectErrorResponse(state: State) {
return state.context.error;
}