diff --git a/.talismanrc b/.talismanrc index bef86b580e..a275b73294 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,8 +1,8 @@ fileignoreconfig: - filename: package.json - checksum: e0b34f1a1efbdf07b40a3e9e9ecd8884dd7d6835f45c57850e30988d2c6371a2 + checksum: fdd5905228a1afbfb004c710fd6c61adf073a12840200327c0592b76bea5e7e3 - filename: package-lock.json - checksum: fc26d97edecbacd0b7507032c6fc1acfcea9f01f5dbe66a2a0341506e2f0daca + checksum: 4515c3d663d9435eab0f56b75480c93c5209aa59befb2b71f2b3fe7c9051d80a - filename: components/PasscodeVerify.tsx checksum: 14654c0f038979fcd0d260170a45894a072f81e0767ca9a0e66935d33b5cc703 - filename: i18n.ts @@ -26,6 +26,13 @@ fileignoreconfig: - filename: screens/WelcomeScreenController.ts checksum: 71917c8c543a4a5b7ab61df259b785d233a80718123b0c8edf6ec81e1b3a81e0 - filename: shared/telemetry/TelemetryUtils.js + checksum: ffe9aac2dcc590b98b0d588885c088eff189504ade653a77f74b67312bfd27ad +- filename: shared/fileStorage.ts + checksum: 07cb337dc1d5b0f0eef56270ac4f4f589260ee5e490183c024cf98a2aeafb139 +- filename: shared/storage.ts + checksum: c8d874aa373bdf526bf59192139822f56915e702ef673bac4e0d7549b0fea3d0 +- filename: screens/Issuers/IssuersScreen.tsx + checksum: bc12c43ccc27ac04e5763fa6a6ed3cee63e4362ba5666c160b5e53269de924ab checksum: 9a61cd59a3718adf1f14faf3024fec66a3295ef373878a878a28e5cb1287afaa - filename: ios/Podfile.lock checksum: cc123c3e1f04d41b394ceb16843b15c08bac3ba619ae853ff322717739761a85 @@ -51,8 +58,6 @@ fileignoreconfig: checksum: 237a2640b7db70770d65da67c79f2929581e32f1162517e50b8d37e409f3387d - filename: shared/cryptoutil/cryptoUtil.ts checksum: b785ff3f01ab9530119072c4d38195048bfeee6155c54ea7dd031559acb722f3 -- filename: package.json - checksum: 1a9d02d94424c1266503d58038cae2318ab71261ab50cf930c5f98e07c0f3ccd - filename: machines/store.typegen.ts checksum: 6d22bc5c77398316b943c512c208ce0846a9fff674c1ccac79e07f21962acd5f - filename: machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts diff --git a/components/KebabPopUpController.tsx b/components/KebabPopUpController.tsx index 8c5d0b24f5..d96f5509a9 100644 --- a/components/KebabPopUpController.tsx +++ b/components/KebabPopUpController.tsx @@ -1,8 +1,7 @@ import {useSelector} from '@xstate/react'; import {ActorRefFrom} from 'xstate'; import { - ExistingMosipVCItemEvents, - ExistingMosipVCItemMachine, + selectBindingAuthFailedError, selectEmptyWalletBindingId, selectIsPinned, selectKebabPopUp, @@ -14,22 +13,14 @@ import { selectShowActivities, selectShowWalletBindingError, selectWalletBindingError, +} from '../machines/VCItemMachine/commonSelectors'; +import { + ExistingMosipVCItemEvents, + ExistingMosipVCItemMachine, } from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine'; - import { EsignetMosipVCItemEvents, EsignetMosipVCItemMachine, - selectEmptyWalletBindingId as esignetSelectEmptyWalletBindingId, - selectIsPinned as esignetSelectIsPinned, - selectKebabPopUp as esignetSelectKebabPopUp, - selectKebabPopUpAcceptingBindingOtp as esignetSelectKebabPopUpAcceptingBindingOtp, - selectKebabPopUpBindingWarning as esignetSelectKebabPopUpBindingWarning, - selectKebabPopUpWalletBindingInProgress as esignetSelectKebabPopUpWalletBindingInProgress, - selectOtpError as esignetSelectOtpError, - selectRemoveWalletWarning as esignetSelectRemoveWalletWarning, - selectShowActivities as esignetSelectShowActivities, - selectShowWalletBindingError as esignetSelectShowWalletBindingError, - selectWalletBindingError as esignetSelectWalletBindingError, } from '../machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine'; import {selectActivities} from '../machines/activityLog'; import {GlobalContext} from '../shared/GlobalContext'; @@ -56,55 +47,31 @@ export function useKebabPopUp(props) { const SHOW_ACTIVITY = () => service.send(vcEvents.SHOW_ACTIVITY()); const INPUT_OTP = (otp: string) => service.send(vcEvents.INPUT_OTP(otp)); const RESEND_OTP = () => service.send(vcEvents.RESEND_OTP()); - let isPinned = useSelector(service, selectIsPinned); - let isBindingWarning = useSelector(service, selectKebabPopUpBindingWarning); - let isRemoveWalletWarning = useSelector(service, selectRemoveWalletWarning); - let isAcceptingOtpInput = useSelector( + const isPinned = useSelector(service, selectIsPinned); + const isBindingWarning = useSelector(service, selectKebabPopUpBindingWarning); + const isRemoveWalletWarning = useSelector(service, selectRemoveWalletWarning); + const isAcceptingOtpInput = useSelector( service, selectKebabPopUpAcceptingBindingOtp, ); - let isWalletBindingError = useSelector(service, selectShowWalletBindingError); - let otpError = useSelector(service, selectOtpError); - let walletBindingError = useSelector(service, selectWalletBindingError); - let WalletBindingInProgress = useSelector( + const isWalletBindingError = useSelector( + service, + selectShowWalletBindingError, + ); + const otpError = useSelector(service, selectOtpError); + const walletBindingError = useSelector(service, selectWalletBindingError); + const bindingAuthFailedError = useSelector( + service, + selectBindingAuthFailedError, + ); + const WalletBindingInProgress = useSelector( service, selectKebabPopUpWalletBindingInProgress, ); - let emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId); - let isKebabPopUp = useSelector(service, selectKebabPopUp); - let isShowActivities = useSelector(service, selectShowActivities); + const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId); + const isKebabPopUp = useSelector(service, selectKebabPopUp); + const isShowActivities = useSelector(service, selectShowActivities); - if (props.vcMetadata.isFromOpenId4VCI()) { - isPinned = useSelector(service, esignetSelectIsPinned); - isBindingWarning = useSelector( - service, - esignetSelectKebabPopUpBindingWarning, - ); - isRemoveWalletWarning = useSelector( - service, - esignetSelectRemoveWalletWarning, - ); - isAcceptingOtpInput = useSelector( - service, - esignetSelectKebabPopUpAcceptingBindingOtp, - ); - isWalletBindingError = useSelector( - service, - esignetSelectShowWalletBindingError, - ); - otpError = useSelector(service, esignetSelectOtpError); - walletBindingError = useSelector(service, esignetSelectWalletBindingError); - WalletBindingInProgress = useSelector( - service, - esignetSelectKebabPopUpWalletBindingInProgress, - ); - emptyWalletBindingId = useSelector( - service, - esignetSelectEmptyWalletBindingId, - ); - isKebabPopUp = useSelector(service, esignetSelectKebabPopUp); - isShowActivities = useSelector(service, esignetSelectShowActivities); - } const {appService} = useContext(GlobalContext); const activityLogService = appService.children.get('activityLog'); @@ -124,6 +91,7 @@ export function useKebabPopUp(props) { isAcceptingOtpInput, isWalletBindingError, walletBindingError, + bindingAuthFailedError, otpError, WalletBindingInProgress, emptyWalletBindingId, diff --git a/components/Passcode.tsx b/components/Passcode.tsx index 1768999e81..cd137a473b 100644 --- a/components/Passcode.tsx +++ b/components/Passcode.tsx @@ -5,13 +5,19 @@ import {PasscodeVerify} from '../components/PasscodeVerify'; import {Column, Text} from '../components/ui'; import {Theme} from '../components/ui/styleUtils'; import { + TelemetryConstants, getImpressionEventData, sendImpressionEvent, } from '../shared/telemetry/TelemetryUtils'; export const Passcode: React.FC = props => { useEffect(() => { - sendImpressionEvent(getImpressionEventData('App Login', 'Passcode')); + sendImpressionEvent( + getImpressionEventData( + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.Screens.passcode, + ), + ); }, []); return ( diff --git a/components/VC/MosipVCItem/MosipVCItem.tsx b/components/VC/MosipVCItem/MosipVCItem.tsx index d6dcf72d07..b5e09727ce 100644 --- a/components/VC/MosipVCItem/MosipVCItem.tsx +++ b/components/VC/MosipVCItem/MosipVCItem.tsx @@ -75,9 +75,14 @@ export const MosipVCItem: React.FC< emptyWalletBindingId={emptyWalletBindingId} showOnlyBindedVc={props.showOnlyBindedVc} /> - + + + - + { size={Theme.ICON_MID_SIZE} type="material-community" containerStyle={{ - marginStart: 1, - marginEnd: 1, + marginEnd: 5, bottom: 1, - marginRight: -2, }} /> ); @@ -29,7 +27,10 @@ const WalletVerifiedIcon: React.FC = () => { name="verified-user" color={Theme.Colors.VerifiedIcon} size={Theme.ICON_MID_SIZE} - containerStyle={{marginStart: 10, bottom: 1, marginLeft: 10}} + containerStyle={{ + marginEnd: 5, + bottom: 1, + }} /> ); }; @@ -39,29 +40,18 @@ const WalletUnverifiedActivationDetails: React.FC< > = props => { const {t} = useTranslation('VcDetails'); return ( - - - {props.verifiableCredential && } - - + + {props.verifiableCredential && } + ); }; @@ -71,29 +61,20 @@ const WalletVerifiedActivationDetails: React.FC< > = props => { const {t} = useTranslation('WalletBinding'); return ( - - - - - + + + ); }; @@ -102,7 +83,7 @@ export const MosipVCItemActivationStatus: React.FC< ExistingMosipVCItemActivationStatusProps > = props => { return ( - + {props.emptyWalletBindingId ? ( service.send(ExistingMosipVCItemEvents.DISMISS()); let KEBAB_POPUP = () => service.send(ExistingMosipVCItemEvents.KEBAB_POPUP()); const isSavingFailedInIdle = useSelector(service, selectIsSavingFailedInIdle); const storeErrorTranslationPath = 'errors.savingFailed'; - let generatedOn = useSelector(service, selectGeneratedOn); + const generatedOn = useSelector(service, selectGeneratedOn); if (props.vcMetadata.isFromOpenId4VCI()) { - context = useSelector(service, esignetSelectContext); - isKebabPopUp = useSelector(service, esignetSelectKebabPopUp); - generatedOn = useSelector(service, esignetSelectGeneratedOn); - emptyWalletBindingId = useSelector( - service, - esignetSelectEmptyWalletBindingId, - ); DISMISS = () => service.send(EsignetMosipVCItemEvents.DISMISS()); KEBAB_POPUP = () => service.send(EsignetMosipVCItemEvents.KEBAB_POPUP()); - verifiableCredential = useSelector( - service, - esignetSelectVerifiableCredentials, - ); } return { service, diff --git a/components/VidItem.tsx b/components/VidItem.tsx index 463be171f4..a0f98badb1 100644 --- a/components/VidItem.tsx +++ b/components/VidItem.tsx @@ -5,9 +5,11 @@ import {CheckBox} from 'react-native-elements'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import {ActorRefFrom} from 'xstate'; import { - createExistingMosipVCItemMachine, selectVerifiableCredential, selectGeneratedOn, +} from '../machines/VCItemMachine/commonSelectors'; +import { + createExistingMosipVCItemMachine, selectId, ExistingMosipVCItemMachine, } from '../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine'; diff --git a/components/ui/themes/DefaultTheme.ts b/components/ui/themes/DefaultTheme.ts index 3181657915..1cf7f2d6e9 100644 --- a/components/ui/themes/DefaultTheme.ts +++ b/components/ui/themes/DefaultTheme.ts @@ -132,17 +132,27 @@ export const DefaultTheme = { statusLabel: { color: Colors.Gray30, fontSize: 12, + flexWrap: 'wrap', + flexShrink: 1, }, activationTab: { - justifyContent: 'space-evenly', + display: 'flex', alignItems: 'center', - marginRight: 20, - marginStart: 10, + overflow: 'hidden', + borderBottomLeftRadius: 10, + borderBottomRightRadius: 10, }, kebabIcon: { + flex: 3, + height: '100%', + }, + kebabPressableContainer: { + display: 'flex', + flexDirection: 'row', + width: '100%', + height: '100%', justifyContent: 'center', alignItems: 'center', - flex: 1, }, verifiedIconContainer: { marginRight: 3, @@ -220,11 +230,26 @@ export const DefaultTheme = { }, verticalLine: { width: 1, - height: 30, backgroundColor: Colors.Grey, marginVertical: 8, - marginLeft: -25, - marginRight: 12, + }, + verticalLineWrapper: { + display: 'flex', + flex: 0.1, + height: '100%', + justifyContent: 'center', + }, + vcActivationStatusContainer: { + display: 'flex', + flex: 7, + alignItems: 'center', + width: '100%', + height: '100%', + padding: 5, + }, + vcActivationDetailsWrapper: { + display: 'flex', + alignItems: 'flex-start', }, closeCardBgContainer: { borderRadius: 10, diff --git a/components/ui/themes/PurpleTheme.ts b/components/ui/themes/PurpleTheme.ts index 73efd68cbf..c3592394a0 100644 --- a/components/ui/themes/PurpleTheme.ts +++ b/components/ui/themes/PurpleTheme.ts @@ -134,17 +134,28 @@ export const PurpleTheme = { statusLabel: { color: Colors.Gray30, fontSize: 12, + flexWrap: 'wrap', + flexShrink: 1, }, activationTab: { - justifyContent: 'space-evenly', + justifyContent: 'space-between', + display: 'flex', alignItems: 'center', - marginRight: 20, - marginStart: 10, + overflow: 'hidden', + borderBottomLeftRadius: 10, + borderBottomRightRadius: 10, }, kebabIcon: { + flex: 3, + height: '100%', + }, + kebabPressableContainer: { + display: 'flex', + flexDirection: 'row', + width: '100%', + height: '100%', justifyContent: 'center', alignItems: 'center', - flex: 1, }, verifiedIconContainer: { marginRight: 3, @@ -222,11 +233,26 @@ export const PurpleTheme = { }, verticalLine: { width: 1, - height: 30, backgroundColor: Colors.Grey, marginVertical: 8, - marginLeft: -25, - marginRight: 12, + }, + verticalLineWrapper: { + display: 'flex', + flex: 0.1, + height: '100%', + justifyContent: 'center', + }, + vcActivationStatusContainer: { + display: 'flex', + flex: 7, + alignItems: 'center', + width: '100%', + height: '100%', + padding: 5, + }, + vcActivationDetailsWrapper: { + display: 'flex', + alignItems: 'flex-start', }, closeCardBgContainer: { borderRadius: 10, diff --git a/ios/Inji.xcodeproj/project.pbxproj b/ios/Inji.xcodeproj/project.pbxproj index f67e4d5971..d9bcc4cd82 100644 --- a/ios/Inji.xcodeproj/project.pbxproj +++ b/ios/Inji.xcodeproj/project.pbxproj @@ -557,4 +557,4 @@ /* End XCConfigurationList section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} +} \ No newline at end of file diff --git a/machines/QrLoginMachine.ts b/machines/QrLoginMachine.ts index 5202ac9195..4c9cf32c5c 100644 --- a/machines/QrLoginMachine.ts +++ b/machines/QrLoginMachine.ts @@ -23,6 +23,7 @@ import { import i18n from '../i18n'; import {parseMetadatas, VCMetadata} from '../shared/VCMetadata'; import { + TelemetryConstants, getEndEventData, sendEndEvent, } from '../shared/telemetry/TelemetryUtils'; @@ -230,7 +231,15 @@ export const qrLoginMachine = }, }, success: { - entry: [() => sendEndEvent(getEndEventData('QR login', 'SUCCESS'))], + entry: [ + () => + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.qrLogin, + TelemetryConstants.EndEventStatus.success, + ), + ), + ], on: { CONFIRM: { target: 'done', diff --git a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts index dc0eb6e048..85652011be 100644 --- a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts +++ b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts @@ -23,6 +23,15 @@ import {ActivityLogEvents} from '../../../machines/activityLog'; import {request} from '../../../shared/request'; import SecureKeystore from 'react-native-secure-keystore'; import {VerifiableCredential} from './vc'; +import { + getEndEventData, + getInteractEventData, + getStartEventData, + sendEndEvent, + sendInteractEvent, + sendStartEvent, + TelemetryConstants, +} from '../../../shared/telemetry/TelemetryUtils'; import {API_URLS} from '../../../shared/api'; const model = createModel( @@ -33,10 +42,8 @@ const model = createModel( verifiableCredential: null as VerifiableCredential, isPinned: false, hashedId: '', - publicKey: '', privateKey: '', - otp: '', otpError: '', idError: '', @@ -45,6 +52,8 @@ const model = createModel( walletBindingResponse: null as WalletBindingResponse, walletBindingError: '', walletBindingSuccess: false, + isMachineInKebabPopupState: false, + bindingAuthFailedMessage: '' as string, }, { events: { @@ -132,6 +141,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( showBindingWarning: { on: { CONFIRM: { + actions: 'sendActivationStartEvent', target: 'requestingBindingOtp', }, CANCEL: { @@ -172,7 +182,11 @@ export const EsignetMosipVCItemMachine = model.createMachine( }, DISMISS: { target: 'idle', - actions: ['clearOtp', 'clearTransactionId'], + actions: [ + 'sendActivationFailedEndEvent', + 'clearOtp', + 'clearTransactionId', + ], }, RESEND_OTP: { target: '.resendOTP', @@ -228,6 +242,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( 'storeContext', 'updateVc', 'setWalletBindingErrorEmpty', + 'sendActivationSuccessEvent', 'logWalletBindingSuccess', ], }, @@ -258,6 +273,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( 'setWalletBindingErrorEmpty', 'setWalletBindingSuccess', 'logWalletBindingSuccess', + 'sendActivationSuccessEvent', ], target: 'idle', }, @@ -295,8 +311,16 @@ export const EsignetMosipVCItemMachine = model.createMachine( }, }, kebabPopUp: { + entry: assign({ + isMachineInKebabPopupState: () => { + return true; + }, + }), on: { DISMISS: { + actions: assign({ + isMachineInKebabPopupState: () => false, + }), target: 'idle', }, ADD_WALLET_BINDING_ID: { @@ -320,6 +344,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( showBindingWarning: { on: { CONFIRM: { + actions: 'sendActivationStartEvent', target: '#vc-item-openid4vci.kebabPopUp.requestingBindingOtp', }, CANCEL: { @@ -362,7 +387,11 @@ export const EsignetMosipVCItemMachine = model.createMachine( }, DISMISS: { target: '#vc-item-openid4vci.kebabPopUp', - actions: ['clearOtp', 'clearTransactionId'], + actions: [ + 'sendActivationFailedEndEvent', + 'clearOtp', + 'clearTransactionId', + ], }, RESEND_OTP: { target: '.resendOTP', @@ -422,6 +451,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( 'updateVc', 'setWalletBindingErrorEmpty', 'sendWalletBindingSuccess', + 'sendActivationSuccessEvent', 'logWalletBindingSuccess', ], }, @@ -452,6 +482,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( 'updateVc', 'setWalletBindingErrorEmpty', 'sendWalletBindingSuccess', + 'sendActivationSuccessEvent', 'logWalletBindingSuccess', ], target: '#vc-item-openid4vci.kebabPopUp', @@ -603,14 +634,22 @@ export const EsignetMosipVCItemMachine = model.createMachine( ), setWalletBindingError: assign({ - walletBindingError: (context, event) => + walletBindingError: () => i18n.t(`errors.genericError`, { ns: 'common', }), + bindingAuthFailedMessage: (_context, event) => { + const error = JSON.parse(JSON.stringify(event.data)).name; + if (error) { + return error; + } + return ''; + }, }), setWalletBindingErrorEmpty: assign({ walletBindingError: () => '', + bindingAuthFailedMessage: () => '', }), setWalletBindingSuccess: assign({ @@ -631,6 +670,50 @@ export const EsignetMosipVCItemMachine = model.createMachine( to: context => context.serviceRefs.vc, }, ), + + sendActivationStartEvent: context => { + sendStartEvent( + getStartEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + ), + ); + sendInteractEvent( + getInteractEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.InteractEventSubtype.click, + 'Activate Button', + ), + ); + }, + + sendActivationFailedEndEvent: context => + sendEndEvent( + getEndEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.failure, + { + errorId: TelemetryConstants.ErrorId.userCancel, + errorMessage: TelemetryConstants.ErrorMessage.activationCancelled, + }, + ), + ), + + sendActivationSuccessEvent: context => + sendEndEvent( + getEndEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.success, + ), + ), + setPublicKey: assign({ publicKey: (context, event) => { if (!isHardwareKeystoreExists) { @@ -841,84 +924,3 @@ export const createEsignetMosipVCItemMachine = ( vcMetadata, }); }; - -type State = StateFrom; - -export function selectVerifiableCredentials(state: State) { - return state.context.verifiableCredential; -} - -export function selectKebabPopUp(state: State) { - return state.matches('kebabPopUp'); -} - -export function selectContext(state: State) { - return state.context; -} - -export function selectGeneratedOn(state: State) { - return state.context.generatedOn; -} - -export function selectWalletBindingSuccess(state: State) { - return state.context.walletBindingSuccess; -} - -export function selectEmptyWalletBindingId(state: State) { - var val = state.context.walletBindingResponse - ? state.context.walletBindingResponse.walletBindingId - : undefined; - return val == undefined || val == null || val.length <= 0 ? true : false; -} - -export function selectWalletBindingError(state: State) { - return state.context.walletBindingError; -} - -export function selectKebabPopUpAcceptingBindingOtp(state: State) { - return state.matches('kebabPopUp.acceptingBindingOtp'); -} - -export function selectKebabPopUpShowWalletBindingError(state: State) { - return state.matches('kebabPopUp.showingWalletBindingError'); -} - -export function selectKebabPopUpWalletBindingInProgress(state: State) { - return state.matches('kebabPopUp.requestingBindingOtp') || - state.matches('kebabPopUp.addingWalletBindingId') || - state.matches('kebabPopUp.addKeyPair') || - state.matches('kebabPopUp.updatingPrivateKey') - ? true - : false; -} - -export function selectKebabPopUpBindingWarning(state: State) { - return state.matches('kebabPopUp.showBindingWarning'); -} - -export function selectRemoveWalletWarning(state: State) { - return state.matches('kebabPopUp.removeWallet'); -} - -export function selectIsPinned(state: State) { - return state.context.isPinned; -} - -export function selectOtpError(state: State) { - return state.context.otpError; -} - -export function selectShowActivities(state: State) { - return state.matches('kebabPopUp.showActivities'); -} - -export function selectShowWalletBindingError(state: State) { - return ( - state.matches('showingWalletBindingError') || - state.matches('kebabPopUp.showingWalletBindingError') - ); -} - -export function selectLogoUrl(state: State) { - return state.context.logoUrl; -} diff --git a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts index dd13500fca..a68bfeb04b 100644 --- a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts +++ b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts @@ -124,6 +124,13 @@ export interface Typegen0 { requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH'; requestVcContext: 'DISMISS' | 'xstate.init'; resetWalletBindingSuccess: 'DISMISS'; + sendActivationFailedEndEvent: 'DISMISS'; + sendActivationStartEvent: 'CONFIRM'; + sendActivationSuccessEvent: + | 'done.invoke.vc-item-openid4vci.addingWalletBindingId:invocation[0]' + | 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]' + | 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]' + | 'done.invoke.vc-item-openid4vci.updatingPrivateKey:invocation[0]'; sendVcUpdated: 'STORE_RESPONSE'; sendWalletBindingSuccess: | 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]' diff --git a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts index 2eb81e0c5b..e2d0db98b5 100644 --- a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts +++ b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts @@ -34,6 +34,9 @@ import { getEndEventData, getStartEventData, sendEndEvent, + TelemetryConstants, + sendInteractEvent, + getInteractEventData, sendStartEvent, } from '../../../shared/telemetry/TelemetryUtils'; import {API_URLS} from '../../../shared/api'; @@ -45,7 +48,7 @@ const model = createModel( idType: '' as VcIdType, vcMetadata: {} as VCMetadata, myVcs: [] as string[], - generatedOn: null as Date, + generatedOn: new Date() as Date, credential: null as DecodedCredential, verifiableCredential: null as VerifiableCredential, storeVerifiableCredential: null as VerifiableCredential, @@ -67,6 +70,8 @@ const model = createModel( walletBindingSuccess: false, publicKey: '', privateKey: '', + isMachineInKebabPopupState: false, + bindingAuthFailedMessage: '' as string, }, { events: { @@ -281,8 +286,12 @@ export const ExistingMosipVCItemMachine = }, }, kebabPopUp: { + entry: assign({isMachineInKebabPopupState: () => true}), on: { DISMISS: { + actions: assign({ + isMachineInKebabPopupState: () => false, + }), target: 'idle', }, ADD_WALLET_BINDING_ID: { @@ -306,9 +315,7 @@ export const ExistingMosipVCItemMachine = showBindingWarning: { on: { CONFIRM: { - actions: [ - () => sendStartEvent(getStartEventData('VC activation')), - ], + actions: 'sendActivationStartEvent', target: '#vc-item.kebabPopUp.requestingBindingOtp', }, CANCEL: { @@ -350,7 +357,11 @@ export const ExistingMosipVCItemMachine = }, DISMISS: { target: '#vc-item.kebabPopUp', - actions: ['clearOtp', 'clearTransactionId'], + actions: [ + 'sendActivationFailedEndEvent', + 'clearOtp', + 'clearTransactionId', + ], }, }, }, @@ -390,6 +401,7 @@ export const ExistingMosipVCItemMachine = 'updateVc', 'setWalletBindingErrorEmpty', 'sendWalletBindingSuccess', + 'sendActivationSuccessEvent', 'logWalletBindingSuccess', ], }, @@ -423,8 +435,7 @@ export const ExistingMosipVCItemMachine = 'setWalletBindingErrorEmpty', 'sendWalletBindingSuccess', 'logWalletBindingSuccess', - () => - sendEndEvent(getEndEventData('VC activation', 'SUCCESS')), + 'sendActivationSuccessEvent', ], target: '#vc-item.kebabPopUp', }, @@ -641,7 +652,7 @@ export const ExistingMosipVCItemMachine = showBindingWarning: { on: { CONFIRM: { - actions: () => sendStartEvent(getStartEventData('VC activation')), + actions: 'sendActivationStartEvent', target: 'requestingBindingOtp', }, CANCEL: { @@ -682,7 +693,11 @@ export const ExistingMosipVCItemMachine = }, DISMISS: { target: 'idle', - actions: ['clearOtp', 'clearTransactionId'], + actions: [ + 'sendActivationFailedEndEvent', + 'clearOtp', + 'clearTransactionId', + ], }, }, }, @@ -721,6 +736,7 @@ export const ExistingMosipVCItemMachine = 'storeContext', 'updateVc', 'setWalletBindingErrorEmpty', + 'sendActivationSuccessEvent', 'logWalletBindingSuccess', ], }, @@ -750,8 +766,15 @@ export const ExistingMosipVCItemMachine = 'updateVc', 'setWalletBindingErrorEmpty', 'setWalletBindingSuccess', + 'sendActivationSuccessEvent', 'logWalletBindingSuccess', - () => sendEndEvent(getEndEventData('VC activation', 'SUCCESS')), + () => + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.success, + ), + ), ], target: 'idle', }, @@ -825,14 +848,24 @@ export const ExistingMosipVCItemMachine = ), setWalletBindingError: assign({ - walletBindingError: () => - i18n.t(`errors.genericError`, { + walletBindingError: () => { + const errorMessage = i18n.t(`errors.genericError`, { ns: 'common', - }), + }); + return errorMessage; + }, + bindingAuthFailedMessage: (_context, event) => { + const error = JSON.parse(JSON.stringify(event.data)).name; + if (error) { + return error; + } + return ''; + }, }), setWalletBindingErrorEmpty: assign({ walletBindingError: () => '', + bindingAuthFailedMessage: () => '', }), setWalletBindingSuccess: assign({ @@ -853,6 +886,51 @@ export const ExistingMosipVCItemMachine = to: context => context.serviceRefs.vc, }, ), + + sendActivationStartEvent: context => { + sendStartEvent( + getStartEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + ), + ); + sendInteractEvent( + getInteractEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.InteractEventSubtype.click, + 'Activate Button', + ), + ); + }, + + sendActivationFailedEndEvent: context => + sendEndEvent( + getEndEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.failure, + { + errorId: TelemetryConstants.ErrorId.userCancel, + errorMessage: + TelemetryConstants.ErrorMessage.activationCancelled, + }, + ), + ), + + sendActivationSuccessEvent: context => + sendEndEvent( + getEndEventData( + context.isMachineInKebabPopupState + ? TelemetryConstants.FlowType.vcActivationFromKebab + : TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.success, + ), + ), + setPublicKey: assign({ publicKey: (context, event) => { if (!isHardwareKeystoreExists) { @@ -1044,7 +1122,10 @@ export const ExistingMosipVCItemMachine = }, ), sendTelemetryEvents: () => { - sendEndEvent({type: 'VC Download', status: 'SUCCESS'}); + sendEndEvent({ + type: TelemetryConstants.FlowType.vcDownload, + status: TelemetryConstants.EndEventStatus.success, + }); }, logWalletBindingSuccess: send( @@ -1455,10 +1536,6 @@ export function selectVc(state: State) { return data; } -export function selectGeneratedOn(state: State) { - return new Date(state.context.generatedOn); -} - export function selectId(state: State) { return state.context.vcMetadata.id; } @@ -1471,25 +1548,10 @@ export function selectCredential(state: State) { return state.context.credential; } -export function selectVerifiableCredential(state: State) { - return state.context.verifiableCredential; -} - -export function selectContext(state: State) { - return state.context; -} - export function selectIsOtpError(state: State) { return state.context.otpError; } -export function selectOtpError(state: State) { - return state.context.otpError; -} -export function selectIsPinned(state: State) { - return state.context.vcMetadata.isPinned; -} - export function selectIsLockingVc(state: State) { return state.matches('lockingVc'); } @@ -1510,17 +1572,6 @@ export function selectIsAcceptingRevokeInput(state: State) { return state.matches('acceptingRevokeInput'); } -export function selectEmptyWalletBindingId(state: State) { - var val = state.context.walletBindingResponse - ? state.context.walletBindingResponse.walletBindingId - : undefined; - return val == undefined || val == null || val.length <= 0 ? true : false; -} - -export function selectWalletBindingError(state: State) { - return state.context.walletBindingError; -} - export function selectRequestBindingOtp(state: State) { return state.matches('requestingBindingOtp'); } @@ -1529,17 +1580,6 @@ export function selectAcceptingBindingOtp(state: State) { return state.matches('acceptingBindingOtp'); } -export function selectShowWalletBindingError(state: State) { - return ( - state.matches('showingWalletBindingError') || - state.matches('kebabPopUp.showingWalletBindingError') - ); -} - -export function selectWalletBindingSuccess(state: State) { - return state.context.walletBindingSuccess; -} - export function selectWalletBindingInProgress(state: State) { return state.matches('requestingBindingOtp') || state.matches('addingWalletBindingId') || @@ -1552,43 +1592,11 @@ export function selectWalletBindingInProgress(state: State) { export function selectBindingWarning(state: State) { return state.matches('showBindingWarning'); } -export function selectKebabPopUp(state: State) { - return state.matches('kebabPopUp'); -} export function selectKebabPopUpRequestBindingOtp(state: State) { return state.matches('kebabPopUp.requestingBindingOtp'); } -export function selectKebabPopUpAcceptingBindingOtp(state: State) { - return state.matches('kebabPopUp.acceptingBindingOtp'); -} - -export function selectKebabPopUpShowWalletBindingError(state: State) { - return state.matches('kebabPopUp.showingWalletBindingError'); -} - -export function selectKebabPopUpWalletBindingInProgress(state: State) { - return state.matches('kebabPopUp.requestingBindingOtp') || - state.matches('kebabPopUp.addingWalletBindingId') || - state.matches('kebabPopUp.addKeyPair') || - state.matches('kebabPopUp.updatingPrivateKey') - ? true - : false; -} - -export function selectKebabPopUpBindingWarning(state: State) { - return state.matches('kebabPopUp.showBindingWarning'); -} - -export function selectRemoveWalletWarning(state: State) { - return state.matches('kebabPopUp.removeWallet'); -} - -export function selectShowActivities(state: State) { - return state.matches('kebabPopUp.showActivities'); -} - export function selectIsSavingFailedInIdle(state: State) { return state.matches('checkingServerData.savingFailed.idle'); } diff --git a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts index f4c21d6383..df05eec2ad 100644 --- a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts +++ b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts @@ -218,6 +218,13 @@ export interface Typegen0 { requestVcContext: 'DISMISS' | 'xstate.init'; resetWalletBindingSuccess: 'DISMISS'; revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]'; + sendActivationFailedEndEvent: 'DISMISS'; + sendActivationStartEvent: 'CONFIRM'; + sendActivationSuccessEvent: + | 'done.invoke.vc-item.addingWalletBindingId:invocation[0]' + | 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]' + | 'done.invoke.vc-item.kebabPopUp.updatingPrivateKey:invocation[0]' + | 'done.invoke.vc-item.updatingPrivateKey:invocation[0]'; sendDownloadLimitExpire: 'error.platform.vc-item.checkingServerData.verifyingDownloadLimitExpiry:invocation[0]'; sendTamperedVc: 'TAMPERED_VC'; sendTelemetryEvents: 'STORE_RESPONSE'; diff --git a/machines/VCItemMachine/commonSelectors.ts b/machines/VCItemMachine/commonSelectors.ts new file mode 100644 index 0000000000..f6324455a1 --- /dev/null +++ b/machines/VCItemMachine/commonSelectors.ts @@ -0,0 +1,86 @@ +import {StateFrom} from 'xstate'; +import {EsignetMosipVCItemMachine} from './EsignetMosipVCItem/EsignetMosipVCItemMachine'; +import {ExistingMosipVCItemMachine} from './ExistingMosipVCItem/ExistingMosipVCItemMachine'; + +type State = StateFrom< + typeof ExistingMosipVCItemMachine & typeof EsignetMosipVCItemMachine +>; + +export function selectVerifiableCredential(state: State) { + return state.context.verifiableCredential; +} + +export function selectKebabPopUp(state: State) { + return state.matches('kebabPopUp'); +} + +export function selectContext(state: State) { + return state.context; +} + +export function selectGeneratedOn(state: State) { + return state.context.generatedOn; +} + +export function selectWalletBindingSuccess(state: State) { + return state.context.walletBindingSuccess; +} + +export function selectEmptyWalletBindingId(state: State) { + var val = state.context.walletBindingResponse + ? state.context.walletBindingResponse.walletBindingId + : undefined; + return val == undefined || val == null || val.length <= 0 ? true : false; +} + +export function selectWalletBindingError(state: State) { + return state.context.walletBindingError; +} + +export function selectBindingAuthFailedError(state: State) { + return state.context.bindingAuthFailedMessage; +} + +export function selectKebabPopUpAcceptingBindingOtp(state: State) { + return state.matches('kebabPopUp.acceptingBindingOtp'); +} + +export function selectKebabPopUpShowWalletBindingError(state: State) { + return state.matches('kebabPopUp.showingWalletBindingError'); +} + +export function selectKebabPopUpWalletBindingInProgress(state: State) { + return state.matches('kebabPopUp.requestingBindingOtp') || + state.matches('kebabPopUp.addingWalletBindingId') || + state.matches('kebabPopUp.addKeyPair') || + state.matches('kebabPopUp.updatingPrivateKey') + ? true + : false; +} + +export function selectKebabPopUpBindingWarning(state: State) { + return state.matches('kebabPopUp.showBindingWarning'); +} + +export function selectRemoveWalletWarning(state: State) { + return state.matches('kebabPopUp.removeWallet'); +} + +export function selectIsPinned(state: State) { + return state.context.vcMetadata.isPinned; +} + +export function selectOtpError(state: State) { + return state.context.otpError; +} + +export function selectShowActivities(state: State) { + return state.matches('kebabPopUp.showActivities'); +} + +export function selectShowWalletBindingError(state: State) { + return ( + state.matches('showingWalletBindingError') || + state.matches('kebabPopUp.showingWalletBindingError') + ); +} diff --git a/machines/auth.typegen.ts b/machines/auth.typegen.ts index bbb32a95ef..71a4e6dc39 100644 --- a/machines/auth.typegen.ts +++ b/machines/auth.typegen.ts @@ -1,47 +1,64 @@ +// This file was automatically generated. Edits will be overwritten - // This file was automatically generated. Edits will be overwritten - - export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - "": { type: "" }; -"done.invoke.auth.authorized:invocation[0]": { type: "done.invoke.auth.authorized:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; -"done.invoke.auth.introSlider:invocation[0]": { type: "done.invoke.auth.introSlider:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; -"xstate.init": { type: "xstate.init" }; - }; - invokeSrcNameMap: { - "downloadFaceSdkModel": "done.invoke.auth.authorized:invocation[0]"; -"generatePasscodeSalt": "done.invoke.auth.introSlider:invocation[0]"; - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - "requestStoredContext": "xstate.init"; -"setBiometrics": "SETUP_BIOMETRICS"; -"setContext": "STORE_RESPONSE"; -"setLanguage": "SETUP_BIOMETRICS" | "SETUP_PASSCODE"; -"setPasscode": "SETUP_PASSCODE"; -"setPasscodeSalt": "done.invoke.auth.introSlider:invocation[0]"; -"storeContext": "SETUP_BIOMETRICS" | "SETUP_PASSCODE" | "STORE_RESPONSE" | "done.invoke.auth.authorized:invocation[0]" | "done.invoke.auth.introSlider:invocation[0]"; - }; - eventsCausingDelays: { - - }; - eventsCausingGuards: { - "hasBiometricSet": ""; -"hasData": "STORE_RESPONSE"; -"hasLanguageset": ""; -"hasPasscodeSet": ""; - }; - eventsCausingServices: { - "downloadFaceSdkModel": "LOGIN" | "SETUP_PASSCODE"; -"generatePasscodeSalt": "SELECT"; - }; - matchesStates: "authorized" | "checkingAuth" | "init" | "introSlider" | "languagesetup" | "savingDefaults" | "settingUp" | "unauthorized"; - tags: never; - } - \ No newline at end of file +export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + '': {type: ''}; + 'done.invoke.auth.authorized:invocation[0]': { + type: 'done.invoke.auth.authorized:invocation[0]'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + 'done.invoke.auth.introSlider:invocation[0]': { + type: 'done.invoke.auth.introSlider:invocation[0]'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + 'xstate.init': {type: 'xstate.init'}; + }; + invokeSrcNameMap: { + downloadFaceSdkModel: 'done.invoke.auth.authorized:invocation[0]'; + generatePasscodeSalt: 'done.invoke.auth.introSlider:invocation[0]'; + }; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + requestStoredContext: 'xstate.init'; + setBiometrics: 'SETUP_BIOMETRICS'; + setContext: 'STORE_RESPONSE'; + setLanguage: 'SETUP_BIOMETRICS' | 'SETUP_PASSCODE'; + setPasscode: 'SETUP_PASSCODE'; + setPasscodeSalt: 'done.invoke.auth.introSlider:invocation[0]'; + storeContext: + | 'SETUP_BIOMETRICS' + | 'SETUP_PASSCODE' + | 'STORE_RESPONSE' + | 'done.invoke.auth.authorized:invocation[0]' + | 'done.invoke.auth.introSlider:invocation[0]'; + }; + eventsCausingDelays: {}; + eventsCausingGuards: { + hasBiometricSet: ''; + hasData: 'STORE_RESPONSE'; + hasLanguageset: ''; + hasPasscodeSet: ''; + }; + eventsCausingServices: { + downloadFaceSdkModel: 'LOGIN' | 'SETUP_PASSCODE'; + generatePasscodeSalt: 'SELECT'; + }; + matchesStates: + | 'authorized' + | 'checkingAuth' + | 'init' + | 'introSlider' + | 'languagesetup' + | 'savingDefaults' + | 'settingUp' + | 'unauthorized'; + tags: never; +} diff --git a/machines/bleShare/scan/scanMachine.ts b/machines/bleShare/scan/scanMachine.ts index ffe291e2f3..6e1a39a4aa 100644 --- a/machines/bleShare/scan/scanMachine.ts +++ b/machines/bleShare/scan/scanMachine.ts @@ -44,6 +44,7 @@ import { getEndEventData, sendStartEvent, sendEndEvent, + TelemetryConstants, } from '../../../shared/telemetry/TelemetryUtils'; import {logState} from '../../../shared/commonUtil'; @@ -434,7 +435,10 @@ export const scanMachine = }, entry: [ 'sendScanData', - () => sendStartEvent(getStartEventData('QR login')), + () => + sendStartEvent( + getStartEventData(TelemetryConstants.FlowType.qrLogin), + ), ], }, connecting: { @@ -572,7 +576,13 @@ export const scanMachine = accepted: { entry: [ 'logShared', - () => sendEndEvent(getEndEventData('VC share', 'SUCCESS')), + () => + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.vcShare, + TelemetryConstants.EndEventStatus.success, + ), + ), ], on: { DISMISS: { @@ -1012,7 +1022,9 @@ export const scanMachine = }, startConnection: context => callback => { - sendStartEvent(getStartEventData('VC share')); + sendStartEvent( + getStartEventData(TelemetryConstants.FlowType.vcShare), + ); wallet.startConnection(context.openId4VpUri); const statusCallback = (event: WalletDataEvent) => { if (event.type === EventTypes.onSecureChannelEstablished) { diff --git a/machines/issuersMachine.ts b/machines/issuersMachine.ts index 7d5607efed..49339f568b 100644 --- a/machines/issuersMachine.ts +++ b/machines/issuersMachine.ts @@ -31,6 +31,7 @@ import { } from '../shared/openId4VCI/Utils'; import {VCMetadata} from '../shared/VCMetadata'; import { + TelemetryConstants, getEndEventData, getImpressionEventData, sendEndEvent, @@ -486,14 +487,28 @@ export const IssuersMachine = model.createMachine( }, ), sendSuccessEndEvent: () => { - sendEndEvent(getEndEventData('VC Download', 'SUCCESS')); + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.vcDownload, + TelemetryConstants.EndEventStatus.success, + ), + ); }, + sendErrorEndEvent: () => { - sendEndEvent(getEndEventData('VC Download', 'FAILURE')); + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.vcDownload, + TelemetryConstants.EndEventStatus.failure, + ), + ); }, sendImpressionEvent: () => { sendImpressionEvent( - getImpressionEventData('VC Download', 'Issuer List'), + getImpressionEventData( + TelemetryConstants.FlowType.vcDownload, + TelemetryConstants.Screens.issuerList, + ), ); }, }, @@ -528,8 +543,9 @@ export const IssuersMachine = model.createMachine( invokeAuthorization: async context => { sendImpressionEvent( getImpressionEventData( - 'VC Download', - context.selectedIssuer.credential_issuer + ' Web View Page', + TelemetryConstants.FlowType.vcDownload, + context.selectedIssuer.credential_issuer + + TelemetryConstants.Screens.webViewPage, ), ); return await authorize( diff --git a/machines/pinInput.typegen.ts b/machines/pinInput.typegen.ts index 9e8dd35ed8..8abad8cd3b 100644 --- a/machines/pinInput.typegen.ts +++ b/machines/pinInput.typegen.ts @@ -2,21 +2,21 @@ export interface Typegen0 { '@@xstate/typegen': true; - 'internalEvents': { - '': { type: '' }; + internalEvents: { + '': {type: ''}; 'xstate.after(INITIAL_FOCUS_DELAY)#pinInput.idle': { type: 'xstate.after(INITIAL_FOCUS_DELAY)#pinInput.idle'; }; - 'xstate.init': { type: 'xstate.init' }; + 'xstate.init': {type: 'xstate.init'}; }; - 'invokeSrcNameMap': {}; - 'missingImplementations': { + invokeSrcNameMap: {}; + missingImplementations: { actions: never; - services: never; - guards: never; delays: never; + guards: never; + services: never; }; - 'eventsCausingActions': { + eventsCausingActions: { clearInput: 'KEY_PRESS'; focusSelected: | 'KEY_PRESS' @@ -27,15 +27,15 @@ export interface Typegen0 { selectPrevInput: 'KEY_PRESS'; updateInput: 'UPDATE_INPUT'; }; - 'eventsCausingServices': {}; - 'eventsCausingGuards': { + eventsCausingDelays: { + INITIAL_FOCUS_DELAY: '' | 'xstate.init'; + }; + eventsCausingGuards: { canGoBack: 'KEY_PRESS'; hasNextInput: 'UPDATE_INPUT'; isBlank: 'UPDATE_INPUT'; }; - 'eventsCausingDelays': { - INITIAL_FOCUS_DELAY: '' | 'xstate.init'; - }; - 'matchesStates': 'idle' | 'selectingNext' | 'selectingPrev'; - 'tags': never; + eventsCausingServices: {}; + matchesStates: 'idle' | 'selectingNext' | 'selectingPrev'; + tags: never; } diff --git a/machines/vc.typegen.ts b/machines/vc.typegen.ts index b107f4932b..9253d5d7f2 100644 --- a/machines/vc.typegen.ts +++ b/machines/vc.typegen.ts @@ -16,19 +16,32 @@ export interface Typegen0 { addVcToInProgressDownloads: 'ADD_VC_TO_IN_PROGRESS_DOWNLOADS'; getReceivedVcsResponse: 'GET_RECEIVED_VCS'; getVcItemResponse: 'GET_VC_ITEM'; - loadMyVcs: 'REFRESH_MY_VCS' | 'xstate.init'; + loadMyVcs: + | 'REFRESH_MY_VCS' + | 'REMOVE_TAMPERED_VCS' + | 'STORE_RESPONSE' + | 'xstate.init'; loadReceivedVcs: 'REFRESH_RECEIVED_VCS' | 'STORE_RESPONSE'; + logTamperedVCsremoved: 'REMOVE_TAMPERED_VCS'; moveExistingVcToTop: 'VC_RECEIVED'; prependToMyVcs: 'VC_ADDED'; prependToReceivedVcs: 'VC_RECEIVED'; - removeVcFromInProgressDownlods: 'REMOVE_VC_FROM_IN_PROGRESS_DOWNLOADS'; + removeDownloadFailedVcsFromStorage: 'DELETE_VC'; + removeDownloadingFailedVcsFromMyVcs: 'STORE_RESPONSE'; + removeTamperedVcs: 'REMOVE_TAMPERED_VCS'; + removeVcFromInProgressDownlods: + | 'DOWNLOAD_LIMIT_EXPIRED' + | 'REMOVE_VC_FROM_IN_PROGRESS_DOWNLOADS'; removeVcFromMyVcs: 'REMOVE_VC_FROM_CONTEXT'; resetAreAllVcsDownloaded: 'RESET_ARE_ALL_VCS_DOWNLOADED'; + resetDownloadFailedVcs: 'STORE_RESPONSE'; resetWalletBindingSuccess: 'RESET_WALLET_BINDING_SUCCESS'; setDownloadedVCFromOpenId4VCI: 'VC_DOWNLOADED_FROM_OPENID4VCI'; setDownloadedVc: 'VC_DOWNLOADED'; + setDownloadingFailedVcs: 'DOWNLOAD_LIMIT_EXPIRED'; setMyVcs: 'STORE_RESPONSE'; setReceivedVcs: 'STORE_RESPONSE'; + setTamperedVcs: 'TAMPERED_VC'; setUpdatedVcMetadatas: 'VC_METADATA_UPDATED'; setVcUpdate: 'VC_UPDATE'; setWalletBindingSuccess: 'WALLET_BINDING_SUCCESS'; @@ -40,6 +53,8 @@ export interface Typegen0 { }; eventsCausingServices: {}; matchesStates: + | 'deletingFailedVcs' + | 'downloadLimitExpired' | 'init' | 'init.myVcs' | 'init.receivedVcs' @@ -50,6 +65,7 @@ export interface Typegen0 { | 'ready.receivedVcs' | 'ready.receivedVcs.idle' | 'ready.receivedVcs.refreshing' + | 'tamperedVCs' | { init?: 'myVcs' | 'receivedVcs'; ready?: diff --git a/package.json b/package.json index 0074902a32..5375fb82e8 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "react-native-vector-icons": "^10.0.0", "short-unique-id": "^4.4.4", "simple-pem2jwk": "^0.2.4", - "telemetry-sdk": "git://github.com/mosip/sunbird-telemetry-sdk.git#f762be5732ee552c0c70bdd540aa4e2701554c71", + "telemetry-sdk": "git://github.com/mosip/sunbird-telemetry-sdk.git#f762be5732ee552c0c70bdd540aa4e2701554c71", "xstate": "^4.35.0" }, "devDependencies": { diff --git a/screens/AuthScreen.tsx b/screens/AuthScreen.tsx index 3630661a28..a517e39909 100644 --- a/screens/AuthScreen.tsx +++ b/screens/AuthScreen.tsx @@ -11,6 +11,7 @@ import { getInteractEventData, sendInteractEvent, sendStartEvent, + TelemetryConstants, } from '../shared/telemetry/TelemetryUtils'; export const AuthScreen: React.FC = props => { @@ -18,9 +19,15 @@ export const AuthScreen: React.FC = props => { const controller = useAuthScreen(props); const handleUsePasscodeButtonPress = () => { - sendStartEvent(getStartEventData('App Onboarding')); + sendStartEvent( + getStartEventData(TelemetryConstants.FlowType.appOnboarding), + ); sendInteractEvent( - getInteractEventData('App Onboarding', 'TOUCH', 'Use Passcode Button'), + getInteractEventData( + TelemetryConstants.FlowType.appOnboarding, + TelemetryConstants.InteractEventSubtype.click, + 'Use Passcode Button', + ), ); controller.usePasscode(); }; diff --git a/screens/AuthScreenController.ts b/screens/AuthScreenController.ts index a46d8756d8..f0a2556f79 100644 --- a/screens/AuthScreenController.ts +++ b/screens/AuthScreenController.ts @@ -25,6 +25,7 @@ import { getImpressionEventData, getEndEventData, sendEndEvent, + TelemetryConstants, } from '../shared/telemetry/TelemetryUtils'; export function useAuthScreen(props: RootRouteProps) { @@ -61,12 +62,22 @@ export function useAuthScreen(props: RootRouteProps) { useEffect(() => { if (isAuthorized) { - sendEndEvent(getEndEventData('App Onboarding', 'SUCCESS')); + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.appOnboarding, + TelemetryConstants.EndEventStatus.success, + ), + ); props.navigation.reset({ index: 0, routes: [{name: 'Main'}], }); - sendImpressionEvent(getImpressionEventData('App Onboarding', 'Home')); + sendImpressionEvent( + getImpressionEventData( + TelemetryConstants.FlowType.appOnboarding, + TelemetryConstants.Screens.home, + ), + ); return; } @@ -80,11 +91,15 @@ 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, - }), + getEndEventData( + TelemetryConstants.FlowType.appOnboarding, + TelemetryConstants.EndEventStatus.failure, + { + errorId: errorResponse.res.error, + errorMessage: errorResponse.res.warning, + stackTrace: errorResponse.stacktrace, + }, + ), ); // show alert message whenever biometric state gets failure if (errorResponse.res.error !== 'user_cancel') { @@ -97,7 +112,9 @@ export function useAuthScreen(props: RootRouteProps) { // we dont need to see this page to user once biometric is unavailable on its device } else if (isUnavailableBio) { - sendStartEvent(getStartEventData('App Onboarding')); + sendStartEvent( + getStartEventData(TelemetryConstants.FlowType.appOnboarding), + ); usePasscode(); } }, [isSuccessBio, isUnavailableBio, errorMsgBio, unEnrolledNoticeBio]); @@ -105,11 +122,13 @@ export function useAuthScreen(props: RootRouteProps) { const useBiometrics = async () => { const isBiometricsEnrolled = await LocalAuthentication.isEnrolledAsync(); if (isBiometricsEnrolled) { - sendStartEvent(getStartEventData('App Onboarding')); + sendStartEvent( + getStartEventData(TelemetryConstants.FlowType.appOnboarding), + ); sendInteractEvent( getInteractEventData( - 'App Onboarding', - 'TOUCH', + TelemetryConstants.FlowType.appOnboarding, + TelemetryConstants.InteractEventSubtype.click, 'Use Biometrics Button', ), ); diff --git a/screens/BiometricScreen.tsx b/screens/BiometricScreen.tsx index dd0a639508..cba950fd8e 100644 --- a/screens/BiometricScreen.tsx +++ b/screens/BiometricScreen.tsx @@ -8,8 +8,10 @@ import {RootRouteProps} from '../routes'; import {useBiometricScreen} from './BiometricScreenController'; import {Passcode} from '../components/Passcode'; import { + TelemetryConstants, getEventType, - incrementPasscodeRetryCount, + incrementRetryCount, + resetRetryCount, } from '../shared/telemetry/TelemetryUtils'; export const BiometricScreen: React.FC = props => { @@ -17,10 +19,18 @@ export const BiometricScreen: React.FC = props => { const controller = useBiometricScreen(props); const handlePasscodeMismatch = (error: string) => { - incrementPasscodeRetryCount(getEventType(props.route.params?.setup)); + incrementRetryCount( + getEventType(props.route.params?.setup), + TelemetryConstants.Screens.passcode, + ); controller.onError(error); }; + const handleOnSuccess = () => { + resetRetryCount(); + controller.onSuccess(); + }; + return ( = props => { {controller.isReEnabling && ( controller.onSuccess()} + onSuccess={handleOnSuccess} onError={handlePasscodeMismatch} storedPasscode={controller.storedPasscode} onDismiss={() => controller.onDismiss()} diff --git a/screens/BiometricScreenController.ts b/screens/BiometricScreenController.ts index 8433174c9d..6d4944dbae 100644 --- a/screens/BiometricScreenController.ts +++ b/screens/BiometricScreenController.ts @@ -22,12 +22,12 @@ import {GlobalContext} from '../shared/GlobalContext'; import { getStartEventData, getEndEventData, - getImpressionEventData, getInteractEventData, sendEndEvent, - sendImpressionEvent, sendInteractEvent, sendStartEvent, + TelemetryConstants, + resetRetryCount, } from '../shared/telemetry/TelemetryUtils'; import {isAndroid} from '../shared/constants'; @@ -51,11 +51,11 @@ export function useBiometricScreen(props: RootRouteProps) { useEffect(() => { if (isAvailable) { - sendStartEvent(getStartEventData('App login')); + sendStartEvent(getStartEventData(TelemetryConstants.FlowType.appLogin)); sendInteractEvent( getInteractEventData( - 'App login', - 'TOUCH', + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.InteractEventSubtype.click, 'Unlock with Biometrics button', ), ); @@ -64,7 +64,12 @@ export function useBiometricScreen(props: RootRouteProps) { useEffect(() => { if (isAuthorized) { - sendEndEvent(getEndEventData('App Login', 'SUCCESS')); + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.EndEventStatus.success, + ), + ); props.navigation.reset({ index: 0, routes: [{name: 'Main'}], @@ -87,11 +92,15 @@ export function useBiometricScreen(props: RootRouteProps) { if (errorMsgBio && !isReEnabling) { sendEndEvent( - getEndEventData('App Login', 'FAILURE', { - errorId: errorResponse.res.error, - errorMessage: errorResponse.res.warning, - stackTrace: errorResponse.stacktrace, - }), + getEndEventData( + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.EndEventStatus.failure, + { + errorId: errorResponse.res.error, + errorMessage: errorResponse.res.warning, + stackTrace: errorResponse.stacktrace, + }, + ), ); } @@ -100,9 +109,13 @@ export function useBiometricScreen(props: RootRouteProps) { index: 0, routes: [{name: 'Passcode'}], }); - sendStartEvent(getStartEventData('App Login')); + sendStartEvent(getStartEventData(TelemetryConstants.FlowType.appLogin)); sendInteractEvent( - getInteractEventData('App Login', 'TOUCH', 'Unlock application button'), + getInteractEventData( + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.InteractEventSubtype.click, + 'Unlock application button', + ), ); } }, [ @@ -132,11 +145,11 @@ export function useBiometricScreen(props: RootRouteProps) { }; const useBiometrics = () => { - sendStartEvent(getStartEventData('App login')); + sendStartEvent(getStartEventData(TelemetryConstants.FlowType.appLogin)); sendInteractEvent( getInteractEventData( - 'App Login', - 'TOUCH', + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.InteractEventSubtype.click, 'Unlock with biometrics button', ), ); @@ -144,6 +157,7 @@ export function useBiometricScreen(props: RootRouteProps) { }; const onSuccess = () => { + resetRetryCount(); bioSend({type: 'AUTHENTICATE'}); setError(''); }; @@ -154,10 +168,14 @@ export function useBiometricScreen(props: RootRouteProps) { const onDismiss = () => { sendEndEvent( - getEndEventData('App Login', 'FAILURE', { - errorId: 'user_cancel', - errorMessage: 'Authentication canceled', - }), + getEndEventData( + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.EndEventStatus.failure, + { + errorId: 'user_cancel', + errorMessage: 'Authentication canceled', + }, + ), ); setReEnabling(false); }; diff --git a/screens/Home/MyVcs/AddVcModal.tsx b/screens/Home/MyVcs/AddVcModal.tsx index 7ee9b8e72d..1b3eb1f999 100644 --- a/screens/Home/MyVcs/AddVcModal.tsx +++ b/screens/Home/MyVcs/AddVcModal.tsx @@ -5,6 +5,7 @@ import {OtpVerificationModal} from './OtpVerificationModal'; import {IdInputModal} from './IdInputModal'; import {useTranslation} from 'react-i18next'; import {GET_INDIVIDUAL_ID} from '../../../shared/constants'; +import {TelemetryConstants} from '../../../shared/telemetry/TelemetryUtils'; import {Button, Column} from '../../../components/ui'; export const AddVcModal: React.FC = props => { @@ -35,15 +36,18 @@ export const AddVcModal: React.FC = props => { onPress={props.onPress} /> - + {(controller.isAcceptingOtpInput || controller.isDownloadCancelled) && ( + + )} context.idInputRef.focus(), - - sendImpressionEvent: () => { - sendImpressionEvent( - getImpressionEventData('VC Download', 'OTP Verification'), - ); - }, }, services: { diff --git a/screens/Home/MyVcs/AddVcModalMachine.typegen.ts b/screens/Home/MyVcs/AddVcModalMachine.typegen.ts index fd84d58be4..7e3aa522f3 100644 --- a/screens/Home/MyVcs/AddVcModalMachine.typegen.ts +++ b/screens/Home/MyVcs/AddVcModalMachine.typegen.ts @@ -46,7 +46,7 @@ export interface Typegen0 { clearId: 'SELECT_ID_TYPE'; clearIdError: 'INPUT_ID' | 'SELECT_ID_TYPE' | 'VALIDATE_INPUT'; clearOtp: - | 'DISMISS' + | 'WAIT' | 'done.invoke.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]' | 'error.platform.AddVcModal.acceptingOtpInput.resendOTP:invocation[0]' | 'error.platform.AddVcModal.requestingCredential:invocation[0]' @@ -59,8 +59,8 @@ export interface Typegen0 { | 'error.platform.AddVcModal.acceptingOtpInput.resendOTP:invocation[0]' | 'error.platform.AddVcModal.requestingCredential:invocation[0]' | 'xstate.after(100)#AddVcModal.acceptingIdInput.focusing'; - forwardToParent: 'DISMISS'; - resetIdInputRef: 'DISMISS'; + forwardToParent: 'CANCEL' | 'DISMISS'; + resetIdInputRef: 'CANCEL'; setId: 'INPUT_ID'; setIdBackendError: | 'error.platform.AddVcModal.acceptingIdInput.requestingOtp:invocation[0]' @@ -74,7 +74,6 @@ export interface Typegen0 { setOtpError: 'error.platform.AddVcModal.requestingCredential:invocation[0]'; setRequestId: 'done.invoke.AddVcModal.requestingCredential:invocation[0]'; setTransactionId: - | 'DISMISS' | 'error.platform.AddVcModal.acceptingOtpInput.resendOTP:invocation[0]' | 'error.platform.AddVcModal.requestingCredential:invocation[0]' | 'xstate.init'; @@ -102,6 +101,7 @@ export interface Typegen0 { | 'acceptingOtpInput' | 'acceptingOtpInput.idle' | 'acceptingOtpInput.resendOTP' + | 'cancelDownload' | 'done' | 'requestingCredential' | { diff --git a/screens/Home/MyVcs/GetVcModal.tsx b/screens/Home/MyVcs/GetVcModal.tsx index 92f2761e9b..421048df6f 100644 --- a/screens/Home/MyVcs/GetVcModal.tsx +++ b/screens/Home/MyVcs/GetVcModal.tsx @@ -1,12 +1,13 @@ import React from 'react'; -import { MessageOverlay } from '../../../components/MessageOverlay'; -import { useGetVcModal, GetVcModalProps } from './GetVcModalController'; -import { OtpVerificationModal } from './OtpVerificationModal'; -import { GetIdInputModal } from './GetIdInputModal'; -import { useTranslation } from 'react-i18next'; +import {MessageOverlay} from '../../../components/MessageOverlay'; +import {useGetVcModal, GetVcModalProps} from './GetVcModalController'; +import {OtpVerificationModal} from './OtpVerificationModal'; +import {GetIdInputModal} from './GetIdInputModal'; +import {useTranslation} from 'react-i18next'; +import {TelemetryConstants} from '../../../shared/telemetry/TelemetryUtils'; -export const GetVcModal: React.FC = (props) => { - const { t } = useTranslation('GetVcModal'); +export const GetVcModal: React.FC = props => { + const {t} = useTranslation('GetVcModal'); const controller = useGetVcModal(props); return ( @@ -22,6 +23,7 @@ export const GetVcModal: React.FC = (props) => { onDismiss={controller.DISMISS} onInputDone={controller.INPUT_OTP} error={controller.otpError} + flow={TelemetryConstants.FlowType.getVcUsingAid} /> { + sendImpressionEvent( + getImpressionEventData( + props.flow, + TelemetryConstants.Screens.otpVerificationModal, + ), + ); + }, [props.flow]); + useEffect(() => { if (timer === 0) return; @@ -31,6 +47,19 @@ export const OtpVerificationModal: React.FC< }`; }; + const handleOtpResend = () => { + incrementRetryCount( + props.flow, + TelemetryConstants.Screens.otpVerificationModal, + ); + props.resend(); + }; + + const handleEnteredOtp = (otp: string) => { + resetRetryCount(); + props.onInputDone(otp); + }; + return ( {props.error} - + 0 ? null : () => { - props.resend(); + handleOtpResend(); setTimer(180); } }> @@ -103,4 +132,5 @@ interface OtpVerificationModalProps extends ModalProps { onInputDone: (otp: string) => void; error?: string; resend?: () => void; + flow: string; } diff --git a/screens/Home/MyVcs/WalletBinding.tsx b/screens/Home/MyVcs/WalletBinding.tsx index 3f93e9834b..5f68a7a27c 100644 --- a/screens/Home/MyVcs/WalletBinding.tsx +++ b/screens/Home/MyVcs/WalletBinding.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import {Icon, ListItem} from 'react-native-elements'; import {Row, Text} from '../../../components/ui'; import {Theme} from '../../../components/ui/styleUtils'; @@ -12,10 +12,39 @@ import {ActorRefFrom} from 'xstate'; import {ExistingMosipVCItemMachine} from '../../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine'; import testIDProps from '../../../shared/commonUtil'; import {VCMetadata} from '../../../shared/VCMetadata'; +import { + TelemetryConstants, + getEndEventData, + getErrorEventData, + sendEndEvent, + sendErrorEvent, +} from '../../../shared/telemetry/TelemetryUtils'; export const WalletBinding: React.FC = props => { const controller = useKebabPopUp(props); + useEffect(() => { + let error = controller.walletBindingError; + if (error) { + error = controller.bindingAuthFailedError + ? controller.bindingAuthFailedError + '-' + error + : error; + sendErrorEvent( + getErrorEventData( + TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.ErrorId.activationFailed, + error, + ), + ); + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.failure, + ), + ); + } + }, [controller.walletBindingError]); + const WalletVerified: React.FC = () => { return ( = props => { onCancel={controller.CANCEL} /> - + {controller.isAcceptingOtpInput && ( + + )} + = props => { if (controller.showHardwareKeystoreNotExistsAlert) { sendErrorEvent( getErrorEventData( - 'App Onboarding', - 'does_not_exist', - 'Some security features will be unavailable as hardware key store is not available', + TelemetryConstants.FlowType.appOnboarding, + TelemetryConstants.ErrorId.doesNotExist, + TelemetryConstants.ErrorMessage.hardwareKeyStore, ), ); } diff --git a/screens/Home/MyVcsTabController.ts b/screens/Home/MyVcsTabController.ts index 766906d187..ea86006838 100644 --- a/screens/Home/MyVcsTabController.ts +++ b/screens/Home/MyVcsTabController.ts @@ -15,7 +15,7 @@ import { import { selectWalletBindingError, selectShowWalletBindingError, -} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine'; +} from '../../machines/VCItemMachine/commonSelectors'; import {ExistingMosipVCItemMachine} from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine'; import {GlobalContext} from '../../shared/GlobalContext'; import {HomeScreenTabProps} from './HomeScreen'; diff --git a/screens/Home/MyVcsTabMachine.typegen.ts b/screens/Home/MyVcsTabMachine.typegen.ts index a0c23bc659..61deee3eb3 100644 --- a/screens/Home/MyVcsTabMachine.typegen.ts +++ b/screens/Home/MyVcsTabMachine.typegen.ts @@ -1,44 +1,73 @@ +// This file was automatically generated. Edits will be overwritten - // This file was automatically generated. Edits will be overwritten - - export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - "done.invoke.AddVcModal": { type: "done.invoke.AddVcModal"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; -"done.invoke.GetVcModal": { type: "done.invoke.GetVcModal"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; -"done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]": { type: "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; -"xstate.init": { type: "xstate.init" }; - }; - invokeSrcNameMap: { - "checkNetworkStatus": "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]"; - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - "resetStoringVcItemStatus": "RESET_STORE_VC_ITEM_STATUS"; -"sendVcAdded": "STORE_RESPONSE"; -"setStoringVcItemStatus": "SET_STORE_VC_ITEM_STATUS" | "STORE_RESPONSE"; -"storeVcItem": "done.invoke.AddVcModal"; -"viewVcFromParent": "VIEW_VC"; - }; - eventsCausingDelays: { - - }; - eventsCausingGuards: { - "isNetworkOn": "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]"; - }; - eventsCausingServices: { - "AddVcModal": "done.invoke.GetVcModal" | "done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]"; -"GetVcModal": "GET_VC"; -"checkNetworkStatus": "ADD_VC" | "TRY_AGAIN"; - }; - matchesStates: "addVc" | "addVc.checkNetwork" | "addVc.networkOff" | "addingVc" | "addingVc.savingFailed" | "addingVc.savingFailed.idle" | "addingVc.storing" | "addingVc.waitingForvcKey" | "gettingVc" | "gettingVc.waitingForvcKey" | "idle" | "viewingVc" | { "addVc"?: "checkNetwork" | "networkOff"; -"addingVc"?: "savingFailed" | "storing" | "waitingForvcKey" | { "savingFailed"?: "idle"; }; -"gettingVc"?: "waitingForvcKey"; }; - tags: never; - } - \ No newline at end of file +export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + 'done.invoke.AddVcModal': { + type: 'done.invoke.AddVcModal'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + 'done.invoke.GetVcModal': { + type: 'done.invoke.GetVcModal'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]': { + type: 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + 'xstate.init': {type: 'xstate.init'}; + }; + invokeSrcNameMap: { + checkNetworkStatus: 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]'; + }; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + resetStoringVcItemStatus: 'RESET_STORE_VC_ITEM_STATUS'; + sendVcAdded: 'STORE_RESPONSE'; + setStoringVcItemStatus: 'SET_STORE_VC_ITEM_STATUS' | 'STORE_RESPONSE'; + storeVcItem: 'done.invoke.AddVcModal'; + viewVcFromParent: 'VIEW_VC'; + }; + eventsCausingDelays: {}; + eventsCausingGuards: { + isNetworkOn: 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]'; + }; + eventsCausingServices: { + AddVcModal: + | 'done.invoke.GetVcModal' + | 'done.invoke.MyVcsTab.addVc.checkNetwork:invocation[0]'; + GetVcModal: 'GET_VC'; + checkNetworkStatus: 'ADD_VC' | 'TRY_AGAIN'; + }; + matchesStates: + | 'addVc' + | 'addVc.checkNetwork' + | 'addVc.networkOff' + | 'addingVc' + | 'addingVc.savingFailed' + | 'addingVc.savingFailed.idle' + | 'addingVc.storing' + | 'addingVc.waitingForvcKey' + | 'gettingVc' + | 'gettingVc.waitingForvcKey' + | 'idle' + | 'viewingVc' + | { + addVc?: 'checkNetwork' | 'networkOff'; + addingVc?: + | 'savingFailed' + | 'storing' + | 'waitingForvcKey' + | {savingFailed?: 'idle'}; + gettingVc?: 'waitingForvcKey'; + }; + tags: never; +} diff --git a/screens/Home/ViewVcModal.tsx b/screens/Home/ViewVcModal.tsx index d82baf024b..16f8413a6a 100644 --- a/screens/Home/ViewVcModal.tsx +++ b/screens/Home/ViewVcModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import {Column} from '../../components/ui'; import {Modal} from '../../components/ui/Modal'; import {MessageOverlay} from '../../components/MessageOverlay'; @@ -8,10 +8,16 @@ import {OIDcAuthenticationModal} from '../../components/OIDcAuth'; import {useViewVcModal, ViewVcModalProps} from './ViewVcModalController'; import {useTranslation} from 'react-i18next'; import {BannerNotification} from '../../components/BannerNotification'; -import {TextEditOverlay} from '../../components/TextEditOverlay'; import {OtpVerificationModal} from './MyVcs/OtpVerificationModal'; import {BindingVcWarningOverlay} from './MyVcs/BindingVcWarningOverlay'; import {VcDetailsContainer} from '../../components/VC/VcDetailsContainer'; +import { + TelemetryConstants, + getEndEventData, + getErrorEventData, + sendEndEvent, + sendErrorEvent, +} from '../../shared/telemetry/TelemetryUtils'; export const ViewVcModal: React.FC = props => { const {t} = useTranslation('ViewVcModal'); @@ -26,6 +32,28 @@ export const ViewVcModal: React.FC = props => { }, ]; + useEffect(() => { + let error = controller.walletBindingError; + if (error) { + error = controller.bindingAuthFailedError + ? controller.bindingAuthFailedError + '-' + error + : error; + sendErrorEvent( + getErrorEventData( + TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.ErrorId.activationFailed, + error, + ), + ); + sendEndEvent( + getEndEventData( + TelemetryConstants.FlowType.vcActivation, + TelemetryConstants.EndEventStatus.failure, + ), + ); + } + }, [controller.walletBindingError]); + return ( = props => { onInputDone={controller.inputOtp} error={controller.otpError} resend={controller.RESEND_OTP} + flow={TelemetryConstants.FlowType.vcLockOrRevoke} /> )} @@ -78,6 +107,7 @@ export const ViewVcModal: React.FC = props => { onInputDone={controller.inputOtp} error={controller.otpError} resend={controller.RESEND_OTP} + flow={TelemetryConstants.FlowType.vcActivation} /> )} diff --git a/screens/Home/ViewVcModalController.ts b/screens/Home/ViewVcModalController.ts index f4dbd01549..aa1dd0bc63 100644 --- a/screens/Home/ViewVcModalController.ts +++ b/screens/Home/ViewVcModalController.ts @@ -7,6 +7,13 @@ import {ModalProps} from '../../components/ui/Modal'; import {GlobalContext} from '../../shared/GlobalContext'; import { selectOtpError, + selectWalletBindingError, + selectEmptyWalletBindingId, + selectShowWalletBindingError, + selectWalletBindingSuccess, + selectBindingAuthFailedError, +} from '../../machines/VCItemMachine/commonSelectors'; +import { selectIsAcceptingOtpInput, selectIsAcceptingRevokeInput, selectIsLockingVc, @@ -15,13 +22,9 @@ import { selectVc, ExistingMosipVCItemEvents, ExistingMosipVCItemMachine, - selectWalletBindingError, selectRequestBindingOtp, selectAcceptingBindingOtp, - selectEmptyWalletBindingId, selectWalletBindingInProgress, - selectShowWalletBindingError, - selectWalletBindingSuccess, selectBindingWarning, } from '../../machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine'; import {selectPasscode} from '../../machines/auth'; @@ -112,6 +115,10 @@ export function useViewVcModal({ toastVisible, vc, otpError: useSelector(vcItemActor, selectOtpError), + bindingAuthFailedError: useSelector( + vcItemActor, + selectBindingAuthFailedError, + ), reAuthenticating, isRevoking, diff --git a/screens/Issuers/IssuersScreen.tsx b/screens/Issuers/IssuersScreen.tsx index 2a37b04448..630133777b 100644 --- a/screens/Issuers/IssuersScreen.tsx +++ b/screens/Issuers/IssuersScreen.tsx @@ -17,6 +17,7 @@ import { Protocols, } from '../../shared/openId4VCI/Utils'; import { + TelemetryConstants, getInteractEventData, getStartEventData, sendInteractEvent, @@ -57,9 +58,15 @@ export const IssuersScreen: React.FC< ]); const onPressHandler = (id: string, protocol: string) => { - sendStartEvent(getStartEventData('VC Download', {id: id})); + sendStartEvent( + getStartEventData(TelemetryConstants.FlowType.vcDownload, {id: id}), + ); sendInteractEvent( - getInteractEventData('VC Download', 'CLICK', `IssuerType: ${id}`), + getInteractEventData( + TelemetryConstants.FlowType.vcDownload, + TelemetryConstants.InteractEventSubtype.click, + `IssuerType: ${id}`, + ), ); protocol === Protocols.OTP ? controller.DOWNLOAD_ID() diff --git a/screens/PasscodeScreen.tsx b/screens/PasscodeScreen.tsx index 985e1d8d12..b759a7a13d 100644 --- a/screens/PasscodeScreen.tsx +++ b/screens/PasscodeScreen.tsx @@ -10,14 +10,16 @@ import {usePasscodeScreen} from './PasscodeScreenController'; import {hashData} from '../shared/commonUtil'; import {argon2iConfig} from '../shared/constants'; import { + TelemetryConstants, getEndEventData, getEventType, getImpressionEventData, + resetRetryCount, sendEndEvent, sendImpressionEvent, } from '../shared/telemetry/TelemetryUtils'; import {BackHandler} from 'react-native'; -import {incrementPasscodeRetryCount} from '../shared/telemetry/TelemetryUtils'; +import {incrementRetryCount} from '../shared/telemetry/TelemetryUtils'; export const PasscodeScreen: React.FC = props => { const {t} = useTranslation('PasscodeScreen'); @@ -26,16 +28,23 @@ export const PasscodeScreen: React.FC = props => { useEffect(() => { sendImpressionEvent( - getImpressionEventData(getEventType(isSettingUp), 'Passcode'), + getImpressionEventData( + getEventType(isSettingUp), + TelemetryConstants.Screens.passcode, + ), ); }, [isSettingUp]); const handleBackButtonPress = () => { sendEndEvent( - getEndEventData(getEventType(isSettingUp), 'FAILURE', { - errorId: 'user_cancel', - errorMessage: 'Authentication canceled', - }), + getEndEventData( + getEventType(isSettingUp), + TelemetryConstants.EndEventStatus.failure, + { + errorId: TelemetryConstants.ErrorId.userCancel, + errorMessage: TelemetryConstants.ErrorMessage.authenticationCancelled, + }, + ), ); return false; }; @@ -57,7 +66,10 @@ export const PasscodeScreen: React.FC = props => { }; const handlePasscodeMismatch = (error: string) => { - incrementPasscodeRetryCount(getEventType(isSettingUp)); + incrementRetryCount( + getEventType(isSettingUp), + TelemetryConstants.Screens.passcode, + ); controller.setError(error); }; @@ -106,7 +118,10 @@ export const PasscodeScreen: React.FC = props => { { + resetRetryCount(); + controller.SETUP_PASSCODE(); + }} onError={handlePasscodeMismatch} passcode={controller.passcode} salt={controller.storedSalt} @@ -136,7 +151,10 @@ export const PasscodeScreen: React.FC = props => { {t('enterPasscode')} { + resetRetryCount(); + controller.LOGIN(); + }} onError={handlePasscodeMismatch} passcode={controller.storedPasscode} salt={controller.storedSalt} diff --git a/screens/PasscodeScreenController.ts b/screens/PasscodeScreenController.ts index ddfabc7283..38c6bd38e5 100644 --- a/screens/PasscodeScreenController.ts +++ b/screens/PasscodeScreenController.ts @@ -9,6 +9,7 @@ import { import {PasscodeRouteProps} from '../routes'; import {GlobalContext} from '../shared/GlobalContext'; import { + TelemetryConstants, getEndEventData, getEventType, sendEndEvent, @@ -26,7 +27,10 @@ export function usePasscodeScreen(props: PasscodeRouteProps) { useEffect(() => { if (isAuthorized) { sendEndEvent( - getEndEventData(getEventType(props.route.params?.setup), 'SUCCESS'), + getEndEventData( + getEventType(props.route.params?.setup), + TelemetryConstants.EndEventStatus.success, + ), ); props.navigation.reset({ index: 0, diff --git a/screens/WelcomeScreenController.ts b/screens/WelcomeScreenController.ts index 8bdf374510..85c6e7bff6 100644 --- a/screens/WelcomeScreenController.ts +++ b/screens/WelcomeScreenController.ts @@ -15,11 +15,10 @@ import {RootRouteProps} from '../routes'; import {GlobalContext} from '../shared/GlobalContext'; import { getStartEventData, - getImpressionEventData, getInteractEventData, - sendImpressionEvent, sendInteractEvent, sendStartEvent, + TelemetryConstants, } from '../shared/telemetry/TelemetryUtils'; export function useWelcomeScreen(props: RootRouteProps) { @@ -59,11 +58,11 @@ export function useWelcomeScreen(props: RootRouteProps) { if (!isSettingUp && isBiometricUnlockEnabled && biometrics !== '') { props.navigation.navigate('Biometric', {setup: isSettingUp}); } else if (!isSettingUp && passcode !== '') { - sendStartEvent(getStartEventData('App Login')); + sendStartEvent(getStartEventData(TelemetryConstants.FlowType.appLogin)); sendInteractEvent( getInteractEventData( - 'App Login', - 'TOUCH', + TelemetryConstants.FlowType.appLogin, + TelemetryConstants.InteractEventSubtype.click, 'Unlock application button', ), ); diff --git a/shared/telemetry/TelemetryUtils.js b/shared/telemetry/TelemetryUtils.js index f3d8256ebb..77659a24b7 100644 --- a/shared/telemetry/TelemetryUtils.js +++ b/shared/telemetry/TelemetryUtils.js @@ -135,19 +135,31 @@ export function getAppInfoEventData() { }; } -let passcodeRetryCount = 1; +let retryCount = 0; -export const incrementPasscodeRetryCount = eventType => { - if (passcodeRetryCount < 5) { - passcodeRetryCount += 1; +export const incrementRetryCount = (eventType, screen) => { + if (retryCount < 4) { + retryCount += 1; } else { - sendErrorEvent( - getErrorEventData(eventType, 'mismatch', 'Passcode did not match'), - ); - passcodeRetryCount = 1; + const [errorId, errorMessage] = + screen === TelemetryConstants.Screens.passcode + ? [ + TelemetryConstants.ErrorId.mismatch, + TelemetryConstants.ErrorMessage.passcodeDidNotMatch, + ] + : [ + TelemetryConstants.ErrorId.resend, + TelemetryConstants.ErrorMessage.resendOtp, + ]; + sendErrorEvent(getErrorEventData(eventType, errorId, errorMessage)); + retryCount = 0; } }; +export const resetRetryCount = () => { + retryCount = 0; +}; + export function configureTelemetry() { const config = getTelemetryConfigData(); initializeTelemetry(config); @@ -155,7 +167,9 @@ export function configureTelemetry() { } export function getEventType(isSettingUp) { - return isSettingUp ? 'App Onboarding' : 'App Login'; + return isSettingUp + ? TelemetryConstants.FlowType.appOnboarding + : TelemetryConstants.FlowType.appLogin; } const languageCodeMap = { @@ -166,3 +180,50 @@ const languageCodeMap = { kn: 'Kannada', ta: 'Tamil', }; +export const TelemetryConstants = { + FlowType: Object.freeze({ + vcDownload: 'VC Download', + qrLogin: 'QR Login', + vcShare: 'VC Share', + vcActivation: 'VC Activation', + vcActivationFromKebab: 'VC Activation from kebab popup', + appOnboarding: 'App Onboarding', + appLogin: 'App Login', + vcLockOrRevoke: 'VC Lock / VC Revoke', + getVcUsingAid: 'Get VC using AID', + }), + + EndEventStatus: Object.freeze({ + success: 'SUCCESS', + failure: 'FAILURE', + }), + + InteractEventSubtype: Object.freeze({ + click: 'CLICK', + }), + + ErrorMessage: Object.freeze({ + authenticationCancelled: 'Authentication Cancelled', + passcodeDidNotMatch: 'Pass code did not match', + resendOtp: 'Otp is requested multiple times', + hardwareKeyStore: + 'Some security features will be unavailable as hardware key store is not available', + activationCancelled: 'Activation Cancelled', + }), + + ErrorId: Object.freeze({ + mismatch: 'MISMATCH', + doesNotExist: 'DOES_NOT_EXIST', + userCancel: 'USER_CANCEL', + resend: 'RESEND', + activationFailed: 'ACTIVATION_FAILED', + }), + + Screens: Object.freeze({ + home: 'Home', + passcode: 'Passcode', + webViewPage: 'Web View Page', + otpVerificationModal: 'Otp Verification Modal', + issuerList: 'Issuer List', + }), +};