Skip to content

FR-25958 FR-25959 FR-25952 feat: Expo config plugin, new architecture, multi-region and getOrRefreshAccessToken - #143

Merged
dianaKhortiuk-frontegg merged 8 commits into
masterfrom
claude/determined-rubin-dp6k5f
Oct 27, 2026
Merged

dianaKhortiuk-frontegg merged 8 commits into
masterfrom
claude/determined-rubin-dp6k5f

Conversation

@dianaKhortiuk-frontegg

@dianaKhortiuk-frontegg dianaKhortiuk-frontegg commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Jira: FR-25958 (Expo config plugin and TurboModule readiness), FR-25959 (multi-region), FR-25952 (get-or-refresh access-token helper).

Summary

Expo config plugin

Add @frontegg/react-native with its options to plugins in app.json and run npx expo prebuild. The plugin generates the native iOS and Android setup from docs/setup.md.

  • Options: baseUrl, clientId, optional applicationId; or regions ({key, baseUrl, clientId, applicationId?}); plus keychainService, embeddedMode, logLevel, useAssetLinks, useChromeCustomTabs, associatedDomains, and the pass-through iosPlist and androidBuildConfigFields.
  • iOS: writes Frontegg.plist, associated domains, the URL scheme, handleOpenUrl in AppDelegate.swift (SDK 53–57 and 58 templates), static frameworks and the SPM hook.
  • Android: manifest placeholders, FRONTEGG_* BuildConfig fields, minSdkVersion 26, intent filters for every extra region, and, with regions, a call to FronteggRegions.initFromBuildConfig(this) in MainApplication.onCreate so regions are set up before React Native starts.
  • Running prebuild twice changes nothing. An Objective-C AppDelegate, a Java MainApplication or build.gradle.kts get a warning and are skipped.
  • expo is a dev dependency and an optional peer dependency.

New architecture (TurboModule)

The module supports both architectures. Codegen spec in src/NativeFronteggRN.ts with codegenConfig (RNFronteggRNSpec). Android uses newarch//oldarch/ source sets and a TurboReactPackage; iOS FronteggRN.m is now FronteggRN.mm and conforms to the spec under RCT_NEW_ARCH_ENABLED.

Multi-region

  • getRegions(), initWithRegion(regionKey) (rejects with INVALID_REGION for an unknown key), useAuth().selectedRegion, and isRegional/regionData in getConstants().
  • iOS reads regions from Frontegg.plist. Android reads the FRONTEGG_REGIONS BuildConfig field and calls FronteggApp.initWithRegions, passing the other FRONTEGG_* options that initWithRegions accepts (offline mode, auto-refresh, session per tenant, entitlements, and so on). Bare React Native apps should call FronteggRegions.initFromBuildConfig(this) in MainApplication.onCreate; Expo does it automatically.

getOrRefreshAccessToken

getOrRefreshAccessToken(): Promise<string | null> returns an access token valid for more than 15 s, refreshing it first when needed. Use it before API calls after the app resumes. It rejects with GET_OR_REFRESH_ACCESS_TOKEN_ERROR on failure.

  • Resolves null when there is no session, when the server rejected the refresh token, or in a multi-region app before a region is selected.
  • Waits up to 20 s for an in-flight initialization or refresh; rejects if it is still running.
  • Device offline: with offline mode on, resolves the cached token (which may be expired); with offline mode off, rejects. Android checks connectivity first and answers right away; iOS retries the refresh a few times first.
  • On Android, if a refresh the SDK started in the background was still finishing, it waits for it and checks the token again, so it never returns a token that is about to expire.

CI

CI and .nvmrc now use Node 22 (was 18): the expo dev dependency requires Node 22.13 or newer.

Risks

  • An over-the-air JS update running on an older native binary will fail on the new methods.
  • Objective-C code that called the old selectors directly will break. Calls from JS are unaffected.

