Crew dismissals: merge remote set before sync (close clobber pattern)
Third and final surface with the encrypted-replaceable-list clobber pattern (after Stash and Contacts). useDismissCrewNotification's cross-device sync path built the published set from cache-or-local-mirror, which are both empty on a fresh device before the useCrewDismissals read query resolves. A non-interactive user dismissing a notification in that window would publish a set with only the new id, overwriting — and wiping — the previously-synced dismissals from other devices. Fix: before encrypting/publishing, re-fetch + decrypt the remote dismissal set and union it with the pending id (and keep the local mirror + cache aligned). On read failure it still publishes the local union — dismissals are additive, so we can only ever under-merge, never silently wipe. Lower stakes than Stash/Contacts (worst case: a handled invite re-appears in the bell on another device, not real data loss), but it keeps the fetch-before-write discipline consistent across every encrypted list in the app. Amber path is unchanged — still local-only, zero extra prompts. Bumps to 1.2.25 (Android versionCode 10225 auto-derived). Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
a31583079b
commit
8affd02bcf
2 changed files with 35 additions and 2 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "skate-spots",
|
"name": "skate-spots",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.24",
|
"version": "1.2.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://skate-spots.com",
|
"homepage": "https://skate-spots.com",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,40 @@ export function useDismissCrewNotification() {
|
||||||
if (!user?.pubkey || !nip44) return; // no NIP-44: local-only, no throw
|
if (!user?.pubkey || !nip44) return; // no NIP-44: local-only, no throw
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = JSON.stringify({ ids: [...next] });
|
// Data-safety: this list is a single replaceable event whose encrypted
|
||||||
|
// content is the WHOLE dismissal set, so we rewrite all of it. If the
|
||||||
|
// read query hasn't resolved yet (fresh device: cache empty, local
|
||||||
|
// mirror empty), `next` would be just this one id and we'd overwrite —
|
||||||
|
// and thus WIPE — the previously-synced dismissals from other devices.
|
||||||
|
// So merge in the freshest remote set before publishing. (Low-stakes
|
||||||
|
// data — worst case a handled invite re-appears — but we keep the
|
||||||
|
// fetch-before-write pattern consistent with Stash/Contacts.)
|
||||||
|
const merged = new Set(next);
|
||||||
|
try {
|
||||||
|
const events = await nostr.query(
|
||||||
|
[{ kinds: [DISMISS_KIND], authors: [user.pubkey], '#d': [DISMISS_D], limit: 1 }],
|
||||||
|
{ signal: AbortSignal.timeout(6000) },
|
||||||
|
);
|
||||||
|
const remote = events[0];
|
||||||
|
if (remote?.content) {
|
||||||
|
const decrypted = await nip44.decrypt(user.pubkey, remote.content);
|
||||||
|
const parsed = JSON.parse(decrypted) as { ids?: unknown };
|
||||||
|
if (Array.isArray(parsed.ids)) {
|
||||||
|
for (const rid of parsed.ids) {
|
||||||
|
if (typeof rid === 'string') merged.add(rid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Couldn't read the remote set — publish the local union anyway; a
|
||||||
|
// dismissal is additive, so at worst we under-merge, never wipe silently.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep local mirror + cache aligned with what we're about to publish.
|
||||||
|
writeLocal([...merged]);
|
||||||
|
queryClient.setQueryData(key, merged);
|
||||||
|
|
||||||
|
const payload = JSON.stringify({ ids: [...merged] });
|
||||||
const content = await nip44.encrypt(user.pubkey, payload);
|
const content = await nip44.encrypt(user.pubkey, payload);
|
||||||
const event = await user.signer.signEvent({
|
const event = await user.signer.signEvent({
|
||||||
kind: DISMISS_KIND,
|
kind: DISMISS_KIND,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue