mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Wallet restore popup: re-offer on login when missing locally (like storage)
The wallet already rode the same login "restore from Nostr" offer as storage, but a once-per-device "offered" flag suppressed it — so on a device without your wallet you fell back to the manual Sync button. Now: - encryptedSyncItem: add hasLocal(pubkey) (non-null payload => item present locally, e.g. a connected wallet / configured storage). - settingsRestorePrompt: re-offer an item whenever it's MISSING locally and a Nostr backup exists — bypassing the "offered once" flag — so it surfaces exactly when you'd want it. Only respect the once-per-device flag when the item IS present locally (no nagging). A new per-device "dismissed" flag, set when you decline, keeps it quiet after an explicit "no". - Accepting enables sync, so it auto-pulls silently thereafter (the "both" behavior: silent auto-pull when enabled + popup when missing & off). - Unknown services (no hasLocal) keep the legacy behavior. - tests: re-offer when missing despite prior offer; no re-offer when present; decline stays quiet. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c562d9ee65
commit
4f56a2bd26
3 files changed
+134
-5
No files matched your search
@@ -59,6 +59,22 @@ export function createEncryptedSyncItem({
|
||||
return Boolean(result?.exists);
|
||||
}
|
||||
|
||||
// Does this device already have the item locally? (A non-null payload means
|
||||
// there's something to sync — e.g. a connected wallet / configured storage.)
|
||||
// Used by the login offer to re-surface a restore when the item is MISSING
|
||||
// here but a Nostr backup exists.
|
||||
async function hasLocal(pubkey) {
|
||||
const key = normalizePubkey(pubkey);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Boolean(await buildPayload(key));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function push(pubkey, { confirmOverwrite } = {}) {
|
||||
const key = normalizePubkey(pubkey);
|
||||
if (!key) {
|
||||
@@ -151,6 +167,7 @@ export function createEncryptedSyncItem({
|
||||
isAvailable,
|
||||
isEnabled,
|
||||
hasRemote,
|
||||
hasLocal,
|
||||
push,
|
||||
pull,
|
||||
autoPullIfNewer,
|
||||
|
||||
@@ -10,13 +10,16 @@ import { showConfirm } from "../ui/confirmDialog.js";
|
||||
import { setSyncEnabled } from "./settingsSyncFlags.js";
|
||||
|
||||
const OFFERED_KEY = "bitvid:settings-sync:offered:v1";
|
||||
// Set when the user explicitly declines a restore offer, so the "re-offer while
|
||||
// the item is missing locally" behavior below doesn't nag them on every login.
|
||||
const DISMISSED_KEY = "bitvid:settings-sync:dismissed:v1";
|
||||
|
||||
function readOffered() {
|
||||
function readMap(storageKey) {
|
||||
try {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return {};
|
||||
}
|
||||
const raw = localStorage.getItem(OFFERED_KEY);
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
const parsed = raw ? JSON.parse(raw) : {};
|
||||
return parsed && typeof parsed === "object" ? parsed : {};
|
||||
} catch (error) {
|
||||
@@ -24,6 +27,31 @@ function readOffered() {
|
||||
}
|
||||
}
|
||||
|
||||
function readOffered() {
|
||||
return readMap(OFFERED_KEY);
|
||||
}
|
||||
|
||||
function isDismissed(pubkey, kind) {
|
||||
const entry = readMap(DISMISSED_KEY)[pubkey];
|
||||
return Boolean(entry && typeof entry === "object" && entry[kind]);
|
||||
}
|
||||
|
||||
function markDismissed(pubkey, kind) {
|
||||
try {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
const map = readMap(DISMISSED_KEY);
|
||||
const kinds =
|
||||
map[pubkey] && typeof map[pubkey] === "object" ? { ...map[pubkey] } : {};
|
||||
kinds[kind] = true;
|
||||
map[pubkey] = kinds;
|
||||
localStorage.setItem(DISMISSED_KEY, JSON.stringify(map));
|
||||
} catch (error) {
|
||||
// Best-effort.
|
||||
}
|
||||
}
|
||||
|
||||
// Per-pubkey, PER-ITEM offered state. Tracking only per-pubkey meant the first
|
||||
// item offered (usually storage) flagged the whole pubkey, so a wallet note
|
||||
// published later was never offered — "the wallet doesn't restore like storage".
|
||||
@@ -78,13 +106,29 @@ export function createSettingsRestorePrompt({
|
||||
["wallet", walletSync],
|
||||
];
|
||||
for (const [kind, service] of services) {
|
||||
// Enabled items auto-pull silently elsewhere; declined items stay quiet.
|
||||
if (
|
||||
!service?.isAvailable?.() ||
|
||||
service.isEnabled(pubkey) ||
|
||||
hasOffered(pubkey, kind)
|
||||
isDismissed(pubkey, kind)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Re-offer whenever the item is MISSING locally (e.g. a fresh device or a
|
||||
// cleared wallet) so a Nostr backup gets surfaced — "restore like storage".
|
||||
// Only when we DO have it locally do we respect the once-per-device flag
|
||||
// (no nagging). Unknown (no hasLocal) → assume present → keep old behavior.
|
||||
let localCopy = true;
|
||||
if (typeof service.hasLocal === "function") {
|
||||
try {
|
||||
localCopy = Boolean(await service.hasLocal(pubkey));
|
||||
} catch (error) {
|
||||
localCopy = true;
|
||||
}
|
||||
}
|
||||
if (localCopy && hasOffered(pubkey, kind)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (await service.hasRemote()) {
|
||||
candidates.push(kind);
|
||||
@@ -147,6 +191,10 @@ export function createSettingsRestorePrompt({
|
||||
`Restore ${candidates.length > 1 ? "them" : "it"} to this device?`
|
||||
);
|
||||
if (!accepted) {
|
||||
// Remember the decline so we don't re-offer a missing item every login.
|
||||
for (const kind of candidates) {
|
||||
markDismissed(key, kind);
|
||||
}
|
||||
return { offered: true, accepted: false };
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user