Skip to content

fix: awaitable logout() with always-settle timeout (both platforms) - #101

Closed
dianaKhortiuk-frontegg wants to merge 4 commits into
masterfrom
fix/android-logout-timeout
Closed

fix: awaitable logout() with always-settle timeout (both platforms)#101
dianaKhortiuk-frontegg wants to merge 4 commits into
masterfrom
fix/android-logout-timeout

Conversation

@dianaKhortiuk-frontegg

Copy link
Copy Markdown
Collaborator

Summary

Makes logout() awaitable on both platforms and hardens it so await logout() can never hang.

Two layers:

  1. Awaitable logout (both platforms). The bridge now resolves a promise from the native SDK's logout completion instead of being fire-and-forget, and logout() is typed Promise<void> in JS. This portion originates from external contribution fix: make logout() awaitable on both platforms — resolve when the session actually ends #98 (Adam Rowe / Healthie) and is included here as the base.

  2. Always-settle timeout hardening (this PR's addition). As submitted, the promise resolved only from the SDK completion callback — if that callback never fired, await logout() would hang forever. Both platforms now schedule a 10s fallback and resolve exactly once:

    • iOS (ios/FronteggRN.swift): settled guard + DispatchQueue.main.asyncAfter(deadline: .now() + 10).
    • Android (FronteggRNModule.kt): handler.postDelayed(..., 10s) + AtomicBoolean.compareAndSet guard (the SDK callback may land on a background thread while the timeout runs on the main looper, so the guard is atomic).

Provenance / review note

The awaitable-logout base comes from a customer fork PR (#98). It was reviewed against the actual diff — no behavioral concerns beyond the hang risk, which this PR closes. Relates to #97 (auth-event delivery parity) and supersedes the Android/iOS halves of #98.

Compatibility

export function logout() already returned the bridge call, so existing callers that ignore the return value are unaffected; callers can now await it. Completion is success-only on both SDKs, so there is no reject path — the promise always resolves.

Validation

  • Static review of threading and resolve-once semantics on both platforms.
  • ⚠️ Not yet compiled in this environment (no Android/iOS toolchain available here) and no on-device run — please let CI + a device smoke test confirm before merge.

Files: android/.../FronteggRNModule.kt, ios/FronteggRN.swift, ios/FronteggRN.m, src/FronteggNative.ts.

🤖 Generated with Claude Code

airowe and others added 4 commits July 24, 2026 12:57
…sion actually ends

The bridge logout was fire-and-forget on both platforms: iOS called
auth.logout() without the completion overload; Android ignored the SDK's
callback parameter. JS had no way to know when the session was actually
gone and relied solely on the auth-state event — which can be lost when
logout coincides with app-level teardown, leaving the JS state
authenticated forever and breaking the next login's state-transition
detection (observed on-device).

Both native SDKs already expose completion callbacks (iOS
FronteggAuth.logout(_ completion:), Android logout(callback:)); the
bridge just didn't use them. logout() now returns a Promise that
resolves when the native SDK reports completion, and iOS pushes the
final auth state to JS before resolving. The JS export is typed
Promise<void>. Existing callers that ignore the return value are
unaffected.
logout(promise) resolved only from the SDK completion callback, so
`await logout()` would hang forever if that callback never fired. Add a
10s timeout fallback (parity with the iOS bridge) and an atomic
compare-and-set guard so the promise resolves exactly once, even though
the SDK callback and the timeout may run on different threads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Harden the awaitable logout() so `await logout()` can never hang if the
SDK completion never fires: add a 10s main-queue timeout fallback and a
`settled` guard so the promise resolves exactly once. Companion to the
Android change; both platforms now share the same 10s settle behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Carried from #97 (closed). startObserving() replayed the current auth
state to JS only when a change was missed while unobserved
(pendingObservingState). Because the JS-side state copy starts from a
default and is only corrected by events, a (re)subscribe that races a
native state change left JS permanently stale — e.g. a logout during
app-level teardown left useAuth() stuck authenticated. Replay
unconditionally so a listener attach always resyncs JS.

The companion subscribe() sink-disposal fix from #97 already landed via
#100 (FR-25940); this carries only the remaining startObserving() piece.

Co-Authored-By: Adam Rowe <52685+airowe@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dianaKhortiuk-frontegg

Copy link
Copy Markdown
Collaborator Author

Consolidated into #104 — combined with the other two RN release-prep PRs (cherry-picked onto current master, no conflicts, authorship preserved). Review and merge there. Closing this one.

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