Merge pull request #1987 from PR0M3TH3AN/load-relays-before-fetching-profile-7456935442893055659

Load user relays before fetching profile and feed
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-30 11:41:33 -05:00
commit cd6e8e9f27
3 files changed
+123 -240

No files matched your search

+15 -168
View File
@@ -4080,10 +4080,6 @@ class Application {
const urlParams = new URLSearchParams(window.location.search);
const hasVideoParam = urlParams.has("v");
if (!normalizedView || normalizedView === "most-recent-videos") {
setHashView("for-you", { preserveVideoParam: hasVideoParam });
}
const rawProviderId =
typeof detail?.providerId === "string" ? detail.providerId.trim() : "";
const rawAuthType =
@@ -4100,75 +4096,6 @@ class Application {
};
const activePubkey = detail?.pubkey || this.pubkey;
Promise.resolve()
.then(() =>
this.handleModerationSettingsChange({
settings: getModerationSettings(),
skipRefresh: true,
}),
)
.catch((error) => {
devLogger.warn(
"Failed to sync moderation settings after login:",
error,
);
});
if (loginContext.identityChanged) {
this.resetHashtagPreferencesState();
try {
this.watchHistoryTelemetry?.resetPlaybackLoggingState?.();
this.watchHistoryTelemetry?.refreshPreferenceSettings?.();
} catch (error) {
devLogger.warn(
"Failed to refresh watch history telemetry after identity change:",
error,
);
}
}
const hashtagPreferencesPromise = Promise.resolve(
this.loadHashtagPreferencesForPubkey(loginContext.pubkey, {
allowPermissionPrompt: false,
}),
).catch((error) => {
devLogger.warn(
"[Application] Background hashtag preferences load failed:",
error
);
});
if (this.zapController) {
try {
this.zapController.setVisibility(
Boolean(this.currentVideo?.lightningAddress),
);
} catch (error) {
devLogger.warn("[Application] Failed to refresh zap visibility after login:", error);
}
}
const shouldReopenZap = this.pendingModalZapOpen;
this.pendingModalZapOpen = false;
if (shouldReopenZap) {
const hasLightning = Boolean(this.currentVideo?.lightningAddress);
if (hasLightning && this.videoModal?.openZapDialog) {
Promise.resolve()
.then(() => this.videoModal.openZapDialog())
.then((opened) => {
if (opened) {
this.zapController?.open();
}
})
.catch((error) => {
devLogger.warn(
"[Application] Failed to reopen zap dialog after login:",
error,
);
});
}
}
const initialLoadingState = {
profile: activePubkey ? "loading" : "idle",
lists: activePubkey ? "loading" : "idle",
@@ -4184,28 +4111,6 @@ class Application {
authLoadingState: this.authLoadingState,
});
const accessControlReadyPromise =
accessControl && typeof accessControl.ensureReady === "function"
? Promise.resolve()
.then(() => accessControl.ensureReady())
.catch((error) => {
userLogger.error(
"[Application] Failed to refresh admin lists after login:",
error,
);
throw error;
})
: null;
const scheduleAfterFirstRender = () =>
new Promise((resolve) => {
if (typeof requestAnimationFrame === "function") {
requestAnimationFrame(() => requestAnimationFrame(resolve));
} else {
setTimeout(resolve, 0);
}
});
const cachedProfile =
detail?.postLogin && typeof detail.postLogin === "object"
? detail.postLogin.profile || null
@@ -4221,78 +4126,6 @@ class Application {
}
}
const startDmHydration = () => {
const dmTasks = [];
if (activePubkey) {
dmTasks.push(
Promise.resolve()
.then(() =>
this.nostrService.loadDirectMessages({
actorPubkey: activePubkey,
limit: 50,
decryptLimit: 50,
initialLoad: true,
}),
)
.catch((error) => {
devLogger.warn(
"[Application] Failed to sync direct messages during login:",
error,
);
throw error;
}),
);
if (
typeof this.nostrService.ensureDirectMessageSubscription === "function"
) {
dmTasks.push(
Promise.resolve()
.then(() =>
this.nostrService.ensureDirectMessageSubscription({
actorPubkey: activePubkey,
}),
)
.catch((error) => {
devLogger.warn(
"[Application] Failed to subscribe to direct messages during login:",
error,
);
throw error;
}),
);
}
}
return dmTasks.length
? Promise.allSettled(dmTasks).then((results) => {
const success = results.every(
(result) => result.status === "fulfilled",
);
this.updateAuthLoadingState({
dms: success ? "ready" : "error",
});
return success;
})
: Promise.resolve(this.updateAuthLoadingState({ dms: "idle" }));
};
const dmStatePromise = Promise.resolve()
.then(() => scheduleAfterFirstRender())
.then(() => startDmHydration())
.catch((error) => {
devLogger.warn("[Application] Delayed DM hydration failed:", error);
this.updateAuthLoadingState({ dms: "error" });
return false;
});
const nwcPromise = Promise.resolve()
.then(() => this.nwcSettingsService.onLogin(loginContext))
.catch((error) => {
devLogger.error("Failed to process NWC settings during login:", error);
return null;
});
if (this.profileController) {
try {
const maybePromise = this.profileController.handleAuthLogin(detail);
@@ -4314,8 +4147,22 @@ class Application {
this.renderSavedProfiles();
}
const accessControlReadyPromise =
accessControl && typeof accessControl.ensureReady === "function"
? Promise.resolve()
.then(() => accessControl.ensureReady())
.catch((error) => {
userLogger.error(
"[Application] Failed to refresh admin lists after login:",
error,
);
throw error;
})
: null;
const profileStatePromise = Promise.resolve(postLoginPromise)
.then((postLogin) => {
.then(async (postLogin) => {
// 1. Relays and Profile are now loaded (sequentially or efficiently by authService)
const nextProfile = postLogin?.profile || cachedProfile;
if (activePubkey && nextProfile) {
this.updateActiveProfileUI(activePubkey, nextProfile);
+68 -64
View File
@@ -697,79 +697,83 @@ export default class AuthService {
return { detail, completionPromise: Promise.resolve(detail) };
}
const runOperation = async (operation) => {
try {
const value = await operation.promise;
const resolved = operation.onFulfilled(value);
detail[operation.name] = resolved;
if (typeof operation.emit === "function") {
operation.emit(resolved, null);
}
return resolved;
} catch (error) {
const resolved = operation.onRejected(error);
detail[operation.name] = resolved;
if (typeof operation.emit === "function") {
operation.emit(resolved, error);
}
return resolved;
}
};
const schedule = (callback) => Promise.resolve().then(() => callback());
const operations = [];
if (!deferBlocks) {
const blocksOperation = this.createBlocksLoadOperation(activePubkey, schedule);
if (blocksOperation) {
operations.push(blocksOperation);
const completionPromise = (async () => {
if (this.relayManager && typeof this.relayManager.loadRelayList === "function") {
const relayOp = {
name: "relaysLoaded",
promise: schedule(() => this.relayManager.loadRelayList(activePubkey)),
onFulfilled: () => true,
onRejected: (error) => {
this.log("[AuthService] Failed to load relay list", error);
return false;
},
emit: (value, error) => {
this.emit("relaysLoaded", {
pubkey: activePubkey,
relaysLoaded: value,
error: error ?? null,
});
},
};
await runOperation(relayOp);
}
}
if (this.relayManager && typeof this.relayManager.loadRelayList === "function") {
operations.push({
name: "relaysLoaded",
promise: schedule(() => this.relayManager.loadRelayList(activePubkey)),
onFulfilled: () => true,
onRejected: (error) => {
this.log("[AuthService] Failed to load relay list", error);
return false;
},
emit: (value, error) => {
this.emit("relaysLoaded", {
pubkey: activePubkey,
relaysLoaded: value,
error: error ?? null,
const concurrentOps = [];
if (!deferBlocks) {
const blocksOperation = this.createBlocksLoadOperation(activePubkey, schedule);
if (blocksOperation) {
concurrentOps.push(blocksOperation);
}
}
concurrentOps.push({
name: "profile",
promise: schedule(() => this.loadOwnProfile(activePubkey)),
onFulfilled: (value) => {
const profile = value || FALLBACK_PROFILE;
this.setProfileCacheEntry(activePubkey, profile, {
persist: true,
reason: "post-login",
});
return profile;
},
onRejected: (error) => {
this.log("[AuthService] Failed to load own profile", error);
const fallbackProfile = cachedProfile || FALLBACK_PROFILE;
this.setProfileCacheEntry(activePubkey, fallbackProfile, {
persist: true,
reason: "post-login-fallback",
});
return fallbackProfile;
},
});
}
operations.push({
name: "profile",
promise: schedule(() => this.loadOwnProfile(activePubkey)),
onFulfilled: (value) => {
const profile = value || FALLBACK_PROFILE;
this.setProfileCacheEntry(activePubkey, profile, {
persist: true,
reason: "post-login",
});
return profile;
},
onRejected: (error) => {
this.log("[AuthService] Failed to load own profile", error);
const fallbackProfile = cachedProfile || FALLBACK_PROFILE;
this.setProfileCacheEntry(activePubkey, fallbackProfile, {
persist: true,
reason: "post-login-fallback",
});
return fallbackProfile;
},
});
await Promise.allSettled(concurrentOps.map(runOperation));
const wrappedPromises = operations.map((operation) =>
operation.promise
.then((value) => {
const resolved = operation.onFulfilled(value);
detail[operation.name] = resolved;
if (typeof operation.emit === "function") {
operation.emit(resolved, null);
}
return resolved;
})
.catch((error) => {
const resolved = operation.onRejected(error);
detail[operation.name] = resolved;
if (typeof operation.emit === "function") {
operation.emit(resolved, error);
}
return resolved;
}),
);
const completionPromise = Promise.allSettled(wrappedPromises).then(() => detail);
return detail;
})();
return { detail, completionPromise };
}
+40 -8
View File
@@ -13,6 +13,7 @@ import {
setActiveProfilePubkey,
getProfileCacheMap,
} from "../js/state/cache.js";
import { profileCache } from "../js/state/profileCache.js";
if (typeof globalThis.window === "undefined") {
globalThis.window = {};
@@ -61,6 +62,12 @@ function resetState() {
if (cache && typeof cache.clear === "function") {
cache.clear();
}
if (profileCache && typeof profileCache.reset === "function") {
profileCache.reset();
}
if (typeof localStorage !== "undefined") {
localStorage.clear();
}
}
await (async () => {
@@ -94,13 +101,15 @@ await (async () => {
const promise = service.applyPostLoginState();
await Promise.resolve();
await new Promise((resolve) => setTimeout(resolve, 10));
assert.deepEqual(calls, ["blocks:start", "relays:start", "profile:start"]);
assert.deepEqual(calls, ["relays:start", "blocks:start", "profile:start"]);
resolveBlocks();
const detail = await promise;
const result = await promise;
const detail = result.detail;
await result.completionPromise;
assert.equal(detail.pubkey, SAMPLE_PUBKEY);
assert.equal(detail.blocksLoaded, true);
@@ -130,12 +139,22 @@ await (async () => {
service.loadOwnProfile = () => Promise.reject(new Error("profile failed"));
const detail = await service.applyPostLoginState();
const result = service.applyPostLoginState();
const detail = result.detail;
await result.completionPromise;
assert.equal(detail.pubkey, SAMPLE_PUBKEY);
assert.equal(detail.blocksLoaded, false);
assert.equal(detail.relaysLoaded, false);
assert.equal(detail.profile, null);
assert.deepEqual(detail.profile, {
name: "Unknown",
picture: "assets/svg/default-profile.svg",
about: "",
website: "",
banner: "",
lud16: "",
lud06: "",
});
assert.equal(logs.length, 3);
assert(logs.some(({ message }) => message.includes("block list")));
@@ -147,13 +166,23 @@ await (async () => {
resetState();
const service = new AuthService();
const detail = await service.applyPostLoginState();
const result = service.applyPostLoginState();
const detail = result.detail;
await result.completionPromise;
assert.deepEqual(detail, {
pubkey: null,
blocksLoaded: false,
relaysLoaded: false,
profile: null,
profile: {
name: "Unknown",
picture: "assets/svg/default-profile.svg",
about: "",
website: "",
banner: "",
lud16: "",
lud06: "",
},
});
})();
@@ -168,7 +197,10 @@ await (async () => {
profile: { name: "Stub" },
};
service.applyPostLoginState = async () => postLogin;
service.applyPostLoginState = () => ({
detail: postLogin,
completionPromise: Promise.resolve(postLogin),
});
const detail = await service.login(SAMPLE_PUBKEY);