MotorSignal-App/lib/services/entitlements/entitlement_storage.dart

95 lines
2.8 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'entitlement_state.dart';
/// Contract defining persistence operations for cached entitlement state.
/// This abstraction isolates the billing coordinator and domain from physical storage.
abstract class EntitlementStorage {
/// Reads cached entitlement state, returning [EntitlementState.free] if none exists or invalid.
Future<EntitlementState> read();
/// Persists [state] to cache.
Future<void> write(EntitlementState state);
/// Clears cached entitlement, reverting cache to default.
Future<void> clear();
}
/// In-memory implementation of [EntitlementStorage] for unit testing.
class InMemoryEntitlementStorage implements EntitlementStorage {
EntitlementState _state;
InMemoryEntitlementStorage([this._state = const EntitlementState.free()]);
@override
Future<EntitlementState> read() async => _state;
@override
Future<void> write(EntitlementState state) async {
_state = state;
}
@override
Future<void> clear() async {
_state = const EntitlementState.free();
}
}
/// Production implementation backed by [FlutterSecureStorage].
/// Stores normalized JSON with versioning. Never stores raw Google Play purchase tokens.
class SecureEntitlementStorage implements EntitlementStorage {
static const String storageKey = 'motorsignal_entitlement_v1';
final FlutterSecureStorage _storage;
const SecureEntitlementStorage({FlutterSecureStorage? storage})
: _storage = storage ?? const FlutterSecureStorage();
@override
Future<EntitlementState> read() async {
try {
final raw = await _storage.read(key: storageKey);
if (raw == null || raw.trim().isEmpty) {
return const EntitlementState.free();
}
final decoded = jsonDecode(raw);
if (decoded is! Map<String, dynamic>) {
debugPrint('[EntitlementStorage] Cache payload not a valid JSON map');
return const EntitlementState.free();
}
return EntitlementState.fromJson(decoded);
} catch (e, st) {
debugPrint(
'[EntitlementStorage] Error reading cached entitlement: $e\n$st',
);
return const EntitlementState.free();
}
}
@override
Future<void> write(EntitlementState state) async {
try {
final payload = jsonEncode(state.toJson());
await _storage.write(key: storageKey, value: payload);
} catch (e, st) {
debugPrint(
'[EntitlementStorage] Error writing cached entitlement: $e\n$st',
);
}
}
@override
Future<void> clear() async {
try {
await _storage.delete(key: storageKey);
} catch (e, st) {
debugPrint(
'[EntitlementStorage] Error clearing cached entitlement: $e\n$st',
);
}
}
}