diff --git a/js/app.js b/js/app.js index f324b11e..564c9f81 100644 --- a/js/app.js +++ b/js/app.js @@ -276,6 +276,7 @@ class Application { this.authLoadingState = { profile: "idle", lists: "idle", dms: "idle" }; this.relayUiRefreshTimeout = null; this.lastRelayHealthWarningAt = 0; + this.appStartedAt = Date.now(); this.activeIntervals = []; this.torrentStatusIntervalId = null; this.torrentStatusNodes = null; @@ -6485,6 +6486,13 @@ class Application { return false; } + // Give the app a grace period to establish initial connections before complaining. + const now = Date.now(); + const GRACE_PERIOD_MS = 10000; + if (now - (this.appStartedAt || now) < GRACE_PERIOD_MS) { + return false; + } + const relayCandidates = Array.isArray(nostrClient.relays) ? nostrClient.relays : []; if (!relayCandidates.length) { return false; @@ -6495,7 +6503,6 @@ class Application { return false; } - const now = Date.now(); const cooldownMs = 30000; if (now - (this.lastRelayHealthWarningAt || 0) < cooldownMs) { return true; diff --git a/js/nostr/client.js b/js/nostr/client.js index 893edabb..965ed7e3 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -2110,7 +2110,9 @@ export class NostrClient { } } - const adapter = await createNip07Adapter(extension); + const adapter = await createNip07Adapter(extension, { + preloadedPubkey: pubkey, + }); adapter.pubkey = pubkey; setActiveSigner(adapter); diff --git a/js/subscriptions.js b/js/subscriptions.js index bccb873e..9af01a36 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -40,7 +40,7 @@ import { relaySubscriptionService } from "./services/relaySubscriptionService.js const SUBSCRIPTION_SET_KIND = getNostrEventSchema(NOTE_TYPES.SUBSCRIPTION_LIST)?.kind ?? 30000; -const DECRYPT_TIMEOUT_MS = 90000; +const DECRYPT_TIMEOUT_MS = 15000; const DECRYPT_RETRY_DELAY_MS = 10000; function normalizeHexPubkey(value) { @@ -937,13 +937,19 @@ class SubscriptionsManager { } if (nostrApi) { + const decrypterOptions = { + priority: NIP07_PRIORITY.HIGH, + timeoutMs: 12000, + retryMultiplier: 1, + }; + if (typeof nostrApi.nip04?.decrypt === "function") { registerDecryptor("nip04", (payload) => runNip07WithRetry( () => nostrApi.nip04.decrypt(userPubkey, payload), { label: "nip04.decrypt", - priority: NIP07_PRIORITY.HIGH, + ...decrypterOptions, }, ), ); @@ -960,7 +966,7 @@ class SubscriptionsManager { () => nip44.decrypt(userPubkey, payload), { label: "nip44.decrypt", - priority: NIP07_PRIORITY.HIGH, + ...decrypterOptions, }, ), ); @@ -974,7 +980,7 @@ class SubscriptionsManager { () => nip44v2.decrypt(userPubkey, payload), { label: "nip44.v2.decrypt", - priority: NIP07_PRIORITY.HIGH, + ...decrypterOptions, }, ), ); @@ -984,7 +990,7 @@ class SubscriptionsManager { () => nip44v2.decrypt(userPubkey, payload), { label: "nip44.v2.decrypt", - priority: NIP07_PRIORITY.HIGH, + ...decrypterOptions, }, ), ); diff --git a/js/userBlocks.js b/js/userBlocks.js index 4601dee6..bb8fc4fa 100644 --- a/js/userBlocks.js +++ b/js/userBlocks.js @@ -79,7 +79,7 @@ export const USER_BLOCK_EVENTS = Object.freeze({ const FAST_BLOCKLIST_RELAY_LIMIT = 3; const FAST_BLOCKLIST_TIMEOUT_MS = 2500; const BACKGROUND_BLOCKLIST_TIMEOUT_MS = 6000; -const DECRYPT_TIMEOUT_MS = 90000; +const DECRYPT_TIMEOUT_MS = 15000; const DECRYPT_RETRY_DELAY_MS = 10000; const MAX_BLOCKLIST_ENTRIES = 5000; @@ -1031,6 +1031,12 @@ class UserBlockListManager { } const nostrApi = typeof window !== "undefined" ? window?.nostr : null; + const decrypterOptions = { + priority: NIP07_PRIORITY.HIGH, + timeoutMs: 12000, + retryMultiplier: 1, + }; + if (nostrApi) { if (typeof nostrApi.nip04?.decrypt === "function") { registerDecryptor( @@ -1038,7 +1044,7 @@ class UserBlockListManager { (payload) => runNip07WithRetry( () => nostrApi.nip04.decrypt(normalized, payload), - { label: "nip04.decrypt", priority: NIP07_PRIORITY.HIGH }, + { label: "nip04.decrypt", ...decrypterOptions }, ), "extension", ); @@ -1055,7 +1061,7 @@ class UserBlockListManager { (payload) => runNip07WithRetry(() => nip44.decrypt(normalized, payload), { label: "nip44.decrypt", - priority: NIP07_PRIORITY.HIGH, + ...decrypterOptions, }), "extension", ); @@ -1069,7 +1075,7 @@ class UserBlockListManager { (payload) => runNip07WithRetry(() => nip44v2.decrypt(normalized, payload), { label: "nip44.v2.decrypt", - priority: NIP07_PRIORITY.HIGH, + ...decrypterOptions, }), "extension", ); @@ -1079,7 +1085,7 @@ class UserBlockListManager { (payload) => runNip07WithRetry( () => nip44v2.decrypt(normalized, payload), - { label: "nip44.v2.decrypt", priority: NIP07_PRIORITY.HIGH }, + { label: "nip44.v2.decrypt", ...decrypterOptions }, ), "extension", ); diff --git a/tests/auth-service.test.mjs b/tests/auth-service.test.mjs index e89c8406..1866a2d7 100644 --- a/tests/auth-service.test.mjs +++ b/tests/auth-service.test.mjs @@ -117,6 +117,57 @@ await (async () => { assert.deepEqual(detail.profile, { name: "Test User" }); })(); +await (async () => { + resetState(); + setPubkey(SAMPLE_PUBKEY); + + const calls = []; + // We leave resolveBlocks undefined/pending forever to simulate slow/hanging blocks + let resolveBlocks; + + const service = new AuthService({ + userBlocks: { + loadBlocks: () => { + calls.push("blocks:start"); + return new Promise((resolve) => { + resolveBlocks = resolve; + }); + }, + }, + relayManager: { + loadRelayList: () => { + calls.push("relays:start"); + return Promise.resolve("relays"); + }, + }, + }); + + service.loadOwnProfile = () => { + calls.push("profile:start"); + return Promise.resolve({ name: "Test User" }); + }; + + // Test deferBlocks: true + const promise = service.applyPostLoginState({ deferBlocks: true }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + // Blocks should NOT be started if deferred + // Wait, deferBlocks just means it's not awaited in completionPromise? + // No, checking code: + // if (!deferBlocks) { const blocksOperation = this.createBlocksLoadOperation... } + // So it shouldn't even be called/pushed to concurrentOps. + + assert.deepEqual(calls, ["relays:start", "profile:start"]); + + const result = await promise; + // This should resolve even if blocks are not loaded + await result.completionPromise; + + assert.equal(result.detail.blocksLoaded, null); + assert.equal(result.detail.relaysLoaded, true); +})(); + await (async () => { resetState(); setPubkey(SAMPLE_PUBKEY);