handleAboutToLogOut method
Public handler for pre-logout cleanup.
Can be passed to the AuthService constructor for race-free initialization (see the DeviceServiceInt class documentation's manual-wiring example).
This performs backend token unregistration (unless DeviceService handles it) and is called BEFORE Firebase signOut while still authenticated.
Implementation
Future<void> handleAboutToLogOut() async {
logd('NotificationService: handleAboutToLogOut called');
// FCM-044/BEH-25 (load-bearing placement): set the logout-window guard and
// cancel the refresh subscription at the TOP — BEFORE the DeviceService-
// connected early-return below — so no NEW capture/refresh-driven persist can
// re-create a ghost device doc at the OLD user's path during the logout window
// (after DeviceService deletes the doc, before signOut completes). Placing
// this in or after the post-return fallback would make it DEAD CODE on the
// exact DreamicServices-connected path it protects. Capture the uid while
// still authenticated (D11) for the _FcmLogoutWindowLeak breadcrumb. Do NOT
// rely on onAboutToLogOut priority ordering — same-priority callbacks run in
// parallel via Future.wait. _loggingOut is reset only on the next GENUINE
// login (via _handleLogin's isReplay threading, FCM-121), NOT here and NOT on
// the B.3 replay. Do NOT null _onTokenChanged — it is needed for re-login
// capture.
_loggingOut = true;
_loggingOutUid = _authService?.currentFbUser?.uid;
await _tokenRefreshSubscription?.cancel();
_tokenRefreshSubscription = null;
// When DeviceService is registered and connected, it handles backend cleanup
// by deleting the device doc via its own onAboutToLogOut callback.
// NotificationService should not trigger a token persistence write that
// would race with deletion.
//
// DATA MODEL ASSUMPTION: The FCM token is stored inside the device document,
// not in a separate collection. Therefore, when DeviceService deletes the
// device document on logout, the FCM token is automatically cleaned up.
// If this data model changes (e.g., tokens stored separately), this logic
// must be revisited to ensure NotificationService performs its own cleanup.
if (GetIt.I.isRegistered<DeviceServiceInt>()) {
final deviceService = GetIt.I.get<DeviceServiceInt>();
if (deviceService.isConnectedToAuth) {
logd('NotificationService: handleAboutToLogOut - DeviceService is connected, '
'skipping token persistence (device doc will be deleted by DeviceService)');
return;
}
}
// Fallback for apps without DeviceService integration or when DeviceService
// is not connected - perform backend token unregistration via the custom callback
logd('NotificationService: handleAboutToLogOut - Performing backend token unregistration');
if (_onTokenChanged != null && _cachedFcmToken != null) {
try {
await _onTokenChanged!(null, _cachedFcmToken);
logd('NotificationService: Successfully unregistered FCM token on backend before logout');
} catch (e) {
logw('NotificationService: Failed to unregister FCM token on backend: $e');
// Continue with logout even if backend call fails
}
}
}