[INJIMOB-3190]: Add deeplink support for iOS OVP flow (#1913)

* [INJIMOB-3190]: add support for ovp deeplink in ios

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor ovp deeplink

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: show text for ios ovp deeplink flow

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor ovp deeplink flow

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor state variables and extract functions

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: fix additional message shown in android success overlay

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor no sharable vc variable

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor send vp screen and error modal

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor send vp screen and error modal

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3190]: refactor scan machine actions

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>

* [INJIMOB-3153]: fix closing brace issue

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

---------

Signed-off-by: adityankannan-tw <adityan.kannan@thoughtworks.com>
Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>
Co-authored-by: Alka Prasad <prasadalka1998@gmail.com>
This commit is contained in:
adityankannan-tw
2025-05-12 14:39:18 +05:30
committed by GitHub
parent 3163167feb
commit 1df2d8f26a
29 changed files with 473 additions and 166 deletions

34
App.tsx
View File

@@ -1,6 +1,6 @@
import React, {useContext, useEffect, useState} from 'react';
import {AppLayout} from './screens/AppLayout';
import {useFont} from "./shared/hooks/useFont";
import {useFont} from './shared/hooks/useFont';
import {GlobalContextProvider} from './components/GlobalContextProvider';
import {GlobalContext} from './shared/GlobalContext';
import {useSelector} from '@xstate/react';
@@ -9,7 +9,7 @@ import {
APP_EVENTS,
selectIsDecryptError,
selectIsKeyInvalidateError,
selectIsLinkCode,
selectIsDeepLinkDetected,
selectIsReadError,
selectIsReady,
} from './machines/app';
@@ -29,7 +29,7 @@ import i18n from './i18n';
import {CopilotProvider} from 'react-native-copilot';
import {CopilotTooltip} from './components/CopilotTooltip';
import {Theme} from './components/ui/styleUtils';
import { selectAppSetupComplete } from './machines/auth';
import {selectAppSetupComplete} from './machines/auth';
const {RNSecureKeystoreModule} = NativeModules;
// kludge: this is a bad practice but has been done temporarily to surface
@@ -51,15 +51,15 @@ const DecryptErrorAlert = (controller, t) => {
const AppLayoutWrapper: React.FC = () => {
const {appService} = useContext(GlobalContext);
const isDecryptError = useSelector(appService, selectIsDecryptError);
const isQrLogin = useSelector(appService, selectIsLinkCode);
const isDeepLinkFlow = useSelector(appService, selectIsDeepLinkDetected);
const controller = useApp();
const {t} = useTranslation('WelcomeScreen');
const authService = appService.children.get('auth');
const isAppSetupComplete = useSelector(authService, selectAppSetupComplete);
const [isOverlayVisible, setOverlayVisible] = useState(isQrLogin !== '');
const [isDeepLinkOverlayVisible, setDeepLinkOverlayVisible] = useState(isDeepLinkFlow);
useEffect(() => {
if (AppState.currentState === 'active') {
appService.send(APP_EVENTS.ACTIVE());
@@ -69,8 +69,8 @@ const AppLayoutWrapper: React.FC = () => {
}, []);
useEffect(() => {
setOverlayVisible(isQrLogin !== '');
}, [isQrLogin]);
setDeepLinkOverlayVisible(isDeepLinkFlow);
}, [isDeepLinkFlow]);
if (isDecryptError) {
DecryptErrorAlert(controller, t);
@@ -81,10 +81,12 @@ const AppLayoutWrapper: React.FC = () => {
<AppLayout />
<MessageOverlay
isVisible={isOverlayVisible && !isAppSetupComplete}
title={t('qrLoginOverlay.title')}
message={t('qrLoginOverlay.message')}
onButtonPress={() => {setOverlayVisible(false)}}
isVisible={isDeepLinkOverlayVisible && !isAppSetupComplete}
title={t('errors.appSetupIncomplete.title')}
message={t('errors.appSetupIncomplete.message')}
onButtonPress={() => {
setDeepLinkOverlayVisible(false);
}}
buttonText={t('common:ok')}
minHeight={'auto'}
/>
@@ -139,7 +141,7 @@ const AppLoadingWrapper: React.FC = () => {
const AppInitialization: React.FC = () => {
const {appService} = useContext(GlobalContext);
const hasFontsLoaded = useFont()
const hasFontsLoaded = useFont();
const isReady = useSelector(appService, selectIsReady);
const {t} = useTranslation('common');
@@ -153,9 +155,9 @@ const AppInitialization: React.FC = () => {
}, [i18n.language]);
return isReady && hasFontsLoaded ? (
<AppLayoutWrapper />
<AppLayoutWrapper />
) : (
<AppLoadingWrapper />
<AppLoadingWrapper />
);
};

View File

@@ -8,6 +8,7 @@ import {Header} from './Header';
import {Theme} from './styleUtils';
import testIDProps from '../../shared/commonUtil';
import {Modal} from './Modal';
import {isIOS} from '../../shared/constants';
export const Error: React.FC<ErrorProps> = props => {
const {t} = useTranslation('common');
@@ -22,6 +23,7 @@ export const Error: React.FC<ErrorProps> = props => {
alignActionsOnEnd = false,
title,
message,
additionalMessage,
helpText,
image,
goBack,
@@ -45,9 +47,12 @@ export const Error: React.FC<ErrorProps> = props => {
props.textButtonText === undefined &&
props.primaryButtonText === undefined
) {
const timeout = setTimeout(() => {
setTriggerExitFlow(true);
}, 2000);
const timeout = setTimeout(
() => {
setTriggerExitFlow(true);
},
isIOS() ? 4000 : 2000,
);
return () => clearTimeout(timeout);
}
@@ -77,6 +82,13 @@ export const Error: React.FC<ErrorProps> = props => {
<Text style={Theme.ErrorStyles.message} testID={`${testID}Message`}>
{message}
</Text>
{additionalMessage && (
<Text
style={Theme.ErrorStyles.additionalMessage}
testID={`${testID}AdditionalMessage`}>
{additionalMessage}
</Text>
)}
</View>
{!alignActionsOnEnd && (
<Fragment>
@@ -193,6 +205,7 @@ export interface ErrorProps {
alignActionsOnEnd?: boolean;
title: string;
message: string;
additionalMessage?: string;
helpText?: string;
image: React.ReactElement;
goBack?: () => void;

View File

@@ -1270,7 +1270,7 @@ export const DefaultTheme = {
letterSpacing: 0,
lineHeight: 17,
minHeight: 50,
}
},
}),
SectionLayoutStyles: StyleSheet.create({
headerContainer: {
@@ -1294,7 +1294,7 @@ export const DefaultTheme = {
backgroundColor: Colors.White,
borderBottomLeftRadius: 6,
borderBottomRightRadius: 6,
}
},
}),
TextEditOverlayStyles: StyleSheet.create({
overlay: {
@@ -1557,6 +1557,15 @@ export const DefaultTheme = {
marginHorizontal: 26,
color: Colors.mediumDarkGrey,
},
additionalMessage: {
color: Colors.Black,
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
lineHeight: 21,
paddingTop: 4,
textAlign: 'center',
marginBottom: 10,
},
}),
SetupLanguageScreenStyle: StyleSheet.create({
columnStyle: {

View File

@@ -1299,7 +1299,7 @@ export const PurpleTheme = {
backgroundColor: Colors.White,
borderBottomLeftRadius: 6,
borderBottomRightRadius: 6,
}
},
}),
TextEditOverlayStyles: StyleSheet.create({
overlay: {
@@ -1562,6 +1562,15 @@ export const PurpleTheme = {
marginHorizontal: 26,
color: Colors.mediumDarkGrey,
},
additionalMessage: {
color: Colors.Black,
fontFamily: 'Inter_600SemiBold',
fontSize: 18,
lineHeight: 21,
paddingTop: 4,
textAlign: 'center',
marginBottom: 10,
},
}),
SetupLanguageScreenStyle: StyleSheet.create({
columnStyle: {

View File

@@ -10,6 +10,9 @@
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
1E55C2072DB12044009DF38B /* IntentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E55C2062DB12044009DF38B /* IntentData.swift */; };
1E55C20B2DB120C2009DF38B /* RNDeepLinkIntentModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E55C20A2DB120C2009DF38B /* RNDeepLinkIntentModule.swift */; };
1E55C20D2DB120E7009DF38B /* RNDeepLinkIntentModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E55C20C2DB120E7009DF38B /* RNDeepLinkIntentModule.m */; };
1E6875E92CA554E80086D870 /* OpenID4VP in Frameworks */ = {isa = PBXBuildFile; productRef = 1E6875E82CA554E80086D870 /* OpenID4VP */; };
1E6875EB2CA554FD0086D870 /* RNOpenID4VPModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */; };
1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */; };
@@ -69,6 +72,9 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Inji/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Inji/main.m; sourceTree = "<group>"; };
1D23B9CD47CFD7F3C87D202F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Inji/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1E55C2062DB12044009DF38B /* IntentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentData.swift; sourceTree = "<group>"; };
1E55C20A2DB120C2009DF38B /* RNDeepLinkIntentModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNDeepLinkIntentModule.swift; sourceTree = "<group>"; };
1E55C20C2DB120E7009DF38B /* RNDeepLinkIntentModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNDeepLinkIntentModule.m; sourceTree = "<group>"; };
1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNOpenID4VPModule.m; sourceTree = "<group>"; };
1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNOpenID4VPModule.swift; sourceTree = "<group>"; };
1EED69F82DA913D30042EAFC /* IntentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentData.swift; sourceTree = "<group>"; };
@@ -552,6 +558,7 @@
files = (
1EED69FD2DA914D00042EAFC /* RNDeepLinkIntentModule.m in Sources */,
1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */,
1E55C20B2DB120C2009DF38B /* RNDeepLinkIntentModule.swift in Sources */,
9C48504F2C3E59B5002ECBD5 /* RNVersionModule.swift in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
9C0E86BB2BEE36C300E9F9F6 /* RNPixelpassModule.m in Sources */,

View File

@@ -34,6 +34,12 @@
<string>io.mosip.residentapp.inji</string>
</array>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>openid4vp</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>

View File

@@ -60,4 +60,4 @@ import Foundation
}
}
}
}

View File

@@ -6,4 +6,4 @@
RCT_EXTERN_METHOD(getDeepLinkIntentData:(NSString *)flowType resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(resetDeepLinkIntentData:(NSString *)flowType)
@end
@end

View File

@@ -21,4 +21,4 @@ class RNDeepLinkIntentModule: NSObject, RCTBridgeModule {
@objc static func requiresMainQueueSetup() -> Bool {
return false
}
}
}

View File

@@ -725,6 +725,7 @@
"accepted": {
"title": "تمت مشاركة المعرف بنجاح!",
"message": "لقد تمت مشاركة هويتك بنجاح.",
"additionalMessage": "يمكنك الآن العودة إلى Verifier.",
"home": "المنزل",
"history": "تاريخ"
},
@@ -884,11 +885,12 @@
"message": "يمكنك مشاركة رخصة قيادة متنقلة أو مستند واحد فقط في كل مرة. يرجى اختيار مستند واحد فقط للمتابعة."
}
},
"loaders": {
"loading": "ஏற்றுகிறது...",
"subTitle": {
"fetchingVerifiers": "பொருந்தக்கூடிய சரிபார்க்கக்கூடிய நற்சான்றிதழ்களின் பட்டியலைப் பெறுகிறது"
}
"additionalMessage": "يرجى العودة إلى تطبيق التحقق."
},
"loaders": {
"loading": "ஏற்றுகிறது...",
"subTitle": {
"fetchingVerifiers": "பொருந்தக்கூடிய சரிபார்க்கக்கூடிய நற்சான்றிதழ்களின் பட்டியலைப் பெறுகிறது"
}
},
"VerifyIdentityOverlay": {

View File

@@ -733,6 +733,7 @@
"accepted": {
"title": "ID shared successfully!",
"message": "Your ID has been successfully shared.",
"additionalMessage": "You can now go back to the Verifier.",
"home": "Home",
"history": "History"
},
@@ -826,7 +827,7 @@
"SendVcScreen": {
"reasonForSharing": "Reason for sharing (optional)",
"introTitle": "Requester",
"requestMessage":"is requesting your digital ID for the purpose",
"requestMessage": "is requesting your digital ID for the purpose",
"acceptRequest": "Share",
"acceptRequestAndVerify": "Share with Selfie",
"reject": "Reject",
@@ -896,11 +897,12 @@
"message": "You can share only one Mobile Driving License or document at a time. Please select just one to continue."
}
},
"loaders": {
"loading": "Loading...",
"subTitle": {
"fetchingVerifiers": "Fetching your list of matching verifiable credentials"
}
"additionalMessage": "Please go back to the verifier app."
},
"loaders": {
"loading": "Loading...",
"subTitle": {
"fetchingVerifiers": "Fetching your list of matching verifiable credentials"
}
},
"VerifyIdentityOverlay": {
@@ -991,6 +993,10 @@
"invalidateKeyError": {
"title": "App was Reset",
"message": "Due to the fingerprint / facial recognition update, app security was impacted, and downloaded cards were removed. Please download again."
},
"appSetupIncomplete": {
"title": "Setup incomplete",
"message": "Please finish the initial setup to proceed."
}
},
"qrLoginOverlay": {

View File

@@ -500,7 +500,7 @@
"pending": {
"title": "Nakabinbing Katayuan:",
"description": "Kasalukuyang nakabinbin ang pag-verify dahil sa mga teknikal na isyu."
},
},
"expired": {
"title": "Nag-expire na Katayuan:",
"description": "Nag-expire na ang kredensyal."
@@ -728,6 +728,7 @@
"accepted": {
"title": "Matagumpay na naibahagi ang ID!",
"message": "Ang iyong ID ay matagumpay na naibahagi",
"additionalMessage": "Maaari ka na ngayong bumalik sa Verifier.",
"home": "Bahay",
"history": "Kasaysayan"
},
@@ -887,11 +888,12 @@
"message": "Maaari ka lamang magbahagi ng isang Mobile Driving License o dokumento sa bawat pagkakataon. Mangyaring pumili lamang ng isa upang magpatuloy."
}
},
"loaders": {
"loading": "Naglo-load...",
"subTitle": {
"fetchingVerifiers": "Kinukuha ang iyong listahan ng mga tumutugmang nabe-verify na kredensyal"
}
"additionalMessage": "Mangyaring bumalik sa verifier app."
},
"loaders": {
"loading": "Naglo-load...",
"subTitle": {
"fetchingVerifiers": "Kinukuha ang iyong listahan ng mga tumutugmang nabe-verify na kredensyal"
}
},
"VerifyIdentityOverlay": {

View File

@@ -161,7 +161,7 @@
"shared": "साझा",
"received": "प्राप्त",
"deleted": "हटाए गए",
"historyHeaderLabel":"इतिहास"
"historyHeaderLabel": "इतिहास"
},
"SettingScreen": {
"header": "समायोजन",
@@ -502,7 +502,7 @@
"pending": {
"title": "लंबित स्थिति:",
"description": "तकनीकी समस्याओं के कारण सत्यापन फिलहाल लंबित है।"
},
},
"expired": {
"title": "समाप्त स्थिति:",
"description": "क्रेडेंशियल समाप्त हो गया है।"
@@ -731,6 +731,7 @@
"accepted": {
"title": "आईडी सफलतापूर्वक साझा की गई!",
"message": "आपकी आईडी सफलतापूर्वक साझा कर दी गई है.",
"additionalMessage": "अब आप सत्यापनकर्ता के पास वापस जा सकते हैं।",
"home": "होम",
"history": "इतिहास"
},
@@ -820,7 +821,7 @@
"SendVcScreen": {
"reasonForSharing": "साझा करने का कारण (वैकल्पिक)",
"introTitle": "अनुरोधकर्ता",
"requestMessage":"आपके डिजिटल आईडी के लिए अनुरोध कर रहा है",
"requestMessage": "आपके डिजिटल आईडी के लिए अनुरोध कर रहा है",
"acceptRequest": "शेयर करना",
"acceptRequestAndVerify": "सेल्फी के साथ शेयर करें",
"consentToPhotoVerification": "मैं सत्यापन के लिए अपना फोटो लेने की अनुमति देता हूं",
@@ -890,11 +891,12 @@
"message": "आप एक समय में केवल एक मोबाइल ड्राइविंग लाइसेंस या दस्तावेज़ साझा कर सकते हैं। कृपया जारी रखने के लिए केवल एक का चयन करें।"
}
},
"loaders": {
"loading": "लोड हो रहा है...",
"subTitle": {
"fetchingVerifiers": "मेल खाने वाले सत्यापन योग्य क्रेडेंशियल्स की आपकी सूची लाई जा रह है"
}
"additionalMessage": "कृपया सत्यापनकर्ता ऐप पर वापस जाएं।"
},
"loaders": {
"loading": "लोड हो रह है...",
"subTitle": {
"fetchingVerifiers": "मेल खाने वाले सत्यापन योग्य क्रेडेंशियल्स की आपकी सूची लाई जा रही है"
}
},
"VerifyIdentityOverlay": {

View File

@@ -729,6 +729,7 @@
"accepted": {
"title": "ಐಡಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ!",
"message": "ನಿಮ್ಮ ಐಡಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.",
"additionalMessage": "ನೀವು ಈಗ ಪರಿಶೀಲಕಕ್ಕೆ ಹಿಂತಿರುಗಬಹುದು.",
"home": "ಮನೆ",
"history": "ಇತಿಹಾಸ"
},
@@ -888,11 +889,12 @@
"message": "ಒಂದು ವೇಳೆ ಒಂದು ಮೊಬೈಲ್ ಡ್ರೈವಿಂಗ್ ಲೈಸೆನ್ಸ್ ಅಥವಾ ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ಮಾತ್ರ ಹಂಚಬಹುದು. ಮುಂದುವರಿಸಲು ಒಂದು ಡಾಕ್ಯುಮೆಂಟ್ ಅನ್ನು ಮಾತ್ರ ಆಯ್ಕೆಮಾಡಿ."
}
},
"loaders": {
"loading": "ಲೋಡ್ ಆಗುತ್ತಿದೆ...",
"subTitle": {
"fetchingVerifiers": "ನಿಮ್ಮ ಹೊಂದಾಣಿಕೆಯ ಪರಿಶೀಲಿಸಬಹುದಾದ ರುಜುವಾತುಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆಯಲಾಗುತ್ತಿದೆ"
}
"additionalMessage": "ದಯವಿಟ್ಟು ಪರಿಶೀಲನಾ ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಹಿಂತಿರುಗಿ."
},
"loaders": {
"loading": "ಲೋಡ್ ಆಗುತ್ತಿದೆ...",
"subTitle": {
"fetchingVerifiers": "ನಿಮ್ಮ ಹೊಂದಾಣಿಕೆಯ ಪರಿಶೀಲಿಸಬಹುದಾದ ರುಜುವಾತುಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆಯಲಾಗುತ್ತಿದೆ"
}
},
"VerifyIdentityOverlay": {

View File

@@ -729,6 +729,7 @@
"accepted": {
"title": "ஐடி வெற்றிகரமாகப் பகிரப்பட்டது!",
"message": "உங்கள் ஐடி வெற்றிகரமாகப் பகிரப்பட்டது.",
"additionalMessage": "நீங்கள் இப்போது சரிபார்ப்பாளருக்குத் திரும்பலாம்.",
"home": "வீடு",
"history": "வரலாறு"
},
@@ -818,7 +819,7 @@
"SendVcScreen": {
"reasonForSharing": "பகிர்வதற்கான காரணம் (விரும்பினால்)",
"introTitle": "கோரியவர்",
"requestMessage":"உங்கள் டிஜிட்டல் ஐடியை கோருகிறார்",
"requestMessage": "உங்கள் டிஜிட்டல் ஐடியை கோருகிறார்",
"acceptRequest": "பகிர்",
"acceptRequestAndVerify": "செல்ஃபியுடன் பகிரவும்",
"reject": "நிராகரிக்கவும்",
@@ -888,11 +889,12 @@
"message": "ஒரே நேரத்தில் ஒரு மொபைல் டிரைவிங் லைசன்ஸ் அல்லது ஆவணத்தை மட்டுமே பகிர முடியும். தொடர ஒரு ஆவணத்தை மட்டும் தேர்ந்தெடுக்கவும்."
}
},
"loaders": {
"loading": "ஏற்றுகிறது...",
"subTitle": {
"fetchingVerifiers": "பொருந்தக்கூடிய சரிபார்க்கக்கூடிய நற்சான்றிதழ்களின் பட்டியலைப் பெறுகிறது"
}
"additionalMessage": "சரிபார்ப்பு பயன்பாட்டிற்குத் திரும்பவும்."
},
"loaders": {
"loading": "ஏற்றுகிறது...",
"subTitle": {
"fetchingVerifiers": "பொருந்தக்கூடிய சரிபார்க்கக்கூடிய நற்சான்றிதழ்களின் பட்டியலைப் பெறுகிறது"
}
},
"VerifyIdentityOverlay": {

View File

@@ -570,3 +570,9 @@ export function selectIsLinkCode(state: State) {
export function selectAuthorizationRequest(state: State) {
return state.context.authorizationRequest;
}
export function selectIsDeepLinkDetected(state: State) {
return !!(
state.context.authorizationRequest !== '' || state.context.linkCode !== ''
);
}

View File

@@ -18,7 +18,7 @@ const model = createModel(
isOnboarding: true,
isInitialDownload: true,
isTourGuide: false,
appInitialSetupDone: false,
isAppSetupComplete: false,
},
{
events: {
@@ -66,7 +66,7 @@ export const authMachine = model.createMachine(
actions: 'setTourGuide',
},
BIOMETRIC_CANCELLED: {
target: 'init'
target: 'init',
},
},
states: {
@@ -81,9 +81,11 @@ export const authMachine = model.createMachine(
},
{target: 'savingDefaults'},
],
BIOMETRIC_CANCELLED: [{
target: 'init'
}],
BIOMETRIC_CANCELLED: [
{
target: 'init',
},
],
},
},
savingDefaults: {
@@ -124,11 +126,21 @@ export const authMachine = model.createMachine(
on: {
SETUP_PASSCODE: {
target: 'authorized',
actions: ['setPasscode', 'setLanguage', 'setAppSetupComplete', 'storeContext'],
},
actions: [
'setPasscode',
'setLanguage',
'setAppSetupComplete',
'storeContext',
],
},
SETUP_BIOMETRICS: {
target: 'authorized',
actions: ['setBiometrics', 'setLanguage', 'setAppSetupComplete', 'storeContext'],
actions: [
'setBiometrics',
'setLanguage',
'setAppSetupComplete',
'storeContext',
],
},
},
},
@@ -159,6 +171,10 @@ export const authMachine = model.createMachine(
},
{
actions: {
setAppSetupComplete: assign({
isAppSetupComplete: context => true,
}),
requestStoredContext: send(StoreEvents.GET('auth'), {
to: context => context.serviceRefs.store,
}),
@@ -194,10 +210,6 @@ export const authMachine = model.createMachine(
selectLanguage: context => true,
}),
setAppSetupComplete: assign({
appInitialSetupDone: context => true,
}),
setPasscodeSalt: assign({
passcodeSalt: (context, event) => {
return event.data as string;
@@ -256,10 +268,6 @@ export function selectPasscode(state: State) {
return state?.context?.passcode;
}
export function selectAppSetupComplete(state: State) {
return state.context.appInitialSetupDone;
}
export function selectPasscodeSalt(state: State) {
return state.context.passcodeSalt;
}
@@ -309,3 +317,7 @@ export function selectIsBiometricToggleFromSettings(state: State) {
}
return false;
}
export function selectAppSetupComplete(state: State) {
return state.context.isAppSetupComplete;
}

View File

@@ -63,6 +63,11 @@ export const ScanActions = (model: any) => {
resetLinkCode: model.assign({
linkcode: '',
}),
resetAuthorizationRequest: model.assign({
authorizationRequest: '',
}),
updateShowFaceAuthConsent: model.assign({
showFaceAuthConsent: (_, event) => {
return event.response || event.response === null;
@@ -104,7 +109,7 @@ export const ScanActions = (model: any) => {
sendVPScanData: context =>
context.OpenId4VPRef.send({
type: 'AUTHENTICATE',
encodedAuthRequest: context.linkCode,
encodedAuthRequest: context.authorizationRequest,
flowType: context.openID4VPFlowType,
selectedVC: context.selectedVc,
isOVPViaDeepLink: context.isOVPViaDeepLink,
@@ -285,6 +290,12 @@ export const ScanActions = (model: any) => {
linkCode: (_, event) => event.linkCode,
}),
setAuthRequestFromDeepLink: assign({
authorizationRequest: (_, event) => {
return event.params ?? event.authorizationRequest;
},
}),
setIsQrLoginViaDeepLink: assign({
isQrLoginViaDeepLink: true,
}),

View File

@@ -38,6 +38,9 @@ export const ScanGuards = () => {
isQrLoginViaDeepLinking: context => context.isQrLoginViaDeepLink === true,
isOVPViaDeepLink: context => context.isOVPViaDeepLink === true,
isFlowTypeDeepLink: context =>
context.isOVPViaDeepLink || context.isQrLoginViaDeepLink,
isFlowTypeMiniViewShareWithSelfie: context =>
context.flowType === VCShareFlowType.MINI_VIEW_SHARE_WITH_SELFIE,

View File

@@ -49,8 +49,12 @@ export const scanMachine =
actions: [
'removeLoggers',
'resetFlowType',
'resetOpenID4VPFlowType',
'resetSelectedVc',
'resetIsQrLoginViaDeepLink',
'resetIsOVPViaDeepLink',
'resetAuthorizationRequest',
'resetLinkCode',
],
target: '.checkStorage',
},
@@ -75,7 +79,7 @@ export const scanMachine =
},
OVP_VIA_DEEP_LINK: {
actions: [
'setLinkCodeFromDeepLink',
'setAuthRequestFromDeepLink',
'setIsOVPViaDeepLink',
'setOpenId4VPFlowType',
],
@@ -110,10 +114,6 @@ export const scanMachine =
cond: 'isMinimumStorageRequiredForAuditEntryReached',
target: 'restrictSharingVc',
},
{
cond: 'isOVPViaDeepLink',
target: '#scan.checkFaceAuthConsent',
},
{
target: 'startPermissionCheck',
},
@@ -125,6 +125,10 @@ export const scanMachine =
startPermissionCheck: {
on: {
START_PERMISSION_CHECK: [
{
cond: 'isFlowTypeDeepLink',
target: '#scan.checkFaceAuthConsent',
},
{
cond: 'uptoAndroid11',
target: '#scan.checkBluetoothPermission',
@@ -340,6 +344,7 @@ export const scanMachine =
},
{
cond: 'isOVPViaDeepLink',
actions: ['setOpenId4VPFlowType'],
target: '#scan.startVPSharing',
},
{
@@ -373,7 +378,7 @@ export const scanMachine =
{
target: 'startVPSharing',
cond: 'isOnlineSharing',
actions: ['setOpenId4VPFlowType', 'setLinkCode'],
actions: ['setOpenId4VPFlowType', 'setAuthRequestFromDeepLink'],
},
{
target: 'decodeQuickShareData',

View File

@@ -61,7 +61,7 @@ const ScanEvents = {
IN_PROGRESS: () => ({}),
TIMEOUT: () => ({}),
QRLOGIN_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}),
OVP_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}),
OVP_VIA_DEEP_LINK: (authorizationRequest: string) => ({authorizationRequest}),
};
export const ScanModel = createModel(

View File

@@ -15,7 +15,7 @@ import {SvgImage} from '../../components/ui/svg';
import {View, I18nManager} from 'react-native';
import {Text} from './../../components/ui';
import {BannerStatusType} from '../../components/BannerNotification';
import {LIVENESS_CHECK} from '../../shared/constants';
import {isIOS, LIVENESS_CHECK} from '../../shared/constants';
import {SendVPScreen} from './SendVPScreen';
const ScanStack = createNativeStackNavigator();
@@ -107,21 +107,7 @@ export const ScanLayout: React.FC = () => {
}}
/>
)}
<ScanStack.Screen
name={SCAN_ROUTES.ScanScreen}
component={ScanScreen}
options={{
title: t('MainLayout:share'),
headerTitle: props => (
<View style={Theme.Styles.scanLayoutHeaderContainer}>
<Text style={Theme.Styles.scanLayoutHeaderTitle}>
{props.children}
</Text>
</View>
),
}}
/>
{controller.openID4VPFlowType === VCShareFlowType.OPENID4VP && (
{controller.openID4VPFlowType === VCShareFlowType.OPENID4VP ? (
<ScanStack.Screen
name={SCAN_ROUTES.SendVPScreen}
component={SendVPScreen}
@@ -158,6 +144,21 @@ export const ScanLayout: React.FC = () => {
),
}}
/>
) : (
<ScanStack.Screen
name={SCAN_ROUTES.ScanScreen}
component={ScanScreen}
options={{
title: t('MainLayout:share'),
headerTitle: props => (
<View style={Theme.Styles.scanLayoutHeaderContainer}>
<Text style={Theme.Styles.scanLayoutHeaderTitle}>
{props.children}
</Text>
</View>
),
}}
/>
)}
</ScanStack.Navigator>
@@ -169,6 +170,11 @@ export const ScanLayout: React.FC = () => {
}
title={t('status.accepted.title')}
message={t('status.accepted.message')}
additionalMessage={
controller.isOVPViaDeepLink && isIOS()
? t('status.accepted.additionalMessage')
: ''
}
image={SvgImage.SuccessLogo()}
goToHome={controller.GOTO_HOME}
goToHistory={controller.GOTO_HISTORY}

View File

@@ -307,9 +307,11 @@ export function useScanLayout() {
if (linkCode != '') {
scanService.send(ScanEvents.QRLOGIN_VIA_DEEP_LINK(linkCode));
appService.send(APP_EVENTS.RESET_LINKCODE());
} else if (authorizationRequest != '' && shareableVcsMetadata.length) {
} else if (authorizationRequest != '') {
scanService.send(ScanEvents.OVP_VIA_DEEP_LINK(authorizationRequest));
appService.send(APP_EVENTS.RESET_AUTHORIZATION_REQUEST());
if (shareableVcsMetadata.length !== 0) {
appService.send(APP_EVENTS.RESET_AUTHORIZATION_REQUEST());
}
} else if (isQrLoginDoneViaDeeplink) {
changeTabBarVisible('flex');
navigation.navigate(BOTTOM_TAB_ROUTES.home);

View File

@@ -29,7 +29,6 @@ import {VerifyIdentityOverlay} from '../VerifyIdentityOverlay';
import {VCShareFlowType} from '../../shared/Utils';
import {APP_EVENTS} from '../../machines/app';
import {GlobalContext} from '../../shared/GlobalContext';
import {OpenID4VP} from '../../shared/openID4VP/OpenID4VP';
export const ScanScreen: React.FC = () => {
const {t} = useTranslation('ScanScreen');
@@ -61,11 +60,16 @@ export const ScanScreen: React.FC = () => {
// TODO(kludge): skip running this hook on every render
useEffect(() => {
if (
scanScreenController.isStartPermissionCheck &&
!scanScreenController.isNoSharableVCs
)
scanScreenController.START_PERMISSION_CHECK();
if (scanScreenController.isStartPermissionCheck) {
if (
scanScreenController.authorizationRequest !== '' &&
scanScreenController.isNoSharableVCs
) {
scanScreenController.START_PERMISSION_CHECK();
} else if (!scanScreenController.isNoSharableVCs) {
scanScreenController.START_PERMISSION_CHECK();
}
}
});
useEffect(() => {
@@ -75,20 +79,14 @@ export const ScanScreen: React.FC = () => {
useEffect(() => {
if (
scanScreenController.isNoSharableVCs &&
scanScreenController.authorizationRequest != ''
) {
scanScreenController.linkcode !== ''
)
setTimeout(() => {
OpenID4VP.initialize();
OpenID4VP.sendErrorToVerifier(OVP_ERROR_MESSAGES.NO_MATCHING_VCS);
BackHandler.exitApp();
scanScreenController.GOTO_HOME();
appService.send(APP_EVENTS.RESET_AUTHORIZATION_REQUEST());
appService.send(APP_EVENTS.RESET_LINKCODE());
BackHandler.exitApp();
}, 2000);
}
}, [
scanScreenController.isNoSharableVCs,
scanScreenController.authorizationRequest,
]);
}, [scanScreenController.isNoSharableVCs, scanScreenController.linkcode]);
const openSettings = () => {
Linking.openSettings();
@@ -202,7 +200,10 @@ export const ScanScreen: React.FC = () => {
}
function loadQRScanner() {
if (scanScreenController.isNoSharableVCs) {
if (
scanScreenController.isNoSharableVCs &&
scanScreenController.authorizationRequest === ''
) {
return noShareableVcText();
}
if (scanScreenController.selectIsInvalid) {
@@ -274,6 +275,22 @@ export const ScanScreen: React.FC = () => {
);
}
const getPrimaryButtonText = () => {
if (
sendVPScreenController.errorModal.showRetryButton &&
sendVPScreenController.openID4VPRetryCount < 3
) {
return t('ScanScreen:status.retry');
}
return undefined;
};
const getTextButtonText = () => {
return sendVPScreenController.isOVPViaDeepLink
? undefined
: t('ScanScreen:status.accepted.home');
};
const faceVerificationController = sendVPScreenController.flowType.startsWith(
'OpenID4VP',
)
@@ -353,19 +370,10 @@ export const ScanScreen: React.FC = () => {
message={sendVPScreenController.errorModal.message}
image={SvgImage.PermissionDenied()}
primaryButtonTestID={'retry'}
primaryButtonText={
sendVPScreenController.errorModal.showRetryButton &&
sendVPScreenController.openID4VPRetryCount < 3
? t('ScanScreen:status.retry')
: undefined
}
primaryButtonText={getPrimaryButtonText()}
primaryButtonEvent={sendVPScreenController.RETRY}
textButtonTestID={'home'}
textButtonText={
sendVPScreenController.isOVPViaDeepLink
? undefined
: t('ScanScreen:status.accepted.home')
}
textButtonText={getTextButtonText()}
textButtonEvent={handleTextButtonEvent}
customImageStyles={{paddingBottom: 0, marginBottom: -6}}
customStyles={{marginTop: '30%'}}

View File

@@ -27,7 +27,7 @@ import {selectIsMinimumStorageRequiredForAuditEntryLimitReached} from '../../mac
import {BOTTOM_TAB_ROUTES} from '../../routes/routesConstants';
import {MainBottomTabParamList} from '../../routes/routeTypes';
import {useNavigation, NavigationProp} from '@react-navigation/native';
import {selectAuthorizationRequest} from '../../machines/app';
import {selectAuthorizationRequest, selectIsLinkCode} from '../../machines/app';
export function useScanScreen() {
const {t} = useTranslation('ScanScreen');
@@ -74,7 +74,10 @@ export function useScanScreen() {
}
type ScanScreenNavigation = NavigationProp<MainBottomTabParamList>;
const navigation = useNavigation<ScanScreenNavigation>();
const GOTO_HOME = () => navigation.navigate(BOTTOM_TAB_ROUTES.home);
const GOTO_HOME = () => {
scanService.send(ScanEvents.RESET());
navigation.navigate(BOTTOM_TAB_ROUTES.home);
};
const ALLOWED = () => scanService.send(ScanEvents.ALLOWED());
const DENIED = () => scanService.send(ScanEvents.DENIED());
const isLocalPermissionRational = useSelector(
@@ -117,5 +120,6 @@ export function useScanScreen() {
DENIED,
isLocalPermissionRational,
authorizationRequest: useSelector(appService, selectAuthorizationRequest),
linkcode: useSelector(appService, selectIsLinkCode),
};
}

View File

@@ -1,11 +1,15 @@
import {useFocusEffect} from '@react-navigation/native';
import React, {useEffect, useLayoutEffect, useState} from 'react';
import React, {useContext, useEffect, useLayoutEffect, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {BackHandler, I18nManager, View} from 'react-native';
import {Button, Column, Row, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {VcItemContainer} from '../../components/VC/VcItemContainer';
import {LIVENESS_CHECK, OVP_ERROR_MESSAGES} from '../../shared/constants';
import {
isIOS,
LIVENESS_CHECK,
OVP_ERROR_MESSAGES,
} from '../../shared/constants';
import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
import {
getImpressionEventData,
@@ -24,19 +28,29 @@ import {Loader} from '../../components/ui/Loader';
import {Icon} from 'react-native-elements';
import {ScanLayoutProps} from '../../routes/routeTypes';
import {OpenID4VP} from '../../shared/openID4VP/OpenID4VP';
import {GlobalContext} from '../../shared/GlobalContext';
import {APP_EVENTS} from '../../machines/app';
import {useScanScreen} from './ScanScreenController';
export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
const {t} = useTranslation('SendVPScreen');
const controller = useSendVPScreen();
const scanScreenController = useScanScreen();
const vcsMatchingAuthRequest = controller.vcsMatchingAuthRequest;
const {appService} = useContext(GlobalContext);
const [triggerExitFlow, setTriggerExitFlow] = useState(false);
useEffect(() => {
if (controller.errorModal.show && controller.isOVPViaDeepLink) {
const timeout = setTimeout(() => {
setTriggerExitFlow(true);
}, 2000);
const timeout = setTimeout(
() => {
OpenID4VP.sendErrorToVerifier(OVP_ERROR_MESSAGES.NO_MATCHING_VCS);
setTriggerExitFlow(true);
},
isIOS() ? 4000 : 2000,
);
return () => clearTimeout(timeout);
}
@@ -44,7 +58,11 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
useEffect(() => {
if (triggerExitFlow) {
controller.RESET_LOGGED_ERROR();
controller.GO_TO_HOME();
controller.RESET_RETRY_COUNT();
appService.send(APP_EVENTS.RESET_AUTHORIZATION_REQUEST());
setTriggerExitFlow(false);
BackHandler.exitApp();
}
}, [triggerExitFlow]);
@@ -71,6 +89,19 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
}, []),
);
useEffect(() => {
if (scanScreenController.isStartPermissionCheck) {
if (
scanScreenController.authorizationRequest !== '' &&
scanScreenController.isNoSharableVCs
) {
scanScreenController.START_PERMISSION_CHECK();
} else if (!scanScreenController.isNoSharableVCs) {
scanScreenController.START_PERMISSION_CHECK();
}
}
});
const handleDismiss = () => {
if (controller.isOVPViaDeepLink) {
controller.GO_TO_HOME();
@@ -91,6 +122,19 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
}
};
const getAdditionalMessage = () => {
if (
controller.isOVPViaDeepLink &&
!(
controller.errorModal.showRetryButton &&
controller.openID4VPRetryCount < 3
)
) {
return controller.errorModal.additionalMessage;
}
return undefined;
};
useLayoutEffect(() => {
if (controller.showLoadingScreen) {
props.navigation.setOptions({
@@ -163,6 +207,19 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
return controller.overlayDetails?.primaryButtonEvent;
};
const getPrimaryButtonText = () => {
return controller.errorModal.showRetryButton &&
controller.openID4VPRetryCount < 3
? t('ScanScreen:status.retry')
: undefined;
};
const getTextButtonText = () => {
return controller.isOVPViaDeepLink
? undefined
: t('ScanScreen:status.accepted.home');
};
const getVcKey = vcData => {
return VCMetadata.fromVcMetadataString(vcData.vcMetadata).getVcKey();
};
@@ -354,21 +411,13 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
isVisible={controller.errorModal.show}
title={controller.errorModal.title}
message={controller.errorModal.message}
additionalMessage={getAdditionalMessage()}
image={SvgImage.PermissionDenied()}
primaryButtonTestID={'retry'}
primaryButtonText={
controller.errorModal.showRetryButton &&
controller.openID4VPRetryCount < 3
? t('ScanScreen:status.retry')
: undefined
}
primaryButtonText={getPrimaryButtonText()}
primaryButtonEvent={controller.RETRY}
textButtonTestID={'home'}
textButtonText={
!controller.isOVPViaDeepLink
? t('ScanScreen:status.accepted.home')
: undefined
}
textButtonText={getTextButtonText()}
textButtonEvent={handleTextButtonEvent}
customImageStyles={{paddingBottom: 0, marginBottom: -6}}
customStyles={{marginTop: '30%'}}

View File

@@ -1,6 +1,6 @@
import {NavigationProp, useNavigation} from '@react-navigation/native';
import {useSelector} from '@xstate/react';
import {useContext, useState} from 'react';
import {useContext, useEffect, useRef, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {ActorRefFrom} from 'xstate';
import {Theme} from '../../components/ui/styleUtils';
@@ -44,6 +44,8 @@ import {VCMetadata} from '../../shared/VCMetadata';
import {VPShareOverlayProps} from './VPShareOverlay';
import {ActivityLogEvents} from '../../machines/activityLog';
import {VPShareActivityLog} from '../../components/VPShareActivityLogEvent';
import {SelectedCredentialsForVPSharing} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import {isIOS} from '../../shared/constants';
type MyVcsTabNavigation = NavigationProp<RootRouteProps>;
@@ -62,6 +64,9 @@ export function useSendVPScreen() {
const [selectedVCKeys, setSelectedVCKeys] = useState<Record<string, string>>(
{},
);
const hasLoggedErrorRef = useRef(false);
const shareableVcs = useSelector(vcMetaService, selectShareableVcs);
const myVcs = useSelector(vcMetaService, selectMyVcs);
@@ -135,12 +140,19 @@ export function useSendVPScreen() {
selectOpenID4VPRetryCount,
);
const noCredentialsMatchingVPRequest =
isSelectingVCs && Object.keys(vcsMatchingAuthRequest).length === 0;
let errorModal = {
show: error !== '' || noCredentialsMatchingVPRequest,
title: '',
message: '',
showRetryButton: false,
isSelectingVCs &&
(Object.keys(vcsMatchingAuthRequest).length === 0 ||
Object.values(vcsMatchingAuthRequest).every(
value => Array.isArray(value) && value.length === 0,
));
const isOVPViaDeepLink = useSelector(
openID4VPService,
selectIsOVPViaDeeplink,
);
const getAdditionalMessage = () => {
return isOVPViaDeepLink && isIOS() ? t('errors.additionalMessage') : '';
};
function generateAndStoreLogMessage(logType: string, errorInfo?: string) {
@@ -158,6 +170,15 @@ export function useSendVPScreen() {
openID4VPService,
selectRequestedClaimsByVerifier,
);
const [errorModal, setErrorModalData] = useState({
show: false,
title: '',
message: '',
additionalMessage: '',
showRetryButton: false,
});
const claimsAsString = '[' + requestedClaimsByVerifier + ']';
if (noCredentialsMatchingVPRequest) {
errorModal.title = t('errors.noMatchingCredentials.title');
@@ -196,7 +217,7 @@ export function useSendVPScreen() {
errorModal.title = t('errors.duplicateMdocCredential.title');
errorModal.message = t('errors.duplicateMdocCredential.message');
errorModal.showRetryButton = false;
}else if (error.startsWith('send vp')) {
} else if (error.startsWith('send vp')) {
errorModal.title = t('errors.genericError.title');
errorModal.message = t('errors.genericError.message');
errorModal.showRetryButton = true;
@@ -206,6 +227,102 @@ export function useSendVPScreen() {
generateAndStoreLogMessage('TECHNICAL_ERROR');
}
useEffect(() => {
if (noCredentialsMatchingVPRequest && !hasLoggedErrorRef.current) {
setErrorModalData({
show: true,
title: t('errors.noMatchingCredentials.title'),
message: t('errors.noMatchingCredentials.message', {
claims: claimsAsString,
}),
additionalMessage: getAdditionalMessage(),
showRetryButton: false,
});
generateAndStoreLogMessage(
'NO_CREDENTIAL_MATCHING_REQUEST',
claimsAsString,
);
hasLoggedErrorRef.current = true;
} else if (
(error.includes('Verifier authentication was unsuccessful') ||
error.startsWith('api error')) &&
!hasLoggedErrorRef.current
) {
setErrorModalData({
show: true,
title: t('errors.invalidVerifier.title'),
message: t('errors.invalidVerifier.message'),
additionalMessage: getAdditionalMessage(),
showRetryButton: false,
});
generateAndStoreLogMessage('VERIFIER_AUTHENTICATION_FAILED');
hasLoggedErrorRef.current = true;
} else if (
error.includes('credential mismatch detected') &&
!hasLoggedErrorRef.current
) {
setErrorModalData({
show: true,
title: t('errors.credentialsMismatch.title'),
message: t('errors.credentialsMismatch.message', {
claims: claimsAsString,
}),
additionalMessage: getAdditionalMessage(),
showRetryButton: false,
});
generateAndStoreLogMessage(
'CREDENTIAL_MISMATCH_FROM_KEBAB',
claimsAsString,
);
hasLoggedErrorRef.current = true;
} else if (
error.includes('none of the selected VC has image') &&
!hasLoggedErrorRef.current
) {
setErrorModalData({
show: true,
title: t('errors.noImage.title'),
message: t('errors.noImage.message'),
additionalMessage: getAdditionalMessage(),
showRetryButton: false,
});
generateAndStoreLogMessage('NO_SELECTED_VC_HAS_IMAGE');
hasLoggedErrorRef.current = true;
} else if (
error.startsWith('vc validation') &&
!hasLoggedErrorRef.current
) {
setErrorModalData({
show: true,
title: t('errors.invalidQrCode.title'),
message: t('errors.invalidQrCode.message'),
additionalMessage: getAdditionalMessage(),
showRetryButton: false,
});
generateAndStoreLogMessage('INVALID_AUTH_REQUEST');
hasLoggedErrorRef.current = true;
} else if (error.startsWith('send vp') && !hasLoggedErrorRef.current) {
setErrorModalData({
show: true,
title: t('errors.genericError.title'),
message: t('errors.genericError.message'),
additionalMessage: getAdditionalMessage(),
showRetryButton: true,
});
hasLoggedErrorRef.current = true;
} else if (error !== '' && !hasLoggedErrorRef.current) {
setErrorModalData({
show: true,
title: t('errors.genericError.title'),
message: t('errors.genericError.message'),
additionalMessage: getAdditionalMessage(),
showRetryButton: false,
});
generateAndStoreLogMessage('TECHNICAL_ERROR');
hasLoggedErrorRef.current = true;
}
}, [error, noCredentialsMatchingVPRequest]);
let overlayDetails: Omit<VPShareOverlayProps, 'isVisible'> | null = null;
let vpVerifierName = useSelector(
openID4VPService,
@@ -256,6 +373,16 @@ export function useSendVPScreen() {
getSelectedVCs,
errorModal,
overlayDetails,
RESET_LOGGED_ERROR: () => {
hasLoggedErrorRef.current = false;
setErrorModalData({
show: false,
title: '',
message: '',
additionalMessage: '',
showRetryButton: false,
});
},
scanScreenError: useSelector(scanService, selectIsSendingVPError),
vcsMatchingAuthRequest,
userSelectedVCs: useSelector(openID4VPService, selectSelectedVCs),
@@ -272,7 +399,7 @@ export function useSendVPScreen() {
openID4VPService,
selectIsFaceVerificationConsent,
),
isOVPViaDeepLink: useSelector(openID4VPService, selectIsOVPViaDeeplink),
isOVPViaDeepLink,
credentials: useSelector(openID4VPService, selectCredentials),
verifiableCredentialsData: useSelector(
openID4VPService,
@@ -290,9 +417,9 @@ export function useSendVPScreen() {
RETRY_VERIFICATION: () =>
openID4VPService.send(OpenID4VPEvents.RETRY_VERIFICATION()),
GO_TO_HOME: () => {
navigation.navigate(BOTTOM_TAB_ROUTES.home, {screen: 'HomeScreen'});
scanService.send(ScanEvents.DISMISS());
openID4VPService.send(OpenID4VPEvents.RESET_ERROR());
scanService.send(ScanEvents.RESET());
navigation.navigate(BOTTOM_TAB_ROUTES.home, {screen: 'HomeScreen'});
changeTabBarVisible('flex');
},
SELECT_VC_ITEM:

View File

@@ -6,6 +6,7 @@ import {Pressable, Dimensions, BackHandler} from 'react-native';
import {Button, Column, Row, Text} from '../../components/ui';
import testIDProps from '../../shared/commonUtil';
import {SvgImage} from '../../components/ui/svg';
import {isIOS} from '../../shared/constants';
export const SharingStatusModal: React.FC<SharingStatusModalProps> = props => {
const {t} = useTranslation('ScanScreen');
@@ -18,9 +19,12 @@ export const SharingStatusModal: React.FC<SharingStatusModalProps> = props => {
let timeoutId: NodeJS.Timeout | undefined;
if (props.isVisible && props.buttonStatus === 'none') {
timeoutId = setTimeout(() => {
resetAndExit();
}, 2000);
timeoutId = setTimeout(
() => {
resetAndExit();
},
isIOS() ? 4000 : 2000,
);
}
return () => {
if (timeoutId) {
@@ -54,6 +58,13 @@ export const SharingStatusModal: React.FC<SharingStatusModalProps> = props => {
color={Theme.Colors.statusMessage}>
{props.message}
</Text>
<Text
testID="sharingStatusAdditionalMessage"
margin="20 0"
style={Theme.TextStyles.bold}
size={'large'}>
{props.additionalMessage}
</Text>
</Column>
{props.buttonStatus === 'homeAndHistoryIcons' ? (
<Row
@@ -119,6 +130,7 @@ interface SharingStatusModalProps {
buttonStatus?: 'homeAndHistoryIcons' | 'none';
title: String;
message: String;
additionalMessage?: String;
image: React.ReactElement;
gradientButtonTitle?: String;
clearButtonTitle?: String;

View File

@@ -9,8 +9,8 @@ export const walletMetadata = {
],
},
mso_mdoc: {
alg_values_supported: ['ES256']
}
alg_values_supported: ['ES256'],
},
},
client_id_schemes_supported: ['redirect_uri', 'did', 'pre-registered'],
request_object_signing_alg_values_supported: ['EdDSA'],