Skip to content

Commit 8c29f79

Browse files
feat: bridge entitlements and admin portal from native SDKs
Bumps FronteggSwift 1.2.76 -> 1.3.6 and com.frontegg.sdk:android 1.3.18 -> 1.3.31, which is where the entitlements API and the embedded admin portal landed on the native side. Adds a thin Capacitor bridge so JS callers can use both features without touching native code. New JS surface on FronteggService: - loadEntitlements(forceRefresh?) — call after login and after switchTenant - getFeatureEntitlement(key) -> { isEntitled, justification } - getPermissionEntitlement(key) -> { isEntitled, justification } - showAdminPortal() — Beta; presents AdminPortalView on iOS, starts AdminPortalActivity on Android (declared by the SDK manifest) Docs (usage.md) gain "Entitlements" and "Admin portal (Beta)" sections, including the justification enum table. Version bumped to 2.1.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ae60f43 commit 8c29f79

11 files changed

Lines changed: 287 additions & 4 deletions

File tree

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.6"
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.31'
6666

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

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33

44

5+
import android.app.Activity;
56
import android.os.Handler;
67
import android.os.Looper;
78
import android.util.Log;
89

910
import kotlin.Unit;
1011

12+
import com.frontegg.android.AdminPortalActivity;
1113
import com.frontegg.android.FronteggApp;
1214
import com.frontegg.android.FronteggAppKt;
1315
import com.frontegg.android.FronteggAuth;
16+
import com.frontegg.android.models.Entitlement;
1417
import com.frontegg.android.models.User;
1518
import com.frontegg.android.regions.RegionConfig;
1619
import com.getcapacitor.JSObject;
@@ -295,6 +298,62 @@ public void refreshToken(PluginCall call) {
295298
}
296299

297300

