Skip to content

Commit

Permalink
feat(Inji-402): track login & onboarding flow events in telemetry (#918)
Browse files Browse the repository at this point in the history
* feat(INJI-402): track different flows of login and onboarding features in telemetry

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): add missing events in the login flow

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): track hardware keystore not supported error in telemetry

Signed-off-by: PuBHARGAVI <[email protected]>

* fix(INJI-402): send biometric event only when biometrics are enrolled in device

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): send error event for every 5 passcode mismatch attempts

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): add telemetry events to track passcode screen flow when biometrics change

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): add subtype to impression and interact event

Signed-off-by: PuBHARGAVI <[email protected]>

* fix(INJI-402): remove additionalParamters in error event

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): remove extra impression events and fix the biometrics reenabling flow

Signed-off-by: PuBHARGAVI <[email protected]>

* refactor(INJI-402): change getData method name to getStartEventData in telemetry utils

Signed-off-by: PuBHARGAVI <[email protected]>

* feat(INJI-402): don't show biometric failed alert message when user cancels the flow

Signed-off-by: PuBHARGAVI <[email protected]>

* refactor(INJI-402): change telemetry events name

Signed-off-by: PuBHARGAVI <[email protected]>

* fix(INJI-402): add missing functions in telemetry utils

Signed-off-by: PuBHARGAVI <[email protected]>

* refactor(INJI-402): add impression event in passcode screen and change Main to Home in event

Signed-off-by: PuBHARGAVI <[email protected]>

---------

Signed-off-by: PuBHARGAVI <[email protected]>
  • Loading branch information
PuBHARGAVI authored Oct 13, 2023
1 parent 8c11cb1 commit 853cda3
Show file tree
Hide file tree
Showing 16 changed files with 360 additions and 90 deletions.
22 changes: 15 additions & 7 deletions components/Passcode.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import React from 'react';
import { Modal as RNModal } from 'react-native';
import { Icon } from 'react-native-elements';
import { PasscodeVerify } from '../components/PasscodeVerify';
import { Column, Text } from '../components/ui';
import { Theme } from '../components/ui/styleUtils';
import React, {useEffect} from 'react';
import {Modal as RNModal} from 'react-native';
import {Icon} from 'react-native-elements';
import {PasscodeVerify} from '../components/PasscodeVerify';
import {Column, Text} from '../components/ui';
import {Theme} from '../components/ui/styleUtils';
import {
getImpressionEventData,
sendImpressionEvent,
} from '../shared/telemetry/TelemetryUtils';

export const Passcode: React.FC<PasscodeProps> = props => {
useEffect(() => {
sendImpressionEvent(getImpressionEventData('App Login', 'Passcode'));
}, []);

export const Passcode: React.FC<PasscodeProps> = (props) => {
return (
<RNModal
animationType="slide"
Expand Down
17 changes: 12 additions & 5 deletions components/PasscodeVerify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const PasscodeVerify: React.FC<PasscodeVerifyProps> = props => {
useEffect(() => {
if (isVerified) {
props.onSuccess();
setIsVerified(false);
}
}, [isVerified]);

Expand All @@ -21,11 +22,17 @@ export const PasscodeVerify: React.FC<PasscodeVerifyProps> = props => {
);

async function verify(value: string) {
const hashedPasscode = await hashData(value, props.salt, argon2iConfig);
if (props.passcode === hashedPasscode) {
setIsVerified(true);
} else {
props.onError(t('passcodeMismatchError'));
try {
const hashedPasscode = await hashData(value, props.salt, argon2iConfig);
if (props.passcode === hashedPasscode) {
setIsVerified(true);
} else {
if (props.onError) {
props.onError(t('passcodeMismatchError'));
}
}
} catch (error) {
console.log('error:', error);
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion machines/QrLoginMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export const qrLoginMachine =
},
},
success: {
entry: [() => sendEndEvent(getEndEventData('QR login'))],
entry: [() => sendEndEvent(getEndEventData('QR login', 'SUCCESS'))],
on: {
CONFIRM: {
target: 'done',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@ export const ExistingMosipVCItemMachine =
'setWalletBindingErrorEmpty',
'sendWalletBindingSuccess',
'logWalletBindingSuccess',
() => sendEndEvent(getEndEventData('VC activation')),
() =>
sendEndEvent(getEndEventData('VC activation', 'SUCCESS')),
],
target: '#vc-item.kebabPopUp',
},
Expand Down Expand Up @@ -746,7 +747,7 @@ export const ExistingMosipVCItemMachine =
'setWalletBindingErrorEmpty',
'setWalletBindingSuccess',
'logWalletBindingSuccess',
() => sendEndEvent(getEndEventData('VC activation')),
() => sendEndEvent(getEndEventData('VC activation', 'SUCCESS')),
],
target: 'idle',
},
Expand Down Expand Up @@ -1003,7 +1004,11 @@ export const ExistingMosipVCItemMachine =
};
case 'GET_VC_RESPONSE':
case 'CREDENTIAL_DOWNLOADED':
return {...context, ...event.vc, vcMetadata: context.vcMetadata};
return {
...context,
...event.vc,
vcMetadata: context.vcMetadata,
};
}
}),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,17 @@ export interface Typegen0 {
| 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]'
| 'done.invoke.vc-item.updatingPrivateKey:invocation[0]';
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
refreshMyVcs: 'STORE_RESPONSE';
removeTamperedVcItem: 'TAMPERED_VC';
removeVcFromInProgressDownloads: 'STORE_RESPONSE';
removeVcItem: 'CONFIRM';
removeVcMetaDataFromStorage: 'STORE_ERROR';
removeVcMetaDataFromVcMachine: 'DISMISS';
removedVc: 'STORE_RESPONSE';
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'DISMISS' | 'xstate.init';
resetWalletBindingSuccess: 'DISMISS';
revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
sendTamperedVc: 'TAMPERED_VC';
sendVcUpdated: 'PIN_CARD';
sendWalletBindingSuccess:
| 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]'
Expand Down
44 changes: 36 additions & 8 deletions machines/biometrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const model = createModel(
isEnrolled: false,
status: null,
retry: false,
error: {},
},
{
events: {
Expand All @@ -22,7 +23,7 @@ const model = createModel(
AUTHENTICATE: () => ({}),
RETRY_AUTHENTICATE: () => ({}),
},
}
},
);
// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -108,9 +109,20 @@ export const biometricsMachine = model.createMachine(
// disableDeviceFallback: true,
// fallbackLabel: 'Invalid fingerprint attempts, Please try again.'
});

