mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
walletSyncService and storageSyncService were near-identical copies (the "two sync systems"). Extract the shared machinery into createEncryptedSyncItem() — per-pubkey enable flags, conflict-checked push, cheap remote-exists check, enable/disable/pull — so each service is now a thin config differing only in d-tag, kind, availability, and buildPayload/applyPayload. Public surface (isAvailable/isEnabled/hasRemote/push/pull/enable/disable + the *_DTAG export) preserved; both dedicated suites pass (8). Next (step 2): shared confirm dialog + consistent notifications + seamless auto-sync in the two profile controllers (the user-facing UX unification). Co-Authored-By: Claude Opus 4.8 <[email protected]>
127 lines
3.8 KiB
JavaScript
127 lines
3.8 KiB
JavaScript
// One encrypted cross-login sync implementation, shared by every syncable item
|
|
// (NWC wallet, storage credentials, …). Previously walletSyncService and
|
|
// storageSyncService were near-identical copies; the only per-item differences
|
|
// are the d-tag, the sync "kind", the availability check, and how the payload is
|
|
// built (push) / applied (pull). This factory captures the shared machinery —
|
|
// per-pubkey enable flags, conflict-checked push, cheap remote existence check,
|
|
// enable/disable — so the two "systems" become one.
|
|
//
|
|
// Item config:
|
|
// id "wallet" | "storage" — the per-pubkey sync-flag kind
|
|
// dtag stable d-tag for the encrypted note
|
|
// encryptedSync the encrypted-sync facade
|
|
// available () => boolean (extra availability beyond encryptedSync)
|
|
// buildPayload async (pubkey) => payload | null (null => "nothing-to-sync")
|
|
// applyPayload async (pubkey, payload) => void | { imported:false, error }
|
|
// logLabel short label for warnings
|
|
|
|
import { encryptedSync as defaultEncryptedSync } from "../nostr/encryptedSyncFacade.js";
|
|
import { isSyncEnabled, setSyncEnabled } from "./settingsSyncFlags.js";
|
|
import { pushWithConflictCheck } from "./syncConflict.js";
|
|
import { userLogger } from "../utils/logger.js";
|
|
|
|
export function createEncryptedSyncItem({
|
|
id,
|
|
dtag,
|
|
encryptedSync = defaultEncryptedSync,
|
|
available = () => true,
|
|
buildPayload,
|
|
applyPayload,
|
|
logLabel = id || "sync",
|
|
} = {}) {
|
|
function normalizePubkey(pubkey) {
|
|
return typeof pubkey === "string" ? pubkey.trim() : "";
|
|
}
|
|
|
|
function isAvailable() {
|
|
return Boolean(encryptedSync?.isAvailable?.()) && Boolean(available());
|
|
}
|
|
|
|
function isEnabled(pubkey) {
|
|
return isSyncEnabled(normalizePubkey(pubkey), id);
|
|
}
|
|
|
|
function setEnabledFlag(pubkey, enabled) {
|
|
setSyncEnabled(normalizePubkey(pubkey), id, enabled);
|
|
}
|
|
|
|
// Cheap existence check (no decrypt) for the login-restore offer.
|
|
async function hasRemote() {
|
|
if (typeof encryptedSync.exists !== "function") {
|
|
return false;
|
|
}
|
|
const result = await encryptedSync.exists(dtag);
|
|
return Boolean(result?.exists);
|
|
}
|
|
|
|
async function push(pubkey, { confirmOverwrite } = {}) {
|
|
const key = normalizePubkey(pubkey);
|
|
if (!key) {
|
|
return { ok: false, error: "missing-pubkey" };
|
|
}
|
|
let payload;
|
|
try {
|
|
payload = await buildPayload(key);
|
|
} catch (error) {
|
|
userLogger.warn(`[${logLabel}] Failed to build sync payload:`, error);
|
|
return { ok: false, error: "export-failed", cause: error };
|
|
}
|
|
if (!payload) {
|
|
return { ok: false, error: "nothing-to-sync" };
|
|
}
|
|
return pushWithConflictCheck({
|
|
encryptedSync,
|
|
dTag: dtag,
|
|
kind: id,
|
|
pubkey: key,
|
|
payload,
|
|
confirmOverwrite,
|
|
});
|
|
}
|
|
|
|
async function pull(pubkey) {
|
|
const key = normalizePubkey(pubkey);
|
|
if (!key) {
|
|
return { found: false, error: "missing-pubkey" };
|
|
}
|
|
const result = await encryptedSync.pull(dtag);
|
|
if (!result?.found) {
|
|
return result;
|
|
}
|
|
try {
|
|
const applied = await applyPayload(key, result.payload || {});
|
|
if (applied && applied.imported === false) {
|
|
return { ...result, imported: false, error: applied.error };
|
|
}
|
|
} catch (error) {
|
|
userLogger.warn(`[${logLabel}] Failed to apply pulled settings:`, error);
|
|
return { ...result, imported: false, error: "import-failed", cause: error };
|
|
}
|
|
return { ...result, imported: true };
|
|
}
|
|
|
|
async function enable(pubkey, options = {}) {
|
|
setEnabledFlag(pubkey, true);
|
|
return push(pubkey, options);
|
|
}
|
|
|
|
async function disable(pubkey) {
|
|
setEnabledFlag(pubkey, false);
|
|
return encryptedSync.clear(dtag);
|
|
}
|
|
|
|
return {
|
|
id,
|
|
dtag,
|
|
isAvailable,
|
|
isEnabled,
|
|
hasRemote,
|
|
push,
|
|
pull,
|
|
enable,
|
|
disable,
|
|
};
|
|
}
|
|
|
|
export default createEncryptedSyncItem;
|