From fd62159e615b4eb79e34d3a9cfe681337a4bd118 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:16:56 -0400 Subject: [PATCH 1/2] Guard NIP-07 login calls with shared timeout --- js/nostr.js | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/js/nostr.js b/js/nostr.js index 7651157f..9c92585f 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -31,6 +31,8 @@ const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1"; const LEGACY_EVENTS_STORAGE_KEY = "bitvidEvents"; const EVENTS_CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes const NIP07_LOGIN_TIMEOUT_MS = 15_000; // 15 seconds +const NIP07_LOGIN_TIMEOUT_ERROR_MESSAGE = + "Timed out waiting for the NIP-07 extension. Check the extension prompt and try again."; const SESSION_ACTOR_STORAGE_KEY = "bitvid:sessionActor:v1"; const WATCH_HISTORY_CACHE_STORAGE_KEY = "bitvid:watchHistoryCache:v1"; @@ -52,6 +54,24 @@ function logErrorOnce(message, eventContent = null) { } } +function withNip07Timeout(operation) { + let timeoutId; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error(NIP07_LOGIN_TIMEOUT_ERROR_MESSAGE)); + }, NIP07_LOGIN_TIMEOUT_MS); + }); + + return Promise.race([ + Promise.resolve().then(operation), + timeoutPromise, + ]).finally(() => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }); +} + function pointerKey(pointer) { if (!pointer || typeof pointer !== "object") { return ""; @@ -2895,7 +2915,7 @@ class NostrClient { console.log("Requesting permissions from NIP-07 extension..."); } try { - await extension.enable(); + await withNip07Timeout(() => extension.enable()); } catch (enableErr) { throw new Error( enableErr && enableErr.message @@ -2907,8 +2927,8 @@ class NostrClient { if (allowAccountSelection && typeof extension.selectAccounts === "function") { try { - const selection = await extension.selectAccounts( - expectPubkey ? [expectPubkey] : undefined + const selection = await withNip07Timeout(() => + extension.selectAccounts(expectPubkey ? [expectPubkey] : undefined) ); const didCancelSelection = @@ -2928,24 +2948,7 @@ class NostrClient { throw new Error(message); } } - - let timeoutId; - const pubkey = await Promise.race([ - extension.getPublicKey(), - new Promise((_, reject) => { - timeoutId = setTimeout(() => { - reject( - new Error( - "Timed out waiting for the NIP-07 extension. Check the extension prompt and try again." - ) - ); - }, NIP07_LOGIN_TIMEOUT_MS); - }), - ]).finally(() => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }); + const pubkey = await withNip07Timeout(() => extension.getPublicKey()); if (!pubkey || typeof pubkey !== "string") { throw new Error( "The NIP-07 extension did not return a public key. Please try again." From 8d7d7a86cfafa1ca0cdb4cb78e2a4138f05deb81 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:22:50 -0400 Subject: [PATCH 2/2] Fix NIP-07 timeout helper to preserve user gestures --- js/nostr.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/js/nostr.js b/js/nostr.js index 9c92585f..c0043c40 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -62,10 +62,19 @@ function withNip07Timeout(operation) { }, NIP07_LOGIN_TIMEOUT_MS); }); - return Promise.race([ - Promise.resolve().then(operation), - timeoutPromise, - ]).finally(() => { + let operationResult; + try { + operationResult = operation(); + } catch (err) { + if (timeoutId) { + clearTimeout(timeoutId); + } + throw err; + } + + const operationPromise = Promise.resolve(operationResult); + + return Promise.race([operationPromise, timeoutPromise]).finally(() => { if (timeoutId) { clearTimeout(timeoutId); }