Testing

  • CI is green: lint/typecheck, unit tests (41/41), build, Android E2E and iOS E2E. The E2E jobs build the example app, so the module's Android and iOS code compiles and runs on the old architecture.
  • Expo plugin run against blank Expo SDK 53, 55 and 58 projects, and its MainApplication change against the SDK 53–58 templates.
  • Android, new architecture: codegen, the module's Kotlin and the generated C++ compiled locally. The full example app build fails inside react-native-screens 3.32.0 (the example's dependency) on RN 0.72, not in this module.
  • Not verified: the new-architecture iOS build (the #import "RNFronteggRNSpec.h" under static frameworks needs checking on an Expo SDK 53+ app), a build against RN 0.79+, and device runs.

The first commit's message doesn't follow the conventional-commit format; reword it on merge if needed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a


Note

Medium Risk
Touches authentication token refresh, multi-region SDK initialization timing, and native bridge/codegen; OTA JS bundles that call new APIs need a matching native build.

Overview
Adds an Expo config plugin (app.plugin.js + plugin/) that runs on expo prebuild to generate Frontegg.plist, Android FRONTEGG_* BuildConfig/manifest placeholders, associated domains, URL handling in Swift AppDelegate, multi-region intent filters, and FronteggRegions.initFromBuildConfig in Kotlin MainApplication.

Converts the native module to a TurboModule with codegen (src/NativeFronteggRN.ts, RNFronteggRNSpec), TurboReactPackage on Android (newarch/oldarch specs), and FronteggRN.mm for new-arch iOS. JS now loads the module via TurboModuleRegistry instead of NativeModules.

Ships multi-region: getRegions(), initWithRegion(), useAuth().selectedRegion, and isRegional/regionData in constants; Android reads FRONTEGG_REGIONS and initializes via RegionsInitializer / FronteggRegions.

Adds getOrRefreshAccessToken() on both platforms (Android AccessTokenRefresher with JWT validity, offline mode, and concurrent-refresh handling; iOS delegates to the native SDK).

CI / toolchain: Node 22 in E2E workflows and .nvmrc for the Expo dev dependency. Docs expanded for Expo setup, regions, new architecture, and token usage.

Reviewed by Cursor Bugbot for commit 28ba607. Bugbot is set up for automated code reviews on this repo. Configure here.

…boModule support

Expo config plugin generates the iOS and Android native setup on prebuild,
including multi-region via Frontegg.plist and FRONTEGG_REGIONS.
Exposes getRegions, initWithRegion and selectedRegion, adds
getOrRefreshAccessToken, and supports the new architecture through a
codegen spec with old and new arch module implementations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEp5Aemy81i5PZmfV7iX4t
The multi-region init only forwarded useAssetsLinks and useChromeCustomTabs,
so FRONTEGG_ENABLE_OFFLINE_MODE, FRONTEGG_DISABLE_AUTO_REFRESH,
FRONTEGG_ENABLE_SESSION_PER_TENANT, FRONTEGG_ENTITLEMENTS_ENABLED,
FRONTEGG_USE_DISK_CACHE_WEBVIEW, FRONTEGG_MAIN_ACTIVITY_CLASS and
FRONTEGG_ORGANIZATION were silently ignored in multi-region apps. Read them
through the SDK's FronteggConstantsProvider, as its single-region auto-init does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a
Regions were only configured when React Native created the module, so a
process restarted straight into the login redirect started the SDK without
them. Add FronteggRegions.initFromBuildConfig, which MainApplication can call
before React Native starts, and have the Expo config plugin insert it after
super.onCreate() in Kotlin MainApplication files for multi-region apps. The
module's own init stays idempotent, so the second call is a no-op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a
…or fresh token

- Resolve null in a multi-region app before a region is selected instead of
  waiting 20 seconds on initializing and rejecting.
- Check connectivity before refreshing: with offline mode enabled resolve the
  cached token, otherwise reject right away instead of waiting about a minute
  in refreshTokenAndWait. Offline mode is read from FronteggConstantsProvider,
  the source the SDK is initialized from.
- When refreshTokenAndWait reports success but the token is still not valid
  for 15 seconds (another refresh was running), wait up to 10 seconds for it,
  then refresh once more before failing.

Document the exact per-platform behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a
expo 58 declares node ^22.13.0, so yarn install --frozen-lockfile failed on
Node 18 in every job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a
@dianaKhortiuk-frontegg
dianaKhortiuk-frontegg marked this pull request as ready for review October 26, 2026 19:50

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

The once-guard was set before reading the SDK constants and calling
FronteggApp.initWithRegions, so an exception there left it set: the module's
later call skipped setup and the SDK fell back to single-region with no
domain, and the exception crashed MainApplication.onCreate. Log the error and
reset the guard instead, so the next call retries. A malformed
FRONTEGG_REGIONS still keeps the guard set, since it cannot succeed later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

initWithRegion rejected every exception as INVALID_REGION. Check the key
against the configured regions first, as iOS does, and reject a failure of
the SDK switch itself with INIT_WITH_REGION_ERROR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Both native modules update selectedRegion synchronously and push it through
the auth state event, which the JS listener debounces by 50 ms, so the
promise resolves before useAuth() sees the new region.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2yW3aohcrkGZ96iyLpv3a

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 28ba607. Configure here.

@dianaKhortiuk-frontegg
dianaKhortiuk-frontegg merged commit 788359c into master Oct 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants