Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,6 +56,8 @@ class FronteggMethodCallHandler(

"loadEntitlements" -> loadEntitlements(call, result)

"openAdminPortal" -> openAdminPortal(result)

else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion application_id/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
20 changes: 20 additions & 0 deletions application_id/lib/user_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@ class _UserPageState extends State<UserPage> {
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"),
),
),
],
),
),
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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+**.

Expand Down
23 changes: 23 additions & 0 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,26 @@ Future<void> 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=<applicationId>` 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.
1 change: 1 addition & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
| `requestAuthorize` | - `refreshToken` (required): String<br>- `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

Expand Down
2 changes: 1 addition & 1 deletion embedded/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
5 changes: 2 additions & 3 deletions embedded/integration_test/e2e/embedded_e2e_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});

Expand Down
28 changes: 20 additions & 8 deletions embedded/integration_test/e2e/embedded_e2e_test_case.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,28 @@ class EmbeddedE2ETestCase {
await waitForText($, email, timeout: timeout);
}

Future<void> tapSemantics(PatrolIntegrationTester $, String label, {Duration timeout = const Duration(seconds: 10)}) async {
Future<void> 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<void> tapByKey(PatrolIntegrationTester $, String keyValue, {Duration timeout = const Duration(seconds: 10)}) async {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions embedded/lib/user_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,30 @@ class _UserPageState extends State<UserPage> {
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,
Expand Down
2 changes: 1 addition & 1 deletion hosted/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
20 changes: 20 additions & 0 deletions hosted/lib/user_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,26 @@ class _UserPageState extends State<UserPage> {
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"),
),
),
],
),
),
Expand Down
2 changes: 1 addition & 1 deletion ios/frontegg_flutter/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Foundation
import FronteggSwift
import Flutter
import SwiftUI
import UIKit

class FronteggMethodCallHandler {
private var fronteggApp: FronteggApp
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
10 changes: 10 additions & 0 deletions lib/src/frontegg_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> openAdminPortal() => _runAction(() async {
await FronteggPlatform.instance.openAdminPortal();
});

Future<T> _runAction<T>(Future<T> Function() action) async {
try {
return await action();
Expand Down
Loading
Loading