Skip to content

Commit a7b9782

Browse files
Merge branch 'master' into redesign-for-demo-apps
Resolve example/src/app/tab1/tab1.page.html: keep the redesign button styling and add master's Open Admin Portal button in that style (keeping the aria-label for the E2E tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 parents 5e2064d + 3af7d99 commit a7b9782

59 files changed

Lines changed: 5299 additions & 9 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

FronteggIonicCapacitor.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
1313
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
1414
s.ios.deployment_target = '14.0'
1515
s.dependency 'Capacitor'
16-
s.dependency "FronteggSwift", "1.2.76"
16+
s.dependency "FronteggSwift", "1.3.10"
1717
s.swift_version = '5.1'
1818
s.pod_target_xcconfig = {
1919
'CODE_SIGNING_ALLOWED' => 'YES'

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ dependencies {
6262
implementation "androidx.browser:browser:1.8.0"
6363
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
6464
implementation 'com.google.code.gson:gson:2.10'
65-
implementation 'com.frontegg.sdk:android:1.3.18'
65+
implementation 'com.frontegg.sdk:android:1.3.34'
6666

6767
testImplementation "junit:junit:$junitVersion"
6868
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"

android/src/main/java/com/frontegg/ionic/FronteggNativePlugin.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.frontegg.ionic;
22

33

4+
45
import android.os.Handler;
56
import android.os.Looper;
67
import android.util.Log;
78

89
import kotlin.Unit;
910

11+
import com.frontegg.android.AdminPortalActivity;
1012
import com.frontegg.android.FronteggApp;
1113
import com.frontegg.android.FronteggAppKt;
1214
import com.frontegg.android.FronteggAuth;
@@ -101,6 +103,14 @@ public void load() {
101103
String clientId = config.getString("clientId");
102104
String applicationId = config.getString("applicationId");
103105

106+
// Allow E2E tests to override the base URL via system property.
107+
// Tests set this via: System.setProperty("FRONTEGG_E2E_BASE_URL", url)
108+
String e2eBaseUrl = System.getProperty("FRONTEGG_E2E_BASE_URL");
109+
if (e2eBaseUrl != null && !e2eBaseUrl.isEmpty()) {
110+
Log.i("FronteggNative", "E2E override: using base URL " + e2eBaseUrl);
111+
baseUrl = e2eBaseUrl;
112+
}
113+
104114
if (baseUrl == null || clientId == null) {
105115
throw new RuntimeException("Missing required config parameters: baseUrl, clientId");
106116
}
@@ -313,4 +323,15 @@ public void getConstants(PluginCall call) {
313323
call.resolve(resultMap);
314324
}
315325

326+
@PluginMethod
327+
public void openAdminPortal(PluginCall call) {
328+
if (this.getActivity() == null) {
329+
call.reject("NO_ACTIVITY", "Cannot open Admin Portal without an active Activity");
330+
return;
331+
}
332+
333+
AdminPortalActivity.open(this.getActivity());
334+
call.resolve();
335+
}
336+
316337
}

docs/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ For full documentation, visit the Frontegg Developer Portal:
2222

2323
---
2424

25+
## 🔐 Native SDK versions
26+
27+
The Ionic Capacitor wrapper depends on the underlying native SDKs:
28+
29+
- On **Android**, the plugin and example app use `com.frontegg.sdk:android:1.3.34`.
30+
- On **iOS**, the plugin depends on `FronteggSwift` **1.3.10** via CocoaPods.
31+
32+
After upgrading, run `pod install` in your iOS project and rebuild both platforms.
33+
34+
---
35+
36+
## 🔐 Native SDK versions
37+
38+
The Ionic Capacitor wrapper depends on the underlying native SDKs:
39+
40+
- On **Android**, the plugin and example app use `com.frontegg.sdk:android:1.3.34`.
41+
- On **iOS**, the plugin depends on `FronteggSwift` **1.3.10** via CocoaPods.
42+
43+
After upgrading, run `pod install` in your iOS project and rebuild both platforms.
44+
45+
---
46+
2547
## 🧑‍💻 Getting Started with Frontegg
2648

2749
Don't have a Frontegg account yet?

docs/_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
- [Setup Guide](setup.md)
44
- [Usage Examples](usage.md)
55
- [Advanced Topics](advanced.md)
6+
- [API Reference](api.md)

docs/advanced.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,25 @@ export default config;
244244

245245
If your app supports multiple environments or regions, define `applicationId` inside each region object:
246246

247-
```
247+
## Admin Portal (Beta)
248+
249+
The Admin Portal is a hosted page that lets end users manage their account, profile, sessions, and tenant settings. The Ionic Capacitor SDK exposes `openAdminPortal()` on `FronteggService`, which delegates to the native SDKs:
250+
251+
- **Android** — launches `AdminPortalActivity` (full-screen `WebView`)
252+
- **iOS** — presents `AdminPortalView` as a page sheet (`WKWebView`)
253+
254+
The portal loads `${baseUrl}/oauth/portal?appId=<applicationId>` and shares the SDK session, so authenticated users are not asked to sign in again.
255+
256+
> **Beta.** The API may change in future minor releases. Pin to an exact SDK version when embedding this in a shipping app.
257+
258+
### Multi-app prerequisite
259+
260+
For multi-app workspaces, configure `applicationId` in your native setup (see [Multi-apps support](#multi-apps-support)). Without `?appId=` the portal renders **"Application not found"** after sign-in.
261+
262+
### Open the portal
263+
264+
```typescript
265+
await this.fronteggService.openAdminPortal();
266+
```
267+
268+
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.

docs/usage.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,13 @@ export class MyPage implements OnInit {
287287
}
288288
}
289289
```
290+
291+
## Admin Portal (Beta)
292+
293+
Open the embedded Frontegg Admin Portal for authenticated users:
294+
295+
```typescript
296+
await this.fronteggService.openAdminPortal();
297+
```
298+
299+
See [Advanced Topics](advanced.md#admin-portal-beta) for prerequisites and platform behavior.

example/android/app/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ dependencies {
6262

6363
implementation project(':capacitor-android')
6464
implementation project(':capacitor-cordova-android-plugins')
65-
implementation 'com.frontegg.sdk:android:1.3.18'
65+
implementation 'com.frontegg.sdk:android:1.3.34'
6666

6767
testImplementation "junit:junit:$junitVersion"
6868
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
6969
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
70+
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.3.0"
71+
androidTestImplementation "androidx.test:runner:1.5.2"
72+
androidTestImplementation "androidx.test:rules:1.5.0"
73+
androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.12.0"
7074
}
7175

7276
apply from: 'capacitor.build.gradle'
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.frontegg.demo
2+
3+
import androidx.test.uiautomator.By
4+
import com.frontegg.demo.utils.UiTestInstrumentation
5+
import org.junit.Before
6+
import org.junit.Test
7+
import java.util.regex.Pattern
8+
9+
/**
10+
* Verifies that a cold launch with no persisted session shows the login page.
11+
*
12+
* Mirrors frontegg-ios-swift MultiRegion `testColdLaunchWithNoSessionShowsLoginPage`
13+
* and Embedded `testColdLaunchWithOfflineModeDisabledReachesLoginQuickly`.
14+
*
15+
* NOTE: This test assumes no prior session exists. If tests run sequentially
16+
* after a login test that doesn't clean up, this may see the authenticated
17+
* state instead. For true isolation, clear app data before running.
18+
*/
19+
class ColdLaunchTest {
20+
private lateinit var instrumentation: UiTestInstrumentation
21+
22+
@Before
23+
fun setUp() {
24+
instrumentation = UiTestInstrumentation()
25+
instrumentation.openApp()
26+
}
27+
28+
@Test
29+
fun cold_launch_shows_login_or_authenticated_state() {
30+
// On a fresh install (or after logout), the app should show the login
31+
// page with "Not Logged In" text and a "Login" button.
32+
// If a prior test left a session, we accept the authenticated state too.
33+
val loginPattern = Pattern.compile("login", Pattern.CASE_INSENSITIVE)
34+
val loginButton = instrumentation.waitForView(By.text(loginPattern), timeout = 15_000)
35+
36+
val notLoggedIn = instrumentation.waitForView(By.text("Not Logged In"), timeout = 3_000)
37+
val logoutPattern = Pattern.compile("logout", Pattern.CASE_INSENSITIVE)
38+
val logoutButton = instrumentation.waitForView(By.text(logoutPattern), timeout = 3_000)
39+
40+
// Must show EITHER the login page OR the authenticated state.
41+
val isLoginPage = loginButton != null && notLoggedIn != null
42+
val isAuthenticated = logoutButton != null
43+
44+
if (!isLoginPage && !isAuthenticated) {
45+
throw Exception(
46+
"Cold launch did not reach a valid state — " +
47+
"expected login page or authenticated state"
48+
)
49+
}
50+
}
51+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.frontegg.demo
2+
3+
import androidx.test.uiautomator.By
4+
import com.frontegg.demo.utils.Env
5+
import com.frontegg.demo.utils.UiTestInstrumentation
6+
import com.frontegg.demo.utils.delay
7+
import com.frontegg.demo.utils.logout
8+
import com.frontegg.demo.utils.tapLoginButton
9+
import org.junit.Assume.assumeTrue
10+
import org.junit.Before
11+
import org.junit.Test
12+
import java.util.regex.Pattern
13+
14+
/**
15+
* P2: Verifies Google social login via Chrome Custom Tabs.
16+
*
17+
* Mirrors frontegg-android-kotlin's LoginViaGoogleTest. The flow:
18+
* 1. Tap Login on the Ionic login page.
19+
* 2. On the Frontegg hosted login, tap the Google social button.
20+
* 3. Chrome Custom Tab opens with Google's sign-in.
21+
* 4. Authenticate with Google test credentials.
22+
* 5. Return to the app — Logout button appears.
23+
*
24+
* Requires GOOGLE_EMAIL and GOOGLE_PASSWORD instrumentation arguments.
25+
* Skipped if credentials are not provided.
26+
*/
27+
class GoogleSocialLoginTest {
28+
private lateinit var instrumentation: UiTestInstrumentation
29+
30+
@Before
31+
fun setUp() {
32+
instrumentation = UiTestInstrumentation()
33+
instrumentation.openApp()
34+
}
35+
36+
@Test
37+
fun success_login_with_google() {
38+
// Skip if Google credentials not provided.
39+
assumeTrue(
40+
"GOOGLE_EMAIL not set — skipping Google social login test",
41+
Env.googleEmail.isNotEmpty()
42+
)
43+
assumeTrue(
44+
"GOOGLE_PASSWORD not set — skipping Google social login test",
45+
Env.googlePassword.isNotEmpty()
46+
)
47+
48+
// 1. Open the Frontegg hosted login.
49+
instrumentation.tapLoginButton()
50+
51+
// 2. Tap the Google social login button on the Frontegg hosted page.
52+
// The button may be labeled "Continue with Google" or show a Google icon.
53+
val googleButton = instrumentation.waitForView(
54+
By.text(Pattern.compile(".*google.*", Pattern.CASE_INSENSITIVE)),
55+
timeout = 10_000
56+
) ?: throw Exception("Google social login button not found on hosted login page")
57+
googleButton.click()
58+
59+
// 3. Chrome Custom Tab opens. Wait for Google's sign-in page.
60+
delay(3_000) // allow Chrome Custom Tab to open
61+
62+
// Handle "Choose an account" / sign-in form.
63+
// First, try to find an email input field.
64+
val emailField = instrumentation.waitForView(
65+
By.clazz(android.widget.EditText::class.java),
66+
timeout = 10_000
67+
)
68+
if (emailField != null) {
69+
emailField.text = Env.googleEmail
70+
instrumentation.clickByText("Next", timeout = 5_000)
71+
72+
delay(2_000)
73+
74+
// Password field.
75+
val passwordField = instrumentation.waitForView(
76+
By.clazz(android.widget.EditText::class.java),
77+
timeout = 10_000
78+
)
79+
if (passwordField != null) {
80+
passwordField.text = Env.googlePassword
81+
instrumentation.clickByText("Next", timeout = 5_000)
82+
}
83+
}
84+
85+
// 4. Handle potential consent screens ("Accept & continue", "No thanks").
86+
delay(2_000)
87+
instrumentation.clickByText("Accept & continue", timeout = 3_000)
88+
delay(1_000)
89+
instrumentation.clickByText("No thanks", timeout = 3_000)
90+
91+
// 5. Verify authenticated state — Logout button appears.
92+
val logoutPattern = Pattern.compile("logout", Pattern.CASE_INSENSITIVE)
93+
instrumentation.waitForView(By.text(logoutPattern), timeout = 30_000)
94+
?: throw Exception("Google login did not complete — Logout button not found")
95+
96+
// 6. Cleanup.
97+
instrumentation.logout()
98+
}
99+
}

0 commit comments

Comments
 (0)