Tier login hydration and trim DM fetch

This commit is contained in:
thePR0M3TH3AN committed 2026-01-29 15:43:34 -05:00
1 parent 83efdfcec7
commit aad2fe465a
4 files changed
+174 -86

No files matched your search

+91 -65
View File
@@ -2698,7 +2698,7 @@ class Application {
}
const messageContext =
reason === "login" && postLoginResult?.blocksLoaded !== false
reason === "login" && postLoginResult?.blocksLoaded === true
? "Applying your filters…"
: "Refreshing videos…";
@@ -4285,56 +4285,96 @@ class Application {
authLoadingState: this.authLoadingState,
});
const dmTasks = [];
if (activePubkey) {
dmTasks.push(
Promise.resolve()
.then(() =>
this.nostrService.loadDirectMessages({
actorPubkey: activePubkey,
limit: 50,
initialLoad: true,
}),
)
.catch((error) => {
devLogger.warn(
"[Application] Failed to sync direct messages during login:",
error,
);
throw error;
}),
);
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;
if (typeof this.nostrService.ensureDirectMessageSubscription === "function") {
const scheduleAfterFirstRender = () =>
new Promise((resolve) => {
if (typeof requestAnimationFrame === "function") {
requestAnimationFrame(() => requestAnimationFrame(resolve));
} else {
setTimeout(resolve, 0);
}
});
const tier1ReadyPromise = Promise.allSettled(
[postLoginPromise, accessControlReadyPromise].filter(Boolean),
);
const startDmHydration = () => {
const dmTasks = [];
if (activePubkey) {
dmTasks.push(
Promise.resolve()
.then(() =>
this.nostrService.ensureDirectMessageSubscription({
this.nostrService.loadDirectMessages({
actorPubkey: activePubkey,
limit: 50,
decryptLimit: 50,
initialLoad: true,
}),
)
.catch((error) => {
devLogger.warn(
"[Application] Failed to subscribe to direct messages during login:",
"[Application] Failed to sync direct messages during login:",
error,
);
throw error;
}),
);
}
}
const dmStatePromise = dmTasks.length
? Promise.allSettled(dmTasks).then((results) => {
const success = results.every(
(result) => result.status === "fulfilled",
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;
}),
);
this.updateAuthLoadingState({
dms: success ? "ready" : "error",
});
return success;
})
: Promise.resolve(this.updateAuthLoadingState({ dms: "idle" }));
}
}
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 = tier1ReadyPromise
.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))
@@ -4382,46 +4422,32 @@ class Application {
});
const listTasks = [];
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;
if (activePubkey) {
if (activePubkey && typeof this.authService.loadBlocksForPubkey === "function") {
listTasks.push(
postLoginPromise.then((postLogin) => {
const blocksLoaded = postLogin?.blocksLoaded !== false;
const relaysLoaded = postLogin?.relaysLoaded !== false;
if (!blocksLoaded || !relaysLoaded) {
const listError = new Error("Post-login list hydration incomplete.");
listError.blocksLoaded = blocksLoaded;
listError.relaysLoaded = relaysLoaded;
throw listError;
}
return { blocksLoaded, relaysLoaded };
}),
tier1ReadyPromise
.then(() => this.authService.loadBlocksForPubkey(activePubkey))
.catch((error) => {
devLogger.warn(
"[Application] Failed to load blocks during login:",
error,
);
throw error;
}),
);
}
if (accessControlReadyPromise) {
listTasks.push(accessControlReadyPromise);
}
if (
activePubkey &&
subscriptions &&
typeof subscriptions.ensureLoaded === "function"
) {
listTasks.push(
subscriptions
.ensureLoaded(activePubkey, { allowPermissionPrompt: false })
tier1ReadyPromise
.then(() =>
subscriptions.ensureLoaded(activePubkey, {
allowPermissionPrompt: false,
}),
)
.catch((error) => {
devLogger.warn(
"[Application] Failed to load subscriptions during login:",
+57 -20
View File
@@ -623,7 +623,9 @@ export default class AuthService {
postLoginError: null,
};
const { detail: postLoginDetail, completionPromise } = this.applyPostLoginState();
const { detail: postLoginDetail, completionPromise } = this.applyPostLoginState({
deferBlocks: true,
});
detail.postLogin = postLoginDetail;
const postLoginPromise = Promise.resolve(completionPromise)
@@ -680,13 +682,13 @@ export default class AuthService {
return detail;
}
applyPostLoginState() {
applyPostLoginState({ deferBlocks = false } = {}) {
const activePubkey = this.normalizeHexPubkey(getPubkey()) || getPubkey();
const cachedEntry = activePubkey ? this.getProfileCacheEntry(activePubkey) : null;
const cachedProfile = cachedEntry?.profile || FALLBACK_PROFILE;
const detail = {
pubkey: activePubkey || null,
blocksLoaded: false,
blocksLoaded: deferBlocks ? null : false,
relaysLoaded: false,
profile: cachedProfile,
};
@@ -699,23 +701,11 @@ export default class AuthService {
const operations = [];
if (this.userBlocks && typeof this.userBlocks.loadBlocks === "function") {
operations.push({
name: "blocksLoaded",
promise: schedule(() => this.userBlocks.loadBlocks(activePubkey)),
onFulfilled: () => true,
onRejected: (error) => {
this.log("[AuthService] Failed to load block list", error);
return false;
},
emit: (value, error) => {
this.emit("blocksLoaded", {
pubkey: activePubkey,
blocksLoaded: value,
error: error ?? null,
});
},
});
if (!deferBlocks) {
const blocksOperation = this.createBlocksLoadOperation(activePubkey, schedule);
if (blocksOperation) {
operations.push(blocksOperation);
}
}
if (this.relayManager && typeof this.relayManager.loadRelayList === "function") {
@@ -784,6 +774,53 @@ export default class AuthService {
return { detail, completionPromise };
}
createBlocksLoadOperation(activePubkey, schedule = (callback) => Promise.resolve().then(() => callback())) {
if (!this.userBlocks || typeof this.userBlocks.loadBlocks !== "function") {
return null;
}
return {
name: "blocksLoaded",
promise: schedule(() => this.userBlocks.loadBlocks(activePubkey)),
onFulfilled: () => true,
onRejected: (error) => {
this.log("[AuthService] Failed to load block list", error);
return false;
},
emit: (value, error) => {
this.emit("blocksLoaded", {
pubkey: activePubkey,
blocksLoaded: value,
error: error ?? null,
});
},
};
}
async loadBlocksForPubkey(pubkey) {
const normalized = this.normalizeHexPubkey(pubkey) ||
(typeof pubkey === "string" ? pubkey.trim() : "");
if (!normalized) {
return false;
}
const operation = this.createBlocksLoadOperation(normalized);
if (!operation) {
return false;
}
try {
const value = await operation.promise;
const resolved = operation.onFulfilled(value);
operation.emit?.(resolved, null);
return resolved;
} catch (error) {
const resolved = operation.onRejected(error);
operation.emit?.(resolved, error);
return resolved;
}
}
async logout() {
if (this.nostrClient && typeof this.nostrClient.logout === "function") {
try {
+25
View File
@@ -982,6 +982,8 @@ export class NostrService {
: Number.isFinite(decryptLimitCandidate) && decryptLimitCandidate > 0
? Math.floor(decryptLimitCandidate)
: null;
const sinceCandidate = Number(options.since);
const untilCandidate = Number(options.until);
if (resolvedDecryptLimit && resolvedDecryptLimit > resolvedLimit) {
resolvedDecryptLimit = resolvedLimit;
@@ -1006,6 +1008,27 @@ export class NostrService {
this.dmActorPubkey = normalizedActor;
let resolvedSince =
Number.isFinite(sinceCandidate) && sinceCandidate >= 0
? Math.floor(sinceCandidate)
: null;
const resolvedUntil =
Number.isFinite(untilCandidate) && untilCandidate >= 0
? Math.floor(untilCandidate)
: null;
if (resolvedSince === null && initialLoad) {
let latestTimestamp = 0;
for (const message of Array.isArray(this.dmMessages) ? this.dmMessages : []) {
const timestamp = Number(message?.timestamp);
if (Number.isFinite(timestamp) && timestamp > latestTimestamp) {
latestTimestamp = timestamp;
}
}
if (latestTimestamp > 0) {
resolvedSince = Math.max(latestTimestamp - 60, 0);
}
}
let messages = [];
try {
messages = await this.nostrClient.listDirectMessages(normalizedActor, {
@@ -1013,6 +1036,8 @@ export class NostrService {
...options,
limit: resolvedLimit,
decryptLimit: resolvedDecryptLimit,
...(resolvedSince !== null ? { since: resolvedSince } : {}),
...(resolvedUntil !== null ? { until: resolvedUntil } : {}),
onMessage: (message) => {
this.applyDirectMessage(message, { reason: "load-incremental" });
},
+1 -1
View File
@@ -79,7 +79,7 @@ export default class VideoListViewController {
}
const messageContext =
reason === "login" && postLoginResult?.blocksLoaded !== false
reason === "login" && postLoginResult?.blocksLoaded === true
? "Applying your filters…"
: "Refreshing videos…";