diff --git a/apps/mobile/apps/client/lib/main.dart b/apps/mobile/apps/client/lib/main.dart index 6d3c2ac4..9a2feccd 100644 --- a/apps/mobile/apps/client/lib/main.dart +++ b/apps/mobile/apps/client/lib/main.dart @@ -30,16 +30,6 @@ void main() async { logStateChanges: false, // Set to true for verbose debugging ); - // Initialize V2 session listener for Firebase Auth state changes. - // Role validation calls GET /auth/session via the V2 API. - V2SessionService.instance.initializeAuthListener( - allowedRoles: [ - 'CLIENT', - 'BUSINESS', - 'BOTH', - ], // Only allow users with CLIENT, BUSINESS, or BOTH roles - ); - runApp( ModularApp( module: AppModule(), diff --git a/apps/mobile/apps/client/lib/src/widgets/session_listener.dart b/apps/mobile/apps/client/lib/src/widgets/session_listener.dart index 5d5f124d..e6b26b37 100644 --- a/apps/mobile/apps/client/lib/src/widgets/session_listener.dart +++ b/apps/mobile/apps/client/lib/src/widgets/session_listener.dart @@ -27,11 +27,20 @@ class _SessionListenerState extends State { @override void initState() { super.initState(); - _setupSessionListener(); + _initializeSession(); } - void _setupSessionListener() { - _sessionSubscription = V2SessionService.instance.onSessionStateChanged + void _initializeSession() { + // Resolve V2SessionService via DI — this triggers CoreModule's lazy + // singleton, which wires setApiService(). Must happen before + // initializeAuthListener so the session endpoint is reachable. + final V2SessionService sessionService = Modular.get(); + + sessionService.initializeAuthListener( + allowedRoles: const ['CLIENT', 'BUSINESS', 'BOTH'], + ); + + _sessionSubscription = sessionService.onSessionStateChanged .listen((SessionState state) { _handleSessionChange(state); }); diff --git a/apps/mobile/apps/staff/lib/main.dart b/apps/mobile/apps/staff/lib/main.dart index 66fc30c8..3b0d4dd3 100644 --- a/apps/mobile/apps/staff/lib/main.dart +++ b/apps/mobile/apps/staff/lib/main.dart @@ -28,15 +28,6 @@ void main() async { logStateChanges: false, // Set to true for verbose debugging ); - // Initialize V2 session listener for Firebase Auth state changes. - // Role validation calls GET /auth/session via the V2 API. - V2SessionService.instance.initializeAuthListener( - allowedRoles: [ - 'STAFF', - 'BOTH', - ], // Only allow users with STAFF or BOTH roles - ); - runApp( ModularApp( module: AppModule(), diff --git a/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart b/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart index f5385ed9..fd064c12 100644 --- a/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart +++ b/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart @@ -27,11 +27,20 @@ class _SessionListenerState extends State { @override void initState() { super.initState(); - _setupSessionListener(); + _initializeSession(); } - void _setupSessionListener() { - _sessionSubscription = V2SessionService.instance.onSessionStateChanged + void _initializeSession() { + // Resolve V2SessionService via DI — this triggers CoreModule's lazy + // singleton, which wires setApiService(). Must happen before + // initializeAuthListener so the session endpoint is reachable. + final V2SessionService sessionService = Modular.get(); + + sessionService.initializeAuthListener( + allowedRoles: const ['STAFF', 'BOTH'], + ); + + _sessionSubscription = sessionService.onSessionStateChanged .listen((SessionState state) { _handleSessionChange(state); }); diff --git a/apps/mobile/packages/core/lib/src/core_module.dart b/apps/mobile/packages/core/lib/src/core_module.dart index a1b90277..40145f27 100644 --- a/apps/mobile/packages/core/lib/src/core_module.dart +++ b/apps/mobile/packages/core/lib/src/core_module.dart @@ -18,9 +18,8 @@ class CoreModule extends Module { // 2. Register the base API service i.addLazySingleton(() => ApiService(i.get())); - // 2b. Wire the V2 session service with the API service. - // This uses a post-registration callback so the singleton gets - // its dependency as soon as the injector resolves BaseApiService. + // 2b. Register V2SessionService — wires the singleton with ApiService. + // Resolved eagerly by SessionListener.initState() after Modular is ready. i.addLazySingleton(() { final V2SessionService service = V2SessionService.instance; service.setApiService(i.get()); diff --git a/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart b/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart index 126ada3b..35b8879a 100644 --- a/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart +++ b/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart @@ -42,23 +42,10 @@ class V2SessionService with SessionHandlerMixin { @override Future fetchUserRole(String userId) async { try { - // Wait for ApiService to be injected (happens after CoreModule.exportedBinds). - // On cold start, initializeAuthListener fires before DI is ready. - if (_apiService == null) { - debugPrint( - '[V2SessionService] ApiService not yet injected; ' - 'waiting for DI initialization...', - ); - for (int i = 0; i < 10; i++) { - await Future.delayed(const Duration(milliseconds: 200)); - if (_apiService != null) break; - } - } - final BaseApiService? api = _apiService; if (api == null) { debugPrint( - '[V2SessionService] ApiService still null after waiting 2 s; ' + '[V2SessionService] ApiService not injected; ' 'cannot fetch user role.', ); return null; @@ -74,8 +61,16 @@ class V2SessionService with SessionHandlerMixin { // Per V2 auth doc, GET /auth/session is used for app startup hydration. _hydrateSessionStores(data); - final String? role = data['role'] as String?; - return role; + // Derive role from the presence of staff/business context. + // The session endpoint returns { user, tenant, business, vendor, staff } + // — there is no explicit "role" field. + final bool hasStaff = data['staff'] is Map; + final bool hasBusiness = data['business'] is Map; + + if (hasStaff && hasBusiness) return 'BOTH'; + if (hasStaff) return 'STAFF'; + if (hasBusiness) return 'BUSINESS'; + return null; } return null; } catch (e) {