import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:uuid/uuid.dart'; class RemoteStateStore { static const _storage = FlutterSecureStorage( aOptions: AndroidOptions( encryptedSharedPreferences: true, resetOnError: true, ), ); static const _kWsSessionId = 'ws_session_id'; static const _kProcessedRing = 'ws_processed_events_ring'; static const _kLastSyncIso = 'remote_last_sync_iso'; // ✅ NEW: last seen timestamp (ms) come sync.js: ws_last_seen static const _kWsLastSeenMs = 'ws_last_seen_ms'; // --------------------------- // WS session id // --------------------------- Future getOrCreateSessionId() async { var id = await _storage.read(key: _kWsSessionId); if (id == null || id.isEmpty) { id = const Uuid().v4(); await _storage.write(key: _kWsSessionId, value: id); } return id; } Future clearSessionId() => _storage.delete(key: _kWsSessionId); /// ✅ Alias “sync.js-like”: reset session id (usato quando sessione dormiente > 2 min) Future resetSessionId() => clearSessionId(); // --------------------------- // Last sync ISO // --------------------------- Future getLastSyncIso() => _storage.read(key: _kLastSyncIso); Future setLastSyncIso(String iso) => _storage.write(key: _kLastSyncIso, value: iso); // --------------------------- // WS last seen (ms) // --------------------------- /// Ritorna l’ultimo timestamp (ms) in cui abbiamo ricevuto un messaggio WS. Future getWsLastSeenMs() async { final raw = await _storage.read(key: _kWsLastSeenMs); if (raw == null || raw.isEmpty) return 0; final v = int.tryParse(raw); return v ?? 0; } /// Salva l’ultimo timestamp (ms) in cui abbiamo ricevuto un messaggio WS. Future setWsLastSeenMs(int ms) async { await _storage.write(key: _kWsLastSeenMs, value: ms.toString()); } Future clearWsLastSeenMs() => _storage.delete(key: _kWsLastSeenMs); // --------------------------- // Processed events ring // --------------------------- Future> loadProcessedRing({int maxSize = 2000}) async { final raw = await _storage.read(key: _kProcessedRing); if (raw == null || raw.isEmpty) return []; try { final v = jsonDecode(raw); if (v is List) { final list = v.whereType().toList(); return list.length > maxSize ? list.sublist(list.length - maxSize) : list; } } catch (_) {} return []; } Future saveProcessedRing(List ring, {int maxSize = 2000}) async { final trimmed = ring.length > maxSize ? ring.sublist(ring.length - maxSize) : ring; await _storage.write(key: _kProcessedRing, value: jsonEncode(trimmed)); } } class EventRingDeduper { EventRingDeduper(this.store, {this.maxSize = 2000}); final RemoteStateStore store; final int maxSize; final List _ring = []; final Set _set = {}; Future init() async { final saved = await store.loadProcessedRing(maxSize: maxSize); _ring ..clear() ..addAll(saved); _set ..clear() ..addAll(saved); } bool has(String? id) => id != null && id.isNotEmpty && _set.contains(id); Future mark(String? id) async { if (id == null || id.isEmpty) return; if (_set.contains(id)) return; _ring.add(id); _set.add(id); while (_ring.length > maxSize) { final old = _ring.removeAt(0); _set.remove(old); } await store.saveProcessedRing(_ring, maxSize: maxSize); } }