From c5d217d6dcce9ebb4ce7c731d653d456f34b08ce Mon Sep 17 00:00:00 2001 From: David Antoon Date: Mon, 4 Dec 2023 22:55:48 +0200 Subject: [PATCH 1/5] Upgrade frontegg native sdk version --- FronteggIonicCapacitor.podspec | 2 +- android/build.gradle | 2 +- example/package-lock.json | 6 +++--- ios/Podfile | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FronteggIonicCapacitor.podspec b/FronteggIonicCapacitor.podspec index a3e866e..c5cf567 100644 --- a/FronteggIonicCapacitor.podspec +++ b/FronteggIonicCapacitor.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '14.0' s.dependency 'Capacitor' - s.dependency "FronteggSwift", "1.1.1" + s.dependency "FronteggSwift", "1.2.1" s.swift_version = '5.1' s.pod_target_xcconfig = { 'CODE_SIGNING_ALLOWED' => 'YES' diff --git a/android/build.gradle b/android/build.gradle index a060fe0..7f81048 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -55,7 +55,7 @@ dependencies { implementation "androidx.browser:browser:1.5.0" implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1' implementation 'com.google.code.gson:gson:2.8.9' - implementation 'com.frontegg.sdk:android:1.1.3' + implementation 'com.frontegg.sdk:android:1.2.0' testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" diff --git a/example/package-lock.json b/example/package-lock.json index b5caf21..ec74910 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -3096,9 +3096,9 @@ } }, "node_modules/@frontegg/ionic-capacitor": { - "version": "1.0.0-alpha.3", + "version": "1.0.0", "resolved": "file:../frontegg-ionic-capacitor.tgz", - "integrity": "sha512-78FK71TXKhrqQzXbsVSrGKtEF7wpnqcTOkaa+TmwWTg33Yt6FGhO0noRqX292XH4qo+nv3w2KD2Ozeh4WIopaw==", + "integrity": "sha512-Giv7RAinGkASHzR3j2F33p0ilandJTbD4XhJBzx5HqaFSpbGiIniOiKNlgwbrkvWrGBH8w/1XdmSy+VcT9Beww==", "license": "MIT", "dependencies": { "@frontegg/rest-api": "^3.1.5" @@ -17841,7 +17841,7 @@ }, "@frontegg/ionic-capacitor": { "version": "file:../frontegg-ionic-capacitor.tgz", - "integrity": "sha512-78FK71TXKhrqQzXbsVSrGKtEF7wpnqcTOkaa+TmwWTg33Yt6FGhO0noRqX292XH4qo+nv3w2KD2Ozeh4WIopaw==", + "integrity": "sha512-Giv7RAinGkASHzR3j2F33p0ilandJTbD4XhJBzx5HqaFSpbGiIniOiKNlgwbrkvWrGBH8w/1XdmSy+VcT9Beww==", "requires": { "@frontegg/rest-api": "^3.1.5" } diff --git a/ios/Podfile b/ios/Podfile index 90f499a..a7e6ba1 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -5,7 +5,7 @@ def capacitor_pods use_frameworks! pod 'Capacitor', :path => '../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' - pod 'FronteggSwift', "1.1.1" + pod 'FronteggSwift', "1.2.1" end target 'Plugin' do From b259183b401d3fdfdddbcc44df3f883ba2dce992 Mon Sep 17 00:00:00 2001 From: David Antoon Date: Mon, 4 Dec 2023 22:56:23 +0200 Subject: [PATCH 2/5] add multi region bridge with native sdks --- ios/Plugin/FronteggNativePlugin.m | 1 + ios/Plugin/FronteggNativePlugin.swift | 48 ++++++++++++++++++++++++--- src/definitions.ts | 21 ++++++++++-- src/frontegg.service.ts | 26 ++++++++++++++- src/web.ts | 14 ++++++-- 5 files changed, 100 insertions(+), 10 deletions(-) diff --git a/ios/Plugin/FronteggNativePlugin.m b/ios/Plugin/FronteggNativePlugin.m index a59d75b..fdffc1a 100644 --- a/ios/Plugin/FronteggNativePlugin.m +++ b/ios/Plugin/FronteggNativePlugin.m @@ -10,6 +10,7 @@ CAP_PLUGIN_METHOD(logout, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(switchTenant, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(refreshToken, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(initWithRegion, CAPPluginReturnPromise); ) diff --git a/ios/Plugin/FronteggNativePlugin.swift b/ios/Plugin/FronteggNativePlugin.swift index 89b25cc..07bd7e5 100644 --- a/ios/Plugin/FronteggNativePlugin.swift +++ b/ios/Plugin/FronteggNativePlugin.swift @@ -34,7 +34,7 @@ public class FronteggNativePlugin: CAPPlugin { auth.$isLoading.map {_ in }, auth.$initializing.map {_ in }, auth.$showLoader.map {_ in }, - auth.$appLink.map {_ in } + auth.$selectedRegion.map{_ in} ) .eraseToAnyPublisher() } @@ -48,6 +48,8 @@ public class FronteggNativePlugin: CAPPlugin { self.sendEvent() } + + func sendEvent() { let auth = fronteggApp.auth @@ -64,17 +66,44 @@ public class FronteggNativePlugin: CAPPlugin { "isLoading": auth.isLoading, "initializing": auth.initializing, "showLoader": auth.showLoader, - "appLink": auth.appLink + "selectedRegion": regionToJson(auth.selectedRegion) ] self.notifyListeners("onFronteggAuthEvent", data: body as [String : Any]) } + + func regionToJson(_ region: RegionConfig?) -> [String:String]? { + + if let reg = region { + return [ + "baseUrl": reg.baseUrl, + "clientId": reg.clientId, + "key": reg.key + ] + }else { + return nil + } + } + func regionsToJson(_ regions: [RegionConfig]) -> [[String:String]] { + + var regionData: [[String:String]] = [] + regions.forEach { reg in + if let region = regionToJson(reg) { + regionData.append(region) + } + } + + return regionData + } + @objc func getConstants(_ call: CAPPluginCall) { call.resolve([ "baseUrl": fronteggApp.baseUrl, "clientId": fronteggApp.clientId, - "bundleId": Bundle.main.bundleIdentifier! + "bundleId": Bundle.main.bundleIdentifier!, + "isRegional": fronteggApp.auth.isRegional, + "regionData": regionsToJson(fronteggApp.auth.regionData) ]) } @@ -103,6 +132,15 @@ public class FronteggNativePlugin: CAPPlugin { } } + @objc func initWithRegion(_ call: CAPPluginCall) { + guard let regionKey = call.options["regionKey"] as? String else { + call.reject("No regionKey provided") + return + } + + fronteggApp.initWithRegion(regionKey: regionKey) + } + @objc func refreshToken(_ call: CAPPluginCall) { DispatchQueue.global(qos: .background).async { @@ -128,9 +166,9 @@ public class FronteggNativePlugin: CAPPlugin { "isLoading": auth.isLoading, "initializing": auth.initializing, "showLoader": auth.showLoader, - "appLink": auth.appLink + "selectedRegion": regionToJson(auth.selectedRegion) ] - call.resolve(body as? [String: Any] ?? [:]) + call.resolve(body as [String: Any] ) } } diff --git a/src/definitions.ts b/src/definitions.ts index 9d750d7..20853b6 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -12,6 +12,7 @@ export interface FronteggState { isAuthenticated: boolean; user: User | null; showLoader: boolean; + selectedRegion: string | null; } @@ -21,10 +22,18 @@ export type SubscribeMap = { } +export interface FronteggConstants { + baseUrl: string; + clientId: string; + bundleId: string; + isRegional: boolean; + regionData?: { key: string, baseUrl: string, clientId: string }[] +} + export interface FronteggNativePlugin { addListener(eventName: string, listenerFunc: ListenerCallback): Promise & PluginListenerHandle - getConstants(): Promise>; + getConstants(): Promise; getAuthState(): Promise; @@ -32,7 +41,15 @@ export interface FronteggNativePlugin { logout(): void; - switchTenant(payload:{tenantId: string}): Promise; + switchTenant(payload: { tenantId: string }): Promise; + + /** + * used to initialize the plugin with multiple regions + * for more information see: + * iOS: https://github.com/frontegg/frontegg-ios-swift#multi-region-support + * Android: https://github.com/frontegg/frontegg-android-kotlin#multi-region-support + */ + initWithRegion(payload: { regionKey: string }): Promise; refreshToken(): Promise; diff --git a/src/frontegg.service.ts b/src/frontegg.service.ts index 54e0747..b985292 100644 --- a/src/frontegg.service.ts +++ b/src/frontegg.service.ts @@ -1,6 +1,6 @@ import { registerPlugin } from '@capacitor/core'; -import type { FronteggNativePlugin, FronteggState, SubscribeMap } from './definitions'; +import type { FronteggConstants, FronteggNativePlugin, FronteggState, SubscribeMap } from './definitions'; import { createObservable } from './observables'; @@ -17,6 +17,7 @@ export class FronteggService { 'accessToken', 'user', 'isAuthenticated', + 'selectedRegion', 'showLoader', ] @@ -27,6 +28,7 @@ export class FronteggService { user: null, accessToken: null, refreshToken: null, + selectedRegion: null, } this.mapListeners = { @@ -35,6 +37,7 @@ export class FronteggService { 'user': new Set(), 'accessToken': new Set(), 'refreshToken': new Set(), + 'selectedRegion': new Set(), } FronteggNative.addListener('onFronteggAuthEvent', (state: FronteggState) => { console.log('onFronteggAuthEvent', { @@ -74,6 +77,10 @@ export class FronteggService { return this.state; } + public getNativeState(): Promise { + return FronteggNative.getAuthState(); + } + public get $isLoading() { return createObservable(this.mapListeners, this.state, 'showLoader') } @@ -90,6 +97,10 @@ export class FronteggService { return createObservable(this.mapListeners, this.state, 'accessToken') } + public get $selectedRegion() { + return createObservable(this.mapListeners, this.state, 'selectedRegion') + } + /** * Call frontegg native login method */ @@ -101,10 +112,23 @@ export class FronteggService { FronteggNative.logout(); } + public getConstants(): Promise { + return FronteggNative.getConstants(); + } + public switchTenant(tenantId: string): Promise { return FronteggNative.switchTenant({ tenantId }) } + /** + * used to initialize the plugin with multiple regions + * for more information see: + * iOS: https://github.com/frontegg/frontegg-ios-swift#multi-region-support + * Android: https://github.com/frontegg/frontegg-android-kotlin#multi-region-support + */ + public initWithRegion(regionKey: string): Promise { + return FronteggNative.initWithRegion({ regionKey }) + } public refreshToken(): Promise { return FronteggNative.refreshToken() diff --git a/src/web.ts b/src/web.ts index 07e6be9..65b96d3 100644 --- a/src/web.ts +++ b/src/web.ts @@ -1,12 +1,12 @@ import { WebPlugin } from '@capacitor/core'; -import type { FronteggNativePlugin, FronteggState } from './definitions'; +import type { FronteggConstants, FronteggNativePlugin, FronteggState } from './definitions'; export class FronteggNativeWeb extends WebPlugin implements FronteggNativePlugin { - async getConstants(): Promise> { + async getConstants(): Promise { throw Error('FronteggNative.getConstants not implemented in web') } @@ -26,6 +26,16 @@ export class FronteggNativeWeb throw Error(`FronteggNative.switchTenant ${payload} not implemented in web`) } + /** + * used to initialize the plugin with multiple regions + * for more information see: + * iOS: https://github.com/frontegg/frontegg-ios-swift#multi-region-support + * Android: https://github.com/frontegg/frontegg-android-kotlin#multi-region-support + */ + async initWithRegion(payload: { regionKey: string }): Promise { + throw Error(`FronteggNative.initWithRegion ${payload} not implemented in web`) + } + async refreshToken(): Promise { throw Error(`FronteggNative.refreshToken not implemented in web`) } From 524d875d784f5b100e30a0cf22e961384468b6d9 Mon Sep 17 00:00:00 2001 From: David Antoon Date: Mon, 4 Dec 2023 22:56:53 +0200 Subject: [PATCH 3/5] add multi region example code and guard --- README.md | 2 +- example/ios/App/App/Frontegg-Multi.plist | 29 ++++++++++++++ example/src/app/app-routing.module.ts | 20 ++++++++-- example/src/app/app.component.ts | 1 + example/src/app/app.module.ts | 3 +- .../src/app/{auth.gaurd.ts => auth.guard.ts} | 0 example/src/app/region.guard.ts | 31 +++++++++++++++ .../select-region/select-region.component.ts | 38 +++++++++++++++++++ .../app/select-region/select-region.module.ts | 16 ++++++++ .../app/select-region/select-region.page.html | 23 +++++++++++ 10 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 example/ios/App/App/Frontegg-Multi.plist rename example/src/app/{auth.gaurd.ts => auth.guard.ts} (100%) create mode 100644 example/src/app/region.guard.ts create mode 100644 example/src/app/select-region/select-region.component.ts create mode 100644 example/src/app/select-region/select-region.module.ts create mode 100644 example/src/app/select-region/select-region.page.html diff --git a/README.md b/README.md index 4f80f1e..882019d 100644 --- a/README.md +++ b/README.md @@ -429,7 +429,7 @@ import { FronteggService } from '@frontegg/ionic-capacitor'; 2. Open the `src/app-routing.module.ts` file and add wrap the app routes with loadChildren and apply CanActivate guard: ```typescript -import { AuthGuard } from './auth.gaurd'; +import { AuthGuard } from './auth.guard'; const routes: Routes = [ { diff --git a/example/ios/App/App/Frontegg-Multi.plist b/example/ios/App/App/Frontegg-Multi.plist new file mode 100644 index 0000000..6507f3c --- /dev/null +++ b/example/ios/App/App/Frontegg-Multi.plist @@ -0,0 +1,29 @@ + + + + + regions + + + key + eu + baseUrl + https://auth.davidantoon.me + clientId + b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca + + + key + us + baseUrl + https://davidprod.frontegg.com + clientId + d7d07347-2c57-4450-8418-0ec7ee6e096b + + + embeddedMode + + logLevel + trace + + diff --git a/example/src/app/app-routing.module.ts b/example/src/app/app-routing.module.ts index b4d09ac..56fc875 100644 --- a/example/src/app/app-routing.module.ts +++ b/example/src/app/app-routing.module.ts @@ -1,13 +1,25 @@ import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; -import { AuthGuard } from './auth.gaurd'; +import { AuthGuard } from './auth.guard'; +import { RegionGuard } from './region.guard'; +import { SelectRegionComponent } from './select-region/select-region.component'; const routes: Routes = [ { path: '', - canActivate: [ AuthGuard ], - loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule) - }, + canActivate: [ RegionGuard ], + children: [ + { + path: '', + canActivate: [ AuthGuard ], + loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule) + }, + ] + }, { + path: 'select-region', + component: SelectRegionComponent + } + ]; @NgModule({ diff --git a/example/src/app/app.component.ts b/example/src/app/app.component.ts index 913de3d..59a53fb 100644 --- a/example/src/app/app.component.ts +++ b/example/src/app/app.component.ts @@ -6,5 +6,6 @@ import { Component } from '@angular/core'; styleUrls: ['app.component.scss'], }) export class AppComponent { + constructor() {} } diff --git a/example/src/app/app.module.ts b/example/src/app/app.module.ts index 0d51072..2d86d6d 100644 --- a/example/src/app/app.module.ts +++ b/example/src/app/app.module.ts @@ -7,10 +7,11 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { FronteggService } from '@frontegg/ionic-capacitor'; +import { SelectRegionModule } from './select-region/select-region.module'; @NgModule({ declarations: [ AppComponent ], - imports: [ BrowserModule, IonicModule.forRoot(), AppRoutingModule ], + imports: [ BrowserModule, IonicModule.forRoot(), AppRoutingModule, SelectRegionModule ], providers: [ { provide: 'Frontegg', useValue: new FronteggService(), diff --git a/example/src/app/auth.gaurd.ts b/example/src/app/auth.guard.ts similarity index 100% rename from example/src/app/auth.gaurd.ts rename to example/src/app/auth.guard.ts diff --git a/example/src/app/region.guard.ts b/example/src/app/region.guard.ts new file mode 100644 index 0000000..efec3f4 --- /dev/null +++ b/example/src/app/region.guard.ts @@ -0,0 +1,31 @@ +import { CanActivateFn, Router } from '@angular/router'; +import { Inject, Injectable } from '@angular/core'; +import { FronteggService } from '@frontegg/ionic-capacitor'; + +@Injectable({ + providedIn: 'root' +}) +export class RegionGuard { + + constructor(@Inject('Frontegg') private fronteggService: FronteggService, private router: Router) { + + /** + * Listens to $isAuthenticated changes + * Reload the page to trigger canActivate function again + */ + this.fronteggService.$selectedRegion.subscribe(async () => { + window.location.reload() + }); + } + + canActivate: CanActivateFn = async () => { + const { isRegional } = await this.fronteggService.getConstants(); + const nativeState = await this.fronteggService.getNativeState() + + if (!isRegional || nativeState.selectedRegion != null) { + return true + } + + return this.router.navigate([ '/select-region' ]) + } +} diff --git a/example/src/app/select-region/select-region.component.ts b/example/src/app/select-region/select-region.component.ts new file mode 100644 index 0000000..67429cd --- /dev/null +++ b/example/src/app/select-region/select-region.component.ts @@ -0,0 +1,38 @@ +import { Component, Inject, NgZone, OnInit } from '@angular/core'; +import { FronteggService } from '@frontegg/ionic-capacitor' +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-select-region', + templateUrl: 'select-region.page.html', +}) +export class SelectRegionComponent implements OnInit { + + constructor(private ngZone: NgZone, @Inject('Frontegg') private fronteggService: FronteggService, private router: Router) { + } + + regions: { key: string, baseUrl: string, clientId: string }[] = [] + + async ngOnInit() { + console.log('onInit() select region ') + + const { selectedRegion } = await this.fronteggService.getNativeState() + + if (selectedRegion != null) { + this.router.navigate([ '/' ], { replaceUrl: true }) + return; + } + const { regionData } = await this.fronteggService.getConstants(); + this.regions = regionData ?? [] + } + + + selectRegion(key: string) { + + this.fronteggService.initWithRegion(key).then(() => { + this.ngZone.run(() => { + this.router.navigate([ '/' ], { replaceUrl: true }) + }) + }); + } +} diff --git a/example/src/app/select-region/select-region.module.ts b/example/src/app/select-region/select-region.module.ts new file mode 100644 index 0000000..d685c81 --- /dev/null +++ b/example/src/app/select-region/select-region.module.ts @@ -0,0 +1,16 @@ +import { IonicModule } from '@ionic/angular'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SelectRegionComponent } from './select-region.component'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + ], + declarations: [ SelectRegionComponent ] +}) +export class SelectRegionModule { +} diff --git a/example/src/app/select-region/select-region.page.html b/example/src/app/select-region/select-region.page.html new file mode 100644 index 0000000..db792b9 --- /dev/null +++ b/example/src/app/select-region/select-region.page.html @@ -0,0 +1,23 @@ + + + + Select Region + + + + + + + + Select Region + + + + + + + {{reg.key}} + {{reg.baseUrl}} + + + From d10c625478c4548a3c657dca5b7ec127c42b4ecf Mon Sep 17 00:00:00 2001 From: David Antoon Date: Wed, 20 Dec 2023 15:54:24 +0200 Subject: [PATCH 4/5] - Update frontegg native SDKs - Add option to configure frontegg native modules with capacitor config - Add support for opening sso / social login in external browser - Bug fixes and performance improvement --- FronteggIonicCapacitor.podspec | 2 +- android/build.gradle | 4 +- .../frontegg/ionic/FronteggNativePlugin.java | 119 +++++++++++------- .../main/java/com/frontegg/ionic/Parser.java | 26 ++++ example/android/app/build.gradle | 2 - .../android/app/src/main/AndroidManifest.xml | 2 +- example/capacitor.config.ts | 13 ++ example/ios/App/App.xcodeproj/project.pbxproj | 8 +- example/ios/App/App/Frontegg.plist | 8 +- example/package-lock.json | 4 +- example/src/app/region.guard.ts | 1 + ios/Plugin/FronteggNativePlugin.swift | 101 ++++++++++----- ios/Podfile | 2 +- src/definitions.ts | 40 ++++++ src/frontegg.service.ts | 13 +- 15 files changed, 254 insertions(+), 91 deletions(-) diff --git a/FronteggIonicCapacitor.podspec b/FronteggIonicCapacitor.podspec index c5cf567..99877c3 100644 --- a/FronteggIonicCapacitor.podspec +++ b/FronteggIonicCapacitor.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '14.0' s.dependency 'Capacitor' - s.dependency "FronteggSwift", "1.2.1" + s.dependency "FronteggSwift", "1.2.3" s.swift_version = '5.1' s.pod_target_xcconfig = { 'CODE_SIGNING_ALLOWED' => 'YES' diff --git a/android/build.gradle b/android/build.gradle index 7f81048..eac7a7f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,7 +21,7 @@ android { namespace "com.frontegg.ionic" compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 33 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22 + minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 26 targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 33 versionCode 1 versionName "1.0" @@ -55,7 +55,7 @@ dependencies { implementation "androidx.browser:browser:1.5.0" implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1' implementation 'com.google.code.gson:gson:2.8.9' - implementation 'com.frontegg.sdk:android:1.2.0' + implementation 'com.frontegg.sdk:android:1.2.2' testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" diff --git a/android/src/main/java/com/frontegg/ionic/FronteggNativePlugin.java b/android/src/main/java/com/frontegg/ionic/FronteggNativePlugin.java index 47f1e96..cfaa8fe 100644 --- a/android/src/main/java/com/frontegg/ionic/FronteggNativePlugin.java +++ b/android/src/main/java/com/frontegg/ionic/FronteggNativePlugin.java @@ -7,14 +7,21 @@ import com.frontegg.android.FronteggApp; import com.frontegg.android.FronteggAuth; import com.frontegg.android.models.User; +import com.frontegg.android.regions.RegionConfig; import com.getcapacitor.JSObject; import com.getcapacitor.Plugin; import com.getcapacitor.PluginCall; +import com.getcapacitor.PluginConfig; import com.getcapacitor.PluginMethod; import com.getcapacitor.annotation.CapacitorPlugin; -import java.lang.reflect.Field; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -26,18 +33,46 @@ @CapacitorPlugin(name = "FronteggNative") public class FronteggNativePlugin extends Plugin { private Disposable disposable = null; - private Debouncer debouncer = new Debouncer(50); // 200ms delay + private final Debouncer debouncer = new Debouncer(50); // 200ms delay @Override public void load() { - Map constants = this.getConstants(); - FronteggApp.Companion.init( - Objects.requireNonNull(constants.get("baseUrl")), - Objects.requireNonNull(constants.get("clientId")), - this.getContext() - ); + // for regions initialization + List regions = new ArrayList<>(); + JSONArray array; + try { + array = this.getConfig().getConfigJSON().getJSONArray("regions"); + + for (int i = 0; i < array.length(); i++) { + JSONObject regionJson = (JSONObject) array.get(i); + regions.add(new RegionConfig( + regionJson.getString("key"), + regionJson.getString("baseUrl"), + regionJson.getString("clientId") + )); + } + + } catch (JSONException e) { + throw new RuntimeException(e); + } + if(regions.size() == 0) { + PluginConfig config = this.getConfig(); + String baseUrl = config.getString("baseUrl"); + String clientId = config.getString("clientId"); + String regionKey = config.getString("regionKey"); + if (baseUrl == null || clientId == null || regionKey == null) { + throw new RuntimeException("Missing required config parameters: baseUrl, clientId, regionKey"); + } + FronteggApp.Companion.init( + baseUrl, + clientId, + this.getContext() + ); + }else { + FronteggApp.Companion.initWithRegions(regions, this.getContext()); + } FronteggAuth auth = FronteggAuth.Companion.getInstance(); @@ -73,6 +108,7 @@ private JSObject getData() { boolean isLoading = auth.isLoading().getValue(); boolean initializing = auth.getInitializing().getValue(); boolean showLoader = auth.getShowLoader().getValue(); + RegionConfig selectedRegion = auth.getSelectedRegion(); JSObject data = new JSObject(); @@ -85,6 +121,7 @@ private JSObject getData() { data.put("isLoading", isLoading); data.put("initializing", initializing); data.put("showLoader", showLoader); + data.put("selectedRegion", Parser.regionToJSONObject(selectedRegion)); return data; } @@ -117,6 +154,22 @@ public void switchTenant(PluginCall call) { }); } + + @PluginMethod + public void initWithRegion(PluginCall call) { + String regionKey = call.getString("regionKey"); + if (regionKey == null) { + call.reject("No regionKey provided"); + return; + } + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(() -> { + FronteggApp.Companion.getInstance().initWithRegion(regionKey); + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(call::resolve); + }); + } + @PluginMethod public void refreshToken(PluginCall call) { ExecutorService executor = Executors.newSingleThreadExecutor(); @@ -133,43 +186,23 @@ public void getAuthState(PluginCall call) { } - public Map getConstants() { + @PluginMethod + public void getConstants(PluginCall call) { + + String baseUrl = FronteggAuth.Companion.getInstance().getBaseUrl(); + String clientId = FronteggAuth.Companion.getInstance().getBaseUrl(); String packageName = getContext().getPackageName(); - String className = packageName + ".BuildConfig"; - try { - Class buildConfigClass = Class.forName(className); - - // Get the field from BuildConfig class - Field baseUrlField = buildConfigClass.getField("FRONTEGG_DOMAIN"); - Field clientIdField = buildConfigClass.getField("FRONTEGG_CLIENT_ID"); - String baseUrl = (String) baseUrlField.get(null); // Assuming it's a String - String clientId = (String) clientIdField.get(null); // Assuming it's a String - - Map resultMap = new HashMap<>(); - resultMap.put("baseUrl", baseUrl); - resultMap.put("clientId", clientId); - resultMap.put("bundleId", packageName); - - return resultMap; - } catch (ClassNotFoundException e) { - System.out.println("Class not found: " + className); - throw new RuntimeException(e); - } catch (NoSuchFieldException e) { - System.out.println( - "Field not found in BuildConfig: " + - "buildConfigField \"String\", 'FRONTEGG_DOMAIN', \"\\\"$fronteggDomain\\\"\"\n" + - "buildConfigField \"String\", 'FRONTEGG_CLIENT_ID', \"\\\"$fronteggClientId\\\"\"" - ); - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - System.out.println( - "Access problem with field in BuildConfig: " + - "buildConfigField \"String\", 'FRONTEGG_DOMAIN', \"\\\"$fronteggDomain\\\"\"\n" + - "buildConfigField \"String\", 'FRONTEGG_CLIENT_ID', \"\\\"$fronteggClientId\\\"\"" - ); - throw new RuntimeException(e); - } + List regionsData = FronteggApp.Companion.getInstance().getRegions(); + + JSObject resultMap = new JSObject(); + resultMap.put("baseUrl", baseUrl); + resultMap.put("clientId", clientId); + resultMap.put("bundleId", packageName); + resultMap.put("isRegional", regionsData.size() > 0); + resultMap.put("regionData", Parser.regionsToJSONObject(regionsData)); + + call.resolve(resultMap); } } diff --git a/android/src/main/java/com/frontegg/ionic/Parser.java b/android/src/main/java/com/frontegg/ionic/Parser.java index 8a4a24b..c783732 100644 --- a/android/src/main/java/com/frontegg/ionic/Parser.java +++ b/android/src/main/java/com/frontegg/ionic/Parser.java @@ -2,11 +2,16 @@ import com.frontegg.android.models.User; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import com.frontegg.android.regions.RegionConfig; import com.google.gson.Gson; +import java.util.List; +import java.util.Map; + public class Parser { @@ -19,4 +24,25 @@ public static JSONObject userToJSONObject(User user) { } } + + public static JSONObject regionToJSONObject(RegionConfig regionConfig) { + String jsonStr = new Gson().toJson(regionConfig, RegionConfig.class); + try { + return new JSONObject(jsonStr); + } catch (JSONException e) { + return null; + } + } + public static JSONArray regionsToJSONObject(List regions) { + + JSONArray regionsData = new JSONArray(); + for (RegionConfig regionConfig : regions) { + JSONObject region = regionToJSONObject(regionConfig); + if(region != null) { + regionsData.put(region); + } + } + return regionsData; + } + } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index dbe1858..b838e91 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -27,8 +27,6 @@ android { "frontegg_client_id": fronteggClientId ] - buildConfigField "String", 'FRONTEGG_DOMAIN', "\"$fronteggDomain\"" - buildConfigField "String", 'FRONTEGG_CLIENT_ID', "\"$fronteggClientId\"" } buildTypes { release { diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index dec9d8b..6c323fd 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ - baseUrl - https://auth.davidantoon.me clientId b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca - logLevel - trace + baseUrl + https://auth.davidantoon.me + lateInit + diff --git a/example/package-lock.json b/example/package-lock.json index ec74910..a1e9d8b 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -3098,7 +3098,7 @@ "node_modules/@frontegg/ionic-capacitor": { "version": "1.0.0", "resolved": "file:../frontegg-ionic-capacitor.tgz", - "integrity": "sha512-Giv7RAinGkASHzR3j2F33p0ilandJTbD4XhJBzx5HqaFSpbGiIniOiKNlgwbrkvWrGBH8w/1XdmSy+VcT9Beww==", + "integrity": "sha512-0v9pctbxfWTnJmUV58yzTKX115zbQmYM42bF7560WJaVQuPD9CDDOKNe/VfpBOR/T8L92SIx0hnrcOH/B3YC8g==", "license": "MIT", "dependencies": { "@frontegg/rest-api": "^3.1.5" @@ -17841,7 +17841,7 @@ }, "@frontegg/ionic-capacitor": { "version": "file:../frontegg-ionic-capacitor.tgz", - "integrity": "sha512-Giv7RAinGkASHzR3j2F33p0ilandJTbD4XhJBzx5HqaFSpbGiIniOiKNlgwbrkvWrGBH8w/1XdmSy+VcT9Beww==", + "integrity": "sha512-0v9pctbxfWTnJmUV58yzTKX115zbQmYM42bF7560WJaVQuPD9CDDOKNe/VfpBOR/T8L92SIx0hnrcOH/B3YC8g==", "requires": { "@frontegg/rest-api": "^3.1.5" } diff --git a/example/src/app/region.guard.ts b/example/src/app/region.guard.ts index efec3f4..6ad129d 100644 --- a/example/src/app/region.guard.ts +++ b/example/src/app/region.guard.ts @@ -14,6 +14,7 @@ export class RegionGuard { * Reload the page to trigger canActivate function again */ this.fronteggService.$selectedRegion.subscribe(async () => { + window.location.reload() }); } diff --git a/ios/Plugin/FronteggNativePlugin.swift b/ios/Plugin/FronteggNativePlugin.swift index 07bd7e5..7f8b3c1 100644 --- a/ios/Plugin/FronteggNativePlugin.swift +++ b/ios/Plugin/FronteggNativePlugin.swift @@ -11,19 +11,62 @@ import Capacitor public class FronteggNativePlugin: CAPPlugin { public let fronteggApp = FronteggApp.shared var cancellables = Set() - + private var workItem: DispatchWorkItem? private let delay: TimeInterval = 0.05 // 200ms delay - + func debounce(_ action: @escaping () -> Void) { workItem?.cancel() let newWorkItem = DispatchWorkItem(block: action) DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: newWorkItem) workItem = newWorkItem } - + override public func load() { - + + let config = self.getConfig() + + let handleLoginWithSocialLogin = config.getBoolean("handleLoginWithSocialLogin", true) + let handleLoginWithSSO = config.getBoolean("handleLoginWithSSO", false) + + if let array = config.getArray("regions"), + array.count > 0 { + print("region initialization") + var regions:[RegionConfig] = [] + + array.forEach {item in + if let dict = item as? [String:String] { + regions.append(RegionConfig( + key: dict["key"]!, + baseUrl: dict["baseUrl"]!, + clientId: dict["clientId"]! + )) + } + } + + if(regions.isEmpty){ + print("Frontegg Error: Missing regions configurations") + exit(1) + } + fronteggApp.manualInitRegions(regions: regions, + handleLoginWithSocialLogin: handleLoginWithSocialLogin, + handleLoginWithSSO: handleLoginWithSSO) + } else { + print("standard initialization") + if let baseUrl = config.getString("baseUrl"), + let clientId = config.getString("clientId") { + fronteggApp.manualInit(baseUrl: baseUrl, + cliendId: clientId, + handleLoginWithSocialLogin: handleLoginWithSocialLogin, + handleLoginWithSSO: handleLoginWithSSO) + }else { + print("Frontegg Error: Missing baseUrl or clientId in project configurations") + exit(1) + } + } + + + let auth = fronteggApp.auth var anyChange: AnyPublisher { return Publishers.Merge8 ( @@ -38,26 +81,26 @@ public class FronteggNativePlugin: CAPPlugin { ) .eraseToAnyPublisher() } - + anyChange.sink(receiveValue: { () in self.debounce() { self.sendEvent() } }).store(in: &cancellables) - + self.sendEvent() } - - - + + + func sendEvent() { let auth = fronteggApp.auth - + var jsonUser: [String: Any]? = nil if let userData = try? JSONEncoder().encode(auth.user) { jsonUser = try? JSONSerialization.jsonObject(with: userData, options: .allowFragments) as? [String: Any] } - + let body: [String: Any?] = [ "accessToken": auth.accessToken, "refreshToken": auth.refreshToken, @@ -68,13 +111,13 @@ public class FronteggNativePlugin: CAPPlugin { "showLoader": auth.showLoader, "selectedRegion": regionToJson(auth.selectedRegion) ] - + self.notifyListeners("onFronteggAuthEvent", data: body as [String : Any]) } - - + + func regionToJson(_ region: RegionConfig?) -> [String:String]? { - + if let reg = region { return [ "baseUrl": reg.baseUrl, @@ -86,17 +129,17 @@ public class FronteggNativePlugin: CAPPlugin { } } func regionsToJson(_ regions: [RegionConfig]) -> [[String:String]] { - + var regionData: [[String:String]] = [] regions.forEach { reg in if let region = regionToJson(reg) { regionData.append(region) } } - + return regionData } - + @objc func getConstants(_ call: CAPPluginCall) { call.resolve([ "baseUrl": fronteggApp.baseUrl, @@ -106,43 +149,43 @@ public class FronteggNativePlugin: CAPPlugin { "regionData": regionsToJson(fronteggApp.auth.regionData) ]) } - + @objc func login(_ call: CAPPluginCall) { DispatchQueue.main.sync { fronteggApp.auth.login() } call.resolve() } - + @objc func logout(_ call: CAPPluginCall) { DispatchQueue.main.sync { fronteggApp.auth.logout() } call.resolve() } - + @objc func switchTenant(_ call: CAPPluginCall) { guard let tenantId = call.options["tenantId"] as? String else { call.reject("No tenantId provided") return } - + fronteggApp.auth.switchTenant(tenantId: tenantId) { _ in call.resolve() } } - + @objc func initWithRegion(_ call: CAPPluginCall) { guard let regionKey = call.options["regionKey"] as? String else { call.reject("No regionKey provided") return } - + fronteggApp.initWithRegion(regionKey: regionKey) } - + @objc func refreshToken(_ call: CAPPluginCall) { - + DispatchQueue.global(qos: .background).async { Task { await self.fronteggApp.auth.refreshTokenIfNeeded() @@ -150,14 +193,14 @@ public class FronteggNativePlugin: CAPPlugin { } } } - + @objc func getAuthState(_ call: CAPPluginCall) { let auth = fronteggApp.auth var jsonUser: [String: Any]? = nil if let userData = try? JSONEncoder().encode(auth.user) { jsonUser = try? JSONSerialization.jsonObject(with: userData, options: .allowFragments) as? [String: Any] } - + let body: [String: Any?] = [ "accessToken": auth.accessToken, "refreshToken": auth.refreshToken, @@ -170,5 +213,5 @@ public class FronteggNativePlugin: CAPPlugin { ] call.resolve(body as [String: Any] ) } - + } diff --git a/ios/Podfile b/ios/Podfile index a7e6ba1..7959898 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -5,7 +5,7 @@ def capacitor_pods use_frameworks! pod 'Capacitor', :path => '../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' - pod 'FronteggSwift', "1.2.1" + pod 'FronteggSwift', "1.2.3" end target 'Plugin' do diff --git a/src/definitions.ts b/src/definitions.ts index 20853b6..0ff75a8 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -1,3 +1,4 @@ + import type { ListenerCallback, PluginListenerHandle } from '@capacitor/core'; import type { ITenantsResponse, IUserProfile } from '@frontegg/rest-api'; @@ -54,3 +55,42 @@ export interface FronteggNativePlugin { refreshToken(): Promise; } + + + +export type RegionConfig = { + key: string; + baseUrl: string; + clientId: string; +}; + + + +type FronteggNativeStandardOptions = { + baseUrl: string; + clientId: string; + +} +type FronteggNativeRegionOptions = { + /** + * This is an array of regions to be used as frontegg app. + * + * @since 1.0.0 + * @example [{key: "us", baseUrl: "https://us-api.frontegg.com", clientId: "us-client-id"}] + */ + regions?: RegionConfig[]; +} +type FronteggNativeOptions = (FronteggNativeStandardOptions | FronteggNativeRegionOptions) & { + handleLoginWithSocialLogin?: boolean; + handleLoginWithSSO?: boolean; +} +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +declare module "@capacitor/cli" { + export interface PluginsConfig { + /** + * You can configure the way the push notifications are displayed when the app is in foreground. + */ + FronteggNative?: FronteggNativeOptions; + } +} diff --git a/src/frontegg.service.ts b/src/frontegg.service.ts index b985292..60d1125 100644 --- a/src/frontegg.service.ts +++ b/src/frontegg.service.ts @@ -46,11 +46,12 @@ export class FronteggService { user: `${state.user}`, // prevent log full user object // null | undefined | [object Object] accessToken: state.accessToken && state.accessToken.length > 50 ? `${state.accessToken.slice(0, 50)}...` : state.accessToken, refreshToken: state.refreshToken, + selectedRegion: state.selectedRegion, }) const keys = this.orderedListenerKeys; keys.forEach(key => { - if (this.state[key] !== state[key]) { + if (this.isChanged(this.state[key], state[key])) { console.log('onFronteggAuthEvent key: ', key); (this.state as any)[key] = state[key]; this.mapListeners[key].forEach((listener: any) => listener(state[key])) @@ -65,7 +66,7 @@ export class FronteggService { const keys = Object.keys(this.mapListeners) for (const item of keys) { const key = item as keyof FronteggState - if (this.state[key] !== state[key]) { + if (this.isChanged(this.state[key], state[key])) { (this.state as any)[key] = state[key] this.mapListeners[key].forEach((listener: any) => listener(state[key])) } @@ -73,6 +74,14 @@ export class FronteggService { }) } + private isChanged(value1: any, value2: any): boolean { + if (value1 == value2) { + return false; + } + return JSON.stringify(value1) != JSON.stringify(value2); + + } + public getState() { return this.state; } From 6fccff27bbcdb8335008dc5422c210109c7e0e21 Mon Sep 17 00:00:00 2001 From: David Antoon Date: Wed, 20 Dec 2023 17:21:36 +0200 Subject: [PATCH 5/5] update readme and add breaking changes --- FronteggIonicCapacitor.podspec | 2 +- README.md | 273 +++++++++++++++++++++++++---- example/ios/App/App/Frontegg.plist | 5 +- example/package-lock.json | 4 +- ios/Podfile | 2 +- package.json | 2 +- 6 files changed, 249 insertions(+), 39 deletions(-) diff --git a/FronteggIonicCapacitor.podspec b/FronteggIonicCapacitor.podspec index 99877c3..b39898b 100644 --- a/FronteggIonicCapacitor.podspec +++ b/FronteggIonicCapacitor.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '14.0' s.dependency 'Capacitor' - s.dependency "FronteggSwift", "1.2.3" + s.dependency "FronteggSwift", "1.2.4" s.swift_version = '5.1' s.pod_target_xcconfig = { 'CODE_SIGNING_ALLOWED' => 'YES' diff --git a/README.md b/README.md index 882019d..f341734 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,29 @@ features for the product-led era. - [Project Requirements](#project-requirements) - [Getting Started](#getting-started) - - [Prepare Frontegg workspace](#prepare-frontegg-workspace) - - [Setup Hosted Login](#setup-hosted-login) - - [Add frontegg package to the project](#add-frontegg-package-to-the-project) - - [Configure your application](#configure-your-application) + - [Prepare Frontegg workspace](#prepare-frontegg-workspace) + - [Setup Hosted Login](#setup-hosted-login) + - [Add frontegg package to the project](#add-frontegg-package-to-the-project) + - [Configure your application](#configure-your-application) - [Setup iOS Project](#setup-ios-project) - - [Create Frontegg plist file](#create-frontegg-plist-file) - - [Config iOS associated domain](#config-ios-associated-domain) + - [Create Frontegg plist file](#create-frontegg-plist-file) + - [Config iOS associated domain](#config-ios-associated-domain) - [Setup Android Project](#setup-android-project) - - [Set minimum SDK version](#set-minimum-sdk-version) - - [Configure build config fields](#configure-build-config-fields) - - [Config Android AssetLinks](#config-ios-associated-domain) + - [Set minimum SDK version](#set-minimum-sdk-version) + - [Configure build config fields](#configure-build-config-fields) + - [Config Android AssetLinks](#config-ios-associated-domain) - [Angular Usages](#angular-usages) - - [Integrate Frontegg](#integrate-frontegg) - - [Protect Routes](#protect-routes) - - [Get Logged In User](#get-logged-in-user) - - [Switch Tenant](#switch-tenant) + - [Integrate Frontegg](#integrate-frontegg) + - [Protect Routes](#protect-routes) + - [Get Logged In User](#get-logged-in-user) + - [Switch Tenant](#switch-tenant) - [Embedded Webview vs Hosted](#embedded-webview-vs-hosted) - - [Enable hosted webview in iOS Platform](#enable-hosted-webview-in-ios-platform) - - [Enable hosted webview in Android Platform](#enable-hosted-webview-in-android-platform) + - [Enable hosted webview in iOS Platform](#enable-hosted-webview-in-ios-platform) + - [Enable hosted webview in Android Platform](#enable-hosted-webview-in-android-platform) +- [Multi-Region Support](#multi-region-support) + - [Step 1: Add regions to your Frontegg configuration](#step-1-add-regions-to-your-frontegg-configuration) + - [Setup multi-region support for iOS Platform](#setup-multi-region-support-for-ios-platform) + - [Setup multi-region support for Android Platform](#setup-multi-region-support-for-android-platform) ## Project Requirements @@ -94,6 +98,13 @@ yarn add @frontegg/react-native android: { path: 'android', }, + + plugins: { + FronteggNative:{ + baseUrl: 'https://{FRONTEGG_DOMAIN_HOST.com}', + clientId: '{FRONTEGG_CLIENT_ID}', + } + } }; export default config; @@ -127,10 +138,8 @@ To setup your SwiftUI application to communicate with Frontegg. - baseUrl - https://[DOMAIN_HOST_FROM_PREVIOUS_STEP] - clientId - [CLIENT_ID_FROM_PREVIOUS_STEP] + lateInit + ``` @@ -234,9 +243,6 @@ android { "frontegg_domain" : fronteggDomain, "frontegg_client_id": fronteggClientId ] - - buildConfigField "String", 'FRONTEGG_DOMAIN', "\"$fronteggDomain\"" - buildConfigField "String", 'FRONTEGG_CLIENT_ID', "\"$fronteggClientId\"" } @@ -516,8 +522,8 @@ Frontegg SDK supports two authentication methods: - Embedded Webview - Hosted Webview - - `iOS`: ASWebAuthenticationSession - - `Android`: Custom Chrome Tab + - `iOS`: ASWebAuthenticationSession + - `Android`: Custom Chrome Tab By default, Frontegg SDK will use Embedded Webview. @@ -530,14 +536,11 @@ To use ASWebAuthenticationSession you have to set `embeddedMode` to `NO` in `Fro - baseUrl - https://[DOMAIN_HOST_FROM_PREVIOUS_STEP] - clientId - [CLIENT_ID_FROM_PREVIOUS_STEP] - + lateInit + embeddedMode - + @@ -564,3 +567,213 @@ the application manifest: ``` + +## Multi-Region Support + +This guide outlines the steps to configure your Ionic application to support multiple regions. + +### Step 1: Add regions to your Frontegg configuration + +Add `region` to your Frontegg configuration in `capacitor.config.ts` file: + +Find example code in [example/capacitor.config.ts](example/capacitor.config.ts) file. + +```typescript +import { CapacitorConfig } from '@capacitor/cli'; + +const config: CapacitorConfig = { + /*...*/ + plugins: { + + /*...*/ + + FronteggNative: { + + /** Remove baseUrl and clientId from here */ + // baseUrl: 'https://{FRONTEGG_DOMAIN_HOST.com}', + // clientId: '{FRONTEGG_CLIENT_ID}', + + regions: [ { + key: 'REGION_1_KEY', + baseUrl: 'https://region1.forntegg.com', + clientId: 'REGION_1_CLIEND_ID', + }, { + key: 'REGION_2_KEY', + baseUrl: 'https://region2.forntegg.com', + clientId: 'REGION_2_CLIEND_ID', + } ] + } + } +}; + +export default config; + +``` + +### Step 2: Create region guard service + +Create region guard service, this guard will prevent application init if region not selected, +and checks if specific region selected by getting the native state from the Frontegg SDK. +If the region not exists, the guard will redirect to region selector page. + +Find example code in [example/src/app/region.guard.ts](example/src/app/region.guard.ts) file. + +```typescript +import { CanActivateFn, Router } from '@angular/router'; +import { Inject, Injectable } from '@angular/core'; +import { FronteggService } from '@frontegg/ionic-capacitor'; + +@Injectable({ + providedIn: 'root' +}) +export class RegionGuard { + constructor(@Inject('Frontegg') private fronteggService: FronteggService, private router: Router) { + /** + * Listens to $isAuthenticated changes + * Reload the page to trigger canActivate function again + */ + this.fronteggService.$selectedRegion.subscribe(async () => { + window.location.reload() + }); + } + + canActivate: CanActivateFn = async () => { + const { isRegional } = await this.fronteggService.getConstants(); + const nativeState = await this.fronteggService.getNativeState() + + if (!isRegional || nativeState.selectedRegion != null) { + /** + * region already selected, activate navigation + */ + return true + } + + /** + * region not selected, redirect to region selector page + */ + return this.router.navigate([ '/select-region' ]) + } +} +``` + +### Step 3: Add region guard to application router + +Find example code in [example/src/app/app-routing.module.ts](example/src/app/app-routing.module.ts) file. + +```typescript +const routes: Routes = [ + { + path: '', + canActivate: [ RegionGuard ], + children: [ + /** + * Wrap all routes with region guard + * to redirect to region selector page + * if region not exists + */ + { + path: '', + canActivate: [ AuthGuard ], + loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule) + }, + ] + }, { + /** + * Add region selector page + * to select region if not exists + */ + path: 'select-region', + component: SelectRegionComponent + } +]; +``` + +### Step 4: Setup multi-region support for iOS Platform + +Following guide outlines the steps to configure iOS application to support multiple regions. + +**First, Adjust Your Frontegg.plist File for Multiple Regions:** + - Remove the existing `baseUrl` and `clientId` keys. + - Add a new boolean property, `lateInit`, and set it to `true`. + + Example Frontegg.plist Structure: + ```xml + + + + + lateInit + + + + ``` +**Secondly, Add Associated Domains for Each Region:** + +Configure the associated domains for each region in your application's settings. This step is crucial for correct API routing and authentication. +Follow the guide [Config iOS Associated Domain](#config-ios-associated-domain) to add your iOS associated domain to your Frontegg application. + + +### Step 5: Setup multi-region support for Android Platform + +Following guide outlines the steps to configure Android application to support multiple regions. + +**First, Modify the Build.gradle file** + - remove buildConfigFields from your build.gradle file: `legacy` + + ```groovy + + android { + // remove this lines: + // buildConfigField "String", 'FRONTEGG_DOMAIN', "\"$fronteggDomain\"" + // buildConfigField "String", 'FRONTEGG_CLIENT_ID', "\"$fronteggClientId\"" + } + ``` + +**Secondly, Add AssetLinks for Each Region:** + +For each region, configuring your Android `AssetLinks`. This is vital for proper API routing and authentication. +Follow [Config Android AssetLinks](#config-android-assetlinks) to add your Android domains to your Frontegg application. + +**Lastly, Add Intent-Filter in Manifest.xml:** + +The first domain will be placed automatically in the `AndroidManifest.xml` file. For each additional region, you will +need to add an `intent-filter`. + +NOTE: if you are using `Custom Chrome Tab` you have to use `android:name` `com.frontegg.android.HostedAuthActivity` instead of `com.frontegg.android.EmbeddedAuthActivity` + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` diff --git a/example/ios/App/App/Frontegg.plist b/example/ios/App/App/Frontegg.plist index 415421c..76283c3 100644 --- a/example/ios/App/App/Frontegg.plist +++ b/example/ios/App/App/Frontegg.plist @@ -2,10 +2,7 @@ - clientId - b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca - baseUrl - https://auth.davidantoon.me + lateInit diff --git a/example/package-lock.json b/example/package-lock.json index a1e9d8b..cab962a 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -3098,7 +3098,7 @@ "node_modules/@frontegg/ionic-capacitor": { "version": "1.0.0", "resolved": "file:../frontegg-ionic-capacitor.tgz", - "integrity": "sha512-0v9pctbxfWTnJmUV58yzTKX115zbQmYM42bF7560WJaVQuPD9CDDOKNe/VfpBOR/T8L92SIx0hnrcOH/B3YC8g==", + "integrity": "sha512-7eOjLESUhXQTlL7nVjnVBKkB2cuUC8RJ7KEy7hAlHsQVsLj5t4SxmNsDeNmNTc1H/+qHzjx1vPZzNoenjSRcYg==", "license": "MIT", "dependencies": { "@frontegg/rest-api": "^3.1.5" @@ -17841,7 +17841,7 @@ }, "@frontegg/ionic-capacitor": { "version": "file:../frontegg-ionic-capacitor.tgz", - "integrity": "sha512-0v9pctbxfWTnJmUV58yzTKX115zbQmYM42bF7560WJaVQuPD9CDDOKNe/VfpBOR/T8L92SIx0hnrcOH/B3YC8g==", + "integrity": "sha512-7eOjLESUhXQTlL7nVjnVBKkB2cuUC8RJ7KEy7hAlHsQVsLj5t4SxmNsDeNmNTc1H/+qHzjx1vPZzNoenjSRcYg==", "requires": { "@frontegg/rest-api": "^3.1.5" } diff --git a/ios/Podfile b/ios/Podfile index 7959898..1896f81 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -5,7 +5,7 @@ def capacitor_pods use_frameworks! pod 'Capacitor', :path => '../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' - pod 'FronteggSwift', "1.2.3" + pod 'FronteggSwift', "1.2.4" end target 'Plugin' do diff --git a/package.json b/package.json index e46ea95..a2a108e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@frontegg/ionic-capacitor", - "version": "1.0.0", + "version": "2.0.0", "description": "Frontegg Ionic Capacitor SDK", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js",