diff --git a/FronteggRN.podspec b/FronteggRN.podspec
index 417bd66..5cc8c47 100644
--- a/FronteggRN.podspec
+++ b/FronteggRN.podspec
@@ -15,15 +15,16 @@ Pod::Spec.new do |s|
s.source = { :git => "https://github.com/frontegg/frontegg-react-native.git", :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
+ s.exclude_files = "ios/Package.swift"
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
+ # FronteggSwift via SPM — linked in post_integrate by ios/frontegg_spm.rb (see docs/setup.md).
+
if respond_to?(:install_modules_dependencies, true)
install_modules_dependencies(s)
- s.dependency "FronteggSwift", "1.3.10"
else
s.dependency "React-Core"
- s.dependency "FronteggSwift", "1.3.10"
# Don't install the dependencies when we run `pod install` in the old architecture.
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
diff --git a/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt b/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt
index 449ae18..1c6b4e2 100644
--- a/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt
+++ b/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt
@@ -14,6 +14,8 @@ import com.frontegg.android.AdminPortalActivity
import com.frontegg.android.fronteggAuth
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
class FronteggRNModule(val reactContext: ReactApplicationContext) :
@@ -158,6 +160,31 @@ class FronteggRNModule(val reactContext: ReactApplicationContext) :
}
}
+ @ReactMethod
+ fun isSteppedUp(maxAgeSeconds: Double, promise: Promise) {
+ val maxAge =
+ if (maxAgeSeconds < 0) null else maxAgeSeconds.toDuration(DurationUnit.SECONDS)
+ promise.resolve(auth.isSteppedUp(maxAge))
+ }
+
+ @ReactMethod
+ fun stepUp(maxAgeSeconds: Double, promise: Promise) {
+ val activity = currentActivity
+ if (activity == null) {
+ promise.reject("NO_ACTIVITY", "Current activity is null")
+ return
+ }
+ val maxAge =
+ if (maxAgeSeconds < 0) null else maxAgeSeconds.toDuration(DurationUnit.SECONDS)
+ auth.stepUp(activity, maxAge) { error ->
+ if (error != null) {
+ promise.reject("STEP_UP_ERROR", error.message, error)
+ } else {
+ promise.resolve(null)
+ }
+ }
+ }
+
@ReactMethod
fun requestAuthorize(refreshToken: String, deviceTokenCookie: String?, promise: Promise) {
try {
diff --git a/docs/setup.md b/docs/setup.md
index 978bd7e..b178ba7 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -4,6 +4,85 @@ This section guides you through configuring the Frontegg React Native SDK for bo
## Setup iOS Project
+### Integrate FronteggSwift via Swift Package Manager
+
+The iOS native SDK (`FronteggSwift`) is delivered through **Swift Package Manager (SPM)** only. The CocoaPods pod `FronteggSwift` is **not** used (previously pinned at `1.2.76`). React Native still uses CocoaPods for `FronteggRN` and other dependencies; `FronteggSwift` is linked into the `FronteggRN` pod target via SPM after `pod install`.
+
+**Requirements:** Xcode **15+**, iOS deployment target **14.0+**, CocoaPods.
+
+#### 1. Update `ios/Podfile`
+
+Inside your app target (the same block as `use_native_modules!`):
+
+```ruby
+target 'YourApp' do
+ # Required so the FronteggRN pod can import the FronteggSwift SPM product.
+ use_frameworks! :linkage => :static
+
+ config = use_native_modules!
+ # ... use_react_native! etc.
+```
+
+After `post_install` (do **not** put the SPM hook inside `post_install` — CocoaPods would overwrite the patched `Pods` project):
+
+```ruby
+ post_install do |installer|
+ react_native_post_install(installer, config[:reactNativePath], :mac_catalyst_enabled => false)
+ # ...your other post_install hooks...
+ end
+
+ post_integrate do |installer|
+ require_relative '../node_modules/@frontegg/react-native/ios/frontegg_spm'
+ FronteggSPM.link_frontegg_rn_pod(installer)
+ end
+end
+```
+
+Adjust the `require_relative` path if your `node_modules` layout differs (monorepo, etc.).
+
+#### 2. Install pods
+
+From the `ios` directory:
+
+```bash
+cd ios
+bundle exec pod install # recommended if you use a Gemfile
+```
+
+**React Native 0.72:** if `pod install` fails downloading `boost`, add the boost URL workaround at the top of your `Podfile` (see the [example `ios/Podfile`](https://github.com/frontegg/frontegg-react-native/blob/master/example/ios/Podfile)).
+
+#### 3. Build and run
+
+- Open **`YourApp.xcworkspace`** (not `.xcodeproj`).
+- For local development on a **simulator**, prefer:
+
+```bash
+cd ..
+yarn start # Metro — one terminal
+yarn ios --simulator "iPhone 17 Pro Max"
+```
+
+If a physical iPhone is connected, React Native may target the device and fail on code signing (`com.your.bundle`). Use `--simulator` or disconnect the device.
+
+The `frontegg_spm` helper pins **FronteggSwift 1.3.10** from `https://github.com/frontegg/frontegg-ios-swift.git`, links it to the **FronteggRN** CocoaPods target, and configures the app Xcode project so SPM resolves without duplicate symbol errors at link time.
+
+> **Embedded mode step-up:** `FronteggSwift` **1.3.10** routes `stepUp()` through the embedded `WKWebView` when `embeddedMode` is enabled (instead of `ASWebAuthenticationSession`), so MFA/step-up reuses the existing web session. Set `embeddedMode` in `Frontegg.plist` if you use the embedded login box.
+
+#### Step-up from JavaScript
+
+```ts
+import { isSteppedUp, stepUp } from '@frontegg/react-native';
+
+const maxAgeSeconds = 60;
+if (await isSteppedUp(maxAgeSeconds)) {
+ // already stepped up
+} else {
+ await stepUp(maxAgeSeconds);
+}
+```
+
+See the [example `HomeScreen`](https://github.com/frontegg/frontegg-react-native/blob/master/example/src/HomeScreen.tsx) for a **Sensitive action** button matching the native iOS SDK demo.
+
### Create Frontegg.plist
1. Add a new file named `Frontegg.plist` to your root project directory.
diff --git a/example/Gemfile.lock b/example/Gemfile.lock
index ec146ff..0e51744 100644
--- a/example/Gemfile.lock
+++ b/example/Gemfile.lock
@@ -49,6 +49,8 @@ GEM
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
+ cocoapods-spm (0.1.20)
+ xcodeproj (>= 1.23.0)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
@@ -92,6 +94,7 @@ PLATFORMS
DEPENDENCIES
cocoapods (~> 1.12)
+ cocoapods-spm (~> 0.1.20)
RUBY VERSION
ruby 2.6.10p210
diff --git a/example/README.md b/example/README.md
index 12470c3..e994026 100644
--- a/example/README.md
+++ b/example/README.md
@@ -1,74 +1,32 @@
-This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
+# Frontegg React Native — example app
-# Getting Started
+## Prerequisites
->**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
+- [React Native environment](https://reactnative.dev/docs/environment-setup) (Node, Xcode 15+, CocoaPods)
+- Configure `ios/Frontegg.plist` with your Frontegg `baseUrl` and `clientId` (see [Setup Guide](../docs/setup.md))
-## Step 1: Start the Metro Server
+## iOS (SPM + CocoaPods)
-First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native.
-
-To start Metro, run the following command from the _root_ of your React Native project:
+From the **example** directory:
```bash
-# using npm
-npm start
-
-# OR using Yarn
-yarn start
+yarn install
+cd ios && bundle exec pod install && cd ..
+yarn start # terminal 1
+yarn ios --simulator "iPhone 17 Pro Max" # terminal 2 — use an available simulator name
```
-## Step 2: Start your Application
+Open `ios/ReactNativeExample.xcworkspace` in Xcode if you build from the IDE.
-Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app:
+> Use `--simulator` when a physical iPhone is connected, otherwise `yarn ios` may try to deploy to the device and fail on provisioning.
-### For Android
+## Android
```bash
-# using npm
-npm run android
-
-# OR using Yarn
yarn android
```
-### For iOS
-
-```bash
-# using npm
-npm run ios
-
-# OR using Yarn
-yarn ios
-```
-
-If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
-
-This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively.
-
-## Step 3: Modifying your App
-
-Now that you have successfully run the app, let's modify it.
-
-1. Open `App.tsx` in your text editor of choice and edit some lines.
-2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes!
-
- For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes!
-
-## Congratulations! :tada:
-
-You've successfully run and modified your React Native App. :partying_face:
-
-### Now what?
-
-- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
-- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started).
-
-# Troubleshooting
-
-If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
-
-# Learn More
+## More
To learn more about React Native, take a look at the following resources:
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 5948679..f49d132 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -1,3 +1,18 @@
+# boostorg.jfrog.io often returns a bad tarball; use archives.boost.io (RN 0.72 workaround).
+boost_podspec_path = File.expand_path('../node_modules/react-native/third-party-podspecs/boost.podspec', __dir__)
+if File.exist?(boost_podspec_path)
+ boost_podspec = File.read(boost_podspec_path)
+ unless boost_podspec.include?('archives.boost.io')
+ File.write(
+ boost_podspec_path,
+ boost_podspec.gsub(
+ 'https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2',
+ 'https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2'
+ )
+ )
+ end
+end
+
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
@@ -26,6 +41,9 @@ if linkage != nil
end
target 'ReactNativeExample' do
+ # Required so FronteggRN (Swift) can import the FronteggSwift SPM product.
+ use_frameworks! :linkage => :static
+
config = use_native_modules!
# Flags change depending on the env values.
@@ -59,4 +77,9 @@ target 'ReactNativeExample' do
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
+
+ post_integrate do |installer|
+ require_relative '../../ios/frontegg_spm'
+ FronteggSPM.link_frontegg_rn_pod(installer)
+ end
end
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 6350afa..49ed929 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -10,12 +10,9 @@ PODS:
- React-jsi (= 0.72.1)
- ReactCommon/turbomodule/core (= 0.72.1)
- fmt (6.2.1)
- - FronteggRN (1.2.14):
- - FronteggSwift (= 1.2.76)
+ - FronteggRN (1.2.16):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- - FronteggSwift (1.2.76):
- - Sentry (~> 8.46.0)
- glog (0.3.5)
- hermes-engine (0.72.1):
- hermes-engine/Pre-built (= 0.72.1)
@@ -436,9 +433,6 @@ PODS:
- RCT-Folly (= 2021.07.22.00)
- React-Core
- React-RCTImage
- - Sentry (8.46.0):
- - Sentry/Core (= 8.46.0)
- - Sentry/Core (8.46.0)
- SocketRocket (0.6.1)
- Yoga (1.14.0)
@@ -491,9 +485,7 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- fmt
- - FronteggSwift
- libevent
- - Sentry
- SocketRocket
EXTERNAL SOURCES:
@@ -589,8 +581,7 @@ SPEC CHECKSUMS:
FBLazyVector: 55cd4593d570bd9e5e227488d637ce6a9581ce51
FBReactNativeSpec: 799b0e1a1561699cd0e424e24fe5624da38402f0
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
- FronteggRN: 38e704aa6bc3999f1251361e3bd17ff3ee4859b9
- FronteggSwift: df127087b79c5fc65240805136f36c7c28d4988d
+ FronteggRN: ce2450115a3c86ad7dbcd5b991fe1ff2bcab04a7
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 9df83855a0fd15ef8eb61694652bae636b0c466e
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
@@ -599,39 +590,38 @@ SPEC CHECKSUMS:
RCTTypeSafety: 75fa444becadf0ebfa0a456b8c64560c7c89c7df
React: 3e5b3962f27b7334eaf5517a35b3434503df35ad
React-callinvoker: c3a225610efe0caadac78da53b6fe78e53eb2b03
- React-Codegen: fa4e79f8d56b5aa5f91d45984d21c21b31e211c8
- React-Core: cbfc97c3c0061d4fdaca617357086ba1347c730e
- React-CoreModules: 0d18899272223a2a038e4c2313a7760ca6b280a7
- React-cxxreact: f78070b70980e9e19fb448fec9d49a9181952535
- React-debug: 8aa2bd54b0f0011049300ce3339b0e51254ef3b5
- React-hermes: c783d9dcf275ec48f97b164824dcdfdda480e6a6
- React-jsi: cc0d4c585001fca0979591587e00e6d2a4c13820
- React-jsiexecutor: a2b883b0e3fbc606a1e6e97a15059b71d6da1715
+ React-Codegen: 319a74f758104130092a5984431045056c32fbab
+ React-Core: 40003a89fd94da98b03c3fad8834871299485cfa
+ React-CoreModules: 1304c0710deb87054623ccc9c552e23a1fcce219
+ React-cxxreact: f82f0f1832606fabb9e8c9d61c4230704a3d2d2f
+ React-debug: 3e34ac6fb438f0e8c05a1678eedd6ebd8928245c
+ React-hermes: f076cb5f7351d6cc1600125bef3259ea880460fb
+ React-jsi: 9f381c8594161b2328b93cd3ba5d0bcfcd1e093a
+ React-jsiexecutor: 184eae1ecdedc7a083194bd9ff809c93f08fd34c
React-jsinspector: d0b5bfd1085599265f4212034321e829bdf83cc0
- React-logger: 70abe174614ac3f165e02ab8300fec50d2f9572d
- react-native-safe-area-context: 8745463257fac6150abd2281b02de640b3595ec7
- React-NativeModulesApple: 3f444b421d3c1e463414cb5aee385c4ffe6bae42
+ React-logger: b8103c9b04e707b50cdd2b1aeb382483900cbb37
+ react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
+ React-NativeModulesApple: bdfd86e972ea8103941faab9c7f64ea9be7e2be6
React-perflogger: 3d501f34c8d4b10cb75f348e43591765788525ad
React-RCTActionSheet: f5335572c979198c0c3daff67b07bd1ad8370c1d
- React-RCTAnimation: 5d0d31a4f9c49a70f93f32e4da098fb49b5ae0b3
- React-RCTAppDelegate: d285573b1d55da98149560f38df549293e4eb20d
- React-RCTBlob: f7777ca0bad9e6d98efe921003f9f3cca70730f1
- React-RCTImage: e15d22db53406401cdd1407ce51080a66a9c7ed4
- React-RCTLinking: 39815800ec79d6fb15e6329244d195ebeabf7541
- React-RCTNetwork: 2a6548e13d2577b112d4250ac5be74ae62e1e86b
- React-RCTSettings: a76aee44d5398144646be146c334b15c90ad9582
+ React-RCTAnimation: 897b1170a1e934dee9b7eb7d5582920dea72c1c8
+ React-RCTAppDelegate: f8dc4f198bb7a7fdc9ccd5a6e246936bbdacbe42
+ React-RCTBlob: b69213136de8eed26fdf112602e8aaf91923093c
+ React-RCTImage: 9f2303a18cec1f715a447dca5b82021643661e6b
+ React-RCTLinking: f88453408f33a99f36bb4f6e11278ffbb81aa45f
+ React-RCTNetwork: 5ef8d399ad389442404683d2572007ace0e26076
+ React-RCTSettings: 516d917b059c94f33f85f601974429de7c3d34ca
React-RCTText: afad390f3838f210c2bc9e1a19bb048003b2a771
- React-RCTVibration: 29bbaa5c57c02dc036d7e557584b492000b1d3e7
+ React-RCTVibration: 28cc9ac2c062b515f207a1743f0f27083d62acc5
React-rncore: 50966ce412d63bee9ffe5c98249857c23870a3c4
React-runtimeexecutor: d129f2b53d61298093d7c7d8ebfafa664f911ce6
- React-runtimescheduler: c8f959651fd874b93feeb32781b1e402a6801ec2
- React-utils: c9e15d684b491d6324b4139790306836f0e8b1d6
- ReactCommon: f7d2978a6f8e8552a4908f067e65d4cfda686138
- RNScreens: 67dd26e0c1e6df63f4ccaaf281c1ecdd88c995c3
- Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
+ React-runtimescheduler: 34e27d6db52587cf93e7d9d44d5ff2dc4948243a
+ React-utils: 4324c58d17bcbcb8c49fe7f8c14b4f61d5dd4a42
+ ReactCommon: e33f60179aae29423fcf4ea19fdedc038813ea3f
+ RNScreens: 835807a1f3c74c8ea0d0b9e089bf14333141f17f
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: 65286bb6a07edce5e4fe8c90774da977ae8fc009
-PODFILE CHECKSUM: 010992354d93ba29cfe4cff5fab47f9b2920a2da
+PODFILE CHECKSUM: fc09f9972b0d9b6a48315e1f02b457d29013ffd5
-COCOAPODS: 1.16.2
+COCOAPODS: 1.14.3
diff --git a/example/ios/ReactNativeExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeExample.xcodeproj/project.pbxproj
index 6b25ee4..d7503f6 100644
--- a/example/ios/ReactNativeExample.xcodeproj/project.pbxproj
+++ b/example/ios/ReactNativeExample.xcodeproj/project.pbxproj
@@ -14,8 +14,8 @@
41872B5B2AFBC5CF0014FB13 /* FronteggSwiftAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41872B5A2AFBC5CF0014FB13 /* FronteggSwiftAdapter.swift */; };
41E63BA62A52DA4E00BF6DE4 /* Frontegg.plist in Resources */ = {isa = PBXBuildFile; fileRef = 41E63BA52A52DA4E00BF6DE4 /* Frontegg.plist */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
- 9FAF02B86F1794E4CE2B25FC /* libPods-ReactNativeExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 48D7E04979345AE7E4960E7D /* libPods-ReactNativeExample.a */; };
- B24DB32AEF180101559169F0 /* libPods-ReactNativeExample-ReactNativeExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A2FEEAB4BC4E6A87D996DBDB /* libPods-ReactNativeExample-ReactNativeExampleTests.a */; };
+ AC4FB686196FFEB9960B11F2 /* Pods_ReactNativeExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D49B967030CC3BCC1327ACBF /* Pods_ReactNativeExample.framework */; };
+ B015078B67589DC0CDE8619B /* Pods_ReactNativeExample_ReactNativeExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2823F7D5F43B923F80107C24 /* Pods_ReactNativeExample_ReactNativeExampleTests.framework */; };
D847CC182D99D1DA00CAB625 /* FronteggLoaderInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D847CC172D99D1D900CAB625 /* FronteggLoaderInitializer.swift */; };
/* End PBXBuildFile section */
@@ -39,16 +39,16 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeExample/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeExample/Info.plist; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ReactNativeExample/main.m; sourceTree = ""; };
+ 2823F7D5F43B923F80107C24 /* Pods_ReactNativeExample_ReactNativeExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReactNativeExample_ReactNativeExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
41872B5A2AFBC5CF0014FB13 /* FronteggSwiftAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FronteggSwiftAdapter.swift; sourceTree = ""; };
41AE42B42B34A53900CB704F /* hermes.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = hermes.xcframework; path = "Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework"; sourceTree = ""; };
41E63BA52A52DA4E00BF6DE4 /* Frontegg.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Frontegg.plist; sourceTree = ""; };
41E63BA72A5430AB00BF6DE4 /* ReactNativeExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ReactNativeExample.entitlements; path = ReactNativeExample/ReactNativeExample.entitlements; sourceTree = ""; };
- 48D7E04979345AE7E4960E7D /* libPods-ReactNativeExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
62920412DB1DD6B64B10C127 /* Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig"; sourceTree = ""; };
759B7129AA820E19A4DCA83A /* Pods-ReactNativeExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample.release.xcconfig"; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeExample/LaunchScreen.storyboard; sourceTree = ""; };
- A2FEEAB4BC4E6A87D996DBDB /* libPods-ReactNativeExample-ReactNativeExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeExample-ReactNativeExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
CD982C31CEF36640C39F4113 /* Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig"; sourceTree = ""; };
+ D49B967030CC3BCC1327ACBF /* Pods_ReactNativeExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReactNativeExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D847CC172D99D1D900CAB625 /* FronteggLoaderInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FronteggLoaderInitializer.swift; sourceTree = ""; };
DBD4E57CAF9F6DF06DB159C4 /* Pods-ReactNativeExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample.debug.xcconfig"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -59,7 +59,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- B24DB32AEF180101559169F0 /* libPods-ReactNativeExample-ReactNativeExampleTests.a in Frameworks */,
+ B015078B67589DC0CDE8619B /* Pods_ReactNativeExample_ReactNativeExampleTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -67,7 +67,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9FAF02B86F1794E4CE2B25FC /* libPods-ReactNativeExample.a in Frameworks */,
+ AC4FB686196FFEB9960B11F2 /* Pods_ReactNativeExample.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -113,8 +113,8 @@
children = (
41AE42B42B34A53900CB704F /* hermes.xcframework */,
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
- 48D7E04979345AE7E4960E7D /* libPods-ReactNativeExample.a */,
- A2FEEAB4BC4E6A87D996DBDB /* libPods-ReactNativeExample-ReactNativeExampleTests.a */,
+ D49B967030CC3BCC1327ACBF /* Pods_ReactNativeExample.framework */,
+ 2823F7D5F43B923F80107C24 /* Pods_ReactNativeExample_ReactNativeExampleTests.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -234,7 +234,10 @@
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
- productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
+ packageReferences = (
+ 2F0A5C48A15A74750BE517A0 /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */,
+ );
+ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
@@ -626,6 +629,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
+ "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
+ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
+ );
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
@@ -693,6 +704,14 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
+ "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
+ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
+ );
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
@@ -749,6 +768,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+/* Begin XCRemoteSwiftPackageReference section */
+ 2F0A5C48A15A74750BE517A0 /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/frontegg/frontegg-ios-swift.git";
+ requirement = {
+ kind = exactVersion;
+ version = 1.3.10;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 6DD78663B3B3114CC0D8A6DA /* FronteggSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2F0A5C48A15A74750BE517A0 /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */;
+ productName = FronteggSwift;
+ };
+/* End XCSwiftPackageProductDependency section */
+
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
diff --git a/example/ios/ReactNativeExample.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/ReactNativeExample.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 0000000..12df1f6
--- /dev/null
+++ b/example/ios/ReactNativeExample.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,24 @@
+{
+ "originHash" : "4101dbe3b3b6896fb67e506ae29b38a75a9292e5037ce292dd32c0e5083cf1d3",
+ "pins" : [
+ {
+ "identity" : "frontegg-ios-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/frontegg/frontegg-ios-swift.git",
+ "state" : {
+ "revision" : "e702dd7d4fedd1290cee9ca9a72b0b8ed24a2fd3",
+ "version" : "1.3.10"
+ }
+ },
+ {
+ "identity" : "sentry-cocoa",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/getsentry/sentry-cocoa.git",
+ "state" : {
+ "revision" : "dad229c665bfd043c5d80ac7aa77717cbd19a1c3",
+ "version" : "8.58.3"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/example/src/HomeScreen.tsx b/example/src/HomeScreen.tsx
index 71d8cbf..a52ac83 100644
--- a/example/src/HomeScreen.tsx
+++ b/example/src/HomeScreen.tsx
@@ -12,15 +12,49 @@ import {
registerPasskeys,
loginWithPasskeys,
requestAuthorize,
+ isSteppedUp,
+ stepUp,
openAdminPortal,
} from '@frontegg/react-native';
import { useState } from 'react';
import type { ITenantsResponse } from '@frontegg/rest-api';
+const STEP_UP_MAX_AGE_SECONDS = 60;
+
export default function HomeScreen() {
const [switching, setSwitching] = useState('');
+ const [stepUpMessage, setStepUpMessage] = useState<{
+ text: string;
+ isSuccess: boolean;
+ } | null>(null);
const state = useAuth();
+ const handleSensitiveAction = async () => {
+ setStepUpMessage(null);
+ try {
+ const alreadySteppedUp = await isSteppedUp(STEP_UP_MAX_AGE_SECONDS);
+ if (alreadySteppedUp) {
+ setStepUpMessage({
+ text: 'You are already stepped up',
+ isSuccess: true,
+ });
+ return;
+ }
+ await stepUp(STEP_UP_MAX_AGE_SECONDS);
+ setStepUpMessage({
+ text: 'Action completed successfully',
+ isSuccess: true,
+ });
+ } catch (error) {
+ setStepUpMessage({
+ text: `Action completed with failure${
+ error instanceof Error ? `: ${error.message}` : ''
+ }`,
+ isSuccess: false,
+ });
+ }
+ };
+
return (
@@ -62,23 +96,71 @@ export default function HomeScreen() {
)}
-
- {
- try {
- const user = await requestAuthorize(
- '1251386f-dcab-413b-aebf-c5b2903eccf6',
- '0827d7b7-e07c-46c5-bb03-b85189d57d21'
- );
- console.log('Authorization Success:', user);
- } catch (error) {
- console.error('Authorization Failed:', error);
- }
- }}
- />
-
+ {state.isAuthenticated ? (
+
+
+
+ ) : null}
+
+ {stepUpMessage ? (
+
+
+ {stepUpMessage.text}
+
+
+ ) : null}
+
+ {state.isAuthenticated ? (
+
+ {
+ if (!state.refreshToken) {
+ setStepUpMessage({
+ text: 'No refresh token available',
+ isSuccess: false,
+ });
+ return;
+ }
+ try {
+ const user = await requestAuthorize(state.refreshToken);
+ console.log('Authorization Success:', user);
+ setStepUpMessage({
+ text: 'Request authorize successful',
+ isSuccess: true,
+ });
+ } catch (error) {
+ console.error('Authorization Failed:', error);
+ setStepUpMessage({
+ text: `Request authorize failed${
+ error instanceof Error ? `: ${error.message}` : ''
+ }`,
+ isSuccess: false,
+ });
+ }
+ }}
+ />
+
+ ) : null}
{state.isAuthenticated ? (
@@ -215,4 +297,26 @@ const styles = StyleSheet.create({
marginBottom: 8,
alignSelf: 'flex-start',
},
+ messageBanner: {
+ borderRadius: 16,
+ padding: 16,
+ marginVertical: 8,
+ width: '100%',
+ },
+ messageBannerSuccess: {
+ backgroundColor: '#E8F5E9',
+ },
+ messageBannerError: {
+ backgroundColor: '#FFEBEE',
+ },
+ messageText: {
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ messageTextSuccess: {
+ color: '#2E7D32',
+ },
+ messageTextError: {
+ color: '#C62828',
+ },
});
diff --git a/ios/FronteggRN.m b/ios/FronteggRN.m
index 44aae96..1509d78 100644
--- a/ios/FronteggRN.m
+++ b/ios/FronteggRN.m
@@ -44,6 +44,16 @@ @interface RCT_EXTERN_MODULE(FronteggRN, RCTEventEmitter)
resolver: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject
)
+RCT_EXTERN_METHOD(
+ isSteppedUp: (nonnull NSNumber *)maxAgeSeconds
+ resolver: (RCTPromiseResolveBlock)resolve
+ rejecter: (RCTPromiseRejectBlock)reject
+ )
+RCT_EXTERN_METHOD(
+ stepUp: (nonnull NSNumber *)maxAgeSeconds
+ resolver: (RCTPromiseResolveBlock)resolve
+ rejecter: (RCTPromiseRejectBlock)reject
+ )
RCT_EXTERN_METHOD(
openAdminPortal: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject
diff --git a/ios/FronteggRN.swift b/ios/FronteggRN.swift
index 1228e15..7bc15cf 100644
--- a/ios/FronteggRN.swift
+++ b/ios/FronteggRN.swift
@@ -197,6 +197,41 @@ class FronteggRN: RCTEventEmitter {
fronteggApp.auth.loginWithPasskeys(completion)
}
+ /// React Native bridge requires nonnull NSNumber; pass -1 from JS when maxAge is omitted.
+ private func maxAgeInterval(from maxAgeSeconds: NSNumber) -> TimeInterval? {
+ let seconds = maxAgeSeconds.doubleValue
+ return seconds < 0 ? nil : seconds
+ }
+
+ @objc
+ func isSteppedUp(
+ _ maxAgeSeconds: NSNumber,
+ resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: @escaping RCTPromiseRejectBlock
+ ) {
+ resolver(fronteggApp.auth.isSteppedUp(maxAge: maxAgeInterval(from: maxAgeSeconds)))
+ }
+
+ @objc
+ func stepUp(
+ _ maxAgeSeconds: NSNumber,
+ resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: @escaping RCTPromiseRejectBlock
+ ) {
+ let completion: FronteggAuth.CompletionHandler = { result in
+ switch result {
+ case .success:
+ resolver(nil)
+ case .failure(let error):
+ rejecter(error.failureReason, error.localizedDescription, error)
+ }
+ }
+
+ Task {
+ await fronteggApp.auth.stepUp(maxAge: maxAgeInterval(from: maxAgeSeconds), completion)
+ }
+ }
+
@objc
func requestAuthorize(
_ refreshToken: String,
diff --git a/ios/Package.swift b/ios/Package.swift
new file mode 100644
index 0000000..9feb10b
--- /dev/null
+++ b/ios/Package.swift
@@ -0,0 +1,17 @@
+// swift-tools-version: 5.5
+// Reference manifest for the FronteggSwift version required by FronteggRN.
+// React Native apps integrate this package via SPM in the host Podfile (see docs/setup.md).
+
+import PackageDescription
+
+let package = Package(
+ name: "FronteggRNPackage",
+ platforms: [
+ .iOS(.v14)
+ ],
+ products: [],
+ dependencies: [
+ .package(url: "https://github.com/frontegg/frontegg-ios-swift.git", exact: "1.3.10"),
+ ],
+ targets: []
+)
diff --git a/ios/frontegg_spm.rb b/ios/frontegg_spm.rb
new file mode 100644
index 0000000..2ecc065
--- /dev/null
+++ b/ios/frontegg_spm.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+
+# Links FronteggSwift (SPM 1.3.10) to the FronteggRN CocoaPods target after `pod install`.
+# Patches Pods.xcodeproj/project.pbxproj as text — xcodeproj gem save() breaks Xcode 26.
+module FronteggSPM
+ PACKAGE_URL = 'https://github.com/frontegg/frontegg-ios-swift.git'
+ PACKAGE_VERSION = '1.3.10'
+ PRODUCT_NAME = 'FronteggSwift'
+
+ PACKAGE_REF_ID = '2F0A5C48A15A74750BE517A0'
+ PRODUCT_DEP_ID = '6DD78663B3B3114CC0D8A6DA'
+ FRONTEGG_RN_TARGET_ID = 'A8A32B38A9DEF00EF3A61386B5FF1887'
+ PODS_PROJECT_ID = '46EB2E00000000'
+ FRONTEGG_RN_FRAMEWORKS_PHASE_ID = '46EB2E000076A0'
+ SPM_TARGET_DEP_ID = '7A1B2C3D4E5F6000100140001'
+ SPM_FRAMEWORK_BUILD_ID = '7A1B2C3D4E5F6000100140002'
+
+ module_function
+
+ def link_frontegg_rn_pod(installer)
+ pbxproj_path = File.join(installer.sandbox.root, 'Pods.xcodeproj', 'project.pbxproj')
+ patch_pods_pbxproj(pbxproj_path)
+ patch_frontegg_rn_xcconfigs(installer.sandbox.root)
+ patch_app_pods_xcconfigs(installer.sandbox.root)
+
+ app_pbxproj = user_app_pbxproj_path(installer)
+ dedupe_app_frontegg_spm_link(app_pbxproj) if app_pbxproj
+ end
+
+ def user_app_pbxproj_path(installer)
+ ios_dir = installer.sandbox.root.parent
+ Dir.glob(File.join(ios_dir, '*.xcodeproj', 'project.pbxproj'))
+ .reject { |path| path.include?('/Pods.xcodeproj/') }
+ .first
+ end
+
+ def patch_app_pods_xcconfigs(pods_root)
+ spm_search_path = '"$(PODS_CONFIGURATION_BUILD_DIR)"'
+ Dir.glob(File.join(pods_root, 'Target Support Files', 'Pods-*', 'Pods-*.xcconfig')).each do |path|
+ content = File.read(path)
+ unless content.include?('SWIFT_INCLUDE_PATHS')
+ content = "#{content}\nSWIFT_INCLUDE_PATHS = $(inherited) #{spm_search_path}\n"
+ end
+ swift_import_flag = '-I"$(PODS_CONFIGURATION_BUILD_DIR)"'
+ if content.include?('OTHER_SWIFT_FLAGS =')
+ content = content.sub(/OTHER_SWIFT_FLAGS = (.+)/) do |line|
+ line.include?(swift_import_flag) ? line : line.sub(/\Z/, " #{swift_import_flag}")
+ end
+ else
+ content = "#{content}\nOTHER_SWIFT_FLAGS = $(inherited) #{swift_import_flag}\n"
+ end
+ File.write(path, content)
+ end
+ end
+
+ # App Swift files import FronteggSwift but must not link it (FronteggRN already does).
+ # Keep package resolution on the app project; drop target-level package products only.
+ def dedupe_app_frontegg_spm_link(app_pbxproj_path)
+ return unless File.exist?(app_pbxproj_path)
+
+ content = File.read(app_pbxproj_path)
+ content = content.sub(%r{^// FRONTEGG_SPM_DEDUPED_MARKER\n}, '')
+
+ content = content.gsub(
+ /\n\t\t\tpackageProductDependencies = \(\n\t\t\t\t#{PRODUCT_DEP_ID} \/\* #{PRODUCT_NAME} \*\/,\n\t\t\t\);/,
+ ''
+ )
+
+ unless content.include?('XCRemoteSwiftPackageReference "frontegg-ios-swift.git"')
+ content = content.sub(
+ /(mainGroup = [A-F0-9]+;\n)(\t\t\tproductRefGroup)/,
+ "\\1\t\t\tpackageReferences = (\n\t\t\t\t#{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference \"frontegg-ios-swift.git\" */,\n\t\t\t);\n\t\t\t\\2"
+ )
+
+ package_sections = <<~PBX
+
+ /* Begin XCRemoteSwiftPackageReference section */
+ \t\t#{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */ = {
+ \t\t\tisa = XCRemoteSwiftPackageReference;
+ \t\t\trepositoryURL = "#{PACKAGE_URL}";
+ \t\t\trequirement = {
+ \t\t\t\tkind = exactVersion;
+ \t\t\t\tversion = #{PACKAGE_VERSION};
+ \t\t\t};
+ \t\t};
+ /* End XCRemoteSwiftPackageReference section */
+
+ /* Begin XCSwiftPackageProductDependency section */
+ \t\t#{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */ = {
+ \t\t\tisa = XCSwiftPackageProductDependency;
+ \t\t\tpackage = #{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */;
+ \t\t\tproductName = #{PRODUCT_NAME};
+ \t\t};
+ /* End XCSwiftPackageProductDependency section */
+ PBX
+
+ content.sub!(/\n\t\};\n\trootObject = /, "#{package_sections}\n\t};\n\trootObject = ")
+ end
+
+ File.write(app_pbxproj_path, content)
+ end
+
+ def patch_frontegg_rn_xcconfigs(pods_root)
+ Dir.glob(File.join(pods_root, 'Target Support Files', 'FronteggRN', 'FronteggRN.*.xcconfig')).each do |path|
+ content = File.read(path)
+ # FronteggSwift.swiftmodule is emitted alongside FronteggRN under PODS_CONFIGURATION_BUILD_DIR.
+ spm_search_path = '"$(PODS_CONFIGURATION_BUILD_DIR)"'
+ lines = {
+ 'CLANG_ENABLE_MODULES' => 'YES',
+ 'SWIFT_ENABLE_EXPLICIT_MODULES' => 'NO',
+ 'SWIFT_INCLUDE_PATHS' => "$(inherited) #{spm_search_path}"
+ }
+ lines.each do |key, value|
+ if content.include?("#{key} =")
+ content = content.gsub(/#{Regexp.escape(key)} = .*/, "#{key} = #{value}")
+ else
+ content = "#{content}\n#{key} = #{value}\n"
+ end
+ end
+ unless content.match?(/FRAMEWORK_SEARCH_PATHS = .*\$\(PODS_CONFIGURATION_BUILD_DIR\)"/)
+ content = content.sub(
+ /(FRAMEWORK_SEARCH_PATHS = .+)/,
+ "\\1 #{spm_search_path}"
+ )
+ end
+ swift_import_flag = '-I"$(PODS_CONFIGURATION_BUILD_DIR)"'
+ if content.include?('OTHER_SWIFT_FLAGS =')
+ content = content.sub(/OTHER_SWIFT_FLAGS = (.+)/) do |line|
+ line.include?(swift_import_flag) ? line : line.sub(/\Z/, " #{swift_import_flag}")
+ end
+ else
+ content = "#{content}\nOTHER_SWIFT_FLAGS = $(inherited) #{swift_import_flag}\n"
+ end
+ File.write(path, content)
+ end
+ end
+
+ def patch_pods_pbxproj(pbxproj_path)
+ content = File.read(pbxproj_path)
+
+ unless content.include?("#{FRONTEGG_RN_TARGET_ID} /* FronteggRN */")
+ raise "FronteggSPM: FronteggRN target not found in #{pbxproj_path}"
+ end
+
+ unless content.include?('XCRemoteSwiftPackageReference "frontegg-ios-swift.git"')
+ content = add_package_reference(content)
+ else
+ content = ensure_package_product_dependencies(content)
+ end
+
+ content = add_spm_target_dependency(content)
+ content = add_spm_framework_build_file(content)
+ File.write(pbxproj_path, content)
+ content
+ end
+
+ def add_package_reference(content)
+ content = content.sub(
+ /(#{Regexp.escape(FRONTEGG_RN_TARGET_ID)} \/\* FronteggRN \*\/ = \{[^}]*?name = FronteggRN;\n\t\t\tproductName = FronteggRN;)/m
+ ) do |block|
+ if block.include?('packageProductDependencies')
+ block
+ else
+ block.sub(
+ "name = FronteggRN;\n\t\t\tproductName = FronteggRN;",
+ "name = FronteggRN;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t#{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */,\n\t\t\t);\n\t\t\tproductName = FronteggRN;"
+ )
+ end
+ end
+
+ content = content.sub(
+ /(#{Regexp.escape(PODS_PROJECT_ID)} \/\* Project object \*\/ = \{[^}]*?mainGroup = 46EB2E00000010;\n)/m
+ ) do |header|
+ if header.include?('packageReferences')
+ header
+ else
+ "#{header}\t\t\tpackageReferences = (\n\t\t\t\t#{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference \"frontegg-ios-swift.git\" */,\n\t\t\t);\n"
+ end
+ end
+
+ package_sections = <<~PBX
+
+ /* Begin XCRemoteSwiftPackageReference section */
+ \t\t#{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */ = {
+ \t\t\tisa = XCRemoteSwiftPackageReference;
+ \t\t\trepositoryURL = "#{PACKAGE_URL}";
+ \t\t\trequirement = {
+ \t\t\t\tkind = exactVersion;
+ \t\t\t\tversion = #{PACKAGE_VERSION};
+ \t\t\t};
+ \t\t};
+ /* End XCRemoteSwiftPackageReference section */
+
+ /* Begin XCSwiftPackageProductDependency section */
+ \t\t#{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */ = {
+ \t\t\tisa = XCSwiftPackageProductDependency;
+ \t\t\tpackage = #{PACKAGE_REF_ID} /* XCRemoteSwiftPackageReference "frontegg-ios-swift.git" */;
+ \t\t\tproductName = #{PRODUCT_NAME};
+ \t\t};
+ /* End XCSwiftPackageProductDependency section */
+ PBX
+
+ content.sub(/\n\t\};\n\trootObject = /, "#{package_sections}\n\t};\n\trootObject = ")
+ end
+
+ def ensure_package_product_dependencies(content)
+ return content if content.include?("#{FRONTEGG_RN_TARGET_ID} /* FronteggRN */") &&
+ content.match?(/#{Regexp.escape(FRONTEGG_RN_TARGET_ID)} \/\* FronteggRN \*\/ = \{[\s\S]*?packageProductDependencies =/)
+
+ content.sub(
+ /(#{Regexp.escape(FRONTEGG_RN_TARGET_ID)} \/\* FronteggRN \*\/ = \{[^}]*?name = FronteggRN;\n\t\t\tproductName = FronteggRN;)/m
+ ) do |block|
+ block.sub(
+ "name = FronteggRN;\n\t\t\tproductName = FronteggRN;",
+ "name = FronteggRN;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t#{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */,\n\t\t\t);\n\t\t\tproductName = FronteggRN;"
+ )
+ end
+ end
+
+ def add_spm_target_dependency(content)
+ return content if content.include?("productRef = #{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */")
+
+ content = content.sub(
+ /(#{Regexp.escape(FRONTEGG_RN_TARGET_ID)} \/\* FronteggRN \*\/ = \{[\s\S]*?dependencies = \(\n)/m
+ ) do |match|
+ "#{match}\t\t\t\t#{SPM_TARGET_DEP_ID} /* PBXTargetDependency */,\n"
+ end
+
+ dep_entry = <<~DEP
+ \t\t#{SPM_TARGET_DEP_ID} /* PBXTargetDependency */ = {
+ \t\t\tisa = PBXTargetDependency;
+ \t\t\tproductRef = #{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */;
+ \t\t};
+ DEP
+
+ if content.include?('/* End PBXTargetDependency section */')
+ content.sub('/* End PBXTargetDependency section */', "#{dep_entry}/* End PBXTargetDependency section */")
+ else
+ content.sub(/\n\t\};\n\trootObject = /, "\n/* Begin PBXTargetDependency section */#{dep_entry}/* End PBXTargetDependency section */\n\t};\n\trootObject = ")
+ end
+ end
+
+ def add_spm_framework_build_file(content)
+ return content if content.include?("#{SPM_FRAMEWORK_BUILD_ID} /* #{PRODUCT_NAME} in Frameworks */")
+
+ build_file = "\t\t#{SPM_FRAMEWORK_BUILD_ID} /* #{PRODUCT_NAME} in Frameworks */ = {isa = PBXBuildFile; productRef = #{PRODUCT_DEP_ID} /* #{PRODUCT_NAME} */; };\n"
+ content = content.sub('/* Begin PBXBuildFile section */', "/* Begin PBXBuildFile section */\n#{build_file}")
+
+ content.sub(
+ /(#{Regexp.escape(FRONTEGG_RN_FRAMEWORKS_PHASE_ID)} \/\* Frameworks \*\/ = \{[\s\S]*?files = \(\n)/m
+ ) do |match|
+ "#{match}\t\t\t\t#{SPM_FRAMEWORK_BUILD_ID} /* #{PRODUCT_NAME} in Frameworks */,\n"
+ end
+ end
+end
diff --git a/src/FronteggNative.ts b/src/FronteggNative.ts
index d8115f4..9c1b2db 100644
--- a/src/FronteggNative.ts
+++ b/src/FronteggNative.ts
@@ -68,6 +68,19 @@ export async function requestAuthorize(
return await FronteggRN.requestAuthorize(refreshToken, deviceTokenCookie);
}
+/** Sentinel for optional maxAge on the native bridge (NSNumber must be nonnull on iOS). */
+const NO_MAX_AGE = -1;
+
+/** Max age in seconds for step-up validity (same semantics as native SDK). */
+export async function isSteppedUp(maxAge?: number): Promise {
+ return FronteggRN.isSteppedUp(maxAge ?? NO_MAX_AGE);
+}
+
+/** Starts step-up authentication (MFA / re-auth). Max age in seconds. */
+export async function stepUp(maxAge?: number): Promise {
+ return FronteggRN.stepUp(maxAge ?? NO_MAX_AGE);
+}
+
export async function registerPasskeys(): Promise {
return FronteggRN.registerPasskeys();
}
diff --git a/src/index.tsx b/src/index.tsx
index 17ee54c..d82856d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -11,5 +11,7 @@ export {
loginWithPasskeys,
registerPasskeys,
requestAuthorize,
+ isSteppedUp,
+ stepUp,
openAdminPortal,
} from './FronteggNative';