diff --git a/.github/workflows/mobile-e2e.yml b/.github/workflows/mobile-e2e.yml index d444c5297..39d1bc593 100644 --- a/.github/workflows/mobile-e2e.yml +++ b/.github/workflows/mobile-e2e.yml @@ -15,6 +15,9 @@ env: # Disable Maestro analytics in CI MAESTRO_CLI_NO_ANALYTICS: true MAESTRO_VERSION: 1.41.0 + MAESTRO_CACHE_VERSION: v1 # Bump this to clear Maestro cache + # Disable Maestro recording/artifacts (keep for debugging - set to "true" to re-enable) + ENABLE_MAESTRO_RECORDING: false on: push: @@ -333,9 +336,35 @@ jobs: uses: actions/cache@v4 with: path: ~/.maestro - key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }} + key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}-${{ env.MAESTRO_CACHE_VERSION }} + restore-keys: | + ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}- + - name: Validate Maestro cache + if: steps.cache-maestro.outputs.cache-hit == 'true' + run: | + echo "✅ Maestro restored from cache" + echo "Validating cached Maestro installation..." + + if [ ! -f "$HOME/.maestro/bin/maestro" ]; then + echo "❌ Maestro binary not found in cache - cache is corrupted" + echo "Clearing corrupted cache..." + rm -rf ~/.maestro + echo "MAESTRO_CACHE_VALID=false" >> $GITHUB_ENV + else + echo "✅ Maestro binary found in cache" + # Test if Maestro is executable + if "$HOME/.maestro/bin/maestro" --version &>/dev/null; then + echo "✅ Maestro cache is valid" + echo "MAESTRO_CACHE_VALID=true" >> $GITHUB_ENV + else + echo "❌ Maestro binary is not executable - cache is corrupted" + echo "Clearing corrupted cache..." + rm -rf ~/.maestro + echo "MAESTRO_CACHE_VALID=false" >> $GITHUB_ENV + fi + fi - name: Install Maestro - if: steps.cache-maestro.outputs.cache-hit != 'true' + if: steps.cache-maestro.outputs.cache-hit != 'true' || env.MAESTRO_CACHE_VALID == 'false' uses: nick-fields/retry@v3 with: timeout_minutes: 5 @@ -344,6 +373,20 @@ jobs: command: curl -Ls "https://get.maestro.mobile.dev" | bash - name: Add Maestro to path run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + - name: Verify Maestro installation + run: | + echo "🔍 Verifying Maestro installation..." + echo "Maestro path: $(which maestro)" + echo "Maestro version:" + maestro --version || { + echo "❌ Maestro installation verification failed" + echo "Maestro binary location:" + find ~/.maestro -name maestro -type f 2>/dev/null || echo "No maestro binary found" + echo "" + echo "To fix: Bump MAESTRO_CACHE_VERSION in workflow file" + exit 1 + } + echo "✅ Maestro installation verified" - name: Set up Xcode uses: maxim-lobanov/setup-xcode@v1 with: @@ -417,6 +460,7 @@ jobs: BUNDLE_GEMFILE=../Gemfile bundle exec bash scripts/pod-install-with-cache-fix.sh env: SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }} + E2E_TESTING: 1 - name: Setup iOS Simulator run: | echo "Setting up iOS Simulator..." @@ -504,6 +548,8 @@ jobs: echo "WORKSPACE_PATH=$WORKSPACE_PATH" >> "$GITHUB_ENV" echo "Resolved workspace: $WORKSPACE_PATH" - name: Build iOS App + env: + E2E_TESTING: 1 run: | echo "Building iOS app..." echo "Project: ${{ env.IOS_PROJECT_NAME }}, Scheme: ${{ env.IOS_PROJECT_SCHEME }}" @@ -538,10 +584,13 @@ jobs: # Use cached derived data and enable parallel builds for faster compilation # Additional flags disable indexing, restrict architecture, and use whole-module Swift compilation # Use the simulator that was set up earlier in the workflow + # E2E_TESTING compilation condition excludes NFCPassportReader which isn't available on simulator 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 app/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 app/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: Install and Test on iOS + continue-on-error: true + id: maestro-test run: | echo "Installing app on simulator..." APP_PATH=$(find app/ios/build/Build/Products/Debug-iphonesimulator -name "*.app" | head -1) @@ -589,20 +638,213 @@ jobs: # Final readiness check (suppress errors to avoid annotations) xcrun simctl get_app_container "$SIMULATOR_ID" "$IOS_BUNDLE_ID" app >/dev/null 2>&1 || sleep 5 - echo "🎭 Running Maestro tests..." - echo "Starting test execution..." + echo "" + echo "╔════════════════════════════════════════════════════════════════╗" + echo "║ 🔍 PRE-TEST DIAGNOSTICS ║" + echo "╚════════════════════════════════════════════════════════════════╝" + echo "" - # Verify Maestro test file exists - if [ ! -f "app/tests/e2e/launch.ios.flow.yaml" ]; then - echo "❌ Maestro test file not found: app/tests/e2e/launch.ios.flow.yaml" + # Create directory for test artifacts + mkdir -p app/test-artifacts + + # Check Maestro version and health + echo "📦 Maestro installation details:" + echo "Binary location: $(which maestro)" + echo "Maestro version:" + maestro --version || { + echo "❌ Failed to get Maestro version" + echo "This is a critical error - Maestro is not working" exit 1 + } + echo "" + + # Check Java (required by Maestro) + echo "☕ Java status (required by Maestro):" + if command -v java &>/dev/null; then + java -version 2>&1 | head -3 + echo "✅ Java is available" + else + echo "⚠️ Java not found - this may cause Maestro to fail" + fi + echo "" + + # Check simulator state + echo "📱 Simulator state:" + xcrun simctl list devices | grep -A 2 "$SIMULATOR_ID" || echo "⚠️ Simulator not found" + echo "" + + # Check if app is installed + echo "📲 Checking app installation:" + xcrun simctl listapps "$SIMULATOR_ID" | grep -i "$IOS_BUNDLE_ID" && echo "✅ App is installed" || echo "⚠️ App not found in installed apps" + echo "" + + # Check if app is running + echo "🏃 Checking if app is running:" + APP_PID=$(xcrun simctl spawn "$SIMULATOR_ID" launchctl list | grep "$IOS_BUNDLE_ID" | awk '{print $1}' || echo "") + if [ -n "$APP_PID" ]; then + echo "✅ App process found (PID: $APP_PID)" + else + echo "ℹ️ App not currently running (will be launched by Maestro)" + fi + echo "" + + # Verify test file + echo "📄 Maestro test file:" + if [ -f "app/tests/e2e/launch.ios.flow.yaml" ]; then + echo "✅ Found: app/tests/e2e/launch.ios.flow.yaml" + echo "Contents:" + cat app/tests/e2e/launch.ios.flow.yaml + else + echo "❌ Test file not found" + fi + echo "" + + # Note: Maestro 1.41.0 doesn't have daemon or devices commands + # The test command handles daemon management internally + echo "🔧 Maestro will manage its daemon automatically during test execution" + echo "" + + # Take a screenshot before running tests (if recording enabled) + if [ "${ENABLE_MAESTRO_RECORDING}" = "true" ]; then + echo "📸 Taking pre-test screenshot..." + xcrun simctl io "$SIMULATOR_ID" screenshot app/test-artifacts/pre-test-screenshot.png || echo "⚠️ Screenshot failed" + echo "" fi - # Run Maestro with error handling for cleanup issues - # Note: Maestro may show NSPOSIXErrorDomain code=3 errors during cleanup when - # terminating the test runner app that's already terminated. This is harmless. - MAESTRO_OUTPUT=$(maestro test app/tests/e2e/launch.ios.flow.yaml --format junit --output app/maestro-results.xml 2>&1) + echo "╔════════════════════════════════════════════════════════════════╗" + echo "║ 🎭 STARTING MAESTRO TEST ║" + echo "╚════════════════════════════════════════════════════════════════╝" + echo "" + + # Start native simulator recording BEFORE running tests (if recording enabled) + # This ensures we capture everything even if Maestro fails immediately + if [ "${ENABLE_MAESTRO_RECORDING}" = "true" ]; then + echo "📹 Starting native simulator recording..." + xcrun simctl io "$SIMULATOR_ID" recordVideo --codec=h264 app/test-artifacts/simulator-recording.mp4 & + RECORDING_PID=$! + echo "✅ Recording started (PID: $RECORDING_PID)" + + # Give recording a moment to initialize + sleep 2 + else + echo "ℹ️ Maestro recording disabled (ENABLE_MAESTRO_RECORDING=false)" + fi + + # Run Maestro with verbose output and capture all output (including errors) + echo "🎭 Starting Maestro test with verbose logging..." + echo "Command: maestro test app/tests/e2e/launch.ios.flow.yaml --format junit --output app/maestro-results.xml" + echo "Environment variables:" + echo " MAESTRO_DEVICE_ID: $SIMULATOR_ID" + echo " MAESTRO_DRIVER_STARTUP_TIMEOUT: 180000" + echo " MAESTRO_CLI_NO_ANALYTICS: $MAESTRO_CLI_NO_ANALYTICS" + echo " Working directory: $(pwd)" + echo "" + + set +e # Don't exit on error + # Run with explicit device ID, increased timeout, and verbose output + # Note: exit code 2 typically means Maestro couldn't connect to device or daemon + MAESTRO_OUTPUT=$(MAESTRO_DEVICE_ID="$SIMULATOR_ID" MAESTRO_DRIVER_STARTUP_TIMEOUT=180000 maestro test app/tests/e2e/launch.ios.flow.yaml --format junit --output app/maestro-results.xml 2>&1) MAESTRO_EXIT_CODE=$? + set -e + + echo "Maestro command completed with exit code: $MAESTRO_EXIT_CODE" + echo "" + + # Stop the simulator recording (if recording was started) + if [ "${ENABLE_MAESTRO_RECORDING}" = "true" ] && [ -n "${RECORDING_PID:-}" ]; then + echo "" + echo "🛑 Stopping recording..." + kill -SIGINT $RECORDING_PID 2>/dev/null || true + wait $RECORDING_PID 2>/dev/null || true + sleep 2 # Give time for video to finalize + fi + + # Take a screenshot after test (if recording enabled) + if [ "${ENABLE_MAESTRO_RECORDING}" = "true" ]; then + echo "📸 Taking post-test screenshot..." + xcrun simctl io "$SIMULATOR_ID" screenshot app/test-artifacts/post-test-screenshot.png || echo "⚠️ Screenshot failed" + echo "" + fi + + # Save Maestro output to file for debugging + echo "$MAESTRO_OUTPUT" > app/test-artifacts/maestro-output.log + echo "📝 Maestro output saved to maestro-output.log" + + # Show Maestro output + echo "" + echo "╔════════════════════════════════════════════════════════════════╗" + echo "║ 📋 MAESTRO OUTPUT ║" + echo "╚════════════════════════════════════════════════════════════════╝" + echo "$MAESTRO_OUTPUT" + echo "" + + # Post-test diagnostics + echo "╔════════════════════════════════════════════════════════════════╗" + echo "║ 🔍 POST-TEST DIAGNOSTICS ║" + echo "╚════════════════════════════════════════════════════════════════╝" + echo "" + + # Check if app is still running + echo "🏃 App status after test:" + APP_PID_AFTER=$(xcrun simctl spawn "$SIMULATOR_ID" launchctl list | grep "$IOS_BUNDLE_ID" | awk '{print $1}' || echo "") + if [ -n "$APP_PID_AFTER" ]; then + echo "✅ App still running (PID: $APP_PID_AFTER)" + else + echo "⚠️ App not running" + fi + echo "" + + # Check simulator logs for any crashes + echo "🔍 Checking for crash logs..." + CRASH_LOGS=$(find ~/Library/Logs/DiagnosticReports -name "Self*.crash" -o -name "OpenPassport*.crash" -mmin -5 2>/dev/null | head -5) + if [ -n "$CRASH_LOGS" ]; then + echo "⚠️ Recent crash logs found:" + echo "$CRASH_LOGS" + # Copy crash logs to artifacts + for log in $CRASH_LOGS; do + cp "$log" app/test-artifacts/ 2>/dev/null || true + done + else + echo "✅ No recent crash logs" + fi + echo "" + + # Check system log for relevant messages + echo "📋 Recent simulator system logs (last 50 lines):" + xcrun simctl spawn "$SIMULATOR_ID" log show --predicate 'process == "Self" OR process == "OpenPassport"' --last 5m --style compact 2>/dev/null | tail -50 > app/test-artifacts/simulator-system.log || echo "⚠️ Could not retrieve system logs" + if [ -f app/test-artifacts/simulator-system.log ]; then + echo "✅ System logs saved to simulator-system.log" + echo "Last 20 lines:" + tail -20 app/test-artifacts/simulator-system.log + fi + echo "" + + # Check if video was created (if recording was enabled) + if [ "${ENABLE_MAESTRO_RECORDING}" = "true" ]; then + if [ -f "app/test-artifacts/simulator-recording.mp4" ]; then + VIDEO_SIZE=$(ls -lh app/test-artifacts/simulator-recording.mp4 | awk '{print $5}') + echo "✅ Video recording saved: simulator-recording.mp4 ($VIDEO_SIZE)" + echo "" + echo "📹 ====================================================" + echo "📹 VIDEO RECORDING AVAILABLE" + echo "📹 ====================================================" + echo "📹 To view the test recording:" + echo "📹 1. Go to: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + echo "📹 2. Scroll to the 'Artifacts' section at the bottom" + echo "📹 3. Download 'maestro-artifacts-ios'" + echo "📹 4. Extract and open 'simulator-recording.mp4'" + echo "📹 ====================================================" + echo "" + else + echo "⚠️ Video recording not created" + fi + else + echo "ℹ️ Video recording disabled (ENABLE_MAESTRO_RECORDING=false)" + fi + + # Analyze test results + echo "🔍 Analyzing test results..." + echo "Maestro exit code: $MAESTRO_EXIT_CODE" # Check if tests actually passed (ignore cleanup errors) if echo "$MAESTRO_OUTPUT" | grep -q "Flow Passed"; then @@ -616,20 +858,76 @@ jobs: fi elif echo "$MAESTRO_OUTPUT" | grep -q "Flow Failed"; then echo "❌ Maestro tests failed" + echo "Check the video recording and maestro-output.log for details" exit 1 elif [ $MAESTRO_EXIT_CODE -ne 0 ]; then # Check results file if exit code is non-zero if [ -f "app/maestro-results.xml" ] && ! grep -q "/dev/null || echo " (No artifacts directory found)" + echo "" + - name: Fail job if tests failed + if: steps.maestro-test.outcome == 'failure' + run: | + echo "❌ Maestro tests failed - failing job after artifact upload" + exit 1 diff --git a/app/Gemfile.lock b/app/Gemfile.lock index 903950d00..7a9539418 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.1204.0) - aws-sdk-core (3.241.3) + aws-partitions (1.1209.0) + aws-sdk-core (3.241.4) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -32,11 +32,11 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.120.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-kms (1.121.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.211.0) - aws-sdk-core (~> 3, >= 3.241.3) + aws-sdk-s3 (1.212.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml index 57fdf56d1..c8db40b13 100644 --- a/app/android/app/src/debug/AndroidManifest.xml +++ b/app/android/app/src/debug/AndroidManifest.xml @@ -12,6 +12,12 @@ android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" - tools:replace="android:usesCleartextTraffic" - /> + tools:replace="android:usesCleartextTraffic"> + + + + diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index ff997caca..7180fec3c 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -106,5 +106,11 @@ + + + diff --git a/app/android/build.gradle b/app/android/build.gradle index cef069ee1..ec6e6c33b 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -41,6 +41,7 @@ allprojects { url("$rootDir/../../node_modules/jsc-android/dist") } maven { url 'https://jitpack.io' } + maven { url "https://maven.sumsub.com/repository/maven-public/" } } configurations.configureEach { resolutionStrategy.dependencySubstitution { diff --git a/app/env.sample b/app/env.sample index b75137a3b..de71afad0 100644 --- a/app/env.sample +++ b/app/env.sample @@ -8,4 +8,5 @@ IS_TEST_BUILD= MIXPANEL_NFC_PROJECT_TOKEN= SEGMENT_KEY= SENTRY_DSN= +SUMSUB_TEE_URL= IS_TEST_BUILD= diff --git a/app/env.ts b/app/env.ts index de7072800..c5e041280 100644 --- a/app/env.ts +++ b/app/env.ts @@ -28,6 +28,9 @@ export const IS_TEST_BUILD = process.env.IS_TEST_BUILD === 'true'; export const MIXPANEL_NFC_PROJECT_TOKEN = undefined; export const SEGMENT_KEY = process.env.SEGMENT_KEY; export const SENTRY_DSN = process.env.SENTRY_DSN; +export const SUMSUB_TEE_URL = + process.env.SUMSUB_TEE_URL || 'http://localhost:8080'; +export const SUMSUB_TEST_TOKEN = process.env.SUMSUB_TEST_TOKEN; export const TURNKEY_AUTH_PROXY_CONFIG_ID = process.env.TURNKEY_AUTH_PROXY_CONFIG_ID; diff --git a/app/ios/Podfile b/app/ios/Podfile index c44bb5f41..b08972387 100755 --- a/app/ios/Podfile +++ b/app/ios/Podfile @@ -1,4 +1,16 @@ source "https://cdn.cocoapods.org/" + +# Skip Sumsub configuration for E2E testing +unless ENV["E2E_TESTING"] == "1" + source "https://github.com/SumSubstance/Specs.git" + + # Enable Fisherman (Device Intelligence) module + ENV["IDENSIC_WITH_FISHERMAN"] = "true" + + # Enable VideoIdent module + ENV["IDENSIC_WITH_VIDEOIDENT"] = "true" +end + use_frameworks! require "tmpdir" @@ -39,6 +51,7 @@ def using_https_git_auth? end target "Self" do + # Native module exclusion for E2E testing is handled in react-native.config.cjs config = use_native_modules! use_frameworks! diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 54a7d7609..57d0412d9 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -11,6 +11,7 @@ PODS: - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.76.9) + - FingerprintPro (2.12.0) - Firebase (10.24.0): - Firebase/Core (= 10.24.0) - Firebase/Core (10.24.0): @@ -149,6 +150,19 @@ PODS: - hermes-engine (0.76.9): - hermes-engine/Pre-built (= 0.76.9) - hermes-engine/Pre-built (0.76.9) + - IdensicMobileSDK (1.40.2): + - IdensicMobileSDK/Default (= 1.40.2) + - IdensicMobileSDK/Core (1.40.2) + - IdensicMobileSDK/Default (1.40.2): + - IdensicMobileSDK/Core + - 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 @@ -1537,6 +1551,11 @@ PODS: - Yoga - react-native-get-random-values (1.11.0): - React-Core + - 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 - react-native-nfc-manager (3.16.3): @@ -2133,7 +2152,7 @@ PODS: - ReactCommon/turbomodule/core - Sentry/HybridSDK (= 8.53.2) - Yoga - - RNSVG (15.14.0): + - RNSVG (15.15.0): - DoubleConversion - glog - hermes-engine @@ -2153,9 +2172,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.14.0) + - RNSVG/common (= 15.15.0) - Yoga - - RNSVG/common (15.14.0): + - RNSVG/common (15.15.0): - DoubleConversion - glog - hermes-engine @@ -2185,6 +2204,7 @@ PODS: - React-Core - SwiftQRScanner (1.1.6) - SwiftyTesseract (3.1.3) + - TwilioVideo (5.11.1) - Yoga (0.0.0) DEPENDENCIES: @@ -2243,6 +2263,7 @@ DEPENDENCIES: - react-native-cloud-storage (from `../node_modules/react-native-cloud-storage`) - "react-native-compat (from `../node_modules/@walletconnect/react-native-compat`)" - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) + - "react-native-mobilesdk-module (from `../node_modules/@sumsub/react-native-mobilesdk-module`)" - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`) - react-native-passkey (from `../node_modules/react-native-passkey`) @@ -2296,8 +2317,11 @@ DEPENDENCIES: - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: + https://github.com/SumSubstance/Specs.git: + - IdensicMobileSDK trunk: - AppAuth + - FingerprintPro - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -2322,6 +2346,7 @@ SPEC REPOS: - Sentry - SocketRocket - SwiftyTesseract + - TwilioVideo EXTERNAL SOURCES: boost: @@ -2416,6 +2441,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@walletconnect/react-native-compat" react-native-get-random-values: :path: "../node_modules/react-native-get-random-values" + react-native-mobilesdk-module: + :path: "../node_modules/@sumsub/react-native-mobilesdk-module" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-nfc-manager: @@ -2534,6 +2561,7 @@ SPEC CHECKSUMS: DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 + FingerprintPro: 035517a1b4e3e4fc073486b53b9956509010f8db Firebase: 91fefd38712feb9186ea8996af6cbdef41473442 FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe FirebaseAnalytics: b5efc493eb0f40ec560b04a472e3e1a15d39ca13 @@ -2551,6 +2579,7 @@ SPEC CHECKSUMS: GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 + IdensicMobileSDK: 00b13320e1b1e0574e68475bd0fbc7cd30fce26e lottie-ios: a881093fab623c467d3bce374367755c272bdd59 lottie-react-native: 7bb65bc88d3f9996ea2f646a96694285405df2f9 Mixpanel-swift: e9bef28a9648faff384d5ba6f48ecc2787eb24c0 @@ -2595,6 +2624,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-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7 react-native-passkey: 8853c3c635164864da68a6dbbcec7148506c3bcf @@ -2641,15 +2671,16 @@ SPEC CHECKSUMS: RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73 RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8 RNSentry: 6ad982be2c8e32dab912afb4132b6a0d88484ea0 - RNSVG: e1cf5a9a5aa12c69f2ec47031defbd87ae7fb697 + RNSVG: 39476f26bbbe72ffe6194c6fc8f6acd588087957 segment-analytics-react-native: 0eae155b0e9fa560fa6b17d78941df64537c35b7 Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 sovran-react-native: a3ad3f8ff90c2002b2aa9790001a78b0b0a38594 SwiftQRScanner: e85a25f9b843e9231dab89a96e441472fe54a724 SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb + TwilioVideo: 9f51085d4e4fb3aff8e168b8215b31cb0f486a2f Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407 -PODFILE CHECKSUM: 0aa47f53692543349c43673cda7380fa23049eba +PODFILE CHECKSUM: f03c12b5d96fb6e22afe20fba517840fef44e76f COCOAPODS: 1.16.2 diff --git a/app/ios/SelfAnalytics.swift b/app/ios/SelfAnalytics.swift index c0b96f9de..de0acfba1 100644 --- a/app/ios/SelfAnalytics.swift +++ b/app/ios/SelfAnalytics.swift @@ -1,9 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import Foundation -import NFCPassportReader import Mixpanel +#if !E2E_TESTING +import NFCPassportReader + public class SelfAnalytics: Analytics { private let enableDebugLogs: Bool @@ -67,3 +69,13 @@ public class SelfAnalytics: Analytics { Mixpanel.mainInstance().flush() } } +#else +// E2E Testing stub - SelfAnalytics is not used when NFCPassportReader is excluded +public class SelfAnalytics { + public init(token: String, enableDebugLogs: Bool = false, trackAutomaticEvents: Bool = false) {} + public func trackEvent(_ name: String, properties: [String: Any]? = nil) {} + public func trackDebugEvent(_ name: String, properties: [String: Any]? = nil) {} + public func trackError(_ error: Error, context: String) {} + public func flush() {} +} +#endif diff --git a/app/jest.config.cjs b/app/jest.config.cjs index a7d19eb81..7c4197174 100644 --- a/app/jest.config.cjs +++ b/app/jest.config.cjs @@ -16,7 +16,7 @@ module.exports = { 'node', ], transformIgnorePatterns: [ - 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags|react-native-blur-effect)/)', + 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags|react-native-blur-effect|@sumsub)/)', ], setupFiles: ['/jest.setup.js'], testMatch: [ diff --git a/app/jest.setup.js b/app/jest.setup.js index 1dc286223..4a6401f35 100644 --- a/app/jest.setup.js +++ b/app/jest.setup.js @@ -1237,3 +1237,29 @@ jest.mock('react-native/Libraries/AppState/AppState', () => { }, }; }); + +// Mock @sumsub/react-native-mobilesdk-module +jest.mock('@sumsub/react-native-mobilesdk-module', () => { + const createBuilder = () => ({ + withHandlers: jest.fn().mockReturnThis(), + withDebug: jest.fn().mockReturnThis(), + withLocale: jest.fn().mockReturnThis(), + withAnalyticsEnabled: jest.fn().mockReturnThis(), + build: jest.fn().mockReturnValue({ + launch: jest.fn().mockResolvedValue({ success: true }), + }), + }); + + const MockSNSMobileSDK = { + init: jest + .fn() + .mockImplementation((accessToken, tokenExpirationHandler) => + createBuilder(), + ), + }; + + return { + __esModule: true, + default: MockSNSMobileSDK, + }; +}); diff --git a/app/package.json b/app/package.json index e1cea068b..e6fb8dca6 100644 --- a/app/package.json +++ b/app/package.json @@ -110,6 +110,7 @@ "@sentry/react": "^9.32.0", "@sentry/react-native": "7.0.1", "@stablelib/cbor": "^2.0.1", + "@sumsub/react-native-mobilesdk-module": "1.40.2", "@tamagui/animations-css": "1.126.14", "@tamagui/animations-react-native": "1.126.14", "@tamagui/config": "1.126.14", @@ -163,13 +164,13 @@ "react-native-safe-area-context": "^5.6.1", "react-native-screens": "4.15.3", "react-native-sqlite-storage": "^6.0.1", - "react-native-svg": "15.14.0", + "react-native-svg": "15.15.0", "react-native-svg-web": "1.0.9", "react-native-url-polyfill": "^3.0.0", "react-native-web": "^0.19.0", "react-native-webview": "^13.16.0", "react-qr-barcode-scanner": "^2.1.8", - "socket.io-client": "^4.8.1", + "socket.io-client": "^4.8.3", "tamagui": "1.126.14", "uuid": "^11.1.0", "xstate": "^5.20.2", diff --git a/app/react-native.config.cjs b/app/react-native.config.cjs index 2aafdb3d6..aea045223 100644 --- a/app/react-native.config.cjs +++ b/app/react-native.config.cjs @@ -2,10 +2,19 @@ // SPDX-License-Identifier: BUSL-1.1 // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. +const dependencies = { + '@selfxyz/mobile-sdk-alpha': { platforms: { android: null, ios: null } }, +}; + +// Disable Sumsub SDK autolinking during E2E testing to avoid build issues +if (process.env.E2E_TESTING === '1') { + dependencies['@sumsub/react-native-mobilesdk-module'] = { + platforms: { android: null, ios: null }, + }; +} + module.exports = { project: { ios: {}, android: {} }, - dependencies: { - '@selfxyz/mobile-sdk-alpha': { platforms: { android: null, ios: null } }, - }, + dependencies, assets: ['../src/assets/fonts'], }; diff --git a/app/src/integrations/sumsub/index.ts b/app/src/integrations/sumsub/index.ts new file mode 100644 index 000000000..8a75be10a --- /dev/null +++ b/app/src/integrations/sumsub/index.ts @@ -0,0 +1,14 @@ +// 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. + +export type { + AccessTokenResponse, + SumsubApplicantInfo, + SumsubResult, +} from '@/integrations/sumsub/types'; +export { + type SumsubConfig, + fetchAccessToken, + launchSumsub, +} from '@/integrations/sumsub/sumsubService'; diff --git a/app/src/integrations/sumsub/sumsubService.ts b/app/src/integrations/sumsub/sumsubService.ts new file mode 100644 index 000000000..567c499d1 --- /dev/null +++ b/app/src/integrations/sumsub/sumsubService.ts @@ -0,0 +1,102 @@ +// 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 { SUMSUB_TEE_URL } from '@env'; +import SNSMobileSDK from '@sumsub/react-native-mobilesdk-module'; + +import type { + AccessTokenResponse, + SumsubResult, +} from '@/integrations/sumsub/types'; + +export interface SumsubConfig { + accessToken: string; + locale?: string; + debug?: boolean; + onStatusChanged?: (prevStatus: string, newStatus: string) => void; + onEvent?: (eventType: string, payload: unknown) => void; +} + +const FETCH_TIMEOUT_MS = 30000; // 30 seconds + +export const fetchAccessToken = async ( + phoneNumber: string, +): Promise => { + const apiUrl = SUMSUB_TEE_URL; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); + + try { + const response = await fetch(`${apiUrl}/access-token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ phone: phoneNumber }), + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error( + `Failed to get Sumsub access token (HTTP ${response.status})`, + ); + } + + const body = await response.json(); + + // Handle both string and object responses + if (typeof body === 'string') { + return JSON.parse(body) as AccessTokenResponse; + } + + return body as AccessTokenResponse; + } catch (err) { + clearTimeout(timeoutId); + + if (err instanceof Error) { + if (err.name === 'AbortError') { + throw new Error( + `Request to Sumsub TEE timed out after ${FETCH_TIMEOUT_MS / 1000}s`, + ); + } + throw new Error(`Failed to get Sumsub access token: ${err.message}`); + } + + throw new Error('Failed to get Sumsub access token: Unknown error'); + } +}; + +export const launchSumsub = async ( + config: SumsubConfig, +): Promise => { + const 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', + ); + }) + .withHandlers({ + onStatusChanged: event => { + console.log(`Sumsub status: ${event.prevStatus} => ${event.newStatus}`); + config.onStatusChanged?.(event.prevStatus, event.newStatus); + }, + onLog: _event => { + // Log event received but don't log message (may contain PII) + console.log('[Sumsub] Log event received'); + }, + onEvent: event => { + // Only log event type, not full payload (may contain PII) + console.log(`Sumsub event: ${event.eventType}`); + config.onEvent?.(event.eventType, event.payload); + }, + }) + .withDebug(config.debug ?? __DEV__) + .withLocale(config.locale ?? 'en') + .withAnalyticsEnabled(true) // Device Intelligence requires this + .build(); + + return sdk.launch(); +}; diff --git a/app/src/integrations/sumsub/types.ts b/app/src/integrations/sumsub/types.ts new file mode 100644 index 000000000..f94923439 --- /dev/null +++ b/app/src/integrations/sumsub/types.ts @@ -0,0 +1,40 @@ +// 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. + +export interface AccessTokenResponse { + token: string; + userId: string; +} + +export interface SumsubApplicantInfo { + id: string; + createdAt: string; + key: string; + clientId: string; + inspectionId: string; + externalUserId: string; + info?: { + firstName?: string; + lastName?: string; + dob?: string; + country?: string; + phone?: string; + }; + email?: string; + phone?: string; + review: { + reviewAnswer: string; + reviewResult: { + reviewAnswer: string; + }; + }; + type: string; +} + +export interface SumsubResult { + success: boolean; + status: string; + errorType?: string; + errorMsg?: string; +} diff --git a/app/src/navigation/devTools.ts b/app/src/navigation/devTools.tsx similarity index 90% rename from app/src/navigation/devTools.ts rename to app/src/navigation/devTools.tsx index 3822c18a2..00890401e 100644 --- a/app/src/navigation/devTools.ts +++ b/app/src/navigation/devTools.tsx @@ -13,6 +13,7 @@ import DevHapticFeedbackScreen from '@/screens/dev/DevHapticFeedbackScreen'; import DevLoadingScreen from '@/screens/dev/DevLoadingScreen'; import DevPrivateKeyScreen from '@/screens/dev/DevPrivateKeyScreen'; import DevSettingsScreen from '@/screens/dev/DevSettingsScreen'; +import SumsubTestScreen from '@/screens/dev/SumsubTestScreen'; const devHeaderOptions: NativeStackNavigationOptions = { headerStyle: { @@ -21,6 +22,7 @@ const devHeaderOptions: NativeStackNavigationOptions = { headerTitleStyle: { color: white, }, + headerTintColor: white, headerBackTitle: 'close', }; @@ -80,6 +82,13 @@ const devScreens = { title: 'Dev Loading Screen', } as NativeStackNavigationOptions, }, + SumsubTest: { + screen: SumsubTestScreen, + options: { + ...devHeaderOptions, + title: 'Sumsub Test', + } as NativeStackNavigationOptions, + }, }; export default devScreens; diff --git a/app/src/screens/dev/DevSettingsScreen.tsx b/app/src/screens/dev/DevSettingsScreen.tsx index e4bc857f4..ea4e58bc1 100644 --- a/app/src/screens/dev/DevSettingsScreen.tsx +++ b/app/src/screens/dev/DevSettingsScreen.tsx @@ -681,6 +681,29 @@ const DevSettingsScreen: React.FC = ({}) => { + {IS_DEV_MODE && ( + + + {/* Success Header */} + + + ✓ Verification Complete + + + Your verification was successful + + + + {/* Applicant Info */} + + + Applicant Information + + + + + + Name: + + + {applicantInfo.info?.firstName || 'N/A'}{' '} + {applicantInfo.info?.lastName || 'N/A'} + + + + + + Date of Birth: + + + {applicantInfo.info?.dob || 'N/A'} + + + + + + Country: + + + {applicantInfo.info?.country || 'N/A'} + + + + + + Phone: + + + {applicantInfo.info?.phone || 'N/A'} + + + + + + Email: + + + {applicantInfo.email || 'N/A'} + + + + + + Review Result: + + + {applicantInfo.review.reviewAnswer} + + + + + {/* Raw JSON */} + + + Raw Data: + + + {JSON.stringify(applicantInfo, null, 2)} + + + + + + + + ); + } + + return ( + + + {/* Back Button */} + + + + + {/* TEE Service Status */} + + + TEE Service + + + {SUMSUB_TEE_URL} + + + + {/* Phone Number Input */} + + + Phone Number + + + + + {/* Generate Token Button */} + + + {/* Token Status */} + {accessToken && ( + + + ✓ Access Token Generated + + + User ID: {userId} + + + Token: {accessToken.substring(0, 30)}... + + + )} + + {/* Launch SDK Button */} + {accessToken && ( + + )} + + {/* Error Display */} + {error && ( + + + Error + + + {error} + + + )} + + {/* SDK Result Display */} + {result && ( + + + SDK Result + + + + + Success:{' '} + + {result.success ? 'Yes' : 'No'} + + + + + Status:{' '} + + {result.status} + + + + {result.errorType && ( + + Error Type:{' '} + + {result.errorType} + + + )} + + {result.errorMsg && ( + + Error Message:{' '} + + {result.errorMsg} + + + )} + + + + Waiting for verification results from WebSocket... + + + )} + + {/* Instructions */} + + + Instructions + + + 1. Make sure the TEE service is running at {SUMSUB_TEE_URL} + + + 2. Enter a phone number and tap "Generate Access Token" + + + 3. Tap "Launch Sumsub SDK" to start verification + + + 4. Complete the verification flow + + + 5. Results will appear automatically via WebSocket + + + + + ); +}; + +export default SumsubTestScreen; diff --git a/app/src/types/sumsub.d.ts b/app/src/types/sumsub.d.ts new file mode 100644 index 000000000..d981aa3c9 --- /dev/null +++ b/app/src/types/sumsub.d.ts @@ -0,0 +1,56 @@ +// 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. + +declare module '@sumsub/react-native-mobilesdk-module' { + export interface SumsubEvent { + eventType: string; + payload: Record; + } + + export interface SumsubHandlers { + onStatusChanged?: (event: SumsubStatusChangedEvent) => void; + onLog?: (event: SumsubLogEvent) => void; + onEvent?: (event: SumsubEvent) => void; + } + + export interface SumsubLogEvent { + message: string; + } + + export interface SumsubResult { + success: boolean; + status: string; + errorType?: string; + errorMsg?: string; + } + + export interface SumsubSDK { + withHandlers(handlers: SumsubHandlers): SumsubSDK; + withDebug(debug: boolean): SumsubSDK; + withLocale(locale: string): SumsubSDK; + withAnalyticsEnabled(enabled: boolean): SumsubSDK; + withAutoCloseOnApprove(seconds: number): SumsubSDK; + withApplicantConf(config: { email?: string; phone?: string }): SumsubSDK; + withPreferredDocumentDefinitions( + definitions: Record, + ): SumsubSDK; + withDisableMLKit(disable: boolean): SumsubSDK; + withStrings(strings: Record): SumsubSDK; + build(): SumsubSDK; + launch(): Promise; + dismiss(): void; + } + + export interface SumsubStatusChangedEvent { + prevStatus: string; + newStatus: string; + } + + export default class SNSMobileSDK { + static init( + accessToken: string, + tokenExpirationHandler: () => Promise, + ): SumsubSDK; + } +} diff --git a/app/tests/src/integrations/nfc/nfcScanner.test.ts b/app/tests/src/integrations/nfc/nfcScanner.test.ts index 0d9381b6e..1b107731c 100644 --- a/app/tests/src/integrations/nfc/nfcScanner.test.ts +++ b/app/tests/src/integrations/nfc/nfcScanner.test.ts @@ -7,7 +7,7 @@ // This pattern avoids hoisting issues with jest.mock import { Buffer } from 'buffer'; -import { parseScanResponse, scan } from '@/integrations/nfc/nfcScanner'; +import { scan } from '@/integrations/nfc/nfcScanner'; import { PassportReader } from '@/integrations/nfc/passportReader'; // Declare global variable for platform OS that can be modified per-test diff --git a/app/tests/src/navigation.test.tsx b/app/tests/src/navigation.test.tsx index 9a02ad058..6723984c9 100644 --- a/app/tests/src/navigation.test.tsx +++ b/app/tests/src/navigation.test.tsx @@ -24,6 +24,24 @@ jest.mock('@/services/analytics', () => ({ flush: jest.fn(), })); +// Mock Sumsub SDK to prevent ES module parsing errors in isolateModules +jest.mock('@sumsub/react-native-mobilesdk-module', () => { + const createBuilder = () => ({ + withHandlers: jest.fn().mockReturnThis(), + withDebug: jest.fn().mockReturnThis(), + withLocale: jest.fn().mockReturnThis(), + withAnalyticsEnabled: jest.fn().mockReturnThis(), + build: jest.fn().mockReturnValue({ + launch: jest.fn().mockResolvedValue({ success: true }), + }), + }); + + return { + __esModule: true, + default: { init: jest.fn(() => createBuilder()) }, + }; +}); + describe('navigation', () => { beforeEach(() => { jest.clearAllMocks(); @@ -88,6 +106,7 @@ describe('navigation', () => { 'ShowRecoveryPhrase', 'Splash', 'StarfallPushCode', + 'SumsubTest', 'WebView', ]); }); diff --git a/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch b/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch new file mode 100644 index 000000000..8b514d876 --- /dev/null +++ b/patches/@sumsub+react-native-mobilesdk-module+1.40.2.patch @@ -0,0 +1,14 @@ +diff --git a/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle b/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle +index 1234567..abcdefg 100644 +--- a/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle ++++ b/node_modules/@sumsub/react-native-mobilesdk-module/android/build.gradle +@@ -69,9 +69,9 @@ dependencies { + implementation "com.sumsub.sns:idensic-mobile-sdk:1.40.2" + + // 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" + // 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" + // remove comment if you need EID support diff --git a/yarn.lock b/yarn.lock index ba4ea7f69..a80c25d33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8486,6 +8486,7 @@ __metadata: "@sentry/react": "npm:^9.32.0" "@sentry/react-native": "npm:7.0.1" "@stablelib/cbor": "npm:^2.0.1" + "@sumsub/react-native-mobilesdk-module": "npm:1.40.2" "@tamagui/animations-css": "npm:1.126.14" "@tamagui/animations-react-native": "npm:1.126.14" "@tamagui/config": "npm:1.126.14" @@ -8577,7 +8578,7 @@ __metadata: react-native-safe-area-context: "npm:^5.6.1" react-native-screens: "npm:4.15.3" react-native-sqlite-storage: "npm:^6.0.1" - react-native-svg: "npm:15.14.0" + react-native-svg: "npm:15.15.0" react-native-svg-transformer: "npm:^1.5.1" react-native-svg-web: "npm:1.0.9" react-native-url-polyfill: "npm:^3.0.0" @@ -8586,7 +8587,7 @@ __metadata: react-qr-barcode-scanner: "npm:^2.1.8" react-test-renderer: "npm:^18.3.1" rollup-plugin-visualizer: "npm:^6.0.3" - socket.io-client: "npm:^4.8.1" + socket.io-client: "npm:^4.8.3" stream-browserify: "npm:^3.0.0" tamagui: "npm:1.126.14" ts-morph: "npm:^22.0.0" @@ -11147,6 +11148,15 @@ __metadata: languageName: node linkType: hard +"@sumsub/react-native-mobilesdk-module@npm:1.40.2": + version: 1.40.2 + resolution: "@sumsub/react-native-mobilesdk-module@npm:1.40.2" + peerDependencies: + react-native: ">=0.60.0-rc.0 <1.0.x" + checksum: 10c0/fc5f368d3afdffdb27496ab3b152b38bb0ee8b52f8592b2b48e0a3941197de33fd445502123eeca676d0ee1ecaeaa2e7c6573601e06c4eea79dfa2aec64ed75b + languageName: node + linkType: hard + "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" @@ -29840,9 +29850,9 @@ __metadata: languageName: node linkType: hard -"react-native-svg@npm:15.14.0": - version: 15.14.0 - resolution: "react-native-svg@npm:15.14.0" +"react-native-svg@npm:15.15.0": + version: 15.15.0 + resolution: "react-native-svg@npm:15.15.0" dependencies: css-select: "npm:^5.1.0" css-tree: "npm:^1.1.3" @@ -29850,7 +29860,7 @@ __metadata: peerDependencies: react: "*" react-native: "*" - checksum: 10c0/5855bee2a76313f580ac3f8c476d07bb63d1a8e5e0883154275be5a1f4224e35f2416b0ba3b03f5d4c637c3b2f9320df34ede918da27188adb6881b3b8ac96c8 + checksum: 10c0/0da39529fedcc84d2deaad19e261453069675520cb446a08d97eebc10f1205ce0393a9760ad58bc7b80feaa60e94c30d2d5b90d3111451f260b3888a4fbc2e06 languageName: node linkType: hard @@ -31782,7 +31792,7 @@ __metadata: languageName: node linkType: hard -"socket.io-client@npm:^4.8.1": +"socket.io-client@npm:^4.8.1, socket.io-client@npm:^4.8.3": version: 4.8.3 resolution: "socket.io-client@npm:4.8.3" dependencies: