import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import '../../domain/entities/pos_session.dart'; /// Keeps a terminal signed in across restarts. /// /// A till is not a browser. It signs in when a shop opens and bills for the /// whole trading day, often on a connection that comes and goes, and it must /// survive being rebooted mid-shift without a queue of customers waiting while /// somebody finds the manager's password. /// /// The whole session goes to the platform keystore rather than to SQLite. The /// token is a bearer credential — anything holding it can bill as this shop — /// and SQLite here is a file on a machine behind a shop counter, readable by /// anything that can open it. The rest of the session travels with the token /// because splitting them invites the two halves to disagree about which outlet /// this terminal is. class SessionStore { SessionStore({FlutterSecureStorage? secureStorage}) : _secure = secureStorage ?? const FlutterSecureStorage(); final FlutterSecureStorage _secure; static const _key = 'pos.session'; /// Reads the saved session, or null when there is none worth using. /// /// An expired session is treated as absent rather than returned for the /// caller to check. Every caller would have to make the same check, and the /// one that forgot would send a dead token all day and read the resulting /// 401s as a server fault. Future read({DateTime? now}) async { final String? raw; try { raw = await _secure.read(key: _key); } on Object catch (e) { // No keystore — a headless test host, or a Linux box with no secret // service. Signing in again is the safe way to fail. debugPrint('Secure storage unavailable, session not loaded: $e'); return null; } if (raw == null || raw.isEmpty) return null; try { final session = PosSession.fromJson(jsonDecode(raw) as Map); if (!session.isValidAt(now ?? DateTime.now())) return null; if (session.token.isEmpty || session.locationId <= 0) return null; return session; } on Object catch (e) { // A stored session this build cannot parse — most likely written by an // older one. Dropped rather than repaired: a half-understood session is // worse than none, and re-authenticating costs one screen. debugPrint('Stored session could not be read, discarding: $e'); return null; } } Future write(PosSession session) async { try { await _secure.write(key: _key, value: jsonEncode(session.toJson())); } on Object catch (e) { // The terminal keeps working on the session it holds in memory; it just // will not survive a restart. Failing the sign-in over this would close a // shop for a keystore problem. debugPrint('Could not persist the session: $e'); } } Future clear() async { try { await _secure.delete(key: _key); } on Object catch (e) { debugPrint('Could not clear the session: $e'); } } }