if (res.error) {
throw new Error(JSON.stringify(res));
}

return res.success;
},
onError: 'failure',
onError: [
{
target: 'failure',
actions: ['sendFailedEndEvent'],
},
],

onDone: {
target: 'authentication',
actions: ['setStatus'],
Expand Down Expand Up @@ -192,6 +204,7 @@ export const biometricsMachine = model.createMachine(
meta: {
message: 'errors.generic',
},
exit: 'resetError',
},
},
on: {
Expand Down Expand Up @@ -222,15 +235,26 @@ export const biometricsMachine = model.createMachine(
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,
isStatusSuccess: ctx => ctx.status,
isStatusFail: ctx => !ctx.status,
checkIfAvailable: ctx => ctx.isAvailable && ctx.isEnrolled,
checkIfUnavailable: ctx => !ctx.isAvailable,
checkIfUnenrolled: ctx => !ctx.isEnrolled,
},
}
},
);

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -281,3 +305,7 @@ export function selectUnenrolledNotice(state: State) {
? selectFailMessage(state)
: null;
}

export function selectErrorResponse(state: State) {
return state.context.error;
}
2 changes: 1 addition & 1 deletion machines/bleShare/scan/scanMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ export const scanMachine =
accepted: {
entry: [
'logShared',
() => sendEndEvent(getEndEventData('VC share')),
() => sendEndEvent(getEndEventData('VC share', 'SUCCESS')),
],
on: {
DISMISS: {
Expand Down
15 changes: 14 additions & 1 deletion screens/AuthScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@ import {Button, Column, Text} from '../components/ui';
import {Theme} from '../components/ui/styleUtils';
import {RootRouteProps} from '../routes';
import {useAuthScreen} from './AuthScreenController';
import {
getStartEventData,
getInteractEventData,
sendInteractEvent,
sendStartEvent,
} from '../shared/telemetry/TelemetryUtils';

export const AuthScreen: React.FC<RootRouteProps> = props => {
const {t} = useTranslation('AuthScreen');
const controller = useAuthScreen(props);

const handleUsePasscodeButtonPress = () => {
sendStartEvent(getStartEventData('App Onboarding'));
sendInteractEvent(
getInteractEventData('App Onboarding', 'TOUCH', 'Use Passcode Button'),
);
controller.usePasscode();
};
return (
<Column
fill
Expand Down Expand Up @@ -54,7 +67,7 @@ export const AuthScreen: React.FC<RootRouteProps> = props => {
testID="usePasscode"
type="clear"
title={t('usePasscode')}
onPress={controller.usePasscode}
onPress={() => handleUsePasscodeButtonPress()}
/>
</Column>
</Column>
Expand Down
67 changes: 48 additions & 19 deletions screens/AuthScreenController.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import { useMachine, useSelector } from '@xstate/react';
import { useContext, useEffect, useState } from 'react';
import {useMachine, useSelector} from '@xstate/react';
import {useContext, useEffect, useState} from 'react';
import * as LocalAuthentication from 'expo-local-authentication';

import {
AuthEvents,
selectSettingUp,
selectAuthorized,
} from '../machines/auth';
import { RootRouteProps } from '../routes';
import { GlobalContext } from '../shared/GlobalContext';
import {AuthEvents, selectSettingUp, selectAuthorized} from '../machines/auth';
import {RootRouteProps} from '../routes';
import {GlobalContext} from '../shared/GlobalContext';
import {
biometricsMachine,
selectError,
selectIsEnabled,
selectIsSuccess,
selectIsUnvailable,
selectUnenrolledNotice,
selectErrorResponse,
} from '../machines/biometrics';
import { SettingsEvents } from '../machines/settings';
import { useTranslation } from 'react-i18next';
import {SettingsEvents} from '../machines/settings';
import {useTranslation} from 'react-i18next';
import {
sendStartEvent,
sendImpressionEvent,
sendInteractEvent,
getStartEventData,
getInteractEventData,
getImpressionEventData,
getEndEventData,
sendEndEvent,
} from '../shared/telemetry/TelemetryUtils';

export function useAuthScreen(props: RootRouteProps) {
const { appService } = useContext(GlobalContext);
const {appService} = useContext(GlobalContext);
const authService = appService.children.get('auth');
const settingsService = appService.children.get('settings');

Expand All @@ -38,12 +45,13 @@ export function useAuthScreen(props: RootRouteProps) {
const isSuccessBio = useSelector(bioService, selectIsSuccess);
const errorMsgBio = useSelector(bioService, selectError);
const unEnrolledNoticeBio = useSelector(bioService, selectUnenrolledNotice);
const errorResponse = useSelector(bioService, selectErrorResponse);

const usePasscode = () => {
props.navigation.navigate('Passcode', { setup: isSettingUp });
props.navigation.navigate('Passcode', {setup: isSettingUp});
};

const { t } = useTranslation('AuthScreen');
const {t} = useTranslation('AuthScreen');

const fetchIsAvailable = async () => {
const result = await LocalAuthentication.hasHardwareAsync();
Expand All @@ -53,10 +61,12 @@ export function useAuthScreen(props: RootRouteProps) {

useEffect(() => {
if (isAuthorized) {
sendEndEvent(getEndEventData('App Onboarding', 'SUCCESS'));
props.navigation.reset({
index: 0,
routes: [{ name: 'Main' }],
routes: [{name: 'Main'}],
});
sendImpressionEvent(getImpressionEventData('App Onboarding', 'Home'));
return;
}

Expand All @@ -69,28 +79,47 @@ export function useAuthScreen(props: RootRouteProps) {

// handle biometric failure unknown error
} else if (errorMsgBio) {
sendEndEvent(
getEndEventData('App Onboarding', 'FAILURE', {
errorId: errorResponse.res.error,
errorMessage: errorResponse.res.warning,
stackTrace: errorResponse.stacktrace,
}),
);
// show alert message whenever biometric state gets failure
setHasAlertMsg(t(errorMsgBio));
if (errorResponse.res.error !== 'user_cancel') {
setHasAlertMsg(t(errorMsgBio));
}

// handle any unenrolled notice
} else if (unEnrolledNoticeBio) {
setHasAlertMsg(t(unEnrolledNoticeBio));

// we dont need to see this page to user once biometric is unavailable on its device
} else if (isUnavailableBio) {
sendStartEvent(getStartEventData('App Onboarding'));
usePasscode();
}
}, [isSuccessBio, isUnavailableBio, errorMsgBio, unEnrolledNoticeBio]);

const useBiometrics = async () => {
const isBiometricsEnrolled = await LocalAuthentication.isEnrolledAsync();
if (isBiometricsEnrolled) {
if (biometricState.matches({ failure: 'unenrolled' })) {
biometricSend({ type: 'RETRY_AUTHENTICATE' });
sendStartEvent(getStartEventData('App Onboarding'));
sendInteractEvent(
getInteractEventData(
'App Onboarding',
'TOUCH',
'Use Biometrics Button',
),
);

if (biometricState.matches({failure: 'unenrolled'})) {
biometricSend({type: 'RETRY_AUTHENTICATE'});
return;
}

biometricSend({ type: 'AUTHENTICATE' });
biometricSend({type: 'AUTHENTICATE'});
} else {
setHasAlertMsg(t('errors.unenrolled'));
}
Expand Down
Loading

0 comments on commit 853cda3

Please sign in to comment.