c9a5d79ae31e

fix: add getVaultVersion, addEntryDirect, updateEntryDirect, deleteEntryDirect to vault.ts (v3.6.98)

time
2026-05-22 18:31 UTC · 5 months ago
machine
koh2
parent
5fa72ee8d048
2 files src/background/chirp-sync-vault-adapter.ts+2 −12src/background/vault.ts+52 −0
src/background/chirp-sync-vault-adapter.ts+2 −12
@@ -36,6 +36,7 @@3636   getDerivedKeyHex,3737   getEntries,3838   getEntryById,39+  getVaultVersion,3940   isUnlocked,4041 } from "./vault";4142 import {@@ -74,18 +75,7 @@7475     },7576 7677     async getLocalVersion() {77-      try {78-        const raw = await chrome.storage.local.get("peach_last_modified_at");79-        const v = (raw as Record<string, unknown>).peach_last_modified_at;80-        if (typeof v === "number") return v;81-        if (typeof v === "string" && v !== "") {82-          const n = Number.parseInt(v, 10);83-          return Number.isFinite(n) ? n : 0;84-        }85-        return 0;86-      } catch {87-        return 0;88-      }78+      return getVaultVersion();8979     },9080 9181     async buildEntryManifest() {

view file

src/background/vault.ts+52 −0
@@ -1672,6 +1672,58 @@16721672 }16731673 16741674 /**1675+ * Returns the vault's monotonic version — the `peach_last_modified_at` value1676+ * stamped on every user-initiated write. Remote (sync) writes do NOT advance1677+ * this version so a sync round never cascades into a second outbound sync.1678+ */1679+export async function getVaultVersion(): Promise<number> {1680+  try {1681+    const raw = await chrome.storage.local.get("peach_last_modified_at");1682+    const v = (raw as Record<string, unknown>).peach_last_modified_at;1683+    if (typeof v === "number") return v;1684+    if (typeof v === "string" && v !== "") {1685+      const n = Number.parseInt(v, 10);1686+      return Number.isFinite(n) ? n : 0;1687+    }1688+    return 0;1689+  } catch {1690+    return 0;1691+  }1692+}1693+1694+/**1695+ * Internal write variants — bypass `fireSyncTrigger` so the sync layer can1696+ * apply entries without cascading into another outbound sync round.1697+ *1698+ * INVARIANT: callers must not pass entries whose IDs collide with existing1699+ * active (non-deleted) entries unless the intent is an explicit overwrite.1700+ * These functions do not validate field-level constraints (password required,1701+ * card number required, etc.) — they are trust-the-caller paths for sync and1702+ * test harness use only.1703+ */1704+export async function addEntryDirect(entry: VaultEntry): Promise<void> {1705+  if (!derivedKey) throw new Error("vault_locked");1706+  entries = [...entries, entry];1707+  await persistEntries("sync_received");1708+}1709+1710+export async function updateEntryDirect(patch: VaultEntry): Promise<void> {1711+  if (!derivedKey) throw new Error("vault_locked");1712+  const idx = entries.findIndex((e) => e.id === patch.id);1713+  if (idx === -1) throw new Error("entry_not_found");1714+  entries[idx] = { ...entries[idx], ...patch };1715+  await persistEntries("sync_received");1716+}1717+1718+export async function deleteEntryDirect(id: string, deletedAt: number): Promise<void> {1719+  if (!derivedKey) throw new Error("vault_locked");1720+  const idx = entries.findIndex((e) => e.id === id);1721+  if (idx === -1) throw new Error("entry_not_found");1722+  entries[idx] = { ...entries[idx], deletedAt, modifiedAt: deletedAt };1723+  await persistEntries("sync_received");1724+}1725+1726+/**16751727  * Apply a batch of remote entries + tombstones from a chirp-sync peer.16761728  * Last-write-wins by `modifiedAt`; a tombstone supersedes a live entry when16771729  * `deletedAt > modifiedAt` on the local side. Mutates `entries` directly and

view file