mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-04 20:02:26 +00:00
Merge pull request #2139 from PR0M3TH3AN/claude/nostr-relay-sync-login-eezJv
Optimize login flow: parallelize list loading with relay readiness
This commit is contained in:
3 files changed
+88
-38
No files matched your search
@@ -4135,6 +4135,14 @@ class Application {
|
||||
: Promise.resolve(detail?.postLogin ?? null);
|
||||
const postLoginResult = detail?.postLogin ?? null;
|
||||
|
||||
// relaysReadyPromise resolves as soon as the user's relay list is loaded,
|
||||
// before profile fetching completes. Lists can start loading immediately
|
||||
// after relays are available — they don't depend on profile data.
|
||||
const relaysReadyPromise =
|
||||
detail && typeof detail.relaysReadyPromise?.then === "function"
|
||||
? detail.relaysReadyPromise
|
||||
: postLoginPromise;
|
||||
|
||||
if (detail && typeof detail === "object") {
|
||||
try {
|
||||
detail.__handled = true;
|
||||
@@ -4270,8 +4278,10 @@ class Application {
|
||||
return null;
|
||||
});
|
||||
|
||||
// Chain list loading to profile state to ensure relays are loaded first.
|
||||
const listStatePromise = profileStatePromise.then(() => {
|
||||
// Start list loading as soon as relays are ready — lists don't depend on
|
||||
// profile data, only on relay URLs. This runs in parallel with profile
|
||||
// fetching instead of sequentially after it.
|
||||
const listStatePromise = relaysReadyPromise.then(() => {
|
||||
const tasks = [];
|
||||
if (activePubkey && typeof this.authService.loadBlocksForPubkey === "function") {
|
||||
tasks.push(
|
||||
@@ -4328,6 +4338,7 @@ class Application {
|
||||
});
|
||||
});
|
||||
|
||||
// DMs can wait for profile since they need encryption context.
|
||||
const dmStatePromise = profileStatePromise.then(() => {
|
||||
if (
|
||||
activePubkey &&
|
||||
@@ -4373,7 +4384,8 @@ class Application {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const hashtagPreferencesPromise = profileStatePromise.then(() => {
|
||||
// Hashtag preferences drive feed filtering — start as soon as relays are ready.
|
||||
const hashtagPreferencesPromise = relaysReadyPromise.then(() => {
|
||||
if (
|
||||
activePubkey &&
|
||||
this.hashtagPreferences &&
|
||||
@@ -4387,6 +4399,9 @@ class Application {
|
||||
this.capturePermissionPromptFromError(
|
||||
this.hashtagPreferences?.lastLoadError,
|
||||
);
|
||||
// Ensure the cached snapshot is updated before the feed renders
|
||||
// so the feed engine has access to interests/disinterests.
|
||||
this.updateCachedHashtagPreferences();
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.warn(
|
||||
@@ -4399,13 +4414,9 @@ class Application {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
void Promise.allSettled([
|
||||
profileStatePromise,
|
||||
listStatePromise,
|
||||
dmStatePromise,
|
||||
]);
|
||||
// Ensure DMs are tracked for cleanup.
|
||||
void dmStatePromise;
|
||||
void nwcPromise;
|
||||
void hashtagPreferencesPromise;
|
||||
|
||||
if (activePubkey) {
|
||||
const seedBlacklist = () => {
|
||||
@@ -4438,20 +4449,37 @@ class Application {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Subscriptions are loaded in list tasks to align with per-category loading states.
|
||||
}
|
||||
|
||||
// Show loading state immediately while lists are syncing.
|
||||
try {
|
||||
this.reinitializeVideoListView({ reason: "login", postLoginResult });
|
||||
} catch (error) {
|
||||
devLogger.warn("Failed to reinitialize video list view after login:", error);
|
||||
}
|
||||
|
||||
this.lastIdentityRefreshPromise = this.refreshAllVideoGrids({
|
||||
reason: "auth-login",
|
||||
forceMainReload: true,
|
||||
});
|
||||
// Wait for the critical feed-filtering lists (blocks, subscriptions,
|
||||
// hashtag preferences) to settle before rendering the video grid. This
|
||||
// prevents the feed from briefly showing unfiltered content. We use a
|
||||
// time-boxed wait so the UI isn't blocked indefinitely if decryption stalls.
|
||||
const FEED_SYNC_TIMEOUT_MS = 8000;
|
||||
const feedSyncPromise = Promise.race([
|
||||
Promise.allSettled([
|
||||
listStatePromise,
|
||||
hashtagPreferencesPromise,
|
||||
profileStatePromise,
|
||||
]),
|
||||
new Promise((resolve) => setTimeout(resolve, FEED_SYNC_TIMEOUT_MS)),
|
||||
]).catch(() => null);
|
||||
|
||||
// Chain the grid refresh after the feed data is available.
|
||||
this.lastIdentityRefreshPromise = feedSyncPromise
|
||||
.then(() =>
|
||||
this.refreshAllVideoGrids({
|
||||
reason: "auth-login",
|
||||
forceMainReload: true,
|
||||
}),
|
||||
);
|
||||
this.lastIdentityRefreshPromise
|
||||
.catch((error) => {
|
||||
devLogger.error("Failed to refresh video grids after login:", error);
|
||||
|
||||
@@ -623,10 +623,11 @@ export default class AuthService {
|
||||
postLoginError: null,
|
||||
};
|
||||
|
||||
const { detail: postLoginDetail, completionPromise } = this.applyPostLoginState({
|
||||
const { detail: postLoginDetail, completionPromise, relaysReadyPromise } = this.applyPostLoginState({
|
||||
deferBlocks: true,
|
||||
});
|
||||
detail.postLogin = postLoginDetail;
|
||||
detail.relaysReadyPromise = relaysReadyPromise;
|
||||
|
||||
const postLoginPromise = Promise.resolve(completionPromise)
|
||||
.then((postLogin) => {
|
||||
@@ -694,7 +695,8 @@ export default class AuthService {
|
||||
};
|
||||
|
||||
if (!activePubkey) {
|
||||
return { detail, completionPromise: Promise.resolve(detail) };
|
||||
const resolved = Promise.resolve(detail);
|
||||
return { detail, completionPromise: resolved, relaysReadyPromise: resolved };
|
||||
}
|
||||
|
||||
const runOperation = async (operation) => {
|
||||
@@ -718,6 +720,13 @@ export default class AuthService {
|
||||
|
||||
const schedule = (callback) => Promise.resolve().then(() => callback());
|
||||
|
||||
// Expose relay readiness separately so list loading can start immediately
|
||||
// after relays are available, without waiting for profile to complete.
|
||||
let resolveRelaysReady;
|
||||
const relaysReadyPromise = new Promise((resolve) => {
|
||||
resolveRelaysReady = resolve;
|
||||
});
|
||||
|
||||
const completionPromise = (async () => {
|
||||
if (this.relayManager && typeof this.relayManager.loadRelayList === "function") {
|
||||
const relayOp = {
|
||||
@@ -738,6 +747,7 @@ export default class AuthService {
|
||||
};
|
||||
await runOperation(relayOp);
|
||||
}
|
||||
resolveRelaysReady(detail);
|
||||
|
||||
const concurrentOps = [];
|
||||
|
||||
@@ -775,7 +785,7 @@ export default class AuthService {
|
||||
return detail;
|
||||
})();
|
||||
|
||||
return { detail, completionPromise };
|
||||
return { detail, completionPromise, relaysReadyPromise };
|
||||
}
|
||||
|
||||
createBlocksLoadOperation(
|
||||
|
||||
+32
-20
@@ -502,14 +502,19 @@ class SubscriptionsManager {
|
||||
);
|
||||
const hadCachedSnapshot = cachedSnapshot.hasSnapshot;
|
||||
const shouldForceFullFetch = !cachedSnapshot.hasSnapshot;
|
||||
let events = await nostrClient.fetchListIncrementally({
|
||||
kind: SUBSCRIPTION_SET_KIND,
|
||||
pubkey: normalizedUserPubkey,
|
||||
dTag: SUBSCRIPTION_LIST_IDENTIFIER,
|
||||
relayUrls,
|
||||
since: shouldForceFullFetch ? 0 : (cachedSnapshot.createdAt || 0),
|
||||
timeoutMs: 12000,
|
||||
});
|
||||
|
||||
// Fetch user and session actor subscription lists in parallel to reduce
|
||||
// total relay wait time during login.
|
||||
const fetchPromises = [
|
||||
nostrClient.fetchListIncrementally({
|
||||
kind: SUBSCRIPTION_SET_KIND,
|
||||
pubkey: normalizedUserPubkey,
|
||||
dTag: SUBSCRIPTION_LIST_IDENTIFIER,
|
||||
relayUrls,
|
||||
since: shouldForceFullFetch ? 0 : (cachedSnapshot.createdAt || 0),
|
||||
timeoutMs: 12000,
|
||||
}),
|
||||
];
|
||||
|
||||
const normalizedSessionActorPubkey = normalizeNostrPubkey(
|
||||
nostrClient?.sessionActor?.pubkey,
|
||||
@@ -525,17 +530,22 @@ class SubscriptionsManager {
|
||||
),
|
||||
);
|
||||
const shouldForceSessionFetch = !sessionCachedSnapshot.hasSnapshot;
|
||||
const sessionEvents = await nostrClient.fetchListIncrementally({
|
||||
kind: SUBSCRIPTION_SET_KIND,
|
||||
pubkey: normalizedSessionActorPubkey,
|
||||
dTag: SUBSCRIPTION_LIST_IDENTIFIER,
|
||||
relayUrls,
|
||||
since: shouldForceSessionFetch ? 0 : (sessionCachedSnapshot.createdAt || 0),
|
||||
timeoutMs: 12000,
|
||||
});
|
||||
if (sessionEvents.length) {
|
||||
events = events.concat(sessionEvents);
|
||||
}
|
||||
fetchPromises.push(
|
||||
nostrClient.fetchListIncrementally({
|
||||
kind: SUBSCRIPTION_SET_KIND,
|
||||
pubkey: normalizedSessionActorPubkey,
|
||||
dTag: SUBSCRIPTION_LIST_IDENTIFIER,
|
||||
relayUrls,
|
||||
since: shouldForceSessionFetch ? 0 : (sessionCachedSnapshot.createdAt || 0),
|
||||
timeoutMs: 12000,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const fetchResults = await Promise.all(fetchPromises);
|
||||
let events = fetchResults[0] || [];
|
||||
if (shouldFetchSessionActor && fetchResults[1]?.length) {
|
||||
events = events.concat(fetchResults[1]);
|
||||
}
|
||||
|
||||
const mergedEvents = [];
|
||||
@@ -809,7 +819,9 @@ class SubscriptionsManager {
|
||||
|
||||
try {
|
||||
// Race against a timeout so the UI doesn't hang indefinitely if relays stall.
|
||||
const timeoutMs = 6000;
|
||||
// The timeout must accommodate relay fetch (~12s) + decryption (~3-12s per
|
||||
// scheme attempt), so we use 30s to give the full pipeline enough room.
|
||||
const timeoutMs = 30000;
|
||||
await Promise.race([
|
||||
loader,
|
||||
new Promise((_, reject) =>
|
||||
|
||||
Reference in new issue
Block a user