301+
@PluginMethod
302+
public void loadEntitlements(PluginCall call) {
303+
Boolean forceRefresh = call.getBoolean("forceRefresh", false);
304+
FronteggAppKt.getFronteggAuth(this.getContext()).loadEntitlements(
305+
forceRefresh != null ? forceRefresh : false,
306+
(success) -> {
307+
JSObject result = new JSObject();
308+
result.put("success", success);
309+
call.resolve(result);
310+
return null;
311+
});
312+
}
313+
314+
@PluginMethod
315+
public void getFeatureEntitlement(PluginCall call) {
316+
String key = call.getString("key");
317+
if (key == null) {
318+
call.reject("No key provided");
319+
return;
320+
}
321+
Entitlement entitlement = FronteggAppKt.getFronteggAuth(this.getContext())
322+
.getFeatureEntitlements(key, null);
323+
JSObject result = new JSObject();
324+
result.put("isEntitled", entitlement.isEntitled());
325+
result.put("justification", entitlement.getJustification());
326+
call.resolve(result);
327+
}
328+
329+
@PluginMethod
330+
public void getPermissionEntitlement(PluginCall call) {
331+
String key = call.getString("key");
332+
if (key == null) {
333+
call.reject("No key provided");
334+
return;
335+
}
336+
Entitlement entitlement = FronteggAppKt.getFronteggAuth(this.getContext())
337+
.getPermissionEntitlements(key, null);
338+
JSObject result = new JSObject();
339+
result.put("isEntitled", entitlement.isEntitled());
340+
result.put("justification", entitlement.getJustification());
341+
call.resolve(result);
342+
}
343+
344+
@PluginMethod
345+
public void showAdminPortal(PluginCall call) {
346+
Activity activity = this.getActivity();
347+
if (activity == null) {
348+
call.reject("No host activity available");
349+
return;
350+
}
351+
new Handler(Looper.getMainLooper()).post(() -> {
352+
AdminPortalActivity.open(activity);
353+
call.resolve();
354+
});
355+
}
356+
298357
@PluginMethod
299358
public void getAuthState(PluginCall call) {
300359
call.resolve(getData());

docs/usage.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,56 @@ export class MyPage implements OnInit {
287287
}
288288
}
289289
```
290+
291+
### Entitlements
292+
293+
The SDK exposes the underlying Frontegg entitlements API so you can gate features and check permissions on-device. Call `loadEntitlements()` once after a successful login, and again after `switchTenant()`the tenant context determines which features and permissions resolve.
294+
295+
```typescript
296+
import { Inject } from '@angular/core';
297+
import { FronteggService } from '@frontegg/ionic-capacitor';
298+
299+
@Component({
300+
/** .... */
301+
})
302+
export class MyPage {
303+
304+
constructor(@Inject('Frontegg') private fronteggService: FronteggService) {}
305+
306+
async ngOnInit() {
307+
// After login (or after switchTenant):
308+
await this.fronteggService.loadEntitlements();
309+
310+
const feature = await this.fronteggService.getFeatureEntitlement('beta-dashboard');
311+
if (feature.isEntitled) {
312+
// show the gated feature
313+
} else {
314+
console.log('Not entitled because:', feature.justification);
315+
}
316+
317+
const permission = await this.fronteggService.getPermissionEntitlement('billing.read');
318+
// permission.isEntitled / permission.justification
319+
}
320+
}
321+
```
322+
323+
`justification` values returned when `isEntitled` is `false`:
324+
325+
| Justification | Meaning |
326+
|---|---|
327+
| `ENTITLEMENTS_DISABLED` | Entitlements are disabled for this account. |
328+
| `ENTITLEMENTS_NOT_LOADED` | `loadEntitlements()` was not called (or failed). |
329+
| `MISSING_FEATURE` / `MISSING_PERMISSION` | The user does not hold this key. |
330+
| `NOT_AUTHENTICATED` (iOS) | No user is currently signed in. |
331+
332+
### Admin portal (Beta)
333+
334+
The SDK can present the Frontegg-hosted admin portal inside the app. On iOS this is a SwiftUI sheet over your root view controller; on Android this is `AdminPortalActivity` (declared by the SDK).
335+
336+
```typescript
337+
this.fronteggService.showAdminPortal();
338+
```
339+
340+
> **Beta:** the portal API may change in future minor releases. Behavior and styling are inherited from the underlying iOS/Android SDKs.
341+
342+
Android requires no manifest changethe SDK declares `AdminPortalActivity` itself. iOS requires no additional setup. Just call `showAdminPortal()` from any view.

ios/Plugin/FronteggNativePlugin.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
CAP_PLUGIN_METHOD(refreshToken, CAPPluginReturnPromise);
1313
CAP_PLUGIN_METHOD(initWithRegion, CAPPluginReturnPromise);
1414
CAP_PLUGIN_METHOD(directLoginAction, CAPPluginReturnPromise);
15+
CAP_PLUGIN_METHOD(loadEntitlements, CAPPluginReturnPromise);
16+
CAP_PLUGIN_METHOD(getFeatureEntitlement, CAPPluginReturnPromise);
17+
CAP_PLUGIN_METHOD(getPermissionEntitlement, CAPPluginReturnPromise);
18+
CAP_PLUGIN_METHOD(showAdminPortal, CAPPluginReturnPromise);
1519
)
1620

1721

ios/Plugin/FronteggNativePlugin.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import FronteggSwift
22
import Foundation
33
import Combine
44
import Capacitor
5+
import SwiftUI
56

67
/**
78
* Please read the Capacitor iOS Plugin Development Guide
@@ -252,6 +253,55 @@ public class FronteggNativePlugin: CAPPlugin {
252253
}
253254
}
254255

256+
@objc func loadEntitlements(_ call: CAPPluginCall) {
257+
let forceRefresh = call.getBool("forceRefresh", false)
258+
fronteggApp.auth.loadEntitlements(forceRefresh: forceRefresh) { success in
259+
call.resolve(["success": success])
260+
}
261+
}
262+
263+
@objc func getFeatureEntitlement(_ call: CAPPluginCall) {
264+
guard let key = call.options["key"] as? String else {
265+
call.reject("No key provided")
266+
return
267+
}
268+
let entitlement = fronteggApp.auth.getFeatureEntitlements(featureKey: key)
269+
call.resolve([
270+
"isEntitled": entitlement.isEntitled,
271+
"justification": entitlement.justification as Any
272+
])
273+
}
274+
275+
@objc func getPermissionEntitlement(_ call: CAPPluginCall) {
276+
guard let key = call.options["key"] as? String else {
277+
call.reject("No key provided")
278+
return
279+
}
280+
let entitlement = fronteggApp.auth.getPermissionEntitlements(permissionKey: key)
281+
call.resolve([
282+
"isEntitled": entitlement.isEntitled,
283+
"justification": entitlement.justification as Any
284+
])
285+
}
286+
287+
@objc func showAdminPortal(_ call: CAPPluginCall) {
288+
DispatchQueue.main.async {
289+
guard let root = self.bridge?.viewController else {
290+
call.reject("No host view controller available")
291+
return
292+
}
293+
if #available(iOS 14.0, *) {
294+
let host = UIHostingController(rootView: AdminPortalView())
295+
host.modalPresentationStyle = .pageSheet
296+
root.present(host, animated: true) {
297+
call.resolve()
298+
}
299+
} else {
300+
call.reject("Admin portal requires iOS 14.0+")
301+
}
302+
}
303+
}
304+
255305
@objc func getAuthState(_ call: CAPPluginCall) {
256306
let auth = fronteggApp.auth
257307
var jsonUser: [String: Any]? = nil

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@frontegg/ionic-capacitor",
3-
"version": "2.0.12",
3+
"version": "2.1.0",
44
"description": "Frontegg Ionic Capacitor SDK",
55
"main": "dist/plugin.cjs.js",
66
"module": "dist/esm/index.js",

src/definitions.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,61 @@ export interface FronteggNativePlugin {
192192
* @returns A promise that resolves when the token refresh is completed.
193193
*/
194194
refreshToken(): Promise<void>;
195+
196+
/**
197+
* Loads (or reloads) the current user's entitlements for the active tenant.
198+
* Must be called after a successful login and after switching tenants.
199+
* @param payload.forceRefresh - If true, bypasses any cached state and re-fetches from the server.
200+
* @returns A promise resolving to `{ success: true }` if entitlements were loaded.
201+
*/
202+
loadEntitlements(payload?: {
203+
forceRefresh?: boolean;
204+
}): Promise<{ success: boolean }>;
205+
206+
/**
207+
* Checks whether the current user is entitled to a specific feature.
208+
* @param payload.key - The feature key as configured in Frontegg.
209+
* @returns A promise resolving to an Entitlement describing the result.
210+
*/
211+
getFeatureEntitlement(payload: { key: string }): Promise<Entitlement>;
212+
213+
/**
214+
* Checks whether the current user holds a specific permission.
215+
* @param payload.key - The permission key as configured in Frontegg.
216+
* @returns A promise resolving to an Entitlement describing the result.
217+
*/
218+
getPermissionEntitlement(payload: { key: string }): Promise<Entitlement>;
219+
220+
/**
221+
* Opens the Frontegg admin portal (BETA).
222+
* On iOS this presents a SwiftUI sheet containing a WKWebView; on Android
223+
* it starts an embedded WebView Activity. The portal is dismissed by the
224+
* user (swipe / X button / back button).
225+
*
226+
* NOTE: Beta API — the surface may change in future minor releases.
227+
*
228+
* Android: requires `AdminPortalActivity` declared in the host app's
229+
* AndroidManifest.xml. See the setup guide.
230+
*/
231+
showAdminPortal(): Promise<void>;
232+
}
233+
234+
/**
235+
* Represents the result of an entitlement check.
236+
*/
237+
export interface Entitlement {
238+
/**
239+
* Whether the user is entitled.
240+
*/
241+
isEntitled: boolean;
242+
243+
/**
244+
* Optional reason when `isEntitled` is false. Common values:
245+
* - "ENTITLEMENTS_DISABLED": entitlements feature is disabled on this account
246+
* - "ENTITLEMENTS_NOT_LOADED": `loadEntitlements()` was not called yet
247+
* - "MISSING_FEATURE" / "MISSING_PERMISSION": user does not hold this key
248+
*/
249+
justification?: string | null;
195250
}
196251

197252
/**

src/frontegg.service.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { registerPlugin } from '@capacitor/core';
22

33
import type {
4+
Entitlement,
45
FronteggConstants,
56
FronteggNativePlugin,
67
FronteggServiceOptions,
@@ -289,4 +290,36 @@ export class FronteggService {
289290
public refreshToken(): Promise<void> {
290291
return FronteggNative.refreshToken();
291292
}
293+
294+
/**
295+
* Load (or reload) entitlements for the current user and active tenant.
296+
* Call after a successful login and after `switchTenant()`.
297+
*/
298+
public loadEntitlements(forceRefresh = false): Promise<{ success: boolean }> {
299+
return FronteggNative.loadEntitlements({ forceRefresh });
300+
}
301+
302+
/**
303+
* Check whether the current user is entitled to a feature.
304+
*/
305+
public getFeatureEntitlement(key: string): Promise<Entitlement> {
306+
return FronteggNative.getFeatureEntitlement({ key });
307+
}
308+
309+
/**
310+
* Check whether the current user holds a permission.
311+
*/
312+
public getPermissionEntitlement(key: string): Promise<Entitlement> {
313+
return FronteggNative.getPermissionEntitlement({ key });
314+
}
315+
316+
/**
317+
* Open the Frontegg admin portal (BETA).
318+
* iOS: presents a SwiftUI sheet over the host view controller.
319+
* Android: starts AdminPortalActivity — must be declared in the host
320+
* AndroidManifest.xml.
321+
*/
322+
public showAdminPortal(): Promise<void> {
323+
return FronteggNative.showAdminPortal();
324+
}
292325
}

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { FronteggService } from './frontegg.service';
2-
export { FronteggState } from './definitions';
2+
export { Entitlement, FronteggState } from './definitions';
33
export { LogLevel } from './logger';

0 commit comments

Comments
 (0)