Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR-14342 - Add support for multiple regions #21

Merged
merged 11 commits into from
Dec 3, 2023
Merged
2 changes: 1 addition & 1 deletion FronteggSwift.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FronteggSwift'
s.version = '1.1.3'
s.version = '1.2.0'
s.summary = 'A swift library for easy integrating iOS application with Frontegg Services'
s.description = 'Frontegg is an end-to-end user management platform for B2B SaaS, powering strategies from PLG to enterprise readiness. Easy migration, no credit card required'
s.homepage = 'https://github.com/frontegg/frontegg-ios-swift'
Expand Down
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and integrate them into their SaaS portals in up to 5 lines of code.
- [Add custom UIKit loading screen (coming-soon)](#Add-custom-uikit-loading-screen)
- [Embedded Webview vs ASWebAuthenticationSession](#embedded-webview-vs-aswebauthenticationsession)
- [Config iOS associated domain](#config-ios-associated-domain)
- [Multi-Region support](#multi-region-support)

## Project Requirements

Expand Down Expand Up @@ -301,3 +302,116 @@ In order to add your iOS associated domain to your Frontegg application, you wil
}
```
In order to use our API’s, follow [this guide](‘https://docs.frontegg.com/reference/getting-started-with-your-api’) to generate a vendor token.

Next, you will need to add your associated domain to your iOS application. To do so, follow the steps below:

1. Open your project in Xcode.
2. Select your project in the Project Navigator.
3. Select your target.
4. Select the Signing & Capabilities tab.
5. Expand the Associated Domains section.
6. Click the + button.
7. Enter your associated domain in the format `applinks:[YOUR_ASSOCIATED_DOMAIN]`.
7. Enter your associated domain in the format `webcredentials:[YOUR_ASSOCIATED_DOMAIN]`.
8. Click Done.

`[YOUR_ASSOCIATED_DOMAIN]` is the associated domain that you would like to use with your iOS application.
For example, if you would like to use `https://example.com` as your associated domain, you would enter `applinks:example.com` and `webcredentials:example.com`.


## Multi-Region Support

This guide outlines the steps to configure your iOS application to support multiple regions.

### Step 1: Modify the Frontegg.plist File

First, adjust your Frontegg.plist file to handle multiple regions:

**Modifications**:
- **Remove** the existing `baseUrl` and `clientId` keys.
- **Add** a new array key named `regions`. This array will hold dictionaries for each region.

Example Frontegg.plist Structure:
```xml
<key>regions</key>
<array>
<dict>
<key>key</key>
<string>us-region</string>
<key>baseUrl</key>
<string>https://us-region-api.frontegg.com</string>
<key>clientId</key>
<string>your-client-id-for-us-region</string>
</dict>
<!-- Add additional regions in a similar format -->
</array>
```

### Step 2: Add Associated Domains for Each Region

For each region, configure the associated domains in your application's settings. This is vital for proper API routing and authentication.

Example Associated Domain Configuration:
[demo-multi-region.entitlements](demo-multi-region%2Fdemo-multi-region%2Fdemo-multi-region.entitlements)

Follow [Config iOS associated domain](#config-ios-associated-domain) to add your iOS associated domain to your Frontegg application.


### Step 3: Implement Region Selection UI

The final step is to implement a UI for the user to select their region. **This can be done in any way you see fit**.
The example application uses a simple picker view to allow the user to select their region.

**Important Considerations**
- **Switching Regions**: To switch regions, update the selection in UserDefaults. If issues arise, a **re-installation** of the application might be necessary.
- **Data Isolation**: Ensure data handling and APIs are region-specific to prevent data leakage between regions.

| Select EU Region | Select US Region |
|:--------------------------------------------------------:|:--------------------------------------------------------:|
| ![eu-region-example.gif](assets%2Feu-region-example.gif) | ![us-region-example.gif](assets%2Fus-region-example.gif) |

Example Region Selection UI:
```swift
import SwiftUI
import FronteggSwift

struct SelectRegionView: View {
@EnvironmentObject var fronteggAuth: FronteggAuth

var body: some View {
VStack(alignment: .leading) {
Text("Welcome to MyApp")
.font(.largeTitle)

Text("Select your region:")
.padding(.top, 8)
.padding(.bottom, 20)
.font(.title2)


ForEach(fronteggAuth.regionData, id: \.key.self) { item in
Button(action: {
FronteggApp.shared.initWithRegion(regionKey: item.key)
}) {
VStack(alignment: .leading) {
Text("Region - \(item.key.uppercased())")
.font(.title2)
.padding(.bottom, 1)
Text("\(item.baseUrl)")
.font(.caption)
.tint(.black)
.padding(.bottom, 8)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}

Spacer()

}
.padding()
.navigationTitle("Region")
}
}
```
82 changes: 75 additions & 7 deletions Sources/FronteggSwift/FronteggApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,69 @@ public class FronteggApp {

public static let shared = FronteggApp()

public let auth: FronteggAuth
public let baseUrl: String
public let clientId: String
let api: Api
public var auth: FronteggAuth
public var baseUrl: String = ""
public var clientId: String = ""
public var bundleIdentifier: String = ""

public var regionData: [RegionConfig] = []
let credentialManager: CredentialManager
let logger = getLogger("FronteggApp")


init() {

if let data = try? PlistHelper.fronteggRegionalConfig() {
logger.info("Regional frontegg initialization")
self.bundleIdentifier = data.bundleIdentifier
self.credentialManager = CredentialManager(serviceKey: data.keychainService)
self.regionData = data.regions



self.auth = FronteggAuth(
baseUrl: self.baseUrl,
clientId: self.clientId,
credentialManager: self.credentialManager,
isRegional:true,
regionData: self.regionData
)

if let config = self.auth.selectedRegion {
self.baseUrl = config.baseUrl
self.clientId = config.clientId
self.auth.reinitWithRegion(config: config)

logger.info("Frontegg Initialized succcessfully (region: \(config.key))")
return;
}else {
// skip automatic authorize for regional config
self.auth.initializing = false
self.auth.isLoading = false
self.auth.showLoader = false
}

return;
}


logger.info("Standard frontegg initialization")
guard let data = try? PlistHelper.fronteggConfig() else {
exit(1)
}


self.baseUrl = data.baseUrl
self.clientId = data.clientId
self.bundleIdentifier = data.bundleIdentifier
self.credentialManager = CredentialManager(serviceKey: data.keychainService)
self.api = Api(baseUrl: self.baseUrl, clientId: self.clientId)

self.auth = FronteggAuth(
baseUrl: self.baseUrl,
clientId: self.clientId,
api: self.api,
credentialManager: self.credentialManager
credentialManager: self.credentialManager,
isRegional: false,
regionData: []
)

logger.info("Frontegg Initialized succcessfully")
Expand All @@ -44,4 +84,32 @@ public class FronteggApp {
logger.info("Frontegg baseURL: \(self.baseUrl)")
}

public func initWithRegion( regionKey:String ){

if ( self.regionData.count == 0 ){
logger.critical("illegal state. Frontegg.plist does not contains regions array")
exit(1)
}



guard let config = self.regionData.first(where: { config in
config.key == regionKey
}) else {
let keys: String = self.regionData.map { config in
config.key
}.joined(separator: ", ")
logger.critical("invalid region key \(regionKey). available regions: \(keys)")
exit(1)
}

CredentialManager.saveSelectedRegion(regionKey)

self.baseUrl = config.baseUrl
self.clientId = config.clientId
self.auth.reinitWithRegion(config: config)

logger.info("Frontegg Initialized succcessfully (region: \(regionKey))")
}

}
Loading
Loading