enrollment-app/lib/service/token_storage_service.dart
2026-07-28 10:40:36 +05:30

311 lines
9.1 KiB
Dart
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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