Ariadne mobile 0.10: bundle BNS starter index in APK

Mobile Bns.java already had a disk cache + background refresh, but on a
FRESH install the disk was empty — first .bch navigation blocked on a
full electrum walk (~10-60 s over mobile network). Fix: ship a starter
BNS index inside the APK so the first navigation returns from cache.

* Argus/src/generate-mobile-index.mjs — new build-time tool. Reads the
  raw name-beacon snapshot from Theseus's bundle, runs the SAME
  buildIndexFromSnapshot reduction, serialises in the shape Bns.saveDisk
  writes. Regenerate every release: `node src/generate-mobile-index.mjs`.
* AriadneResolver/mobile/app/assets/bns-index.json — the generated file
  (44 names, snapshot root c37b8596…). Packaged via aapt2 -A.
* build.ps1 — pass `-A app/assets` to aapt2 link if the dir exists, so
  the asset makes it into the APK. Version bumped to 0.10.
* AndroidManifest.xml — versionCode 10 / versionName 0.10.
* Bns.java — new setBundledIndex(byte[]); loadDisk() shares its parser
  (parseIndexBytes) with the bundled path so both cache sources produce
  identical Entry shapes. On cold start, if the user disk cache is empty
  we fall through to the bundled bytes; existing behaviour otherwise.
* MainActivity.applyResolverSettings() — reads bns-index.json from
  assets and hands the bytes to Bns.setBundledIndex. Best-effort: a
  missing asset just skips the fallback and the network path takes over.
This commit is contained in:
Local Dev 2026-08-30 22:15:10 +02:00
parent e6f2e4b99f
commit 4bfb7ba68f

View file

@ -0,0 +1,52 @@
// Bundle a starter BNS index for the Ariadne mobile app.
//
// The mobile Bns.java stores its disk cache as a JSON array of {name, category,
// records, txid, height, updatedTxid} — the shape saveDisk() writes and
// loadDisk() reads. When the on-device cache is empty (fresh install), the app
// falls back to a bundled copy in APK assets so the first .bch page opens
// without waiting on a full electrum walk.
//
// This script builds that bundle from the raw name-beacon snapshot on disk
// (produced by src/snapshot-name-to-file.mjs). Runs the SAME reduction
// (buildIndexFromSnapshot) so the bundled index is byte-for-byte what a live
// buildIndex would produce from the same chain state.
//
// node src/generate-mobile-index.mjs [outfile]
//
// Default outfile: AriadneResolver/mobile/app/assets/bns-index.json — the
// path build.ps1 packages via aapt2's `-A` flag.
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { buildIndexFromSnapshot } from "./lib/resolver-web.js";
const IN = fileURLToPath(new URL("../../TheseusNavigator/snapshots/bns-name-snapshot.json", import.meta.url));
const OUT = process.argv[2] || fileURLToPath(
new URL("../../AriadneResolver/mobile/app/assets/bns-index.json", import.meta.url),
);
const snap = JSON.parse(readFileSync(IN, "utf8"));
console.log(`snapshot: ${snap.history.length} beacon txs, root=${snap.root}`);
const idx = buildIndexFromSnapshot({ snapshot: snap });
// Serialise in mobile Bns.saveDisk() shape. `records` stays as a JSON object
// (Bns.Entry stores it as JSONObject and re-serialises identically on save).
// `updatedTxid` is null-safe: mobile writes JSONObject.NULL for null; JSON.stringify
// emits `null` which is the same thing over the wire.
const out = [];
for (const entry of idx.values()) {
out.push({
name: entry.name,
category: entry.category,
records: entry.records ?? {},
txid: entry.txid,
height: entry.height,
updatedTxid: entry.updatedTxid ?? null,
});
}
mkdirSync(path.dirname(OUT), { recursive: true });
writeFileSync(OUT, JSON.stringify(out));
console.log(`wrote ${idx.size} names to ${OUT}`);