writeHubState function

Future<void> writeHubState(
  1. File file, {
  2. required String? masterSecret,
  3. required Map<String, String> clients,
})

Persists {masterSecret, clients} (0600 — the file carries secrets). Best-effort: IO failures never take the hub down.

Temp + chmod + rename: the file must never be world-readable for even a moment, and content-first/chmod-second on the live path leaves exactly that window while hub.json carries the master secret (issue #794 review).

Implementation

Future<void> writeHubState(
  File file, {
  required String? masterSecret,
  required Map<String, String> clients,
}) async {
  try {
    if (!await file.parent.exists()) {
      await file.parent.create(recursive: true);
    }
    // Unique temp name: two overlapping writes must not race one
    // shared .tmp (A renames it away; B's rename then throws and its
    // enrollment is silently lost — issue #794 review round 5). The
    // write counter keeps same-microsecond writes apart; microseconds
    // keep restarts apart.
    final tmp = File(
      '${file.path}.${DateTime.now().microsecondsSinceEpoch}'
      '.${_hubStateWriteSeq++}.tmp',
    );
    await tmp.writeAsString(
      jsonEncode({'masterSecret': masterSecret, 'clients': clients}),
      flush: true,
    );
    if (!Platform.isWindows) {
      await Process.run('chmod', ['600', tmp.path]);
    }
    await tmp.rename(file.path);
  } on Object {
    // Persistence is best-effort; the in-memory state still serves.
    // (A leftover uniquely-named .tmp from a crashed write is never
    // read — harmless.)
  }
}