From e750692b5ede934220ea54260a831715c4ac97d2 Mon Sep 17 00:00:00 2001 From: Adam Rowe Date: Wed, 22 Jul 2026 08:25:14 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20make=20logout()=20awaitable=20on=20both?= =?UTF-8?q?=20platforms=20=E2=80=94=20resolve=20when=20the=20session=20act?= =?UTF-8?q?ually=20ends?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. Existing callers that ignore the return value are unaffected. --- .../frontegg/reactnative/FronteggRNModule.kt | 8 ++++-- ios/FronteggRN.m | 5 +++- ios/FronteggRN.swift | 26 ++++++++++++++++--- src/FronteggNative.ts | 2 +- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt b/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt index a88993d..19190fe 100644 --- a/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt +++ b/android/src/main/java/com/frontegg/reactnative/FronteggRNModule.kt @@ -108,8 +108,12 @@ class FronteggRNModule(val reactContext: ReactApplicationContext) : } @ReactMethod - fun logout() { - auth.logout() + fun logout(promise: Promise) { + // Resolve only after the SDK reports the logout finished, so JS can + // await the actual end of the session (parity with the iOS bridge). + auth.logout { + promise.resolve("Success") + } } @ReactMethod diff --git a/ios/FronteggRN.m b/ios/FronteggRN.m index 7b91721..159da8c 100644 --- a/ios/FronteggRN.m +++ b/ios/FronteggRN.m @@ -5,7 +5,10 @@ @interface RCT_EXTERN_MODULE(FronteggRN, RCTEventEmitter) RCT_EXTERN_METHOD(subscribe) -RCT_EXTERN_METHOD(logout) +RCT_EXTERN_METHOD( + logout: (RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) RCT_EXTERN_METHOD( login: (NSString *)loginHint resolver: (RCTPromiseResolveBlock)resolve diff --git a/ios/FronteggRN.swift b/ios/FronteggRN.swift index a3c530f..0e5531c 100644 --- a/ios/FronteggRN.swift +++ b/ios/FronteggRN.swift @@ -109,11 +109,29 @@ class FronteggRN: RCTEventEmitter { } @objc - func logout() -> [AnyHashable : Any]! { - DispatchQueue.main.sync { - fronteggApp.auth.logout() + func logout(_ resolve: @escaping RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) -> Void { + DispatchQueue.main.async { + // Use the completion overload so JS can await the actual end of + // the session, and push the final state when it lands. The + // fire-and-forget call relies solely on Combine-driven events, + // 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. The SDK + // completion is success-only, so there is no reject path. + self.fronteggApp.auth.logout { _ in + // Read/write hasListeners + pendingObservingState on main — + // RCTEventEmitter mutates them on main (start/stopObserving), + // so touching them off-main would be a data race. + DispatchQueue.main.async { + if self.hasListeners { + self.sendEventToJS() + } else { + self.pendingObservingState = true + } + resolve("Success") + } + } } - return ["status": "OK"] } @objc diff --git a/src/FronteggNative.ts b/src/FronteggNative.ts index 8783ee2..104b839 100644 --- a/src/FronteggNative.ts +++ b/src/FronteggNative.ts @@ -31,7 +31,7 @@ export function login(loginHint?: string) { }); } -export function logout() { +export function logout(): Promise { return FronteggRN.logout(); }