refactor(session): migrate V2 session listener initialization to improve dependency injection and role handling

This commit is contained in:
Achintha Isuru
2026-03-17 10:55:18 -04:00
parent b289ed3b02
commit ba5bf8e1d7
6 changed files with 37 additions and 44 deletions

View File

@@ -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: <String>[
'CLIENT',
'BUSINESS',
'BOTH',
], // Only allow users with CLIENT, BUSINESS, or BOTH roles
);
runApp(
ModularApp(
module: AppModule(),

View File

@@ -27,11 +27,20 @@ class _SessionListenerState extends State<SessionListener> {
@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<V2SessionService>();
sessionService.initializeAuthListener(
allowedRoles: const <String>['CLIENT', 'BUSINESS', 'BOTH'],
);
_sessionSubscription = sessionService.onSessionStateChanged
.listen((SessionState state) {
_handleSessionChange(state);
});

View File

@@ -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: <String>[
'STAFF',
'BOTH',
], // Only allow users with STAFF or BOTH roles
);
runApp(
ModularApp(
module: AppModule(),

View File

@@ -27,11 +27,20 @@ class _SessionListenerState extends State<SessionListener> {
@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<V2SessionService>();
sessionService.initializeAuthListener(
allowedRoles: const <String>['STAFF', 'BOTH'],
);
_sessionSubscription = sessionService.onSessionStateChanged
.listen((SessionState state) {
_handleSessionChange(state);
});

View File

@@ -18,9 +18,8 @@ class CoreModule extends Module {
// 2. Register the base API service
i.addLazySingleton<BaseApiService>(() => ApiService(i.get<Dio>()));
// 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<V2SessionService>(() {
final V2SessionService service = V2SessionService.instance;
service.setApiService(i.get<BaseApiService>());

View File

@@ -42,23 +42,10 @@ class V2SessionService with SessionHandlerMixin {
@override
Future<String?> 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<void>.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<String, dynamic>;
final bool hasBusiness = data['business'] is Map<String, dynamic>;
if (hasStaff && hasBusiness) return 'BOTH';
if (hasStaff) return 'STAFF';
if (hasBusiness) return 'BUSINESS';
return null;
}
return null;
} catch (e) {