From 0fe6915bb81250ee1be93b6dd926407faceeea8c Mon Sep 17 00:00:00 2001 From: balachandarg-tw Date: Tue, 22 Jul 2025 15:42:05 +0530 Subject: [PATCH] [INJIMO-3192]: Integrate swift error response enhancement (#2001) * [INJIMOB-3192]: Native module integration for OVP error handling Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Fixing iOS build failure Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Refactoring code to move useEffect from SendVPScreenController to UI component Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Updating useOvpErrorModal while resolving merge conflicts Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Update iOs package dependencies to develop for OVP library Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Update history error messages Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Update the user declined history message Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Remove moduleClassName and use existing method Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Fix for retry scenario error messages Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Fix for sendErrorToVerifier crash Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Add Log for decline scenario Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Fix for decline crash issue Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Common method for reject Exceptions Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Modify general exception for retry scenario Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Remove comments Signed-off-by: balachandarg-tw * [INJIMOB-3192]: Update package resolved Signed-off-by: balachandarg-tw --------- Signed-off-by: balachandarg-tw --- .talismanrc | 5 +- .../residentapp/InjiOpenID4VPModule.java | 33 ++- ios/Inji.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/swiftpm/Package.resolved | 31 ++- ios/RNOpenID4VPModule.m | 1 + ios/RNOpenID4VPModule.swift | 57 +++-- locales/ara.json | 13 +- locales/en.json | 12 +- locales/fil.json | 13 +- locales/hin.json | 13 +- locales/kan.json | 12 +- locales/tam.json | 12 +- machines/openID4VP/openID4VPActions.ts | 4 +- screens/Scan/ScanScreen.tsx | 19 +- screens/Scan/SendVPScreen.tsx | 40 +-- screens/Scan/SendVPScreenController.ts | 230 ++---------------- shared/hooks/useOvpErrorModal.ts | 193 +++++++++++++++ 17 files changed, 401 insertions(+), 295 deletions(-) create mode 100644 shared/hooks/useOvpErrorModal.ts diff --git a/.talismanrc b/.talismanrc index 8fadf897..55162bbd 100644 --- a/.talismanrc +++ b/.talismanrc @@ -341,7 +341,7 @@ fileignoreconfig: - filename: android/app/src/main/java/io/mosip/residentapp/InjiVciClientModule.java checksum: 17f55840bab193bc353034445ba4fce53e1ce466e95f616c15a1351f8d2f23bc - filename: ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved - checksum: 785f2a3390ffd7b93974f449132008c7c1b8b6d1e8deaba7b1d0737be1b5f2f4 + checksum: 81d8398a4f4b052c3fb10d92f0e0af398d09095d9f882cc35335c43630c6c371 - filename: injitest/src/main/resources/Vids.json checksum: 8bcffed7a6dd565ae695e1b29de0655e10bd5c5420af2718defd593a687b8817 - filename: injitest/src/main/java/inji/utils/UpdateNetworkSettings.java @@ -398,7 +398,8 @@ fileignoreconfig: checksum: 6600a3d75033af4d702dd8c9663e12ad7c2c096a529bac2771bb856cc75a5ed0 - filename: machines/openID4VP/openID4VPModel.ts checksum: 5d1ed430f84852d6c85bc439c47641cfb5b19cbd1a03faf8918429685db51e07 - version: "" + - filename: shared/hooks/useOvpErrorModal.ts + checksum: dc83cf9d688fda587cfb10e738b966694794181f233dd1b6f5cf3f818dae412d - filename: shared/openID4VP/OpenID4VPHelper.ts checksum: 2ab5f935ea3d1ec4d109d8614c2246f40e284594288566338f185611470e6928 - filename: components/VC/common/VCUtils.tsx diff --git a/android/app/src/main/java/io/mosip/residentapp/InjiOpenID4VPModule.java b/android/app/src/main/java/io/mosip/residentapp/InjiOpenID4VPModule.java index 5bea6afa..1b01e693 100644 --- a/android/app/src/main/java/io/mosip/residentapp/InjiOpenID4VPModule.java +++ b/android/app/src/main/java/io/mosip/residentapp/InjiOpenID4VPModule.java @@ -99,12 +99,7 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule { String authRequestJson = gson.toJson(authRequest, AuthorizationRequest.class); promise.resolve(authRequestJson); } catch (Exception e) { - if (e instanceof OpenID4VPExceptions) { - OpenID4VPExceptions ex = (OpenID4VPExceptions) e; - promise.reject(ex.getErrorCode(), ex.getMessage()); - } else { - promise.reject(e); - } + rejectWithOpenID4VPExceptions(e, promise); } } @@ -115,12 +110,7 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule { Map vpTokens = openID4VP.constructUnsignedVPToken(selectedVCsMap, holderId, signatureSuite); promise.resolve(toJsonString(vpTokens)); } catch (Exception e) { - if (e instanceof OpenID4VPExceptions) { - OpenID4VPExceptions ex = (OpenID4VPExceptions) e; - promise.reject(ex.getErrorCode(), ex.getMessage()); - } else { - promise.reject(e); - } + rejectWithOpenID4VPExceptions(e, promise); } } @@ -131,12 +121,17 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule { String response = openID4VP.shareVerifiablePresentation(authContainer); promise.resolve(response); } catch (Exception e) { - if (e instanceof OpenID4VPExceptions) { - OpenID4VPExceptions ex = (OpenID4VPExceptions) e; - promise.reject(ex.getErrorCode(), ex.getMessage()); - } else { - promise.reject(e); - } + rejectWithOpenID4VPExceptions(e, promise); + } + } + + @ReactMethod + private static void rejectWithOpenID4VPExceptions(Exception e, Promise promise) { + if (e instanceof OpenID4VPExceptions) { + OpenID4VPExceptions ex = (OpenID4VPExceptions) e; + promise.reject(ex.getErrorCode(), ex.getMessage(), ex); + } else { + promise.reject("ERR_UNKNOWN", e.getMessage(), e); } } @@ -349,7 +344,7 @@ public class InjiOpenID4VPModule extends ReactContextBaseJavaModule { } else if (MSO_MDOC.getValue().equals(formatStr)) { return MSO_MDOC; } - throw new UnsupportedOperationException("Credential format not supported: " + formatStr); + throw new UnsupportedOperationException("Credential format '" + formatStr + "' is not supported"); } private List convertReadableArrayToList(ReadableArray readableArray) { diff --git a/ios/Inji.xcodeproj/project.pbxproj b/ios/Inji.xcodeproj/project.pbxproj index 31cbd916..b5207f2f 100644 --- a/ios/Inji.xcodeproj/project.pbxproj +++ b/ios/Inji.xcodeproj/project.pbxproj @@ -641,11 +641,11 @@ CODE_SIGN_ENTITLEMENTS = Inji/InjiRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = V2ABX7953Z; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = V2ABX7953Z; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; INFOPLIST_FILE = Inji/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -663,7 +663,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.mosip.inji.wallet.mobileid; PRODUCT_NAME = Inji; PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.mosip.inji.wallet.mobileid"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.mosip.inji.wallet.mobileid"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.mosip.inji.wallet.mobileid"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -701,7 +701,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CXX = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5bb6825e..393f1ad8 100644 --- a/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,6 +19,24 @@ "version" : "1.1.0" } }, + { + "identity" : "base58swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/keefertaylor/Base58Swift.git", + "state" : { + "revision" : "ce8ba8aa893f7e209e9c99c35ee61d6f1235881d", + "version" : "2.1.7" + } + }, + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/BigInt.git", + "state" : { + "revision" : "018a5925f60f9e0523edd261de394a0898fe95b7", + "version" : "3.1.0" + } + }, { "identity" : "crcswift", "kind" : "remoteSourceControl", @@ -52,7 +70,7 @@ "location" : "https://github.com/mosip/inji-openid4vp-ios-swift", "state" : { "branch" : "develop", - "revision" : "879995bc2b858b0d1c6265c2d38d81f60b4e4611" + "revision" : "b93f78c94cd7632f2a80e869520788abd1378621" } }, { @@ -100,6 +118,15 @@ "version" : "0.3.0" } }, + { + "identity" : "siphash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/SipHash", + "state" : { + "revision" : "e325083424688055363bbfcb7f1a440d7d7a1bae", + "version" : "1.2.2" + } + }, { "identity" : "swiftcbor", "kind" : "remoteSourceControl", @@ -128,5 +155,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/ios/RNOpenID4VPModule.m b/ios/RNOpenID4VPModule.m index 98cdc787..d6157a3e 100644 --- a/ios/RNOpenID4VPModule.m +++ b/ios/RNOpenID4VPModule.m @@ -25,6 +25,7 @@ RCT_EXTERN_METHOD(shareVerifiablePresentation:(id)vpResponseMetadata rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(sendErrorToVerifier:(NSString *)error + :(NSString *)errorCode resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) diff --git a/ios/RNOpenID4VPModule.swift b/ios/RNOpenID4VPModule.swift index 762eb1a1..2f8a5f38 100644 --- a/ios/RNOpenID4VPModule.swift +++ b/ios/RNOpenID4VPModule.swift @@ -7,6 +7,7 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { private var openID4VP: OpenID4VP? + static func moduleName() -> String { return "InjiOpenID4VP" } @@ -21,7 +22,7 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { reject("OPENID4VP", error.localizedDescription, error) } } - + @objc func authenticateVerifier(_ urlEncodedAuthorizationRequest: String, trustedVerifierJSON: AnyObject, @@ -52,7 +53,8 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { let response = try toJsonString(jsonObject: authenticationResponse) resolve(response) } catch { - reject("OPENID4VP", error.localizedDescription, error) + rejectWithOpenID4VPError(error, reject: reject) + } } } @@ -100,7 +102,7 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { reject("ERROR", "Failed to serialize JSON", nil) } } catch { - reject("OPENID4VP", error.localizedDescription, error) + rejectWithOpenID4VPError(error, reject: reject) } } } @@ -143,7 +145,8 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { formattedVPTokenSigningResults[.mso_mdoc] = MdocVPTokenSigningResult(docTypeToDeviceAuthentication: docTypeToDeviceAuthentication) default: - reject("OPENID4VP", "Credential format not supported", nil) + let error = NSError(domain: "Credential format '\(credentialFormat)' is not supported", code: 0) + rejectWithOpenID4VPError(error, reject: reject) return } } @@ -151,23 +154,32 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { let response = try await openID4VP?.shareVerifiablePresentation(vpTokenSigningResults: formattedVPTokenSigningResults) resolve(response) } catch { - reject("OPENID4VP", error.localizedDescription, error) + rejectWithOpenID4VPError(error, reject: reject) } } } - @objc - func sendErrorToVerifier(_ error: String, _ errorCode: String, - resolver resolve: @escaping RCTPromiseResolveBlock, - rejecter reject: @escaping RCTPromiseRejectBlock) { +@objc +func sendErrorToVerifier(_ error: String, _ errorCode: String, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock) { Task { - enum VerifierError: Error { - case customError(String) - } - await openID4VP?.sendErrorToVerifier(error: VerifierError.customError(error)) - resolve(true) + let exception: OpenID4VPException = { + switch errorCode { + case OpenID4VPErrorCodes.accessDenied: + return AccessDenied(message: error, className: Self.moduleName()) + case OpenID4VPErrorCodes.invalidTransactionData: + return InvalidTransactionData(message: error, className: Self.moduleName()) + default: + return GenericFailure(message: error, className: Self.moduleName()) + } + }() + + await openID4VP?.sendErrorToVerifier(error: exception) + resolve(true) } - } +} + func toJsonString(jsonObject: AuthorizationRequest) throws -> String { let encoder = JSONEncoder() @@ -183,6 +195,17 @@ class RNOpenId4VpModule: NSObject, RCTBridgeModule { static func requiresMainQueueSetup() -> Bool { return true } + + func rejectWithOpenID4VPError(_ error: Error, reject: RCTPromiseRejectBlock) { + if let openidError = error as? OpenID4VPException { + reject(openidError.errorCode, openidError.message, openidError) + } else { + let nsError = NSError(domain: error.localizedDescription, code: 0) + reject("ERR_UNKNOWN", nsError.localizedDescription, nsError) + } + } + + } struct EncodableWrapper: Encodable { @@ -207,7 +230,7 @@ func getWalletMetadataFromDict(_ walletMetadata: Any, reject("OPENID4VP", "Invalid wallet metadata format", nil) throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid Wallet Metadata"]) } - + var vpFormatsSupported: [FormatType: VPFormatSupported] = [:] if let vpFormatsSupportedDict = metadata["vp_formats_supported"] as? [String: Any], let ldpVcDict = vpFormatsSupportedDict["ldp_vc"] as? [String: Any] { @@ -220,7 +243,7 @@ func getWalletMetadataFromDict(_ walletMetadata: Any, } else { vpFormatsSupported[.ldp_vc] = VPFormatSupported(algValuesSupported: nil) } - + let walletMetadataObject = try WalletMetadata( presentationDefinitionURISupported: metadata["presentation_definition_uri_supported"] as? Bool, vpFormatsSupported: vpFormatsSupported, diff --git a/locales/ara.json b/locales/ara.json index eeebf667..c5516698 100644 --- a/locales/ara.json +++ b/locales/ara.json @@ -19,8 +19,7 @@ "SHARED_SUCCESSFULLY": "تمت مشاركة العرض التقديمي لبيانات الاعتماد بنجاح.", "SHARED_WITH_FACE_VERIFIACTION": "تم التحقق من الوجه بنجاح، وتمت مشاركة العرض التقديمي لبيانات الاعتماد بنجاح.", "VERIFIER_AUTHENTICATION_FAILED": "فشلت مصادقة خدمة التحقق أثناء مشاركة VP.", - "INVALID_AUTH_REQUEST": "يحتوي رمز الاستجابة السريعة على بيانات غير صالحة أو مفقودة. تحقق مع خدمة التحقق.", - "USER_DECLINED_CONSENT": "رفض المستخدم الموافقة على مشاركة العرض التقديمي لبيانات الاعتماد.", + "USER_DECLINED_CONSENT": "رفض المستخدم منح الموافقة لمشاركة عرض بيانات الاعتماد.", "SHARED_AFTER_RETRY": "تمت مشاركة العرض التقديمي لبيانات الاعتماد بنجاح بعد إعادة المحاولة.", "SHARED_WITH_FACE_VERIFICATION_AFTER_RETRY": "تم التحقق من الوجه بنجاح، وتمت مشاركة العرض التقديمي لبيانات الاعتماد بنجاح بعد إعادة المحاولة.", "SHARING_FAILED": "فشلت مشاركة العرض التقديمي لبيانات الاعتماد بسبب خطأ فني.", @@ -31,7 +30,13 @@ "NO_SELECTED_VC_HAS_IMAGE": "فشلت مشاركة العرض التقديمي لبيانات الاعتماد لأن بيانات الاعتماد المحددة التي يمكن التحقق منها تفتقر إلى صورة الوجه.", "CREDENTIAL_MISMATCH_FROM_KEBAB": "حدث عدم تطابق في العرض التقديمي لبيانات الاعتماد أثناء المشاركة من القائمة، بناءً على المطالبات المطلوبة: {{info}}.", "NO_CREDENTIAL_MATCHING_REQUEST": "لم يتم العثور على بيانات اعتماد تطابق طلب خدمة التحقق بناءً على المطالبات المقدمة: {{info}}.", - "TECHNICAL_ERROR": "تعذر متابعة مشاركة العرض التقديمي لبيانات الاعتماد بسبب خطأ فني." + "TECHNICAL_ERROR": "حدث خطأ أثناء معالجة طلبك.", + "INVALID_REQUEST_URI_METHOD": "لم تتم مشاركة الاعتماد — استخدم المُحقق طريقة غير مدعومة.", + "INVALID_AUTH_REQUEST": "فشل مشاركة الاعتماد — الطلب يحتوي على معلومات مفقودة أو غير صحيحة.", + "REQUEST_COULD_NOT_BE_PROCESSED": "لم تتم مشاركة الاعتماد — تعذر معالجة الطلب بسبب مشكلة فنية.", + "INVALID_PRESENTATION_DEFINITION_URI": "لم تتم مشاركة الاعتماد — تحتوي طلب المحقق على تنسيق بيانات غير صحيح.", + "SEND_VP_ERROR": "فشلت مشاركة الاعتماد بسبب مشكلة فنية." + } }, "DeviceInfoList": { @@ -901,7 +906,7 @@ }, "sendVPError": { "title": "حدث خطأ ما", - "message": "الرسالة: لم نتمكن من مشاركة بطاقتك بسبب مشكلة فنية. انقر فوق إعادة المحاولة للمحاولة مرة أخرى، أو انتقل إلى الصفحة الرئيسية للخروج." + "message": "حدثت مشكلة فنية أثناء مشاركة بطاقتك. انقر على \"إعادة المحاولة\" للمحاولة مرة أخرى أو ارجع إلى \"الرئيسية\"." }, "noImage": { "title": "حدث خطأ!", diff --git a/locales/en.json b/locales/en.json index 353cd9f8..4e79e0f5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,8 +19,7 @@ "SHARED_SUCCESSFULLY": "Credential Presentation is shared successfully.", "SHARED_WITH_FACE_VERIFIACTION": "Face verification is successful, and the Credential Presentation is shared successfully.", "VERIFIER_AUTHENTICATION_FAILED": "Failed to authenticate the Verification Service during VP sharing.", - "INVALID_AUTH_REQUEST": "QR code has invalid or missing data. Check with the verification service.", - "USER_DECLINED_CONSENT": "User declined consent for Credential Presentation sharing.", + "USER_DECLINED_CONSENT": "User declined consent for credential presentation sharing.", "SHARED_AFTER_RETRY": "Credential Presentation is shared successfully after a retry.", "SHARED_WITH_FACE_VERIFICATION_AFTER_RETRY": "Face verification is successful, and the Credential Presentation is shared successfully after a retry.", "SHARING_FAILED": "Credential Presentation sharing failed due to technical error.", @@ -31,7 +30,12 @@ "NO_SELECTED_VC_HAS_IMAGE": "Credential Presentation sharing failed as the selected verifiable credentials lack a face image.", "CREDENTIAL_MISMATCH_FROM_KEBAB": "Credentials Presentation mismatch occurred during sharing from the menu, based on requested claims: {{info}}.", "NO_CREDENTIAL_MATCHING_REQUEST": "No credentials were found that match the verification service's request based on the provided claims: {{info}}.", - "TECHNICAL_ERROR": "Credential Presentation sharing could not proceed due to technical error." + "TECHNICAL_ERROR": "Something went wrong while processing your request.", + "INVALID_REQUEST_URI_METHOD": "Credential not shared— The verifier used a method that isn’t supported.", + "INVALID_AUTH_REQUEST" : "Credential sharing failed — The request had missing or incorrect information.", + "REQUEST_COULD_NOT_BE_PROCESSED" : "Credential not shared — Request couldn’t be processed due to a technical issue.", + "INVALID_PRESENTATION_DEFINITION_URI": "Credential not shared — The verifier’s request had incorrect data format.", + "SEND_VP_ERROR": "Credential sharing failed due to a technical issue." } }, "DeviceInfoList": { @@ -914,7 +918,7 @@ }, "sendVPError": { "title": "Something Went Wrong", - "message": "Message: We couldn't share your card due to a technical issue. Click Retry to try again, or go to Home to exit." + "message": "There was a technical issue while sharing your card. Tap \"Retry\" to try again or go back to \"Home\"." }, "noImage": { "title": "An Error Occured!", diff --git a/locales/fil.json b/locales/fil.json index 6ad0c50f..1b10a3f9 100644 --- a/locales/fil.json +++ b/locales/fil.json @@ -19,8 +19,7 @@ "SHARED_SUCCESSFULLY": "Matagumpay na naibahagi ang Pagtatanghal ng Kredensyal.", "SHARED_WITH_FACE_VERIFIACTION": "Matagumpay ang pag-verify sa mukha, at matagumpay na naibahagi ang Credential Presentation.", "VERIFIER_AUTHENTICATION_FAILED": "Nabigong i-authenticate ang Serbisyo sa Pag-verify sa panahon ng pagbabahagi ng VP.", - "INVALID_AUTH_REQUEST": "Ang QR code ay may di-wasto o nawawalang data. Tingnan sa serbisyo ng pag-verify.", - "USER_DECLINED_CONSENT": "Tinanggihan ng user ang pahintulot para sa pagbabahagi ng Credential Presentation.", + "USER_DECLINED_CONSENT": "பயனர் சான்றுகள் பகிர்வுக்கு ஒப்புதலை நிராகரித்தார்.", "SHARED_AFTER_RETRY": "Ang Pagtatanghal ng Kredensyal ay matagumpay na naibahagi pagkatapos ng muling pagsubok.", "SHARED_WITH_FACE_VERIFICATION_AFTER_RETRY": "Matagumpay ang pag-verify sa mukha, at matagumpay na naibahagi ang Credential Presentation pagkatapos ng muling pagsubok.", "SHARING_FAILED": "Nabigo ang pagbabahagi ng Credential Presentation dahil sa teknikal na error.", @@ -31,7 +30,13 @@ "NO_SELECTED_VC_HAS_IMAGE": "Nabigo ang pagbabahagi ng Pagtatanghal ng Kredensyal dahil walang larawan ng mukha ang mga napiling nabe-verify na kredensyal.", "CREDENTIAL_MISMATCH_FROM_KEBAB": "Ang hindi pagkakatugma ng Pagtatanghal ng Mga Kredensyal ay naganap sa panahon ng pagbabahagi mula sa menu, batay sa mga hiniling na claim: {{info}}.", "NO_CREDENTIAL_MATCHING_REQUEST": "Walang nakitang mga kredensyal na tumutugma sa kahilingan ng serbisyo sa pag-verify batay sa mga ibinigay na claim: {{info}}.", - "TECHNICAL_ERROR": "Hindi matuloy ang pagbabahagi ng Kredensyal na Presentasyon dahil sa teknikal na error." + "TECHNICAL_ERROR": "May nangyaring mali habang pinoproseso ang iyong kahilingan.", + "INVALID_REQUEST_URI_METHOD": "Hindi naibahagi ang kredensyal — Gumamit ang verifier ng pamamaraang hindi suportado.", + "INVALID_AUTH_REQUEST": "Nabigong maibahagi ang kredensyal — May kulang o maling impormasyon sa kahilingan.", + "REQUEST_COULD_NOT_BE_PROCESSED": "Hindi naibahagi ang kredensyal — Hindi naproseso ang kahilingan dahil sa teknikal na isyu.", + "INVALID_PRESENTATION_DEFINITION_URI": "Hindi naibahagi ang kredensyal — Mali ang format ng datos sa kahilingan ng verifier.", + "SEND_VP_ERROR": "Nabigong maibahagi ang kredensyal dahil sa teknikal na isyu." + } }, "DeviceInfoList": { @@ -904,7 +909,7 @@ }, "sendVPError": { "title": "May Nangyaring Mali", - "message": "Mensahe: Hindi namin naibahagi ang iyong card dahil sa isang teknikal na isyu. I-click ang Retry para subukang muli, o pumunta sa Home upang lumabas." + "message": "Nagkaroon ng teknikal na problema habang ibinabahagi ang iyong card. Pindutin ang \"Subukang Muli\" para subukang muli o bumalik sa \"Home\"." }, "noImage": { "title": "Isang Error ang Naganap!", diff --git a/locales/hin.json b/locales/hin.json index 875156cb..1d8a0a31 100644 --- a/locales/hin.json +++ b/locales/hin.json @@ -19,8 +19,7 @@ "SHARED_SUCCESSFULLY": "क्रेडेंशियल प्रेजेंटेशन सफलतापूर्वक साझा किया गया है.", "SHARED_WITH_FACE_VERIFIACTION": "चेहरा सत्यापन सफल है, और क्रेडेंशियल प्रस्तुति सफलतापूर्वक साझा की गई है।", "VERIFIER_AUTHENTICATION_FAILED": "वीपी साझाकरण के दौरान सत्यापन सेवा को प्रमाणित करने में विफल।", - "INVALID_AUTH_REQUEST": "QR कोड में अमान्य या अनुपलब्ध डेटा है. सत्यापन सेवा से जांचें.", - "USER_DECLINED_CONSENT": "उपयोगकर्ता ने क्रेडेंशियल प्रेजेंटेशन साझा करने के लिए सहमति देने से इनकार कर दिया।", + "USER_DECLINED_CONSENT": "उपयोगकर्ता ने क्रेडेंशियल प्रस्तुति साझा करने के लिए सहमति अस्वीकार कर दी।", "SHARED_AFTER_RETRY": "पुन: प्रयास के बाद क्रेडेंशियल प्रस्तुति सफलतापूर्वक साझा की जाती है।", "SHARED_WITH_FACE_VERIFICATION_AFTER_RETRY": "चेहरे का सत्यापन सफल है, और पुन: प्रयास के बाद क्रेडेंशियल प्रस्तुति सफलतापूर्वक साझा की जाती है।", "SHARING_FAILED": "तकनीकी त्रुटि के कारण क्रेडेंशियल प्रेजेंटेशन साझा करना विफल रहा।", @@ -31,7 +30,13 @@ "NO_SELECTED_VC_HAS_IMAGE": "क्रेडेंशियल प्रस्तुति साझाकरण विफल रहा क्योंकि चयनित सत्यापन योग्य क्रेडेंशियल में चेहरे की छवि का अभाव है।", "CREDENTIAL_MISMATCH_FROM_KEBAB": "अनुरोधित दावों के आधार पर, मेनू से साझा करने के दौरान क्रेडेंशियल प्रस्तुतिकरण बेमेल हुआ: {{info}}।", "NO_CREDENTIAL_MATCHING_REQUEST": "प्रदान किए गए दावों के आधार पर सत्यापन सेवा के अनुरोध से मेल खाने वाला कोई क्रेडेंशियल नहीं मिला: {{info}}।", - "TECHNICAL_ERROR": "तकनीकी त्रुटि के कारण क्रेडेंशियल प्रेजेंटेशन साझाकरण आगे नहीं बढ़ सका।" + "TECHNICAL_ERROR": "आपके अनुरोध को प्रोसेस करते समय कुछ गलत हो गया।", + "INVALID_REQUEST_URI_METHOD": "क्रेडेंशियल साझा नहीं किया गया — सत्यापनकर्ता ने एक असमर्थित विधि का उपयोग किया।", + "INVALID_AUTH_REQUEST": "क्रेडेंशियल साझा विफल रहा — अनुरोध में जानकारी अधूरी या गलत थी।", + "REQUEST_COULD_NOT_BE_PROCESSED": "क्रेडेंशियल साझा नहीं किया गया — तकनीकी समस्या के कारण अनुरोध प्रोसेस नहीं हो सका।", + "INVALID_PRESENTATION_DEFINITION_URI": "क्रेडेंशियल साझा नहीं किया गया — सत्यापनकर्ता की अनुरोध में गलत डेटा प्रारूप था।", + "SEND_VP_ERROR": "तकनीकी समस्या के कारण क्रेडेंशियल साझा करना विफल रहा।" + } }, "DeviceInfoList": { @@ -907,7 +912,7 @@ }, "sendVPError": { "title": "कुछ गलत हो गया", - "message": "संदेश: तकनीकी समस्या के कारण हम आपका कार्ड साझा नहीं कर सके। पुनः प्रयास करने के लिए 'पुनः प्रयास करें' पर क्लिक करें, या बाहर निकलने के लिए 'मुखपृष्ठ' पर जाएँ।" + "message": "आपका कार्ड साझा करते समय एक तकनीकी समस्या हुई। फिर से प्रयास करने के लिए \"पुनः प्रयास करें\" टैप करें या \"होम\" पर वापस जाएं।" }, "noImage": { "title": "एक त्रुटि हुई!", diff --git a/locales/kan.json b/locales/kan.json index 376a60d5..08ba57e1 100644 --- a/locales/kan.json +++ b/locales/kan.json @@ -19,8 +19,7 @@ "SHARED_SUCCESSFULLY": "ರುಜುವಾತು ಪ್ರಸ್ತುತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.", "SHARED_WITH_FACE_VERIFIACTION": "ಮುಖ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ ಮತ್ತು ರುಜುವಾತು ಪ್ರಸ್ತುತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.", "VERIFIER_AUTHENTICATION_FAILED": "VP ಹಂಚಿಕೆಯ ಸಮಯದಲ್ಲಿ ಪರಿಶೀಲನೆ ಸೇವೆಯನ್ನು ದೃಢೀಕರಿಸಲು ವಿಫಲವಾಗಿದೆ.", - "INVALID_AUTH_REQUEST": "QR ಕೋಡ್ ಅಮಾನ್ಯ ಅಥವಾ ಕಾಣೆಯಾದ ಡೇಟಾವನ್ನು ಹೊಂದಿದೆ. ಪರಿಶೀಲನಾ ಸೇವೆಯೊಂದಿಗೆ ಪರಿಶೀಲಿಸಿ.", - "USER_DECLINED_CONSENT": "ರುಜುವಾತು ಪ್ರಸ್ತುತಿ ಹಂಚಿಕೆಗೆ ಬಳಕೆದಾರರು ಒಪ್ಪಿಗೆ ನಿರಾಕರಿಸಿದ್ದಾರೆ.", + "USER_DECLINED_CONSENT": "ಬಳಕೆದಾರರು ರುಜುವಾತು ಪ್ರಸ್ತುತಿ ಹಂಚಿಕೆಗೆ ಒಪ್ಪಿಗೆ ನೀಡಲಿಲ್ಲ.", "SHARED_AFTER_RETRY": "ಮರುಪ್ರಯತ್ನದ ನಂತರ ರುಜುವಾತು ಪ್ರಸ್ತುತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.", "SHARED_WITH_FACE_VERIFICATION_AFTER_RETRY": "ಮುಖ ಪರಿಶೀಲನೆ ಯಶಸ್ವಿಯಾಗಿದೆ ಮತ್ತು ಮರುಪ್ರಯತ್ನದ ನಂತರ ರುಜುವಾತು ಪ್ರಸ್ತುತಿಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಹಂಚಿಕೊಳ್ಳಲಾಗಿದೆ.", "SHARING_FAILED": "ತಾಂತ್ರಿಕ ದೋಷದಿಂದಾಗಿ ರುಜುವಾತು ಪ್ರಸ್ತುತಿ ಹಂಚಿಕೆ ವಿಫಲವಾಗಿದೆ.", @@ -31,7 +30,12 @@ "NO_SELECTED_VC_HAS_IMAGE": "ಆಯ್ಕೆಮಾಡಿದ ಪರಿಶೀಲಿಸಬಹುದಾದ ರುಜುವಾತುಗಳಲ್ಲಿ ಮುಖದ ಚಿತ್ರದ ಕೊರತೆಯಿಂದಾಗಿ ರುಜುವಾತು ಪ್ರಸ್ತುತಿ ಹಂಚಿಕೆ ವಿಫಲವಾಗಿದೆ.", "CREDENTIAL_MISMATCH_FROM_KEBAB": "ವಿನಂತಿಸಿದ ಹಕ್ಕುಗಳ ಆಧಾರದ ಮೇಲೆ ಮೆನುವಿನಿಂದ ಹಂಚಿಕೊಳ್ಳುವಾಗ ರುಜುವಾತುಗಳ ಪ್ರಸ್ತುತಿ ಹೊಂದಿಕೆಯಾಗುತ್ತಿಲ್ಲ: {{info}}.", "NO_CREDENTIAL_MATCHING_REQUEST": "ಒದಗಿಸಿದ ಹಕ್ಕುಗಳ ಆಧಾರದ ಮೇಲೆ ಪರಿಶೀಲನೆ ಸೇವೆಯ ವಿನಂತಿಗೆ ಹೊಂದಿಕೆಯಾಗುವ ಯಾವುದೇ ರುಜುವಾತುಗಳು ಕಂಡುಬಂದಿಲ್ಲ: {{info}}.", - "TECHNICAL_ERROR": "ತಾಂತ್ರಿಕ ದೋಷದಿಂದಾಗಿ ರುಜುವಾತು ಪ್ರಸ್ತುತಿ ಹಂಚಿಕೆಯನ್ನು ಮುಂದುವರಿಸಲಾಗಲಿಲ್ಲ." + "TECHNICAL_ERROR": "ನಿಮ್ಮ ವಿನಂತಿಯನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುವಾಗ ಏನೋ ತಪ್ಪು ಸಂಭವಿಸಿದೆ.", + "INVALID_REQUEST_URI_METHOD": "ಅಧికారಪತ್ರವನ್ನು ಹಂಚಲಾಗಿಲ್ಲ — ಪರಿಶೀಲಕರೊಬ್ಬರು ಬೆಂಬಲವಿಲ್ಲದ ವಿಧಾನವನ್ನು ಬಳಸಿದರು.", + "INVALID_AUTH_REQUEST": "ಅಧికారಪತ್ರ ಹಂಚಿಕೆಯಲ್ಲಿ ವಿಫಲವಾಯಿತು — ವಿನಂತಿಯಲ್ಲಿ ತಪ್ಪು ಅಥವಾ ಕೊರತೆಯ ಮಾಹಿತಿ ಇದೆ.", + "REQUEST_COULD_NOT_BE_PROCESSED": "ಅಧಕಾರಪತ್ರವನ್ನು ಹಂಚಲಾಗಿಲ್ಲ — ತಾಂತ್ರಿಕ ತೊಂದರೆಯಿಂದ ವಿನಂತಿಯನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "INVALID_PRESENTATION_DEFINITION_URI": "ಅಧಕಾರಪತ್ರವನ್ನು ಹಂಚಲಾಗಿಲ್ಲ — ಪರಿಶೀಲಕರ ವಿನಂತಿಯಲ್ಲಿ ತಪ್ಪಾದ ಡೇಟಾ ಸ್ವರೂಪವಿದೆ.", + "SEND_VP_ERROR": "ತಾಂತ್ರಿಕ ಸಮಸ್ಯೆಯ ಕಾರಣದಿಂದ ಅಧಿಕಾರಪತ್ರ ಹಂಚಿಕೆಯಲ್ಲಿ ವಿಫಲವಾಯಿತು." } }, "DeviceInfoList": { @@ -905,7 +909,7 @@ }, "sendVPError": { "title": "ಏನೋ ತಪ್ಪು ಸಂಭವಿಸಿದೆ", - "message": "ಸಂದೇಶ: ತಾಂತ್ರಿಕ ತೊಂದರೆಯಿಂದಾಗಿ ನಿಮ್ಮ ಕಾರ್ಡ್ ಅನ್ನು ಹಂಚಲಾಗಲಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು 'ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ' ಕ್ಲಿಕ್ ಮಾಡಿ ಅಥವಾ ನಿರ್ಗಮಿಸಲು 'ಮುಖಪುಟ' ಗೆ ಹೋಗಿ." + "message": "ನಿಮ್ಮ ಕಾರ್ಡ್ ಹಂಚಿಕೊಳ್ಳುವಾಗ ತಾಂತ್ರಿಕ ಸಮಸ್ಯೆ ಸಂಭವಿಸಿದೆ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು \"ಮರುಪ್ರಯತ್ನಿಸಿ\" ಒತ್ತಿರಿ ಅಥವಾ \"ಮುಖಪುಟ\" ಗೆ ಹಿಂದಿರುಗಿ." }, "noImage": { "title": "ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ!", diff --git a/locales/tam.json b/locales/tam.json index fc92a0f4..5df39e78 100644 --- a/locales/tam.json +++ b/locales/tam.json @@ -19,8 +19,7 @@ "SHARED_SUCCESSFULLY": "நற்சான்றிதழ் வழங்கல் வெற்றிகரமாகப் பகிரப்பட்டது.", "SHARED_WITH_FACE_VERIFIACTION": "முக சரிபார்ப்பு வெற்றிகரமாக உள்ளது, மேலும் நற்சான்றிதழ் விளக்கக்காட்சி வெற்றிகரமாக பகிரப்பட்டது.", "VERIFIER_AUTHENTICATION_FAILED": "VP பகிர்வின் போது சரிபார்ப்பு சேவையை அங்கீகரிப்பதில் தோல்வி.", - "INVALID_AUTH_REQUEST": "QR குறியீட்டில் தவறான அல்லது விடுபட்ட தரவு உள்ளது. சரிபார்ப்பு சேவையுடன் சரிபார்க்கவும்.", - "USER_DECLINED_CONSENT": "நற்சான்றிதழ் விளக்கக்காட்சி பகிர்வுக்கான ஒப்புதலை பயனர் நிராகரித்தார்.", + "USER_DECLINED_CONSENT": "பயனர் சான்றுகள் பகிர்வுக்கு ஒப்புதலை நிராகரித்தார்.", "SHARED_AFTER_RETRY": "நற்சான்றிதழ் விளக்கக்காட்சி மீண்டும் முயற்சித்த பிறகு வெற்றிகரமாகப் பகிரப்பட்டது.", "SHARED_WITH_FACE_VERIFICATION_AFTER_RETRY": "முகச் சரிபார்ப்பு வெற்றிகரமாக உள்ளது, மீண்டும் முயற்சித்த பிறகு நற்சான்றிதழ் விளக்கக்காட்சி வெற்றிகரமாகப் பகிரப்பட்டது.", "SHARING_FAILED": "தொழில்நுட்பப் பிழை காரணமாக நற்சான்றிதழ் வழங்கல் பகிர்வு தோல்வியடைந்தது.", @@ -31,7 +30,12 @@ "NO_SELECTED_VC_HAS_IMAGE": "தேர்ந்தெடுக்கப்பட்ட சரிபார்க்கக்கூடிய நற்சான்றிதழ்களில் முகப் படம் இல்லாததால் நற்சான்றிதழ் வழங்கல் பகிர்வு தோல்வியடைந்தது.", "CREDENTIAL_MISMATCH_FROM_KEBAB": "கோரப்பட்ட உரிமைகோரல்களின் அடிப்படையில், மெனுவிலிருந்து பகிரும் போது நற்சான்றிதழ்கள் வழங்கல் பொருந்தவில்லை: {{info}}.", "NO_CREDENTIAL_MATCHING_REQUEST": "வழங்கப்பட்ட உரிமைகோரல்களின் அடிப்படையில் சரிபார்ப்பு சேவையின் கோரிக்கையுடன் பொருந்தக்கூடிய நற்சான்றிதழ்கள் எதுவும் கண்டறியப்படவில்லை: {{info}}.", - "TECHNICAL_ERROR": "தொழில்நுட்ப பிழை காரணமாக நற்சான்றிதழ் வழங்கல் பகிர்வை தொடர முடியவில்லை." + "TECHNICAL_ERROR": "உங்கள் கோரிக்கையை செயலாக்கும் போது ஏதோ தவறு ஏற்பட்டது.", + "INVALID_REQUEST_URI_METHOD": "அங்கீகாரம் பகிரப்படவில்லை — சரிபார்ப்பவர் ஆதரிக்கப்படாத முறையை பயன்படுத்தினார்.", + "INVALID_AUTH_REQUEST": "அங்கீகாரம் பகிர்வு தோல்வியடைந்தது — கோரிக்கையில் தவறான அல்லது காணாமற்போன தகவல்கள் உள்ளன.", + "REQUEST_COULD_NOT_BE_PROCESSED": "அங்கீகாரம் பகிரப்படவில்லை — தொழில்நுட்ப பிரச்சனை காரணமாக கோரிக்கையை செயலாக்க முடியவில்லை.", + "INVALID_PRESENTATION_DEFINITION_URI": "அங்கீகாரம் பகிரப்படவில்லை — சரிபார்ப்பவரின் கோரிக்கையில் தவறான தரவுகள் உள்ளன.", + "SEND_VP_ERROR": "அங்கீகாரம் பகிர்வதில் தொழில்நுட்ப பிரச்சனை ஏற்பட்டது." } }, "DeviceInfoList": { @@ -905,7 +909,7 @@ }, "sendVPError": { "title": "ஏதோ தவறு நடந்துவிட்டது", - "message": "தகவல்: தொழில்நுட்ப சிக்கலால் உங்கள் அட்டையை பகிர முடியவில்லை. மீண்டும் முயற்சிக்க 'மீண்டும் முயற்சி' என்பதை கிளிக் செய்யவும், அல்லது வெளியேற 'முகப்பு' என்பதைக் கிளிக் செய்யவும்." + "message": "உங்கள் கார்டைப் பகிரும்போது தொழில்நுட்ப சிக்கல் ஏற்பட்டது. மீண்டும் முயற்சிக்க \"மீண்டும் முயற்சி செய்\" என்பதைத் தட்டவும் அல்லது \"முகப்பு\" பக்கம் திரும்பவும்." }, "noImage": { "title": "ஒரு பிழை ஏற்பட்டது!", diff --git a/machines/openID4VP/openID4VPActions.ts b/machines/openID4VP/openID4VPActions.ts index 37162ea9..e9b317d2 100644 --- a/machines/openID4VP/openID4VPActions.ts +++ b/machines/openID4VP/openID4VPActions.ts @@ -218,7 +218,7 @@ export const openID4VPActions = (model: any) => { ), shareDeclineStatus: () => { - OpenID4VP.getInstance().sendErrorToVerifier( + OpenID4VP.sendErrorToVerifier( OVP_ERROR_MESSAGES.DECLINED, OVP_ERROR_CODE.DECLINED, ); @@ -308,7 +308,7 @@ function getVcsMatchingAuthRequest(context, event) { } if (Object.keys(matchingVCs).length === 0) { - OpenID4VP.getInstance().sendErrorToVerifier( + OpenID4VP.sendErrorToVerifier( OVP_ERROR_MESSAGES.NO_MATCHING_VCS, OVP_ERROR_CODE.NO_MATCHING_VCS, ); diff --git a/screens/Scan/ScanScreen.tsx b/screens/Scan/ScanScreen.tsx index 5476d7c0..ba2fcbc8 100644 --- a/screens/Scan/ScanScreen.tsx +++ b/screens/Scan/ScanScreen.tsx @@ -29,16 +29,27 @@ import {VerifyIdentityOverlay} from '../VerifyIdentityOverlay'; import {VCShareFlowType} from '../../shared/Utils'; import {APP_EVENTS} from '../../machines/app'; import {GlobalContext} from '../../shared/GlobalContext'; +import {useOvpErrorModal} from '../../shared/hooks/useOvpErrorModal'; export const ScanScreen: React.FC = () => { const {t} = useTranslation('ScanScreen'); const scanScreenController = useScanScreen(); const sendVcScreenController = useSendVcScreen(); const sendVPScreenController = useSendVPScreen(); + const [errorModal] = useOvpErrorModal({ + error: sendVPScreenController.error, + noCredentialsMatchingVPRequest: + sendVPScreenController.noCredentialsMatchingVPRequest, + requestedClaimsByVerifier: sendVPScreenController.requestedClaimsByVerifier, + getAdditionalMessage: sendVPScreenController.getAdditionalMessage, + generateAndStoreLogMessage: + sendVPScreenController.generateAndStoreLogMessage, + t, + }); const [isBluetoothOn, setIsBluetoothOn] = useState(false); const showErrorModal = sendVPScreenController.scanScreenError || - (sendVPScreenController.errorModal.show && + (errorModal.show && (sendVPScreenController.flowType === VCShareFlowType.MINI_VIEW_SHARE_OPENID4VP || sendVPScreenController.flowType === @@ -277,7 +288,7 @@ export const ScanScreen: React.FC = () => { const getPrimaryButtonText = () => { if ( - sendVPScreenController.errorModal.showRetryButton && + errorModal.showRetryButton && sendVPScreenController.openID4VPRetryCount < 3 ) { return t('ScanScreen:status.retry'); @@ -366,8 +377,8 @@ export const ScanScreen: React.FC = () => { alignActionsOnEnd showClose={false} isVisible={showErrorModal} - title={sendVPScreenController.errorModal.title} - message={sendVPScreenController.errorModal.message} + title={errorModal.title} + message={errorModal.message} image={SvgImage.PermissionDenied()} primaryButtonTestID={'retry'} primaryButtonText={getPrimaryButtonText()} diff --git a/screens/Scan/SendVPScreen.tsx b/screens/Scan/SendVPScreen.tsx index 89facc5b..8a4f6030 100644 --- a/screens/Scan/SendVPScreen.tsx +++ b/screens/Scan/SendVPScreen.tsx @@ -32,19 +32,29 @@ import OpenID4VP from '../../shared/openID4VP/OpenID4VP'; import {GlobalContext} from '../../shared/GlobalContext'; import {APP_EVENTS} from '../../machines/app'; import {useScanScreen} from './ScanScreenController'; +import {useOvpErrorModal} from '../../shared/hooks/useOvpErrorModal'; export const SendVPScreen: React.FC = props => { const {t} = useTranslation('SendVPScreen'); const controller = useSendVPScreen(); const scanScreenController = useScanScreen(); + const [errorModal, resetErrorModal] = useOvpErrorModal({ + error: controller.error, + noCredentialsMatchingVPRequest: controller.noCredentialsMatchingVPRequest, + requestedClaimsByVerifier: controller.requestedClaimsByVerifier, + getAdditionalMessage: controller.getAdditionalMessage, + generateAndStoreLogMessage: controller.generateAndStoreLogMessage, + t, + }); + const vcsMatchingAuthRequest = controller.vcsMatchingAuthRequest; const {appService} = useContext(GlobalContext); const [triggerExitFlow, setTriggerExitFlow] = useState(false); useEffect(() => { - if (controller.errorModal.show && controller.isOVPViaDeepLink) { + if (errorModal.show && controller.isOVPViaDeepLink) { const timeout = setTimeout( () => { OpenID4VP.sendErrorToVerifier( @@ -58,11 +68,11 @@ export const SendVPScreen: React.FC = props => { return () => clearTimeout(timeout); } - }, [controller.errorModal.show, controller.isOVPViaDeepLink]); + }, [errorModal.show, controller.isOVPViaDeepLink]); useEffect(() => { if (triggerExitFlow) { - controller.RESET_LOGGED_ERROR(); + RESET_LOGGED_ERROR(); controller.GO_TO_HOME(); controller.RESET_RETRY_COUNT(); appService.send(APP_EVENTS.RESET_AUTHORIZATION_REQUEST()); @@ -106,11 +116,16 @@ export const SendVPScreen: React.FC = props => { } }); + const RESET_LOGGED_ERROR = () => { + resetErrorModal(); + }; + const handleDismiss = () => { OpenID4VP.sendErrorToVerifier( OVP_ERROR_MESSAGES.DECLINED, OVP_ERROR_CODE.DECLINED, ); + controller.generateAndStoreLogMessage('USER_DECLINED_CONSENT'); if (controller.isOVPViaDeepLink) { controller.GO_TO_HOME(); BackHandler.exitApp(); @@ -124,6 +139,7 @@ export const SendVPScreen: React.FC = props => { OVP_ERROR_MESSAGES.DECLINED, OVP_ERROR_CODE.DECLINED, ); + controller.generateAndStoreLogMessage('USER_DECLINED_CONSENT'); if (controller.isOVPViaDeepLink) { controller.GO_TO_HOME(); BackHandler.exitApp(); @@ -135,12 +151,9 @@ export const SendVPScreen: React.FC = props => { const getAdditionalMessage = () => { if ( controller.isOVPViaDeepLink && - !( - controller.errorModal.showRetryButton && - controller.openID4VPRetryCount < 3 - ) + !(errorModal.showRetryButton && controller.openID4VPRetryCount < 3) ) { - return controller.errorModal.additionalMessage; + return errorModal.additionalMessage; } return undefined; }; @@ -221,8 +234,7 @@ export const SendVPScreen: React.FC = props => { }; const getPrimaryButtonText = () => { - return controller.errorModal.showRetryButton && - controller.openID4VPRetryCount < 3 + return errorModal.showRetryButton && controller.openID4VPRetryCount < 3 ? t('ScanScreen:status.retry') : undefined; }; @@ -417,14 +429,14 @@ export const SendVPScreen: React.FC = props => { /> )} - {controller.errorModal.show && ( + {errorModal.show && ( { + const getAdditionalMessage = useCallback(() => { return isOVPViaDeepLink && isIOS() ? t('errors.additionalMessage') : ''; - }; + }, [isOVPViaDeepLink, t]); + + const generateAndStoreLogMessage = useCallback( + (logType: string, errorInfo?: string) => { + activityLogService.send( + ActivityLogEvents.LOG_ACTIVITY( + VPShareActivityLog.getLogFromObject({ + timestamp: Date.now(), + type: logType, + info: errorInfo, + }), + ), + ); + }, + [activityLogService], + ); - function generateAndStoreLogMessage(logType: string, errorInfo?: string) { - activityLogService.send( - ActivityLogEvents.LOG_ACTIVITY( - VPShareActivityLog.getLogFromObject({ - timestamp: Date.now(), - type: logType, - info: errorInfo, - }), - ), - ); - } const requestedClaimsByVerifier = useSelector( openID4VPService, selectRequestedClaimsByVerifier, ); - const [errorModal, setErrorModalData] = useState({ - show: false, - title: '', - message: '', - additionalMessage: '', - showRetryButton: false, - }); - - const isClaimsEmpty = - !requestedClaimsByVerifier || requestedClaimsByVerifier.trim() === ''; - if (noCredentialsMatchingVPRequest) { - errorModal.title = isClaimsEmpty - ? t('errors.noMatchingCredentialsWithMissingClaims.title') - : t('errors.noMatchingCredentials.title'); - errorModal.message = isClaimsEmpty - ? t('errors.noMatchingCredentialsWithMissingClaims.message') - : t('errors.noMatchingCredentials.message', { claims: requestedClaimsByVerifier }); - generateAndStoreLogMessage( - 'NO_CREDENTIAL_MATCHING_REQUEST', - requestedClaimsByVerifier, - ); - } else if ( - error.includes('Verifier authentication was unsuccessful') || - error.startsWith('api error') - ) { - errorModal.title = t('errors.invalidVerifier.title'); - errorModal.message = t('errors.invalidVerifier.message'); - generateAndStoreLogMessage('VERIFIER_AUTHENTICATION_FAILED'); - } else if (error.includes('credential mismatch detected')) { - errorModal.title = t('errors.credentialsMismatch.title'); - errorModal.message = t('errors.credentialsMismatch.message', { - claims: requestedClaimsByVerifier, - }); - generateAndStoreLogMessage( - 'CREDENTIAL_MISMATCH_FROM_KEBAB', - requestedClaimsByVerifier, - ); - } else if (error.includes('none of the selected VC has image')) { - errorModal.title = t('errors.noImage.title'); - errorModal.message = t('errors.noImage.message'); - generateAndStoreLogMessage('NO_SELECTED_VC_HAS_IMAGE'); - } else if (error.includes('invalid_request_uri_method')) { - errorModal.title = t('errors.invalidRequestURI.title'); - errorModal.message = t('errors.invalidRequestURI.message'); - generateAndStoreLogMessage('INVALID_REQUEST_URI_METHOD'); - } else if (error.includes('invalid_request')) { - errorModal.title = t('errors.invalidQrCode.title'); - errorModal.message = t('errors.invalidQrCode.message'); - generateAndStoreLogMessage('INVALID_AUTH_REQUEST'); - } else if (error.includes('vp_formats_not_supported')) { - errorModal.title = t('errors.vpFormatsNotSupported.title'); - errorModal.message = t('errors.vpFormatsNotSupported.message'); - generateAndStoreLogMessage('VP_FORMATS_NOT_SUPPORTED'); - } else if (error.includes('invalid_presentation_definition_uri')) { - errorModal.title = t('errors.invalidPresentationDefinitionURI.title'); - errorModal.message = t('errors.invalidPresentationDefinitionURI.message'); - generateAndStoreLogMessage('INVALID_PRESENTATION_DEFINITION_URI'); - } else if (error.includes('invalid_presentation_definition_reference')) { - errorModal.title = t('errors.invalidPresentationDefinitionRef.title'); - errorModal.message = t('errors.invalidPresentationDefinitionRef.message'); - generateAndStoreLogMessage('INVALID_PRESENTATION_DEFINITION_REFERENCE'); - } else if (error.startsWith('send vp')) { - errorModal.title = t('errors.sendVPError.title'); - errorModal.message = t('errors.sendVPError.message'); - errorModal.showRetryButton = true; - } else if (error !== '') { - errorModal.title = t('errors.genericError.title'); - errorModal.message = t('errors.genericError.message'); - generateAndStoreLogMessage('TECHNICAL_ERROR'); - } - - useEffect(() => { - if (noCredentialsMatchingVPRequest && !hasLoggedErrorRef.current) { - const isClaimsEmpty = - !requestedClaimsByVerifier || requestedClaimsByVerifier.trim() === ''; - setErrorModalData({ - show: true, - title: isClaimsEmpty - ? t('errors.noMatchingCredentialsWithMissingClaims.title') - : t('errors.noMatchingCredentials.title'), - message: isClaimsEmpty - ? t('errors.noMatchingCredentialsWithMissingClaims.message') - : t('errors.noMatchingCredentials.message', { - claims: requestedClaimsByVerifier, - }), - additionalMessage: getAdditionalMessage(), - showRetryButton: false, - }); - generateAndStoreLogMessage( - 'NO_CREDENTIAL_MATCHING_REQUEST', - requestedClaimsByVerifier, - ); - 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: requestedClaimsByVerifier, - }), - additionalMessage: getAdditionalMessage(), - showRetryButton: false, - }); - generateAndStoreLogMessage( - 'CREDENTIAL_MISMATCH_FROM_KEBAB', - requestedClaimsByVerifier, - ); - 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; - } else if (error === '') { - setErrorModalData({ - show: false, - title: '', - message: '', - additionalMessage: '', - showRetryButton: false, - }); - hasLoggedErrorRef.current = false; - } - }, [error, noCredentialsMatchingVPRequest]); - let overlayDetails: Omit | null = null; let vpVerifierName = useSelector( openID4VPService, @@ -401,18 +223,12 @@ export function useSendVPScreen() { checkIfAnyVCHasImage, checkIfAllVCsHasImage, getSelectedVCs, - errorModal, + error, + noCredentialsMatchingVPRequest, + requestedClaimsByVerifier, + getAdditionalMessage, overlayDetails, - RESET_LOGGED_ERROR: () => { - hasLoggedErrorRef.current = false; - setErrorModalData({ - show: false, - title: '', - message: '', - additionalMessage: '', - showRetryButton: false, - }); - }, + generateAndStoreLogMessage, scanScreenError: useSelector(scanService, selectIsSendingVPError), vcsMatchingAuthRequest, userSelectedVCs: useSelector(openID4VPService, selectSelectedVCs), diff --git a/shared/hooks/useOvpErrorModal.ts b/shared/hooks/useOvpErrorModal.ts new file mode 100644 index 00000000..23b187b9 --- /dev/null +++ b/shared/hooks/useOvpErrorModal.ts @@ -0,0 +1,193 @@ +import {useEffect, useState} from 'react'; + +interface OvpErrorModal { + show: boolean; + title: string; + message: string; + additionalMessage: string; + showRetryButton: boolean; +} + +interface UseOvpErrorModalProps { + error: string; + noCredentialsMatchingVPRequest: boolean; + requestedClaimsByVerifier: string; + getAdditionalMessage: () => string; + generateAndStoreLogMessage: (logType: string, errorInfo?: string) => void; + t: (key: string, options?: any) => string; +} +export function useOvpErrorModal({ + error, + noCredentialsMatchingVPRequest, + requestedClaimsByVerifier, + getAdditionalMessage, + generateAndStoreLogMessage, + t, +}: UseOvpErrorModalProps): [OvpErrorModal, () => void] { + const [errorModal, setErrorModal] = useState({ + show: false, + title: '', + message: '', + additionalMessage: '', + showRetryButton: false, + }); + + useEffect(() => { + const isClaimsEmpty = + !requestedClaimsByVerifier || requestedClaimsByVerifier.trim() === ''; + const additionalMessage = getAdditionalMessage(); + + if (noCredentialsMatchingVPRequest) { + setErrorModal({ + show: true, + title: isClaimsEmpty + ? t('errors.noMatchingCredentialsWithMissingClaims.title') + : t('errors.noMatchingCredentials.title'), + message: isClaimsEmpty + ? t('errors.noMatchingCredentialsWithMissingClaims.message') + : t('errors.noMatchingCredentials.message', { + claims: requestedClaimsByVerifier, + }), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage( + 'NO_CREDENTIAL_MATCHING_REQUEST', + requestedClaimsByVerifier, + ); + } else if ( + error.includes('Verifier authentication was unsuccessful') || + error.startsWith('api error') + ) { + setErrorModal({ + show: true, + title: t('errors.invalidVerifier.title'), + message: t('errors.invalidVerifier.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('VERIFIER_AUTHENTICATION_FAILED'); + } else if (error.includes('credential mismatch detected')) { + setErrorModal({ + show: true, + title: t('errors.credentialsMismatch.title'), + message: t('errors.credentialsMismatch.message', { + claims: requestedClaimsByVerifier, + }), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage( + 'CREDENTIAL_MISMATCH_FROM_KEBAB', + requestedClaimsByVerifier, + ); + } else if (error.includes('none of the selected VC has image')) { + setErrorModal({ + show: true, + title: t('errors.noImage.title'), + message: t('errors.noImage.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('NO_SELECTED_VC_HAS_IMAGE'); + } else if (error.includes('invalid_request_uri_method')) { + setErrorModal({ + show: true, + title: t('errors.invalidRequestURI.title'), + message: t('errors.invalidRequestURI.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('INVALID_REQUEST_URI_METHOD'); + } else if (error.includes('invalid_request')) { + setErrorModal({ + show: true, + title: t('errors.invalidQrCode.title'), + message: t('errors.invalidQrCode.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('INVALID_AUTH_REQUEST'); + } else if (error.includes('vp_formats_not_supported')) { + setErrorModal({ + show: true, + title: t('errors.vpFormatsNotSupported.title'), + message: t('errors.vpFormatsNotSupported.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('REQUEST_COULD_NOT_BE_PROCESSED'); + } else if (error.includes('invalid_presentation_definition_uri')) { + setErrorModal({ + show: true, + title: t('errors.invalidPresentationDefinitionURI.title'), + message: t('errors.invalidPresentationDefinitionURI.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('INVALID_PRESENTATION_DEFINITION_URI'); + } else if (error.includes('invalid_presentation_definition_reference')) { + setErrorModal({ + show: true, + title: t('errors.invalidPresentationDefinitionRef.title'), + message: t('errors.invalidPresentationDefinitionRef.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('REQUEST_COULD_NOT_BE_PROCESSED'); + } else if (error.includes('invalid_client')) { + setErrorModal({ + show: true, + title: t('errors.invalidQrCode.title'), + message: t('errors.invalidQrCode.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('REQUEST_COULD_NOT_BE_PROCESSED'); + } else if (error.startsWith('send vp')) { + setErrorModal({ + show: true, + title: t('errors.sendVPError.title'), + message: t('errors.sendVPError.message'), + additionalMessage, + showRetryButton: true, + }); + generateAndStoreLogMessage('SEND_VP_ERROR'); + } else if (error !== '') { + setErrorModal({ + show: true, + title: t('errors.genericError.title'), + message: t('errors.genericError.message'), + additionalMessage, + showRetryButton: false, + }); + generateAndStoreLogMessage('TECHNICAL_ERROR'); + } else { + setErrorModal({ + show: false, + title: '', + message: '', + additionalMessage: '', + showRetryButton: false, + }); + } + }, [ + error, + noCredentialsMatchingVPRequest, + requestedClaimsByVerifier, + getAdditionalMessage, + t, + ]); + + const resetErrorModal = () => { + setErrorModal({ + show: false, + title: '', + message: '', + additionalMessage: '', + showRetryButton: false, + }); + }; + + return [errorModal, resetErrorModal]; +}