311 lines
9.1 KiB
Dart
Executable File
311 lines
9.1 KiB
Dart
Executable File
import 'dart:convert';
|
||
|
||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||
|
||
import '../presentation/claims_overview/claims_overview_cache.dart';
|
||
import 'package:nhancepolicy/logger.dart';
|
||
|
||
class TokenStorageService {
|
||
static final TokenStorageService _instance = TokenStorageService._internal();
|
||
factory TokenStorageService() => _instance;
|
||
TokenStorageService._internal();
|
||
|
||
// 🔐 Secure storage instance
|
||
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage();
|
||
|
||
// Storage keys
|
||
static const String _preEnrollmentKey = 'pre_enrollment_data';
|
||
static const String _postEnrollmentKey = 'post_enrollment_data';
|
||
static const String _selectedBranchKey = 'selected_branch';
|
||
static const String _decodedTokenKey = 'decoded_token';
|
||
static const String _branchNameKey = 'branch_name';
|
||
|
||
// In-memory cache
|
||
List<dynamic>? _preEnrollmentData;
|
||
List<dynamic>? _postEnrollmentData;
|
||
Map<String, dynamic>? _selectedBranch;
|
||
Map<String, dynamic>? _decodedToken;
|
||
|
||
// 🔄 Initialize from secure storage
|
||
Future<void> initialize() async {
|
||
final preData = await _secureStorage.read(key: _preEnrollmentKey);
|
||
if (preData != null) {
|
||
_preEnrollmentData = json.decode(preData);
|
||
}
|
||
|
||
final postData = await _secureStorage.read(key: _postEnrollmentKey);
|
||
if (postData != null) {
|
||
_postEnrollmentData = json.decode(postData);
|
||
}
|
||
|
||
final branchData = await _secureStorage.read(key: _selectedBranchKey);
|
||
if (branchData != null) {
|
||
_selectedBranch = json.decode(branchData);
|
||
}
|
||
|
||
final tokenData = await _secureStorage.read(key: _decodedTokenKey);
|
||
if (tokenData != null) {
|
||
_decodedToken = json.decode(tokenData);
|
||
}
|
||
}
|
||
|
||
// 💾 Save enrollment data
|
||
Future<void> saveEnrollmentData(
|
||
List<dynamic> preData,
|
||
List<dynamic> postData,
|
||
) async {
|
||
_preEnrollmentData = preData;
|
||
_postEnrollmentData = postData;
|
||
|
||
await _secureStorage.write(
|
||
key: _preEnrollmentKey, value: json.encode(preData));
|
||
await _secureStorage.write(
|
||
key: _postEnrollmentKey, value: json.encode(postData));
|
||
}
|
||
|
||
List<Map<String, dynamic>> getCombinedBranches() {
|
||
List<Map<String, dynamic>> combined = [];
|
||
Set<String> seenTokens = {};
|
||
Set<String> seenIds = {};
|
||
|
||
// Helper: check valid token
|
||
bool hasValidToken(dynamic token) {
|
||
return token != null && token.toString().trim().isNotEmpty;
|
||
}
|
||
|
||
// Add pre-enrollment data
|
||
if (_preEnrollmentData != null) {
|
||
for (var item in _preEnrollmentData!) {
|
||
final token = item['token'];
|
||
|
||
// ❌ SKIP if token is empty or null
|
||
if (!hasValidToken(token)) continue;
|
||
|
||
String uniqueId =
|
||
'${item['id']}_${item['client_id']}_${item['client_branch_id']}';
|
||
|
||
if (seenIds.contains(uniqueId)) continue;
|
||
if (token.isNotEmpty && seenTokens.contains(token)) continue;
|
||
|
||
if (token.isNotEmpty) seenTokens.add(token);
|
||
seenIds.add(uniqueId);
|
||
|
||
combined.add({...item, 'enrollment_type': 'pre'});
|
||
}
|
||
}
|
||
|
||
// Add post-enrollment data
|
||
if (_postEnrollmentData != null) {
|
||
for (var item in _postEnrollmentData!) {
|
||
final token = item['token'];
|
||
|
||
// ❌ SKIP if token is empty or null
|
||
if (!hasValidToken(token)) continue;
|
||
|
||
String uniqueId =
|
||
'${item['id']}_${item['client_id']}_${item['client_branch_id']}';
|
||
|
||
if (seenIds.contains(uniqueId)) continue;
|
||
if (token.isNotEmpty && seenTokens.contains(token)) continue;
|
||
|
||
if (token.isNotEmpty) seenTokens.add(token);
|
||
seenIds.add(uniqueId);
|
||
|
||
combined.add({...item, 'enrollment_type': 'post'});
|
||
}
|
||
}
|
||
|
||
return combined;
|
||
}
|
||
|
||
// 🌿 Save selected branch + decode JWT
|
||
Future<void> saveSelectedBranch(Map<String, dynamic> branch) async {
|
||
_selectedBranch = branch;
|
||
|
||
String token = branch['token']?.toString() ?? '';
|
||
_decodedToken = token.isNotEmpty ? _decodeJWT(token) : null;
|
||
|
||
await _secureStorage.write(
|
||
key: _selectedBranchKey, value: json.encode(branch));
|
||
|
||
if (_decodedToken != null) {
|
||
await _secureStorage.write(
|
||
key: _decodedTokenKey, value: json.encode(_decodedToken));
|
||
}
|
||
|
||
await _secureStorage.write(
|
||
key: _branchNameKey,
|
||
value: branch['branch_name']?.toString() ?? '',
|
||
);
|
||
|
||
// Prefer claims_sub_menu from branch payload when available.
|
||
final claimsSubMenu = branch['claims_sub_menu'];
|
||
if (claimsSubMenu is List) {
|
||
await _secureStorage.write(
|
||
key: 'claims_sub_menu',
|
||
value: jsonEncode(claimsSubMenu),
|
||
);
|
||
}
|
||
}
|
||
|
||
// 🔓 Decode JWT
|
||
Map<String, dynamic>? _decodeJWT(String token) {
|
||
try {
|
||
final parts = token.split('.');
|
||
if (parts.length != 3) return null;
|
||
|
||
final payload = base64Url.normalize(parts[1]);
|
||
final decoded = utf8.decode(base64Url.decode(payload));
|
||
|
||
return json.decode(decoded);
|
||
} catch (e) {
|
||
logDebug('JWT decode error: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Getters
|
||
Map<String, dynamic>? getSelectedBranch() => _selectedBranch;
|
||
Map<String, dynamic>? getDecodedToken() => _decodedToken;
|
||
String? getCurrentToken() => _selectedBranch?['token'];
|
||
|
||
bool isLoggedIn() =>
|
||
_selectedBranch != null &&
|
||
_selectedBranch!['token'] != null &&
|
||
_selectedBranch!['token'].toString().isNotEmpty;
|
||
|
||
// 🚪 Logout (clear everything)
|
||
Future<void> clearAll() async {
|
||
_preEnrollmentData = null;
|
||
_postEnrollmentData = null;
|
||
_selectedBranch = null;
|
||
_decodedToken = null;
|
||
|
||
await _secureStorage.deleteAll();
|
||
}
|
||
|
||
Future<void> saveDecodedSessionData(
|
||
Map<String, dynamic> decodedToken,
|
||
String token,
|
||
) async {
|
||
// Handle allowed_modules safely
|
||
dynamic allowedModules = decodedToken['allowed_modules'];
|
||
if (allowedModules is String) {
|
||
allowedModules = jsonDecode(allowedModules);
|
||
}
|
||
|
||
await _secureStorage.write(
|
||
key: 'empClientBranchId',
|
||
value: decodedToken['post_branch_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'empPrimaryId', value: decodedToken['post_hr_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'empClientId', value: decodedToken['post_client_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'empHrId', value: decodedToken['post_hr_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'empAllowed_modules',
|
||
value: jsonEncode(allowedModules?['post'] ?? []));
|
||
|
||
// ================= PRE (Enrollment) =================
|
||
|
||
await _secureStorage.write(
|
||
key: 'enrollmentEmpClientBranchId',
|
||
value: decodedToken['pre_branch_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'enrollmentEmpPrimaryId',
|
||
value: decodedToken['pre_hr_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'enrollmentClient_id',
|
||
value: decodedToken['pre_client_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'enrollmentHrId', value: decodedToken['pre_hr_id']?.toString());
|
||
|
||
await _secureStorage.write(
|
||
key: 'enrollmentAllowed_modules',
|
||
value: jsonEncode(allowedModules?['pre'] ?? []));
|
||
|
||
// ================= TOKEN =================
|
||
await _secureStorage.write(key: 'token', value: token);
|
||
|
||
// ================= CLAIMS SUB MENU =================
|
||
if (decodedToken.containsKey('claims_sub_menu')) {
|
||
final claimsSubMenu = decodedToken['claims_sub_menu'];
|
||
await _secureStorage.write(
|
||
key: 'claims_sub_menu',
|
||
value: jsonEncode(claimsSubMenu is List ? claimsSubMenu : []),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<String?> readValue(String key) async {
|
||
return await _secureStorage.read(key: key);
|
||
}
|
||
|
||
Future<void> writeValue(String key, String value) async {
|
||
await _secureStorage.write(key: key, value: value);
|
||
}
|
||
|
||
Future<void> removeValue(String key) async {
|
||
await _secureStorage.delete(key: key);
|
||
}
|
||
|
||
Future<void> clearBranchSession() async {
|
||
final keysToRemove = [
|
||
'selected_branch',
|
||
'decoded_token',
|
||
'clientLogo',
|
||
'clientName',
|
||
'empAllowed_modules',
|
||
'empClientBranchId',
|
||
'empClientId',
|
||
'empEmail',
|
||
'empHrId',
|
||
'empPrimaryId',
|
||
'enrollmentAllowed_modules',
|
||
'enrollmentClient_id',
|
||
'enrollmentEmpClientBranchId',
|
||
'enrollmentEmpPrimaryId',
|
||
'enrollmentHrId',
|
||
'token',
|
||
'claims_sub_menu',
|
||
];
|
||
|
||
for (final key in keysToRemove) {
|
||
await _secureStorage.delete(key: key);
|
||
}
|
||
}
|
||
|
||
Future<void> resetSessionAndSwitchBranch(
|
||
Map<String, dynamic> newBranch,
|
||
) async {
|
||
// 1️⃣ Clear ONLY branch/session related keys
|
||
await clearBranchSession();
|
||
|
||
// 2️⃣ Clear claims overview cache so the dashboard fetches fresh data
|
||
await ClaimsOverviewCache.clearAll();
|
||
|
||
// 3️⃣ Save selected branch
|
||
await saveSelectedBranch(newBranch);
|
||
|
||
// 3️⃣ Rebuild decoded session data from token
|
||
final token = newBranch['token']?.toString();
|
||
if (token != null && token.isNotEmpty) {
|
||
final decoded = _decodeJWT(token);
|
||
if (decoded != null) {
|
||
await saveDecodedSessionData(decoded, token);
|
||
}
|
||
}
|
||
|
||
// 4️⃣ Update in-memory cache
|
||
_selectedBranch = newBranch;
|
||
}
|
||
}
|