diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3385f9..cf947edd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,9 @@ Bump frontegg-kotlin sdk to 1.3.32. Bump frontegg-ios sfk to 1.3.7. ## v1.0.47 -## Summary -- Trust server tenant on fresh login instead of stale cache. -- Gate `AndroidDebugConfigurationChecker` on host app debuggable flag. +## Summary +- Trust server tenant on fresh login instead of stale cache. +- Gate `AndroidDebugConfigurationChecker` on host app debuggable flag. ## v1.0.46 Bump frontegg-android-kotlin version to 1.3.28. @@ -19,10 +19,10 @@ Bump frontegg-android-kotlin sdk to 1.3.27. Bump frontegg-ios-swift version to 1.3.3. ## v1.0.43 -Added support for disable automatic token refresh +Added support for disable automatic token refresh -## v1.0.42 -- Bump frontegg-android-kotlin sdk to 1.3.25 version. +## v1.0.42 +- Bump frontegg-android-kotlin sdk to 1.3.25 version. - Added offlie mode support for flutter-sdk. ## v1.0.41 diff --git a/android/build.gradle b/android/build.gradle index 02157b70..bfe5a85c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -66,7 +66,7 @@ dependencies { testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'org.mockito:mockito-core:5.0.0' - implementation 'com.frontegg.sdk:android:1.3.32' + implementation 'com.frontegg.sdk:android:1.3.34' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation 'androidx.core:core-ktx:1.10.0' diff --git a/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt b/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt index cf0a8ddc..3ffc0b43 100644 --- a/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt +++ b/android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt @@ -2,6 +2,7 @@ package com.frontegg.flutter import android.content.Context import android.util.Base64 +import com.frontegg.android.AdminPortalActivity import com.frontegg.android.EmbeddedAuthActivity import com.frontegg.android.exceptions.FronteggException import com.frontegg.android.fronteggAuth @@ -55,6 +56,8 @@ class FronteggMethodCallHandler( "loadEntitlements" -> loadEntitlements(call, result) + "openAdminPortal" -> openAdminPortal(result) + else -> result.notImplemented() } } @@ -424,4 +427,19 @@ class FronteggMethodCallHandler( result.success(success) } } + + private fun openAdminPortal(result: MethodChannel.Result) { + val activity = activityProvider.getActivity() + if (activity == null) { + result.error( + "NO_ACTIVITY", + "Cannot open Admin Portal without an active Activity", + null, + ) + return + } + + AdminPortalActivity.open(activity) + result.success(null) + } } \ No newline at end of file diff --git a/application_id/android/app/build.gradle b/application_id/android/app/build.gradle index 9aa1ab8a..392e189b 100644 --- a/application_id/android/app/build.gradle +++ b/application_id/android/app/build.gradle @@ -128,5 +128,5 @@ flutter { dependencies { androidTestUtil "androidx.test:orchestrator:1.5.1" - implementation 'com.frontegg.sdk:android:1.3.32' + implementation 'com.frontegg.sdk:android:1.3.34' } diff --git a/application_id/lib/user_page.dart b/application_id/lib/user_page.dart index 24b9cc90..8efe7b77 100644 --- a/application_id/lib/user_page.dart +++ b/application_id/lib/user_page.dart @@ -160,6 +160,26 @@ class _UserPageState extends State { child: const Text("Sensitive action"), ), ), + Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 10.5, + right: 10.5, + bottom: 24, + ), + child: ElevatedButton( + onPressed: () async { + try { + await frontegg.openAdminPortal(); + } catch (e) { + _showFailureMessage( + "Failed to open Admin Portal: $e", + ); + } + }, + child: const Text("Open Admin Portal"), + ), + ), ], ), ), diff --git a/docs/README.md b/docs/README.md index 66c1ab1f..4d35d466 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,9 +34,9 @@ Sign up here → [https://portal.us.frontegg.com/signup](https://portal.us.front The Flutter SDK supports Frontegg's **per-tenant sessions** feature through the underlying native SDKs. -- On **Android**, the plugin and example apps use `com.frontegg.sdk:android:1.3.32`. +- On **Android**, the plugin and example apps use `com.frontegg.sdk:android:1.3.34`. - On **iOS**, the plugin depends on `FronteggSwift`: - - **Flutter 3.41+** (SPM): `1.3.7` from GitHub. Run `flutter config --enable-swift-package-manager`, then `flutter pub get` and build. + - **Flutter 3.41+** (SPM): `1.3.10` from GitHub. Run `flutter config --enable-swift-package-manager`, then `flutter pub get` and build. - Note: CocoaPods fallback is no longer supported for `FronteggSwift`. - SPM integration requires **Xcode 15+**. diff --git a/docs/advanced.md b/docs/advanced.md index 20a38609..ee13facc 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -223,3 +223,26 @@ Future performSensitiveAction() async { // Continue with the sensitive action } ``` + +## Admin Portal (Beta) + +The Admin Portal is a hosted page that lets end users manage their account, profile, sessions, and tenant settings. The Flutter SDK exposes `openAdminPortal()` which delegates to the native SDKs: + +- **Android** — launches `AdminPortalActivity` (full-screen `WebView`) +- **iOS** — presents `AdminPortalView` as a page sheet (`WKWebView`) + +The portal loads `${baseUrl}/oauth/portal?appId=` and shares the SDK session, so authenticated users are not asked to sign in again. + +> **Beta.** The API may change in future minor releases. Pin to an exact SDK version when embedding this in a shipping app. + +### Multi-app prerequisite + +For multi-app workspaces, configure `applicationId` in your native setup (see [Multi-app support](#multi-app-support)). Without `?appId=` the portal renders **"Application not found"** after sign-in. + +### Open the portal + +```dart +await frontegg.openAdminPortal(); +``` + +The portal is dismissed when the user swipes down (iOS) or taps the built-in close button. On Android, `window.close()` finishes the activity automatically. diff --git a/docs/api.md b/docs/api.md index b1c3f10e..c941c5ee 100644 --- a/docs/api.md +++ b/docs/api.md @@ -40,6 +40,7 @@ | `requestAuthorize` | - `refreshToken` (required): String
- `deviceTokenCookie`: String? | Initiates authorization request. Returns `FronteggUser` if successful, `null` if fails. | | `isSteppedUp` | `maxAge`: Duration? | Checks if user has completed step-up authentication. | | `stepUp` | `maxAge`: Duration? | Initiates the step-up authentication process. | +| `openAdminPortal` | None | Opens the embedded Frontegg Admin Portal in a native WebView. | ### Utility Methods diff --git a/embedded/android/app/build.gradle b/embedded/android/app/build.gradle index 915e87f7..f37db400 100644 --- a/embedded/android/app/build.gradle +++ b/embedded/android/app/build.gradle @@ -128,5 +128,5 @@ flutter { dependencies { androidTestUtil "androidx.test:orchestrator:1.5.1" - implementation 'com.frontegg.sdk:android:1.3.32' + implementation 'com.frontegg.sdk:android:1.3.34' } diff --git a/embedded/integration_test/e2e/embedded_e2e_test.dart b/embedded/integration_test/e2e/embedded_e2e_test.dart index 95959f07..7b0d6a0a 100644 --- a/embedded/integration_test/e2e/embedded_e2e_test.dart +++ b/embedded/integration_test/e2e/embedded_e2e_test.dart @@ -82,9 +82,8 @@ void main() { e2ePatrolTest('testRequestAuthorizeFlow', ($) async { await tc.launchApp($); await tc.waitForLoginPage($, timeout: const Duration(seconds: 35)); - await tc.tapByKey($, 'E2ESeedRequestAuthorizeTokenButtonKey'); - await Future.delayed(const Duration(seconds: 2)); - await tc.tapByKey($, 'RequestAuthorizeButtonKey'); + await tc.tapSemantics($, 'E2ESeedRequestAuthorizeTokenButton'); + await tc.tapSemantics($, 'RequestAuthorizeButton'); await tc.waitForUserEmail($, 'signup@frontegg.com', timeout: const Duration(seconds: 120)); }); diff --git a/embedded/integration_test/e2e/embedded_e2e_test_case.dart b/embedded/integration_test/e2e/embedded_e2e_test_case.dart index 40f140d5..7de76b00 100644 --- a/embedded/integration_test/e2e/embedded_e2e_test_case.dart +++ b/embedded/integration_test/e2e/embedded_e2e_test_case.dart @@ -99,16 +99,28 @@ class EmbeddedE2ETestCase { await waitForText($, email, timeout: timeout); } - Future tapSemantics(PatrolIntegrationTester $, String label, {Duration timeout = const Duration(seconds: 10)}) async { + Future tapSemantics( + PatrolIntegrationTester $, + String label, { + Duration timeout = const Duration(seconds: 10), + int maxScrollAttempts = 6, + }) async { await waitForSemantics($, label, timeout: timeout); final finder = _semFinder(label).first; - await $.tester.ensureVisible(finder); - await Future.delayed(const Duration(milliseconds: 300)); - await $.pump(); - await $.tester.tap(finder); - // Do not pump() after tap: embedded login can present a native webview - // immediately; WidgetTester.pump then blocks and never returns, so job hits CI timeout. - await Future.delayed(const Duration(milliseconds: 500)); + for (var attempt = 0; attempt <= maxScrollAttempts; attempt++) { + await $.tester.scrollUntilVisible(finder, 300); + await Future.delayed(const Duration(milliseconds: 300)); + await $.pump(); + if (finder.evaluate().isEmpty) continue; + try { + await $.tester.tap(finder); + await Future.delayed(const Duration(milliseconds: 500)); + return; + } catch (_) { + if (attempt == maxScrollAttempts) rethrow; + } + } + throw AssertionError('Could not tap semantics label=$label'); } Future tapByKey(PatrolIntegrationTester $, String keyValue, {Duration timeout = const Duration(seconds: 10)}) async { diff --git a/embedded/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/embedded/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 269da299..2a860dd5 100644 --- a/embedded/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/embedded/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/frontegg/frontegg-ios-swift.git", "state" : { - "revision" : "fd3c29aa46e1f35d03e8af21d6af394248e91fa9", - "version" : "1.3.7" + "revision" : "e702dd7d4fedd1290cee9ca9a72b0b8ed24a2fd3", + "version" : "1.3.10" } }, { diff --git a/embedded/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/embedded/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 269da299..2a860dd5 100644 --- a/embedded/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/embedded/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/frontegg/frontegg-ios-swift.git", "state" : { - "revision" : "fd3c29aa46e1f35d03e8af21d6af394248e91fa9", - "version" : "1.3.7" + "revision" : "e702dd7d4fedd1290cee9ca9a72b0b8ed24a2fd3", + "version" : "1.3.10" } }, { diff --git a/embedded/lib/user_page.dart b/embedded/lib/user_page.dart index 89740928..b392e8b9 100644 --- a/embedded/lib/user_page.dart +++ b/embedded/lib/user_page.dart @@ -211,6 +211,30 @@ class _UserPageState extends State { child: const Text("Load Entitlements"), ), ), + Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 10.5, + right: 10.5, + bottom: 8, + ), + child: Semantics( + label: 'OpenAdminPortalButton', + child: ElevatedButton( + key: const ValueKey('OpenAdminPortalButton'), + onPressed: () async { + try { + await frontegg.openAdminPortal(); + } catch (e) { + _showFailureMessage( + "Failed to open Admin Portal: $e", + ); + } + }, + child: const Text("Open Admin Portal"), + ), + ), + ), Padding( padding: const EdgeInsets.only( top: 8.0, diff --git a/hosted/android/app/build.gradle b/hosted/android/app/build.gradle index 97be3ada..49257b38 100644 --- a/hosted/android/app/build.gradle +++ b/hosted/android/app/build.gradle @@ -124,5 +124,5 @@ flutter { dependencies { androidTestUtil "androidx.test:orchestrator:1.5.1" - implementation 'com.frontegg.sdk:android:1.3.32' + implementation 'com.frontegg.sdk:android:1.3.34' } diff --git a/hosted/lib/user_page.dart b/hosted/lib/user_page.dart index ca899cab..565f05ad 100644 --- a/hosted/lib/user_page.dart +++ b/hosted/lib/user_page.dart @@ -192,6 +192,26 @@ class _UserPageState extends State { child: const Text("Request Authorize"), ), ), + Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 10.5, + right: 10.5, + bottom: 24, + ), + child: ElevatedButton( + onPressed: () async { + try { + await frontegg.openAdminPortal(); + } catch (e) { + _showFailureMessage( + "Failed to open Admin Portal: $e", + ); + } + }, + child: const Text("Open Admin Portal"), + ), + ), ], ), ), diff --git a/ios/frontegg_flutter/Package.swift b/ios/frontegg_flutter/Package.swift index 3d22ed8f..0e3e6ed5 100644 --- a/ios/frontegg_flutter/Package.swift +++ b/ios/frontegg_flutter/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(name: "FlutterFramework", path: "../FlutterFramework"), - .package(url: "https://github.com/frontegg/frontegg-ios-swift.git", exact: "1.3.7"), + .package(url: "https://github.com/frontegg/frontegg-ios-swift.git", exact: "1.3.10"), ], targets: [ .target( diff --git a/ios/frontegg_flutter/Sources/frontegg_flutter/FronteggMethodCallHandler.swift b/ios/frontegg_flutter/Sources/frontegg_flutter/FronteggMethodCallHandler.swift index c16c79d3..fa60d22e 100644 --- a/ios/frontegg_flutter/Sources/frontegg_flutter/FronteggMethodCallHandler.swift +++ b/ios/frontegg_flutter/Sources/frontegg_flutter/FronteggMethodCallHandler.swift @@ -1,6 +1,8 @@ import Foundation import FronteggSwift import Flutter +import SwiftUI +import UIKit class FronteggMethodCallHandler { private var fronteggApp: FronteggApp @@ -43,6 +45,8 @@ class FronteggMethodCallHandler { isSteppedUp(call: call, result: result) case "loadEntitlements": loadEntitlements(call: call, result: result) + case "openAdminPortal": + openAdminPortal(result: result) default: result(FlutterMethodNotImplemented) @@ -357,4 +361,43 @@ class FronteggMethodCallHandler { result(success) } } + + private func openAdminPortal(result: @escaping FlutterResult) { + DispatchQueue.main.async { + guard let viewController = Self.topViewController() else { + result(FlutterError( + code: "NO_VIEW_CONTROLLER", + message: "Cannot open Admin Portal without an active view controller", + details: nil + )) + return + } + + if #available(iOS 14.0, *) { + let host = UIHostingController(rootView: AdminPortalView()) + host.modalPresentationStyle = .pageSheet + viewController.present(host, animated: true) + result(nil) + } else { + result(FlutterError( + code: "UNSUPPORTED", + message: "Admin Portal requires iOS 14+", + details: nil + )) + } + } + } + + private static func topViewController() -> UIViewController? { + let keyWindow = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow } + + var topController = keyWindow?.rootViewController + while let presented = topController?.presentedViewController { + topController = presented + } + return topController + } } diff --git a/lib/src/frontegg_flutter.dart b/lib/src/frontegg_flutter.dart index 650a0f23..12835c7d 100644 --- a/lib/src/frontegg_flutter.dart +++ b/lib/src/frontegg_flutter.dart @@ -339,6 +339,16 @@ class FronteggFlutter { ), ); + /// Opens the embedded Frontegg Admin Portal. + /// + /// The portal loads `${baseUrl}/oauth/portal` in a native WebView that shares + /// the SDK session, so authenticated users are not asked to sign in again. + /// + /// Throws a [FronteggException] if the portal cannot be opened. + Future openAdminPortal() => _runAction(() async { + await FronteggPlatform.instance.openAdminPortal(); + }); + Future _runAction(Future Function() action) async { try { return await action(); diff --git a/lib/src/frontegg_method_channel.dart b/lib/src/frontegg_method_channel.dart index 481d37f4..9b1eb3f2 100644 --- a/lib/src/frontegg_method_channel.dart +++ b/lib/src/frontegg_method_channel.dart @@ -27,6 +27,7 @@ class FronteggMethodChannel extends FronteggPlatform { static const String stepUpMethodName = "stepUp"; static const String isSteppedUpMethodName = "isSteppedUp"; static const String loadEntitlementsMethodName = "loadEntitlements"; + static const String openAdminPortalMethodName = "openAdminPortal"; /// MethodChannel used for invoking platform-specific methods. @visibleForTesting @@ -255,4 +256,8 @@ class FronteggMethodChannel extends FronteggPlatform { }, )) ?? false; + + @override + Future openAdminPortal() => + methodChannel.invokeMethod(openAdminPortalMethodName); } diff --git a/lib/src/frontegg_platform_interface.dart b/lib/src/frontegg_platform_interface.dart index 6ad70905..152429da 100644 --- a/lib/src/frontegg_platform_interface.dart +++ b/lib/src/frontegg_platform_interface.dart @@ -112,4 +112,9 @@ abstract class FronteggPlatform extends PlatformInterface { Future forceStateUpdate() { throw UnimplementedError("forceStateUpdate() has not been implemented."); } + + /// Opens the embedded Frontegg Admin Portal. + Future openAdminPortal() { + throw UnimplementedError("openAdminPortal() has not been implemented."); + } } diff --git a/lib/src/inner_utils.dart b/lib/src/inner_utils.dart index 9e9c01e1..c0379ec3 100644 --- a/lib/src/inner_utils.dart +++ b/lib/src/inner_utils.dart @@ -1,3 +1,20 @@ extension StringEx on String { DateTime toDateTime() => DateTime.parse(this); } + +/// Reads a bool from platform-channel payloads (iOS may send `NSNumber` as `int`). +bool readPlatformBool(Object? value, {bool defaultValue = false}) { + if (value is bool) return value; + if (value is num) return value != 0; + if (value is String) { + switch (value.toLowerCase()) { + case 'true': + case '1': + return true; + case 'false': + case '0': + return false; + } + } + return defaultValue; +} diff --git a/lib/src/models/frontegg_state.dart b/lib/src/models/frontegg_state.dart index 2aebf1c0..4a255ffe 100644 --- a/lib/src/models/frontegg_state.dart +++ b/lib/src/models/frontegg_state.dart @@ -1,10 +1,5 @@ -import 'package:frontegg_flutter/frontegg_flutter.dart'; - -bool _readBool(Object? value) { - if (value is bool) return value; - if (value is num) return value != 0; - return false; -} +import 'package:frontegg_flutter/src/inner_utils.dart'; +import 'package:frontegg_flutter/src/models/frontegg_user.dart'; /// Represents the authentication state in the Frontegg system. class FronteggState { @@ -103,13 +98,13 @@ class FronteggState { accessToken: map["accessToken"] as String?, refreshToken: map["refreshToken"] as String?, user: user != null ? FronteggUser.fromMap(user) : null, - isAuthenticated: map["isAuthenticated"] as bool, - isLoading: map["isLoading"] as bool, - initializing: map["initializing"] as bool, - showLoader: map["showLoader"] as bool, - appLink: map["appLink"] as bool, - refreshingToken: map["refreshingToken"] as bool, - isOfflineMode: _readBool(map["isOfflineMode"]), + isAuthenticated: readPlatformBool(map["isAuthenticated"]), + isLoading: readPlatformBool(map["isLoading"]), + initializing: readPlatformBool(map["initializing"]), + showLoader: readPlatformBool(map["showLoader"]), + appLink: readPlatformBool(map["appLink"]), + refreshingToken: readPlatformBool(map["refreshingToken"]), + isOfflineMode: readPlatformBool(map["isOfflineMode"]), ); } diff --git a/lib/src/models/frontegg_tenant.dart b/lib/src/models/frontegg_tenant.dart index 9cb2e1df..351b401e 100644 --- a/lib/src/models/frontegg_tenant.dart +++ b/lib/src/models/frontegg_tenant.dart @@ -58,7 +58,7 @@ class FronteggTenant { creatorEmail: map["creatorEmail"] as String?, creatorName: map["creatorName"] as String?, vendorId: map["vendorId"] as String, - isReseller: map["isReseller"] as bool, + isReseller: readPlatformBool(map["isReseller"]), metadata: map["metadata"] as String, createdAt: (map["createdAt"] as String).toDateTime(), updatedAt: (map["updatedAt"] as String).toDateTime(), diff --git a/lib/src/models/frontegg_user.dart b/lib/src/models/frontegg_user.dart index a81ec75c..0ae82796 100644 --- a/lib/src/models/frontegg_user.dart +++ b/lib/src/models/frontegg_user.dart @@ -1,4 +1,5 @@ import "package:flutter/foundation.dart"; +import "package:frontegg_flutter/src/inner_utils.dart"; import "frontegg_tenant.dart"; import "frontegg_user_role.dart"; @@ -82,7 +83,7 @@ class FronteggUser { return FronteggUser( id: map["id"] as String, email: map["email"] as String, - mfaEnrolled: map["mfaEnrolled"] as bool, + mfaEnrolled: readPlatformBool(map["mfaEnrolled"]), name: map["name"] as String, profilePictureUrl: map["profilePictureUrl"] as String, phoneNumber: map["phoneNumber"] as String?, @@ -102,10 +103,10 @@ class FronteggUser { .toList(), activeTenant: FronteggTenant.fromMap(map["activeTenant"] as Map), - activatedForTenant: map["activatedForTenant"] as bool, + activatedForTenant: readPlatformBool(map["activatedForTenant"]), metadata: map["metadata"] as String?, - verified: map["verified"] as bool, - superUser: map["superUser"] as bool, + verified: readPlatformBool(map["verified"]), + superUser: readPlatformBool(map["superUser"]), ); } diff --git a/lib/src/models/frontegg_user_role.dart b/lib/src/models/frontegg_user_role.dart index 4c38cce2..e431bfab 100644 --- a/lib/src/models/frontegg_user_role.dart +++ b/lib/src/models/frontegg_user_role.dart @@ -51,7 +51,7 @@ class FronteggUserRole { return FronteggUserRole( id: map["id"] as String, key: map["key"] as String, - isDefault: map["isDefault"] as bool, + isDefault: readPlatformBool(map["isDefault"]), name: map["name"] as String, description: map["description"] as String?, permissions: (map["permissions"] as List) diff --git a/lib/src/models/frontegg_user_role_permission.dart b/lib/src/models/frontegg_user_role_permission.dart index afdfcc21..d876aa19 100644 --- a/lib/src/models/frontegg_user_role_permission.dart +++ b/lib/src/models/frontegg_user_role_permission.dart @@ -45,7 +45,7 @@ class FronteggUserRolePermission { name: map["name"] as String, categoryId: map["categoryId"] as String, description: map["description"] as String?, - fePermission: map["fePermission"] as bool, + fePermission: readPlatformBool(map["fePermission"]), createdAt: (map["createdAt"] as String).toDateTime(), updatedAt: (map["updatedAt"] as String).toDateTime(), ); diff --git a/test/src/frontegg_method_channel_test.dart b/test/src/frontegg_method_channel_test.dart index 3b2aa001..d60eea01 100644 --- a/test/src/frontegg_method_channel_test.dart +++ b/test/src/frontegg_method_channel_test.dart @@ -62,6 +62,8 @@ void main() { return null; case FronteggMethodChannel.stepUpMethodName: return null; + case FronteggMethodChannel.openAdminPortalMethodName: + return null; } throw Exception("${methodCall.method} Not Implemented"); }, @@ -131,6 +133,10 @@ void main() { await platform.stepUp(); }); + test('openAdminPortal()', () async { + await platform.openAdminPortal(); + }); + group('StateEventChannel', () { test('should return valid FronteggState Map', () async { // Asser Later diff --git a/test/src/models/frontegg_state_test.dart b/test/src/models/frontegg_state_test.dart index 5c926a2b..fe9fb1f8 100644 --- a/test/src/models/frontegg_state_test.dart +++ b/test/src/models/frontegg_state_test.dart @@ -28,12 +28,28 @@ void main() { test( "should return a valid Loaded model", - () async { - // Act + () { final result = FronteggState.fromMap(tLoadedFronteggStateMap); - // Assert expect(result, equals(tLoadedFronteggState)); }, ); + + test( + "should parse iOS platform-channel bools encoded as ints", + () { + final map = Map.from(tLoadedFronteggStateMap); + map["isAuthenticated"] = 1; + map["isLoading"] = 0; + map["initializing"] = 0; + map["showLoader"] = 0; + map["isOfflineMode"] = 0; + + final result = FronteggState.fromMap(map); + + expect(result.isAuthenticated, isTrue); + expect(result.isLoading, isFalse); + expect(result.user, isNotNull); + }, + ); }); } diff --git a/test/src/models/frontegg_user_test.dart b/test/src/models/frontegg_user_test.dart index 6ac5b476..13023c24 100644 --- a/test/src/models/frontegg_user_test.dart +++ b/test/src/models/frontegg_user_test.dart @@ -8,12 +8,37 @@ void main() { group('fromMap', () { test( "should return a valid model", - () async { - // Act + () { final result = FronteggUser.fromMap(tFronteggUserMap); - // Assert expect(result, equals(tFronteggUser)); }, ); + + test( + "should parse iOS platform-channel bools encoded as ints", + () { + final map = Map.from(tFronteggUserMap); + map["mfaEnrolled"] = 0; + map["verified"] = 1; + map["activatedForTenant"] = 1; + map["superUser"] = 0; + final tenant = Map.from(map["activeTenant"] as Map); + tenant["isReseller"] = 0; + map["activeTenant"] = tenant; + final tenants = [ + Map.from((map["tenants"] as List).first as Map) + ..["isReseller"] = 0, + ]; + map["tenants"] = tenants; + + final result = FronteggUser.fromMap(map); + + expect(result.mfaEnrolled, isFalse); + expect(result.verified, isTrue); + expect(result.activatedForTenant, isTrue); + expect(result.superUser, isFalse); + expect(result.activeTenant.isReseller, isFalse); + }, + ); }); }