From 3674422c4943217a0029812ae9b63332528158a8 Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Tue, 30 Jun 2026 09:42:49 -0400 Subject: [PATCH 1/7] fixes sdk versions in Android builds --- packages/react-native-platform/android/build.gradle | 10 +++++++--- .../react-native-webcrypto-bridge/android/build.gradle | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/react-native-platform/android/build.gradle b/packages/react-native-platform/android/build.gradle index d9d80326..ae2747a6 100644 --- a/packages/react-native-platform/android/build.gradle +++ b/packages/react-native-platform/android/build.gradle @@ -27,14 +27,18 @@ if (isNewArchitectureEnabled()) { apply plugin: 'com.facebook.react' } +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + android { namespace "com.okta.reactnativeplatform" - compileSdkVersion 36 + compileSdkVersion safeExtGet('compileSdkVersion', 36) defaultConfig { - minSdkVersion 24 - targetSdkVersion 35 + minSdkVersion safeExtGet('minSdkVersion', 24) + targetSdkVersion safeExtGet('targetSdkVersion', 35) versionCode 1 versionName "1.0" diff --git a/packages/react-native-webcrypto-bridge/android/build.gradle b/packages/react-native-webcrypto-bridge/android/build.gradle index 7b4c144c..e2c4f274 100644 --- a/packages/react-native-webcrypto-bridge/android/build.gradle +++ b/packages/react-native-webcrypto-bridge/android/build.gradle @@ -27,13 +27,17 @@ if (isNewArchitectureEnabled()) { apply plugin: 'com.facebook.react' } +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + android { namespace "com.okta.webcryptobridge" - compileSdkVersion 35 + compileSdkVersion safeExtGet('compileSdkVersion', 35) defaultConfig { - minSdkVersion 24 - targetSdkVersion 35 + minSdkVersion safeExtGet('minSdkVersion', 24) + targetSdkVersion safeExtGet('targetSdkVersion', 35) versionCode 1 versionName "1.0" } From 04a135a4f02e98b88ceb64562db39816d052183a Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Tue, 30 Jun 2026 09:53:09 -0400 Subject: [PATCH 2/7] adds ios project to e2e sample --- e2e/apps/react-native-oidc/.gitignore | 1 - e2e/apps/react-native-oidc/ios/.gitignore | 30 + e2e/apps/react-native-oidc/ios/.xcode.env | 11 + e2e/apps/react-native-oidc/ios/Podfile | 63 + e2e/apps/react-native-oidc/ios/Podfile.lock | 2671 +++++++++++++++++ .../ios/Podfile.properties.json | 7 + .../project.pbxproj | 548 ++++ .../xcschemes/reporeactnativeoidc.xcscheme | 88 + .../contents.xcworkspacedata | 10 + .../ios/reporeactnativeoidc/AppDelegate.swift | 70 + .../App-Icon-1024x1024@1x.png | Bin 0 -> 5856 bytes .../AppIcon.appiconset/Contents.json | 14 + .../Images.xcassets/Contents.json | 6 + .../Contents.json | 20 + .../ios/reporeactnativeoidc/Info.plist | 82 + .../reporeactnativeoidc/PrivacyInfo.xcprivacy | 48 + .../SplashScreen.storyboard | 39 + .../reporeactnativeoidc/Supporting/Expo.plist | 12 + .../reporeactnativeoidc-Bridging-Header.h | 3 + .../reporeactnativeoidc.entitlements | 5 + 20 files changed, 3727 insertions(+), 1 deletion(-) create mode 100644 e2e/apps/react-native-oidc/ios/.gitignore create mode 100644 e2e/apps/react-native-oidc/ios/.xcode.env create mode 100644 e2e/apps/react-native-oidc/ios/Podfile create mode 100644 e2e/apps/react-native-oidc/ios/Podfile.lock create mode 100644 e2e/apps/react-native-oidc/ios/Podfile.properties.json create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcworkspace/contents.xcworkspacedata create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/AppDelegate.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/Contents.json create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/SplashScreenBackground.colorset/Contents.json create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Info.plist create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/PrivacyInfo.xcprivacy create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/SplashScreen.storyboard create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Supporting/Expo.plist create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc.entitlements diff --git a/e2e/apps/react-native-oidc/.gitignore b/e2e/apps/react-native-oidc/.gitignore index 4884c274..9dbcbc27 100644 --- a/e2e/apps/react-native-oidc/.gitignore +++ b/e2e/apps/react-native-oidc/.gitignore @@ -8,7 +8,6 @@ node_modules/ dist/ web-build/ expo-env.d.ts -ios/ # Native .kotlin/ diff --git a/e2e/apps/react-native-oidc/ios/.gitignore b/e2e/apps/react-native-oidc/ios/.gitignore new file mode 100644 index 00000000..8beb3443 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/.gitignore @@ -0,0 +1,30 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace +.xcode.env.local + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/Pods/ diff --git a/e2e/apps/react-native-oidc/ios/.xcode.env b/e2e/apps/react-native-oidc/ios/.xcode.env new file mode 100644 index 00000000..3d5782c7 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/e2e/apps/react-native-oidc/ios/Podfile b/e2e/apps/react-native-oidc/ios/Podfile new file mode 100644 index 00000000..e011308e --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/Podfile @@ -0,0 +1,63 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") +require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") + +require 'json' +podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} + +def ccache_enabled?(podfile_properties) + # Environment variable takes precedence + return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE'] + + # Fall back to Podfile properties + podfile_properties['apple.ccacheEnabled'] == 'true' +end + +ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false' +ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] +ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false' +ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false' +platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1' + +prepare_react_native_project! + +target 'reporeactnativeoidc' do + use_expo_modules! + + if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' + config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; + else + config_command = [ + 'node', + '--no-warnings', + '--eval', + 'require(\'expo/bin/autolinking\')', + 'expo-modules-autolinking', + 'react-native-config', + '--json', + '--platform', + 'ios' + ] + end + + config = use_native_modules!(config_command) + + use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] + use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] + + use_react_native!( + :path => config[:reactNativePath], + :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/..", + :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', + ) + + post_install do |installer| + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + :ccache_enabled => ccache_enabled?(podfile_properties), + ) + end +end diff --git a/e2e/apps/react-native-oidc/ios/Podfile.lock b/e2e/apps/react-native-oidc/ios/Podfile.lock new file mode 100644 index 00000000..ac3b0581 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/Podfile.lock @@ -0,0 +1,2671 @@ +PODS: + - EXConstants (18.0.13): + - ExpoModulesCore + - EXJSONUtils (0.15.0) + - EXManifests (1.0.10): + - ExpoModulesCore + - Expo (54.0.33): + - ExpoModulesCore + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-client (6.0.20): + - EXManifests + - expo-dev-launcher + - expo-dev-menu + - expo-dev-menu-interface + - EXUpdatesInterface + - expo-dev-launcher (6.0.20): + - EXManifests + - expo-dev-launcher/Main (= 6.0.20) + - expo-dev-menu + - expo-dev-menu-interface + - ExpoModulesCore + - EXUpdatesInterface + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-launcher/Main (6.0.20): + - EXManifests + - expo-dev-launcher/Unsafe + - expo-dev-menu + - expo-dev-menu-interface + - ExpoModulesCore + - EXUpdatesInterface + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-launcher/Unsafe (6.0.20): + - EXManifests + - expo-dev-menu + - expo-dev-menu-interface + - ExpoModulesCore + - EXUpdatesInterface + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactAppDependencyProvider + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-menu (7.0.18): + - expo-dev-menu/Main (= 7.0.18) + - expo-dev-menu/ReactNativeCompatibles (= 7.0.18) + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-menu-interface (2.0.0) + - expo-dev-menu/Main (7.0.18): + - EXManifests + - expo-dev-menu-interface + - ExpoModulesCore + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - expo-dev-menu/ReactNativeCompatibles (7.0.18): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - ExpoAsset (12.0.12): + - ExpoModulesCore + - ExpoBlur (15.0.8): + - ExpoModulesCore + - ExpoCrypto (15.0.8): + - ExpoModulesCore + - ExpoFileSystem (19.0.21): + - ExpoModulesCore + - ExpoFont (14.0.11): + - ExpoModulesCore + - ExpoHaptics (15.0.8): + - ExpoModulesCore + - ExpoHead (6.0.23): + - ExpoModulesCore + - RNScreens + - ExpoImage (3.0.11): + - ExpoModulesCore + - libavif/libdav1d + - SDWebImage (~> 5.21.0) + - SDWebImageAVIFCoder (~> 0.11.0) + - SDWebImageSVGCoder (~> 1.7.0) + - SDWebImageWebPCoder (~> 0.14.6) + - ExpoKeepAwake (15.0.8): + - ExpoModulesCore + - ExpoLinking (8.0.11): + - ExpoModulesCore + - ExpoModulesCore (3.0.29): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - ExpoSplashScreen (31.0.13): + - ExpoModulesCore + - ExpoSymbols (1.0.8): + - ExpoModulesCore + - ExpoSystemUI (6.0.9): + - ExpoModulesCore + - EXUpdatesInterface (2.0.0): + - ExpoModulesCore + - FBLazyVector (0.81.5) + - hermes-engine (0.81.5): + - hermes-engine/Pre-built (= 0.81.5) + - hermes-engine/Pre-built (0.81.5) + - libavif/core (1.0.0) + - libavif/libdav1d (1.0.0): + - libavif/core + - libdav1d (>= 0.6.0) + - libdav1d (1.2.0) + - libwebp (1.5.0): + - libwebp/demux (= 1.5.0) + - libwebp/mux (= 1.5.0) + - libwebp/sharpyuv (= 1.5.0) + - libwebp/webp (= 1.5.0) + - libwebp/demux (1.5.0): + - libwebp/webp + - libwebp/mux (1.5.0): + - libwebp/demux + - libwebp/sharpyuv (1.5.0) + - libwebp/webp (1.5.0): + - libwebp/sharpyuv + - RCTDeprecation (0.81.5) + - RCTRequired (0.81.5) + - RCTTypeSafety (0.81.5): + - FBLazyVector (= 0.81.5) + - RCTRequired (= 0.81.5) + - React-Core (= 0.81.5) + - React (0.81.5): + - React-Core (= 0.81.5) + - React-Core/DevSupport (= 0.81.5) + - React-Core/RCTWebSocket (= 0.81.5) + - React-RCTActionSheet (= 0.81.5) + - React-RCTAnimation (= 0.81.5) + - React-RCTBlob (= 0.81.5) + - React-RCTImage (= 0.81.5) + - React-RCTLinking (= 0.81.5) + - React-RCTNetwork (= 0.81.5) + - React-RCTSettings (= 0.81.5) + - React-RCTText (= 0.81.5) + - React-RCTVibration (= 0.81.5) + - React-callinvoker (0.81.5) + - React-Core (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.81.5) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core-prebuilt (0.81.5): + - ReactNativeDependencies + - React-Core/CoreModulesHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/Default (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/DevSupport (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.81.5) + - React-Core/RCTWebSocket (= 0.81.5) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTActionSheetHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTAnimationHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTBlobHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTImageHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTLinkingHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTNetworkHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTSettingsHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTTextHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTVibrationHeaders (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-Core/RCTWebSocket (0.81.5): + - hermes-engine + - RCTDeprecation + - React-Core-prebuilt + - React-Core/Default (= 0.81.5) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-CoreModules (0.81.5): + - RCTTypeSafety (= 0.81.5) + - React-Core-prebuilt + - React-Core/CoreModulesHeaders (= 0.81.5) + - React-jsi (= 0.81.5) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-NativeModulesApple + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.81.5) + - React-runtimeexecutor + - ReactCommon + - ReactNativeDependencies + - React-cxxreact (0.81.5): + - hermes-engine + - React-callinvoker (= 0.81.5) + - React-Core-prebuilt + - React-debug (= 0.81.5) + - React-jsi (= 0.81.5) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.81.5) + - React-perflogger (= 0.81.5) + - React-runtimeexecutor + - React-timing (= 0.81.5) + - ReactNativeDependencies + - React-debug (0.81.5) + - React-defaultsnativemodule (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-domnativemodule + - React-featureflagsnativemodule + - React-idlecallbacksnativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-RCTFBReactNativeSpec + - ReactNativeDependencies + - React-domnativemodule (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-Fabric + - React-Fabric/bridging + - React-FabricComponents + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/animations (= 0.81.5) + - React-Fabric/attributedstring (= 0.81.5) + - React-Fabric/bridging (= 0.81.5) + - React-Fabric/componentregistry (= 0.81.5) + - React-Fabric/componentregistrynative (= 0.81.5) + - React-Fabric/components (= 0.81.5) + - React-Fabric/consistency (= 0.81.5) + - React-Fabric/core (= 0.81.5) + - React-Fabric/dom (= 0.81.5) + - React-Fabric/imagemanager (= 0.81.5) + - React-Fabric/leakchecker (= 0.81.5) + - React-Fabric/mounting (= 0.81.5) + - React-Fabric/observers (= 0.81.5) + - React-Fabric/scheduler (= 0.81.5) + - React-Fabric/telemetry (= 0.81.5) + - React-Fabric/templateprocessor (= 0.81.5) + - React-Fabric/uimanager (= 0.81.5) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/animations (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/attributedstring (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/bridging (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistry (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/componentregistrynative (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.81.5) + - React-Fabric/components/root (= 0.81.5) + - React-Fabric/components/scrollview (= 0.81.5) + - React-Fabric/components/view (= 0.81.5) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/legacyviewmanagerinterop (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/root (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/scrollview (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/components/view (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-Fabric/consistency (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/core (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/dom (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/imagemanager (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/leakchecker (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/mounting (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.81.5) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/observers/events (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/scheduler (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancetimeline + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/telemetry (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/templateprocessor (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.81.5) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-Fabric/uimanager/consistency (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-FabricComponents (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.81.5) + - React-FabricComponents/textlayoutmanager (= 0.81.5) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.81.5) + - React-FabricComponents/components/iostextinput (= 0.81.5) + - React-FabricComponents/components/modal (= 0.81.5) + - React-FabricComponents/components/rncore (= 0.81.5) + - React-FabricComponents/components/safeareaview (= 0.81.5) + - React-FabricComponents/components/scrollview (= 0.81.5) + - React-FabricComponents/components/switch (= 0.81.5) + - React-FabricComponents/components/text (= 0.81.5) + - React-FabricComponents/components/textinput (= 0.81.5) + - React-FabricComponents/components/unimplementedview (= 0.81.5) + - React-FabricComponents/components/virtualview (= 0.81.5) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/inputaccessory (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/iostextinput (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/modal (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/rncore (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/safeareaview (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/scrollview (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/switch (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/text (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/textinput (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/unimplementedview (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/components/virtualview (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricComponents/textlayoutmanager (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-FabricImage (0.81.5): + - hermes-engine + - RCTRequired (= 0.81.5) + - RCTTypeSafety (= 0.81.5) + - React-Core-prebuilt + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.81.5) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-featureflags (0.81.5): + - React-Core-prebuilt + - ReactNativeDependencies + - React-featureflagsnativemodule (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-graphics (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-utils + - ReactNativeDependencies + - React-hermes (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.81.5) + - React-jsi + - React-jsiexecutor (= 0.81.5) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-perflogger (= 0.81.5) + - React-runtimeexecutor + - ReactNativeDependencies + - React-idlecallbacksnativemodule (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-ImageManager (0.81.5): + - React-Core-prebuilt + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - ReactNativeDependencies + - React-jserrorhandler (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - ReactCommon/turbomodule/bridging + - ReactNativeDependencies + - React-jsi (0.81.5): + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsiexecutor (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact (= 0.81.5) + - React-jsi (= 0.81.5) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-perflogger (= 0.81.5) + - React-runtimeexecutor + - ReactNativeDependencies + - React-jsinspector (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-oscompat + - React-perflogger (= 0.81.5) + - React-runtimeexecutor + - ReactNativeDependencies + - React-jsinspectorcdp (0.81.5): + - React-Core-prebuilt + - ReactNativeDependencies + - React-jsinspectornetwork (0.81.5): + - React-Core-prebuilt + - React-featureflags + - React-jsinspectorcdp + - React-performancetimeline + - React-timing + - ReactNativeDependencies + - React-jsinspectortracing (0.81.5): + - React-Core-prebuilt + - React-oscompat + - React-timing + - ReactNativeDependencies + - React-jsitooling (0.81.5): + - React-Core-prebuilt + - React-cxxreact (= 0.81.5) + - React-jsi (= 0.81.5) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-runtimeexecutor + - ReactNativeDependencies + - React-jsitracing (0.81.5): + - React-jsi + - React-logger (0.81.5): + - React-Core-prebuilt + - ReactNativeDependencies + - React-Mapbuffer (0.81.5): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-microtasksnativemodule (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - react-native-platform (0.7.0): + - React-Core + - react-native-safe-area-context (5.6.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common (= 5.6.2) + - react-native-safe-area-context/fabric (= 5.6.2) + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/common (5.6.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-safe-area-context/fabric (5.6.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-webcrypto-bridge (0.7.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - react-native-webview (13.15.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - React-NativeModulesApple (0.81.5): + - hermes-engine + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-cxxreact + - React-featureflags + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - React-oscompat (0.81.5) + - React-perflogger (0.81.5): + - React-Core-prebuilt + - ReactNativeDependencies + - React-performancetimeline (0.81.5): + - React-Core-prebuilt + - React-featureflags + - React-jsinspectortracing + - React-perflogger + - React-timing + - ReactNativeDependencies + - React-RCTActionSheet (0.81.5): + - React-Core/RCTActionSheetHeaders (= 0.81.5) + - React-RCTAnimation (0.81.5): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTAnimationHeaders + - React-featureflags + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTAppDelegate (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsitooling + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTNetwork + - React-RCTRuntime + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon + - ReactNativeDependencies + - React-RCTBlob (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTFabric (0.81.5): + - hermes-engine + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-RCTAnimation + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-renderercss + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - Yoga + - React-RCTFBReactNativeSpec (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec/components (= 0.81.5) + - ReactCommon + - ReactNativeDependencies + - React-RCTFBReactNativeSpec/components (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon + - ReactNativeDependencies + - Yoga + - React-RCTImage (0.81.5): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - ReactNativeDependencies + - React-RCTLinking (0.81.5): + - React-Core/RCTLinkingHeaders (= 0.81.5) + - React-jsi (= 0.81.5) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.81.5) + - React-RCTNetwork (0.81.5): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTNetworkHeaders + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTRuntime (0.81.5): + - hermes-engine + - React-Core + - React-Core-prebuilt + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-RuntimeApple + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - ReactNativeDependencies + - React-RCTSettings (0.81.5): + - RCTTypeSafety + - React-Core-prebuilt + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-RCTText (0.81.5): + - React-Core/RCTTextHeaders (= 0.81.5) + - Yoga + - React-RCTVibration (0.81.5): + - React-Core-prebuilt + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactNativeDependencies + - React-rendererconsistency (0.81.5) + - React-renderercss (0.81.5): + - React-debug + - React-utils + - React-rendererdebug (0.81.5): + - React-Core-prebuilt + - React-debug + - ReactNativeDependencies + - React-RuntimeApple (0.81.5): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-RuntimeCore (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-cxxreact + - React-Fabric + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactNativeDependencies + - React-runtimeexecutor (0.81.5): + - React-Core-prebuilt + - React-debug + - React-featureflags + - React-jsi (= 0.81.5) + - React-utils + - ReactNativeDependencies + - React-RuntimeHermes (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-jsitracing + - React-RuntimeCore + - React-runtimeexecutor + - React-utils + - ReactNativeDependencies + - React-runtimescheduler (0.81.5): + - hermes-engine + - React-callinvoker + - React-Core-prebuilt + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-jsinspectortracing + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - ReactNativeDependencies + - React-timing (0.81.5): + - React-debug + - React-utils (0.81.5): + - hermes-engine + - React-Core-prebuilt + - React-debug + - React-jsi (= 0.81.5) + - ReactNativeDependencies + - ReactAppDependencyProvider (0.81.5): + - ReactCodegen + - ReactCodegen (0.81.5): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-RCTAppDelegate + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - ReactCommon (0.81.5): + - React-Core-prebuilt + - ReactCommon/turbomodule (= 0.81.5) + - ReactNativeDependencies + - ReactCommon/turbomodule (0.81.5): + - hermes-engine + - React-callinvoker (= 0.81.5) + - React-Core-prebuilt + - React-cxxreact (= 0.81.5) + - React-jsi (= 0.81.5) + - React-logger (= 0.81.5) + - React-perflogger (= 0.81.5) + - ReactCommon/turbomodule/bridging (= 0.81.5) + - ReactCommon/turbomodule/core (= 0.81.5) + - ReactNativeDependencies + - ReactCommon/turbomodule/bridging (0.81.5): + - hermes-engine + - React-callinvoker (= 0.81.5) + - React-Core-prebuilt + - React-cxxreact (= 0.81.5) + - React-jsi (= 0.81.5) + - React-logger (= 0.81.5) + - React-perflogger (= 0.81.5) + - ReactNativeDependencies + - ReactCommon/turbomodule/core (0.81.5): + - hermes-engine + - React-callinvoker (= 0.81.5) + - React-Core-prebuilt + - React-cxxreact (= 0.81.5) + - React-debug (= 0.81.5) + - React-featureflags (= 0.81.5) + - React-jsi (= 0.81.5) + - React-logger (= 0.81.5) + - React-perflogger (= 0.81.5) + - React-utils (= 0.81.5) + - ReactNativeDependencies + - ReactNativeDependencies (0.81.5) + - RNGestureHandler (2.28.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNReanimated (4.1.7): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNReanimated/reanimated (= 4.1.7) + - RNWorklets + - Yoga + - RNReanimated/reanimated (4.1.7): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNReanimated/reanimated/apple (= 4.1.7) + - RNWorklets + - Yoga + - RNReanimated/reanimated/apple (4.1.7): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets + - Yoga + - RNScreens (4.16.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNScreens/common (= 4.16.0) + - Yoga + - RNScreens/common (4.16.0): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - RNWorklets (0.5.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets/worklets (= 0.5.1) + - Yoga + - RNWorklets/worklets (0.5.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - RNWorklets/worklets/apple (= 0.5.1) + - Yoga + - RNWorklets/worklets/apple (0.5.1): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga + - SDWebImage (5.21.7): + - SDWebImage/Core (= 5.21.7) + - SDWebImage/Core (5.21.7) + - SDWebImageAVIFCoder (0.11.1): + - libavif/core (>= 0.11.0) + - SDWebImage (~> 5.10) + - SDWebImageSVGCoder (1.7.0): + - SDWebImage/Core (~> 5.6) + - SDWebImageWebPCoder (0.14.6): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.17) + - Yoga (0.0.0) + +DEPENDENCIES: + - EXConstants (from `../../../../node_modules/expo-constants/ios`) + - EXJSONUtils (from `../../../../node_modules/expo-json-utils/ios`) + - EXManifests (from `../../../../node_modules/expo-manifests/ios`) + - Expo (from `../../../../node_modules/expo`) + - expo-dev-client (from `../../../../node_modules/expo-dev-client/ios`) + - expo-dev-launcher (from `../../../../node_modules/expo-dev-launcher`) + - expo-dev-menu (from `../../../../node_modules/expo-dev-menu`) + - expo-dev-menu-interface (from `../../../../node_modules/expo-dev-menu-interface/ios`) + - ExpoAsset (from `../../../../node_modules/expo-asset/ios`) + - ExpoBlur (from `../../../../node_modules/expo-blur/ios`) + - ExpoCrypto (from `../../../../node_modules/expo-crypto/ios`) + - ExpoFileSystem (from `../../../../node_modules/expo-file-system/ios`) + - ExpoFont (from `../../../../node_modules/expo-font/ios`) + - ExpoHaptics (from `../../../../node_modules/expo-haptics/ios`) + - ExpoHead (from `../../../../node_modules/expo-router/ios`) + - ExpoImage (from `../../../../node_modules/expo-image/ios`) + - ExpoKeepAwake (from `../../../../node_modules/expo-keep-awake/ios`) + - ExpoLinking (from `../../../../node_modules/expo-linking/ios`) + - ExpoModulesCore (from `../../../../node_modules/expo-modules-core`) + - ExpoSplashScreen (from `../../../../node_modules/expo-splash-screen/ios`) + - ExpoSymbols (from `../../../../node_modules/expo-symbols/ios`) + - ExpoSystemUI (from `../../../../node_modules/expo-system-ui/ios`) + - EXUpdatesInterface (from `../../../../node_modules/expo-updates-interface/ios`) + - FBLazyVector (from `../../../../node_modules/react-native/Libraries/FBLazyVector`) + - hermes-engine (from `../../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - RCTDeprecation (from `../../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../../../../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../../../../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../../../../node_modules/react-native/`) + - React-callinvoker (from `../../../../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../../../../node_modules/react-native/`) + - React-Core-prebuilt (from `../../../../node_modules/react-native/React-Core-prebuilt.podspec`) + - React-Core/RCTWebSocket (from `../../../../node_modules/react-native/`) + - React-CoreModules (from `../../../../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../../../../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../../../../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../../../../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../../../../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../../../../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../../../../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../../../../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../../../../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../../../../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../../../../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../../../../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../../../../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../../../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../../../../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../../../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../../../../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../../../../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../../../../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../../../../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-platform (from `../../../../packages/react-native-platform`) + - react-native-safe-area-context (from `../../../../node_modules/react-native-safe-area-context`) + - react-native-webcrypto-bridge (from `../../../../packages/react-native-webcrypto-bridge`) + - react-native-webview (from `../../../../node_modules/react-native-webview`) + - React-NativeModulesApple (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-oscompat (from `../../../../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../../../../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancetimeline (from `../../../../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../../../../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../../../../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../../../../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../../../../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../../../../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../../../../node_modules/react-native/React`) + - React-RCTImage (from `../../../../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../../../../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../../../../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../../../../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../../../../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../../../../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../../../../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../../../../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../../../../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../../../../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-RuntimeApple (from `../../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../../../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../../../../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../../../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../../../../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../../../../node_modules/react-native/ReactCommon/react/utils`) + - ReactAppDependencyProvider (from `build/generated/ios`) + - ReactCodegen (from `build/generated/ios`) + - ReactCommon/turbomodule/core (from `../../../../node_modules/react-native/ReactCommon`) + - ReactNativeDependencies (from `../../../../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) + - RNGestureHandler (from `../../../../node_modules/react-native-gesture-handler`) + - RNReanimated (from `../../../../node_modules/react-native-reanimated`) + - RNScreens (from `../../../../node_modules/react-native-screens`) + - RNWorklets (from `../../../../node_modules/react-native-worklets`) + - Yoga (from `../../../../node_modules/react-native/ReactCommon/yoga`) + +SPEC REPOS: + trunk: + - libavif + - libdav1d + - libwebp + - SDWebImage + - SDWebImageAVIFCoder + - SDWebImageSVGCoder + - SDWebImageWebPCoder + +EXTERNAL SOURCES: + EXConstants: + :path: "../../../../node_modules/expo-constants/ios" + EXJSONUtils: + :path: "../../../../node_modules/expo-json-utils/ios" + EXManifests: + :path: "../../../../node_modules/expo-manifests/ios" + Expo: + :path: "../../../../node_modules/expo" + expo-dev-client: + :path: "../../../../node_modules/expo-dev-client/ios" + expo-dev-launcher: + :path: "../../../../node_modules/expo-dev-launcher" + expo-dev-menu: + :path: "../../../../node_modules/expo-dev-menu" + expo-dev-menu-interface: + :path: "../../../../node_modules/expo-dev-menu-interface/ios" + ExpoAsset: + :path: "../../../../node_modules/expo-asset/ios" + ExpoBlur: + :path: "../../../../node_modules/expo-blur/ios" + ExpoCrypto: + :path: "../../../../node_modules/expo-crypto/ios" + ExpoFileSystem: + :path: "../../../../node_modules/expo-file-system/ios" + ExpoFont: + :path: "../../../../node_modules/expo-font/ios" + ExpoHaptics: + :path: "../../../../node_modules/expo-haptics/ios" + ExpoHead: + :path: "../../../../node_modules/expo-router/ios" + ExpoImage: + :path: "../../../../node_modules/expo-image/ios" + ExpoKeepAwake: + :path: "../../../../node_modules/expo-keep-awake/ios" + ExpoLinking: + :path: "../../../../node_modules/expo-linking/ios" + ExpoModulesCore: + :path: "../../../../node_modules/expo-modules-core" + ExpoSplashScreen: + :path: "../../../../node_modules/expo-splash-screen/ios" + ExpoSymbols: + :path: "../../../../node_modules/expo-symbols/ios" + ExpoSystemUI: + :path: "../../../../node_modules/expo-system-ui/ios" + EXUpdatesInterface: + :path: "../../../../node_modules/expo-updates-interface/ios" + FBLazyVector: + :path: "../../../../node_modules/react-native/Libraries/FBLazyVector" + hermes-engine: + :podspec: "../../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 + RCTDeprecation: + :path: "../../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + RCTRequired: + :path: "../../../../node_modules/react-native/Libraries/Required" + RCTTypeSafety: + :path: "../../../../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../../../../node_modules/react-native/" + React-callinvoker: + :path: "../../../../node_modules/react-native/ReactCommon/callinvoker" + React-Core: + :path: "../../../../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../../../../node_modules/react-native/React-Core-prebuilt.podspec" + React-CoreModules: + :path: "../../../../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../../../../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../../../../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../../../../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../../../../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../../../../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../../../../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../../../../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-jserrorhandler: + :path: "../../../../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../../../../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../../../../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../../../../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../../../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../../../../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../../../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../../../../node_modules/react-native/ReactCommon/jsitooling" + React-jsitracing: + :path: "../../../../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../../../../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../../../../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-platform: + :path: "../../../../packages/react-native-platform" + react-native-safe-area-context: + :path: "../../../../node_modules/react-native-safe-area-context" + react-native-webcrypto-bridge: + :path: "../../../../packages/react-native-webcrypto-bridge" + react-native-webview: + :path: "../../../../node_modules/react-native-webview" + React-NativeModulesApple: + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-oscompat: + :path: "../../../../node_modules/react-native/ReactCommon/oscompat" + React-perflogger: + :path: "../../../../node_modules/react-native/ReactCommon/reactperflogger" + React-performancetimeline: + :path: "../../../../node_modules/react-native/ReactCommon/react/performance/timeline" + React-RCTActionSheet: + :path: "../../../../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../../../../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../../../../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../../../../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../../../../node_modules/react-native/React" + React-RCTFBReactNativeSpec: + :path: "../../../../node_modules/react-native/React" + React-RCTImage: + :path: "../../../../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../../../../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../../../../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: "../../../../node_modules/react-native/React/Runtime" + React-RCTSettings: + :path: "../../../../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../../../../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../../../../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/css" + React-rendererdebug: + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/debug" + React-RuntimeApple: + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../../../../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-timing: + :path: "../../../../node_modules/react-native/ReactCommon/react/timing" + React-utils: + :path: "../../../../node_modules/react-native/ReactCommon/react/utils" + ReactAppDependencyProvider: + :path: build/generated/ios + ReactCodegen: + :path: build/generated/ios + ReactCommon: + :path: "../../../../node_modules/react-native/ReactCommon" + ReactNativeDependencies: + :podspec: "../../../../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" + RNGestureHandler: + :path: "../../../../node_modules/react-native-gesture-handler" + RNReanimated: + :path: "../../../../node_modules/react-native-reanimated" + RNScreens: + :path: "../../../../node_modules/react-native-screens" + RNWorklets: + :path: "../../../../node_modules/react-native-worklets" + Yoga: + :path: "../../../../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + EXConstants: fce59a631a06c4151602843667f7cfe35f81e271 + EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd + EXManifests: a8d97683e5c7a3b026ffbd58559c64dc655b747b + Expo: aadbcc8c6c14ff3105a9154f1683cb1424ff0d2a + expo-dev-client: 425ee077d6754a98cfe3a2e2410d29b440b24c9d + expo-dev-launcher: a4f4cdef064ab1fb8621e5b8c7c457cd6e9568c3 + expo-dev-menu: 05b18812110c175814c6af0d09dd658abcc5e00d + expo-dev-menu-interface: 600df12ea01efecdd822daaf13cc0ac091775533 + ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18 + ExpoBlur: b90747a3f22a8b6ceffd9cb0dc41a4184efdc656 + ExpoCrypto: b6105ebaa15d6b38a811e71e43b52cd934945322 + ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063 + ExpoFont: f543ce20a228dd702813668b1a07b46f51878d47 + ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536 + ExpoHead: 7e19b2272edf492262acf8afb391a295198a6b23 + ExpoImage: 686f972bff29525733aa13357f6691dc90aa03d8 + ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296 + ExpoLinking: 8f0aaf69aa56f832913030503b6263dc6f647f37 + ExpoModulesCore: f3da4f1ab5a8375d0beafab763739dbee8446583 + ExpoSplashScreen: bc3cffefca2716e5f22350ca109badd7e50ec14d + ExpoSymbols: 349ee2b4d7d5ff3ea8436467914f8a67635aa354 + ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc + EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734 + FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12 + hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 + libavif: 5f8e715bea24debec477006f21ef9e95432e254d + libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f + libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 + RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990 + RCTRequired: 8f3cfc90cc25cf6e420ddb3e7caaaabc57df6043 + RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c + React: 914f8695f9bf38e6418228c2ffb70021e559f92f + React-callinvoker: 1c0808402aee0c6d4a0d8e7220ce6547af9fba71 + React-Core: c61410ef0ca6055e204a963992e363227e0fd1c5 + React-Core-prebuilt: 02f0ad625ddd47463c009c2d0c5dd35c0d982599 + React-CoreModules: 1f6d1744b5f9f2ec684a4bb5ced25370f87e5382 + React-cxxreact: 3af79478e8187b63ffc22b794cd42d3fc1f1f2da + React-debug: 6328c2228e268846161f10082e80dc69eac2e90a + React-defaultsnativemodule: d635ef36d755321e5d6fc065bd166b2c5a0e9833 + React-domnativemodule: dd28f6d96cd21236e020be2eff6fe0b7d4ec3b66 + React-Fabric: 2e32c3fdbb1fbcf5fde54607e3abe453c6652ce2 + React-FabricComponents: 5ed0cdb81f6b91656cb4d3be432feaa28a58071a + React-FabricImage: 2bc714f818cb24e454f5d3961864373271b2faf8 + React-featureflags: 847642f41fa71ad4eec5e0351badebcad4fe6171 + React-featureflagsnativemodule: c868a544b2c626fa337bcbd364b1befe749f0d3f + React-graphics: 192ec701def5b3f2a07db2814dfba5a44986cff6 + React-hermes: e875778b496c86d07ab2ccaa36a9505d248a254b + React-idlecallbacksnativemodule: 4d57965cdf82c14ee3b337189836cd8491632b76 + React-ImageManager: bd0b99e370b13de82c9cd15f0f08144ff3de079e + React-jserrorhandler: a2fdef4cbcfdcdf3fa9f5d1f7190f7fd4535248d + React-jsi: 89d43d1e7d4d0663f8ba67e0b39eb4e4672c27de + React-jsiexecutor: abe4874aaab90dfee5dec480680220b2f8af07e3 + React-jsinspector: a0b3e051aef842b0b2be2353790ae2b2a5a65a8f + React-jsinspectorcdp: 6346013b2247c6263fbf5199adf4a8751e53bd89 + React-jsinspectornetwork: 26281aa50d49fc1ec93abf981d934698fa95714f + React-jsinspectortracing: 55eedf6d57540507570259a778663b90060bbd6e + React-jsitooling: 0e001113fa56d8498aa8ac28437ac0d36348e51a + React-jsitracing: b713793eb8a5bbc4d86a84e9d9e5023c0f58cbaf + React-logger: 50fdb9a8236da90c0b1072da5c32ee03aeb5bf28 + React-Mapbuffer: 9050ee10c19f4f7fca8963d0211b2854d624973e + React-microtasksnativemodule: f775db9e991c6f3b8ccbc02bfcde22770f96e23b + react-native-platform: cdb9af440cab4b65b7129e6336fdcf75fd483db3 + react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2 + react-native-webcrypto-bridge: 388532d5c2dbf359a12c14ae7412bd58e9e35e77 + react-native-webview: b29007f4723bca10872028067b07abacfa1cb35a + React-NativeModulesApple: 8969913947d5b576de4ed371a939455a8daf28aa + React-oscompat: ce47230ed20185e91de62d8c6d139ae61763d09c + React-perflogger: 02b010e665772c7dcb859d85d44c1bfc5ac7c0e4 + React-performancetimeline: 130db956b5a83aa4fb41ddf5ae68da89f3fb1526 + React-RCTActionSheet: 0b14875b3963e9124a5a29a45bd1b22df8803916 + React-RCTAnimation: a7b90fd2af7bb9c084428867445a1481a8cb112e + React-RCTAppDelegate: 3262bedd01263f140ec62b7989f4355f57cec016 + React-RCTBlob: c17531368702f1ebed5d0ada75a7cf5915072a53 + React-RCTFabric: 6409edd8cfdc3133b6cc75636d3b858fdb1d11ea + React-RCTFBReactNativeSpec: c004b27b4fa3bd85878ad2cf53de3bbec85da797 + React-RCTImage: c68078a120d0123f4f07a5ac77bea3bb10242f32 + React-RCTLinking: cf8f9391fe7fe471f96da3a5f0435235eca18c5b + React-RCTNetwork: ca31f7c879355760c2d9832a06ee35f517938a20 + React-RCTRuntime: a6cf4a1e42754fc87f493e538f2ac6b820e45418 + React-RCTSettings: e0e140b2ff4bf86d34e9637f6316848fc00be035 + React-RCTText: 75915bace6f7877c03a840cc7b6c622fb62bfa6b + React-RCTVibration: 25f26b85e5e432bb3c256f8b384f9269e9529f25 + React-rendererconsistency: 2dac03f448ff337235fd5820b10f81633328870d + React-renderercss: 477da167bb96b5ac86d30c5d295412fb853f5453 + React-rendererdebug: 2a1798c6f3ef5f22d466df24c33653edbabb5b89 + React-RuntimeApple: 28cf4d8eb18432f6a21abbed7d801ab7f6b6f0b4 + React-RuntimeCore: 41bf0fd56a00de5660f222415af49879fa49c4f0 + React-runtimeexecutor: 1afb774dde3011348e8334be69d2f57a359ea43e + React-RuntimeHermes: f3b158ea40e8212b1a723a68b4315e7a495c5fc6 + React-runtimescheduler: 3e1e2bec7300bae512533107d8e54c6e5c63fe0f + React-timing: 6fa9883de2e41791e5dc4ec404e5e37f3f50e801 + React-utils: 6e2035b53d087927768649a11a26c4e092448e34 + ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79 + ReactCodegen: faaff01770b53ca9f352bd2e8a1a67348300da1b + ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8 + ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a + RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3 + RNReanimated: b5e0a327e97b5815b881b998faf067e422699cc6 + RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845 + RNWorklets: 8fa92bb195a414b2526ec8e8f7b19e2276bba30e + SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf + SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 + SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c + SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 + Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a + +PODFILE CHECKSUM: d595f9b088be7340e78be66de7fa1626e7a905ad + +COCOAPODS: 1.16.2 diff --git a/e2e/apps/react-native-oidc/ios/Podfile.properties.json b/e2e/apps/react-native-oidc/ios/Podfile.properties.json new file mode 100644 index 00000000..dde7553a --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/Podfile.properties.json @@ -0,0 +1,7 @@ +{ + "expo.jsEngine": "hermes", + "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", + "newArchEnabled": "true", + "ios.forceStaticLinking": "[]", + "apple.privacyManifestAggregationEnabled": "true" +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj new file mode 100644 index 00000000..6592b57f --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj @@ -0,0 +1,548 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 01C95F7E5C6405AC339BA6A3 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B0490335CCA6D05E2E76475A /* PrivacyInfo.xcprivacy */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 28A8FCF7C3DD66AEE647C911 /* libPods-reporeactnativeoidc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F876E37084976B1C5221DB6E /* libPods-reporeactnativeoidc.a */; }; + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; + 59B56CB53FF13F1B0AEB3692 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF88F9EE8937BA82174B5EE /* ExpoModulesProvider.swift */; }; + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; + F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* reporeactnativeoidc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = reporeactnativeoidc.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = reporeactnativeoidc/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = reporeactnativeoidc/Info.plist; sourceTree = ""; }; + 342D5AA23C1ADEDC4068513B /* Pods-reporeactnativeoidc.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-reporeactnativeoidc.release.xcconfig"; path = "Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc.release.xcconfig"; sourceTree = ""; }; + 5CF88F9EE8937BA82174B5EE /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-reporeactnativeoidc/ExpoModulesProvider.swift"; sourceTree = ""; }; + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = reporeactnativeoidc/SplashScreen.storyboard; sourceTree = ""; }; + B0490335CCA6D05E2E76475A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = reporeactnativeoidc/PrivacyInfo.xcprivacy; sourceTree = ""; }; + BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; + E8105371E36AC8728791D1E1 /* Pods-reporeactnativeoidc.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-reporeactnativeoidc.debug.xcconfig"; path = "Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc.debug.xcconfig"; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = reporeactnativeoidc/AppDelegate.swift; sourceTree = ""; }; + F11748442D0722820044C1D9 /* reporeactnativeoidc-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "reporeactnativeoidc-Bridging-Header.h"; path = "reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h"; sourceTree = ""; }; + F876E37084976B1C5221DB6E /* libPods-reporeactnativeoidc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-reporeactnativeoidc.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 28A8FCF7C3DD66AEE647C911 /* libPods-reporeactnativeoidc.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* reporeactnativeoidc */ = { + isa = PBXGroup; + children = ( + F11748412D0307B40044C1D9 /* AppDelegate.swift */, + F11748442D0722820044C1D9 /* reporeactnativeoidc-Bridging-Header.h */, + BB2F792B24A3F905000567C9 /* Supporting */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, + B0490335CCA6D05E2E76475A /* PrivacyInfo.xcprivacy */, + ); + name = reporeactnativeoidc; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + F876E37084976B1C5221DB6E /* libPods-reporeactnativeoidc.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 49D9F00E47B53EB16F481FFF /* reporeactnativeoidc */ = { + isa = PBXGroup; + children = ( + 5CF88F9EE8937BA82174B5EE /* ExpoModulesProvider.swift */, + ); + name = reporeactnativeoidc; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* reporeactnativeoidc */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + AF2B5D37B5DD086690E213AB /* Pods */, + 87F70740B181AC390CFD90BF /* ExpoModulesProviders */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* reporeactnativeoidc.app */, + ); + name = Products; + sourceTree = ""; + }; + 87F70740B181AC390CFD90BF /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + 49D9F00E47B53EB16F481FFF /* reporeactnativeoidc */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; + AF2B5D37B5DD086690E213AB /* Pods */ = { + isa = PBXGroup; + children = ( + E8105371E36AC8728791D1E1 /* Pods-reporeactnativeoidc.debug.xcconfig */, + 342D5AA23C1ADEDC4068513B /* Pods-reporeactnativeoidc.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + BB2F792B24A3F905000567C9 /* Supporting */ = { + isa = PBXGroup; + children = ( + BB2F792C24A3F905000567C9 /* Expo.plist */, + ); + name = Supporting; + path = reporeactnativeoidc/Supporting; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* reporeactnativeoidc */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "reporeactnativeoidc" */; + buildPhases = ( + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, + CED9D22B4BE08C63AE6D8AF1 /* [Expo] Configure project */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, + E9CB239F796921D687166C24 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = reporeactnativeoidc; + productName = reporeactnativeoidc; + productReference = 13B07F961A680F5B00A75B9A /* reporeactnativeoidc.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "reporeactnativeoidc" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* reporeactnativeoidc */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, + 01C95F7E5C6405AC339BA6A3 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env", + "$(SRCROOT)/.xcode.env.local", + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; + }; + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-reporeactnativeoidc-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + CED9D22B4BE08C63AE6D8AF1 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env", + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/reporeactnativeoidc/reporeactnativeoidc.entitlements", + "$(SRCROOT)/Pods/Target Support Files/Pods-reporeactnativeoidc/expo-configure-project.sh", + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Pods/Target Support Files/Pods-reporeactnativeoidc/ExpoModulesProvider.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-reporeactnativeoidc/expo-configure-project.sh\"\n"; + }; + E9CB239F796921D687166C24 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */, + 59B56CB53FF13F1B0AEB3692 /* ExpoModulesProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E8105371E36AC8728791D1E1 /* Pods-reporeactnativeoidc.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = reporeactnativeoidc/reporeactnativeoidc.entitlements; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "FB_SONARKIT_ENABLED=1", + ); + INFOPLIST_FILE = reporeactnativeoidc/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.reporeactnativeoidc; + PRODUCT_NAME = reporeactnativeoidc; + SWIFT_OBJC_BRIDGING_HEADER = "reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 342D5AA23C1ADEDC4068513B /* Pods-reporeactnativeoidc.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = reporeactnativeoidc/reporeactnativeoidc.entitlements; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = reporeactnativeoidc/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.reporeactnativeoidc; + PRODUCT_NAME = reporeactnativeoidc; + SWIFT_OBJC_BRIDGING_HEADER = "reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = NO; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "reporeactnativeoidc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "reporeactnativeoidc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme new file mode 100644 index 00000000..6635c45c --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcworkspace/contents.xcworkspacedata b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..998503cf --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/AppDelegate.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/AppDelegate.swift new file mode 100644 index 00000000..a7887e1e --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/AppDelegate.swift @@ -0,0 +1,70 @@ +import Expo +import React +import ReactAppDependencyProvider + +@UIApplicationMain +public class AppDelegate: ExpoAppDelegate { + var window: UIWindow? + + var reactNativeDelegate: ExpoReactNativeFactoryDelegate? + var reactNativeFactory: RCTReactNativeFactory? + + public override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + let delegate = ReactNativeDelegate() + let factory = ExpoReactNativeFactory(delegate: delegate) + delegate.dependencyProvider = RCTAppDependencyProvider() + + reactNativeDelegate = delegate + reactNativeFactory = factory + bindReactNativeFactory(factory) + +#if os(iOS) || os(tvOS) + window = UIWindow(frame: UIScreen.main.bounds) + factory.startReactNative( + withModuleName: "main", + in: window, + launchOptions: launchOptions) +#endif + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + // Linking API + public override func application( + _ app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options) + } + + // Universal Links + public override func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { + let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) + return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result + } +} + +class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { + // Extension point for config-plugins + + override func sourceURL(for bridge: RCTBridge) -> URL? { + // needed to return the correct URL for expo-dev-client. + bridge.bundleURL ?? bundleURL() + } + + override func bundleURL() -> URL? { +#if DEBUG + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry") +#else + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") +#endif + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..ac881f6063fdf449befe03c655b937ae58472728 GIT binary patch literal 5856 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsOza!(h>kP5~(2OD`A6d0Hk zKL4M;+FGXIoj6cQLKHKQJd+J18Y+2#q``VdAocxjVc}u zjnT9*no~xLg3*EzSRsrS1*1j5Xi+d)6pR)Hzz`TM3Py{9(V}3qC>SjYM#l<9M@~k^ zkp}yy+P-fO8bE`Ei~(QjOYEIMgMS6!!M}Il!N0wr!M`2g;jZtX4E+E+_;&|9Qdm8z gcr-Mqoi=zM#d80VU4Lc)FtIUsy85}Sb4q9e06!I^tN;K2 literal 0 HcmV?d00001 diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/Contents.json b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..90d8d4c2 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "filename": "App-Icon-1024x1024@1x.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/Contents.json b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/Contents.json new file mode 100644 index 00000000..ed285c2e --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "expo" + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/SplashScreenBackground.colorset/Contents.json b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/SplashScreenBackground.colorset/Contents.json new file mode 100644 index 00000000..15f02abe --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Images.xcassets/SplashScreenBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors": [ + { + "color": { + "components": { + "alpha": "1.000", + "blue": "1.00000000000000", + "green": "1.00000000000000", + "red": "1.00000000000000" + }, + "color-space": "srgb" + }, + "idiom": "universal" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Info.plist b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Info.plist new file mode 100644 index 00000000..bcdd5844 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Info.plist @@ -0,0 +1,82 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + @repo/react-native-oidc + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + com.oktapreview.jperreault-test + com.anonymous.reporeactnativeoidc + + + + CFBundleURLSchemes + + exp+reporeact-native-oidc + + + + CFBundleVersion + 1 + LSMinimumSystemVersion + 12.0 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + NSUserActivityTypes + + $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route + + RCTNewArchEnabled + + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + arm64 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + + + \ No newline at end of file diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/PrivacyInfo.xcprivacy b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..5bb83c5d --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/PrivacyInfo.xcprivacy @@ -0,0 +1,48 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + 0A2A.1 + 3B52.1 + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + 85F4.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/SplashScreen.storyboard b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/SplashScreen.storyboard new file mode 100644 index 00000000..6c99b2a9 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/SplashScreen.storyboard @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Supporting/Expo.plist b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Supporting/Expo.plist new file mode 100644 index 00000000..750be020 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/Supporting/Expo.plist @@ -0,0 +1,12 @@ + + + + + EXUpdatesCheckOnLaunch + ALWAYS + EXUpdatesEnabled + + EXUpdatesLaunchWaitMs + 0 + + \ No newline at end of file diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h new file mode 100644 index 00000000..8361941a --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc-Bridging-Header.h @@ -0,0 +1,3 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc.entitlements b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc.entitlements new file mode 100644 index 00000000..f683276c --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc/reporeactnativeoidc.entitlements @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 41e8e767b226c89f6d6a13d14bcec24e0246a38e Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Thu, 2 Jul 2026 17:33:47 -0400 Subject: [PATCH 3/7] progress --- e2e/apps/react-native-oidc/ios/Podfile.lock | 12 +- .../project.pbxproj | 160 ++++++++++- .../xcschemes/reporeactnativeoidc.xcscheme | 11 + .../Helpers/OAuthHelper.swift | 214 ++++++++++++++ .../Helpers/TestHelpers.swift | 255 +++++++++++++++++ .../Helpers/XCTestHelpers.swift | 98 +++++++ .../CredentialsScreenPageObject.swift | 118 ++++++++ .../PageObjects/LoginScreenPageObject.swift | 151 ++++++++++ .../PageObjects/TokenScreenPageObject.swift | 128 +++++++++ .../ReactNativeOIDCAppUITests.swift | 266 ++++++++++++++++++ .../BrowserSessionBridge.m | 1 + 11 files changed, 1405 insertions(+), 9 deletions(-) create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/XCTestHelpers.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift create mode 100644 e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift diff --git a/e2e/apps/react-native-oidc/ios/Podfile.lock b/e2e/apps/react-native-oidc/ios/Podfile.lock index ac3b0581..2a744388 100644 --- a/e2e/apps/react-native-oidc/ios/Podfile.lock +++ b/e2e/apps/react-native-oidc/ios/Podfile.lock @@ -2165,7 +2165,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNWorklets (0.5.1): + - RNWorklets (0.5.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2187,9 +2187,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNWorklets/worklets (= 0.5.1) + - RNWorklets/worklets (= 0.5.2) - Yoga - - RNWorklets/worklets (0.5.1): + - RNWorklets/worklets (0.5.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2211,9 +2211,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNWorklets/worklets/apple (= 0.5.1) + - RNWorklets/worklets/apple (= 0.5.2) - Yoga - - RNWorklets/worklets/apple (0.5.1): + - RNWorklets/worklets/apple (0.5.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2659,7 +2659,7 @@ SPEC CHECKSUMS: RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3 RNReanimated: b5e0a327e97b5815b881b998faf067e422699cc6 RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845 - RNWorklets: 8fa92bb195a414b2526ec8e8f7b19e2276bba30e + RNWorklets: e203d41d1c9e0f3402105d7ea17707366c1fc06a SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj index 6592b57f..e822a838 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -16,6 +16,16 @@ F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + E55BC1902FF45F6F009F0154 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = reporeactnativeoidc; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 13B07F961A680F5B00A75B9A /* reporeactnativeoidc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = reporeactnativeoidc.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = reporeactnativeoidc/Images.xcassets; sourceTree = ""; }; @@ -23,8 +33,9 @@ 342D5AA23C1ADEDC4068513B /* Pods-reporeactnativeoidc.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-reporeactnativeoidc.release.xcconfig"; path = "Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc.release.xcconfig"; sourceTree = ""; }; 5CF88F9EE8937BA82174B5EE /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-reporeactnativeoidc/ExpoModulesProvider.swift"; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = reporeactnativeoidc/SplashScreen.storyboard; sourceTree = ""; }; - B0490335CCA6D05E2E76475A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = reporeactnativeoidc/PrivacyInfo.xcprivacy; sourceTree = ""; }; + B0490335CCA6D05E2E76475A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = reporeactnativeoidc/PrivacyInfo.xcprivacy; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; + E55BC18A2FF45F6F009F0154 /* reporeactnativeoidcUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = reporeactnativeoidcUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E8105371E36AC8728791D1E1 /* Pods-reporeactnativeoidc.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-reporeactnativeoidc.debug.xcconfig"; path = "Target Support Files/Pods-reporeactnativeoidc/Pods-reporeactnativeoidc.debug.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = reporeactnativeoidc/AppDelegate.swift; sourceTree = ""; }; @@ -32,6 +43,10 @@ F876E37084976B1C5221DB6E /* libPods-reporeactnativeoidc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-reporeactnativeoidc.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + E55BC18B2FF45F6F009F0154 /* reporeactnativeoidcUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = reporeactnativeoidcUITests; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -41,6 +56,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E55BC1872FF45F6F009F0154 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -87,6 +109,7 @@ children = ( 13B07FAE1A68108700A75B9A /* reporeactnativeoidc */, 832341AE1AAA6A7D00B99B32 /* Libraries */, + E55BC18B2FF45F6F009F0154 /* reporeactnativeoidcUITests */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, AF2B5D37B5DD086690E213AB /* Pods */, @@ -101,6 +124,7 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* reporeactnativeoidc.app */, + E55BC18A2FF45F6F009F0154 /* reporeactnativeoidcUITests.xctest */, ); name = Products; sourceTree = ""; @@ -119,7 +143,6 @@ E8105371E36AC8728791D1E1 /* Pods-reporeactnativeoidc.debug.xcconfig */, 342D5AA23C1ADEDC4068513B /* Pods-reporeactnativeoidc.release.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -157,17 +180,43 @@ productReference = 13B07F961A680F5B00A75B9A /* reporeactnativeoidc.app */; productType = "com.apple.product-type.application"; }; + E55BC1892FF45F6F009F0154 /* reporeactnativeoidcUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E55BC1942FF45F6F009F0154 /* Build configuration list for PBXNativeTarget "reporeactnativeoidcUITests" */; + buildPhases = ( + E55BC1862FF45F6F009F0154 /* Sources */, + E55BC1872FF45F6F009F0154 /* Frameworks */, + E55BC1882FF45F6F009F0154 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + E55BC1912FF45F6F009F0154 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + E55BC18B2FF45F6F009F0154 /* reporeactnativeoidcUITests */, + ); + name = reporeactnativeoidcUITests; + productName = reporeactnativeoidcUITests; + productReference = E55BC18A2FF45F6F009F0154 /* reporeactnativeoidcUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 2600; LastUpgradeCheck = 1130; TargetAttributes = { 13B07F861A680F5B00A75B9A = { LastSwiftMigration = 1250; }; + E55BC1892FF45F6F009F0154 = { + CreatedOnToolsVersion = 26.0.1; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "reporeactnativeoidc" */; @@ -184,6 +233,7 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* reporeactnativeoidc */, + E55BC1892FF45F6F009F0154 /* reporeactnativeoidcUITests */, ); }; /* End PBXProject section */ @@ -200,6 +250,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E55BC1882FF45F6F009F0154 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -334,8 +391,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E55BC1862FF45F6F009F0154 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + E55BC1912FF45F6F009F0154 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* reporeactnativeoidc */; + targetProxy = E55BC1902FF45F6F009F0154 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; @@ -521,6 +593,79 @@ }; name = Release; }; + E55BC1922FF45F6F009F0154 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = Okta.reporeactnativeoidcUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = reporeactnativeoidc; + }; + name = Debug; + }; + E55BC1932FF45F6F009F0154 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = Okta.reporeactnativeoidcUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = reporeactnativeoidc; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -542,6 +687,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + E55BC1942FF45F6F009F0154 /* Build configuration list for PBXNativeTarget "reporeactnativeoidcUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E55BC1922FF45F6F009F0154 /* Debug */, + E55BC1932FF45F6F009F0154 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme index 6635c45c..952c5e62 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidc.xcodeproj/xcshareddata/xcschemes/reporeactnativeoidc.xcscheme @@ -38,6 +38,17 @@ ReferencedContainer = "container:reporeactnativeoidc.xcodeproj"> + + + + Bool { + let deadline = Date().addingTimeInterval(timeout) + var attempts = 0 + let maxAttempts = Int(timeout * 10) + + while Date() < deadline && attempts < maxAttempts { + // Check if any text field is visible that might be from the OAuth provider + // This is a heuristic since ASWebAuthenticationSession webview is restricted + let webviewElements = app.webViews + if webviewElements.element.exists { + return true + } + + // Also check for any text containing "Sign In" or "Login" which might come from OAuth provider + if app.staticTexts["Sign In"].exists || app.webViews.element.exists { + return true + } + + Thread.sleep(forTimeInterval: 0.1) + attempts += 1 + } + + return app.webViews.element.exists + } + + /// Attempt to enter OAuth credentials in the ASWebAuthenticationSession + /// LIMITED FUNCTIONALITY: XCUITest has restricted access to ASWebAuthenticationSession webview. + /// This method attempts to interact with form elements if they are accessible. + /// - parameter username: Username to enter + /// - parameter password: Password to enter + /// - throws: OAuthError if interaction fails + func enterOAuthCredentials(username: String, password: String) throws { + // Wait for webview to load + guard waitForOAuthUI(timeout: 8) else { + throw OAuthError.webViewNotAccessible + } + + let webView = app.webViews.element + + // Attempt to find and fill username field + // Note: Safari/system webviews may expose form elements through the accessibility tree + let usernameField = webView.textFields.element(boundBy: 0) + if usernameField.exists { + usernameField.tap() + Thread.sleep(forTimeInterval: 0.2) + usernameField.typeText(username) + Thread.sleep(forTimeInterval: 0.3) + } else { + // If direct field access fails, attempt keyboard input + // This assumes the field is already focused + let remoteDismiss = app.keys["Delete"] + if remoteDismiss.exists { + // Attempt to clear any existing text + for _ in 0..<20 { + remoteDismiss.press() + } + } + app.typeText(username) + } + + // Move to password field and enter password + app.typeText("\t") // Tab to next field + Thread.sleep(forTimeInterval: 0.3) + + let passwordField = webView.secureTextFields.element(boundBy: 0) + if passwordField.exists { + passwordField.tap() + Thread.sleep(forTimeInterval: 0.2) + passwordField.typeText(password) + Thread.sleep(forTimeInterval: 0.3) + } else { + app.typeText(password) + } + + // Attempt to submit form + let submitButton = webView.buttons["Sign In"] + if submitButton.exists { + submitButton.tap() + } else { + // Try pressing Enter as fallback + app.typeText("\n") + } + + Thread.sleep(forTimeInterval: 0.5) + } + + /// Wait for OAuth flow to complete and app to return to foreground + /// Waits for the app's authentication status to update after OAuth callback + /// - parameter timeout: Maximum time to wait for OAuth completion in seconds + func waitForOAuthCompletion(timeout: TimeInterval = 10) throws { + let deadline = Date().addingTimeInterval(timeout) + let authStatusProperty = "label" + + var authStatusFound = false + while Date() < deadline { + // Check if app is back in foreground (no longer showing OAuth sheet) + let webviewGone = !app.webViews.element.exists + let appInForeground = app.staticTexts["Authentication"].exists || app.buttons["requestTokenButton"].exists + + if webviewGone && appInForeground { + authStatusFound = true + break + } + + Thread.sleep(forTimeInterval: 0.2) + } + + guard authStatusFound else { + throw OAuthError.completionTimeout + } + + // Brief additional wait for app state to settle + Thread.sleep(forTimeInterval: 1) + } + + /// Dismiss the OAuth sheet (simulating user cancellation) + /// This attempts to dismiss ASWebAuthenticationSession by tapping close or canceling + func dismissOAuthSheet() throws { + // Attempt to find and tap system close button (usually in top-left of OAuth sheet) + let closeButton = app.buttons["Close"] + if closeButton.exists { + closeButton.tap() + Thread.sleep(forTimeInterval: 0.5) + return + } + + // Fallback: try tapping cancel button + let cancelButton = app.buttons["Cancel"] + if cancelButton.exists { + cancelButton.tap() + Thread.sleep(forTimeInterval: 0.5) + return + } + + // If no close button found, try ESC key + app.typeText("\u{1B}") // ESC key + Thread.sleep(forTimeInterval: 0.5) + + // Verify dismiss was successful (OAuth sheet should be gone) + let webviewGone = !app.webViews.element.exists + XCTAssertTrue(webviewGone, "OAuth sheet should be dismissed") + } + + /// Wait for app to transition from authenticated to not authenticated state + /// Used when testing logout/revocation flows + /// - parameter timeout: Maximum time to wait in seconds + func waitForUnauthenticatedState(timeout: TimeInterval = 5) throws { + let deadline = Date().addingTimeInterval(timeout) + + while Date() < deadline { + let notAuthElement = app.staticTexts.element(containingText: "โŒ Not Authenticated") + if notAuthElement.exists { + return + } + Thread.sleep(forTimeInterval: 0.2) + } + + throw OAuthError.stateTransitionTimeout + } + + /// Wait for app to transition to authenticated state + /// - parameter timeout: Maximum time to wait in seconds + func waitForAuthenticatedState(timeout: TimeInterval = 5) throws { + let deadline = Date().addingTimeInterval(timeout) + + while Date() < deadline { + let authElement = app.staticTexts.element(containingText: "โœ… Authenticated") + if authElement.exists { + return + } + Thread.sleep(forTimeInterval: 0.2) + } + + throw OAuthError.stateTransitionTimeout + } +} + +// MARK: - Error Types + +enum OAuthError: Error, CustomStringConvertible { + case webViewNotAccessible + case completionTimeout + case stateTransitionTimeout + case dismissalFailed + + var description: String { + switch self { + case .webViewNotAccessible: + return "ASWebAuthenticationSession webview is not accessible to XCUITest" + case .completionTimeout: + return "OAuth flow did not complete within timeout period" + case .stateTransitionTimeout: + return "App authentication state did not transition as expected" + case .dismissalFailed: + return "Failed to dismiss OAuth sheet" + } + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift new file mode 100644 index 00000000..ca88e038 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift @@ -0,0 +1,255 @@ +import XCTest + +/// Shared test utilities for React Native OIDC e2e tests +class TestHelpers { + let app: XCUIApplication + + init(app: XCUIApplication) { + self.app = app + } + + // MARK: - Setup & Teardown + + /// Load OAuth credentials from testenv file or environment variables + /// Reads from testenv file (like Android build.gradle) if it exists, otherwise falls back to env vars + /// - returns: Tuple of (username, password) + /// - throws: XCTestError if credentials not found + static func loadOAuthCredentials() throws -> (username: String, password: String) { + var username: String? + var password: String? + + // First, try to read from testenv file (mirrors Android build.gradle approach) + if let testenvCredentials = try? loadCredentialsFromTestenv() { + return testenvCredentials + } + + // Fallback to environment variables + username = ProcessInfo.processInfo.environment["USERNAME"] + password = ProcessInfo.processInfo.environment["PASSWORD"] + + guard let username = username, !username.isEmpty else { + throw NSError(domain: "TestHelpers", code: 1, + userInfo: [NSLocalizedDescriptionKey: "USERNAME not found in testenv file or environment variable"]) + } + guard let password = password, !password.isEmpty else { + throw NSError(domain: "TestHelpers", code: 2, + userInfo: [NSLocalizedDescriptionKey: "PASSWORD not found in testenv file or environment variable"]) + } + + return (username, password) + } + + /// Load credentials from testenv file + /// Searches for testenv file at workspace root and parses USERNAME and PASSWORD + /// - returns: Tuple of (username, password) if found + /// - throws: Error if file not found or credentials missing + private static func loadCredentialsFromTestenv() throws -> (username: String, password: String) { + // Find testenv file relative to this source file location + // Source is at: e2e/apps/react-native-oidc/ios/E2e/Helpers/TestHelpers.swift + // Need to go up to workspace root + let sourcePath = #filePath // Current file path + let fileManager = FileManager.default + + // Walk up the directory tree to find testenv + var currentPath = (sourcePath as NSString).deletingLastPathComponent + var attempts = 0 + let maxAttempts = 10 // Prevent infinite loops + + while attempts < maxAttempts { + let potentialTestenvPath = (currentPath as NSString).appendingPathComponent("testenv") + + if fileManager.fileExists(atPath: potentialTestenvPath) { + return try parseTestenvFile(at: potentialTestenvPath) + } + + let parentPath = (currentPath as NSString).deletingLastPathComponent + if parentPath == currentPath { + // Reached root directory + break + } + + currentPath = parentPath + attempts += 1 + } + + throw NSError(domain: "TestEnv", code: 1, + userInfo: [NSLocalizedDescriptionKey: "testenv file not found"]) + } + + /// Parse testenv file and extract USERNAME and PASSWORD + /// - parameter path: Path to testenv file + /// - returns: Tuple of (username, password) + /// - throws: Error if credentials not found + private static func parseTestenvFile(at path: String) throws -> (username: String, password: String) { + let content = try String(contentsOfFile: path, encoding: .utf8) + var username: String? + var password: String? + + let lines = content.components(separatedBy: .newlines) + + for line in lines { + let trimmed = line.trimmingCharacters(in: .whitespaces) + + // Skip empty lines and comments + if trimmed.isEmpty || trimmed.starts(with: "#") { + continue + } + + // Parse KEY=VALUE format + let components = trimmed.components(separatedBy: "=") + guard components.count == 2 else { continue } + + let key = components[0].trimmingCharacters(in: .whitespaces) + var value = components[1].trimmingCharacters(in: .whitespaces) + + // Remove surrounding quotes if present + if value.starts(with: "\"") && value.hasSuffix("\"") { + value = String(value.dropFirst().dropLast()) + } + + if key == "USERNAME" { + username = value + } else if key == "PASSWORD" { + password = value + } + } + + guard let username = username, !username.isEmpty else { + throw NSError(domain: "TestEnv", code: 2, + userInfo: [NSLocalizedDescriptionKey: "USERNAME not found in testenv file"]) + } + guard let password = password, !password.isEmpty else { + throw NSError(domain: "TestEnv", code: 3, + userInfo: [NSLocalizedDescriptionKey: "PASSWORD not found in testenv file"]) + } + + print("๐Ÿ“‹ Loaded credentials from testenv file") + return (username, password) + } + + // MARK: - App State Assertions + + /// Verify app launched successfully + func verifyAppLaunched(timeout: TimeInterval = 10) throws { + let authTab = app.buttons["loginTab"] + let exists = authTab.waitForExistence(timeout: timeout) + XCTAssertTrue(exists, "App should launch and show authentication tab") + } + + /// Verify fresh app state (not authenticated, no credentials) + func assertFreshAppState() throws { + try verifyAuthenticationStatus(expected: false) + try verifyCredentialsCount(expected: 0) + } + + /// Verify authentication status: either "โœ… Authenticated" or "โŒ Not Authenticated" + /// - parameter expected: true for authenticated, false for not authenticated + func verifyAuthenticationStatus(expected: Bool) throws { + let expectedText = expected ? "โœ… Authenticated" : "โŒ Not Authenticated" + let statusElement = app.staticTexts.element(containingText: expectedText) + + XCTestWait.waitForElement( + statusElement, + timeout: 5, + message: "Should show \(expectedText)" + ) + XCTAssertTrue( + statusElement.exists, + "Expected authentication status: \(expectedText)" + ) + } + + /// Verify number of stored credentials + /// - parameter expected: Expected credential count + func verifyCredentialsCount(expected: Int) throws { + let countText = "\(expected) credential\(expected == 1 ? "" : "s") stored" + let countElement = app.staticTexts.element(containingText: countText) + + // Navigate to Credentials tab first + try navigateToTab(name: "Creds") + + XCTestWait.waitForElement( + countElement, + timeout: 5, + message: "Should show \(countText)" + ) + XCTAssertTrue( + countElement.exists, + "Expected to see: \(countText)" + ) + } + + // MARK: - App Navigation + + /// Navigate to a specific tab in the app + /// - parameter name: Tab name ("Login", "Creds", or "Token") + func navigateToTab(name: String) throws { + let tabConfigs: [String: (contentDesc: String, title: String)] = [ + "Login": (contentDesc: "loginTab", title: "Authentication"), + "Creds": (contentDesc: "credentialsTab", title: "Credentials"), + "Token": (contentDesc: "tokenTab", title: "Token Details") + ] + + guard let config = tabConfigs[name] else { + throw NSError(domain: "TestHelpers", code: 3, + userInfo: [NSLocalizedDescriptionKey: "Unknown tab: \(name)"]) + } + + let tabButton = app.buttons[config.contentDesc] + XCTAssertTrue( + tabButton.exists, + "Tab button for \(name) should exist" + ) + tabButton.tapSafely() + + // Wait for tab content to load + let titleElement = app.staticTexts.element(containingText: config.title) + XCTestWait.waitForElement( + titleElement, + timeout: 3, + message: "Should navigate to \(name) tab" + ) + } + + // MARK: - Action Helpers + + /// Tap the "Request Token" button to start OAuth flow + func tapRequestToken() throws { + let button = app.buttons["requestTokenButton"] + XCTAssertTrue(button.exists, "Request Token button should exist") + button.tapSafely() + } + + /// Tap the "Sign Out" button + func tapSignOut() throws { + let button = app.buttons["signOutButton"] + XCTAssertTrue(button.exists, "Sign Out button should exist") + button.tapSafely() + } + + /// Tap the "Clear" button + func tapClear() throws { + let button = app.buttons["clearButton"] + XCTAssertTrue(button.exists, "Clear button should exist") + button.tapSafely() + } + + /// Scroll to element if needed (useful for buttons at bottom of screen) + func scrollToElement(_ element: XCUIElement, swipeCount: Int = 3) { + for _ in 0.. Bool { + return self.waitForExistence(timeout: timeout) + } + + /// Check if element is displayed (exists and is hittable) + var isDisplayed: Bool { + return self.exists && self.isHittable + } + + /// Tap element with a small delay to ensure responsiveness + func tapSafely() { + if self.isDisplayed { + self.tap() + Thread.sleep(forTimeInterval: 0.3) + } + } + + /// Get element text value + var textValue: String { + return (self.value as? String) ?? "" + } +} + +// MARK: - XCUIApplication Extensions + +extension XCUIApplication { + /// Move app to background and foreground to simulate user switching apps + func toggleBackgroundAndForeground(duration: TimeInterval = 1.0) { + XCUIDevice.shared.press(.home) + Thread.sleep(forTimeInterval: duration) + self.activate() + Thread.sleep(forTimeInterval: 0.5) + } +} + +// MARK: - XCUIElementQuery Extensions + +extension XCUIElementQuery { + /// Find element containing exact text + /// - parameter text: The exact text to search for + /// - returns: First matching element + func element(withExactText text: String) -> XCUIElement { + return self.element(matching: NSPredicate(format: "label == %@", text)) + } + + /// Find element containing partial text + /// - parameter text: Partial text to search for + /// - returns: First matching element + func element(containingText text: String) -> XCUIElement { + return self.element(matching: NSPredicate(format: "label CONTAINS %@", text)) + } +} + +// MARK: - Wait Utilities + +class XCTestWait { + /// Wait for condition to be true with timeout + /// - parameter timeout: Maximum time to wait in seconds + /// - parameter condition: Closure that returns true when condition is met + static func waitFor(timeout: TimeInterval = 5, condition: @escaping () -> Bool) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + var attempt = 0 + let maxAttempts = Int(timeout * 10) + + while Date() < deadline && attempt < maxAttempts { + if condition() { + return true + } + Thread.sleep(forTimeInterval: 0.1) + attempt += 1 + } + + return condition() + } + + /// Wait for element to exist + /// - parameter element: Element to wait for + /// - parameter timeout: Maximum time to wait in seconds + static func waitForElement( + _ element: XCUIElement, + timeout: TimeInterval = 5, + message: String? = nil + ) -> Bool { + let result = element.waitForExistence(timeout: timeout) + if !result, let msg = message { + XCTFail("\(msg) - Element did not exist after \(timeout)s") + } + return result + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift new file mode 100644 index 00000000..03c004c1 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift @@ -0,0 +1,118 @@ +import XCTest + +/// Page Object for the Credentials tab +class CredentialsScreenPageObject { + let app: XCUIApplication + + init(app: XCUIApplication) { + self.app = app + } + + // MARK: - Elements + + var credentialsTabButton: XCUIElement { + return app.buttons["credentialsTab"] + } + + var credentialCountText: XCUIElement { + // Looks for text like "2 credentials stored" + return app.staticTexts.element(containingText: "stored") + } + + var defaultBadge: XCUIElement { + return app.staticTexts["DEFAULT"] + } + + var credentialsTableView: XCUIElement { + return app.tables.element + } + + // MARK: - Navigation + + /// Navigate to Credentials tab + func navigateToTab() { + XCTAssertTrue( + credentialsTabButton.exists, + "Credentials tab button should exist" + ) + credentialsTabButton.tapSafely() + + // Wait for tab to load + Thread.sleep(forTimeInterval: 0.5) + + // Verify tab title appears + let tabTitle = app.staticTexts["Credentials"] + XCTAssertTrue( + tabTitle.waitForExistence(timeout: 3), + "Credentials tab should load" + ) + } + + // MARK: - State Verification + + /// Get the current credential count from displayed text + /// - returns: Integer count of credentials, or nil if not readable + func getCredentialCount() -> Int? { + let countElement = credentialCountText + if !countElement.exists { + return nil + } + + let text = countElement.label + // Extract number from text like "2 credentials stored" + let components = text.components(separatedBy: " ") + if let firstComponent = components.first, let count = Int(firstComponent) { + return count + } + return nil + } + + /// Verify credential count matches expected value + /// - parameter expected: Expected number of credentials + /// - returns: True if count matches, false otherwise + func verifyCredentialCount(expected: Int) -> Bool { + guard let count = getCredentialCount() else { + return false + } + return count == expected + } + + /// Wait for credential count to match expected value + /// - parameter expected: Expected number of credentials + /// - parameter timeout: Maximum time to wait in seconds + /// - returns: True if count matches within timeout, false otherwise + func waitForCredentialCount(expected: Int, timeout: TimeInterval = 5) -> Bool { + return XCTestWait.waitFor(timeout: timeout) { + return self.verifyCredentialCount(expected: expected) + } + } + + /// Check if DEFAULT badge is visible + /// - returns: True if DEFAULT badge exists and is displayed, false otherwise + func isDefaultBadgeVisible() -> Bool { + return defaultBadge.exists && defaultBadge.isDisplayed + } + + /// Wait for DEFAULT badge to become visible + /// - parameter timeout: Maximum time to wait in seconds + /// - returns: True if badge becomes visible, false on timeout + func waitForDefaultBadge(timeout: TimeInterval = 3) -> Bool { + return XCTestWait.waitFor(timeout: timeout) { + return self.isDefaultBadgeVisible() + } + } + + /// Wait for DEFAULT badge to disappear + /// - parameter timeout: Maximum time to wait in seconds + /// - returns: True if badge disappears, false on timeout + func waitForDefaultBadgeToDisappear(timeout: TimeInterval = 3) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if !isDefaultBadgeVisible() { + return true + } + Thread.sleep(forTimeInterval: 0.1) + } + return !isDefaultBadgeVisible() + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift new file mode 100644 index 00000000..174ae8d3 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift @@ -0,0 +1,151 @@ +import XCTest + +/// Page Object for the Login/Authentication tab +class LoginScreenPageObject { + let app: XCUIApplication + + init(app: XCUIApplication) { + self.app = app + } + + // MARK: - Elements + + var authStatusElement: XCUIElement { + return app.staticTexts.element(containingText: "Authenticated") + } + + var requestTokenButton: XCUIElement { + return app.buttons["requestTokenButton"] + } + + var signOutButton: XCUIElement { + return app.buttons["signOutButton"] + } + + var clearButton: XCUIElement { + return app.buttons["clearButton"] + } + + var loginTabButton: XCUIElement { + return app.buttons["loginTab"] + } + + // MARK: - State Verification + + /// Verify current authentication status + /// - parameter expected: true for authenticated, false for not authenticated + func verifyAuthStatus(expected: Bool) -> Bool { + let expectedText = expected ? "โœ… Authenticated" : "โŒ Not Authenticated" + let element = app.staticTexts.element(containingText: expectedText) + return element.exists && element.isDisplayed + } + + /// Wait for authentication status to match expected value + /// - parameter expected: true for authenticated, false for not authenticated + /// - parameter timeout: Maximum time to wait in seconds + /// - returns: True if status matches, false on timeout + func waitForAuthStatus(expected: Bool, timeout: TimeInterval = 5) -> Bool { + return XCTestWait.waitFor(timeout: timeout) { + return self.verifyAuthStatus(expected: expected) + } + } + + // MARK: - Actions + + /// Tap "Request Token" button to initiate OAuth flow + func tapRequestToken() { + XCTAssertTrue( + requestTokenButton.exists, + "Request Token button should exist" + ) + requestTokenButton.tapSafely() + } + + /// Tap "Sign Out" button to revoke token and logout + func tapSignOut() { + XCTAssertTrue( + signOutButton.exists, + "Sign Out button should exist" + ) + signOutButton.tapSafely() + } + + /// Tap "Clear" button to clear stored credentials + func tapClear() { + XCTAssertTrue( + clearButton.exists, + "Clear button should exist" + ) + clearButton.tapSafely() + } + + /// Ensure we're on the Login tab + func navigateToTab() { + XCTAssertTrue( + loginTabButton.exists, + "Login tab button should exist" + ) + if !loginTabButton.isDisplayed { + loginTabButton.tap() + Thread.sleep(forTimeInterval: 0.3) + } + } + + /// Perform complete login flow from request to completion + /// - parameter oauthHelper: Helper for OAuth interaction + /// - parameter credentials: (username, password) tuple + func performLogin( + oauthHelper: OAuthHelper, + credentials: (username: String, password: String) + ) throws { + // Start OAuth flow + tapRequestToken() + + // Wait for OAuth UI to appear + let oAuthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) + XCTAssertTrue(oAuthUIAppeared, "OAuth UI should appear") + + // Enter credentials and authorize + try oauthHelper.enterOAuthCredentials( + username: credentials.username, + password: credentials.password + ) + + // Wait for OAuth to complete and app to return + try oauthHelper.waitForOAuthCompletion(timeout: 10) + + // Verify authenticated state + try oauthHelper.waitForAuthenticatedState(timeout: 5) + + XCTAssertTrue( + verifyAuthStatus(expected: true), + "Should show authenticated status after successful login" + ) + } + + /// Perform logout by tapping Sign Out + func performLogout() throws { + tapSignOut() + + // Wait for state to update + Thread.sleep(forTimeInterval: 2) + + XCTAssertTrue( + verifyAuthStatus(expected: false), + "Should show not authenticated status after logout" + ) + } + + /// Clear app state + func performClear() throws { + tapClear() + + // Wait for clear to complete + Thread.sleep(forTimeInterval: 2) + + XCTAssertTrue( + verifyAuthStatus(expected: false), + "Should show not authenticated status after clear" + ) + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift new file mode 100644 index 00000000..285d2104 --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift @@ -0,0 +1,128 @@ +import XCTest + +/// Page Object for the Token Details tab +class TokenScreenPageObject { + let app: XCUIApplication + + init(app: XCUIApplication) { + self.app = app + } + + // MARK: - Elements + + var tokenTabButton: XCUIElement { + return app.buttons["tokenTab"] + } + + var revokeTokenButton: XCUIElement { + return app.buttons["revokeTokenButton"] + } + + var tokenDetailsView: XCUIElement { + return app.staticTexts.element(containingText: "Token Details") + } + + var tokenExpirationText: XCUIElement { + return app.staticTexts.element(containingText: "expiresAt") + } + + // MARK: - Navigation + + /// Navigate to Token tab + func navigateToTab() { + XCTAssertTrue( + tokenTabButton.exists, + "Token tab button should exist" + ) + tokenTabButton.tapSafely() + + // Wait for tab to load + Thread.sleep(forTimeInterval: 0.5) + + // Verify tab title appears + let tabTitle = app.staticTexts["Token Details"] + XCTAssertTrue( + tabTitle.waitForExistence(timeout: 3), + "Token Details tab should load" + ) + } + + // MARK: - State Verification + + /// Check if token details are displayed + /// - returns: True if token information is visible, false otherwise + func isTokenDisplayed() -> Bool { + return tokenDetailsView.exists && tokenDetailsView.isDisplayed + } + + /// Wait for token to be displayed + /// - parameter timeout: Maximum time to wait in seconds + /// - returns: True if token appears, false on timeout + func waitForTokenDisplay(timeout: TimeInterval = 3) -> Bool { + return XCTestWait.waitFor(timeout: timeout) { + return self.isTokenDisplayed() + } + } + + /// Check if revoke button is accessible (visible and hittable) + /// - returns: True if button is accessible, false otherwise + func isRevokeButtonAccessible() -> Bool { + return revokeTokenButton.exists && revokeTokenButton.isHittable + } + + /// Scroll to revoke button if needed + /// The button may be off-screen, requiring scroll + func scrollToRevokeButton() { + var attempts = 0 + let maxAttempts = 5 + + while !isRevokeButtonAccessible() && attempts < maxAttempts { + app.swipeUp() + Thread.sleep(forTimeInterval: 0.2) + attempts += 1 + } + + XCTAssertTrue( + isRevokeButtonAccessible(), + "Revoke Token button should be accessible after scrolling" + ) + } + + // MARK: - Actions + + /// Tap "Revoke Token" button + /// Note: Automatically scrolls if button is off-screen + func tapRevokeToken() { + // Ensure button is visible and accessible + scrollToRevokeButton() + + revokeTokenButton.tapSafely() + + // Wait for revocation to complete + Thread.sleep(forTimeInterval: 1.5) + } + + /// Perform token revocation flow + /// - parameter credentialsPageObject: Used to verify credential count changes + func performRevocation(credentialsPageObject: CredentialsScreenPageObject) throws { + // Get current credential count before revocation + let countBefore = credentialsPageObject.getCredentialCount() ?? 0 + + // Revoke the token + tapRevokeToken() + + // Navigate to Credentials tab to verify count decreased + credentialsPageObject.navigateToTab() + + let expectedCount = max(0, countBefore - 1) + let countDecreased = credentialsPageObject.waitForCredentialCount( + expected: expectedCount, + timeout: 5 + ) + + XCTAssertTrue( + countDecreased, + "Credential count should decrease from \(countBefore) to \(expectedCount)" + ) + } +} diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift new file mode 100644 index 00000000..140472cc --- /dev/null +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift @@ -0,0 +1,266 @@ +import XCTest + +/** + Hybrid E2E tests for OAuth authentication flows on iOS. + + These tests use XCUITest to interact with the React Native OIDC test app and + ASWebAuthenticationSession for OAuth provider interaction. + + Prerequisites: + - USERNAME and PASSWORD environment variables must be set (from testenv file) + - iOS simulator must have networking access to OAuth provider (Okta) + */ +final class ReactNativeOIDCAppUITests: XCTestCase { + + // MARK: - Properties + + var app: XCUIApplication! + var testHelpers: TestHelpers! + var oauthHelper: OAuthHelper! + var loginScreen: LoginScreenPageObject! + var credentialsScreen: CredentialsScreenPageObject! + var tokenScreen: TokenScreenPageObject! + + var oauthCredentials: (username: String, password: String)! + + // MARK: - Setup & Teardown + + override func setUpWithError() throws { + // Disable automatic screenshot capture to speed up tests + continueAfterFailure = false + + // Initialize app + app = XCUIApplication() + testHelpers = TestHelpers(app: app) + oauthHelper = OAuthHelper(app: app) + loginScreen = LoginScreenPageObject(app: app) + credentialsScreen = CredentialsScreenPageObject(app: app) + tokenScreen = TokenScreenPageObject(app: app) + + // Load OAuth credentials from environment + do { + oauthCredentials = try TestHelpers.loadOAuthCredentials() + print("โœ“ OAuth credentials loaded") + } catch { + XCTFail("Failed to load OAuth credentials: \(error)") + throw error + } + + // Launch app + app.launch() + + // Wait for app to fully load + let authTab = app.buttons["loginTab"] + let launched = authTab.waitForExistence(timeout: 10) + XCTAssertTrue(launched, "App should launch successfully") + + // Verify fresh app state + try testHelpers.assertFreshAppState() + } + + override func tearDownWithError() throws { + // Clear app data after each test + try testHelpers.navigateToTab(name: "Login") + try testHelpers.tapClear() + Thread.sleep(forTimeInterval: 1) + + app = nil + } + + // MARK: - Test Cases + + /// Test Case 1: Complete OAuth Login with Valid Credentials + /// + /// Flow: + /// 1. Tap "Request Token" to initiate OAuth flow + /// 2. Wait for ASWebAuthenticationSession to appear + /// 3. Enter username and password in OAuth provider + /// 4. Authorize (approve) the request + /// 5. Verify app receives callback and shows authenticated state + func testOAuthFlow_CompleteLoginWithValidCredentials() throws { + print("Starting: testOAuthFlow_CompleteLoginWithValidCredentials") + + // Verify initial not authenticated state + try loginScreen.performLogin( + oauthHelper: oauthHelper, + credentials: oauthCredentials + ) + + print("โœ“ Login successful, app authenticated") + } + + /// Test Case 2: ASWebAuthenticationSession Dismissed Before Completion + /// + /// Flow: + /// 1. Tap "Request Token" to initiate OAuth flow + /// 2. Wait for ASWebAuthenticationSession to appear + /// 3. Dismiss the OAuth sheet before completing authorization + /// 4. Verify app remains in not authenticated state + /// + /// Note: Behavioral equivalent to Android's "Chrome Tab Closed Before Completion" test + func testOAuthFlow_ASWebAuthSessionDismissedBeforeCompletion() throws { + print("Starting: testOAuthFlow_ASWebAuthSessionDismissedBeforeCompletion") + + // Wait for app to launch + Thread.sleep(forTimeInterval: 2) + + // Verify not authenticated at start + try loginScreen.navigateToTab() + XCTAssertTrue( + loginScreen.verifyAuthStatus(expected: false), + "Should start in not authenticated state" + ) + + // Start OAuth flow + loginScreen.tapRequestToken() + + // Wait for OAuth UI to appear + let oauthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) + XCTAssertTrue(oauthUIAppeared, "OAuth UI should appear") + + // Dismiss the OAuth sheet + try oauthHelper.dismissOAuthSheet() + + // Wait for app to return to foreground + Thread.sleep(forTimeInterval: 2) + + // Verify app is still in not authenticated state (no callback received) + XCTAssertTrue( + loginScreen.verifyAuthStatus(expected: false), + "App should remain not authenticated after OAuth dismissal" + ) + + print("โœ“ OAuth dismissal handled correctly") + } + + /// Test Case 3: Token Revocation After Login + /// + /// Flow: + /// 1. Complete OAuth login (authenticated) + /// 2. Navigate to Token tab + /// 3. Tap "Revoke Token" button + /// 4. Verify credentials are removed + /// 5. Verify app shows not authenticated state + func testOAuthFlow_TokenRevokeAfterLogin() throws { + print("Starting: testOAuthFlow_TokenRevokeAfterLogin") + + // First, complete login + try loginScreen.performLogin( + oauthHelper: oauthHelper, + credentials: oauthCredentials + ) + + XCTAssertTrue( + loginScreen.verifyAuthStatus(expected: true), + "Should be authenticated after login" + ) + + // Navigate to Token tab + tokenScreen.navigateToTab() + XCTAssertTrue( + tokenScreen.isTokenDisplayed(), + "Token details should be displayed" + ) + + // Revoke the token + tokenScreen.tapRevokeToken() + + // Navigate back to Login tab to verify logout + try loginScreen.navigateToTab() + + // Wait for authenticated status to change to not authenticated + let loggedOut = loginScreen.waitForAuthStatus(expected: false, timeout: 5) + XCTAssertTrue( + loggedOut, + "Should show not authenticated status after token revocation" + ) + + print("โœ“ Token revocation successful") + } + + /// Test Case 4: Request Multiple Tokens and Credential Management + /// + /// Flow: + /// 1. Complete OAuth login (1st credential) + /// 2. Complete OAuth login again (2nd credential) + /// 3. Navigate to Credentials tab + /// 4. Verify "2 credentials stored" is displayed + /// 5. Navigate to Token tab + /// 6. Revoke the default credential + /// 7. Verify credentials count decreases to 1 + /// 8. Verify DEFAULT badge is no longer displayed + func testOAuthFlow_RequestMultipleTokens() throws { + print("Starting: testOAuthFlow_RequestMultipleTokens") + + // First login - acquire 1st token + try loginScreen.performLogin( + oauthHelper: oauthHelper, + credentials: oauthCredentials + ) + + // Wait between logins + Thread.sleep(forTimeInterval: 2) + + // Second login - acquire 2nd token + // Note: Assuming ephemeralSession=true in app config, no bound redirect + loginScreen.tapRequestToken() + let oauthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) + XCTAssertTrue(oauthUIAppeared, "OAuth UI should appear for 2nd login") + + try oauthHelper.enterOAuthCredentials( + username: oauthCredentials.username, + password: oauthCredentials.password + ) + try oauthHelper.waitForOAuthCompletion(timeout: 10) + + // Wait for state to settle + Thread.sleep(forTimeInterval: 2) + + // Verify still authenticated + XCTAssertTrue( + loginScreen.verifyAuthStatus(expected: true), + "Should still be authenticated after 2nd login" + ) + + // Navigate to Credentials tab to verify 2 credentials exist + credentialsScreen.navigateToTab() + + let twoCredentials = credentialsScreen.waitForCredentialCount( + expected: 2, + timeout: 5 + ) + XCTAssertTrue( + twoCredentials, + "Should show 2 credentials stored" + ) + + XCTAssertTrue( + credentialsScreen.isDefaultBadgeVisible(), + "Should display DEFAULT badge for active credential" + ) + + // Navigate to Token tab to revoke default credential + tokenScreen.navigateToTab() + tokenScreen.tapRevokeToken() + + // Navigate back to Credentials tab to verify count decreased + credentialsScreen.navigateToTab() + + let oneCredential = credentialsScreen.waitForCredentialCount( + expected: 1, + timeout: 5 + ) + XCTAssertTrue( + oneCredential, + "Should show 1 credential stored after revocation" + ) + + let defaultGone = credentialsScreen.waitForDefaultBadgeToDisappear(timeout: 3) + XCTAssertTrue( + defaultGone, + "DEFAULT badge should no longer be displayed" + ) + + print("โœ“ Multiple token and credential management verified") + } +} diff --git a/packages/react-native-platform/ios/Sources/RNBrowserSessionBridge/BrowserSessionBridge.m b/packages/react-native-platform/ios/Sources/RNBrowserSessionBridge/BrowserSessionBridge.m index edcad56e..4855f762 100644 --- a/packages/react-native-platform/ios/Sources/RNBrowserSessionBridge/BrowserSessionBridge.m +++ b/packages/react-native-platform/ios/Sources/RNBrowserSessionBridge/BrowserSessionBridge.m @@ -4,6 +4,7 @@ @interface RCT_EXTERN_MODULE(BrowserSessionBridge, NSObject) RCT_EXTERN_METHOD(openAuthSession:(NSString *)url redirectScheme:(NSString *)redirectScheme + options:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) From c39893d44d7015719bb4ac3b4139715785127b79 Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Thu, 9 Jul 2026 14:09:34 -0400 Subject: [PATCH 4/7] first pass at mock oauth server --- .gitignore | 1 + e2e/apps/react-native-oidc/auth.ts | 1 + .../Helpers/OAuthHelper.swift | 95 ++-- .../Helpers/TestHelpers.swift | 93 +++- .../Helpers/XCTestHelpers.swift | 23 +- .../CredentialsScreenPageObject.swift | 33 +- .../PageObjects/LoginScreenPageObject.swift | 14 +- .../PageObjects/TokenScreenPageObject.swift | 45 -- .../ReactNativeOIDCAppUITests.swift | 62 ++- e2e/apps/redirect-model/src/auth.tsx | 3 +- .../src/Credential/CredentialDataSource.ts | 4 +- packages/auth-foundation/src/Token.ts | 3 + .../src/jwt/IDTokenValidator.ts | 4 + packages/auth-foundation/src/oauth2/client.ts | 7 +- .../src/oauth2/configuration.ts | 5 + packages/mock-auth-server/crypto.ts | 70 +++ packages/mock-auth-server/flow.ts | 215 ++++++++ packages/mock-auth-server/index.ts | 92 ++++ packages/mock-auth-server/mocks/token.ts | 81 +++ packages/mock-auth-server/package.json | 64 +++ packages/mock-auth-server/tsconfig.json | 24 + packages/mock-auth-server/types.ts | 0 yarn.lock | 510 +++++++++++++----- 23 files changed, 1127 insertions(+), 322 deletions(-) create mode 100644 packages/mock-auth-server/crypto.ts create mode 100644 packages/mock-auth-server/flow.ts create mode 100644 packages/mock-auth-server/index.ts create mode 100644 packages/mock-auth-server/mocks/token.ts create mode 100644 packages/mock-auth-server/package.json create mode 100644 packages/mock-auth-server/tsconfig.json create mode 100644 packages/mock-auth-server/types.ts diff --git a/.gitignore b/.gitignore index 930baf58..7f03142c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ node_modules testenv testenv* testenv.yml +.watchmanconfig # Testing coverage diff --git a/e2e/apps/react-native-oidc/auth.ts b/e2e/apps/react-native-oidc/auth.ts index d89c481b..93af509b 100644 --- a/e2e/apps/react-native-oidc/auth.ts +++ b/e2e/apps/react-native-oidc/auth.ts @@ -13,6 +13,7 @@ export const client = new OAuth2Client({ clientId: Constants?.expoConfig?.extra?.env.NATIVE_CLIENT_ID, scopes: ['openid', 'email', 'profile', 'offline_access'], dpop: false, + allowHTTP: true }); export const flow = new AuthorizationCodeFlow(client, { diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift index 5121ea64..bd95f648 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift @@ -14,7 +14,8 @@ class OAuthHelper { /// This waits for the system OAuth sheet to be presented /// - parameter timeout: Maximum time to wait for OAuth UI in seconds /// - returns: True if OAuth UI appeared, false if timeout - func waitForOAuthUI(timeout: TimeInterval = 8) -> Bool { + func waitForOAuthUI(timeout: TimeInterval = 5) -> Bool { + print("โณ [OAuthHelper] Waiting for OAuth UI (timeout: \(timeout)s)...") let deadline = Date().addingTimeInterval(timeout) var attempts = 0 let maxAttempts = Int(timeout * 10) @@ -24,11 +25,13 @@ class OAuthHelper { // This is a heuristic since ASWebAuthenticationSession webview is restricted let webviewElements = app.webViews if webviewElements.element.exists { + print("โœ… [OAuthHelper] Found webview element") return true } // Also check for any text containing "Sign In" or "Login" which might come from OAuth provider if app.staticTexts["Sign In"].exists || app.webViews.element.exists { + print("โœ… [OAuthHelper] Found OAuth UI element") return true } @@ -36,6 +39,9 @@ class OAuthHelper { attempts += 1 } + print("โŒ [OAuthHelper] OAuth UI not found after \(attempts) attempts (\(timeout)s)") + print(" DEBUG: webviews.count = \(app.webViews.count)") + print(" DEBUG: staticTexts.count = \(app.staticTexts.count)") return app.webViews.element.exists } @@ -51,42 +57,48 @@ class OAuthHelper { throw OAuthError.webViewNotAccessible } - let webView = app.webViews.element - - // Attempt to find and fill username field - // Note: Safari/system webviews may expose form elements through the accessibility tree - let usernameField = webView.textFields.element(boundBy: 0) - if usernameField.exists { - usernameField.tap() - Thread.sleep(forTimeInterval: 0.2) - usernameField.typeText(username) - Thread.sleep(forTimeInterval: 0.3) - } else { - // If direct field access fails, attempt keyboard input - // This assumes the field is already focused - let remoteDismiss = app.keys["Delete"] - if remoteDismiss.exists { - // Attempt to clear any existing text - for _ in 0..<20 { - remoteDismiss.press() - } - } - app.typeText(username) - } - - // Move to password field and enter password - app.typeText("\t") // Tab to next field + // let webView = app.webViews.element + + // // Attempt to find and fill username field + // // Note: Safari/system webviews may expose form elements through the accessibility tree + // let usernameField = webView.textFields.element(boundBy: 0) + // if usernameField.exists { + // usernameField.tap() + // Thread.sleep(forTimeInterval: 0.2) + // usernameField.typeText(username) + // Thread.sleep(forTimeInterval: 0.3) + // } else { + // // If direct field access fails, attempt keyboard input + // // This assumes the field is already focused + // let remoteDismiss = app.keys["Delete"] + // if remoteDismiss.exists { + // // Attempt to clear any existing text + // for _ in 0..<20 { + // remoteDismiss.press(forDuration: 0.5) + // } + // } + // app.typeText(username) + // } + + app.typeText(username) + app.typeText(XCUIKeyboardKey.enter) + Thread.sleep(forTimeInterval: 0.3) - let passwordField = webView.secureTextFields.element(boundBy: 0) - if passwordField.exists { - passwordField.tap() - Thread.sleep(forTimeInterval: 0.2) - passwordField.typeText(password) - Thread.sleep(forTimeInterval: 0.3) - } else { - app.typeText(password) - } + // let passwordField = webView.secureTextFields.element(boundBy: 0) + // if passwordField.exists { + // passwordField.tap() + // Thread.sleep(forTimeInterval: 0.2) + // passwordField.typeText(password) + // Thread.sleep(forTimeInterval: 0.3) + // } else { + // app.typeText(password) + // } + + // TODO: select password authenticator + + app.typeText(password) + app.typeText(XCUIKeyboardKey.enter) // Attempt to submit form let submitButton = webView.buttons["Sign In"] @@ -103,18 +115,26 @@ class OAuthHelper { /// Wait for OAuth flow to complete and app to return to foreground /// Waits for the app's authentication status to update after OAuth callback /// - parameter timeout: Maximum time to wait for OAuth completion in seconds - func waitForOAuthCompletion(timeout: TimeInterval = 10) throws { + func waitForOAuthCompletion(timeout: TimeInterval = 8) throws { + print("โณ [OAuthHelper] Waiting for OAuth completion (timeout: \(timeout)s)...") let deadline = Date().addingTimeInterval(timeout) let authStatusProperty = "label" var authStatusFound = false + var iterations = 0 while Date() < deadline { + iterations += 1 // Check if app is back in foreground (no longer showing OAuth sheet) let webviewGone = !app.webViews.element.exists let appInForeground = app.staticTexts["Authentication"].exists || app.buttons["requestTokenButton"].exists + if iterations % 20 == 0 { + print(" [OAuthHelper iteration \(iterations)] webviewGone=\(webviewGone), appInForeground=\(appInForeground)") + } + if webviewGone && appInForeground { authStatusFound = true + print("โœ… [OAuthHelper] OAuth completion detected") break } @@ -122,11 +142,12 @@ class OAuthHelper { } guard authStatusFound else { + print("โŒ [OAuthHelper] OAuth completion timeout after \(iterations) iterations") throw OAuthError.completionTimeout } // Brief additional wait for app state to settle - Thread.sleep(forTimeInterval: 1) + Thread.sleep(forTimeInterval: 0.5) } /// Dismiss the OAuth sheet (simulating user cancellation) diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift index ca88e038..553a51e3 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift @@ -15,18 +15,29 @@ class TestHelpers { /// - returns: Tuple of (username, password) /// - throws: XCTestError if credentials not found static func loadOAuthCredentials() throws -> (username: String, password: String) { + print("๐Ÿ”‘ [TestHelpers] Starting credential load...") + var username: String? var password: String? // First, try to read from testenv file (mirrors Android build.gradle approach) - if let testenvCredentials = try? loadCredentialsFromTestenv() { - return testenvCredentials + do { + if let testenvCredentials = try? loadCredentialsFromTestenv() { + print("โœ… [TestHelpers] Successfully loaded from testenv file") + return testenvCredentials + } + } catch { + print("โš ๏ธ [TestHelpers] testenv load failed: \(error)") } // Fallback to environment variables + print("๐Ÿ” [TestHelpers] Checking environment variables...") username = ProcessInfo.processInfo.environment["USERNAME"] password = ProcessInfo.processInfo.environment["PASSWORD"] + print(" USERNAME from env: \(username?.isEmpty == false ? "***" : "(not set)")") + print(" PASSWORD from env: \(password?.isEmpty == false ? "***" : "(not set)")") + guard let username = username, !username.isEmpty else { throw NSError(domain: "TestHelpers", code: 1, userInfo: [NSLocalizedDescriptionKey: "USERNAME not found in testenv file or environment variable"]) @@ -36,42 +47,38 @@ class TestHelpers { userInfo: [NSLocalizedDescriptionKey: "PASSWORD not found in testenv file or environment variable"]) } + print("โœ… [TestHelpers] Loaded from environment variables") return (username, password) } /// Load credentials from testenv file - /// Searches for testenv file at workspace root and parses USERNAME and PASSWORD + /// Searches for testenv file in known workspace locations /// - returns: Tuple of (username, password) if found /// - throws: Error if file not found or credentials missing private static func loadCredentialsFromTestenv() throws -> (username: String, password: String) { - // Find testenv file relative to this source file location - // Source is at: e2e/apps/react-native-oidc/ios/E2e/Helpers/TestHelpers.swift - // Need to go up to workspace root - let sourcePath = #filePath // Current file path let fileManager = FileManager.default - // Walk up the directory tree to find testenv - var currentPath = (sourcePath as NSString).deletingLastPathComponent - var attempts = 0 - let maxAttempts = 10 // Prevent infinite loops + // Try common monorepo root locations + let commonPaths = [ + // CI environment variable (can be set by build system) + ProcessInfo.processInfo.environment["TESTENV_PATH"], + // Typical local dev setup - absolute path + "/Users/jaredperreault/Code/devex/client-js/testenv", + // Try from current working directory + (FileManager.default.currentDirectoryPath as NSString).appendingPathComponent("testenv"), + ].compactMap { $0 } - while attempts < maxAttempts { - let potentialTestenvPath = (currentPath as NSString).appendingPathComponent("testenv") - - if fileManager.fileExists(atPath: potentialTestenvPath) { - return try parseTestenvFile(at: potentialTestenvPath) - } - - let parentPath = (currentPath as NSString).deletingLastPathComponent - if parentPath == currentPath { - // Reached root directory - break + print("๐Ÿ” [TestHelpers] Searching for testenv in \(commonPaths.count) locations...") + + for path in commonPaths { + print("๐Ÿ” [TestHelpers] Checking: \(path)") + if fileManager.fileExists(atPath: path) { + print("โœ… [TestHelpers] Found testenv at: \(path)") + return try parseTestenvFile(at: path) } - - currentPath = parentPath - attempts += 1 } + print("โŒ [TestHelpers] testenv file not found in any location") throw NSError(domain: "TestEnv", code: 1, userInfo: [NSLocalizedDescriptionKey: "testenv file not found"]) } @@ -82,10 +89,13 @@ class TestHelpers { /// - throws: Error if credentials not found private static func parseTestenvFile(at path: String) throws -> (username: String, password: String) { let content = try String(contentsOfFile: path, encoding: .utf8) + print("๐Ÿ“„ [TestHelpers] testenv file content:\n\(content)") + var username: String? var password: String? let lines = content.components(separatedBy: .newlines) + print("๐Ÿ“„ [TestHelpers] Parsing \(lines.count) lines from testenv") for line in lines { let trimmed = line.trimmingCharacters(in: .whitespaces) @@ -107,6 +117,8 @@ class TestHelpers { value = String(value.dropFirst().dropLast()) } + print(" โ†’ \(key) = \(value.isEmpty ? "(empty)" : "***")") + if key == "USERNAME" { username = value } else if key == "PASSWORD" { @@ -115,15 +127,17 @@ class TestHelpers { } guard let username = username, !username.isEmpty else { + print("โŒ [TestHelpers] USERNAME not found or empty in testenv") throw NSError(domain: "TestEnv", code: 2, userInfo: [NSLocalizedDescriptionKey: "USERNAME not found in testenv file"]) } guard let password = password, !password.isEmpty else { + print("โŒ [TestHelpers] PASSWORD not found or empty in testenv") throw NSError(domain: "TestEnv", code: 3, userInfo: [NSLocalizedDescriptionKey: "PASSWORD not found in testenv file"]) } - print("๐Ÿ“‹ Loaded credentials from testenv file") + print("โœ… [TestHelpers] Loaded credentials from testenv file") return (username, password) } @@ -138,8 +152,13 @@ class TestHelpers { /// Verify fresh app state (not authenticated, no credentials) func assertFreshAppState() throws { + print(" โณ Checking authentication status...") try verifyAuthenticationStatus(expected: false) + print(" โœ… Auth status verified") + + print(" โณ Checking credentials count...") try verifyCredentialsCount(expected: 0) + print(" โœ… Credentials count verified") } /// Verify authentication status: either "โœ… Authenticated" or "โŒ Not Authenticated" @@ -148,6 +167,9 @@ class TestHelpers { let expectedText = expected ? "โœ… Authenticated" : "โŒ Not Authenticated" let statusElement = app.staticTexts.element(containingText: expectedText) + print(" โ†’ Looking for auth status: '\(expectedText)'...") + print(" โ†’ Element exists: \(statusElement.exists)") + XCTestWait.waitForElement( statusElement, timeout: 5, @@ -157,17 +179,26 @@ class TestHelpers { statusElement.exists, "Expected authentication status: \(expectedText)" ) + print(" โœ… Auth status correct: '\(expectedText)'") } /// Verify number of stored credentials /// - parameter expected: Expected credential count func verifyCredentialsCount(expected: Int) throws { - let countText = "\(expected) credential\(expected == 1 ? "" : "s") stored" + let countText: String + if expected == 0 { + countText = "No credentials found" + } else { + countText = "\(expected) credential\(expected == 1 ? "" : "s") stored" + } let countElement = app.staticTexts.element(containingText: countText) + print(" โ†’ Navigating to Credentials tab...") // Navigate to Credentials tab first try navigateToTab(name: "Creds") + print(" โœ… On Credentials tab") + print(" โ†’ Waiting for: '\(countText)'...") XCTestWait.waitForElement( countElement, timeout: 5, @@ -177,6 +208,7 @@ class TestHelpers { countElement.exists, "Expected to see: \(countText)" ) + print(" โœ… Found: '\(countText)'") } // MARK: - App Navigation @@ -195,13 +227,19 @@ class TestHelpers { userInfo: [NSLocalizedDescriptionKey: "Unknown tab: \(name)"]) } + print(" [navigating to '\(name)' tab]") let tabButton = app.buttons[config.contentDesc] + print(" โ†’ Tab button exists: \(tabButton.exists)") XCTAssertTrue( tabButton.exists, "Tab button for \(name) should exist" ) + + print(" โ†’ Tapping tab...") tabButton.tapSafely() + Thread.sleep(forTimeInterval: 0.3) + print(" โ†’ Waiting for tab title '\(config.title)'...") // Wait for tab content to load let titleElement = app.staticTexts.element(containingText: config.title) XCTestWait.waitForElement( @@ -209,6 +247,7 @@ class TestHelpers { timeout: 3, message: "Should navigate to \(name) tab" ) + print(" โœ… Tab '\(name)' loaded") } // MARK: - Action Helpers diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/XCTestHelpers.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/XCTestHelpers.swift index e6eb9044..77670215 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/XCTestHelpers.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/XCTestHelpers.swift @@ -3,13 +3,6 @@ import XCTest // MARK: - XCUIElement Extensions extension XCUIElement { - /// Wait for element to exist with optional timeout - /// - parameter timeout: Maximum time to wait in seconds (default: 5) - /// - returns: True if element exists, false otherwise - func waitForExistence(timeout: TimeInterval = 5) -> Bool { - return self.waitForExistence(timeout: timeout) - } - /// Check if element is displayed (exists and is hittable) var isDisplayed: Bool { return self.exists && self.isHittable @@ -27,6 +20,22 @@ extension XCUIElement { var textValue: String { return (self.value as? String) ?? "" } + + func clearAndEnterText(text: String) { + guard let currentValue = self.value as? String else { + XCTFail("Cannot clear element without a value") + return + } + + // focus the element + self.tap() + + // creates string of delete characters to clear the current value + let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: currentValue.count) + + self.typeText(deleteString) + self.typeText(text) + } } // MARK: - XCUIApplication Extensions diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift index 03c004c1..e1df753d 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift @@ -15,8 +15,10 @@ class CredentialsScreenPageObject { } var credentialCountText: XCUIElement { - // Looks for text like "2 credentials stored" - return app.staticTexts.element(containingText: "stored") + // Looks for text like "2 credentials stored" or "No credentials found" + let stored = app.staticTexts.element(containingText: "stored") + let notFound = app.staticTexts.element(containingText: "No credentials") + return stored.exists ? stored : notFound } var defaultBadge: XCUIElement { @@ -27,27 +29,6 @@ class CredentialsScreenPageObject { return app.tables.element } - // MARK: - Navigation - - /// Navigate to Credentials tab - func navigateToTab() { - XCTAssertTrue( - credentialsTabButton.exists, - "Credentials tab button should exist" - ) - credentialsTabButton.tapSafely() - - // Wait for tab to load - Thread.sleep(forTimeInterval: 0.5) - - // Verify tab title appears - let tabTitle = app.staticTexts["Credentials"] - XCTAssertTrue( - tabTitle.waitForExistence(timeout: 3), - "Credentials tab should load" - ) - } - // MARK: - State Verification /// Get the current credential count from displayed text @@ -59,6 +40,12 @@ class CredentialsScreenPageObject { } let text = countElement.label + + // Check for "No credentials found" first + if text.contains("No credentials") { + return 0 + } + // Extract number from text like "2 credentials stored" let components = text.components(separatedBy: " ") if let firstComponent = components.first, let count = Int(firstComponent) { diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift index 174ae8d3..0cc77ae4 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift @@ -37,7 +37,7 @@ class LoginScreenPageObject { func verifyAuthStatus(expected: Bool) -> Bool { let expectedText = expected ? "โœ… Authenticated" : "โŒ Not Authenticated" let element = app.staticTexts.element(containingText: expectedText) - return element.exists && element.isDisplayed + return element.exists } /// Wait for authentication status to match expected value @@ -79,18 +79,6 @@ class LoginScreenPageObject { clearButton.tapSafely() } - /// Ensure we're on the Login tab - func navigateToTab() { - XCTAssertTrue( - loginTabButton.exists, - "Login tab button should exist" - ) - if !loginTabButton.isDisplayed { - loginTabButton.tap() - Thread.sleep(forTimeInterval: 0.3) - } - } - /// Perform complete login flow from request to completion /// - parameter oauthHelper: Helper for OAuth interaction /// - parameter credentials: (username, password) tuple diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift index 285d2104..06a764e0 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift @@ -26,27 +26,6 @@ class TokenScreenPageObject { return app.staticTexts.element(containingText: "expiresAt") } - // MARK: - Navigation - - /// Navigate to Token tab - func navigateToTab() { - XCTAssertTrue( - tokenTabButton.exists, - "Token tab button should exist" - ) - tokenTabButton.tapSafely() - - // Wait for tab to load - Thread.sleep(forTimeInterval: 0.5) - - // Verify tab title appears - let tabTitle = app.staticTexts["Token Details"] - XCTAssertTrue( - tabTitle.waitForExistence(timeout: 3), - "Token Details tab should load" - ) - } - // MARK: - State Verification /// Check if token details are displayed @@ -101,28 +80,4 @@ class TokenScreenPageObject { // Wait for revocation to complete Thread.sleep(forTimeInterval: 1.5) } - - /// Perform token revocation flow - /// - parameter credentialsPageObject: Used to verify credential count changes - func performRevocation(credentialsPageObject: CredentialsScreenPageObject) throws { - // Get current credential count before revocation - let countBefore = credentialsPageObject.getCredentialCount() ?? 0 - - // Revoke the token - tapRevokeToken() - - // Navigate to Credentials tab to verify count decreased - credentialsPageObject.navigateToTab() - - let expectedCount = max(0, countBefore - 1) - let countDecreased = credentialsPageObject.waitForCredentialCount( - expected: expectedCount, - timeout: 5 - ) - - XCTAssertTrue( - countDecreased, - "Credential count should decrease from \(countBefore) to \(expectedCount)" - ) - } } diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift index 140472cc..dd60f808 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift @@ -26,9 +26,14 @@ final class ReactNativeOIDCAppUITests: XCTestCase { // MARK: - Setup & Teardown override func setUpWithError() throws { + print("\n" + String(repeating: "=", count: 60)) + print("๐Ÿงช setUp START") + print(String(repeating: "=", count: 60)) + // Disable automatic screenshot capture to speed up tests continueAfterFailure = false + print("๐Ÿ“ฑ Initializing app...") // Initialize app app = XCUIApplication() testHelpers = TestHelpers(app: app) @@ -37,34 +42,54 @@ final class ReactNativeOIDCAppUITests: XCTestCase { credentialsScreen = CredentialsScreenPageObject(app: app) tokenScreen = TokenScreenPageObject(app: app) + print("๐Ÿ” Loading OAuth credentials...") // Load OAuth credentials from environment do { oauthCredentials = try TestHelpers.loadOAuthCredentials() - print("โœ“ OAuth credentials loaded") + print("โœ… OAuth credentials loaded: \(oauthCredentials.username)") } catch { + print("โŒ Failed to load OAuth credentials: \(error)") XCTFail("Failed to load OAuth credentials: \(error)") throw error } + + app.launchEnvironment["XCODE_WAIT_FOR_IDLE_TIMEOUT"] = "5" - // Launch app + print("๐Ÿš€ Launching app...") + // Launch app - XCTest will wait for app to idle after launch app.launch() + print("๐Ÿ“ฒ App launched, waiting for UI elements...") // Wait for app to fully load + print("โณ Waiting for loginTab button (10s timeout)...") let authTab = app.buttons["loginTab"] let launched = authTab.waitForExistence(timeout: 10) + print(" โ†’ loginTab exists: \(authTab.exists), launched: \(launched)") XCTAssertTrue(launched, "App should launch successfully") + print("โœ… App UI loaded") + print("๐Ÿ” Verifying fresh app state...") // Verify fresh app state - try testHelpers.assertFreshAppState() + do { + try testHelpers.assertFreshAppState() + print("โœ… Fresh app state verified") + } catch { + print("โŒ Fresh app state check failed: \(error)") + throw error + } + + print("โœ… setUp COMPLETE\n") } override func tearDownWithError() throws { - // Clear app data after each test - try testHelpers.navigateToTab(name: "Login") - try testHelpers.tapClear() - Thread.sleep(forTimeInterval: 1) - - app = nil + print("\n๐Ÿงน tearDown START") + defer { print("โœ… tearDown COMPLETE\n") } + + print(" Cleaning up app...") + // Simply terminate without trying to interact with UI + // This avoids hanging if app is in bad state + app.terminate() + print(" โ†’ App terminated") } // MARK: - Test Cases @@ -78,15 +103,16 @@ final class ReactNativeOIDCAppUITests: XCTestCase { /// 4. Authorize (approve) the request /// 5. Verify app receives callback and shows authenticated state func testOAuthFlow_CompleteLoginWithValidCredentials() throws { - print("Starting: testOAuthFlow_CompleteLoginWithValidCredentials") + print("\n๐Ÿงช TEST #1: testOAuthFlow_CompleteLoginWithValidCredentials") - // Verify initial not authenticated state + print(" Performing login...") + try testHelpers.navigateToTab(name: "Login") try loginScreen.performLogin( oauthHelper: oauthHelper, credentials: oauthCredentials ) - print("โœ“ Login successful, app authenticated") + print("โœ… Test #1 PASSED: Login successful, app authenticated\n") } /// Test Case 2: ASWebAuthenticationSession Dismissed Before Completion @@ -105,7 +131,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { Thread.sleep(forTimeInterval: 2) // Verify not authenticated at start - try loginScreen.navigateToTab() + try testHelpers.navigateToTab(name: "Login") XCTAssertTrue( loginScreen.verifyAuthStatus(expected: false), "Should start in not authenticated state" @@ -156,7 +182,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { ) // Navigate to Token tab - tokenScreen.navigateToTab() + try testHelpers.navigateToTab(name: "Token") XCTAssertTrue( tokenScreen.isTokenDisplayed(), "Token details should be displayed" @@ -166,7 +192,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { tokenScreen.tapRevokeToken() // Navigate back to Login tab to verify logout - try loginScreen.navigateToTab() + try testHelpers.navigateToTab(name: "Login") // Wait for authenticated status to change to not authenticated let loggedOut = loginScreen.waitForAuthStatus(expected: false, timeout: 5) @@ -223,7 +249,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { ) // Navigate to Credentials tab to verify 2 credentials exist - credentialsScreen.navigateToTab() + try testHelpers.navigateToTab(name: "Creds") let twoCredentials = credentialsScreen.waitForCredentialCount( expected: 2, @@ -240,11 +266,11 @@ final class ReactNativeOIDCAppUITests: XCTestCase { ) // Navigate to Token tab to revoke default credential - tokenScreen.navigateToTab() + try testHelpers.navigateToTab(name: "Token") tokenScreen.tapRevokeToken() // Navigate back to Credentials tab to verify count decreased - credentialsScreen.navigateToTab() + try testHelpers.navigateToTab(name: "Creds") let oneCredential = credentialsScreen.waitForCredentialCount( expected: 1, diff --git a/e2e/apps/redirect-model/src/auth.tsx b/e2e/apps/redirect-model/src/auth.tsx index d644afd4..abd38896 100644 --- a/e2e/apps/redirect-model/src/auth.tsx +++ b/e2e/apps/redirect-model/src/auth.tsx @@ -18,7 +18,8 @@ export const oauthConfig: any = { issuer: __ISSUER__, clientId: USE_DPOP ? __DPOP_CLIENT_ID__ : __SPA_CLIENT_ID__, scopes: [...(isOIDC ? ['openid', 'profile', 'email'] : []), 'offline_access', ...customScopes], - dpop: USE_DPOP + dpop: USE_DPOP, + allowHTTP: true // not recommended for production }; oauthConfig.baseURL = oauthConfig.issuer; diff --git a/packages/auth-foundation/src/Credential/CredentialDataSource.ts b/packages/auth-foundation/src/Credential/CredentialDataSource.ts index 2574b03f..0c1fd7b4 100644 --- a/packages/auth-foundation/src/Credential/CredentialDataSource.ts +++ b/packages/auth-foundation/src/Credential/CredentialDataSource.ts @@ -73,9 +73,9 @@ export class DefaultCredentialDataSource implements CredentialDataSource { // moving `new Credential` to protected method to ease testing // it is weirdly difficult to spy on Constructors in jest protected createCredential (token: Token, metadata?: Token.Metadata) { - const { issuer, clientId, scopes, dpopPairId } = token.context; + const { issuer, clientId, scopes, dpopPairId, clientSettings } = token.context; const dpop = token.tokenType === 'DPoP' && !!dpopPairId; - const client = this.createOAuth2Client({ baseURL: issuer, clientId, scopes, dpop }); + const client = this.createOAuth2Client({ baseURL: issuer, clientId, scopes, dpop, ...(clientSettings ?? {}) }); return new this.CredentialConstructor(token, client, metadata); } diff --git a/packages/auth-foundation/src/Token.ts b/packages/auth-foundation/src/Token.ts index ab885a04..9b50e7fa 100644 --- a/packages/auth-foundation/src/Token.ts +++ b/packages/auth-foundation/src/Token.ts @@ -13,6 +13,7 @@ import { isOAuth2ErrorResponse, } from './types/index.ts'; import type { OAuth2Client } from './oauth2/client.ts'; +import type { OAuth2ClientOptions } from './oauth2/configuration.ts'; import { OAuth2Error } from './errors/index.ts'; import { validateURL } from './utils/validators.ts'; import { shortID } from './crypto/index.ts'; @@ -342,6 +343,7 @@ export namespace Token { dpopPairId?: string; acrValues?: AcrValues; maxAge?: TimeInterval; + clientSettings?: OAuth2ClientOptions; }; // https://stackoverflow.com/a/54308812 @@ -353,6 +355,7 @@ export namespace Token { dpopPairId: undefined, acrValues: undefined, maxAge: undefined, + clientSettings: undefined } satisfies Record<(keyof Context), undefined>) as (keyof Context)[]; /** diff --git a/packages/auth-foundation/src/jwt/IDTokenValidator.ts b/packages/auth-foundation/src/jwt/IDTokenValidator.ts index 7511727d..0ebf8ff9 100644 --- a/packages/auth-foundation/src/jwt/IDTokenValidator.ts +++ b/packages/auth-foundation/src/jwt/IDTokenValidator.ts @@ -16,6 +16,7 @@ import { Platform } from '../platform/Platform.ts'; * @group JWT */ export interface IDTokenValidatorContext { + allowHTTP?: boolean; nonce?: string; maxAge?: number; acrValues?: AcrValues; @@ -82,6 +83,9 @@ export const DefaultIDTokenValidator: IDTokenValidator = { throw new JWTError('invalid audience (aud) claim'); case 'scheme': + if (context.allowHTTP) { + break; + } if (jwt.issuer) { const tokenIssuer = new URL(jwt.issuer); if (tokenIssuer.protocol === 'https:') { diff --git a/packages/auth-foundation/src/oauth2/client.ts b/packages/auth-foundation/src/oauth2/client.ts index 751ac27b..84e09d7f 100644 --- a/packages/auth-foundation/src/oauth2/client.ts +++ b/packages/auth-foundation/src/oauth2/client.ts @@ -222,8 +222,7 @@ export class OAuth2Client e scopes: (json.scope ?? this.configuration.scopes).split(' '), ...(acrValues && { acrValues }), ...(maxAge && { maxAge }), - // TODO: client info - // clientSettings: tokenRequest.clientConfiguration.serialize() + clientSettings: this.configuration.getOptions() }; if (this.configuration.dpop && dpopPairId) { @@ -274,7 +273,9 @@ export class OAuth2Client e // Will throw if invalid OAuth2Client.idTokenValidator.validate(token.idToken, new URL(issuer), this.configuration.clientId, { // eslint-disable-next-line camelcase - supportedAlgs: id_token_signing_alg_values_supported, ...context + supportedAlgs: id_token_signing_alg_values_supported, + allowHTTP: this.configuration.allowHTTP, + ...context }); await OAuth2Client.accessTokenValidator.validate(token.accessToken, token.idToken); diff --git a/packages/auth-foundation/src/oauth2/configuration.ts b/packages/auth-foundation/src/oauth2/configuration.ts index 4536d7e8..79605938 100644 --- a/packages/auth-foundation/src/oauth2/configuration.ts +++ b/packages/auth-foundation/src/oauth2/configuration.ts @@ -128,6 +128,11 @@ export class Configuration extends APIClient.Configuration implements APIClientC return matches; } + getOptions (): OAuth2ClientOptions { + const { authentication, allowHTTP, syncClockWithAuthorizationServer } = this; + return { authentication, allowHTTP, syncClockWithAuthorizationServer }; + } + toJSON (): JsonRecord { const { issuer, discoveryURL, clientId, scopes, authentication, allowHTTP, syncClockWithAuthorizationServer } = this; return { diff --git a/packages/mock-auth-server/crypto.ts b/packages/mock-auth-server/crypto.ts new file mode 100644 index 00000000..44e094b8 --- /dev/null +++ b/packages/mock-auth-server/crypto.ts @@ -0,0 +1,70 @@ +import { webcrypto as crypto } from 'node:crypto'; +import { randomBytes } from 'node:crypto'; + + +/** + * Represents a JWKS (JSON Web Key Set) public key. + */ +export interface JWK { + kty: string; + use: string; + kid: string; + n: string; + e: string; + alg: string; +} + +/** + * Represents a generated key pair with public key in JWKS format. + */ +export interface KeyPair { + publicKeyJWK: JWK; + publicKey: CryptoKey; + privateKey: CryptoKey; + keyId: string; +} + +/** + * Generate an RSA key pair using WebCrypto. + * Returns the public key in JWKS format and CryptoKey objects. + * + * @returns KeyPair object with public/private keys + */ +export async function generateKeyPair(): Promise { + const keyId = randomBytes(16).toString('hex').substring(0, 10); + + // Generate RSA key pair using WebCrypto + const keyPair = await crypto.subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: 'SHA-256', + }, + true, // extractable + ['sign', 'verify'] + ); + + // Export public key to JWK format + const publicKeyJWK = (await crypto.subtle.exportKey( + 'jwk', + keyPair.publicKey + )) as JWK & { n: string; e: string }; + + // Ensure the JWK has all required fields + const jwk: JWK = { + kty: publicKeyJWK.kty, + use: 'sig', + kid: keyId, + alg: 'RS256', + n: publicKeyJWK.n, + e: publicKeyJWK.e, + }; + + return { + publicKeyJWK: jwk, + publicKey: keyPair.publicKey, + privateKey: keyPair.privateKey, + keyId, + }; +} diff --git a/packages/mock-auth-server/flow.ts b/packages/mock-auth-server/flow.ts new file mode 100644 index 00000000..3ea0dbd5 --- /dev/null +++ b/packages/mock-auth-server/flow.ts @@ -0,0 +1,215 @@ +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; + + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + + +/** + * Represents a mock response with status, body, and headers. + */ +export interface MockResponse { + status: number; + body?: any; + headers?: Record; +} + +/** + * Configuration options for the Flow class. + */ +export interface FlowConfig { + /** Whether to cycle through responses or stay on the last one (default: false) */ + cycleResponses?: boolean; +} + +/** + * Represents a single OAuth2 endpoint with response sequences and request tracking. + */ +export class Endpoint { + private path: string; + private responses: MockResponse[]; + private requestCount: number = 0; + private cycleResponses: boolean; + + /** + * Create an endpoint with a path and response sequence(s). + * + * @param path - The endpoint path (e.g., '/oauth/authorize', '/oauth/token') + * @param responses - A single response or array of responses to return in sequence + * @param cycleResponses - Whether to cycle through responses or stay on the last one (default: false) + */ + constructor( + path: string, + responses: MockResponse | MockResponse[], + cycleResponses: boolean = false + ) { + this.path = path; + this.responses = Array.isArray(responses) ? responses : [responses]; + this.cycleResponses = cycleResponses; + + if (this.responses.length === 0) { + throw new Error('At least one response must be provided'); + } + } + + /** + * Handle a request to this endpoint and return the mock response. + * Increments the request counter before determining which response to return. + * + * @returns The mock response for this request + */ + handleRequest(): MockResponse { + const responseIndex = this.cycleResponses + ? this.requestCount % this.responses.length + : Math.min(this.requestCount, this.responses.length - 1); + + const response = this.responses[responseIndex]; + this.requestCount++; + + return response; + } + + /** + * Get the number of requests received for this endpoint. + * + * @returns The request count + */ + getRequestCount(): number { + return this.requestCount; + } + + /** + * Get the endpoint path. + * + * @returns The endpoint path + */ + getPath(): string { + return this.path; + } + + /** + * Reset the request count to zero. + */ + reset(): void { + this.requestCount = 0; + } +} + +/** + * Mock OAuth2 authorization server flow controller. + * Orchestrates multiple endpoints and their response sequences. + */ +export class Flow { + private endpoints: Map = new Map(); + private config: Required; + + constructor(config?: FlowConfig) { + this.config = { + cycleResponses: config?.cycleResponses ?? false, + }; + } + + /** + * Register an endpoint with one or more mock responses. + * Responses are returned in sequence as requests are received. + * + * @param endpoint - The endpoint path (e.g., '/oauth/authorize', '/oauth/token') + * @param responses - A single response or array of responses + */ + registerEndpoint( + endpoint: string, + responses: MockResponse | MockResponse[] + ): void { + this.endpoints.set( + endpoint, + new Endpoint(endpoint, responses, this.config.cycleResponses) + ); + } + + /** + * Handle a request to an endpoint and return the mock response. + * Increments the request counter for the endpoint. + * + * @param endpoint - The endpoint path + * @returns The mock response for this request + */ + handleRequest(endpoint: string): MockResponse { + const endpointHandler = this.endpoints.get(endpoint); + + if (!endpointHandler) { + throw new Error( + `Endpoint not registered: ${endpoint}. Call registerEndpoint() first.` + ); + } + + return endpointHandler.handleRequest(); + } + + /** + * Get the number of requests received for an endpoint. + * + * @param endpoint - The endpoint path + * @returns The request count + */ + getRequestCount(endpoint: string): number { + return this.endpoints.get(endpoint)?.getRequestCount() ?? 0; + } + + /** + * Get all request counts. + * + * @returns A record of endpoint paths to request counts + */ + getAllRequestCounts(): Record { + const result: Record = {}; + + for (const [path, endpoint] of this.endpoints) { + result[path] = endpoint.getRequestCount(); + } + + return result; + } + + /** + * Check if an endpoint is registered. + * + * @param endpoint - The endpoint path + * @returns True if the endpoint is registered + */ + isEndpointRegistered(endpoint: string): boolean { + return this.endpoints.has(endpoint); + } + + /** + * Get an endpoint handler by path. + * + * @param endpoint - The endpoint path + * @returns The Endpoint instance, or undefined if not registered + */ + getEndpoint(endpoint: string): Endpoint | undefined { + return this.endpoints.get(endpoint); + } + + /** + * Reset the request count for an endpoint. + * + * @param endpoint - The endpoint path to reset, or undefined to reset all + */ + reset(endpoint?: string): void { + if (endpoint === undefined) { + for (const ep of this.endpoints.values()) { + ep.reset(); + } + } else { + this.endpoints.get(endpoint)?.reset(); + } + } + + /** + * Clear all registered endpoints and reset request counts. + */ + clear(): void { + this.endpoints.clear(); + } +} diff --git a/packages/mock-auth-server/index.ts b/packages/mock-auth-server/index.ts new file mode 100644 index 00000000..6ab33b74 --- /dev/null +++ b/packages/mock-auth-server/index.ts @@ -0,0 +1,92 @@ +import express, { type Request, type Response } from 'express'; +import cors from 'cors'; +import { shortID } from '@okta/auth-foundation'; +import path from 'node:path'; +import { generateKeyPair } from './crypto.ts'; +import { createTokenResponseMock } from './mocks/token.ts'; + + +const keyPair = await generateKeyPair(); +console.log(keyPair) + +const app = express(); +app.use(express.json()); // for parsing application/json +app.use(express.urlencoded({ extended: true })); + +function getHostUrl (req: Request) { + return path.join(`${req.protocol}://${req.host}`, '/'); +} + +const pending: Record = {}; + +app.use(cors()); + +app.get('/create-flow', (req: Request, res: Response) => { + const params = req.query; + + +}); + +const authServer = express.Router(); + +authServer.get('/.well-known/openid-configuration', (req: Request, res: Response) => { + const baseUrl = getHostUrl(req); + res.json({ + issuer: path.join(baseUrl, '/'), + authorization_endpoint: path.join(baseUrl, '/oauth2/authorize'), + token_endpoint: path.join(baseUrl, '/oauth2/token'), + jwks_uri: path.join(baseUrl, '/oauth2/keys'), + id_token_signing_alg_values_supported: [ 'RS256' ] + }); +}); + +authServer.get('/authorize', (req: Request, res: Response) => { + const { state, redirect_uri, client_id, scope, nonce } = req.query as Record; + if (!state || !redirect_uri) { + return; + } + + const code = shortID(); + + pending[code] = { + params: { code, state, redirect_uri, client_id, scope, nonce } + }; + + const url = new URL(redirect_uri); + url.searchParams.append('state', state); + url.searchParams.append('code', code); + + res.redirect(url.href); +}); + +authServer.get('/keys', (req: Request, res: Response) => { + res.json({ keys: [ keyPair.publicKeyJWK ] }); +}); + +authServer.post('/token', async (req: Request, res: Response) => { + const { code, grant_type } = req.body; + if (!code || !grant_type) { + return; + } + + if (grant_type === 'authorization_code') { + const issuer = getHostUrl(req); + const transaction = pending[code]; + const { client_id, scope, nonce } = transaction.params; + + const response = await createTokenResponseMock( + { issuer, clientId: client_id, scope, nonce }, + keyPair, + !!req.header('dpop') + ); + + res.json(response); + } + else if (grant_type === 'foo') { + + } +}); + +app.use('/oauth2', authServer); + +app.listen(3030); diff --git a/packages/mock-auth-server/mocks/token.ts b/packages/mock-auth-server/mocks/token.ts new file mode 100644 index 00000000..029beba7 --- /dev/null +++ b/packages/mock-auth-server/mocks/token.ts @@ -0,0 +1,81 @@ +import { JWT, shortID } from '@okta/auth-foundation'; +import { buf, b64u } from '@okta/auth-foundation/internal'; + + +export interface TokenResponse { + access_token: string; + id_token: string; + token_type: string; + expires_in: number; + scope: string; + refresh_token?: string; +} + +export type MockIDTokenParams = { + issuer: string; + clientId: string; + scope: string; + nonce: string; + additionalClaims?: Record; +}; + +export type SigningKey = { + privateKey: CryptoKey; + keyId: string; +}; + +export async function createTokenResponseMock ( + idParams: MockIDTokenParams, + signingKey: SigningKey, + isDPoP: boolean = false +): Promise { + + const now = Math.floor(Date.now() / 1000); + const expiresIn = 3600; // 1 hour + + // Default claims for id_token + const claims: Record = { + iss: idParams.issuer, + sub: 'user123', + aud: idParams.clientId, + iat: now, + exp: now + expiresIn, + nonce: idParams.nonce, + auth_time: now, + ...(idParams.additionalClaims ?? {}), + }; + + // Create JWT header + const header = { + alg: 'RS256', + kid: signingKey.keyId + }; + + const accessToken = Buffer.from( + JSON.stringify({ + sub: claims.sub, + iat: now, + exp: now + expiresIn, + }) + ).toString('base64'); + + const intArr = new Uint8Array(await crypto.subtle.digest('SHA-256', buf(accessToken))); + const atHash = b64u(intArr.slice(0, intArr.length / 2)); + claims.at_hash = atHash; + + const idToken = await JWT.write(header, claims, signingKey.privateKey); + + const response: TokenResponse = { + access_token: accessToken, + id_token: idToken, + token_type: isDPoP ? 'DPoP' : 'Bearer', + expires_in: expiresIn, + scope: idParams.scope + }; + + if (idParams.scope.includes('offline_access')) { + response.refresh_token = shortID(); + } + + return response; +} \ No newline at end of file diff --git a/packages/mock-auth-server/package.json b/packages/mock-auth-server/package.json new file mode 100644 index 00000000..6634ea75 --- /dev/null +++ b/packages/mock-auth-server/package.json @@ -0,0 +1,64 @@ +{ + "name": "@okta/mock-auth-server", + "version": "0.7.0", + "type": "module", + "main": "dist/esm/index.js", + "module": "dist/esm/index.js", + "types": "dist/types/index.d.ts", + "license": "Apache-2.0", + "private": true, + "engines": { + "node": ">=20.11.0" + }, + "files": [ + "./LICENSE", + "./dist", + "*.md", + "package.json" + ], + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "lint": "eslint --ext .js,.ts,.jsx .", + "build": "yarn build:esm && yarn build:types", + "build:watch": "rollup -c --watch & tsc --watch", + "build:esm": "rollup -c", + "build:types": "tsc", + "test": "yarn test:unit", + "test:unit": "jest", + "test:watch": "jest --watchAll", + "test:browser": "jest --config jest.browser.config.js", + "test:node": "jest --config jest.node.config.js" + }, + "dependencies": { + "cors": "^2.8.6", + "express": "^5.2.1" + }, + "devDependencies": { + "@repo/eslint-config": "*", + "@repo/rollup-config": "*", + "@repo/typescript-config": "*", + "@types/express": "^5.0.6", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "rollup": "^4.52.4", + "typescript": "^5.9.2" + }, + "devEngines": { + "runtime": { + "name": "node", + "version": ">=22.13.1", + "onFail": "warn" + }, + "packageManager": { + "name": "yarn", + "version": ">=1.19.0", + "onFail": "warn" + } + } +} diff --git a/packages/mock-auth-server/tsconfig.json b/packages/mock-auth-server/tsconfig.json new file mode 100644 index 00000000..6161d3a6 --- /dev/null +++ b/packages/mock-auth-server/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "rootDir": "./src", + "baseUrl": "./", + "outDir": "./dist/types", + "esModuleInterop": true, + "skipLibCheck": true + }, + "lib": [ + "dom" + ], + "files": [ + "package.json" + ], + "include": [ + "./src/**/*.js", + "./src/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/mock-auth-server/types.ts b/packages/mock-auth-server/types.ts new file mode 100644 index 00000000..e69de29b diff --git a/yarn.lock b/yarn.lock index 82e00f4c..bd92f2a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -168,17 +168,6 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@asamuzakjp/css-color@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz#cc42f5b85c593f79f1fa4f25d2b9b321e61d1794" - integrity sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw== - dependencies: - "@csstools/css-calc" "^2.1.3" - "@csstools/css-color-parser" "^3.0.9" - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - lru-cache "^10.4.3" - "@babel/code-frame@7.10.4", "@babel/code-frame@~7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -1948,34 +1937,6 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@csstools/color-helpers@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" - integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== - -"@csstools/css-calc@^2.1.3", "@csstools/css-calc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.4.tgz#8473f63e2fcd6e459838dd412401d5948f224c65" - integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== - -"@csstools/css-color-parser@^3.0.9": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" - integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== - dependencies: - "@csstools/color-helpers" "^5.1.0" - "@csstools/css-calc" "^2.1.4" - -"@csstools/css-parser-algorithms@^3.0.4": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" - integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== - -"@csstools/css-tokenizer@^3.0.3": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" - integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== - "@cush/relative@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@cush/relative/-/relative-1.0.0.tgz#8cd1769bf9bde3bb27dac356b1bc94af40f6cc16" @@ -4390,6 +4351,11 @@ dependencies: defer-to-connect "^2.0.1" +"@tootallnate/once@2": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.1.tgz#35adc6222e3662fa2222ce123b961476a746b9ea" + integrity sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ== + "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" @@ -4455,6 +4421,21 @@ dependencies: "@babel/types" "^7.20.7" +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + "@types/eslint@7.28.1": version "7.28.1" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.1.tgz#50b07747f1f84c2ba8cd394cf0fe0ba07afce320" @@ -4478,6 +4459,25 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== +"@types/express-serve-static-core@^5.0.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz#19afe821c79f18d05946892bbd5b924b96c8a4f6" + integrity sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.6": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -4502,6 +4502,11 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -4597,6 +4602,16 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== +"@types/qs@*": + version "6.15.1" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.15.1.tgz#8606884272c63f0db96986bd3548650d8a9388bf" + integrity sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + "@types/react-dom@18.3.0": version "18.3.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" @@ -4634,6 +4649,21 @@ dependencies: csstype "^3.0.2" +"@types/send@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -5354,6 +5384,11 @@ js-yaml "^3.10.0" tslib "^2.4.0" +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -5377,11 +5412,26 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.0.2: + version "8.3.5" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== + dependencies: + acorn "^8.11.0" + acorn-walk@^8.1.1: version "8.3.2" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" @@ -5392,6 +5442,11 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1: + version "8.17.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.17.0.tgz#1785adb84faf8d8add10369b93826fc2bd08f1fe" + integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg== + acorn@^8.14.0: version "8.14.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" @@ -5402,6 +5457,13 @@ acorn@^8.4.1, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" @@ -6076,6 +6138,21 @@ body-parser@^2.2.0: raw-body "^3.0.0" type-is "^2.0.0" +body-parser@^2.2.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437" + integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw== + dependencies: + bytes "^3.1.2" + content-type "^2.0.0" + debug "^4.4.3" + http-errors "^2.0.1" + iconv-lite "^0.7.2" + on-finished "^2.4.1" + qs "^6.15.2" + raw-body "^3.0.2" + type-is "^2.1.0" + body-parser@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" @@ -6714,6 +6791,11 @@ content-type@^1.0.5: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== +content-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-2.0.0.tgz#2fb3ede69dffa0af78ca7c4ce7589680638b56df" + integrity sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -6773,6 +6855,14 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.6: + version "2.8.6" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" + integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig@^8.1.3: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" @@ -6873,13 +6963,22 @@ css-value@^0.0.1: resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea" integrity sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q== -cssstyle@^4.2.1: - version "4.6.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.6.0.tgz#ea18007024e3167f4f105315f3ec2d982bf48ed9" - integrity sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: - "@asamuzakjp/css-color" "^3.2.0" - rrweb-cssom "^0.8.0" + cssom "~0.3.6" csstype@^3.0.2, csstype@^3.1.3: version "3.1.3" @@ -6901,13 +7000,14 @@ data-uri-to-buffer@^6.0.2: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b" integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== -data-urls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" - integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== dependencies: - whatwg-mimetype "^4.0.0" - whatwg-url "^14.0.0" + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" data-view-buffer@^1.0.1: version "1.0.1" @@ -7018,7 +7118,7 @@ decamelize@^6.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-6.0.0.tgz#8cad4d916fde5c41a264a43d0ecc56fe3d31749e" integrity sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA== -decimal.js@^10.5.0: +decimal.js@^10.4.2: version "10.6.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== @@ -7185,6 +7285,13 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -7699,7 +7806,7 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== -escodegen@^2.1.0: +escodegen@^2.0.0, escodegen@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== @@ -8457,6 +8564,40 @@ express@^5.1.0: type-is "^2.0.1" vary "^1.1.2" +express@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + external-editor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -9356,12 +9497,12 @@ hosted-git-info@^7.0.0: dependencies: lru-cache "^10.0.1" -html-encoding-sniffer@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" - integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== dependencies: - whatwg-encoding "^3.1.1" + whatwg-encoding "^2.0.0" html-escaper@^2.0.0: version "2.0.2" @@ -9389,7 +9530,7 @@ http-errors@2.0.0, http-errors@^2.0.0: statuses "2.0.1" toidentifier "1.0.1" -http-errors@~2.0.1: +http-errors@^2.0.1, http-errors@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== @@ -9400,6 +9541,15 @@ http-errors@~2.0.1: statuses "~2.0.2" toidentifier "~1.0.1" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" @@ -9416,6 +9566,14 @@ http2-wrapper@^2.1.10: quick-lru "^5.1.1" resolve-alpn "^1.2.0" +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.2, https-proxy-agent@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" @@ -9424,7 +9582,7 @@ https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.2, https-proxy-agent@^7.0.4: agent-base "^7.0.2" debug "4" -https-proxy-agent@^7.0.5, https-proxy-agent@^7.0.6: +https-proxy-agent@^7.0.5: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== @@ -9468,6 +9626,13 @@ iconv-lite@^0.7.0, iconv-lite@~0.7.0: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.3.tgz#84ee12f963e7de50bc01a13e160a078b3b0f415f" + integrity sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -10563,31 +10728,37 @@ jsc-safe-url@^0.2.2, jsc-safe-url@^0.2.4: resolved "https://registry.yarnpkg.com/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz#141c14fbb43791e88d5dc64e85a374575a83477a" integrity sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q== -jsdom@^20.0.0, jsdom@^26.1.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-26.1.0.tgz#ab5f1c1cafc04bd878725490974ea5e8bf0c72b3" - integrity sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg== - dependencies: - cssstyle "^4.2.1" - data-urls "^5.0.0" - decimal.js "^10.5.0" - html-encoding-sniffer "^4.0.0" - http-proxy-agent "^7.0.2" - https-proxy-agent "^7.0.6" +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== + dependencies: + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.16" - parse5 "^7.2.1" - rrweb-cssom "^0.8.0" + nwsapi "^2.2.2" + parse5 "^7.1.1" saxes "^6.0.0" symbol-tree "^3.2.4" - tough-cookie "^5.1.1" - w3c-xmlserializer "^5.0.0" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" webidl-conversions "^7.0.0" - whatwg-encoding "^3.1.1" - whatwg-mimetype "^4.0.0" - whatwg-url "^14.1.1" - ws "^8.18.0" - xml-name-validator "^5.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" jsesc@^2.5.1: version "2.5.2" @@ -11037,11 +11208,6 @@ lru-cache@^10.0.1, lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== -lru-cache@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - lru-cache@^11.0.0: version "11.2.7" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.7.tgz#9127402617f34cd6767b96daee98c28e74458d35" @@ -11996,7 +12162,7 @@ nullthrows@^1.1.1: resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== -nwsapi@^2.2.16: +nwsapi@^2.2.2: version "2.2.24" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.24.tgz#f8927043d4c9b516abdebe804a32c8d1f9484d1f" integrity sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A== @@ -12015,7 +12181,7 @@ ob1@0.83.5: dependencies: flow-enums-runtime "^0.0.6" -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -12030,7 +12196,7 @@ object-inspect@^1.12.0, object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== @@ -12386,7 +12552,7 @@ parse5@^7.0.0: dependencies: entities "^4.4.0" -parse5@^7.2.1: +parse5@^7.1.1: version "7.3.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== @@ -12746,6 +12912,13 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +psl@^1.1.33: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -12800,6 +12973,14 @@ qs@^6.14.1: dependencies: side-channel "^1.1.0" +qs@^6.15.2: + version "6.15.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b" + integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== + dependencies: + es-define-property "^1.0.1" + side-channel "^1.1.1" + query-selector-shadow-dom@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz#1c7b0058eff4881ac44f45d8f84ede32e9a2f349" @@ -12815,6 +12996,11 @@ query-string@^7.1.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -12857,7 +13043,7 @@ raw-body@^3.0.0: iconv-lite "0.6.3" unpipe "1.0.0" -raw-body@^3.0.1: +raw-body@^3.0.1, raw-body@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== @@ -13359,6 +13545,11 @@ requireg@^0.2.2: rc "~1.2.7" resolve "~1.7.1" +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" @@ -13602,11 +13793,6 @@ router@^2.2.0: parseurl "^1.3.3" path-to-regexp "^8.0.0" -rrweb-cssom@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz#3021d1b4352fbf3b614aaeed0bc0d5739abe0bc2" - integrity sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw== - run-async@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-3.0.0.tgz#42a432f6d76c689522058984384df28be379daad" @@ -13985,6 +14171,14 @@ side-channel-list@^1.0.0: es-errors "^1.3.0" object-inspect "^1.13.3" +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-map@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" @@ -14027,6 +14221,17 @@ side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" +side-channel@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -14735,18 +14940,6 @@ tinyglobby@^0.2.13: fdir "^6.4.4" picomatch "^4.0.2" -tldts-core@^6.1.86: - version "6.1.86" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" - integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== - -tldts@^6.1.32: - version "6.1.86" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.86.tgz#087e0555b31b9725ee48ca7e77edc56115cd82f7" - integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== - dependencies: - tldts-core "^6.1.86" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -14776,19 +14969,22 @@ toidentifier@1.0.1, toidentifier@~1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tough-cookie@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" - integrity sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== +tough-cookie@^4.1.2: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== dependencies: - tldts "^6.1.32" + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" -tr46@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" - integrity sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw== +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== dependencies: - punycode "^2.3.1" + punycode "^2.1.1" tr46@~0.0.3: version "0.0.3" @@ -14981,6 +15177,15 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" +type-is@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570" + integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA== + dependencies: + content-type "^2.0.0" + media-typer "^1.1.0" + mime-types "^3.0.0" + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" @@ -15252,6 +15457,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -15337,6 +15547,14 @@ uri-js@^4.2.2, uri-js@^4.4.1: dependencies: punycode "^2.1.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + use-callback-ref@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" @@ -15414,7 +15632,7 @@ validate-npm-package-name@^5.0.0: resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== -vary@^1.1.2, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== @@ -15542,12 +15760,12 @@ vue@^3.5.13: "@vue/server-renderer" "3.5.18" "@vue/shared" "3.5.18" -w3c-xmlserializer@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" - integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== dependencies: - xml-name-validator "^5.0.0" + xml-name-validator "^4.0.0" wait-on@^8.0.2: version "8.0.2" @@ -15656,10 +15874,10 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -whatwg-encoding@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" - integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: iconv-lite "0.6.3" @@ -15668,10 +15886,10 @@ whatwg-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== -whatwg-mimetype@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" - integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== whatwg-url-without-unicode@8.0.0-3: version "8.0.0-3" @@ -15682,12 +15900,12 @@ whatwg-url-without-unicode@8.0.0-3: punycode "^2.1.1" webidl-conversions "^5.0.0" -whatwg-url@^14.0.0, whatwg-url@^14.1.1: - version "14.2.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.2.0.tgz#4ee02d5d725155dae004f6ae95c73e7ef5d95663" - integrity sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw== +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== dependencies: - tr46 "^5.1.0" + tr46 "^3.0.0" webidl-conversions "^7.0.0" whatwg-url@^5.0.0: @@ -15873,16 +16091,16 @@ ws@^7, ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@^8.11.0: + version "8.21.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.0.tgz#012e413fc07429945121b0c153158c4343086951" + integrity sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g== + ws@^8.12.1: version "8.18.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== -ws@^8.18.0: - version "8.21.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.0.tgz#012e413fc07429945121b0c153158c4343086951" - integrity sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g== - ws@^8.8.0: version "8.17.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" @@ -15896,10 +16114,10 @@ xcode@^3.0.1: simple-plist "^1.1.0" uuid "^7.0.3" -xml-name-validator@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" - integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== xml2js@0.6.0: version "0.6.0" From 83b2b7b063e4bd1d597ef40357dd8f022c2f7c91 Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Fri, 10 Jul 2026 16:06:27 -0400 Subject: [PATCH 5/7] progress: working ios tests --- .../react-native-oidc/app/(tabs)/index.tsx | 5 +++ .../Helpers/OAuthHelper.swift | 6 +-- .../CredentialsScreenPageObject.swift | 8 ++-- .../PageObjects/LoginScreenPageObject.swift | 16 ++++---- .../PageObjects/TokenScreenPageObject.swift | 2 +- .../ReactNativeOIDCAppUITests.swift | 37 +++++++------------ packages/mock-auth-server/index.ts | 11 ++++-- 7 files changed, 42 insertions(+), 43 deletions(-) diff --git a/e2e/apps/react-native-oidc/app/(tabs)/index.tsx b/e2e/apps/react-native-oidc/app/(tabs)/index.tsx index 39134a25..e9e5ce38 100644 --- a/e2e/apps/react-native-oidc/app/(tabs)/index.tsx +++ b/e2e/apps/react-native-oidc/app/(tabs)/index.tsx @@ -69,6 +69,11 @@ export default function AuthScreen() { setLoading(true); setError(null); await Credential.clear(); + await checkAuth(); + } + catch (err) { + console.log('CLEAR FAILED', err); + throw error; } finally { setLoading(false); diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift index bd95f648..cd801b9a 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift @@ -57,7 +57,7 @@ class OAuthHelper { throw OAuthError.webViewNotAccessible } - // let webView = app.webViews.element + let webView = app.webViews.element // // Attempt to find and fill username field // // Note: Safari/system webviews may expose form elements through the accessibility tree @@ -81,7 +81,7 @@ class OAuthHelper { // } app.typeText(username) - app.typeText(XCUIKeyboardKey.enter) + app.typeText(XCUIKeyboardKey.enter.rawValue) Thread.sleep(forTimeInterval: 0.3) @@ -98,7 +98,7 @@ class OAuthHelper { // TODO: select password authenticator app.typeText(password) - app.typeText(XCUIKeyboardKey.enter) + app.typeText(XCUIKeyboardKey.enter.rawValue) // Attempt to submit form let submitButton = webView.buttons["Sign In"] diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift index e1df753d..69435b87 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/CredentialsScreenPageObject.swift @@ -16,13 +16,13 @@ class CredentialsScreenPageObject { var credentialCountText: XCUIElement { // Looks for text like "2 credentials stored" or "No credentials found" - let stored = app.staticTexts.element(containingText: "stored") - let notFound = app.staticTexts.element(containingText: "No credentials") + let stored = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'stored'")).firstMatch + let notFound = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'No credentials'")).firstMatch return stored.exists ? stored : notFound } var defaultBadge: XCUIElement { - return app.staticTexts["DEFAULT"] + return app.staticTexts.element(containingText: "DEFAULT") } var credentialsTableView: XCUIElement { @@ -77,7 +77,7 @@ class CredentialsScreenPageObject { /// Check if DEFAULT badge is visible /// - returns: True if DEFAULT badge exists and is displayed, false otherwise func isDefaultBadgeVisible() -> Bool { - return defaultBadge.exists && defaultBadge.isDisplayed + return defaultBadge.exists } /// Wait for DEFAULT badge to become visible diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift index 0cc77ae4..2f6c802e 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift @@ -89,15 +89,15 @@ class LoginScreenPageObject { // Start OAuth flow tapRequestToken() - // Wait for OAuth UI to appear - let oAuthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) - XCTAssertTrue(oAuthUIAppeared, "OAuth UI should appear") + // // Wait for OAuth UI to appear + // let oAuthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) + // XCTAssertTrue(oAuthUIAppeared, "OAuth UI should appear") - // Enter credentials and authorize - try oauthHelper.enterOAuthCredentials( - username: credentials.username, - password: credentials.password - ) + // // Enter credentials and authorize + // try oauthHelper.enterOAuthCredentials( + // username: credentials.username, + // password: credentials.password + // ) // Wait for OAuth to complete and app to return try oauthHelper.waitForOAuthCompletion(timeout: 10) diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift index 06a764e0..f3a5c489 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift @@ -31,7 +31,7 @@ class TokenScreenPageObject { /// Check if token details are displayed /// - returns: True if token information is visible, false otherwise func isTokenDisplayed() -> Bool { - return tokenDetailsView.exists && tokenDetailsView.isDisplayed + return tokenDetailsView.exists } /// Wait for token to be displayed diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift index dd60f808..58884479 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift @@ -71,6 +71,8 @@ final class ReactNativeOIDCAppUITests: XCTestCase { print("๐Ÿ” Verifying fresh app state...") // Verify fresh app state do { + try loginScreen.tapClear() + Thread.sleep(forTimeInterval: 0.5) try testHelpers.assertFreshAppState() print("โœ… Fresh app state verified") } catch { @@ -81,6 +83,8 @@ final class ReactNativeOIDCAppUITests: XCTestCase { print("โœ… setUp COMPLETE\n") } + // TODO: clear token on setup + override func tearDownWithError() throws { print("\n๐Ÿงน tearDown START") defer { print("โœ… tearDown COMPLETE\n") } @@ -125,6 +129,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { /// /// Note: Behavioral equivalent to Android's "Chrome Tab Closed Before Completion" test func testOAuthFlow_ASWebAuthSessionDismissedBeforeCompletion() throws { + throw XCTSkip("Mock Auth server returns 302. Won't have time to cancel") print("Starting: testOAuthFlow_ASWebAuthSessionDismissedBeforeCompletion") // Wait for app to launch @@ -171,6 +176,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { print("Starting: testOAuthFlow_TokenRevokeAfterLogin") // First, complete login + try testHelpers.navigateToTab(name: "Login") try loginScreen.performLogin( oauthHelper: oauthHelper, credentials: oauthCredentials @@ -219,28 +225,22 @@ final class ReactNativeOIDCAppUITests: XCTestCase { print("Starting: testOAuthFlow_RequestMultipleTokens") // First login - acquire 1st token + try testHelpers.navigateToTab(name: "Login") try loginScreen.performLogin( oauthHelper: oauthHelper, credentials: oauthCredentials ) // Wait between logins - Thread.sleep(forTimeInterval: 2) - - // Second login - acquire 2nd token - // Note: Assuming ephemeralSession=true in app config, no bound redirect - loginScreen.tapRequestToken() - let oauthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) - XCTAssertTrue(oauthUIAppeared, "OAuth UI should appear for 2nd login") + Thread.sleep(forTimeInterval: 0.5) - try oauthHelper.enterOAuthCredentials( - username: oauthCredentials.username, - password: oauthCredentials.password + try loginScreen.performLogin( + oauthHelper: oauthHelper, + credentials: oauthCredentials ) - try oauthHelper.waitForOAuthCompletion(timeout: 10) // Wait for state to settle - Thread.sleep(forTimeInterval: 2) + Thread.sleep(forTimeInterval: 0.5) // Verify still authenticated XCTAssertTrue( @@ -259,12 +259,7 @@ final class ReactNativeOIDCAppUITests: XCTestCase { twoCredentials, "Should show 2 credentials stored" ) - - XCTAssertTrue( - credentialsScreen.isDefaultBadgeVisible(), - "Should display DEFAULT badge for active credential" - ) - + // Navigate to Token tab to revoke default credential try testHelpers.navigateToTab(name: "Token") tokenScreen.tapRevokeToken() @@ -281,12 +276,6 @@ final class ReactNativeOIDCAppUITests: XCTestCase { "Should show 1 credential stored after revocation" ) - let defaultGone = credentialsScreen.waitForDefaultBadgeToDisappear(timeout: 3) - XCTAssertTrue( - defaultGone, - "DEFAULT badge should no longer be displayed" - ) - print("โœ“ Multiple token and credential management verified") } } diff --git a/packages/mock-auth-server/index.ts b/packages/mock-auth-server/index.ts index 6ab33b74..0eec47b8 100644 --- a/packages/mock-auth-server/index.ts +++ b/packages/mock-auth-server/index.ts @@ -32,10 +32,11 @@ const authServer = express.Router(); authServer.get('/.well-known/openid-configuration', (req: Request, res: Response) => { const baseUrl = getHostUrl(req); res.json({ - issuer: path.join(baseUrl, '/'), + issuer: path.join(baseUrl, '/oauth2'), authorization_endpoint: path.join(baseUrl, '/oauth2/authorize'), token_endpoint: path.join(baseUrl, '/oauth2/token'), jwks_uri: path.join(baseUrl, '/oauth2/keys'), + revocation_endpoint: path.join(baseUrl, '/oauth2/revoke'), id_token_signing_alg_values_supported: [ 'RS256' ] }); }); @@ -70,7 +71,7 @@ authServer.post('/token', async (req: Request, res: Response) => { } if (grant_type === 'authorization_code') { - const issuer = getHostUrl(req); + const issuer = path.join(getHostUrl(req), '/oauth2'); const transaction = pending[code]; const { client_id, scope, nonce } = transaction.params; @@ -83,10 +84,14 @@ authServer.post('/token', async (req: Request, res: Response) => { res.json(response); } else if (grant_type === 'foo') { - + } }); +authServer.post('/revoke', (req: Request, res: Response) => { + res.send(200); +}); + app.use('/oauth2', authServer); app.listen(3030); From 5d00a53fe460b7717332b8571e8eaa7a4e86612e Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Mon, 13 Jul 2026 13:26:01 -0400 Subject: [PATCH 6/7] progress: e2e ios tests --- .../Helpers/OAuthHelper.swift | 94 +--------- .../Helpers/TestHelpers.swift | 168 +----------------- .../CredentialsScreenPageObject.swift | 35 +--- .../PageObjects/LoginScreenPageObject.swift | 17 +- .../PageObjects/TokenScreenPageObject.swift | 21 +-- .../ReactNativeOIDCAppUITests.swift | 109 +++--------- 6 files changed, 44 insertions(+), 400 deletions(-) diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift index cd801b9a..4a83b1e8 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/OAuthHelper.swift @@ -15,7 +15,6 @@ class OAuthHelper { /// - parameter timeout: Maximum time to wait for OAuth UI in seconds /// - returns: True if OAuth UI appeared, false if timeout func waitForOAuthUI(timeout: TimeInterval = 5) -> Bool { - print("โณ [OAuthHelper] Waiting for OAuth UI (timeout: \(timeout)s)...") let deadline = Date().addingTimeInterval(timeout) var attempts = 0 let maxAttempts = Int(timeout * 10) @@ -39,77 +38,13 @@ class OAuthHelper { attempts += 1 } - print("โŒ [OAuthHelper] OAuth UI not found after \(attempts) attempts (\(timeout)s)") - print(" DEBUG: webviews.count = \(app.webViews.count)") - print(" DEBUG: staticTexts.count = \(app.staticTexts.count)") return app.webViews.element.exists } - /// Attempt to enter OAuth credentials in the ASWebAuthenticationSession - /// LIMITED FUNCTIONALITY: XCUITest has restricted access to ASWebAuthenticationSession webview. - /// This method attempts to interact with form elements if they are accessible. - /// - parameter username: Username to enter - /// - parameter password: Password to enter - /// - throws: OAuthError if interaction fails func enterOAuthCredentials(username: String, password: String) throws { - // Wait for webview to load - guard waitForOAuthUI(timeout: 8) else { - throw OAuthError.webViewNotAccessible - } - - let webView = app.webViews.element - - // // Attempt to find and fill username field - // // Note: Safari/system webviews may expose form elements through the accessibility tree - // let usernameField = webView.textFields.element(boundBy: 0) - // if usernameField.exists { - // usernameField.tap() - // Thread.sleep(forTimeInterval: 0.2) - // usernameField.typeText(username) - // Thread.sleep(forTimeInterval: 0.3) - // } else { - // // If direct field access fails, attempt keyboard input - // // This assumes the field is already focused - // let remoteDismiss = app.keys["Delete"] - // if remoteDismiss.exists { - // // Attempt to clear any existing text - // for _ in 0..<20 { - // remoteDismiss.press(forDuration: 0.5) - // } - // } - // app.typeText(username) - // } - - app.typeText(username) - app.typeText(XCUIKeyboardKey.enter.rawValue) - - Thread.sleep(forTimeInterval: 0.3) - - // let passwordField = webView.secureTextFields.element(boundBy: 0) - // if passwordField.exists { - // passwordField.tap() - // Thread.sleep(forTimeInterval: 0.2) - // passwordField.typeText(password) - // Thread.sleep(forTimeInterval: 0.3) - // } else { - // app.typeText(password) - // } - - // TODO: select password authenticator - - app.typeText(password) - app.typeText(XCUIKeyboardKey.enter.rawValue) - - // Attempt to submit form - let submitButton = webView.buttons["Sign In"] - if submitButton.exists { - submitButton.tap() - } else { - // Try pressing Enter as fallback - app.typeText("\n") - } - - Thread.sleep(forTimeInterval: 0.5) + throw NSError(domain: "OAuthHelper", code: 1, + userInfo: [NSLocalizedDescriptionKey: "This method is not implemented. XCUITest cannot interact with `ASWebAuthenticationSession` views"] + ) } /// Wait for OAuth flow to complete and app to return to foreground @@ -127,11 +62,7 @@ class OAuthHelper { // Check if app is back in foreground (no longer showing OAuth sheet) let webviewGone = !app.webViews.element.exists let appInForeground = app.staticTexts["Authentication"].exists || app.buttons["requestTokenButton"].exists - - if iterations % 20 == 0 { - print(" [OAuthHelper iteration \(iterations)] webviewGone=\(webviewGone), appInForeground=\(appInForeground)") - } - + if webviewGone && appInForeground { authStatusFound = true print("โœ… [OAuthHelper] OAuth completion detected") @@ -178,23 +109,6 @@ class OAuthHelper { XCTAssertTrue(webviewGone, "OAuth sheet should be dismissed") } - /// Wait for app to transition from authenticated to not authenticated state - /// Used when testing logout/revocation flows - /// - parameter timeout: Maximum time to wait in seconds - func waitForUnauthenticatedState(timeout: TimeInterval = 5) throws { - let deadline = Date().addingTimeInterval(timeout) - - while Date() < deadline { - let notAuthElement = app.staticTexts.element(containingText: "โŒ Not Authenticated") - if notAuthElement.exists { - return - } - Thread.sleep(forTimeInterval: 0.2) - } - - throw OAuthError.stateTransitionTimeout - } - /// Wait for app to transition to authenticated state /// - parameter timeout: Maximum time to wait in seconds func waitForAuthenticatedState(timeout: TimeInterval = 5) throws { diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift index 553a51e3..dbed19a5 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/Helpers/TestHelpers.swift @@ -15,29 +15,13 @@ class TestHelpers { /// - returns: Tuple of (username, password) /// - throws: XCTestError if credentials not found static func loadOAuthCredentials() throws -> (username: String, password: String) { - print("๐Ÿ”‘ [TestHelpers] Starting credential load...") - var username: String? var password: String? - // First, try to read from testenv file (mirrors Android build.gradle approach) - do { - if let testenvCredentials = try? loadCredentialsFromTestenv() { - print("โœ… [TestHelpers] Successfully loaded from testenv file") - return testenvCredentials - } - } catch { - print("โš ๏ธ [TestHelpers] testenv load failed: \(error)") - } - - // Fallback to environment variables - print("๐Ÿ” [TestHelpers] Checking environment variables...") - username = ProcessInfo.processInfo.environment["USERNAME"] - password = ProcessInfo.processInfo.environment["PASSWORD"] - - print(" USERNAME from env: \(username?.isEmpty == false ? "***" : "(not set)")") - print(" PASSWORD from env: \(password?.isEmpty == false ? "***" : "(not set)")") - + // FUTURE: load from testenv file (and remove nil-coalesced defaults) + username = ProcessInfo.processInfo.environment["USERNAME"] ?? "foo" + password = ProcessInfo.processInfo.environment["PASSWORD"] ?? "bar" + guard let username = username, !username.isEmpty else { throw NSError(domain: "TestHelpers", code: 1, userInfo: [NSLocalizedDescriptionKey: "USERNAME not found in testenv file or environment variable"]) @@ -46,119 +30,16 @@ class TestHelpers { throw NSError(domain: "TestHelpers", code: 2, userInfo: [NSLocalizedDescriptionKey: "PASSWORD not found in testenv file or environment variable"]) } - - print("โœ… [TestHelpers] Loaded from environment variables") - return (username, password) - } - - /// Load credentials from testenv file - /// Searches for testenv file in known workspace locations - /// - returns: Tuple of (username, password) if found - /// - throws: Error if file not found or credentials missing - private static func loadCredentialsFromTestenv() throws -> (username: String, password: String) { - let fileManager = FileManager.default - - // Try common monorepo root locations - let commonPaths = [ - // CI environment variable (can be set by build system) - ProcessInfo.processInfo.environment["TESTENV_PATH"], - // Typical local dev setup - absolute path - "/Users/jaredperreault/Code/devex/client-js/testenv", - // Try from current working directory - (FileManager.default.currentDirectoryPath as NSString).appendingPathComponent("testenv"), - ].compactMap { $0 } - - print("๐Ÿ” [TestHelpers] Searching for testenv in \(commonPaths.count) locations...") - - for path in commonPaths { - print("๐Ÿ” [TestHelpers] Checking: \(path)") - if fileManager.fileExists(atPath: path) { - print("โœ… [TestHelpers] Found testenv at: \(path)") - return try parseTestenvFile(at: path) - } - } - - print("โŒ [TestHelpers] testenv file not found in any location") - throw NSError(domain: "TestEnv", code: 1, - userInfo: [NSLocalizedDescriptionKey: "testenv file not found"]) - } - - /// Parse testenv file and extract USERNAME and PASSWORD - /// - parameter path: Path to testenv file - /// - returns: Tuple of (username, password) - /// - throws: Error if credentials not found - private static func parseTestenvFile(at path: String) throws -> (username: String, password: String) { - let content = try String(contentsOfFile: path, encoding: .utf8) - print("๐Ÿ“„ [TestHelpers] testenv file content:\n\(content)") - - var username: String? - var password: String? - - let lines = content.components(separatedBy: .newlines) - print("๐Ÿ“„ [TestHelpers] Parsing \(lines.count) lines from testenv") - - for line in lines { - let trimmed = line.trimmingCharacters(in: .whitespaces) - - // Skip empty lines and comments - if trimmed.isEmpty || trimmed.starts(with: "#") { - continue - } - - // Parse KEY=VALUE format - let components = trimmed.components(separatedBy: "=") - guard components.count == 2 else { continue } - - let key = components[0].trimmingCharacters(in: .whitespaces) - var value = components[1].trimmingCharacters(in: .whitespaces) - - // Remove surrounding quotes if present - if value.starts(with: "\"") && value.hasSuffix("\"") { - value = String(value.dropFirst().dropLast()) - } - - print(" โ†’ \(key) = \(value.isEmpty ? "(empty)" : "***")") - - if key == "USERNAME" { - username = value - } else if key == "PASSWORD" { - password = value - } - } - - guard let username = username, !username.isEmpty else { - print("โŒ [TestHelpers] USERNAME not found or empty in testenv") - throw NSError(domain: "TestEnv", code: 2, - userInfo: [NSLocalizedDescriptionKey: "USERNAME not found in testenv file"]) - } - guard let password = password, !password.isEmpty else { - print("โŒ [TestHelpers] PASSWORD not found or empty in testenv") - throw NSError(domain: "TestEnv", code: 3, - userInfo: [NSLocalizedDescriptionKey: "PASSWORD not found in testenv file"]) - } - - print("โœ… [TestHelpers] Loaded credentials from testenv file") + return (username, password) } - + // MARK: - App State Assertions - - /// Verify app launched successfully - func verifyAppLaunched(timeout: TimeInterval = 10) throws { - let authTab = app.buttons["loginTab"] - let exists = authTab.waitForExistence(timeout: timeout) - XCTAssertTrue(exists, "App should launch and show authentication tab") - } - + /// Verify fresh app state (not authenticated, no credentials) func assertFreshAppState() throws { - print(" โณ Checking authentication status...") try verifyAuthenticationStatus(expected: false) - print(" โœ… Auth status verified") - - print(" โณ Checking credentials count...") try verifyCredentialsCount(expected: 0) - print(" โœ… Credentials count verified") } /// Verify authentication status: either "โœ… Authenticated" or "โŒ Not Authenticated" @@ -167,9 +48,6 @@ class TestHelpers { let expectedText = expected ? "โœ… Authenticated" : "โŒ Not Authenticated" let statusElement = app.staticTexts.element(containingText: expectedText) - print(" โ†’ Looking for auth status: '\(expectedText)'...") - print(" โ†’ Element exists: \(statusElement.exists)") - XCTestWait.waitForElement( statusElement, timeout: 5, @@ -179,7 +57,6 @@ class TestHelpers { statusElement.exists, "Expected authentication status: \(expectedText)" ) - print(" โœ… Auth status correct: '\(expectedText)'") } /// Verify number of stored credentials @@ -193,12 +70,9 @@ class TestHelpers { } let countElement = app.staticTexts.element(containingText: countText) - print(" โ†’ Navigating to Credentials tab...") // Navigate to Credentials tab first try navigateToTab(name: "Creds") - print(" โœ… On Credentials tab") - print(" โ†’ Waiting for: '\(countText)'...") XCTestWait.waitForElement( countElement, timeout: 5, @@ -208,7 +82,6 @@ class TestHelpers { countElement.exists, "Expected to see: \(countText)" ) - print(" โœ… Found: '\(countText)'") } // MARK: - App Navigation @@ -227,19 +100,16 @@ class TestHelpers { userInfo: [NSLocalizedDescriptionKey: "Unknown tab: \(name)"]) } - print(" [navigating to '\(name)' tab]") + let tabButton = app.buttons[config.contentDesc] - print(" โ†’ Tab button exists: \(tabButton.exists)") XCTAssertTrue( tabButton.exists, "Tab button for \(name) should exist" ) - - print(" โ†’ Tapping tab...") + tabButton.tapSafely() Thread.sleep(forTimeInterval: 0.3) - print(" โ†’ Waiting for tab title '\(config.title)'...") // Wait for tab content to load let titleElement = app.staticTexts.element(containingText: config.title) XCTestWait.waitForElement( @@ -247,7 +117,6 @@ class TestHelpers { timeout: 3, message: "Should navigate to \(name) tab" ) - print(" โœ… Tab '\(name)' loaded") } // MARK: - Action Helpers @@ -272,23 +141,4 @@ class TestHelpers { XCTAssertTrue(button.exists, "Clear button should exist") button.tapSafely() } - - /// Scroll to element if needed (useful for buttons at bottom of screen) - func scrollToElement(_ element: XCUIElement, swipeCount: Int = 3) { - for _ in 0.. Bool { return defaultBadge.exists } - - /// Wait for DEFAULT badge to become visible - /// - parameter timeout: Maximum time to wait in seconds - /// - returns: True if badge becomes visible, false on timeout - func waitForDefaultBadge(timeout: TimeInterval = 3) -> Bool { - return XCTestWait.waitFor(timeout: timeout) { - return self.isDefaultBadgeVisible() - } - } - - /// Wait for DEFAULT badge to disappear - /// - parameter timeout: Maximum time to wait in seconds - /// - returns: True if badge disappears, false on timeout - func waitForDefaultBadgeToDisappear(timeout: TimeInterval = 3) -> Bool { - let deadline = Date().addingTimeInterval(timeout) - while Date() < deadline { - if !isDefaultBadgeVisible() { - return true - } - Thread.sleep(forTimeInterval: 0.1) - } - return !isDefaultBadgeVisible() - } } diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift index 2f6c802e..6332bb9e 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/LoginScreenPageObject.swift @@ -9,11 +9,7 @@ class LoginScreenPageObject { } // MARK: - Elements - - var authStatusElement: XCUIElement { - return app.staticTexts.element(containingText: "Authenticated") - } - + var requestTokenButton: XCUIElement { return app.buttons["requestTokenButton"] } @@ -25,11 +21,7 @@ class LoginScreenPageObject { var clearButton: XCUIElement { return app.buttons["clearButton"] } - - var loginTabButton: XCUIElement { - return app.buttons["loginTab"] - } - + // MARK: - State Verification /// Verify current authentication status @@ -89,6 +81,11 @@ class LoginScreenPageObject { // Start OAuth flow tapRequestToken() + /** + NOTE: Test suite is now designed against a mock Authorization Server. + This method no longer enters credentials, since `/authorize` requests + respond with a 302 + */ // // Wait for OAuth UI to appear // let oAuthUIAppeared = oauthHelper.waitForOAuthUI(timeout: 8) // XCTAssertTrue(oAuthUIAppeared, "OAuth UI should appear") diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift index f3a5c489..7c65074a 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/PageObjects/TokenScreenPageObject.swift @@ -9,11 +9,7 @@ class TokenScreenPageObject { } // MARK: - Elements - - var tokenTabButton: XCUIElement { - return app.buttons["tokenTab"] - } - + var revokeTokenButton: XCUIElement { return app.buttons["revokeTokenButton"] } @@ -21,11 +17,7 @@ class TokenScreenPageObject { var tokenDetailsView: XCUIElement { return app.staticTexts.element(containingText: "Token Details") } - - var tokenExpirationText: XCUIElement { - return app.staticTexts.element(containingText: "expiresAt") - } - + // MARK: - State Verification /// Check if token details are displayed @@ -34,15 +26,6 @@ class TokenScreenPageObject { return tokenDetailsView.exists } - /// Wait for token to be displayed - /// - parameter timeout: Maximum time to wait in seconds - /// - returns: True if token appears, false on timeout - func waitForTokenDisplay(timeout: TimeInterval = 3) -> Bool { - return XCTestWait.waitFor(timeout: timeout) { - return self.isTokenDisplayed() - } - } - /// Check if revoke button is accessible (visible and hittable) /// - returns: True if button is accessible, false otherwise func isRevokeButtonAccessible() -> Bool { diff --git a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift index 58884479..22136117 100644 --- a/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift +++ b/e2e/apps/react-native-oidc/ios/reporeactnativeoidcUITests/ReactNativeOIDCAppUITests.swift @@ -1,14 +1,13 @@ import XCTest /** - Hybrid E2E tests for OAuth authentication flows on iOS. - - These tests use XCUITest to interact with the React Native OIDC test app and - ASWebAuthenticationSession for OAuth provider interaction. - - Prerequisites: - - USERNAME and PASSWORD environment variables must be set (from testenv file) - - iOS simulator must have networking access to OAuth provider (Okta) + E2E tests to confirm the functionality of `@okta/react-native-platform`, like `TokenStorage` and `BrowserSession`. + + NOTE: The iOS `BrowserSession` API uses `ASWebAuthenticationSession` which cannot be controlled/tested by XCUITest + (XCUITest can only interact with the App process, while `ASWebAuthenticationSession` creates it's own). Therefore + these tests are written against a Mock Authorization Server. This server returns mock responses and does not require + entering any credentials into a UI, which eliminates the need for the tests to interact with the `ASWebAuthenticationSession` + process altogether */ final class ReactNativeOIDCAppUITests: XCTestCase { @@ -26,13 +25,6 @@ final class ReactNativeOIDCAppUITests: XCTestCase { // MARK: - Setup & Teardown override func setUpWithError() throws { - print("\n" + String(repeating: "=", count: 60)) - print("๐Ÿงช setUp START") - print(String(repeating: "=", count: 60)) - - // Disable automatic screenshot capture to speed up tests - continueAfterFailure = false - print("๐Ÿ“ฑ Initializing app...") // Initialize app app = XCUIApplication() @@ -53,25 +45,19 @@ final class ReactNativeOIDCAppUITests: XCTestCase { throw error } - app.launchEnvironment["XCODE_WAIT_FOR_IDLE_TIMEOUT"] = "5" - - print("๐Ÿš€ Launching app...") // Launch app - XCTest will wait for app to idle after launch app.launch() - print("๐Ÿ“ฒ App launched, waiting for UI elements...") // Wait for app to fully load - print("โณ Waiting for loginTab button (10s timeout)...") let authTab = app.buttons["loginTab"] let launched = authTab.waitForExistence(timeout: 10) - print(" โ†’ loginTab exists: \(authTab.exists), launched: \(launched)") + XCTAssertTrue(launched, "App should launch successfully") - print("โœ… App UI loaded") - + print("๐Ÿ” Verifying fresh app state...") // Verify fresh app state do { - try loginScreen.tapClear() + try loginScreen.tapClear() // clear any existing tokens Thread.sleep(forTimeInterval: 0.5) try testHelpers.assertFreshAppState() print("โœ… Fresh app state verified") @@ -79,58 +65,32 @@ final class ReactNativeOIDCAppUITests: XCTestCase { print("โŒ Fresh app state check failed: \(error)") throw error } - - print("โœ… setUp COMPLETE\n") } - - // TODO: clear token on setup override func tearDownWithError() throws { - print("\n๐Ÿงน tearDown START") - defer { print("โœ… tearDown COMPLETE\n") } - - print(" Cleaning up app...") - // Simply terminate without trying to interact with UI - // This avoids hanging if app is in bad state app.terminate() - print(" โ†’ App terminated") } // MARK: - Test Cases - /// Test Case 1: Complete OAuth Login with Valid Credentials - /// - /// Flow: - /// 1. Tap "Request Token" to initiate OAuth flow - /// 2. Wait for ASWebAuthenticationSession to appear - /// 3. Enter username and password in OAuth provider - /// 4. Authorize (approve) the request - /// 5. Verify app receives callback and shows authenticated state + /// Happy path single token login func testOAuthFlow_CompleteLoginWithValidCredentials() throws { - print("\n๐Ÿงช TEST #1: testOAuthFlow_CompleteLoginWithValidCredentials") - - print(" Performing login...") try testHelpers.navigateToTab(name: "Login") try loginScreen.performLogin( oauthHelper: oauthHelper, credentials: oauthCredentials ) - - print("โœ… Test #1 PASSED: Login successful, app authenticated\n") } - /// Test Case 2: ASWebAuthenticationSession Dismissed Before Completion - /// - /// Flow: - /// 1. Tap "Request Token" to initiate OAuth flow - /// 2. Wait for ASWebAuthenticationSession to appear - /// 3. Dismiss the OAuth sheet before completing authorization - /// 4. Verify app remains in not authenticated state - /// - /// Note: Behavioral equivalent to Android's "Chrome Tab Closed Before Completion" test + /// Confirms the `ASWebAuthenticationSession` window can be dismissed and the app recovers gracefully func testOAuthFlow_ASWebAuthSessionDismissedBeforeCompletion() throws { + /** + Since the current state of this test suite uses a mock Authorization Server, the `ASWebAuthenticationSession` + window only exists for a few moments (enough time for the /authorize 302 to occur). The window closes (due to + successful auth) before this test could dismiss the window. This test was passing against a live org before + the migration was made to use a mock server. Skipping this test for now + */ throw XCTSkip("Mock Auth server returns 302. Won't have time to cancel") - print("Starting: testOAuthFlow_ASWebAuthSessionDismissedBeforeCompletion") // Wait for app to launch Thread.sleep(forTimeInterval: 2) @@ -160,21 +120,10 @@ final class ReactNativeOIDCAppUITests: XCTestCase { loginScreen.verifyAuthStatus(expected: false), "App should remain not authenticated after OAuth dismissal" ) - - print("โœ“ OAuth dismissal handled correctly") } - /// Test Case 3: Token Revocation After Login - /// - /// Flow: - /// 1. Complete OAuth login (authenticated) - /// 2. Navigate to Token tab - /// 3. Tap "Revoke Token" button - /// 4. Verify credentials are removed - /// 5. Verify app shows not authenticated state + /// Happy path single token acquisition then revoke func testOAuthFlow_TokenRevokeAfterLogin() throws { - print("Starting: testOAuthFlow_TokenRevokeAfterLogin") - // First, complete login try testHelpers.navigateToTab(name: "Login") try loginScreen.performLogin( @@ -206,25 +155,11 @@ final class ReactNativeOIDCAppUITests: XCTestCase { loggedOut, "Should show not authenticated status after token revocation" ) - - print("โœ“ Token revocation successful") } - /// Test Case 4: Request Multiple Tokens and Credential Management - /// - /// Flow: - /// 1. Complete OAuth login (1st credential) - /// 2. Complete OAuth login again (2nd credential) - /// 3. Navigate to Credentials tab - /// 4. Verify "2 credentials stored" is displayed - /// 5. Navigate to Token tab - /// 6. Revoke the default credential - /// 7. Verify credentials count decreases to 1 - /// 8. Verify DEFAULT badge is no longer displayed + /// Requests multiple tokens then revokes one func testOAuthFlow_RequestMultipleTokens() throws { - print("Starting: testOAuthFlow_RequestMultipleTokens") - // First login - acquire 1st token try testHelpers.navigateToTab(name: "Login") try loginScreen.performLogin( oauthHelper: oauthHelper, @@ -239,10 +174,8 @@ final class ReactNativeOIDCAppUITests: XCTestCase { credentials: oauthCredentials ) - // Wait for state to settle Thread.sleep(forTimeInterval: 0.5) - // Verify still authenticated XCTAssertTrue( loginScreen.verifyAuthStatus(expected: true), "Should still be authenticated after 2nd login" @@ -275,7 +208,5 @@ final class ReactNativeOIDCAppUITests: XCTestCase { oneCredential, "Should show 1 credential stored after revocation" ) - - print("โœ“ Multiple token and credential management verified") } } From 487a861763aa4379bc480edab3bae3d25cc50244 Mon Sep 17 00:00:00 2001 From: Jared Perreault Date: Tue, 14 Jul 2026 10:33:57 -0400 Subject: [PATCH 7/7] fixes e2e app --- e2e/apps/react-native-oidc/app/(tabs)/index.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/e2e/apps/react-native-oidc/app/(tabs)/index.tsx b/e2e/apps/react-native-oidc/app/(tabs)/index.tsx index e9e5ce38..fc074726 100644 --- a/e2e/apps/react-native-oidc/app/(tabs)/index.tsx +++ b/e2e/apps/react-native-oidc/app/(tabs)/index.tsx @@ -71,10 +71,6 @@ export default function AuthScreen() { await Credential.clear(); await checkAuth(); } - catch (err) { - console.log('CLEAR FAILED', err); - throw error; - } finally { setLoading(false); }