From 431907c9959e6d414fe13e05f76d1a1a45f7828e Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 30 Aug 2026 13:08:05 -0700 Subject: The DM drain wakes the signer itself: boot's single resume attempt losing to cold-start churn left the phone holding every wrap with nothing ever asking the bunker again — now a drain that finds its decryptor set incomplete calls resume (single-flight, rate-limited) before deciding anything, and the bunker resume deadline stretches to 15s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr --- src/features/messages.js | 13 ++++++++++--- src/nostr-login.js | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/features/messages.js b/src/features/messages.js index 4003e6c..637c2d1 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -1155,10 +1155,17 @@ export function messagesFeature(ctx) { } async function drainPendingWraps() { - if (draining || !pendingWraps.size || !dmDecryptors().length) return; + if (draining || !pendingWraps.size) return; draining = true; - const complete = decryptorsComplete(); try { + // Wraps sealed to the login npub can't open without its signer. Wake it + // up ourselves — the boot-time resume is a single attempt, and if it + // loses to cold-start churn nothing else asks until the user happens to + // touch a screen that calls identity(). Resume single-flights and + // rate-limits itself, so asking here is cheap. + if (!decryptorsComplete()) await Promise.resolve(hook('nostrLoginResume')).catch(() => null); + if (!dmDecryptors().length) return; + const complete = decryptorsComplete(); for (const [id, p] of [...pendingWraps]) { // A remote signer can die mid-pass; counting the remaining wraps as // full-strength failures would evict messages that were never really @@ -1169,7 +1176,7 @@ export function messagesFeature(ctx) { } } finally { draining = false; } // still holding wraps: the signer may connect later — keep a slow retry - if (pendingWraps.size) scheduleDrain(complete ? 30_000 : 60_000); + if (pendingWraps.size) scheduleDrain(decryptorsComplete() ? 30_000 : 60_000); } function openDirectBundle(json) { diff --git a/src/nostr-login.js b/src/nostr-login.js index 4cc1ddf..86c6b79 100644 --- a/src/nostr-login.js +++ b/src/nostr-login.js @@ -126,7 +126,7 @@ export async function bunkerSigner(uri, { onAuth } = {}) { // permission to this client key, not to this page load, so rebuilding from // the same client key and pointer picks the session back up — usually without // prompting the user again. -export async function resumeBunker(session, { onAuth, timeoutMs = 8000 } = {}) { +export async function resumeBunker(session, { onAuth, timeoutMs = 15000 } = {}) { if (!session || !session.local || !session.bp || !session.bp.pubkey) return null; const { BunkerSigner } = await loadNip46(); const local = hex.decode(session.local); -- cgit v1.2.3