From 4eecd2a06535c94050b64a8c05ad509b575fe176 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Wed, 4 Feb 2026 11:31:10 -0800 Subject: [PATCH 01/13] chore: address ITMS-90738 (#1702) * fix ITMS-90738 * update description --- app/ios/OpenPassport/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ios/OpenPassport/Info.plist b/app/ios/OpenPassport/Info.plist index 5499223aa..93486ee46 100644 --- a/app/ios/OpenPassport/Info.plist +++ b/app/ios/OpenPassport/Info.plist @@ -70,7 +70,7 @@ NSHumanReadableCopyright NSLocationWhenInUseUsageDescription - + We use your location to improve document scanning reliability, as performance can vary by region and document type. NSPhotoLibraryUsageDescription We need access to your photo library to allow you to choose passport photos or save generated QR codes. UIAppFonts From 21a5a9a52c8657ee3a100610e8e9e323b12e0f2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:40:51 -0800 Subject: [PATCH 02/13] chore: bump mobile app version to 2.9.15 (#1704) Update build numbers and deployment timestamps after successful deployment. Co-authored-by: github-actions[bot] --- app/version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/version.json b/app/version.json index 1c74b900a..1d0de770b 100644 --- a/app/version.json +++ b/app/version.json @@ -1,7 +1,7 @@ { "ios": { - "build": 210, - "lastDeployed": "2026-02-04T08:27:35.956Z" + "build": 211, + "lastDeployed": "2026-02-04T22:15:04.337Z" }, "android": { "build": 139, From c8be12475f5ee6ff9ee0a2f4be76d026dd315d3b Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Wed, 4 Feb 2026 16:50:53 -0800 Subject: [PATCH 03/13] enable foreground service permissions (#1705) --- app/android/app/src/main/AndroidManifest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index 7ec878b3d..2c777d48e 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -17,8 +17,8 @@ - + + - From e2fae7d62ec46ed0c8f4a72f5fc9c3b0f828c5e1 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Wed, 4 Feb 2026 20:53:39 -0800 Subject: [PATCH 04/13] update build number and updae gemfile lock (#1706) --- app/Gemfile.lock | 10 +++++----- app/android/app/build.gradle | 2 +- app/version.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Gemfile.lock b/app/Gemfile.lock index 7a9539418..72d8b6b94 100644 --- a/app/Gemfile.lock +++ b/app/Gemfile.lock @@ -23,8 +23,8 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.4.0) - aws-partitions (1.1209.0) - aws-sdk-core (3.241.4) + aws-partitions (1.1212.0) + aws-sdk-core (3.242.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -35,7 +35,7 @@ GEM aws-sdk-kms (1.121.0) aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.212.0) + aws-sdk-s3 (1.213.0) aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -230,7 +230,7 @@ GEM i18n (1.14.8) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.18.0) + json (2.18.1) jwt (2.10.2) base64 logger (1.7.0) @@ -254,7 +254,7 @@ GEM optparse (0.8.1) os (1.1.4) plist (3.7.2) - prism (1.8.0) + prism (1.9.0) public_suffix (4.0.7) racc (1.8.1) rake (13.3.1) diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index 0ffafead5..0a78aa6ee 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -134,7 +134,7 @@ android { applicationId "com.proofofpassportapp" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 136 + versionCode 140 versionName "2.9.15" manifestPlaceholders = [appAuthRedirectScheme: 'com.proofofpassportapp'] externalNativeBuild { diff --git a/app/version.json b/app/version.json index 1d0de770b..42daacd00 100644 --- a/app/version.json +++ b/app/version.json @@ -4,7 +4,7 @@ "lastDeployed": "2026-02-04T22:15:04.337Z" }, "android": { - "build": 139, - "lastDeployed": "2026-01-22T23:03:43.685Z" + "build": 140, + "lastDeployed": "2026-02-05T00:58:22Z" } } From 4c8ffbd17f055340f62eedac85bf43d34461ce6c Mon Sep 17 00:00:00 2001 From: Nesopie <87437291+Nesopie@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:43:55 +0530 Subject: [PATCH 05/13] Feat/kyc sdk (#1709) * feat: kyc sdk * lint * bump: minor sdk version --- .../disclose/Verifier_vc_and_disclose_kyc.sol | 2 +- .../chain-11142220/deployed_addresses.json | 3 +- error-selectors.json | 800 ++++++++++++++++++ sdk/core/package.json | 2 +- sdk/core/src/SelfBackendVerifier.ts | 29 +- sdk/core/src/abi/KycVerifier.json | 36 + sdk/core/src/utils/constants.ts | 39 +- sdk/core/src/utils/id.ts | 21 +- sdk/core/src/utils/proof.ts | 4 +- 9 files changed, 901 insertions(+), 35 deletions(-) create mode 100644 error-selectors.json create mode 100644 sdk/core/src/abi/KycVerifier.json diff --git a/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol b/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol index cf4a82547..1fdfd2ae7 100644 --- a/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol +++ b/contracts/contracts/verifiers/disclose/Verifier_vc_and_disclose_kyc.sol @@ -20,7 +20,7 @@ pragma solidity >=0.7.0 <0.9.0; -contract Groth16Verifier { +contract Verifier_vc_and_disclose_kyc { // Scalar field size uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // Base field size diff --git a/contracts/ignition/deployments/chain-11142220/deployed_addresses.json b/contracts/ignition/deployments/chain-11142220/deployed_addresses.json index a80ca80fe..72d748ed2 100644 --- a/contracts/ignition/deployments/chain-11142220/deployed_addresses.json +++ b/contracts/ignition/deployments/chain-11142220/deployed_addresses.json @@ -104,5 +104,6 @@ "DeployKycRegistryModule#IdentityRegistryKycImplV1": "0x94f6DE38E10140B9E3963a770B5B769b38459a3B", "DeployKycRegistryModule#IdentityRegistry": "0x90e907E4AaB6e9bcFB94997Af4A097e8CAadBdf3", "UpdateAllRegistries#PCR0Manager": "0xf2810D5E9938816D42F0Ae69D33F013a23C0aED2", - "UpdateAllRegistries#a3": "0x90e907E4AaB6e9bcFB94997Af4A097e8CAadBdf3" + "UpdateAllRegistries#a3": "0x90e907E4AaB6e9bcFB94997Af4A097e8CAadBdf3", + "DeployAllVerifiers#Verifier_vc_and_disclose_kyc": "0xAAFA189a079D04462e8ab596d9c103e081A1c810" } diff --git a/error-selectors.json b/error-selectors.json new file mode 100644 index 000000000..e7cae30e2 --- /dev/null +++ b/error-selectors.json @@ -0,0 +1,800 @@ +[ + { + "name": "REGISTERED_COMMITMENT", + "signature": "REGISTERED_COMMITMENT()", + "selector": "0x034acfcc", + "file": "contracts/contracts/registry/IdentityRegistryAadhaarImplV1.sol", + "line": 136 + }, + { + "name": "REGISTERED_COMMITMENT", + "signature": "REGISTERED_COMMITMENT()", + "selector": "0x034acfcc", + "file": "contracts/contracts/registry/IdentityRegistryIdCardImplV1.sol", + "line": 144 + }, + { + "name": "REGISTERED_COMMITMENT", + "signature": "REGISTERED_COMMITMENT()", + "selector": "0x034acfcc", + "file": "contracts/contracts/registry/IdentityRegistryImplV1.sol", + "line": 151 + }, + { + "name": "REGISTERED_COMMITMENT", + "signature": "REGISTERED_COMMITMENT()", + "selector": "0x034acfcc", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 179 + }, + { + "name": "InvalidProof", + "signature": "InvalidProof()", + "selector": "0x09bde339", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 57 + }, + { + "name": "InvalidProof", + "signature": "InvalidProof()", + "selector": "0x09bde339", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 35 + }, + { + "name": "InvalidPubSignalsLength", + "signature": "InvalidPubSignalsLength(uint256,uint256)", + "selector": "0x0b42b970", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 43 + }, + { + "name": "NoVerifierSet", + "signature": "NoVerifierSet()", + "selector": "0x0ee78d58", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 151 + }, + { + "name": "NoVerifierSet", + "signature": "NoVerifierSet()", + "selector": "0x0ee78d58", + "file": "contracts/contracts/libraries/DscProofVerifierLib.sol", + "line": 17 + }, + { + "name": "NoVerifierSet", + "signature": "NoVerifierSet()", + "selector": "0x0ee78d58", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 22 + }, + { + "name": "INVALID_TIMESTAMP", + "signature": "INVALID_TIMESTAMP()", + "selector": "0x118818d1", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 189 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 183 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/libraries/CustomVerifier.sol", + "line": 10 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/libraries/DscProofVerifierLib.sol", + "line": 26 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/libraries/OfacCheckLib.sol", + "line": 22 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/libraries/ProofVerifierLib.sol", + "line": 21 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 31 + }, + { + "name": "InvalidAttestationId", + "signature": "InvalidAttestationId()", + "selector": "0x12ec75fe", + "file": "contracts/contracts/libraries/RootCheckLib.sol", + "line": 22 + }, + { + "name": "RegistrationNotOpen", + "signature": "RegistrationNotOpen()", + "selector": "0x153745d3", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 66 + }, + { + "name": "RegistrationNotOpen", + "signature": "RegistrationNotOpen()", + "selector": "0x153745d3", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 38 + }, + { + "name": "InvalidDscProof", + "signature": "InvalidDscProof()", + "selector": "0x1644e049", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 163 + }, + { + "name": "InvalidDscProof", + "signature": "InvalidDscProof()", + "selector": "0x1644e049", + "file": "contracts/contracts/libraries/DscProofVerifierLib.sol", + "line": 20 + }, + { + "name": "InvalidYearRange", + "signature": "InvalidYearRange()", + "selector": "0x16f40c94", + "file": "contracts/contracts/libraries/Formatter.sol", + "line": 12 + }, + { + "name": "InvalidDateDigit", + "signature": "InvalidDateDigit()", + "selector": "0x17af8154", + "file": "contracts/contracts/libraries/Formatter.sol", + "line": 16 + }, + { + "name": "INVALID_OFAC_ROOT", + "signature": "INVALID_OFAC_ROOT()", + "selector": "0x1ce3d3ca", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 176 + }, + { + "name": "HUB_ADDRESS_ZERO", + "signature": "HUB_ADDRESS_ZERO()", + "selector": "0x22697ffa", + "file": "contracts/contracts/registry/IdentityRegistryAadhaarImplV1.sol", + "line": 138 + }, + { + "name": "HUB_ADDRESS_ZERO", + "signature": "HUB_ADDRESS_ZERO()", + "selector": "0x22697ffa", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 181 + }, + { + "name": "RegisteredNullifier", + "signature": "RegisteredNullifier()", + "selector": "0x22cbc6a2", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 81 + }, + { + "name": "RegisteredNullifier", + "signature": "RegisteredNullifier()", + "selector": "0x22cbc6a2", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 43 + }, + { + "name": "InvalidMonthRange", + "signature": "InvalidMonthRange()", + "selector": "0x25e62788", + "file": "contracts/contracts/libraries/Formatter.sol", + "line": 13 + }, + { + "name": "ONLY_TEE_CAN_ACCESS", + "signature": "ONLY_TEE_CAN_ACCESS()", + "selector": "0x2822d0cb", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 177 + }, + { + "name": "UserIdentifierAlreadyRegistered", + "signature": "UserIdentifierAlreadyRegistered()", + "selector": "0x29393238", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 78 + }, + { + "name": "UserIdentifierAlreadyRegistered", + "signature": "UserIdentifierAlreadyRegistered()", + "selector": "0x29393238", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 42 + }, + { + "name": "InvalidFieldElement", + "signature": "InvalidFieldElement()", + "selector": "0x3ae4ed6b", + "file": "contracts/contracts/libraries/Formatter.sol", + "line": 15 + }, + { + "name": "InvalidPubkey", + "signature": "InvalidPubkey()", + "selector": "0x422cc3b7", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 211 + }, + { + "name": "InvalidPubkey", + "signature": "InvalidPubkey()", + "selector": "0x422cc3b7", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 34 + }, + { + "name": "InvalidOlderThan", + "signature": "InvalidOlderThan()", + "selector": "0x49aecbc2", + "file": "contracts/contracts/libraries/CustomVerifier.sol", + "line": 13 + }, + { + "name": "InvalidDscCommitmentRoot", + "signature": "InvalidDscCommitmentRoot()", + "selector": "0x4cb305bb", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 175 + }, + { + "name": "InvalidDscCommitmentRoot", + "signature": "InvalidDscCommitmentRoot()", + "selector": "0x4cb305bb", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 28 + }, + { + "name": "HUB_NOT_SET", + "signature": "HUB_NOT_SET()", + "selector": "0x4ffa9998", + "file": "contracts/contracts/registry/IdentityRegistryAadhaarImplV1.sol", + "line": 132 + }, + { + "name": "HUB_NOT_SET", + "signature": "HUB_NOT_SET()", + "selector": "0x4ffa9998", + "file": "contracts/contracts/registry/IdentityRegistryIdCardImplV1.sol", + "line": 140 + }, + { + "name": "HUB_NOT_SET", + "signature": "HUB_NOT_SET()", + "selector": "0x4ffa9998", + "file": "contracts/contracts/registry/IdentityRegistryImplV1.sol", + "line": 147 + }, + { + "name": "HUB_NOT_SET", + "signature": "HUB_NOT_SET()", + "selector": "0x4ffa9998", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 171 + }, + { + "name": "INVALID_COMMITMENT_ROOT", + "signature": "INVALID_COMMITMENT_ROOT()", + "selector": "0x52906601", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 172 + }, + { + "name": "UnauthorizedCaller", + "signature": "UnauthorizedCaller()", + "selector": "0x5c427cd9", + "file": "contracts/contracts/abstract/SelfVerificationRoot.sol", + "line": 49 + }, + { + "name": "UnauthorizedCaller", + "signature": "UnauthorizedCaller()", + "selector": "0x5c427cd9", + "file": "contracts/contracts/abstract/SelfVerificationRootUpgradeable.sol", + "line": 69 + }, + { + "name": "UserIdentifierAlreadyMinted", + "signature": "UserIdentifierAlreadyMinted()", + "selector": "0x5dd09265", + "file": "contracts/contracts/example/SelfIdentityERC721.sol", + "line": 51 + }, + { + "name": "UserIdentifierAlreadyMinted", + "signature": "UserIdentifierAlreadyMinted()", + "selector": "0x5dd09265", + "file": "contracts/contracts/example/SelfPassportERC721.sol", + "line": 48 + }, + { + "name": "InvalidOfacCheck", + "signature": "InvalidOfacCheck()", + "selector": "0x5fb542f4", + "file": "contracts/contracts/libraries/CustomVerifier.sol", + "line": 11 + }, + { + "name": "CrossChainIsNotSupportedYet", + "signature": "CrossChainIsNotSupportedYet()", + "selector": "0x61296fbb", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 191 + }, + { + "name": "AlreadyClaimed", + "signature": "AlreadyClaimed()", + "selector": "0x646cf558", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 60 + }, + { + "name": "AlreadyClaimed", + "signature": "AlreadyClaimed()", + "selector": "0x646cf558", + "file": "contracts/contracts/example/HappyBirthday.sol", + "line": 67 + }, + { + "name": "AlreadyClaimed", + "signature": "AlreadyClaimed()", + "selector": "0x646cf558", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 36 + }, + { + "name": "InputTooShort", + "signature": "InputTooShort()", + "selector": "0x65ec0cf1", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 195 + }, + { + "name": "InvalidRegisterProof", + "signature": "InvalidRegisterProof()", + "selector": "0x67b61dc7", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 159 + }, + { + "name": "InvalidRegisterProof", + "signature": "InvalidRegisterProof()", + "selector": "0x67b61dc7", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 25 + }, + { + "name": "RegistrationNotClosed", + "signature": "RegistrationNotClosed()", + "selector": "0x697e379b", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 69 + }, + { + "name": "RegistrationNotClosed", + "signature": "RegistrationNotClosed()", + "selector": "0x697e379b", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 39 + }, + { + "name": "INVALID_DSC_PROOF", + "signature": "INVALID_DSC_PROOF()", + "selector": "0x6a86dd76", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 164 + }, + { + "name": "ClaimNotOpen", + "signature": "ClaimNotOpen()", + "selector": "0x6b687806", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 72 + }, + { + "name": "ClaimNotOpen", + "signature": "ClaimNotOpen()", + "selector": "0x6b687806", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 40 + }, + { + "name": "InvalidUidaiTimestamp", + "signature": "InvalidUidaiTimestamp(uint256,uint256)", + "selector": "0x6f26ab8d", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 215 + }, + { + "name": "InvalidUidaiTimestamp", + "signature": "InvalidUidaiTimestamp(uint256,uint256)", + "selector": "0x6f26ab8d", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 37 + }, + { + "name": "INVALID_PROOF", + "signature": "INVALID_PROOF()", + "selector": "0x712eb087", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 183 + }, + { + "name": "INVALID_OFAC", + "signature": "INVALID_OFAC()", + "selector": "0x71b125ed", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 156 + }, + { + "name": "INVALID_IMAGE", + "signature": "INVALID_IMAGE()", + "selector": "0x7f91b413", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 187 + }, + { + "name": "InvalidForbiddenCountries", + "signature": "InvalidForbiddenCountries()", + "selector": "0x82cba848", + "file": "contracts/contracts/libraries/CustomVerifier.sol", + "line": 12 + }, + { + "name": "InsufficientCharcodeLen", + "signature": "InsufficientCharcodeLen()", + "selector": "0x86d41225", + "file": "contracts/contracts/libraries/CircuitAttributeHandler.sol", + "line": 15 + }, + { + "name": "InsufficientCharcodeLen", + "signature": "InsufficientCharcodeLen()", + "selector": "0x86d41225", + "file": "contracts/contracts/libraries/CircuitAttributeHandlerV2.sol", + "line": 17 + }, + { + "name": "InsufficientCharcodeLen", + "signature": "InsufficientCharcodeLen()", + "selector": "0x86d41225", + "file": "contracts/contracts/libraries/IdCardAttributeHandler.sol", + "line": 16 + }, + { + "name": "InvalidDayRange", + "signature": "InvalidDayRange()", + "selector": "0x8930acef", + "file": "contracts/contracts/libraries/Formatter.sol", + "line": 14 + }, + { + "name": "LENGTH_MISMATCH", + "signature": "LENGTH_MISMATCH()", + "selector": "0x899ef10d", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 136 + }, + { + "name": "NO_VERIFIER_SET", + "signature": "NO_VERIFIER_SET()", + "selector": "0x8e727f46", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 140 + }, + { + "name": "InvalidCscaRoot", + "signature": "InvalidCscaRoot()", + "selector": "0x8f1b44c7", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 179 + }, + { + "name": "InvalidCscaRoot", + "signature": "InvalidCscaRoot()", + "selector": "0x8f1b44c7", + "file": "contracts/contracts/libraries/DscProofVerifierLib.sol", + "line": 23 + }, + { + "name": "INVALID_REGISTER_PROOF", + "signature": "INVALID_REGISTER_PROOF()", + "selector": "0x9003ac4d", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 160 + }, + { + "name": "UserContextDataTooShort", + "signature": "UserContextDataTooShort()", + "selector": "0x94ec3503", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 199 + }, + { + "name": "NotWithinBirthdayWindow", + "signature": "NotWithinBirthdayWindow()", + "selector": "0x9b7983d7", + "file": "contracts/contracts/example/HappyBirthday.sol", + "line": 66 + }, + { + "name": "INVALID_CSCA_ROOT", + "signature": "INVALID_CSCA_ROOT()", + "selector": "0xa294ad3c", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 180 + }, + { + "name": "InvalidDataFormat", + "signature": "InvalidDataFormat()", + "selector": "0xa512e2ff", + "file": "contracts/contracts/abstract/SelfVerificationRoot.sol", + "line": 45 + }, + { + "name": "InvalidDataFormat", + "signature": "InvalidDataFormat()", + "selector": "0xa512e2ff", + "file": "contracts/contracts/abstract/SelfVerificationRootUpgradeable.sol", + "line": 65 + }, + { + "name": "ConfigNotSet", + "signature": "ConfigNotSet()", + "selector": "0xace124bc", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 207 + }, + { + "name": "InvalidDateLength", + "signature": "InvalidDateLength()", + "selector": "0xb3375953", + "file": "contracts/contracts/libraries/Formatter.sol", + "line": 11 + }, + { + "name": "ONLY_HUB_CAN_ACCESS", + "signature": "ONLY_HUB_CAN_ACCESS()", + "selector": "0xba0318cb", + "file": "contracts/contracts/registry/IdentityRegistryAadhaarImplV1.sol", + "line": 134 + }, + { + "name": "ONLY_HUB_CAN_ACCESS", + "signature": "ONLY_HUB_CAN_ACCESS()", + "selector": "0xba0318cb", + "file": "contracts/contracts/registry/IdentityRegistryIdCardImplV1.sol", + "line": 142 + }, + { + "name": "ONLY_HUB_CAN_ACCESS", + "signature": "ONLY_HUB_CAN_ACCESS()", + "selector": "0xba0318cb", + "file": "contracts/contracts/registry/IdentityRegistryImplV1.sol", + "line": 149 + }, + { + "name": "ONLY_HUB_CAN_ACCESS", + "signature": "ONLY_HUB_CAN_ACCESS()", + "selector": "0xba0318cb", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 173 + }, + { + "name": "INVALID_FORBIDDEN_COUNTRIES", + "signature": "INVALID_FORBIDDEN_COUNTRIES()", + "selector": "0xbf21b11c", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 152 + }, + { + "name": "NotRegistered", + "signature": "NotRegistered(address)", + "selector": "0xbfc6c337", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 63 + }, + { + "name": "NotRegistered", + "signature": "NotRegistered(address)", + "selector": "0xbfc6c337", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 37 + }, + { + "name": "InvalidOfacRoots", + "signature": "InvalidOfacRoots()", + "selector": "0xc67a44d2", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 223 + }, + { + "name": "InvalidOfacRoots", + "signature": "InvalidOfacRoots()", + "selector": "0xc67a44d2", + "file": "contracts/contracts/libraries/OfacCheckLib.sol", + "line": 19 + }, + { + "name": "CurrentDateNotInValidRange", + "signature": "CurrentDateNotInValidRange()", + "selector": "0xcf46551c", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 155 + }, + { + "name": "INVALID_VC_AND_DISCLOSE_PROOF", + "signature": "INVALID_VC_AND_DISCLOSE_PROOF()", + "selector": "0xd4d37a7a", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 168 + }, + { + "name": "AttestationIdMismatch", + "signature": "AttestationIdMismatch()", + "selector": "0xd7ca437d", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 219 + }, + { + "name": "InvalidVcAndDiscloseProof", + "signature": "InvalidVcAndDiscloseProof()", + "selector": "0xda7bd3a6", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 167 + }, + { + "name": "InvalidVcAndDiscloseProof", + "signature": "InvalidVcAndDiscloseProof()", + "selector": "0xda7bd3a6", + "file": "contracts/contracts/libraries/ProofVerifierLib.sol", + "line": 18 + }, + { + "name": "RegistryNotSet", + "signature": "RegistryNotSet()", + "selector": "0xe048e710", + "file": "contracts/contracts/libraries/RootCheckLib.sol", + "line": 25 + }, + { + "name": "INVALID_REVEALED_DATA_TYPE", + "signature": "INVALID_REVEALED_DATA_TYPE()", + "selector": "0xe0f15544", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 184 + }, + { + "name": "ScopeMismatch", + "signature": "ScopeMismatch()", + "selector": "0xe7bee380", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 187 + }, + { + "name": "InvalidUserIdentifierInProof", + "signature": "InvalidUserIdentifierInProof()", + "selector": "0xebbcc178", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 203 + }, + { + "name": "InvalidPubkeyCommitment", + "signature": "InvalidPubkeyCommitment()", + "selector": "0xebc2fedc", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 227 + }, + { + "name": "InvalidPubkeyCommitment", + "signature": "InvalidPubkeyCommitment()", + "selector": "0xebc2fedc", + "file": "contracts/contracts/libraries/RegisterProofVerifierLib.sol", + "line": 40 + }, + { + "name": "CURRENT_DATE_NOT_IN_VALID_RANGE", + "signature": "CURRENT_DATE_NOT_IN_VALID_RANGE()", + "selector": "0xed8cf9ff", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 144 + }, + { + "name": "INVALID_ROOT_CA", + "signature": "INVALID_ROOT_CA()", + "selector": "0xee57533e", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 185 + }, + { + "name": "InvalidUserIdentifier", + "signature": "InvalidUserIdentifier()", + "selector": "0xf0c426db", + "file": "contracts/contracts/example/Airdrop.sol", + "line": 75 + }, + { + "name": "InvalidUserIdentifier", + "signature": "InvalidUserIdentifier()", + "selector": "0xf0c426db", + "file": "contracts/contracts/example/SelfIdentityERC721.sol", + "line": 52 + }, + { + "name": "InvalidUserIdentifier", + "signature": "InvalidUserIdentifier()", + "selector": "0xf0c426db", + "file": "contracts/contracts/example/SelfPassportERC721.sol", + "line": 49 + }, + { + "name": "InvalidUserIdentifier", + "signature": "InvalidUserIdentifier()", + "selector": "0xf0c426db", + "file": "contracts/contracts/tests/TestAirdrop.sol", + "line": 41 + }, + { + "name": "INVALID_OLDER_THAN", + "signature": "INVALID_OLDER_THAN()", + "selector": "0xf0e539b9", + "file": "contracts/contracts/IdentityVerificationHubImplV1.sol", + "line": 148 + }, + { + "name": "InvalidIdentityCommitmentRoot", + "signature": "InvalidIdentityCommitmentRoot()", + "selector": "0xf53393a7", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 171 + }, + { + "name": "InvalidIdentityCommitmentRoot", + "signature": "InvalidIdentityCommitmentRoot()", + "selector": "0xf53393a7", + "file": "contracts/contracts/libraries/RootCheckLib.sol", + "line": 19 + }, + { + "name": "TEE_NOT_SET", + "signature": "TEE_NOT_SET()", + "selector": "0xfc833fc6", + "file": "contracts/contracts/registry/IdentityRegistryKycImplV1.sol", + "line": 175 + }, + { + "name": "LengthMismatch", + "signature": "LengthMismatch()", + "selector": "0xff633a38", + "file": "contracts/contracts/IdentityVerificationHubImplV2.sol", + "line": 147 + } +] \ No newline at end of file diff --git a/sdk/core/package.json b/sdk/core/package.json index d4a06479f..e66a92628 100644 --- a/sdk/core/package.json +++ b/sdk/core/package.json @@ -1,6 +1,6 @@ { "name": "@selfxyz/core", - "version": "1.1.0-beta.7", + "version": "1.2.0-beta.1", "repository": { "type": "git", "url": "https://github.com/selfxyz/self" diff --git a/sdk/core/src/SelfBackendVerifier.ts b/sdk/core/src/SelfBackendVerifier.ts index ffac6dffe..d59d0f787 100644 --- a/sdk/core/src/SelfBackendVerifier.ts +++ b/sdk/core/src/SelfBackendVerifier.ts @@ -5,6 +5,8 @@ import { AadhaarVerifier__factory, IdentityVerificationHubImpl, IdentityVerificationHubImpl__factory, + KycVerifier, + KycVerifier__factory, Registry__factory, Verifier, Verifier__factory, @@ -222,7 +224,26 @@ export class SelfBackendVerifier { let circuitTimestampYy: number[]; let circuitTimestampMm: number[]; let circuitTimestampDd: number[]; - if (attestationId === 3) { + if (attestationId === 4) { + circuitTimestampYy = publicSignals + .slice( + discloseIndices[attestationId].currentDateIndex, + discloseIndices[attestationId].currentDateIndex + 4 + ) + .map(Number); + circuitTimestampMm = publicSignals + .slice( + discloseIndices[attestationId].currentDateIndex + 4, + discloseIndices[attestationId].currentDateIndex + 6 + ) + .map(Number); + circuitTimestampDd = publicSignals + .slice( + discloseIndices[attestationId].currentDateIndex + 6, + discloseIndices[attestationId].currentDateIndex + 8 + ) + .map(Number); + } else if (attestationId === 3) { circuitTimestampYy = String(publicSignals[discloseIndices[attestationId].currentDateIndex]) .split('') .map(Number); @@ -285,7 +306,7 @@ export class SelfBackendVerifier { throw new ConfigMismatchError(issues); } - let verifierContract: Verifier | AadhaarVerifier; + let verifierContract: Verifier | AadhaarVerifier | KycVerifier; try { const verifierAddress = await this.identityVerificationHubContract.discloseVerifier( '0x' + attestationId.toString(16).padStart(64, '0') @@ -293,7 +314,9 @@ export class SelfBackendVerifier { if (verifierAddress === '0x0000000000000000000000000000000000000000') { throw new VerifierContractError('Verifier contract not found'); } - if (attestationId === 3) { + if (attestationId === 4) { + verifierContract = KycVerifier__factory.connect(verifierAddress, this.provider); + } else if (attestationId === 3) { verifierContract = AadhaarVerifier__factory.connect(verifierAddress, this.provider); } else { verifierContract = Verifier__factory.connect(verifierAddress, this.provider); diff --git a/sdk/core/src/abi/KycVerifier.json b/sdk/core/src/abi/KycVerifier.json new file mode 100644 index 000000000..3fbfb6a3b --- /dev/null +++ b/sdk/core/src/abi/KycVerifier.json @@ -0,0 +1,36 @@ +[ + { + "inputs": [ + { + "internalType": "uint256[2]", + "name": "a", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2][2]", + "name": "b", + "type": "uint256[2][2]" + }, + { + "internalType": "uint256[2]", + "name": "c", + "type": "uint256[2]" + }, + { + "internalType": "uint256[29]", + "name": "pubSignals", + "type": "uint256[29]" + } + ], + "name": "verifyProof", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/sdk/core/src/utils/constants.ts b/sdk/core/src/utils/constants.ts index dafc10fc2..2424a7608 100644 --- a/sdk/core/src/utils/constants.ts +++ b/sdk/core/src/utils/constants.ts @@ -50,9 +50,9 @@ export const discloseIndices = { // Selfrica ID Card - see CircuitConstantsV2.sol for layout documentation 4: { revealedDataPackedIndex: 0, - forbiddenCountriesListPackedIndex: 9, - nullifierIndex: 13, - attestationIdIndex: 29, + forbiddenCountriesListPackedIndex: 10, + nullifierIndex: 14, + attestationIdIndex: 15, merkleRootIndex: 17, currentDateIndex: 21, namedobSmtRootIndex: 18, @@ -139,25 +139,24 @@ export const revealedDataIndices: Record< ofacEnd: 117, }, 4: { - //put everything as 99 issuingStateStart: 99, issuingStateEnd: 99, - nameStart: 99, - nameEnd: 99, - idNumberStart: 99, - idNumberEnd: 99, - nationalityStart: 99, - nationalityEnd: 99, - dateOfBirthStart: 99, - dateOfBirthEnd: 99, - genderStart: 99, - genderEnd: 99, - expiryDateStart: 99, - expiryDateEnd: 99, - olderThanStart: 99, - olderThanEnd: 99, - ofacStart: 99, - ofacEnd: 99, + nameStart: 78, + nameEnd: 141, + idNumberStart: 30, + idNumberEnd: 61, + nationalityStart: 0, + nationalityEnd: 2, + dateOfBirthStart: 142, + dateOfBirthEnd: 149, + genderStart: 194, + genderEnd: 194, + expiryDateStart: 70, + expiryDateEnd: 77, + olderThanStart: 297, + olderThanEnd: 297, + ofacStart: 295, + ofacEnd: 296, }, } as const; diff --git a/sdk/core/src/utils/id.ts b/sdk/core/src/utils/id.ts index ff5e6aff6..2956c7dae 100644 --- a/sdk/core/src/utils/id.ts +++ b/sdk/core/src/utils/id.ts @@ -23,12 +23,19 @@ export const formatRevealedDataPacked = ( discloseIndices[attestationId].forbiddenCountriesListPackedIndex, discloseIndices[attestationId].forbiddenCountriesListPackedIndex + 4 ); - const issuingState = revealedDataPackedString - .subarray( - revealedDataIndices[attestationId].issuingStateStart, - revealedDataIndices[attestationId].issuingStateEnd + 1 - ) - .toString('utf-8'); + let issuingState = ''; + + if (attestationId === 4) { + issuingState = 'UNAVAILABLE'; + } else { + issuingState = revealedDataPackedString + .subarray( + revealedDataIndices[attestationId].issuingStateStart, + revealedDataIndices[attestationId].issuingStateEnd + 1 + ) + .toString('utf-8'); + } + const name = revealedDataPackedString .subarray( revealedDataIndices[attestationId].nameStart, @@ -93,7 +100,7 @@ export const formatRevealedDataPacked = ( .toString('utf-8'); } let olderThan: string; - if (attestationId === 3) { + if (attestationId === 3 || attestationId === 4) { olderThan = revealedDataPackedString .subarray( revealedDataIndices[attestationId].olderThanStart, diff --git a/sdk/core/src/utils/proof.ts b/sdk/core/src/utils/proof.ts index bcfedfe51..0f2e24164 100644 --- a/sdk/core/src/utils/proof.ts +++ b/sdk/core/src/utils/proof.ts @@ -20,7 +20,7 @@ export function getRevealedDataPublicSignalsLength(attestationId: AttestationId) case 3: return Math.ceil(119 / 31); case 4: - return Math.ceil(30 / 31); + return Math.ceil(298 / 31); default: throw new ProofError(`Invalid attestation ID: ${attestationId}`); } @@ -30,7 +30,7 @@ export const bytesCount: Record = { 1: [31, 31, 31], 2: [31, 31, 31, 1], 3: [31, 31, 31, 26], - 4: [31, 31, 31, 26], + 4: [31, 31, 31, 31, 31, 31, 31, 31, 31, 19], }; /** From 2ccc6600cb1a0cbdda21633bd21651bc522aa9df Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Thu, 5 Feb 2026 07:50:21 -0800 Subject: [PATCH 06/13] update xcode version (#1699) --- .github/workflows/mobile-ci.yml | 2 +- .github/workflows/mobile-deploy.yml | 2 +- .github/workflows/mobile-e2e.yml | 2 +- .github/workflows/mobile-sdk-demo-e2e.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mobile-ci.yml b/.github/workflows/mobile-ci.yml index 37fd2ee36..a419eda00 100644 --- a/.github/workflows/mobile-ci.yml +++ b/.github/workflows/mobile-ci.yml @@ -5,7 +5,7 @@ env: RUBY_VERSION: 3.2 JAVA_VERSION: 17 ANDROID_NDK_VERSION: 27.0.12077973 - XCODE_VERSION: 16.4 + XCODE_VERSION: 26 # Path configuration WORKSPACE: ${{ github.workspace }} APP_PATH: ${{ github.workspace }}/app diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 2d273176d..b8dff186f 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -33,7 +33,7 @@ env: JAVA_VERSION: 17 ANDROID_API_LEVEL: 35 ANDROID_NDK_VERSION: 27.0.12077973 - XCODE_VERSION: 16.4 + XCODE_VERSION: 26 # Cache versioning - increment these to bust caches when needed GH_CACHE_VERSION: v1 # Global cache version diff --git a/.github/workflows/mobile-e2e.yml b/.github/workflows/mobile-e2e.yml index 39d1bc593..778c71669 100644 --- a/.github/workflows/mobile-e2e.yml +++ b/.github/workflows/mobile-e2e.yml @@ -5,7 +5,7 @@ env: JAVA_VERSION: 17 ANDROID_API_LEVEL: 33 ANDROID_NDK_VERSION: 27.0.12077973 - XCODE_VERSION: 16.4 + XCODE_VERSION: 26 # Cache versions GH_CACHE_VERSION: v2 # Global cache version - bumped to invalidate caches GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version diff --git a/.github/workflows/mobile-sdk-demo-e2e.yml b/.github/workflows/mobile-sdk-demo-e2e.yml index c90b94af1..d9e33cf72 100644 --- a/.github/workflows/mobile-sdk-demo-e2e.yml +++ b/.github/workflows/mobile-sdk-demo-e2e.yml @@ -5,7 +5,7 @@ env: JAVA_VERSION: 17 ANDROID_API_LEVEL: 33 ANDROID_NDK_VERSION: 27.0.12077973 - XCODE_VERSION: 16.4 + XCODE_VERSION: 26 # Cache versions GH_CACHE_VERSION: v1 GH_GEMS_CACHE_VERSION: v1 From 63fd92da95ce674c6601f15bb2256bd4edca591a Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Thu, 5 Feb 2026 09:07:09 -0800 Subject: [PATCH 07/13] chore: clean up navigation index (#1703) * abstract nav types * fix points flow callbacks --- app/src/hooks/useEarnPointsFlow.ts | 17 +- app/src/navigation/index.tsx | 169 +-------------- app/src/navigation/types.ts | 204 +++++++++++++++++- app/src/screens/home/PointsInfoScreen.tsx | 8 +- app/tests/src/hooks/useEarnPointsFlow.test.ts | 37 +++- 5 files changed, 245 insertions(+), 190 deletions(-) diff --git a/app/src/hooks/useEarnPointsFlow.ts b/app/src/hooks/useEarnPointsFlow.ts index 53749da9a..c440edc2e 100644 --- a/app/src/hooks/useEarnPointsFlow.ts +++ b/app/src/hooks/useEarnPointsFlow.ts @@ -92,13 +92,22 @@ export const useEarnPointsFlow = ({ }, [hasReferrer, navigation, navigateToPointsProof]); const showPointsInfoScreen = useCallback(() => { - navigation.navigate('PointsInfo', { - showNextButton: true, - onNextButtonPress: () => { + const callbackId = registerModalCallbacks({ + onButtonPress: () => { showPointsDisclosureModal(); }, + onModalDismiss: () => { + if (hasReferrer) { + useUserStore.getState().clearDeepLinkReferrer(); + } + }, }); - }, [navigation, showPointsDisclosureModal]); + + navigation.navigate('PointsInfo', { + showNextButton: true, + callbackId, + }); + }, [hasReferrer, navigation, showPointsDisclosureModal]); const handleReferralFlow = useCallback(async () => { if (!referrer) { diff --git a/app/src/navigation/index.tsx b/app/src/navigation/index.tsx index 76fa3a846..e37fc45ea 100644 --- a/app/src/navigation/index.tsx +++ b/app/src/navigation/index.tsx @@ -13,7 +13,6 @@ import { import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import type { DocumentCategory } from '@selfxyz/common/utils/types'; import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; import { DefaultNavBar } from '@/components/navbar'; @@ -28,11 +27,9 @@ import homeScreens from '@/navigation/home'; import onboardingScreens from '@/navigation/onboarding'; import sharedScreens from '@/navigation/shared'; import starfallScreens from '@/navigation/starfall'; +import type { ExplicitRouteParams, OmittedRouteKeys } from '@/navigation/types'; import verificationScreens from '@/navigation/verification'; -import type { ModalNavigationParams } from '@/screens/app/ModalScreen'; -import type { WebViewScreenParams } from '@/screens/shared/WebViewScreen'; import { trackScreenView } from '@/services/analytics'; -import type { ProofHistory } from '@/stores/proofTypes'; export const navigationScreens = { ...appScreens, @@ -58,167 +55,13 @@ const AppNavigation = createNativeStackNavigator({ type BaseRootStackParamList = StaticParamList; -// Explicitly declare route params that are not inferred from initialParams +// Explicitly declare route params that are not inferred from initialParams. +// Route param types are defined in @/navigation/types for better organization. export type RootStackParamList = Omit< BaseRootStackParamList, - | 'AadhaarUpload' - | 'AadhaarUploadError' - | 'AadhaarUploadSuccess' - | 'AccountRecovery' - | 'AccountVerifiedSuccess' - | 'CloudBackupSettings' - | 'ComingSoon' - | 'ConfirmBelonging' - | 'CreateMock' - | 'Disclaimer' - | 'DocumentNFCScan' - | 'DocumentOnboarding' - | 'DocumentSelectorForProving' - | 'ProvingScreenRouter' - | 'Gratification' - | 'Home' - | 'IDPicker' - | 'IdDetails' - | 'KycSuccess' - | 'KYCVerified' - | 'RegistrationFallback' - | 'Loading' - | 'Modal' - | 'MockDataDeepLink' - | 'Points' - | 'PointsInfo' - | 'ProofHistoryDetail' - | 'Prove' - | 'SaveRecoveryPhrase' - | 'WebView' -> & { - // Shared screens - ComingSoon: { - countryCode?: string; - documentCategory?: string; - }; - WebView: WebViewScreenParams; - - // Document screens - IDPicker: { - countryCode: string; - documentTypes: string[]; - }; - ConfirmBelonging: - | { - documentCategory?: DocumentCategory; - signatureAlgorithm?: string; - curveOrExponent?: string; - } - | undefined; - DocumentNFCScan: - | { - passportNumber?: string; - dateOfBirth?: string; - dateOfExpiry?: string; - } - | undefined; - DocumentCameraTrouble: undefined; - DocumentOnboarding: undefined; - - // Aadhaar screens - AadhaarUpload: { - countryCode: string; - }; - AadhaarUploadSuccess: undefined; - AadhaarUploadError: { - errorType: string; - }; - - // Registration Fallback screens - RegistrationFallback: { - errorSource: - | 'mrz_scan_failed' - | 'nfc_scan_failed' - | 'sumsub_initialization' - | 'sumsub_verification'; - countryCode: string; - }; - - // Account/Recovery screens - AccountRecovery: - | { - nextScreen?: string; - } - | undefined; - SaveRecoveryPhrase: - | { - nextScreen?: string; - } - | undefined; - CloudBackupSettings: - | { - nextScreen?: 'SaveRecoveryPhrase'; - returnToScreen?: 'Points'; - } - | undefined; - ProofSettings: undefined; - AccountVerifiedSuccess: undefined; - - // Proof/Verification screens - ProofHistoryDetail: { - data: ProofHistory; - }; - Prove: - | { - scrollOffset?: number; - } - | undefined; - ProvingScreenRouter: undefined; - DocumentSelectorForProving: - | { - documentType?: string; - } - | undefined; - - // App screens - Loading: { - documentCategory?: DocumentCategory; - signatureAlgorithm?: string; - curveOrExponent?: string; - }; - Modal: ModalNavigationParams; - Gratification: { - points?: number; - }; - StarfallPushCode: undefined; - - // Home screens - Home: { - testReferralFlow?: boolean; - }; - Points: undefined; - PointsInfo: - | { - showNextButton?: boolean; - onNextButtonPress?: () => void; - } - | undefined; - IdDetails: undefined; - - // Onboarding screens - Disclaimer: undefined; - KycSuccess: - | { - userId?: string; - } - | undefined; - KYCVerified: - | { - status?: string; - userId?: string; - } - | undefined; - - // Dev screens - CreateMock: undefined; - MockDataDeepLink: undefined; -}; + OmittedRouteKeys +> & + ExplicitRouteParams; export type RootStackScreenProps = NativeStackScreenProps; diff --git a/app/src/navigation/types.ts b/app/src/navigation/types.ts index a31a56e98..c0e7de6dc 100644 --- a/app/src/navigation/types.ts +++ b/app/src/navigation/types.ts @@ -2,18 +2,204 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import type { DocumentCategory } from '@selfxyz/common/types'; +import type { DocumentCategory } from '@selfxyz/common/utils/types'; +import type { ModalNavigationParams } from '@/screens/app/ModalScreen'; +import type { WebViewScreenParams } from '@/screens/shared/WebViewScreen'; +import type { ProofHistory } from '@/stores/proofTypes'; + +// ============================================================================= +// Aadhaar Screens +// ============================================================================= + +export type AadhaarRoutesParamList = { + AadhaarUpload: { + countryCode: string; + }; + AadhaarUploadSuccess: undefined; + AadhaarUploadError: { + errorType: string; + }; +}; + +// ============================================================================= +// Account/Recovery Screens +// ============================================================================= + +export type AccountRoutesParamList = { + AccountRecovery: + | { + nextScreen?: string; + } + | undefined; + SaveRecoveryPhrase: + | { + nextScreen?: string; + } + | undefined; + CloudBackupSettings: + | { + nextScreen?: 'SaveRecoveryPhrase'; + returnToScreen?: 'Points'; + } + | undefined; + ProofSettings: undefined; + AccountVerifiedSuccess: undefined; +}; + +// ============================================================================= +// App Screens +// ============================================================================= + +export type AppRoutesParamList = { + Loading: { + documentCategory?: DocumentCategory; + signatureAlgorithm?: string; + curveOrExponent?: string; + }; + Modal: ModalNavigationParams; + Gratification: { + points?: number; + }; + StarfallPushCode: undefined; +}; + +// ============================================================================= +// Dev Screens +// ============================================================================= + +export type DevRoutesParamList = { + CreateMock: undefined; + MockDataDeepLink: undefined; +}; + +// ============================================================================= +// Document Screens +// ============================================================================= + +export type DocumentRoutesParamList = { + IDPicker: { + countryCode: string; + documentTypes: string[]; + }; + ConfirmBelonging: + | { + documentCategory?: DocumentCategory; + signatureAlgorithm?: string; + curveOrExponent?: string; + } + | undefined; + DocumentNFCScan: + | { + passportNumber?: string; + dateOfBirth?: string; + dateOfExpiry?: string; + } + | undefined; + DocumentCameraTrouble: undefined; + DocumentOnboarding: undefined; + IdDetails: undefined; +}; + +// ============================================================================= +// Combined Types +// ============================================================================= +/** + * All route param types that need to be explicitly defined (not inferred from initialParams). + * This is used to compose RootStackParamList in index.tsx. + */ +export type ExplicitRouteParams = AadhaarRoutesParamList & + AccountRoutesParamList & + AppRoutesParamList & + DevRoutesParamList & + DocumentRoutesParamList & + HomeRoutesParamList & + OnboardingRoutesParamList & + RegistrationRoutesParamList & + SharedRoutesParamList & + VerificationRoutesParamList; + +// ============================================================================= +// Home Screens +// ============================================================================= +export type HomeRoutesParamList = { + Home: { + testReferralFlow?: boolean; + }; + Points: undefined; + PointsInfo: + | { + showNextButton?: boolean; + callbackId?: number; + } + | undefined; +}; + +/** + * Keys that need to be omitted from BaseRootStackParamList before merging with ExplicitRouteParams. + * These are routes whose params are explicitly defined rather than inferred. + */ +export type OmittedRouteKeys = keyof ExplicitRouteParams; + +// ============================================================================= +// Onboarding Screens +// ============================================================================= +export type OnboardingRoutesParamList = { + Disclaimer: undefined; + KycSuccess: + | { + userId?: string; + } + | undefined; + KYCVerified: + | { + status?: string; + userId?: string; + } + | undefined; +}; + +// ============================================================================= +// Registration Fallback Screens +// ============================================================================= +export type RegistrationRoutesParamList = { + RegistrationFallback: { + errorSource: + | 'mrz_scan_failed' + | 'nfc_scan_failed' + | 'sumsub_initialization' + | 'sumsub_verification'; + countryCode: string; + }; +}; + +// ============================================================================= +// Shared Screens +// ============================================================================= export type SharedRoutesParamList = { ComingSoon: { countryCode?: string; - documentCategory?: DocumentCategory; - }; - WebView: { - url: string; - title?: string; - shareTitle?: string; - shareMessage?: string; - shareUrl?: string; + documentCategory?: string; }; + WebView: WebViewScreenParams; +}; + +// ============================================================================= +// Verification/Proof Screens +// ============================================================================= +export type VerificationRoutesParamList = { + ProofHistoryDetail: { + data: ProofHistory; + }; + Prove: + | { + scrollOffset?: number; + } + | undefined; + ProvingScreenRouter: undefined; + DocumentSelectorForProving: + | { + documentType?: string; + } + | undefined; }; diff --git a/app/src/screens/home/PointsInfoScreen.tsx b/app/src/screens/home/PointsInfoScreen.tsx index 9e08d2090..f5d913ac7 100644 --- a/app/src/screens/home/PointsInfoScreen.tsx +++ b/app/src/screens/home/PointsInfoScreen.tsx @@ -22,11 +22,12 @@ import CloudBackupIcon from '@/assets/icons/cloud_backup.svg'; import PushNotificationsIcon from '@/assets/icons/push_notifications.svg'; import StarIcon from '@/assets/icons/star.svg'; import Referral from '@/assets/images/referral.png'; +import { getModalCallbacks } from '@/utils/modalCallbackRegistry'; type PointsInfoScreenProps = StaticScreenProps< | { showNextButton?: boolean; - onNextButtonPress?: () => void; + callbackId?: number; } | undefined >; @@ -90,8 +91,9 @@ const EARN_POINTS_ITEMS = [ const PointsInfoScreen: React.FC = ({ route: { params }, }) => { - const { showNextButton, onNextButtonPress } = params || {}; + const { showNextButton, callbackId } = params || {}; const { left, right, bottom } = useSafeAreaInsets(); + const callbacks = callbackId ? getModalCallbacks(callbackId) : undefined; return ( @@ -138,7 +140,7 @@ const PointsInfoScreen: React.FC = ({ {showNextButton && ( - Next + Next )} diff --git a/app/tests/src/hooks/useEarnPointsFlow.test.ts b/app/tests/src/hooks/useEarnPointsFlow.test.ts index 5b003e3b7..5dd13e265 100644 --- a/app/tests/src/hooks/useEarnPointsFlow.test.ts +++ b/app/tests/src/hooks/useEarnPointsFlow.test.ts @@ -207,12 +207,15 @@ describe('useEarnPointsFlow', () => { expect(mockNavigate).toHaveBeenCalledWith('PointsInfo', { showNextButton: true, - onNextButtonPress: expect.any(Function), + callbackId: expect.any(Number), }); - // We pass onNextButtonPress() that displays the points disclosure modal + // We pass callbackId to retrieve and invoke the callback that displays the points disclosure modal + const callbackId = mockNavigate.mock.calls[0][1].callbackId; + const callbacks = getModalCallbacks(callbackId); + await act(async () => { - await mockNavigate.mock.calls[0][1].onNextButtonPress(); + await callbacks!.onButtonPress(); }); expect(mockNavigate).toHaveBeenCalledWith('Modal', { @@ -243,11 +246,14 @@ describe('useEarnPointsFlow', () => { expect(mockNavigate).toHaveBeenCalledWith('PointsInfo', { showNextButton: true, - onNextButtonPress: expect.any(Function), + callbackId: expect.any(Number), }); + const pointsInfoCallbackId = mockNavigate.mock.calls[0][1].callbackId; + const pointsInfoCallbacks = getModalCallbacks(pointsInfoCallbackId); + await act(async () => { - await mockNavigate.mock.calls[0][1].onNextButtonPress(); + await pointsInfoCallbacks!.onButtonPress(); }); const callbackId = mockNavigate.mock.calls[1][1].callbackId; @@ -290,11 +296,14 @@ describe('useEarnPointsFlow', () => { expect(mockNavigate).toHaveBeenCalledWith('PointsInfo', { showNextButton: true, - onNextButtonPress: expect.any(Function), + callbackId: expect.any(Number), }); + const pointsInfoCallbackId = mockNavigate.mock.calls[0][1].callbackId; + const pointsInfoCallbacks = getModalCallbacks(pointsInfoCallbackId); + await act(async () => { - await mockNavigate.mock.calls[0][1].onNextButtonPress(); + await pointsInfoCallbacks!.onButtonPress(); }); const callbackId = mockNavigate.mock.calls[1][1].callbackId; @@ -662,11 +671,14 @@ describe('useEarnPointsFlow', () => { expect(mockNavigate).toHaveBeenCalledWith('PointsInfo', { showNextButton: true, - onNextButtonPress: expect.any(Function), + callbackId: expect.any(Number), }); + const pointsInfoCallbackId = mockNavigate.mock.calls[0][1].callbackId; + const pointsInfoCallbacks = getModalCallbacks(pointsInfoCallbackId); + await act(async () => { - await mockNavigate.mock.calls[0][1].onNextButtonPress(); + await pointsInfoCallbacks!.onButtonPress(); }); // The function catches errors and returns false, so it should show points disclosure modal @@ -697,11 +709,14 @@ describe('useEarnPointsFlow', () => { expect(mockNavigate).toHaveBeenCalledWith('PointsInfo', { showNextButton: true, - onNextButtonPress: expect.any(Function), + callbackId: expect.any(Number), }); + const pointsInfoCallbackId = mockNavigate.mock.calls[0][1].callbackId; + const pointsInfoCallbacks = getModalCallbacks(pointsInfoCallbackId); + await act(async () => { - await mockNavigate.mock.calls[0][1].onNextButtonPress(); + await pointsInfoCallbacks!.onButtonPress(); }); const callbackId = mockNavigate.mock.calls[1][1].callbackId; From 7acc9bb2a6528b3b258cb9b4c2c8521ca2781efa Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Thu, 5 Feb 2026 13:15:05 -0800 Subject: [PATCH 08/13] chore: fix failing mobile demo ios e2e tests (#1710) * fix failing mobile demo ios e2e tests * temp test * add patch. update files * run demo e2e patch * sort package json * disable running on dev --- .github/workflows/mobile-sdk-demo-e2e.yml | 2 +- app/ios/Podfile.lock | 8 +- app/package.json | 2 +- packages/mobile-sdk-demo/ios/Podfile | 3 +- packages/mobile-sdk-demo/ios/Podfile.lock | 125 ++++++++++++++++------ packages/mobile-sdk-demo/package.json | 1 + patches/react-native-svg+15.15.1.patch | 24 +++++ yarn.lock | 16 +-- 8 files changed, 124 insertions(+), 57 deletions(-) create mode 100644 patches/react-native-svg+15.15.1.patch diff --git a/.github/workflows/mobile-sdk-demo-e2e.yml b/.github/workflows/mobile-sdk-demo-e2e.yml index d9e33cf72..0b31ac989 100644 --- a/.github/workflows/mobile-sdk-demo-e2e.yml +++ b/.github/workflows/mobile-sdk-demo-e2e.yml @@ -457,7 +457,7 @@ jobs: fi FORCE_BUNDLING=1 RCT_NO_LAUNCH_PACKAGER=1 \ - xcodebuild -workspace "$WORKSPACE_PATH" -scheme ${{ env.IOS_PROJECT_SCHEME }} -configuration Debug -destination "id=${{ env.IOS_SIMULATOR_ID }}" -derivedDataPath packages/mobile-sdk-demo/ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets -quiet COMPILER_INDEX_STORE_ENABLE=NO ONLY_ACTIVE_ARCH=YES SWIFT_COMPILATION_MODE=wholemodule || { echo "❌ iOS build failed"; exit 1; } + xcodebuild -workspace "$WORKSPACE_PATH" -scheme ${{ env.IOS_PROJECT_SCHEME }} -configuration Debug -destination "id=${{ env.IOS_SIMULATOR_ID }}" -derivedDataPath packages/mobile-sdk-demo/ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets -quiet COMPILER_INDEX_STORE_ENABLE=NO ONLY_ACTIVE_ARCH=YES SWIFT_COMPILATION_MODE=wholemodule 'SWIFT_ACTIVE_COMPILATION_CONDITIONS=$(inherited) E2E_TESTING' || { echo "❌ iOS build failed"; exit 1; } echo "✅ iOS build succeeded" - name: Build iOS Release Archive (unsigned) run: | diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 3a44a4f13..cfff6c54e 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -2152,7 +2152,7 @@ PODS: - ReactCommon/turbomodule/core - Sentry/HybridSDK (= 8.53.2) - Yoga - - RNSVG (15.12.1): + - RNSVG (15.15.1): - DoubleConversion - glog - hermes-engine @@ -2172,9 +2172,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.12.1) + - RNSVG/common (= 15.15.1) - Yoga - - RNSVG/common (15.12.1): + - RNSVG/common (15.15.1): - DoubleConversion - glog - hermes-engine @@ -2671,7 +2671,7 @@ SPEC CHECKSUMS: RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73 RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8 RNSentry: f79dd124cc49088445c16d23955860dd0d1db6f3 - RNSVG: 0c1fc3e7b147949dc15644845e9124947ac8c9bb + RNSVG: 8cd7dadbe9bdc7d70872910dbe611c8e0f4597bb segment-analytics-react-native: 0eae155b0e9fa560fa6b17d78941df64537c35b7 Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 diff --git a/app/package.json b/app/package.json index 531105645..d21f71be9 100644 --- a/app/package.json +++ b/app/package.json @@ -164,7 +164,7 @@ "react-native-safe-area-context": "^5.6.2", "react-native-screens": "4.15.3", "react-native-sqlite-storage": "^6.0.1", - "react-native-svg": "15.12.1", + "react-native-svg": "15.15.1", "react-native-svg-web": "1.0.9", "react-native-url-polyfill": "^3.0.0", "react-native-web": "^0.21.2", diff --git a/packages/mobile-sdk-demo/ios/Podfile b/packages/mobile-sdk-demo/ios/Podfile index 386604467..381a7fb35 100644 --- a/packages/mobile-sdk-demo/ios/Podfile +++ b/packages/mobile-sdk-demo/ios/Podfile @@ -34,10 +34,9 @@ target "SelfDemoApp" do use_react_native!( :path => config[:reactNativePath], + :hermes_enabled => true, # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/..", - :fabric_enabled => false, - :hermes_enabled => true, ) # Use the custom NFCPassportReader fork diff --git a/packages/mobile-sdk-demo/ios/Podfile.lock b/packages/mobile-sdk-demo/ios/Podfile.lock index d93ccf5a5..2eadc15c9 100644 --- a/packages/mobile-sdk-demo/ios/Podfile.lock +++ b/packages/mobile-sdk-demo/ios/Podfile.lock @@ -8,17 +8,32 @@ PODS: - hermes-engine (0.76.9): - hermes-engine/Pre-built (= 0.76.9) - hermes-engine/Pre-built (0.76.9) - - Mixpanel-swift (5.0.0): - - Mixpanel-swift/Complete (= 5.0.0) - - Mixpanel-swift/Complete (5.0.0) + - lottie-ios (4.5.0) + - lottie-react-native (7.2.2): + - DoubleConversion + - glog + - hermes-engine + - lottie-ios (= 4.5.0) + - RCT-Folly (= 2024.10.14.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - mobile-sdk-alpha (0.1.0): - - NFCPassportReader - QKMRZParser - React-Core - - NFCPassportReader (2.1.1): - - Mixpanel-swift (~> 5.0.0) - - OpenSSL-Universal (= 1.1.1900) - - OpenSSL-Universal (1.1.1900) - QKMRZParser (2.0.0) - RCT-Folly (2024.10.14.00): - boost @@ -1300,7 +1315,7 @@ PODS: - Yoga - react-native-get-random-values (1.11.0): - React-Core - - react-native-safe-area-context (5.6.1): + - react-native-safe-area-context (5.6.2): - DoubleConversion - glog - hermes-engine @@ -1313,8 +1328,8 @@ PODS: - React-featureflags - React-graphics - React-ImageManager - - react-native-safe-area-context/common (= 5.6.1) - - react-native-safe-area-context/fabric (= 5.6.1) + - react-native-safe-area-context/common (= 5.6.2) + - react-native-safe-area-context/fabric (= 5.6.2) - React-NativeModulesApple - React-RCTFabric - React-rendererdebug @@ -1323,7 +1338,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context/common (5.6.1): + - react-native-safe-area-context/common (5.6.2): - DoubleConversion - glog - hermes-engine @@ -1344,7 +1359,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-safe-area-context/fabric (5.6.1): + - react-native-safe-area-context/fabric (5.6.2): - DoubleConversion - glog - hermes-engine @@ -1366,6 +1381,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-webview (13.16.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.10.14.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - React-nativeconfig (0.76.9) - React-NativeModulesApple (0.76.9): - glog @@ -1680,6 +1716,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - RNLocalize (3.6.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.10.14.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - RNReactNativeHapticFeedback (2.3.3): - DoubleConversion - glog @@ -1701,7 +1758,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNSVG (15.12.1): + - RNSVG (15.15.1): - DoubleConversion - glog - hermes-engine @@ -1721,9 +1778,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.12.1) + - RNSVG/common (= 15.15.1) - Yoga - - RNSVG/common (15.12.1): + - RNSVG/common (15.15.1): - DoubleConversion - glog - hermes-engine @@ -1776,8 +1833,8 @@ DEPENDENCIES: - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - lottie-react-native (from `../node_modules/lottie-react-native`) - "mobile-sdk-alpha (from `../node_modules/@selfxyz/mobile-sdk-alpha`)" - - "NFCPassportReader (from `git@github.com:selfxyz/NFCPassportReader.git`, commit `04ede227cbfd377e2b4bc9b38f9a89eebdcab52f`)" - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) @@ -1811,6 +1868,7 @@ DEPENDENCIES: - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - react-native-webview (from `../node_modules/react-native-webview`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -1840,6 +1898,7 @@ DEPENDENCIES: - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - RNKeychain (from `../node_modules/react-native-keychain`) + - RNLocalize (from `../node_modules/react-native-localize`) - RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`) - RNSVG (from `../node_modules/react-native-svg`) - RNVectorIcons (from `../node_modules/react-native-vector-icons`) @@ -1847,8 +1906,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - - Mixpanel-swift - - OpenSSL-Universal + - lottie-ios - QKMRZParser - SocketRocket @@ -1868,11 +1926,10 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-11-12-RNv0.76.2-5b4aa20c719830dcf5684832b89a6edb95ac3d64 + lottie-react-native: + :path: "../node_modules/lottie-react-native" mobile-sdk-alpha: :path: "../node_modules/@selfxyz/mobile-sdk-alpha" - NFCPassportReader: - :commit: 04ede227cbfd377e2b4bc9b38f9a89eebdcab52f - :git: "git@github.com:selfxyz/NFCPassportReader.git" RCT-Folly: :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: @@ -1935,6 +1992,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-get-random-values" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" + react-native-webview: + :path: "../node_modules/react-native-webview" React-nativeconfig: :path: "../node_modules/react-native/ReactCommon" React-NativeModulesApple: @@ -1993,6 +2052,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-async-storage/async-storage" RNKeychain: :path: "../node_modules/react-native-keychain" + RNLocalize: + :path: "../node_modules/react-native-localize" RNReactNativeHapticFeedback: :path: "../node_modules/react-native-haptic-feedback" RNSVG: @@ -2002,11 +2063,6 @@ EXTERNAL SOURCES: Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" -CHECKOUT OPTIONS: - NFCPassportReader: - :commit: 04ede227cbfd377e2b4bc9b38f9a89eebdcab52f - :git: "git@github.com:selfxyz/NFCPassportReader.git" - SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 @@ -2015,10 +2071,9 @@ SPEC CHECKSUMS: fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6 glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 - Mixpanel-swift: e9bef28a9648faff384d5ba6f48ecc2787eb24c0 - mobile-sdk-alpha: 126edf71b65b5a9e294725e4353c2705fa0fd20d - NFCPassportReader: 48873f856f91215dbfa1eaaec20eae639672862e - OpenSSL-Universal: 84efb8a29841f2764ac5403e0c4119a28b713346 + lottie-ios: a881093fab623c467d3bce374367755c272bdd59 + lottie-react-native: cf02eef1b7d48972f9422cf24f2f7b9a49235a32 + mobile-sdk-alpha: 4daf7bbd7c8d7f4a71947234414c2c7f29777b1f QKMRZParser: 6b419b6f07d6bff6b50429b97de10846dc902c29 RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 @@ -2050,7 +2105,8 @@ SPEC CHECKSUMS: React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba - react-native-safe-area-context: 76bd6904253fc0f68fbc3d7f594b6a394d0ac34c + react-native-safe-area-context: a588bcb12a67155e7e16e7b322d4cc43794ed701 + react-native-webview: 416f3e4913deea20b2b7fdc5c44cb9ebf93509dc React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 @@ -2080,12 +2136,13 @@ SPEC CHECKSUMS: ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 RNCAsyncStorage: 87a74d13ba0128f853817e45e21c4051e1f2cd45 RNKeychain: 850638785745df5f70c37251130617a66ec82102 + RNLocalize: bb04044d17e11029cbd3d6df7629f703434498de RNReactNativeHapticFeedback: a4429a7c923cdd31d44c74417b8330d8e93737bd - RNSVG: 8dd938fb169dd81009b74c2334780d7d2a04a373 + RNSVG: bf688cc1423de273f23abc4c79acac9ad2d61d95 RNVectorIcons: c95fdae217b0ed388f2b4d7ed7a4edc457c1df47 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a -PODFILE CHECKSUM: fdd4646d0ec2813c3447acfca4303578e4346210 +PODFILE CHECKSUM: c5da90e01a1f2fbadddbc1fe2c36caacd4e29251 COCOAPODS: 1.16.2 diff --git a/packages/mobile-sdk-demo/package.json b/packages/mobile-sdk-demo/package.json index 9a9a9a281..dce8518ec 100644 --- a/packages/mobile-sdk-demo/package.json +++ b/packages/mobile-sdk-demo/package.json @@ -14,6 +14,7 @@ "clean": "rm -rf ios/build android/app/build android/build && cd android && ./gradlew clean && cd ..", "format": "yarn nice", "ia": "yarn install-app", + "postinstall": "npx patch-package --patch-dir ../../patches || true", "install-app": "yarn install && yarn prebuild && cd ios && pod install && cd ..", "preios": "yarn prebuild", "ios": "react-native run-ios", diff --git a/patches/react-native-svg+15.15.1.patch b/patches/react-native-svg+15.15.1.patch new file mode 100644 index 000000000..07ce8e430 --- /dev/null +++ b/patches/react-native-svg+15.15.1.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp +--- a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp ++++ b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp +@@ -27,8 +27,18 @@ void RNSVGLayoutableShadowNode::updatePosition() { + auto style = yogaNode_.style(); + style.setPositionType(yoga::PositionType::Absolute); + style.setPosition(yoga::Edge::All, yoga::Style::Length::points(0)); +- style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::percent(100)); +- style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::percent(100)); ++#if REACT_NATIVE_MINOR_VERSION >= 78 ++ style.setDimension( ++ yoga::Dimension::Width, yoga::StyleSizeLength::percent(100)); ++ style.setDimension( ++ yoga::Dimension::Height, yoga::StyleSizeLength::percent(100)); ++#elif REACT_NATIVE_MINOR_VERSION >= 77 ++ style.setDimension(yoga::Dimension::Width, yoga::StyleLength::percent(100)); ++ style.setDimension(yoga::Dimension::Height, yoga::StyleLength::percent(100)); ++#else ++ style.setDimension(yoga::Dimension::Width, yoga::value::percent(100)); ++ style.setDimension(yoga::Dimension::Height, yoga::value::percent(100)); ++#endif + yogaNode_.setStyle(style); + } + diff --git a/yarn.lock b/yarn.lock index 4b27c31c1..f735821ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9092,7 +9092,7 @@ __metadata: react-native-safe-area-context: "npm:^5.6.2" react-native-screens: "npm:4.15.3" react-native-sqlite-storage: "npm:^6.0.1" - react-native-svg: "npm:15.12.1" + react-native-svg: "npm:15.15.1" react-native-svg-transformer: "npm:^1.5.2" react-native-svg-web: "npm:1.0.9" react-native-url-polyfill: "npm:^3.0.0" @@ -30315,20 +30315,6 @@ __metadata: languageName: node linkType: hard -"react-native-svg@npm:15.12.1": - version: 15.12.1 - resolution: "react-native-svg@npm:15.12.1" - dependencies: - css-select: "npm:^5.1.0" - css-tree: "npm:^1.1.3" - warn-once: "npm:0.1.1" - peerDependencies: - react: "*" - react-native: "*" - checksum: 10c0/ed94b57007125c715283fc760438ac8eac0677ced3201f6e272a7cd4459f3fecb672a8eed2c32664e5b8e6e0367585353b0f83f99b231f603d75094bb052c01f - languageName: node - linkType: hard - "react-native-svg@npm:15.15.1": version: 15.15.1 resolution: "react-native-svg@npm:15.15.1" From 141fcb67c36f6dc5a04916c9dce856c2b7bf45b2 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Thu, 5 Feb 2026 14:25:00 -0800 Subject: [PATCH 09/13] chore: accept xcode suggestions 2026 02 (#1711) * xcode suggestionsn rd1 * disable sandbox --- .../OpenPassport/OpenPassport.entitlements | 6 +- .../OpenPassportDebug.entitlements | 2 - app/ios/Self.xcodeproj/project.pbxproj | 60 +++++++++++-------- .../xcschemes/OpenPassport.xcscheme | 2 +- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/app/ios/OpenPassport/OpenPassport.entitlements b/app/ios/OpenPassport/OpenPassport.entitlements index 083124efd..514a3d982 100644 --- a/app/ios/OpenPassport/OpenPassport.entitlements +++ b/app/ios/OpenPassport/OpenPassport.entitlements @@ -2,8 +2,8 @@ - aps-environment - production + aps-environment + production com.apple.developer.associated-appclip-app-identifiers 5B29R5LYHQ.com.warroom.proofofpassport.Clip @@ -37,7 +37,5 @@ com.apple.developer.ubiquity-kvstore-identifier $(TeamIdentifierPrefix)$(CFBundleIdentifier) - com.apple.security.device.camera - diff --git a/app/ios/OpenPassport/OpenPassportDebug.entitlements b/app/ios/OpenPassport/OpenPassportDebug.entitlements index b4fa2d725..50490a0e0 100644 --- a/app/ios/OpenPassport/OpenPassportDebug.entitlements +++ b/app/ios/OpenPassport/OpenPassportDebug.entitlements @@ -39,7 +39,5 @@ com.apple.developer.ubiquity-kvstore-identifier $(TeamIdentifierPrefix)$(CFBundleIdentifier) - com.apple.security.device.camera - diff --git a/app/ios/Self.xcodeproj/project.pbxproj b/app/ios/Self.xcodeproj/project.pbxproj index 8d257f24b..fee740bae 100644 --- a/app/ios/Self.xcodeproj/project.pbxproj +++ b/app/ios/Self.xcodeproj/project.pbxproj @@ -236,7 +236,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1620; + LastUpgradeCheck = 2620; TargetAttributes = { 13B07F861A680F5B00A75B9A = { LastSwiftMigration = 1430; @@ -306,10 +306,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-resources.sh\"\n"; @@ -345,10 +349,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-frameworks.sh\"\n"; @@ -363,6 +371,8 @@ "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); name = "[CP-User] [RNFB] Core Configuration"; + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; @@ -427,14 +437,15 @@ baseConfigurationReference = E67B3FF985359E36919C9E20 /* Pods-Self.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 189; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 5B29R5LYHQ; - ENABLE_BITCODE = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 189; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_APP_SANDBOX = NO; + ENABLE_BITCODE = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", @@ -568,13 +579,14 @@ 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 823EAA08DBB5F61225E922CA /* Pods-Self.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements; - CURRENT_PROJECT_VERSION = 189; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 5B29R5LYHQ; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements; + CURRENT_PROJECT_VERSION = 189; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_APP_SANDBOX = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", @@ -736,8 +748,10 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CXX = ""; + DEVELOPMENT_TEAM = 5B29R5LYHQ; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -793,12 +807,10 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; USE_HERMES = true; }; @@ -837,8 +849,10 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; CXX = ""; + DEVELOPMENT_TEAM = 5B29R5LYHQ; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; @@ -886,12 +900,10 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; USE_HERMES = true; VALIDATE_PRODUCT = YES; diff --git a/app/ios/Self.xcodeproj/xcshareddata/xcschemes/OpenPassport.xcscheme b/app/ios/Self.xcodeproj/xcshareddata/xcschemes/OpenPassport.xcscheme index 0ca2ce030..f3765a840 100644 --- a/app/ios/Self.xcodeproj/xcshareddata/xcschemes/OpenPassport.xcscheme +++ b/app/ios/Self.xcodeproj/xcshareddata/xcschemes/OpenPassport.xcscheme @@ -1,6 +1,6 @@ Date: Fri, 6 Feb 2026 22:27:50 +1000 Subject: [PATCH 10/13] Feat/add logo confirmation screen (#1666) * feat: initial logoConfirmation screen * fix: center text in buttons * fix: ensure biometric logo is not cropped * fix: add spacing to logo confirm screen, consistent padding for buttons * feat: add popup to logo confirmation screen * fix: handle Sumsub cancellation correctly in Other ID flow * feat: add pathway from logo confirmation -> sumsub -> success * feat: add document type pre-selection for Sumsub SDK * feat: add KycFailureScreen for when user clicks on failure notification * feat: add KycConnectionErrorScreen for sumsub_initiation error * refactor: edit RegistrationFallbackScreen for new design Now only to be used for MRZ/NFC scan errors, instead of kyc connection/registration issues. Also updated to newest design for screen from Figma * refactor: update AadhaarUploadErrorScreen for new design * fix: removed unused countryCode variable and useRoute import * fix: Sorted imports (moved kyc after documents/selection imports) fix CI * chore: lint/prettier * fix: CI error * refactor: Split RegistrationFallbackScreen into MRZ/NFC error versions * feat: add link from RegistrationFallbackNFCScreen -> DocumentNFCTrouble Clicking on the question mark now takes user to this screen that can help them diagnose issue * fix: on clicking question mark, dismiss screen returns to correct screen * chore: yarn prettier * test: fix failing test for CI --- app/src/assets/icons/epassport_logo.svg | 1 + app/src/assets/icons/shield_error.svg | 3 + app/src/hooks/useSumsubLauncher.ts | 26 +- app/src/integrations/sumsub/sumsubService.ts | 44 ++- app/src/navigation/documents.ts | 55 ++- app/src/navigation/types.ts | 21 +- .../notificationTrackingProvider.tsx | 29 +- app/src/providers/selfClientProvider.tsx | 42 ++- .../aadhaar/AadhaarUploadErrorScreen.tsx | 248 ++++++++++--- .../scanning/DocumentCameraScreen.tsx | 29 +- .../scanning/DocumentCameraTroubleScreen.tsx | 2 +- .../scanning/DocumentNFCScanScreen.tsx | 3 +- .../scanning/DocumentNFCTroubleScreen.tsx | 14 +- .../RegistrationFallbackMRZScreen.tsx | 256 ++++++++++++++ .../RegistrationFallbackNFCScreen.tsx | 282 +++++++++++++++ .../scanning/RegistrationFallbackScreen.tsx | 326 ------------------ .../selection/LogoConfirmationScreen.tsx | 152 ++++++++ .../screens/kyc/KycConnectionErrorScreen.tsx | 175 ++++++++++ app/src/screens/kyc/KycFailureScreen.tsx | 126 +++++++ app/tests/src/navigation.test.tsx | 6 +- .../notificationTrackingProvider.test.tsx | 17 +- .../src/components/buttons/AbstractButton.tsx | 1 + .../onboarding/logo-confirmation-screen.tsx | 104 ++++++ packages/mobile-sdk-alpha/src/index.ts | 2 + packages/mobile-sdk-alpha/src/types/events.ts | 22 ++ 25 files changed, 1536 insertions(+), 450 deletions(-) create mode 100644 app/src/assets/icons/epassport_logo.svg create mode 100644 app/src/assets/icons/shield_error.svg create mode 100644 app/src/screens/documents/scanning/RegistrationFallbackMRZScreen.tsx create mode 100644 app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx delete mode 100644 app/src/screens/documents/scanning/RegistrationFallbackScreen.tsx create mode 100644 app/src/screens/documents/selection/LogoConfirmationScreen.tsx create mode 100644 app/src/screens/kyc/KycConnectionErrorScreen.tsx create mode 100644 app/src/screens/kyc/KycFailureScreen.tsx create mode 100644 packages/mobile-sdk-alpha/src/flows/onboarding/logo-confirmation-screen.tsx diff --git a/app/src/assets/icons/epassport_logo.svg b/app/src/assets/icons/epassport_logo.svg new file mode 100644 index 000000000..3c4c0073d --- /dev/null +++ b/app/src/assets/icons/epassport_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/assets/icons/shield_error.svg b/app/src/assets/icons/shield_error.svg new file mode 100644 index 000000000..273e84aea --- /dev/null +++ b/app/src/assets/icons/shield_error.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/hooks/useSumsubLauncher.ts b/app/src/hooks/useSumsubLauncher.ts index c5cad2260..30d4975ae 100644 --- a/app/src/hooks/useSumsubLauncher.ts +++ b/app/src/hooks/useSumsubLauncher.ts @@ -12,11 +12,7 @@ import { fetchAccessToken, launchSumsub } from '@/integrations/sumsub'; import type { SumsubResult } from '@/integrations/sumsub/types'; import type { RootStackParamList } from '@/navigation'; -export type FallbackErrorSource = - | 'mrz_scan_failed' - | 'nfc_scan_failed' - | 'sumsub_initialization' - | 'sumsub_verification'; +export type FallbackErrorSource = 'mrz_scan_failed' | 'nfc_scan_failed'; export interface UseSumsubLauncherOptions { /** @@ -90,10 +86,12 @@ export const useSumsubLauncher = (options: UseSumsubLauncherOptions) => { if (onError) { await onError(safeError, result); } else { - navigation.navigate('RegistrationFallback', { - errorSource, - countryCode, - }); + // Navigate to the appropriate fallback screen based on error source + if (errorSource === 'mrz_scan_failed') { + navigation.navigate('RegistrationFallbackMRZ', { countryCode }); + } else { + navigation.navigate('RegistrationFallbackNFC', { countryCode }); + } } return; } @@ -110,10 +108,12 @@ export const useSumsubLauncher = (options: UseSumsubLauncherOptions) => { if (onError) { await onError(safeError); } else { - navigation.navigate('RegistrationFallback', { - errorSource, - countryCode, - }); + // Navigate to the appropriate fallback screen based on error source + if (errorSource === 'mrz_scan_failed') { + navigation.navigate('RegistrationFallbackMRZ', { countryCode }); + } else { + navigation.navigate('RegistrationFallbackNFC', { countryCode }); + } } } finally { setIsLoading(false); diff --git a/app/src/integrations/sumsub/sumsubService.ts b/app/src/integrations/sumsub/sumsubService.ts index 5df563d87..20f191399 100644 --- a/app/src/integrations/sumsub/sumsubService.ts +++ b/app/src/integrations/sumsub/sumsubService.ts @@ -5,15 +5,30 @@ import { SUMSUB_TEE_URL } from '@env'; import SNSMobileSDK from '@sumsub/react-native-mobilesdk-module'; +import { alpha2ToAlpha3 } from '@selfxyz/common'; + import type { AccessTokenResponse, SumsubResult, } from '@/integrations/sumsub/types'; +// Maps Self document type codes to Sumsub document types +type SelfDocumentType = 'p' | 'i'; +type SumsubDocumentType = 'PASSPORT' | 'ID_CARD'; + +const DOCUMENT_TYPE_MAP: Record = { + p: 'PASSPORT', + i: 'ID_CARD', +}; + export interface SumsubConfig { accessToken: string; locale?: string; debug?: boolean; + /** Self document type code ('p' for passport, 'i' for ID card) */ + documentType?: SelfDocumentType; + /** ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB') */ + countryCode?: string; onStatusChanged?: (prevStatus: string, newStatus: string) => void; onEvent?: (eventType: string, payload: unknown) => void; } @@ -78,7 +93,7 @@ export const fetchAccessToken = async ( export const launchSumsub = async ( config: SumsubConfig, ): Promise => { - const sdk = SNSMobileSDK.init(config.accessToken, async () => { + let sdk = SNSMobileSDK.init(config.accessToken, async () => { // Token refresh not implemented for test flow throw new Error( 'Sumsub token expired - refresh not implemented for test flow', @@ -101,8 +116,29 @@ export const launchSumsub = async ( }) .withDebug(config.debug ?? __DEV__) .withLocale(config.locale ?? 'en') - .withAnalyticsEnabled(true) // Device Intelligence requires this - .build(); + .withAnalyticsEnabled(true); // Device Intelligence requires this - return sdk.launch(); + // Pre-select document type and country if provided + // This skips the document selection step in Sumsub + if (config.documentType && config.countryCode) { + const sumsubDocType = DOCUMENT_TYPE_MAP[config.documentType]; + // Handle both 2-letter (US) and 3-letter (USA) country codes + // alpha2ToAlpha3 returns undefined for 3-letter codes, so use the original if conversion fails + const alpha3Country = + alpha2ToAlpha3(config.countryCode) ?? config.countryCode; + + if (sumsubDocType && alpha3Country) { + console.log( + `[Sumsub] Pre-selecting document: ${sumsubDocType} from ${alpha3Country}`, + ); + sdk = sdk.withPreferredDocumentDefinitions({ + IDENTITY: { + idDocType: sumsubDocType, + country: alpha3Country, + }, + }); + } + } + + return sdk.build().launch(); }; diff --git a/app/src/navigation/documents.ts b/app/src/navigation/documents.ts index a80aaeecc..32aa325e3 100644 --- a/app/src/navigation/documents.ts +++ b/app/src/navigation/documents.ts @@ -19,11 +19,15 @@ import DocumentCameraTroubleScreen from '@/screens/documents/scanning/DocumentCa import DocumentNFCMethodSelectionScreen from '@/screens/documents/scanning/DocumentNFCMethodSelectionScreen'; import DocumentNFCScanScreen from '@/screens/documents/scanning/DocumentNFCScanScreen'; import DocumentNFCTroubleScreen from '@/screens/documents/scanning/DocumentNFCTroubleScreen'; -import RegistrationFallbackScreen from '@/screens/documents/scanning/RegistrationFallbackScreen'; +import RegistrationFallbackMRZScreen from '@/screens/documents/scanning/RegistrationFallbackMRZScreen'; +import RegistrationFallbackNFCScreen from '@/screens/documents/scanning/RegistrationFallbackNFCScreen'; import ConfirmBelongingScreen from '@/screens/documents/selection/ConfirmBelongingScreen'; import CountryPickerScreen from '@/screens/documents/selection/CountryPickerScreen'; import DocumentOnboardingScreen from '@/screens/documents/selection/DocumentOnboardingScreen'; import IDPickerScreen from '@/screens/documents/selection/IDPickerScreen'; +import LogoConfirmationScreen from '@/screens/documents/selection/LogoConfirmationScreen'; +import KycConnectionErrorScreen from '@/screens/kyc/KycConnectionErrorScreen'; +import KycFailureScreen from '@/screens/kyc/KycFailureScreen'; const documentsScreens = { DocumentCamera: { @@ -94,6 +98,16 @@ const documentsScreens = { documentTypes: [], }, }, + LogoConfirmation: { + screen: LogoConfirmationScreen, + options: { + headerShown: false, + } as NativeStackNavigationOptions, + initialParams: { + documentType: '', + countryCode: '', + }, + }, ConfirmBelonging: { screen: ConfirmBelongingScreen, options: { @@ -148,22 +162,49 @@ const documentsScreens = { AadhaarUploadError: { screen: AadhaarUploadErrorScreen, options: { - title: 'AADHAAR REGISTRATION', - header: AadhaarNavBar, - headerBackVisible: false, + headerShown: false, } as NativeStackNavigationOptions, initialParams: { errorType: 'general', }, }, - RegistrationFallback: { - screen: RegistrationFallbackScreen, + RegistrationFallbackMRZ: { + screen: RegistrationFallbackMRZScreen, options: { title: 'REGISTRATION', headerShown: false, } as NativeStackNavigationOptions, initialParams: { - errorSource: 'sumsub_initialization', + countryCode: '', + }, + }, + RegistrationFallbackNFC: { + screen: RegistrationFallbackNFCScreen, + options: { + title: 'REGISTRATION', + headerShown: false, + } as NativeStackNavigationOptions, + initialParams: { + countryCode: '', + }, + }, + KycFailure: { + screen: KycFailureScreen, + options: { + headerShown: false, + animation: 'fade', + } as NativeStackNavigationOptions, + initialParams: { + countryCode: '', + canRetry: true, + }, + }, + KycConnectionError: { + screen: KycConnectionErrorScreen, + options: { + headerShown: false, + } as NativeStackNavigationOptions, + initialParams: { countryCode: '', }, }, diff --git a/app/src/navigation/types.ts b/app/src/navigation/types.ts index c0e7de6dc..fe158edd3 100644 --- a/app/src/navigation/types.ts +++ b/app/src/navigation/types.ts @@ -82,6 +82,10 @@ export type DocumentRoutesParamList = { countryCode: string; documentTypes: string[]; }; + LogoConfirmation: { + documentType: string; + countryCode: string; + }; ConfirmBelonging: | { documentCategory?: DocumentCategory; @@ -157,18 +161,23 @@ export type OnboardingRoutesParamList = { userId?: string; } | undefined; + KycFailure: { + countryCode?: string; + canRetry?: boolean; + }; + KycConnectionError: { + countryCode?: string; + }; }; // ============================================================================= // Registration Fallback Screens // ============================================================================= export type RegistrationRoutesParamList = { - RegistrationFallback: { - errorSource: - | 'mrz_scan_failed' - | 'nfc_scan_failed' - | 'sumsub_initialization' - | 'sumsub_verification'; + RegistrationFallbackMRZ: { + countryCode: string; + }; + RegistrationFallbackNFC: { countryCode: string; }; }; diff --git a/app/src/providers/notificationTrackingProvider.tsx b/app/src/providers/notificationTrackingProvider.tsx index 5462f291c..834c5088c 100644 --- a/app/src/providers/notificationTrackingProvider.tsx +++ b/app/src/providers/notificationTrackingProvider.tsx @@ -30,17 +30,26 @@ const executeNotificationNavigation = ( const status = remoteMessage.data?.status; // Handle KYC result notifications - if (notificationType === 'kyc_result' && status === 'approved') { - navigationRef.navigate('KYCVerified', { - status: String(status), - userId: remoteMessage.data?.user_id - ? String(remoteMessage.data.user_id) - : undefined, - }); - return true; + if (notificationType === 'kyc_result') { + if (status === 'approved') { + navigationRef.navigate('KYCVerified', { + status: String(status), + userId: remoteMessage.data?.user_id + ? String(remoteMessage.data.user_id) + : undefined, + }); + return true; + } else if (status === 'rejected') { + navigationRef.navigate('KycFailure', { + canRetry: false, + }); + return true; + } else if (status === 'retry') { + // Take user directly to verification flow to retry + navigationRef.navigate('CountryPicker'); + return true; + } } - // Add handling for other notification types here as needed - // For retry/rejected statuses, could navigate to appropriate screens in future return true; // Navigation handled (or not applicable) }; diff --git a/app/src/providers/selfClientProvider.tsx b/app/src/providers/selfClientProvider.tsx index 6bfbc9c2e..dfa74c787 100644 --- a/app/src/providers/selfClientProvider.tsx +++ b/app/src/providers/selfClientProvider.tsx @@ -284,8 +284,7 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { }); addListener(SdkEvents.DOCUMENT_MRZ_READ_FAILURE, () => { - navigateIfReady('RegistrationFallback', { - errorSource: 'mrz_scan_failed', + navigateIfReady('RegistrationFallbackMRZ', { countryCode: currentCountryCode, }); }); @@ -318,10 +317,12 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { if (navigationRef.isReady()) { switch (documentType) { case 'p': - navigationRef.navigate('DocumentOnboarding'); - break; case 'i': - navigationRef.navigate('DocumentOnboarding'); + // Navigate to logo confirmation screen for biometric IDs + navigationRef.navigate('LogoConfirmation', { + documentType, + countryCode, + }); break; case 'a': if (countryCode) { @@ -348,8 +349,21 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { accessToken: accessToken.token, }); - // User cancelled - return silently - if (!result.success && result.status === 'Interrupted') { + console.log('[Sumsub] Result:', JSON.stringify(result)); + + // User cancelled/dismissed without completing verification + // Status values: 'Initial' (never started), 'Incomplete' (started but not finished), + // 'Interrupted' (explicitly cancelled) + const cancelledStatuses = [ + 'Initial', + 'Incomplete', + 'Interrupted', + ]; + if (cancelledStatuses.includes(result.status)) { + console.log( + '[Sumsub] User cancelled or closed without completing, status:', + result.status, + ); return; } @@ -370,15 +384,20 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { } // Guard navigation call after async operations if (navigationRef.isReady()) { - navigationRef.navigate('RegistrationFallback', { - errorSource: 'sumsub_verification', + navigationRef.navigate('KycFailure', { countryCode, + canRetry: true, }); } return; } - // Success case: navigate to KYC success screen + // User completed verification (status: 'Pending', 'Approved', etc.) + // Navigate to KYC success screen + console.log( + '[Sumsub] Verification submitted, status:', + result.status, + ); if (navigationRef.isReady()) { navigationRef.navigate('KycSuccess', { userId: accessToken.userId, @@ -391,8 +410,7 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => { console.error('Error in KYC flow:', safeInitError); // Guard navigation call after async operations if (navigationRef.isReady()) { - navigationRef.navigate('RegistrationFallback', { - errorSource: 'sumsub_initialization', + navigationRef.navigate('KycConnectionError', { countryCode, }); } diff --git a/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx b/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx index 35e8d1726..9fe27ecc7 100644 --- a/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx +++ b/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx @@ -2,25 +2,34 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import React from 'react'; -import { XStack, YStack } from 'tamagui'; +import React, { useCallback } from 'react'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Button, XStack, YStack } from 'tamagui'; import type { RouteProp } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { Image } from '@tamagui/lucide-icons'; import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; -import { BodyText, PrimaryButton } from '@selfxyz/mobile-sdk-alpha/components'; +import { BodyText } from '@selfxyz/mobile-sdk-alpha/components'; import { AadhaarEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics'; import { black, + cyan300, slate100, slate200, + slate300, slate500, white, } from '@selfxyz/mobile-sdk-alpha/constants/colors'; +import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts'; import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks'; -import { getErrorMessages } from '@selfxyz/mobile-sdk-alpha/onboarding/import-aadhaar'; import WarningIcon from '@/assets/images/warning.svg'; +import { NavBar } from '@/components/navbar/BaseNavBar'; +import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; +import { buttonTap } from '@/integrations/haptics'; +import type { RootStackParamList } from '@/navigation'; import { extraYPadding } from '@/utils/styleUtils'; type AadhaarUploadErrorRouteParams = { @@ -32,81 +41,214 @@ type AadhaarUploadErrorRoute = RouteProp< string >; +const getErrorMessages = ( + errorType: 'general' | 'expired', +): { title: string; description: string } => { + switch (errorType) { + case 'expired': + return { + title: 'Your Aadhaar document has expired', + description: 'Please upload a valid Aadhaar document', + }; + case 'general': + default: + return { + title: 'There was a problem reading the code', + description: 'Make sure the QR code is valid and try again', + }; + } +}; + const AadhaarUploadErrorScreen: React.FC = () => { + const insets = useSafeAreaInsets(); const paddingBottom = useSafeBottomPadding(extraYPadding + 35); - const navigation = useNavigation(); + const navigation = + useNavigation>(); const route = useRoute(); const { trackEvent } = useSelfClient(); - const errorType = route.params?.errorType || 'general'; + const errorType = route.params?.errorType || 'general'; const { title, description } = getErrorMessages(errorType); + const { launchSumsubVerification, isLoading: isRetrying } = useSumsubLauncher( + { + countryCode: 'IND', + errorSource: 'mrz_scan_failed', // Use a compatible error source + onCancel: () => { + navigation.goBack(); + }, + onError: () => { + // Stay on this screen - user can try again + }, + onSuccess: () => { + // Success - provider handles its own success UI + }, + }, + ); + + const handleClose = useCallback(() => { + buttonTap(); + navigation.goBack(); + }, [navigation]); + + const handleTryAgain = useCallback(() => { + trackEvent(AadhaarEvents.RETRY_BUTTON_PRESSED, { errorType }); + navigation.goBack(); + }, [errorType, navigation, trackEvent]); + + const handleTryAlternative = useCallback(async () => { + trackEvent(AadhaarEvents.HELP_BUTTON_PRESSED, { errorType }); + await launchSumsubVerification(); + }, [errorType, launchSumsubVerification, trackEvent]); + return ( - - + - + + + AADHAAR REGISTRATION + + {/* Invisible spacer to balance header */} + + + + {/* Progress Bar - Step 2 for Aadhaar upload */} + + + {[1, 2, 3, 4].map(step => ( + + ))} + + {/* Main Content Area */} + + {/* Warning Icon */} + + + + + + + {/* Error Message and Retry Button */} + + + + {title} + + + {description} + + + + {/* Retry Button - Primary style with icon */} + + + + + {/* Bottom Section */} - - {title} - + {/* Secondary Button - White fill, black text, rounded */} + + + {/* Footer Text - Not italic */} - {description} + Registering with alternative methods may take longer to verify your + document. - - - - - { - trackEvent(AadhaarEvents.RETRY_BUTTON_PRESSED, { errorType }); - // Navigate back to upload screen to try again - navigation.goBack(); - }} - > - Try Again - - - {/* - { - trackEvent(AadhaarEvents.HELP_BUTTON_PRESSED, { errorType }); - // TODO: Implement help functionality - }} - > - Need Help? - - */} - - ); }; diff --git a/app/src/screens/documents/scanning/DocumentCameraScreen.tsx b/app/src/screens/documents/scanning/DocumentCameraScreen.tsx index 11115061a..cf47c6ead 100644 --- a/app/src/screens/documents/scanning/DocumentCameraScreen.tsx +++ b/app/src/screens/documents/scanning/DocumentCameraScreen.tsx @@ -2,10 +2,11 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { StyleSheet } from 'react-native'; import { View, XStack, YStack } from 'tamagui'; -import { useIsFocused } from '@react-navigation/native'; +import { useIsFocused, useNavigation } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { DelayedLottieView, @@ -33,21 +34,45 @@ import { import passportScanAnimation from '@/assets/animations/passport_scan.json'; import Scan from '@/assets/icons/passport_camera_scan.svg'; import { PassportCamera } from '@/components/native/PassportCamera'; +import { useErrorInjection } from '@/hooks/useErrorInjection'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; +import type { RootStackParamList } from '@/navigation'; import { getDocumentScanPrompt } from '@/utils/documentAttributes'; const DocumentCameraScreen: React.FC = () => { const isFocused = useIsFocused(); + const navigation = + useNavigation>(); const selfClient = useSelfClient(); const selectedDocumentType = selfClient.useMRZStore( state => state.documentType, ); + const countryCode = selfClient.useMRZStore(state => state.countryCode); + const { shouldInjectError } = useErrorInjection(); // Add a ref to track when the camera screen is mounted const scanStartTimeRef = useRef(Date.now()); const { onPassportRead } = useReadMRZ(scanStartTimeRef); + // Dev-only: Auto-trigger MRZ error after short delay if error injection is enabled + useEffect(() => { + if ( + shouldInjectError('mrz_invalid_format') || + shouldInjectError('mrz_unknown_error') + ) { + const timer = setTimeout(() => { + console.log( + '[DEV] Injecting MRZ error - navigating to fallback screen', + ); + navigation.navigate('RegistrationFallbackMRZ', { + countryCode: countryCode || '', + }); + }, 1500); // 1.5 second delay to show camera briefly + return () => clearTimeout(timer); + } + }, [shouldInjectError, navigation, countryCode]); + const scanPrompt = getDocumentScanPrompt(selectedDocumentType); const navigateToHome = useHapticNavigation('Home', { diff --git a/app/src/screens/documents/scanning/DocumentCameraTroubleScreen.tsx b/app/src/screens/documents/scanning/DocumentCameraTroubleScreen.tsx index 684d7b8f5..a0a23c2f1 100644 --- a/app/src/screens/documents/scanning/DocumentCameraTroubleScreen.tsx +++ b/app/src/screens/documents/scanning/DocumentCameraTroubleScreen.tsx @@ -58,7 +58,7 @@ const DocumentCameraTroubleScreen: React.FC = () => { const kycEnabled = useSettingStore(state => state.kycEnabled); const { launchSumsubVerification, isLoading } = useSumsubLauncher({ countryCode, - errorSource: 'sumsub_initialization', + errorSource: 'mrz_scan_failed', }); // error screen, flush analytics diff --git a/app/src/screens/documents/scanning/DocumentNFCScanScreen.tsx b/app/src/screens/documents/scanning/DocumentNFCScanScreen.tsx index 4f95a70d2..a6cd8fc61 100644 --- a/app/src/screens/documents/scanning/DocumentNFCScanScreen.tsx +++ b/app/src/screens/documents/scanning/DocumentNFCScanScreen.tsx @@ -191,8 +191,7 @@ const DocumentNFCScanScreen: React.FC = () => { }, { message: sanitizeErrorMessage(message) }, ); - navigation.navigate('RegistrationFallback', { - errorSource: 'nfc_scan_failed', + navigation.navigate('RegistrationFallbackNFC', { countryCode, }); }, diff --git a/app/src/screens/documents/scanning/DocumentNFCTroubleScreen.tsx b/app/src/screens/documents/scanning/DocumentNFCTroubleScreen.tsx index a75e96c36..8636754ee 100644 --- a/app/src/screens/documents/scanning/DocumentNFCTroubleScreen.tsx +++ b/app/src/screens/documents/scanning/DocumentNFCTroubleScreen.tsx @@ -2,10 +2,11 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { View } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { YStack } from 'tamagui'; +import { useNavigation } from '@react-navigation/native'; import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; import { Caption, SecondaryButton } from '@selfxyz/mobile-sdk-alpha/components'; @@ -16,6 +17,7 @@ import Tips from '@/components/Tips'; import { useFeedbackAutoHide } from '@/hooks/useFeedbackAutoHide'; import useHapticNavigation from '@/hooks/useHapticNavigation'; import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; +import { selectionChange } from '@/integrations/haptics'; import SimpleScrolledTitleLayout from '@/layouts/SimpleScrolledTitleLayout'; import { flushAllAnalytics } from '@/services/analytics'; import { openSupportForm, SUPPORT_FORM_BUTTON_TEXT } from '@/services/support'; @@ -49,7 +51,11 @@ const tips: TipProps[] = [ ]; const DocumentNFCTroubleScreen: React.FC = () => { - const go = useHapticNavigation('DocumentNFCScan', { action: 'cancel' }); + const navigation = useNavigation(); + const handleDismiss = useCallback(() => { + selectionChange(); + navigation.goBack(); + }, [navigation]); const goToNFCMethodSelection = useHapticNavigation( 'DocumentNFCMethodSelection', ); @@ -59,7 +65,7 @@ const DocumentNFCTroubleScreen: React.FC = () => { const kycEnabled = useSettingStore(state => state.kycEnabled); const { launchSumsubVerification, isLoading } = useSumsubLauncher({ countryCode, - errorSource: 'sumsub_initialization', + errorSource: 'nfc_scan_failed', }); useFeedbackAutoHide(); @@ -78,7 +84,7 @@ const DocumentNFCTroubleScreen: React.FC = () => { return ( , + string +>; + +const getHeaderTitle = (documentType: string): string => { + switch (documentType) { + case 'p': + return 'PASSPORT REGISTRATION'; + case 'i': + return 'ID CARD REGISTRATION'; + default: + return 'DOCUMENT REGISTRATION'; + } +}; + +const RegistrationFallbackMRZScreen: React.FC = () => { + const insets = useSafeAreaInsets(); + const paddingBottom = useSafeBottomPadding(extraYPadding + 35); + const navigation = + useNavigation>(); + const route = useRoute(); + const selfClient = useSelfClient(); + const { trackEvent, useMRZStore } = selfClient; + const storeCountryCode = useMRZStore(state => state.countryCode); + const documentType = useMRZStore(state => state.documentType); + + // Use country code from route params, or fall back to MRZ store + const countryCode = route.params?.countryCode || storeCountryCode || ''; + + const headerTitle = getHeaderTitle(documentType); + + const { launchSumsubVerification, isLoading: isRetrying } = useSumsubLauncher( + { + countryCode, + errorSource: 'mrz_scan_failed', + onCancel: () => { + navigation.goBack(); + }, + onError: (_error, _result) => { + // Stay on this screen - user can try again + // Error is already logged in the hook + }, + onSuccess: () => { + // Success - provider handles its own success UI + // The screen will be navigated away by the provider's flow + }, + }, + ); + + const handleClose = useCallback(() => { + buttonTap(); + navigation.goBack(); + }, [navigation]); + + const handleTryAlternative = useCallback(async () => { + trackEvent('REGISTRATION_FALLBACK_TRY_ALTERNATIVE', { + errorSource: 'mrz_scan_failed', + }); + await launchSumsubVerification(); + }, [launchSumsubVerification, trackEvent]); + + const handleRetryOriginal = useCallback(() => { + trackEvent('REGISTRATION_FALLBACK_RETRY_ORIGINAL', { + errorSource: 'mrz_scan_failed', + }); + navigation.navigate('DocumentCamera'); + }, [navigation, trackEvent]); + + return ( + + {/* Header */} + + + + + {headerTitle} + + {/* Invisible spacer to balance header */} + + + + {/* Progress Bar - Step 2 for MRZ */} + + + {[1, 2, 3, 4].map(step => ( + + ))} + + + + + {/* Main Content Area */} + + {/* Warning Icon */} + + + + + + + {/* Error Message and Retry Button */} + + + + We couldn't read your document's MRZ + + + Make sure the machine-readable zone at the bottom is clearly + visible and try again + + + + {/* Retry Button - Primary style with very rounded corners */} + + + + + {/* Bottom Section */} + + {/* Secondary Button - White fill, black text, rounded */} + + + {/* Footer Text - Not italic */} + + Registering with alternative methods may take longer to verify your + document. + + + + ); +}; + +export default RegistrationFallbackMRZScreen; diff --git a/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx b/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx new file mode 100644 index 000000000..bbe4de02e --- /dev/null +++ b/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx @@ -0,0 +1,282 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Button, XStack, YStack } from 'tamagui'; +import type { RouteProp } from '@react-navigation/native'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; +import { BodyText } from '@selfxyz/mobile-sdk-alpha/components'; +import { + black, + blue600, + cyan300, + slate100, + slate200, + slate300, + slate500, + white, +} from '@selfxyz/mobile-sdk-alpha/constants/colors'; +import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts'; +import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks'; + +import WarningIcon from '@/assets/images/warning.svg'; +import { NavBar } from '@/components/navbar/BaseNavBar'; +import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; +import { buttonTap } from '@/integrations/haptics'; +import type { RootStackParamList } from '@/navigation'; +import { extraYPadding } from '@/utils/styleUtils'; + +type RegistrationFallbackNFCRouteParams = { + countryCode: string; +}; + +type RegistrationFallbackNFCRoute = RouteProp< + Record, + string +>; + +const getHeaderTitle = (documentType: string): string => { + switch (documentType) { + case 'p': + return 'PASSPORT REGISTRATION'; + case 'i': + return 'ID CARD REGISTRATION'; + default: + return 'DOCUMENT REGISTRATION'; + } +}; + +const RegistrationFallbackNFCScreen: React.FC = () => { + const insets = useSafeAreaInsets(); + const paddingBottom = useSafeBottomPadding(extraYPadding + 35); + const navigation = + useNavigation>(); + const route = useRoute(); + const selfClient = useSelfClient(); + const { trackEvent, useMRZStore } = selfClient; + const storeCountryCode = useMRZStore(state => state.countryCode); + const documentType = useMRZStore(state => state.documentType); + + // Use country code from route params, or fall back to MRZ store + const countryCode = route.params?.countryCode || storeCountryCode || ''; + + const headerTitle = getHeaderTitle(documentType); + + const { launchSumsubVerification, isLoading: isRetrying } = useSumsubLauncher( + { + countryCode, + errorSource: 'nfc_scan_failed', + onCancel: () => { + navigation.goBack(); + }, + onError: (_error, _result) => { + // Stay on this screen - user can try again + // Error is already logged in the hook + }, + onSuccess: () => { + // Success - provider handles its own success UI + // The screen will be navigated away by the provider's flow + }, + }, + ); + + const handleClose = useCallback(() => { + buttonTap(); + navigation.goBack(); + }, [navigation]); + + const handleHelp = useCallback(() => { + buttonTap(); + navigation.navigate('DocumentNFCTrouble'); + }, [navigation]); + + const handleTryAlternative = useCallback(async () => { + trackEvent('REGISTRATION_FALLBACK_TRY_ALTERNATIVE', { + errorSource: 'nfc_scan_failed', + }); + await launchSumsubVerification(); + }, [launchSumsubVerification, trackEvent]); + + const handleRetryOriginal = useCallback(() => { + trackEvent('REGISTRATION_FALLBACK_RETRY_ORIGINAL', { + errorSource: 'nfc_scan_failed', + }); + navigation.navigate('DocumentNFCScan', {}); + }, [navigation, trackEvent]); + + return ( + + {/* Header */} + + + + + {headerTitle} + + + + + {/* Progress Bar - Step 3 for NFC */} + + + {[1, 2, 3, 4].map(step => ( + + ))} + + + + + {/* Main Content Area */} + + {/* Warning Icon */} + + + + + + + {/* Error Message and Retry Button */} + + + + There was a problem reading the chip + + + Make sure NFC is enabled and try again + + + + {/* Retry Button - Primary style with very rounded corners */} + + + + + {/* Bottom Section */} + + {/* Secondary Button - White fill, black text, rounded */} + + + {/* Footer Text - Not italic */} + + Registering with alternative methods may take longer to verify your + document. + + + + ); +}; + +export default RegistrationFallbackNFCScreen; diff --git a/app/src/screens/documents/scanning/RegistrationFallbackScreen.tsx b/app/src/screens/documents/scanning/RegistrationFallbackScreen.tsx deleted file mode 100644 index 7a204e0bd..000000000 --- a/app/src/screens/documents/scanning/RegistrationFallbackScreen.tsx +++ /dev/null @@ -1,326 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. -// SPDX-License-Identifier: BUSL-1.1 -// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. - -import React, { useCallback } from 'react'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Button, XStack, YStack } from 'tamagui'; -import type { RouteProp } from '@react-navigation/native'; -import { useNavigation, useRoute } from '@react-navigation/native'; -import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { HelpCircle, X } from '@tamagui/lucide-icons'; - -import { useSelfClient } from '@selfxyz/mobile-sdk-alpha'; -import { - BodyText, - PrimaryButton, - SecondaryButton, -} from '@selfxyz/mobile-sdk-alpha/components'; -import { - black, - cyan300, - slate100, - slate200, - slate300, - slate500, - white, -} from '@selfxyz/mobile-sdk-alpha/constants/colors'; -import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts'; -import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks'; - -import WarningIcon from '@/assets/images/warning.svg'; -import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; -import { buttonTap } from '@/integrations/haptics'; -import type { RootStackParamList } from '@/navigation'; -import { extraYPadding } from '@/utils/styleUtils'; - -type FallbackErrorSource = - | 'mrz_scan_failed' - | 'nfc_scan_failed' - | 'sumsub_initialization' - | 'sumsub_verification'; - -type RegistrationFallbackRouteParams = { - errorSource: FallbackErrorSource; - countryCode: string; -}; - -type RegistrationFallbackRoute = RouteProp< - Record, - string ->; - -const getHeaderTitle = (errorSource: FallbackErrorSource): string => { - switch (errorSource) { - case 'mrz_scan_failed': - return 'MRZ SCAN'; - case 'nfc_scan_failed': - return 'NFC SCAN'; - default: - return 'REGISTRATION'; - } -}; - -const getCurrentStep = (errorSource: FallbackErrorSource): number => { - switch (errorSource) { - case 'mrz_scan_failed': - return 1; // Step 1: MRZ scanning - case 'nfc_scan_failed': - return 2; // Step 2: NFC reading - case 'sumsub_initialization': - case 'sumsub_verification': - return 3; // Step 3: Proving/verification - default: - return 1; - } -}; - -const getRetryButtonText = (errorSource: FallbackErrorSource): string => { - switch (errorSource) { - case 'mrz_scan_failed': - return 'Try scanning again'; - case 'nfc_scan_failed': - return 'Try reading again'; - default: - return 'Try again'; - } -}; - -const getErrorMessages = ( - errorSource: FallbackErrorSource, -): { title: string; description: string; canRetryOriginal: boolean } => { - switch (errorSource) { - case 'mrz_scan_failed': - return { - title: 'There was a problem scanning your document', - description: 'Make sure the document is clearly visible and try again', - canRetryOriginal: true, - }; - case 'nfc_scan_failed': - return { - title: 'There was a problem reading the chip', - description: 'Make sure NFC is enabled and try again', - canRetryOriginal: true, - }; - case 'sumsub_initialization': - return { - title: 'Connection Error', - description: - 'Unable to connect to verification service. Please check your internet connection and try again.', - canRetryOriginal: false, - }; - case 'sumsub_verification': - return { - title: 'Verification Error', - description: - 'Something went wrong during the verification process. Please try again.', - canRetryOriginal: false, - }; - } -}; - -const RegistrationFallbackScreen: React.FC = () => { - const insets = useSafeAreaInsets(); - const paddingBottom = useSafeBottomPadding(extraYPadding + 35); - const navigation = - useNavigation>(); - const route = useRoute(); - const selfClient = useSelfClient(); - const { trackEvent, useMRZStore } = selfClient; - const storeCountryCode = useMRZStore(state => state.countryCode); - - const errorSource = route.params?.errorSource || 'sumsub_initialization'; - // Use country code from route params, or fall back to MRZ store - const countryCode = route.params?.countryCode || storeCountryCode || ''; - - const headerTitle = getHeaderTitle(errorSource); - const retryButtonText = getRetryButtonText(errorSource); - const currentStep = getCurrentStep(errorSource); - const { title, description, canRetryOriginal } = - getErrorMessages(errorSource); - - const { launchSumsubVerification, isLoading: isRetrying } = useSumsubLauncher( - { - countryCode, - errorSource, - onCancel: () => { - navigation.goBack(); - }, - onError: (_error, _result) => { - // Stay on this screen - user can try again - // Error is already logged in the hook - }, - onSuccess: () => { - // Success - provider handles its own success UI - // The screen will be navigated away by the provider's flow - }, - }, - ); - - const handleClose = useCallback(() => { - buttonTap(); - navigation.goBack(); - }, [navigation]); - - const handleTryAlternative = useCallback(async () => { - trackEvent('REGISTRATION_FALLBACK_TRY_ALTERNATIVE', { errorSource }); - await launchSumsubVerification(); - }, [errorSource, launchSumsubVerification, trackEvent]); - - const handleRetryOriginal = useCallback(() => { - trackEvent('REGISTRATION_FALLBACK_RETRY_ORIGINAL', { errorSource }); - - // Navigate back to the appropriate screen based on error source - if (errorSource === 'mrz_scan_failed') { - navigation.navigate('DocumentCamera'); - } else if (errorSource === 'nfc_scan_failed') { - navigation.navigate('DocumentNFCScan', {}); - } else if (errorSource === 'sumsub_initialization') { - // Go back to ID Picker - navigation.goBack(); - } - // TODO: Handle 'sumsub_verification' case - currently falls through without action - // which could leave users stuck when tapping "Try again" after Sumsub verification failure. - // Consider: calling launchSumsubVerification() or navigating to appropriate retry screen. - // Need to determine the correct retry behavior for failed Sumsub verifications. - }, [errorSource, navigation, trackEvent]); - - return ( - - {/* Header */} - - - - - - {headerTitle} - - - - - - {/* Progress Bar */} - - - {[1, 2, 3, 4].map(step => ( - - ))} - - - - - {/* Warning Icon */} - - - - - - - {/* Error Message */} - - - {title} - - - {description} - - - - {/* Top Button - Retry */} - {canRetryOriginal && ( - - - {retryButtonText} - - - )} - - {/* Bottom Section with Grey Line Separator */} - - - {isRetrying ? 'Loading...' : 'Try a different method'} - - - {/* Footer Text */} - - Registering with alternative methods may take longer to verify your - document. - - - - ); -}; - -export default RegistrationFallbackScreen; diff --git a/app/src/screens/documents/selection/LogoConfirmationScreen.tsx b/app/src/screens/documents/selection/LogoConfirmationScreen.tsx new file mode 100644 index 000000000..917ff4db2 --- /dev/null +++ b/app/src/screens/documents/selection/LogoConfirmationScreen.tsx @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { StyleSheet, View } from 'react-native'; +import type { RouteProp } from '@react-navigation/native'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +import { + BodyText, + ButtonsContainer, + PrimaryButton, + SecondaryButton, +} from '@selfxyz/mobile-sdk-alpha/components'; +import { + black, + slate400, + white, +} from '@selfxyz/mobile-sdk-alpha/constants/colors'; +import { advercase, dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts'; + +import EPassportLogo from '@/assets/icons/epassport_logo.svg'; +import { DocumentFlowNavBar } from '@/components/navbar/DocumentFlowNavBar'; +import useHapticNavigation from '@/hooks/useHapticNavigation'; +import { buttonTap } from '@/integrations/haptics'; +import { + fetchAccessToken, + launchSumsub, +} from '@/integrations/sumsub/sumsubService'; +import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; +import type { RootStackParamList } from '@/navigation'; +import { useFeedback } from '@/providers/feedbackProvider'; + +type LogoConfirmationScreenRouteProp = RouteProp< + RootStackParamList, + 'LogoConfirmation' +>; + +const LogoConfirmationScreen: React.FC = () => { + const route = useRoute(); + const { documentType, countryCode } = route.params; + const navigation = + useNavigation>(); + const { showModal } = useFeedback(); + const navigateToOnboarding = useHapticNavigation('DocumentOnboarding'); + + const handleConfirm = useCallback(() => { + buttonTap(); + navigateToOnboarding(); + }, [navigateToOnboarding]); + + const handleNotFound = useCallback(() => { + buttonTap(); + showModal({ + titleText: 'Document Not Supported', + bodyText: + "To complete registration of a document without a biometric chip, you'll be redirected to our third party verification partner.", + buttonText: 'Proceed with an external verifier', + onButtonPress: async () => { + try { + const accessToken = await fetchAccessToken(); + const result = await launchSumsub({ + accessToken: accessToken.token, + // Pre-select document type and country based on user's earlier selection + documentType: documentType as 'p' | 'i', + countryCode, + }); + + // User cancelled/dismissed without completing verification + const cancelledStatuses = ['Initial', 'Incomplete', 'Interrupted']; + if (cancelledStatuses.includes(result.status)) { + return; + } + + // User completed verification - navigate to KycSuccessScreen + navigation.navigate('KycSuccess', { userId: accessToken.userId }); + } catch (error) { + console.error('Error launching Sumsub:', error); + showModal({ + titleText: 'Error', + bodyText: 'Unable to start verification. Please try again.', + buttonText: 'OK', + onButtonPress: () => {}, + }); + } + }, + }); + }, [documentType, countryCode, navigation, showModal]); + + return ( + + + + + + Does your document have this symbol? + + + + + + + + This symbol indicates your document has a biometric chip, which is + required for registration. + + + + + + + Yes + No + + + + ); +}; + +export default LogoConfirmationScreen; + +const styles = StyleSheet.create({ + contentContainer: { + alignItems: 'center', + gap: 24, + maxWidth: 340, + }, + titleText: { + fontSize: 20, + fontFamily: advercase, + textAlign: 'center', + color: black, + }, + logoContainer: { + backgroundColor: white, + borderRadius: 16, + padding: 24, + shadowColor: black, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 4, + }, + descriptionText: { + fontSize: 16, + fontFamily: dinot, + textAlign: 'center', + color: slate400, + }, +}); diff --git a/app/src/screens/kyc/KycConnectionErrorScreen.tsx b/app/src/screens/kyc/KycConnectionErrorScreen.tsx new file mode 100644 index 000000000..ff0b172b9 --- /dev/null +++ b/app/src/screens/kyc/KycConnectionErrorScreen.tsx @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Button, XStack, YStack } from 'tamagui'; +import { useNavigation } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { X } from '@tamagui/lucide-icons'; + +import { + BodyText, + PrimaryButton, + SecondaryButton, +} from '@selfxyz/mobile-sdk-alpha/components'; +import { + black, + slate100, + slate200, + slate500, + white, +} from '@selfxyz/mobile-sdk-alpha/constants/colors'; +import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts'; +import { useSafeBottomPadding } from '@selfxyz/mobile-sdk-alpha/hooks'; + +import WarningIcon from '@/assets/images/warning.svg'; +import { buttonTap } from '@/integrations/haptics'; +import type { RootStackParamList } from '@/navigation'; +import { extraYPadding } from '@/utils/styleUtils'; + +const KycConnectionErrorScreen: React.FC = () => { + const insets = useSafeAreaInsets(); + const paddingBottom = useSafeBottomPadding(extraYPadding + 35); + const navigation = + useNavigation>(); + + const handleClose = useCallback(() => { + buttonTap(); + navigation.goBack(); + }, [navigation]); + + const handleRetry = useCallback(() => { + buttonTap(); + navigation.goBack(); + }, [navigation]); + + const handleTryDifferentMethod = useCallback(() => { + buttonTap(); + navigation.navigate('CountryPicker'); + }, [navigation]); + + return ( + + {/* Header */} + + + + + + CONNECTION ERROR + + + + + + + {/* Warning Icon */} + + + + + + + {/* Error Message */} + + + Connection Error + + + Unable to connect to verification service. Please check your internet + connection and try again. + + + + {/* Retry Button */} + + Try again + + + {/* Bottom Section with Grey Line Separator */} + + + Try a different method + + + {/* Footer Text */} + + Registering with alternative methods may take longer to verify your + document. + + + + ); +}; + +export default KycConnectionErrorScreen; diff --git a/app/src/screens/kyc/KycFailureScreen.tsx b/app/src/screens/kyc/KycFailureScreen.tsx new file mode 100644 index 000000000..9f9cb0a64 --- /dev/null +++ b/app/src/screens/kyc/KycFailureScreen.tsx @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { YStack } from 'tamagui'; +import type { RouteProp } from '@react-navigation/native'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +import { + AbstractButton, + Description, + Title, +} from '@selfxyz/mobile-sdk-alpha/components'; +import { + black, + slate600, + white, +} from '@selfxyz/mobile-sdk-alpha/constants/colors'; + +import ShieldErrorIcon from '@/assets/icons/shield_error.svg'; +import { buttonTap } from '@/integrations/haptics'; +import type { RootStackParamList } from '@/navigation'; + +type KycFailureRouteParams = { + countryCode?: string; + canRetry?: boolean; +}; + +type KycFailureRoute = RouteProp, string>; + +const KycFailureScreen: React.FC = () => { + const navigation = + useNavigation>(); + const route = useRoute(); + const insets = useSafeAreaInsets(); + + const canRetry = route.params?.canRetry ?? true; + + const handleDismiss = useCallback(() => { + buttonTap(); + navigation.navigate('Home', {}); + }, [navigation]); + + const handleTryAgain = useCallback(() => { + buttonTap(); + navigation.navigate('CountryPicker'); + }, [navigation]); + + return ( + + + + + + + Unfortunately we couldn't verify your ID + + + This may be because the files you uploaded were unreadable for some + other issue. + + + + + Dismiss + + {canRetry && ( + + Try again + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: black, + }, + title: { + color: white, + textAlign: 'center', + fontSize: 36, + lineHeight: 44, + letterSpacing: 1, + }, + description: { + color: white, + textAlign: 'center', + fontSize: 20, + lineHeight: 30, + }, + button: { + borderRadius: 100, + }, +}); + +export default KycFailureScreen; diff --git a/app/tests/src/navigation.test.tsx b/app/tests/src/navigation.test.tsx index a46550ba5..f08f5995d 100644 --- a/app/tests/src/navigation.test.tsx +++ b/app/tests/src/navigation.test.tsx @@ -86,8 +86,11 @@ describe('navigation', () => { 'IDPicker', 'IdDetails', 'KYCVerified', + 'KycConnectionError', + 'KycFailure', 'KycSuccess', 'Loading', + 'LogoConfirmation', 'ManageDocuments', 'MockDataDeepLink', 'Modal', @@ -103,7 +106,8 @@ describe('navigation', () => { 'QRCodeViewFinder', 'RecoverWithPhrase', 'Referral', - 'RegistrationFallback', + 'RegistrationFallbackMRZ', + 'RegistrationFallbackNFC', 'SaveRecoveryPhrase', 'Settings', 'ShowRecoveryPhrase', diff --git a/app/tests/src/providers/notificationTrackingProvider.test.tsx b/app/tests/src/providers/notificationTrackingProvider.test.tsx index 84f1e43b9..c1654874f 100644 --- a/app/tests/src/providers/notificationTrackingProvider.test.tsx +++ b/app/tests/src/providers/notificationTrackingProvider.test.tsx @@ -116,7 +116,7 @@ describe('NotificationTrackingProvider', () => { }); }); - it('should not navigate when status is retry', async () => { + it('should navigate to CountryPicker when status is retry', async () => { let notificationHandler: | ((message: FirebaseMessagingTypes.RemoteMessage) => void) | null = null; @@ -151,11 +151,10 @@ describe('NotificationTrackingProvider', () => { expect(analytics.trackEvent).toHaveBeenCalled(); }); - // Should not navigate for retry status - expect(mockNavigationRef.navigate).not.toHaveBeenCalled(); + expect(mockNavigationRef.navigate).toHaveBeenCalledWith('CountryPicker'); }); - it('should not navigate when status is rejected', async () => { + it('should navigate to KycFailure when status is rejected', async () => { let notificationHandler: | ((message: FirebaseMessagingTypes.RemoteMessage) => void) | null = null; @@ -190,8 +189,9 @@ describe('NotificationTrackingProvider', () => { expect(analytics.trackEvent).toHaveBeenCalled(); }); - // Should not navigate for rejected status - expect(mockNavigationRef.navigate).not.toHaveBeenCalled(); + expect(mockNavigationRef.navigate).toHaveBeenCalledWith('KycFailure', { + canRetry: false, + }); }); it('should handle missing notification data gracefully', async () => { @@ -331,7 +331,7 @@ describe('NotificationTrackingProvider', () => { expect(mockNavigationRef.navigate).not.toHaveBeenCalled(); }); - it('should not navigate when status is retry on cold start', async () => { + it('should navigate to CountryPicker when status is retry on cold start', async () => { mockOnNotificationOpenedApp.mockReturnValue(jest.fn()); const remoteMessage = { @@ -358,8 +358,7 @@ describe('NotificationTrackingProvider', () => { ); }); - // Should not navigate for retry status - expect(mockNavigationRef.navigate).not.toHaveBeenCalled(); + expect(mockNavigationRef.navigate).toHaveBeenCalledWith('CountryPicker'); }); it('should queue navigation when navigationRef is not ready on cold start', async () => { diff --git a/packages/mobile-sdk-alpha/src/components/buttons/AbstractButton.tsx b/packages/mobile-sdk-alpha/src/components/buttons/AbstractButton.tsx index dabffb598..7e7dc7de3 100644 --- a/packages/mobile-sdk-alpha/src/components/buttons/AbstractButton.tsx +++ b/packages/mobile-sdk-alpha/src/components/buttons/AbstractButton.tsx @@ -131,6 +131,7 @@ const styles = StyleSheet.create({ borderRadius: 5, }, text: { + flex: 1, fontFamily: dinot, textAlign: 'center', fontSize: 18, diff --git a/packages/mobile-sdk-alpha/src/flows/onboarding/logo-confirmation-screen.tsx b/packages/mobile-sdk-alpha/src/flows/onboarding/logo-confirmation-screen.tsx new file mode 100644 index 000000000..e84447294 --- /dev/null +++ b/packages/mobile-sdk-alpha/src/flows/onboarding/logo-confirmation-screen.tsx @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import type React from 'react'; +import type { ReactNode } from 'react'; +import { StyleSheet } from 'react-native'; + +import { BodyText, PrimaryButton, SecondaryButton, View, YStack } from '../../components'; +import ButtonsContainer from '../../components/ButtonsContainer'; +import { black, slate400, white } from '../../constants/colors'; +import { advercase, dinot } from '../../constants/fonts'; +import { useSelfClient } from '../../context'; +import { buttonTap } from '../../haptic'; +import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout'; +import { SdkEvents } from '../../types/events'; + +type LogoConfirmationScreenProps = { + documentType: string; + countryCode: string; + logo: ReactNode; + safeAreaBottom?: number; + onConfirm?: () => void; + onNotFound?: () => void; +}; + +const LogoConfirmationScreen: React.FC = ({ + documentType, + countryCode, + logo, + safeAreaBottom, + onConfirm, + onNotFound, +}) => { + const selfClient = useSelfClient(); + + const onYesPress = () => { + buttonTap(); + if (onConfirm) { + onConfirm(); + } else { + selfClient.emit(SdkEvents.LOGO_CONFIRMED, { documentType, countryCode }); + } + }; + + const onNoPress = () => { + buttonTap(); + if (onNotFound) { + onNotFound(); + } else { + selfClient.emit(SdkEvents.LOGO_NOT_FOUND, { documentType, countryCode }); + } + }; + + return ( + + + + Does your document have this symbol? + + {logo} + + + This symbol indicates your document has a biometric chip, which is required for registration. + + + + + + + Yes + No + + + + ); +}; + +const styles = StyleSheet.create({ + titleText: { + fontSize: 20, + fontFamily: advercase, + textAlign: 'center', + color: black, + }, + logoContainer: { + backgroundColor: white, + borderRadius: 16, + padding: 24, + shadowColor: black, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 4, + }, + descriptionText: { + fontSize: 16, + fontFamily: dinot, + textAlign: 'center', + color: slate400, + }, +}); + +export default LogoConfirmationScreen; diff --git a/packages/mobile-sdk-alpha/src/index.ts b/packages/mobile-sdk-alpha/src/index.ts index ac8357c4e..1d2d2f64e 100644 --- a/packages/mobile-sdk-alpha/src/index.ts +++ b/packages/mobile-sdk-alpha/src/index.ts @@ -70,6 +70,8 @@ export { sdkError, } from './errors'; +export { default as LogoConfirmationScreen } from './flows/onboarding/logo-confirmation-screen'; + export { NFCScannerScreen } from './components/screens/NFCScannerScreen'; export { type ProvingStateType } from './proving/provingMachine'; diff --git a/packages/mobile-sdk-alpha/src/types/events.ts b/packages/mobile-sdk-alpha/src/types/events.ts index 0a60aca2d..62a5e03ca 100644 --- a/packages/mobile-sdk-alpha/src/types/events.ts +++ b/packages/mobile-sdk-alpha/src/types/events.ts @@ -165,6 +165,20 @@ export enum SdkEvents { * */ DOCUMENT_OWNERSHIP_CONFIRMED = 'DOCUMENT_OWNERSHIP_CONFIRMED', + + /** + * Emitted when the user confirms they see the e-passport chip logo on their document. + * + * **Required:** Navigate to the document scanning flow (DocumentOnboarding). + */ + LOGO_CONFIRMED = 'LOGO_CONFIRMED', + + /** + * Emitted when the user indicates they do not see the e-passport chip logo on their document. + * + * **Required:** Show an error message indicating the document is not supported as it is not a biometric ID. + */ + LOGO_NOT_FOUND = 'LOGO_NOT_FOUND', } /** @@ -223,6 +237,14 @@ export interface SDKEventMap { signatureAlgorithm?: string; curveOrExponent?: string; }; + [SdkEvents.LOGO_CONFIRMED]: { + documentType: string; + countryCode: string; + }; + [SdkEvents.LOGO_NOT_FOUND]: { + documentType: string; + countryCode: string; + }; } /** From c8191aa60bc9567deebea51afefa07b6f51680f1 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 6 Feb 2026 09:04:51 -0800 Subject: [PATCH 11/13] chore: hide kyc entry points for release 2.9.15 (#1712) * hide kyc entry points * formatting --- .../aadhaar/AadhaarUploadErrorScreen.tsx | 72 ++++++++++--------- .../RegistrationFallbackMRZScreen.tsx | 72 ++++++++++--------- .../RegistrationFallbackNFCScreen.tsx | 72 ++++++++++--------- .../selection/LogoConfirmationScreen.tsx | 6 +- 4 files changed, 122 insertions(+), 100 deletions(-) diff --git a/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx b/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx index 9fe27ecc7..50ee11db5 100644 --- a/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx +++ b/app/src/screens/documents/aadhaar/AadhaarUploadErrorScreen.tsx @@ -30,6 +30,7 @@ import { NavBar } from '@/components/navbar/BaseNavBar'; import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; import { buttonTap } from '@/integrations/haptics'; import type { RootStackParamList } from '@/navigation'; +import { useSettingStore } from '@/stores/settingStore'; import { extraYPadding } from '@/utils/styleUtils'; type AadhaarUploadErrorRouteParams = { @@ -66,6 +67,7 @@ const AadhaarUploadErrorScreen: React.FC = () => { useNavigation>(); const route = useRoute(); const { trackEvent } = useSelfClient(); + const kycEnabled = useSettingStore(state => state.kycEnabled); const errorType = route.params?.errorType || 'general'; const { title, description } = getErrorMessages(errorType); @@ -214,40 +216,44 @@ const AadhaarUploadErrorScreen: React.FC = () => { paddingBottom={paddingBottom} gap={10} > - {/* Secondary Button - White fill, black text, rounded */} - + {kycEnabled && ( + <> + {/* Secondary Button - White fill, black text, rounded */} + - {/* Footer Text - Not italic */} - - Registering with alternative methods may take longer to verify your - document. - + {/* Footer Text - Not italic */} + + Registering with alternative methods may take longer to verify + your document. + + + )} ); diff --git a/app/src/screens/documents/scanning/RegistrationFallbackMRZScreen.tsx b/app/src/screens/documents/scanning/RegistrationFallbackMRZScreen.tsx index 8c990bbf8..bcab71bf2 100644 --- a/app/src/screens/documents/scanning/RegistrationFallbackMRZScreen.tsx +++ b/app/src/screens/documents/scanning/RegistrationFallbackMRZScreen.tsx @@ -28,6 +28,7 @@ import { NavBar } from '@/components/navbar/BaseNavBar'; import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; import { buttonTap } from '@/integrations/haptics'; import type { RootStackParamList } from '@/navigation'; +import { useSettingStore } from '@/stores/settingStore'; import { extraYPadding } from '@/utils/styleUtils'; type RegistrationFallbackMRZRouteParams = { @@ -60,6 +61,7 @@ const RegistrationFallbackMRZScreen: React.FC = () => { const { trackEvent, useMRZStore } = selfClient; const storeCountryCode = useMRZStore(state => state.countryCode); const documentType = useMRZStore(state => state.documentType); + const kycEnabled = useSettingStore(state => state.kycEnabled); // Use country code from route params, or fall back to MRZ store const countryCode = route.params?.countryCode || storeCountryCode || ''; @@ -214,40 +216,44 @@ const RegistrationFallbackMRZScreen: React.FC = () => { paddingBottom={paddingBottom} gap={10} > - {/* Secondary Button - White fill, black text, rounded */} - + {kycEnabled && ( + <> + {/* Secondary Button - White fill, black text, rounded */} + - {/* Footer Text - Not italic */} - - Registering with alternative methods may take longer to verify your - document. - + {/* Footer Text - Not italic */} + + Registering with alternative methods may take longer to verify + your document. + + + )} ); diff --git a/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx b/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx index bbe4de02e..a72d277f6 100644 --- a/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx +++ b/app/src/screens/documents/scanning/RegistrationFallbackNFCScreen.tsx @@ -29,6 +29,7 @@ import { NavBar } from '@/components/navbar/BaseNavBar'; import { useSumsubLauncher } from '@/hooks/useSumsubLauncher'; import { buttonTap } from '@/integrations/haptics'; import type { RootStackParamList } from '@/navigation'; +import { useSettingStore } from '@/stores/settingStore'; import { extraYPadding } from '@/utils/styleUtils'; type RegistrationFallbackNFCRouteParams = { @@ -61,6 +62,7 @@ const RegistrationFallbackNFCScreen: React.FC = () => { const { trackEvent, useMRZStore } = selfClient; const storeCountryCode = useMRZStore(state => state.countryCode); const documentType = useMRZStore(state => state.documentType); + const kycEnabled = useSettingStore(state => state.kycEnabled); // Use country code from route params, or fall back to MRZ store const countryCode = route.params?.countryCode || storeCountryCode || ''; @@ -240,40 +242,44 @@ const RegistrationFallbackNFCScreen: React.FC = () => { paddingBottom={paddingBottom} gap={10} > - {/* Secondary Button - White fill, black text, rounded */} - + {kycEnabled && ( + <> + {/* Secondary Button - White fill, black text, rounded */} + - {/* Footer Text - Not italic */} - - Registering with alternative methods may take longer to verify your - document. - + {/* Footer Text - Not italic */} + + Registering with alternative methods may take longer to verify + your document. + + + )} ); diff --git a/app/src/screens/documents/selection/LogoConfirmationScreen.tsx b/app/src/screens/documents/selection/LogoConfirmationScreen.tsx index 917ff4db2..55813cf3d 100644 --- a/app/src/screens/documents/selection/LogoConfirmationScreen.tsx +++ b/app/src/screens/documents/selection/LogoConfirmationScreen.tsx @@ -32,6 +32,7 @@ import { import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout'; import type { RootStackParamList } from '@/navigation'; import { useFeedback } from '@/providers/feedbackProvider'; +import { useSettingStore } from '@/stores/settingStore'; type LogoConfirmationScreenRouteProp = RouteProp< RootStackParamList, @@ -45,6 +46,7 @@ const LogoConfirmationScreen: React.FC = () => { useNavigation>(); const { showModal } = useFeedback(); const navigateToOnboarding = useHapticNavigation('DocumentOnboarding'); + const kycEnabled = useSettingStore(state => state.kycEnabled); const handleConfirm = useCallback(() => { buttonTap(); @@ -112,7 +114,9 @@ const LogoConfirmationScreen: React.FC = () => { Yes - No + {kycEnabled && ( + No + )} From a1b04f2b0315b72002871c5e35ea2b3be2504e8b Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 6 Feb 2026 12:08:08 -0800 Subject: [PATCH 12/13] chore: enable SumSub Fisherman device intelligence with privacy compliance (#1714) * clean up permissions * updates for permissions * update permissions and packages * fix: mark device ID collection as linked in privacy manifest Address CodeRabbit feedback: - Set NSPrivacyCollectedDataTypeLinked to true (device signals are tied to applicant identity) - Clarify Android Data Safety checklist requirements with explicit data-linking declaration Co-authored-by: Cursor * remove for now --------- Co-authored-by: Cursor --- app/Gemfile.lock | 2 +- app/ios/Podfile | 10 ++-- app/ios/Podfile.lock | 21 +++----- app/ios/PrivacyInfo.xcprivacy | 15 +++++- app/ios/Self.xcodeproj/project.pbxproj | 54 +++++++++---------- app/package.json | 2 +- app/src/integrations/sumsub/sumsubService.ts | 12 ++++- packages/mobile-sdk-alpha/package.json | 2 +- ...react-native-mobilesdk-module+1.40.2.patch | 8 ++- patches/react-native-svg+15.15.1.patch | 24 --------- yarn.lock | 18 ++++++- 11 files changed, 89 insertions(+), 79 deletions(-) delete mode 100644 patches/react-native-svg+15.15.1.patch diff --git a/app/Gemfile.lock b/app/Gemfile.lock index 72d8b6b94..e1c7f3f8c 100644 --- a/app/Gemfile.lock +++ b/app/Gemfile.lock @@ -23,7 +23,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.4.0) - aws-partitions (1.1212.0) + aws-partitions (1.1213.0) aws-sdk-core (3.242.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) diff --git a/app/ios/Podfile b/app/ios/Podfile index e033f03f8..51a4d824f 100755 --- a/app/ios/Podfile +++ b/app/ios/Podfile @@ -4,11 +4,15 @@ source "https://cdn.cocoapods.org/" unless ENV["E2E_TESTING"] == "1" source "https://github.com/SumSubstance/Specs.git" - # Enable Fisherman (Device Intelligence) module + # Enable Fisherman (Device Intelligence) module for fraud detection + # Privacy: Device ID collection declared in app/ios/PrivacyInfo.xcprivacy ENV["IDENSIC_WITH_FISHERMAN"] = "true" - # Enable VideoIdent module - ENV["IDENSIC_WITH_VIDEOIDENT"] = "true" + # VideoIdent module disabled for current release + # This feature provides liveness checks via live video calls with human agents + # Disabled to avoid microphone permission requirements on both platforms + # TODO: Re-enable for future release when liveness checks are needed + # ENV["IDENSIC_WITH_VIDEOIDENT"] = "true" end use_frameworks! diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index cfff6c54e..93577e3df 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -158,11 +158,6 @@ PODS: - IdensicMobileSDK/Fisherman (1.40.2): - FingerprintPro (~> 2.11) - IdensicMobileSDK/Core - - IdensicMobileSDK/VideoIdent (1.40.2): - - IdensicMobileSDK/VideoIdent-latest - - IdensicMobileSDK/VideoIdent-latest (1.40.2): - - IdensicMobileSDK/Core - - TwilioVideo (>= 5.8.2) - lottie-ios (4.5.0) - lottie-react-native (7.2.2): - DoubleConversion @@ -1554,7 +1549,6 @@ PODS: - react-native-mobilesdk-module (1.40.2): - IdensicMobileSDK (= 1.40.2) - IdensicMobileSDK/Fisherman (= 1.40.2) - - IdensicMobileSDK/VideoIdent (= 1.40.2) - React-Core - react-native-netinfo (11.4.1): - React-Core @@ -2152,7 +2146,7 @@ PODS: - ReactCommon/turbomodule/core - Sentry/HybridSDK (= 8.53.2) - Yoga - - RNSVG (15.15.1): + - RNSVG (15.12.1): - DoubleConversion - glog - hermes-engine @@ -2172,9 +2166,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.15.1) + - RNSVG/common (= 15.12.1) - Yoga - - RNSVG/common (15.15.1): + - RNSVG/common (15.12.1): - DoubleConversion - glog - hermes-engine @@ -2204,7 +2198,6 @@ PODS: - React-Core - SwiftQRScanner (1.1.6) - SwiftyTesseract (3.1.3) - - TwilioVideo (5.11.1) - Yoga (0.0.0) DEPENDENCIES: @@ -2346,7 +2339,6 @@ SPEC REPOS: - Sentry - SocketRocket - SwiftyTesseract - - TwilioVideo EXTERNAL SOURCES: boost: @@ -2624,7 +2616,7 @@ SPEC CHECKSUMS: react-native-cloud-storage: 8d89f2bc574cf11068dfd90933905974087fb9e9 react-native-compat: b80530ebcd3d574be5dd99cb27b984a17c119abc react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba - react-native-mobilesdk-module: 4770cb45fdd19dc4eed04615f0fcdab013b3dfe2 + react-native-mobilesdk-module: 08c16fea2be97669f8e4c38153106e5fe698126a react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 react-native-nfc-manager: c8891e460b4943b695d63f7f4effc6345bbefc83 react-native-passkey: 8853c3c635164864da68a6dbbcec7148506c3bcf @@ -2671,16 +2663,15 @@ SPEC CHECKSUMS: RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73 RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8 RNSentry: f79dd124cc49088445c16d23955860dd0d1db6f3 - RNSVG: 8cd7dadbe9bdc7d70872910dbe611c8e0f4597bb + RNSVG: 0c1fc3e7b147949dc15644845e9124947ac8c9bb segment-analytics-react-native: 0eae155b0e9fa560fa6b17d78941df64537c35b7 Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 sovran-react-native: a3ad3f8ff90c2002b2aa9790001a78b0b0a38594 SwiftQRScanner: e85a25f9b843e9231dab89a96e441472fe54a724 SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb - TwilioVideo: 9f51085d4e4fb3aff8e168b8215b31cb0f486a2f Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407 -PODFILE CHECKSUM: 8cfd84595c3e826f512f5c545d232a27f1850ff3 +PODFILE CHECKSUM: ced4db0072978f965783277bc810af9a7bebe695 COCOAPODS: 1.16.2 diff --git a/app/ios/PrivacyInfo.xcprivacy b/app/ios/PrivacyInfo.xcprivacy index 41b8317f0..a40e21da8 100644 --- a/app/ios/PrivacyInfo.xcprivacy +++ b/app/ios/PrivacyInfo.xcprivacy @@ -30,7 +30,20 @@ NSPrivacyCollectedDataTypes - + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeFraudPreventionAndSecurity + + + NSPrivacyTracking diff --git a/app/ios/Self.xcodeproj/project.pbxproj b/app/ios/Self.xcodeproj/project.pbxproj index fee740bae..181d48af7 100644 --- a/app/ios/Self.xcodeproj/project.pbxproj +++ b/app/ios/Self.xcodeproj/project.pbxproj @@ -306,14 +306,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-resources.sh\"\n"; @@ -349,14 +345,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Self/Pods-Self-frameworks.sh\"\n"; @@ -371,8 +363,6 @@ "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); name = "[CP-User] [RNFB] Core Configuration"; - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; @@ -437,15 +427,15 @@ baseConfigurationReference = E67B3FF985359E36919C9E20 /* Pods-Self.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 189; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_APP_SANDBOX = NO; - ENABLE_BITCODE = NO; - ENABLE_RESOURCE_ACCESS_CAMERA = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 189; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_APP_SANDBOX = NO; + ENABLE_BITCODE = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", @@ -579,14 +569,14 @@ 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 823EAA08DBB5F61225E922CA /* Pods-Self.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements; - CURRENT_PROJECT_VERSION = 189; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_APP_SANDBOX = NO; - ENABLE_RESOURCE_ACCESS_CAMERA = YES; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements; + CURRENT_PROJECT_VERSION = 189; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_APP_SANDBOX = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", @@ -807,7 +797,10 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -900,7 +893,10 @@ "-DFOLLY_MOBILE=1", "-DFOLLY_USE_LIBCPP=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; STRING_CATALOG_GENERATE_SYMBOLS = YES; diff --git a/app/package.json b/app/package.json index d21f71be9..531105645 100644 --- a/app/package.json +++ b/app/package.json @@ -164,7 +164,7 @@ "react-native-safe-area-context": "^5.6.2", "react-native-screens": "4.15.3", "react-native-sqlite-storage": "^6.0.1", - "react-native-svg": "15.15.1", + "react-native-svg": "15.12.1", "react-native-svg-web": "1.0.9", "react-native-url-polyfill": "^3.0.0", "react-native-web": "^0.21.2", diff --git a/app/src/integrations/sumsub/sumsubService.ts b/app/src/integrations/sumsub/sumsubService.ts index 20f191399..d50524da4 100644 --- a/app/src/integrations/sumsub/sumsubService.ts +++ b/app/src/integrations/sumsub/sumsubService.ts @@ -116,7 +116,17 @@ export const launchSumsub = async ( }) .withDebug(config.debug ?? __DEV__) .withLocale(config.locale ?? 'en') - .withAnalyticsEnabled(true); // Device Intelligence requires this + // Platform configuration: + // - Device Intelligence (Fisherman): Enabled on both iOS and Android + // * iOS: Configured via IDENSIC_WITH_FISHERMAN in Podfile + // * Android: Configured via idensic-mobile-sdk-fisherman in patch file + // * Privacy: iOS declares device ID collection in PrivacyInfo.xcprivacy + // * Privacy: Android should declare device fingerprinting in Google Play Data Safety + // - VideoIdent (live video calls): Disabled on both platforms for current release + // * iOS: Disabled in Podfile (avoids microphone permission requirements) + // * Android: Disabled in patch file (avoids FOREGROUND_SERVICE_MICROPHONE permission) + // * Note: VideoIdent will be re-enabled on both platforms in future release for liveness checks + .withAnalyticsEnabled(true); // Required for Device Intelligence to function // Pre-select document type and country if provided // This skips the document selection step in Sumsub diff --git a/packages/mobile-sdk-alpha/package.json b/packages/mobile-sdk-alpha/package.json index e14606e71..b32a1156d 100644 --- a/packages/mobile-sdk-alpha/package.json +++ b/packages/mobile-sdk-alpha/package.json @@ -186,7 +186,7 @@ "react-native-blur-effect": "^1.1.3", "react-native-haptic-feedback": "^2.3.3", "react-native-localize": "^3.6.1", - "react-native-svg": "15.15.1", + "react-native-svg": "15.12.1", "react-native-web": "^0.21.2", "react-native-webview": "13.16.0", "tsup": "^8.0.1", diff --git a/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch b/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch index fccb8e539..e04a1916a 100644 --- a/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch +++ b/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch @@ -5,12 +5,18 @@ index 0000000..0000001 100644 @@ -77,11 +77,11 @@ dependencies { implementation "com.sumsub.sns:idensic-mobile-sdk:1.40.2" + // Enable Device Intelligence (Fisherman) for fraud detection + // Privacy: Declare device fingerprinting/identifiers in Google Play Data Safety form // remove comment to enable Device Intelligence - // implementation "com.sumsub.sns:idensic-mobile-sdk-fisherman:1.40.2" + implementation "com.sumsub.sns:idensic-mobile-sdk-fisherman:1.40.2" + // VideoIdent disabled on both iOS and Android for current release + // Reason: Avoids microphone permission requirements (FOREGROUND_SERVICE_MICROPHONE on Android) + // Feature: Provides liveness checks via live video calls with human agents + // TODO: Re-enable on both platforms for future release when liveness checks are needed // remove comment if you need VideoIdent support - // implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:1.40.2" -+ implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:1.40.2" ++ // implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:1.40.2" // remove comment if you need EID support // implementation "com.sumsub.sns:idensic-mobile-sdk-eid:1.40.2" // remove comment if you need NFC support diff --git a/patches/react-native-svg+15.15.1.patch b/patches/react-native-svg+15.15.1.patch deleted file mode 100644 index 07ce8e430..000000000 --- a/patches/react-native-svg+15.15.1.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp ---- a/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp -+++ b/node_modules/react-native-svg/common/cpp/react/renderer/components/rnsvg/RNSVGLayoutableShadowNode.cpp -@@ -27,8 +27,18 @@ void RNSVGLayoutableShadowNode::updatePosition() { - auto style = yogaNode_.style(); - style.setPositionType(yoga::PositionType::Absolute); - style.setPosition(yoga::Edge::All, yoga::Style::Length::points(0)); -- style.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::percent(100)); -- style.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::percent(100)); -+#if REACT_NATIVE_MINOR_VERSION >= 78 -+ style.setDimension( -+ yoga::Dimension::Width, yoga::StyleSizeLength::percent(100)); -+ style.setDimension( -+ yoga::Dimension::Height, yoga::StyleSizeLength::percent(100)); -+#elif REACT_NATIVE_MINOR_VERSION >= 77 -+ style.setDimension(yoga::Dimension::Width, yoga::StyleLength::percent(100)); -+ style.setDimension(yoga::Dimension::Height, yoga::StyleLength::percent(100)); -+#else -+ style.setDimension(yoga::Dimension::Width, yoga::value::percent(100)); -+ style.setDimension(yoga::Dimension::Height, yoga::value::percent(100)); -+#endif - yogaNode_.setStyle(style); - } - diff --git a/yarn.lock b/yarn.lock index f735821ab..72a61dcbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9092,7 +9092,7 @@ __metadata: react-native-safe-area-context: "npm:^5.6.2" react-native-screens: "npm:4.15.3" react-native-sqlite-storage: "npm:^6.0.1" - react-native-svg: "npm:15.15.1" + react-native-svg: "npm:15.12.1" react-native-svg-transformer: "npm:^1.5.2" react-native-svg-web: "npm:1.0.9" react-native-url-polyfill: "npm:^3.0.0" @@ -9149,7 +9149,7 @@ __metadata: react-native-haptic-feedback: "npm:^2.3.3" react-native-localize: "npm:^3.6.1" react-native-nfc-manager: "npm:^3.17.2" - react-native-svg: "npm:15.15.1" + react-native-svg: "npm:15.12.1" react-native-svg-circle-country-flags: "npm:^0.2.2" react-native-web: "npm:^0.21.2" react-native-webview: "npm:13.16.0" @@ -30315,6 +30315,20 @@ __metadata: languageName: node linkType: hard +"react-native-svg@npm:15.12.1": + version: 15.12.1 + resolution: "react-native-svg@npm:15.12.1" + dependencies: + css-select: "npm:^5.1.0" + css-tree: "npm:^1.1.3" + warn-once: "npm:0.1.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10c0/ed94b57007125c715283fc760438ac8eac0677ced3201f6e272a7cd4459f3fecb672a8eed2c32664e5b8e6e0367585353b0f83f99b231f603d75094bb052c01f + languageName: node + linkType: hard + "react-native-svg@npm:15.15.1": version: 15.15.1 resolution: "react-native-svg@npm:15.15.1" From df208e787be03807f1011ccb42e50545e170373e Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 6 Feb 2026 13:27:20 -0800 Subject: [PATCH 13/13] chore: fix staging pipelines for 2.9.15 (#1715) * fix versions * update publish logic --- .github/workflows/npm-publish.yml | 159 ++++++++++++++++++++++++++ packages/mobile-sdk-demo/package.json | 2 +- yarn.lock | 16 +-- 3 files changed, 161 insertions(+), 16 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 3ae0a3a19..d714e07dc 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -12,11 +12,26 @@ on: - "sdk/qrcode-angular/package.json" - "contracts/package.json" workflow_dispatch: + inputs: + strict_mode: + description: "Fail workflow on publish errors (false = continue on error)" + required: false + type: boolean + default: false permissions: id-token: write # Required for OIDC contents: read +# Error Handling Strategy: +# - STRICT_PUBLISH_MODE controls whether publish failures stop the workflow +# - Current (false): continue-on-error=true, workflow always succeeds +# - Target (true): continue-on-error=false, fail on real errors (expired tokens, network issues) +# - Manual override: Use workflow_dispatch with strict_mode input to test +# TODO: Set STRICT_PUBLISH_MODE=true once NPM token is rotated and verified +env: + STRICT_PUBLISH_MODE: false + jobs: detect-changes: runs-on: ubuntu-latest @@ -86,8 +101,21 @@ jobs: run: | yarn workspace @selfxyz/core build:deps + - name: Check NPM Token + id: check-token + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "⚠️ Warning: NPM_TOKEN is not set. Skipping publish." + echo "token_available=false" >> $GITHUB_OUTPUT + else + echo "token_available=true" >> $GITHUB_OUTPUT + fi + - name: Publish to npm + if: steps.check-token.outputs.token_available == 'true' working-directory: sdk/core + continue-on-error: ${{ github.event.inputs.strict_mode != 'true' && env.STRICT_PUBLISH_MODE != 'true' }} + id: publish run: | yarn config set npmPublishAccess public yarn npm publish --access public @@ -95,6 +123,17 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish result + if: always() + run: | + if [ "${{ steps.check-token.outputs.token_available }}" != "true" ]; then + echo "::warning::NPM publish skipped - NPM_TOKEN not configured. Please rotate the token in repository secrets." + elif [ "${{ steps.publish.outcome }}" != "success" ]; then + echo "::warning::NPM publish failed - This may be due to an expired or invalid NPM_TOKEN. Please check and rotate the token." + else + echo "✅ Package published successfully" + fi + publish-qrcode: needs: detect-changes if: needs.detect-changes.outputs.qrcode_changed == 'true' @@ -114,8 +153,21 @@ jobs: run: | yarn workspace @selfxyz/qrcode build:deps + - name: Check NPM Token + id: check-token + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "⚠️ Warning: NPM_TOKEN is not set. Skipping publish." + echo "token_available=false" >> $GITHUB_OUTPUT + else + echo "token_available=true" >> $GITHUB_OUTPUT + fi + - name: Publish to npm + if: steps.check-token.outputs.token_available == 'true' working-directory: sdk/qrcode + continue-on-error: ${{ github.event.inputs.strict_mode != 'true' && env.STRICT_PUBLISH_MODE != 'true' }} + id: publish run: | yarn config set npmPublishAccess public yarn npm publish --access public @@ -123,6 +175,17 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish result + if: always() + run: | + if [ "${{ steps.check-token.outputs.token_available }}" != "true" ]; then + echo "::warning::NPM publish skipped - NPM_TOKEN not configured. Please rotate the token in repository secrets." + elif [ "${{ steps.publish.outcome }}" != "success" ]; then + echo "::warning::NPM publish failed - This may be due to an expired or invalid NPM_TOKEN. Please check and rotate the token." + else + echo "✅ Package published successfully" + fi + publish-common: needs: detect-changes if: needs.detect-changes.outputs.common_changed == 'true' @@ -141,14 +204,38 @@ jobs: run: | yarn workspace @selfxyz/common build + - name: Check NPM Token + id: check-token + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "⚠️ Warning: NPM_TOKEN is not set. Skipping publish." + echo "token_available=false" >> $GITHUB_OUTPUT + else + echo "token_available=true" >> $GITHUB_OUTPUT + fi + - name: Publish to npm + if: steps.check-token.outputs.token_available == 'true' working-directory: common + continue-on-error: ${{ github.event.inputs.strict_mode != 'true' && env.STRICT_PUBLISH_MODE != 'true' }} + id: publish run: | yarn config set npmPublishAccess public yarn npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish result + if: always() + run: | + if [ "${{ steps.check-token.outputs.token_available }}" != "true" ]; then + echo "::warning::NPM publish skipped - NPM_TOKEN not configured. Please rotate the token in repository secrets." + elif [ "${{ steps.publish.outcome }}" != "success" ]; then + echo "::warning::NPM publish failed - This may be due to an expired or invalid NPM_TOKEN. Please check and rotate the token." + else + echo "✅ Package published successfully" + fi publish-contracts: needs: detect-changes if: needs.detect-changes.outputs.contracts_changed == 'true' @@ -165,14 +252,38 @@ jobs: - name: Build package run: | yarn workspace @selfxyz/contracts build + - name: Check NPM Token + id: check-token + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "⚠️ Warning: NPM_TOKEN is not set. Skipping publish." + echo "token_available=false" >> $GITHUB_OUTPUT + else + echo "token_available=true" >> $GITHUB_OUTPUT + fi + - name: Publish to npm + if: steps.check-token.outputs.token_available == 'true' working-directory: contracts + continue-on-error: ${{ github.event.inputs.strict_mode != 'true' && env.STRICT_PUBLISH_MODE != 'true' }} + id: publish run: | yarn config set npmPublishAccess public yarn npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish result + if: always() + run: | + if [ "${{ steps.check-token.outputs.token_available }}" != "true" ]; then + echo "::warning::NPM publish skipped - NPM_TOKEN not configured. Please rotate the token in repository secrets." + elif [ "${{ steps.publish.outcome }}" != "success" ]; then + echo "::warning::NPM publish failed - This may be due to an expired or invalid NPM_TOKEN. Please check and rotate the token." + else + echo "✅ Package published successfully" + fi publish-qrcode-angular: needs: detect-changes if: needs.detect-changes.outputs.qrcode_angular_changed == 'true' @@ -192,8 +303,21 @@ jobs: run: | yarn workspace @selfxyz/qrcode-angular build:deps + - name: Check NPM Token + id: check-token + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "⚠️ Warning: NPM_TOKEN is not set. Skipping publish." + echo "token_available=false" >> $GITHUB_OUTPUT + else + echo "token_available=true" >> $GITHUB_OUTPUT + fi + - name: Publish to npm + if: steps.check-token.outputs.token_available == 'true' working-directory: sdk/qrcode-angular + continue-on-error: ${{ github.event.inputs.strict_mode != 'true' && env.STRICT_PUBLISH_MODE != 'true' }} + id: publish run: | yarn config set npmPublishAccess public yarn npm publish --access public @@ -201,6 +325,17 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish result + if: always() + run: | + if [ "${{ steps.check-token.outputs.token_available }}" != "true" ]; then + echo "::warning::NPM publish skipped - NPM_TOKEN not configured. Please rotate the token in repository secrets." + elif [ "${{ steps.publish.outcome }}" != "success" ]; then + echo "::warning::NPM publish failed - This may be due to an expired or invalid NPM_TOKEN. Please check and rotate the token." + else + echo "✅ Package published successfully" + fi + publish-msdk: needs: detect-changes if: needs.detect-changes.outputs.msdk_changed == 'true' @@ -221,11 +356,35 @@ jobs: yarn workspace @selfxyz/common build yarn workspace @selfxyz/mobile-sdk-alpha build + - name: Check NPM Token + id: check-token + run: | + if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + echo "⚠️ Warning: NPM_TOKEN is not set. Skipping publish." + echo "token_available=false" >> $GITHUB_OUTPUT + else + echo "token_available=true" >> $GITHUB_OUTPUT + fi + - name: Publish to npm + if: steps.check-token.outputs.token_available == 'true' working-directory: packages/mobile-sdk-alpha + continue-on-error: ${{ github.event.inputs.strict_mode != 'true' && env.STRICT_PUBLISH_MODE != 'true' }} + id: publish run: | yarn config set npmPublishAccess restricted yarn npm publish --access restricted --tag alpha env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish result + if: always() + run: | + if [ "${{ steps.check-token.outputs.token_available }}" != "true" ]; then + echo "::warning::NPM publish skipped - NPM_TOKEN not configured. Please rotate the token in repository secrets." + elif [ "${{ steps.publish.outcome }}" != "success" ]; then + echo "::warning::NPM publish failed - This may be due to an expired or invalid NPM_TOKEN. Please check and rotate the token." + else + echo "✅ Package published successfully" + fi diff --git a/packages/mobile-sdk-demo/package.json b/packages/mobile-sdk-demo/package.json index dce8518ec..f14794f80 100644 --- a/packages/mobile-sdk-demo/package.json +++ b/packages/mobile-sdk-demo/package.json @@ -49,7 +49,7 @@ "react-native-keychain": "^10.0.0", "react-native-localize": "^3.6.1", "react-native-safe-area-context": "^5.6.2", - "react-native-svg": "15.15.1", + "react-native-svg": "15.12.1", "react-native-vector-icons": "^10.3.0", "react-native-webview": "13.16.0", "stream-browserify": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 72a61dcbd..a6d3701ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27347,7 +27347,7 @@ __metadata: react-native-keychain: "npm:^10.0.0" react-native-localize: "npm:^3.6.1" react-native-safe-area-context: "npm:^5.6.2" - react-native-svg: "npm:15.15.1" + react-native-svg: "npm:15.12.1" react-native-svg-transformer: "npm:^1.5.2" react-native-vector-icons: "npm:^10.3.0" react-native-webview: "npm:13.16.0" @@ -30329,20 +30329,6 @@ __metadata: languageName: node linkType: hard -"react-native-svg@npm:15.15.1": - version: 15.15.1 - resolution: "react-native-svg@npm:15.15.1" - dependencies: - css-select: "npm:^5.1.0" - css-tree: "npm:^1.1.3" - warn-once: "npm:0.1.1" - peerDependencies: - react: "*" - react-native: "*" - checksum: 10c0/9e047e8afdd5121296a3402c5c37363b9b37fbddc41f7b0b49b923ac4d2898954c8b57a6784a625321236987e494ee54f594e22f9de815f807e03a3433d7fefd - languageName: node - linkType: hard - "react-native-url-polyfill@npm:2.0.0": version: 2.0.0 resolution: "react-native-url-polyfill@npm:2.0.0"