import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'api/support_api.dart'; import 'models/account.dart'; import 'models/api_exception.dart'; class SessionStore { SessionStore({FlutterSecureStorage? storage}) : _storage = storage ?? const FlutterSecureStorage(); static const tokenKey = 'auth_token'; static const accountKey = 'account_json'; static const fcmTokenKey = 'fcm_token'; final FlutterSecureStorage _storage; Future readToken() => _storage.read(key: tokenKey); Future readAccount() async { final raw = await _storage.read(key: accountKey); if (raw == null || raw.isEmpty) { return null; } final decoded = jsonDecode(raw); if (decoded is! Map) { return null; } return Account.fromJson(Map.from(decoded)); } Future readFcmToken() => _storage.read(key: fcmTokenKey); Future writeSession(String token, Account account) async { await _storage.write(key: tokenKey, value: token); await _storage.write(key: accountKey, value: jsonEncode(account.toJson())); } Future writeFcmToken(String token) { return _storage.write(key: fcmTokenKey, value: token); } Future clear() async { await _storage.delete(key: tokenKey); await _storage.delete(key: accountKey); await _storage.delete(key: fcmTokenKey); } } class SessionController extends ChangeNotifier { SessionController({required this.store, required this.api}); final SessionStore store; final SupportApi api; Account? account; String? token; String? fcmToken; String? pendingTicketId; bool ready = false; bool get isSignedIn => token != null && account != null && account!.canReadSupport; Future restore() async { try { token = await store.readToken(); account = await store.readAccount(); fcmToken = await store.readFcmToken(); if (token == null || account == null || !account!.canReadSupport) { token = null; account = null; } api.authToken = token; } finally { ready = true; notifyListeners(); } } /// Returns an error message, or null when the inbox can open. Future signIn(String username, String password) async { try { final result = await api.login(username.trim(), password); if (!result.account.canReadSupport) { return 'This account does not have support access'; } await store.writeSession(result.token, result.account); token = result.token; account = result.account; api.authToken = token; notifyListeners(); return null; } on ApiException catch (error) { return error.message; } on Object { return 'Could not save this session.'; } } Future saveFcmToken(String value) async { fcmToken = value; await store.writeFcmToken(value); } Future signOut() async { final deviceToken = fcmToken; if (deviceToken != null && deviceToken.isNotEmpty && token != null) { try { await api.unregisterDevice(deviceToken); } on ApiException { // Local logout still proceeds when the device delete fails. } } token = null; account = null; fcmToken = null; api.authToken = null; await store.clear(); notifyListeners(); } void handleUnauthorized() { if (token == null && account == null) { return; } token = null; account = null; fcmToken = null; api.authToken = null; notifyListeners(); unawaited(store.clear()); } }