fix(auth): optimize login speed and list decryption

- Reduced decryption timeout for user lists from 90s to 15s to fail faster.
- Optimized NIP-07 decryption calls to avoid retries and long timeouts during critical loading paths.
- Fixed a bug where `loginWithExtension` triggered `getPublicKey` twice, doubling the user interaction time.
- Added a 10s grace period for relay health warnings to prevent false positives on startup.
- Verified `deferBlocks` logic in `authService` prevents blocking the login UI on slow list decryption.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-01-30 20:31:24 +00:00
1 parent 13f8b9f6aa
commit 6347ab0db1
5 files changed
+84 -12

No files matched your search

+8 -1
View File
@@ -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;
+3 -1
View File
@@ -2110,7 +2110,9 @@ export class NostrClient {
}
}
const adapter = await createNip07Adapter(extension);
const adapter = await createNip07Adapter(extension, {
preloadedPubkey: pubkey,
});
adapter.pubkey = pubkey;
setActiveSigner(adapter);
+11 -5
View File
@@ -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,
},
),
);
+11 -5
View File
@@ -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",
);
+51
View File
@@ -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);