mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-08 13:52:58 +00:00
perf: speed up nostr login by parallelizing list sync and decryption
Login with NIP-07 extensions was extremely slow because list loading (blocks, subscriptions, hashtag preferences) happened sequentially and each list's decryption tried encryption schemes one at a time. Changes: - Load blocks, subscriptions, and hashtags in parallel instead of sequentially (blocks no longer blocks other lists from starting) - Use Promise.any() to try all decryption schemes simultaneously instead of trying nip44_v2 → nip44 → nip04 one-by-one - Deduplicate same-family schemes (nip44/nip44_v2) to avoid redundant extension calls - Increase NIP-07 request queue concurrency from 3 to 5 to support parallel decrypt operations during login - Raise no-prompt decrypt timeout from 3s/5s to 8s since extensions that have already granted permission still need time to decrypt - Increase feed sync timeout from 8s to 12s to give lists more time to settle before rendering unfiltered content https://claude.ai/code/session_01V6m1gTecsppPrBHQnUyPnF
This commit is contained in:
6 files changed
+160
-105
No files matched your search
+44
-25
@@ -999,10 +999,11 @@ class SubscriptionsManager {
|
||||
decryptors.set(scheme, handler);
|
||||
};
|
||||
|
||||
// Use a shorter per-call timeout during background/login loads (no
|
||||
// permission prompt) so stalled extension calls fail fast. Interactive
|
||||
// loads get more time because the user may be approving a popup.
|
||||
const nip07DecryptTimeoutMs = allowPermissionPrompt ? 15000 : 5000;
|
||||
// PERF: Previous 5s no-prompt timeout was too aggressive — extensions that
|
||||
// have already granted permission still need time to decrypt. 8s gives a
|
||||
// realistic window while still failing faster than the full 15s interactive
|
||||
// timeout.
|
||||
const nip07DecryptTimeoutMs = allowPermissionPrompt ? 15000 : 8000;
|
||||
const signerDecryptOptions = {
|
||||
priority: NIP07_PRIORITY.NORMAL,
|
||||
timeoutMs: nip07DecryptTimeoutMs,
|
||||
@@ -1107,37 +1108,55 @@ class SubscriptionsManager {
|
||||
availableSchemes,
|
||||
cachedScheme,
|
||||
);
|
||||
const attemptErrors = [];
|
||||
// PERF: Try all decryption schemes in parallel via Promise.any().
|
||||
// The first scheme to succeed wins, avoiding sequential 3-15s timeouts
|
||||
// per scheme that previously made worst-case decryption take 45s+.
|
||||
// Deduplicate schemes in the same family (nip44/nip44_v2 both call
|
||||
// the same underlying signer.nip44Decrypt) to avoid redundant calls.
|
||||
const getSchemeFamily = (s) =>
|
||||
s === "nip44" || s === "nip44_v2" ? "nip44" : s;
|
||||
const seenFamilies = new Set();
|
||||
const attempts = order
|
||||
.map((scheme) => {
|
||||
const family = getSchemeFamily(scheme);
|
||||
if (seenFamilies.has(family)) return null;
|
||||
seenFamilies.add(family);
|
||||
const decryptFn = decryptors.get(scheme);
|
||||
if (!decryptFn) return null;
|
||||
return decryptFn(ciphertext).then((plaintext) => {
|
||||
if (typeof plaintext !== "string") {
|
||||
throw new Error("Decryption returned a non-string payload.");
|
||||
}
|
||||
return { plaintext, scheme };
|
||||
});
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
for (const scheme of order) {
|
||||
const decryptFn = decryptors.get(scheme);
|
||||
if (!decryptFn) {
|
||||
continue;
|
||||
}
|
||||
if (attempts.length) {
|
||||
try {
|
||||
const plaintext = await decryptFn(ciphertext);
|
||||
if (typeof plaintext !== "string") {
|
||||
const error = new Error("Decryption returned a non-string payload.");
|
||||
error.code = "subscriptions-invalid-plaintext";
|
||||
attemptErrors.push({ scheme, error });
|
||||
continue;
|
||||
}
|
||||
const result = await Promise.any(attempts);
|
||||
decryptionSuccessCache.set(userPubkey, {
|
||||
scheme,
|
||||
scheme: result.scheme,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
return { ok: true, plaintext, scheme };
|
||||
} catch (error) {
|
||||
attemptErrors.push({ scheme, error });
|
||||
return { ok: true, plaintext: result.plaintext, scheme: result.scheme };
|
||||
} catch (aggregateError) {
|
||||
const attemptErrors = (aggregateError.errors || []).map((err, i) => ({
|
||||
scheme: order[i] || "unknown",
|
||||
error: err,
|
||||
}));
|
||||
const error = new Error("Failed to decrypt subscription list with available schemes.");
|
||||
error.code = "subscriptions-decrypt-failed";
|
||||
if (attemptErrors.length) {
|
||||
error.cause = attemptErrors;
|
||||
}
|
||||
return { ok: false, error, errors: attemptErrors };
|
||||
}
|
||||
}
|
||||
|
||||
const error = new Error("Failed to decrypt subscription list with available schemes.");
|
||||
error.code = "subscriptions-decrypt-failed";
|
||||
if (attemptErrors.length) {
|
||||
error.cause = attemptErrors;
|
||||
}
|
||||
return { ok: false, error, errors: attemptErrors };
|
||||
return { ok: false, error, errors: [] };
|
||||
}
|
||||
|
||||
async addChannel(channelHex, userPubkey) {
|
||||
|
||||
Reference in new issue
Block a user