handleAboutToLogOut method

Future<void> handleAboutToLogOut()

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
    }
  }
}