124 lines
3.6 KiB
Dart
124 lines
3.6 KiB
Dart
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<String> 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<void> clearSessionId() => _storage.delete(key: _kWsSessionId);
|
||
|
||
/// ✅ Alias “sync.js-like”: reset session id (usato quando sessione dormiente > 2 min)
|
||
Future<void> resetSessionId() => clearSessionId();
|
||
|
||
// ---------------------------
|
||
// Last sync ISO
|
||
// ---------------------------
|
||
|
||
Future<String?> getLastSyncIso() => _storage.read(key: _kLastSyncIso);
|
||
|
||
Future<void> 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<int> 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<void> setWsLastSeenMs(int ms) async {
|
||
await _storage.write(key: _kWsLastSeenMs, value: ms.toString());
|
||
}
|
||
|
||
Future<void> clearWsLastSeenMs() => _storage.delete(key: _kWsLastSeenMs);
|
||
|
||
// ---------------------------
|
||
// Processed events ring
|
||
// ---------------------------
|
||
|
||
Future<List<String>> loadProcessedRing({int maxSize = 2000}) async {
|
||
final raw = await _storage.read(key: _kProcessedRing);
|
||
if (raw == null || raw.isEmpty) return <String>[];
|
||
try {
|
||
final v = jsonDecode(raw);
|
||
if (v is List) {
|
||
final list = v.whereType<String>().toList();
|
||
return list.length > maxSize ? list.sublist(list.length - maxSize) : list;
|
||
}
|
||
} catch (_) {}
|
||
return <String>[];
|
||
}
|
||
|
||
Future<void> saveProcessedRing(List<String> 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<String> _ring = [];
|
||
final Set<String> _set = {};
|
||
|
||
Future<void> 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<void> 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);
|
||
}
|
||
}
|