Shared core for the opt-in encrypted sync of bitvid settings (storage creds, NWC
wallet) recorded in todo #15. Publishes settings as NIP-78 app-data (kind 30078)
REPLACEABLE events, NIP-44-encrypted to the user's own key (NIP-04 fallback when
the signer lacks NIP-44).
js/nostr/encryptedSync.js — createEncryptedSyncManager(deps):
- isAvailable(): signer present + can encrypt to self
- push(dTag, payload): encrypt-to-self -> publish to WRITE relays, created_at
forced strictly-newer so a replace actually wins
- pull(dTag): read newest-per-d-tag from read relays -> decrypt (a stale device
cannot clobber newer settings)
- clear(dTag): publish a cleared marker so the wipe propagates (empty-
replaceable-publish lesson)
Dependency-injected (pure) so it unit-tests without a live signer/relay. No UI
wiring yet — no behavior change. Storage pane wiring is the next increment.
Security: encryption hides contents but not that this pubkey stores bitvid
settings (public relays); anyone with the user's key can decrypt; NWC URIs are
spending secrets (caller must gate behind a warning); never log decrypted data.
tests/encrypted-sync.test.mjs — round-trip, no-cleartext-leak, newest-wins,
clear, NIP-04 fallback. Mutation-verified (newest-selection + encryption-bypass
both fail the suite).
Co-Authored-By: Claude Opus 4.8 <[email protected]>