166 lines
4.5 KiB
Dart
166 lines
4.5 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../../features/auth/config/auth_config.dart';
|
|
import '../../features/auth/domain/auth_session.dart';
|
|
|
|
abstract class SessionStorageDriver {
|
|
const SessionStorageDriver();
|
|
|
|
Future<String?> read(String key);
|
|
Future<void> write(String key, String value);
|
|
Future<void> delete(String key);
|
|
}
|
|
|
|
class SecureSessionStorageDriver implements SessionStorageDriver {
|
|
const SecureSessionStorageDriver();
|
|
|
|
static const FlutterSecureStorage _storage = FlutterSecureStorage();
|
|
|
|
@override
|
|
Future<String?> read(String key) {
|
|
return _storage.read(key: key);
|
|
}
|
|
|
|
@override
|
|
Future<void> write(String key, String value) {
|
|
return _storage.write(key: key, value: value);
|
|
}
|
|
|
|
@override
|
|
Future<void> delete(String key) {
|
|
return _storage.delete(key: key);
|
|
}
|
|
}
|
|
|
|
class InMemorySessionStorageDriver implements SessionStorageDriver {
|
|
InMemorySessionStorageDriver({Map<String, String>? initialValues})
|
|
: _values = Map<String, String>.from(initialValues ?? const <String, String>{});
|
|
|
|
final Map<String, String> _values;
|
|
|
|
@override
|
|
Future<String?> read(String key) async {
|
|
return _values[key];
|
|
}
|
|
|
|
@override
|
|
Future<void> write(String key, String value) async {
|
|
_values[key] = value;
|
|
}
|
|
|
|
@override
|
|
Future<void> delete(String key) async {
|
|
_values.remove(key);
|
|
}
|
|
}
|
|
|
|
class SessionStore {
|
|
SessionStore._();
|
|
|
|
static final ValueNotifier<AuthSession?> _current = ValueNotifier<AuthSession?>(null);
|
|
static SessionStorageDriver _storage = const SecureSessionStorageDriver();
|
|
|
|
static ValueListenable<AuthSession?> get listenable => _current;
|
|
static AuthSession? get current => _current.value;
|
|
static bool get isSignedIn => _current.value?.isAuthenticated ?? false;
|
|
|
|
@visibleForTesting
|
|
static void useStorageDriverForTesting(SessionStorageDriver storage) {
|
|
_storage = storage;
|
|
_current.value = null;
|
|
}
|
|
|
|
@visibleForTesting
|
|
static void resetStorageDriverForTesting() {
|
|
_storage = const SecureSessionStorageDriver();
|
|
_current.value = null;
|
|
}
|
|
|
|
static Future<void> hydrate() async {
|
|
if (!AuthConfig.persistSession) {
|
|
return;
|
|
}
|
|
final raw = await _loadPersistedSession();
|
|
if (raw == null || raw.isEmpty) {
|
|
return;
|
|
}
|
|
try {
|
|
final decoded = jsonDecode(raw);
|
|
if (decoded is Map<String, dynamic>) {
|
|
final session = AuthSession.fromJson(decoded);
|
|
if (session.isAuthenticated) {
|
|
_current.value = session;
|
|
} else {
|
|
_current.value = null;
|
|
await _deletePersistedSession();
|
|
}
|
|
}
|
|
} catch (_) {
|
|
_current.value = null;
|
|
await _deletePersistedSession();
|
|
}
|
|
}
|
|
|
|
static Future<void> save(AuthSession session) async {
|
|
_current.value = session;
|
|
if (!AuthConfig.persistSession) {
|
|
return;
|
|
}
|
|
await _storage.write(AuthConfig.sessionStorageKey, jsonEncode(session.toJson()));
|
|
if (_storage is SecureSessionStorageDriver) {
|
|
await _deleteLegacySession();
|
|
}
|
|
}
|
|
|
|
static Future<void> clear() async {
|
|
_current.value = null;
|
|
if (!AuthConfig.persistSession) {
|
|
return;
|
|
}
|
|
await _storage.delete(AuthConfig.sessionStorageKey);
|
|
if (_storage is SecureSessionStorageDriver) {
|
|
await _deleteLegacySession();
|
|
}
|
|
}
|
|
|
|
static Future<String?> _loadPersistedSession() async {
|
|
final raw = await _storage.read(AuthConfig.sessionStorageKey);
|
|
if (raw != null && raw.isNotEmpty) {
|
|
return raw;
|
|
}
|
|
|
|
try {
|
|
final preferences = await SharedPreferences.getInstance();
|
|
final legacyRaw = preferences.getString(AuthConfig.sessionStorageKey);
|
|
if (legacyRaw == null || legacyRaw.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
await _storage.write(AuthConfig.sessionStorageKey, legacyRaw);
|
|
await preferences.remove(AuthConfig.sessionStorageKey);
|
|
return legacyRaw;
|
|
} on MissingPluginException {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static Future<void> _deletePersistedSession() async {
|
|
await _storage.delete(AuthConfig.sessionStorageKey);
|
|
await _deleteLegacySession();
|
|
}
|
|
|
|
static Future<void> _deleteLegacySession() async {
|
|
try {
|
|
final preferences = await SharedPreferences.getInstance();
|
|
await preferences.remove(AuthConfig.sessionStorageKey);
|
|
} on MissingPluginException {
|
|
// Unit tests that use an in-memory driver do not register the legacy plugin.
|
|
}
|
|
}
|
|
}
|