From a3467dd8671e03dcda9936958d326ba7abaf9386 Mon Sep 17 00:00:00 2001 From: Adam Rowe Date: Wed, 22 Jul 2026 08:22:51 -0400 Subject: [PATCH] =?UTF-8?q?fix(ios):=20reliable=20auth-event=20delivery=20?= =?UTF-8?q?=E2=80=94=20replay=20state=20on=20listener=20attach,=20dispose?= =?UTF-8?q?=20sinks=20on=20resubscribe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related event-delivery gaps in the iOS bridge, both already handled correctly by the Android module: 1. startObserving() only replayed the auth state when a change had been missed while unobserved. The JS-side state copy starts from a default and is only ever corrected by events, so a (re)subscribe that races a native state change leaves JS permanently stale — observed on-device as logout events lost during app-level teardown, leaving useAuth() authenticated forever. Replay unconditionally on attach. 2. subscribe() never disposed previous Combine sinks, so repeated calls multiplied event sends. Dispose before resubscribing and push the current state at the end — matching the Android module, which disposes before resubscribing and emits current state on subscribe. --- ios/FronteggRN.swift | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/ios/FronteggRN.swift b/ios/FronteggRN.swift index a3c530f..b10e1f2 100644 --- a/ios/FronteggRN.swift +++ b/ios/FronteggRN.swift @@ -23,10 +23,14 @@ class FronteggRN: RCTEventEmitter { } override func startObserving() { self.hasListeners = true - if(self.pendingObservingState){ - self.pendingObservingState = false - self.sendEventToJS() - } + // Always replay the current auth state when a JS listener attaches, + // not only when a change was missed while unobserved. The JS-side + // state copy starts from a default and is only ever corrected by + // events; without an unconditional replay, a (re)subscribe that + // races a native state change leaves JS permanently stale (observed + // on-device: logout events lost during app-level teardown). + self.pendingObservingState = false + self.sendEventToJS() } override func stopObserving() { @@ -34,7 +38,12 @@ class FronteggRN: RCTEventEmitter { } @objc func subscribe() -> [AnyHashable : Any]! { - + + // Dispose previous sinks before resubscribing (parity with the + // Android module, which disposes before it resubscribes) so repeated + // subscribe() calls don't multiply event sends. + cancellables.removeAll() + let auth = fronteggApp.auth var stateChange: AnyPublisher { @@ -82,7 +91,13 @@ class FronteggRN: RCTEventEmitter { } }).store(in: &cancellables) - + + // Push the current state after (re)subscribing (Android parity) so a + // subscribe() call acts as an on-demand state resync for JS. + if(self.hasListeners){ + self.sendEventToJS() + } + return ["status": "OK"] }