From 4c4917b3697050e0a83d6977d76a76e6f436d5b7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 14 Sep 2026 18:05:05 -0700 Subject: Messages sent while the app was away arrive when it comes back. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two ways a DM or a room message went missing. A phone freezing the tab errors the relay sockets, and an errored socket is one the pool gives up on — so every wrap subscription died silently and nothing sent after the freeze ever arrived, until a reload. Coming back from the background (or back online) now rebuilds the DM and room subscriptions; their windows overlap what is held and seen wraps are dropped before any decryption. And a remote signer that never answered was read as "this wrap isn't ours": three such silences and the wrap was remembered as seen and lost on that device for good. Silence now means "try later" — uncounted — and the drain stops at the first one rather than burning tries on the rest. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR --- src/dm.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/dm.js') diff --git a/src/dm.js b/src/dm.js index 000e63c..46789e8 100644 --- a/src/dm.js +++ b/src/dm.js @@ -112,7 +112,12 @@ export async function unwrapDM(wrap, skOrSigner) { const to = rumor.tags?.find((t) => t[0] === 'p')?.[1]; const mine = rumor.pubkey === signer.pubkey; return { rumor, author: seal.pubkey, peer: mine ? to : rumor.pubkey }; - } catch { + } catch (e) { + // A remote signer that never answered is not "not ours": the caller must + // try again later, not give the wrap up. Every other failure means the + // wrap was sealed to someone else. + if (SIGNER_SILENT.test(e?.message || '')) throw e; return null; } } +export const SIGNER_SILENT = /did not answer|timed? ?out|connect|closed|socket|relay|network/i; -- cgit v1.2.3