From 374601717adcbbdc5bc07bf6ecbc54972a043a62 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 19 Mar 2026 14:49:01 +0100 Subject: [PATCH] more bug-fixes --- src/i18n/locales/de.ts | 2 + src/i18n/locales/en.ts | 2 + src/lib/error-suppression.ts | 7 +++ src/pages/primary/SpellsPage/index.tsx | 29 +++++++--- src/services/client.service.ts | 75 ++++++++++++++++---------- 5 files changed, 80 insertions(+), 35 deletions(-) diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index fa3b40d3..793034e8 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -621,6 +621,8 @@ export default { 'shortcuts.browserBack': 'Zurück im Browser (Verlauf)', spellPickerSectionYours: 'Deine Zaubersprüche', + 'Failed to remove spell from local storage': + 'Zauberspruch konnte lokal nicht entfernt werden', Spells: 'Zaubersprüche', Tags: 'Tags', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index bd3fa681..281050a9 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -784,6 +784,8 @@ export default { 'Add tag filter': 'Add tag filter', spellPickerSectionYours: 'Your spells', + 'Failed to remove spell from local storage': + 'Failed to remove spell from local storage', Spells: 'Spells', diff --git a/src/lib/error-suppression.ts b/src/lib/error-suppression.ts index 78dc3aac..808a6941 100644 --- a/src/lib/error-suppression.ts +++ b/src/lib/error-suppression.ts @@ -272,6 +272,13 @@ function suppressExpectedRejections() { if (msg.includes('The operation is insecure') || (event.reason?.name === 'SecurityError' && msg.includes('insecure'))) { event.preventDefault() event.stopPropagation() + return + } + // nostr-tools: relay.send() attaches to connectionPromise without .catch(); if the socket + // closes before the REQ is sent, the rejection was previously uncaught (SendingOnClosedConnection). + if (event.reason?.name === 'SendingOnClosedConnection') { + event.preventDefault() + event.stopPropagation() } }) } diff --git a/src/pages/primary/SpellsPage/index.tsx b/src/pages/primary/SpellsPage/index.tsx index 351224b6..892021bb 100644 --- a/src/pages/primary/SpellsPage/index.tsx +++ b/src/pages/primary/SpellsPage/index.tsx @@ -25,6 +25,7 @@ import UserAvatar from '@/components/UserAvatar' import Username from '@/components/Username' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' import logger from '@/lib/logger' +import { showPublishingError } from '@/lib/publishing-feedback' import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import client from '@/services/client.service' @@ -147,7 +148,7 @@ function SpellSheetOptionRow({ const SpellsPage = forwardRef(function SpellsPage(_, ref) { const { t } = useTranslation() - const { pubkey, relayList } = useNostr() + const { pubkey, relayList, attemptDelete } = useNostr() const [spells, setSpells] = useState([]) const [favoriteIds, setFavoriteIds] = useState>(new Set()) const [selectedSpell, setSelectedSpell] = useState(null) @@ -449,13 +450,27 @@ const SpellsPage = forwardRef(function SpellsPage(_, ref) { const handleDeleteSpell = useCallback( async (spell: Event) => { - await indexedDb.deleteSpellEvent(spell.id) - const ids = await indexedDb.getSpellFavoriteIds() - await indexedDb.setSpellFavoriteIds(ids.filter((id) => id !== spell.id)) - if (selectedSpell?.id === spell.id) setSelectedSpell(null) - loadSpells() + try { + await attemptDelete(spell) + } catch (e) { + logger.error('Spell deletion publish failed', { error: e, spellId: spell.id }) + showPublishingError(e instanceof Error ? e : new Error(String(e))) + return + } + try { + await indexedDb.deleteSpellEvent(spell.id) + const ids = await indexedDb.getSpellFavoriteIds() + await indexedDb.setSpellFavoriteIds(ids.filter((id) => id !== spell.id)) + if (selectedSpell?.id === spell.id) setSelectedSpell(null) + await loadSpells() + } catch (e) { + logger.error('Spell local cleanup after delete failed', { error: e, spellId: spell.id }) + showPublishingError( + e instanceof Error ? e : new Error(t('Failed to remove spell from local storage')) + ) + } }, - [loadSpells, selectedSpell?.id] + [attemptDelete, loadSpells, selectedSpell?.id, t] ) const { ownSpells, followSpells, otherSpells, spellsForSelect } = useMemo(() => { diff --git a/src/services/client.service.ts b/src/services/client.service.ts index abe3e4a7..c39c6fb7 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -1066,39 +1066,58 @@ class ClientService extends EventTarget { onclose: (reason: string) => { releaseOnce() if (reason.startsWith('auth-required: ') && that.canSignerAuthenticateRelay()) { - relay.auth(async (authEvt: EventTemplate) => { - const evt = await that.signer!.signEvent(authEvt) - if (!evt) throw new Error('sign event failed') - return evt as VerifiedEvent - }).then(() => that.acquireSubSlot(relayKey)).then(() => { - let slotReleased2 = false - const releaseSlot2 = () => { - if (!slotReleased2) { - slotReleased2 = true + relay + .auth(async (authEvt: EventTemplate) => { + const evt = await that.signer!.signEvent(authEvt) + if (!evt) throw new Error('sign event failed') + return evt as VerifiedEvent + }) + .then(async () => { + await that.acquireSubSlot(relayKey) + // After AUTH the socket may be closed or the relay dropped from the pool; + // resubscribe on a fresh connection from ensureRelay (fixes SendingOnClosedConnection). + let liveRelay: AbstractRelay + try { + liveRelay = await that.pool.ensureRelay(url, { connectionTimeout: 5000 }) + } catch (err) { that.releaseSubSlot(relayKey) + handleClose(i, (err as Error)?.message ?? String(err)) + return } - } - const sub2 = relay.subscribe(relayFilters, { - receivedEvent: (_relay, id) => that.trackEventSeenOn(id, _relay), - onevent: (evt: NEvent) => onevent?.(evt), - oneose: () => handleEose(i), - onclose: (reason2: string) => { - releaseSlot2() - handleClose(i, reason2) - }, - alreadyHaveEvent: localAlreadyHaveEvent, - eoseTimeout: 10_000 - }) - subs.push({ - relayKey, - close: () => { + let slotReleased2 = false + const releaseSlot2 = () => { + if (!slotReleased2) { + slotReleased2 = true + that.releaseSubSlot(relayKey) + } + } + try { + const sub2 = liveRelay.subscribe(relayFilters, { + receivedEvent: (_relay, id) => that.trackEventSeenOn(id, _relay), + onevent: (evt: NEvent) => onevent?.(evt), + oneose: () => handleEose(i), + onclose: (reason2: string) => { + releaseSlot2() + handleClose(i, reason2) + }, + alreadyHaveEvent: localAlreadyHaveEvent, + eoseTimeout: 10_000 + }) + subs.push({ + relayKey, + close: () => { + releaseSlot2() + sub2.close() + } + }) + } catch (err) { releaseSlot2() - sub2.close() + handleClose(i, (err as Error)?.message ?? String(err)) } }) - }).catch((err) => { - handleClose(i, `auth failed: ${(err as Error)?.message ?? err}`) - }) + .catch((err) => { + handleClose(i, `auth failed: ${(err as Error)?.message ?? err}`) + }) return } if (reason.startsWith('auth-required: ')) {