Skip to content

iOS: FronteggApp.init() fatalErrors on import when Frontegg.plist is missing — blocks feature-gated rollouts #71

Description

@airowe

Summary

On iOS, the React Native SDK's underlying FronteggSwift singleton (FronteggApp.init()) eagerly reads Frontegg.plist from the main bundle and calls fatalError if it's missing. Because the singleton fires the moment @frontegg/react-native is imported (not when it's first used), any consumer that wants to ship the SDK behind a feature gate / per-variant rollout has no way to keep it dormant in builds that don't include the plist.

This makes a phased iOS rollout effectively impossible without significant Xcode / Pod gymnastics on the consumer side.

Repro

  1. RN app integrates @frontegg/react-native.
  2. Frontegg.plist is bundled only in one target (e.g. a staging variant), not in production / white-label variants.
  3. JS code attempts to gate the SDK at import time:
// src/App.tsx
import { FronteggWrapper } from '@frontegg/react-native'; // ← fires here

return variant === 'staging' ? <FronteggWrapper>{children}</FronteggWrapper> : children;
  1. Any non-staging variant crashes on launch with:
Thread 1: Fatal error: Could not load Frontegg Plist:
Missing Frontegg.plist file with 'clientId' and 'baseUrl' entries in main bundle!

…before any React Native code runs. The JS gate doesn't help — the import alone triggers the singleton.

Root cause (as best we can tell from FronteggSwift)

  • FronteggApp.init() is called at module-load time (Swift static singleton init pattern).
  • It calls Bundle.main.url(forResource: \"Frontegg\", withExtension: \"plist\") and then fatalErrors if nil (PlistHelper.swiftFronteggApp.swift).
  • The RN bridge imports trigger this on the very first JS-side import of any symbol from @frontegg/react-native.

Contrast with Android

The Android SDK reads its config via safeGetValueFromBuildConfig(..., default) — missing fields fall back to defaults rather than crashing the process. We verified empirically: an Android variant without the Frontegg buildConfigFields still launches and runs to the home screen (it just can't authenticate, as expected). The iOS side should behave the same way.

Proposed directions (not prescriptive)

We're happy to follow Frontegg's preferred shape — flagging the architectural issue more than the specific fix. Some options:

  1. Lazy init — defer the Bundle.main plist read until first SDK call (FronteggApp.shared.login(...) etc.). Imports become side-effect-free.
  2. Non-fatal missing-plist — log + no-op when plist is absent, return an SDK instance that errors only on calls. Matches Android's existing behaviour.
  3. Programmatic init APIFronteggApp.init(clientId:baseUrl:) that consumers call explicitly. Plist becomes optional sugar on top.

(1) or (2) would be minimally invasive. (3) is a larger API change but the most explicit.

Workarounds we tried (and why they're not great)

Approach Outcome
JS-level lazy import() only inside the gate Doesn't help — once the import resolves, the singleton fires.
Frontegg.plist in every iOS target Surfaces credentials in production builds; security/compliance concern.
Conditional Pod linking (FronteggRN only in staging target) Workable but invasive on the consumer side; defeats the point of a JS gate.
Metro resolver / noop shim Works for static imports but only the JS import — native pod link still runs init.

Environment

  • @frontegg/react-native@1.2.16
  • React Native 0.80
  • iOS 18.x, Xcode 16
  • Reproduced on iPhone 17 device and iOS Simulator

Related

We've also opened a small batch of Android-side PRs against this repo (JVM target, currentActivity deprecation, BuildConfig lookup for white-label apps). This iOS item is the one architectural change we wanted to raise as an issue rather than a unilateral PR, since the right API direction is Frontegg's call.

Happy to test prototype builds or contribute a PR once a direction is chosen.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions