mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 22:47:14 +01:00
`crdt_sync` is pure Dart so it can run from CLI tools and headless systemd jobs, which means it takes closures instead of depending on flutter_secure_storage and google_sign_in. Every Flutter app then wrote the same ~390 lines to supply them, and five apps ended up carrying near-identical copies of firebase_backend.dart. Those copies drifted. `todo` gained an opt-out flag and a seeded-session path that home_inventory and wake_alarm never received, so the same bug was fixed in one copy and left live in the others -- which is the actual cost being paid here, not the line count. This package owns that glue once: the keystore adapter, the persisted per-install device id, the account store and a one-call bootstrap. A new app needs a SyncApp const and `openSync(kApp)`. The wrapper routes are a parameter rather than a constant, because todo's copy hardcoded its own `~/.config/todo/firebase_auth.json`. An extraction only one app could adopt would be worse than the copies. Also documents what made this hard to find: crdt_sync_dart's README covered only the *old* GitHub transport and never mentioned the Firebase surface at all, and sync_settings_ui -- a complete drop-in settings screen -- had no README whatsoever. The five existing apps stay on their copies and on crdt_sync_dart-v0.10.0. Migration is forward-only and those fallbacks are load-bearing; converging them is a separate task, and the README says so. 35 tests, 100% line coverage, no suppressions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
44 lines
1.9 KiB
Dart
44 lines
1.9 KiB
Dart
/// The per-install device id, persisted in `SharedPreferences`.
|
|
///
|
|
/// Every device that pushes into a namespace needs an id that is its own: it
|
|
/// is the directory segment its log is pushed under, the revision key, and
|
|
/// the node component of every HLC stamp it writes. Two devices sharing an id
|
|
/// overwrite each other's pushed file on every tick, which is why a role
|
|
/// constant (`phone`, `pc`) is wrong and a per-install uuid is right.
|
|
///
|
|
/// Not the keystore: this is an identifier, not a secret, and the keystore is
|
|
/// slower and can be cleared independently of app data. `SharedPreferences`
|
|
/// shares the app-data lifetime the log itself has.
|
|
library;
|
|
|
|
import 'package:crdt_sync/crdt_sync.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
/// Preferences key holding this install's uuid.
|
|
///
|
|
/// Named to match what the existing apps already wrote, so an app adopting
|
|
/// this package keeps its identity instead of appearing as a new device and
|
|
/// re-merging its own history as a stranger's.
|
|
const String kNodeIdKey = 'crdt.nodeId';
|
|
|
|
/// Returns this install's [DeviceIdentity], generating one on first call.
|
|
///
|
|
/// [legacyId] is the fixed role string an app pushed under before it moved to
|
|
/// a persisted uuid. Passing it keeps skip-own-writes correct: stamps already
|
|
/// written carry the old id, and a device that does not recognise its former
|
|
/// id re-downloads its own pre-migration history as though a peer wrote it.
|
|
/// Pass null for a new app, which has no history to be confused by.
|
|
Future<DeviceIdentity> loadDeviceIdentity({
|
|
String? legacyId,
|
|
SharedPreferences? preferences,
|
|
}) async {
|
|
final prefs = preferences ?? await SharedPreferences.getInstance();
|
|
var nodeId = prefs.getString(kNodeIdKey) ?? '';
|
|
if (nodeId.isEmpty) {
|
|
nodeId = const Uuid().v4();
|
|
await prefs.setString(kNodeIdKey, nodeId);
|
|
}
|
|
return DeviceIdentity(deviceId: nodeId, legacyId: legacyId);
|
|
}
|