mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 01:12:39 +00:00
perf(login): stop eagerly loading DM history at login
Real-env console (cold nip-07 login) showed list decryption failing en
masse: blocks/hashtags/subscriptions fetched fine (relay cap working —
every sub now fans out to exactly 8 relays) but decryption timed out for
minutes with "message channel closed", only recovering on a page refresh.
Root cause: the single-threaded nip-07 extension channel is overwhelmed
during the post-login handshake. One contributor is the eager DM load —
loadDirectMessages({ limit: 50, initialLoad: true }) ran at login and
fired a burst of decrypts that competed with the feed-driving lists
(blocks/subscriptions/hashtags) for the same channel. DMs are lower
priority (the user isn't viewing Messages at login) and already load
lazily when the Messages tab opens.
Defer the eager DM load; report dms state as "deferred". The lightweight
unread-indicator refresh still runs so the badge stays accurate. First
increment of the "cut the login burst" work (KNOWN_BUGS #0).
Test: tests/app/login-defers-dms.test.mjs (cheat-resistant — asserts
loadDirectMessages is never called at login, dms state is "deferred", and
the unread indicator still runs).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
55bd181867
commit
71cd04b215
2 files changed
+155
-24
No files matched your search
@@ -618,31 +618,19 @@ export function createAuthSessionCoordinator(deps) {
|
||||
return listSyncDetail;
|
||||
});
|
||||
|
||||
// DMs can wait for profile since they need encryption context.
|
||||
// DMs are intentionally NOT eagerly loaded at login. A cold DM load
|
||||
// (limit: 50) is a burst of nip-07 decrypts that competes with the
|
||||
// feed-driving lists (blocks/subscriptions/hashtags) for the SAME
|
||||
// single-threaded extension channel during the fragile post-login
|
||||
// handshake window — under load that channel drops ("message channel
|
||||
// closed") and every subsequent decrypt times out until a page refresh.
|
||||
// DMs are lower priority (the user isn't viewing Messages at login) and
|
||||
// already load lazily when the Messages tab is opened
|
||||
// (ProfileDirectMessageActions.populateProfileMessages). The lightweight
|
||||
// unread indicator (refreshUnreadDmIndicator, above) still runs so the
|
||||
// badge is accurate without decrypting the full conversation history.
|
||||
const dmStatePromise = profileStatePromise.then(() => {
|
||||
if (
|
||||
activePubkey &&
|
||||
this.nostrService &&
|
||||
typeof this.nostrService.loadDirectMessages === "function"
|
||||
) {
|
||||
return this.nostrService
|
||||
.loadDirectMessages({
|
||||
actorPubkey: activePubkey,
|
||||
limit: 50,
|
||||
initialLoad: true,
|
||||
})
|
||||
.then(() => {
|
||||
this.updateAuthLoadingState({ dms: "ready" });
|
||||
})
|
||||
.catch((error) => {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to load direct messages during login:",
|
||||
error,
|
||||
);
|
||||
this.updateAuthLoadingState({ dms: "error" });
|
||||
});
|
||||
}
|
||||
this.updateAuthLoadingState({ dms: "idle" });
|
||||
this.updateAuthLoadingState({ dms: "deferred" });
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Scenario (SCN-login-defers-dms):
|
||||
// Given a user logs in via the auth session coordinator,
|
||||
// When the post-login hydration runs (profile + feed-driving lists settle),
|
||||
// Then the coordinator MUST NOT eagerly load the full direct-message history
|
||||
// (a cold limit:50 DM load is a burst of nip-07 decrypts that competes with
|
||||
// blocks/subscriptions/hashtags for the single-threaded extension channel
|
||||
// during the fragile handshake window — see KNOWN_BUGS #0). DMs load lazily
|
||||
// when the Messages tab is opened instead.
|
||||
// And the DM loading state is reported as "deferred" (not "ready"/"loading"),
|
||||
// And the lightweight unread indicator still runs so the badge stays accurate.
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
// Minimal browser shim: handleAuthLogin reads window.location.search.
|
||||
if (typeof globalThis.window === "undefined") {
|
||||
globalThis.window = { location: { search: "", href: "http://localhost/" } };
|
||||
}
|
||||
|
||||
const { createAuthSessionCoordinator } = await import(
|
||||
"../../js/app/authSessionCoordinator.js"
|
||||
);
|
||||
|
||||
const ACTIVE_PUBKEY = "a".repeat(64);
|
||||
|
||||
function makeCoordinator({ loadDirectMessages, refreshUnreadDmIndicator }) {
|
||||
const noop = () => {};
|
||||
const asyncNoop = async () => {};
|
||||
const logger = { warn: noop, info: noop, log: noop, error: noop, debug: noop };
|
||||
|
||||
const coord = createAuthSessionCoordinator({
|
||||
devLogger: logger,
|
||||
userLogger: logger,
|
||||
nostrClient: {
|
||||
// Signer resolves so the readiness gate completes (non-extension type so
|
||||
// it doesn't attempt a real permission prompt).
|
||||
ensureActiveSignerForPubkey: async () => ({ type: "nsec", pubkey: ACTIVE_PUBKEY }),
|
||||
ensureExtensionPermissions: async () => ({ ok: true }),
|
||||
},
|
||||
accessControl: { ensureReady: asyncNoop, getBlacklist: () => [] },
|
||||
userBlocks: {
|
||||
getBlockedPubkeys: () => [],
|
||||
seedWithNpubs: asyncNoop,
|
||||
},
|
||||
subscriptions: { ensureLoaded: asyncNoop, lastLoadError: null },
|
||||
hashtagPreferences: { load: asyncNoop, lastLoadError: null },
|
||||
storageService: {},
|
||||
relayManager: {},
|
||||
torrentClient: {},
|
||||
getHashViewName: () => "",
|
||||
setHashView: noop,
|
||||
DEFAULT_NIP07_PERMISSION_METHODS: [],
|
||||
RELAY_UI_BATCH_DELAY_MS: 0,
|
||||
sanitizeRelayList: (x) => x,
|
||||
buildDmRelayListEvent: noop,
|
||||
publishEventToRelays: noop,
|
||||
assertAnyRelayAccepted: noop,
|
||||
queueSignEvent: noop,
|
||||
bootstrapTrustedSeeds: noop,
|
||||
getModerationSettings: () => ({}),
|
||||
getActiveProfilePubkey: () => null,
|
||||
});
|
||||
|
||||
// Collaborators the login flow touches on `this` — stub as inert so the flow
|
||||
// runs end to end without real UI/services.
|
||||
coord.pubkey = ACTIVE_PUBKEY;
|
||||
coord.profileController = null;
|
||||
coord.commentController = null;
|
||||
coord.nwcSettingsService = null;
|
||||
coord.authService = {
|
||||
loadBlocksForPubkey: async () => true,
|
||||
};
|
||||
coord.nostrService = {
|
||||
loadDirectMessages,
|
||||
listDirectMessageConversationSummaries: async () => [],
|
||||
};
|
||||
|
||||
const inert = [
|
||||
"applyAuthenticatedUiState",
|
||||
"capturePermissionPromptFromError",
|
||||
"dispatchAuthChange",
|
||||
"forceRefreshAllProfiles",
|
||||
"maybeShowExperimentalLoginWarning",
|
||||
"reinitializeVideoListView",
|
||||
"renderSavedProfiles",
|
||||
"refreshAllVideoGrids",
|
||||
"resetPermissionPromptState",
|
||||
"resetViewLoggingState",
|
||||
"updateActiveProfileUI",
|
||||
"updateCachedHashtagPreferences",
|
||||
"updateShareNostrAuthState",
|
||||
];
|
||||
for (const name of inert) {
|
||||
coord[name] = noop;
|
||||
}
|
||||
coord.normalizeHexPubkey = (x) => x;
|
||||
coord.refreshUnreadDmIndicator = refreshUnreadDmIndicator;
|
||||
coord.updateAuthLoadingState = (patch) => {
|
||||
coord.authLoadingState = { ...(coord.authLoadingState || {}), ...patch };
|
||||
};
|
||||
coord.authLoadingState = { profile: "idle", lists: "idle", dms: "idle" };
|
||||
|
||||
return coord;
|
||||
}
|
||||
|
||||
test("login does not eagerly load DM history; it is deferred", async () => {
|
||||
let loadDmCalls = 0;
|
||||
let unreadIndicatorCalls = 0;
|
||||
const coord = makeCoordinator({
|
||||
loadDirectMessages: async () => {
|
||||
loadDmCalls += 1;
|
||||
return { ok: true };
|
||||
},
|
||||
refreshUnreadDmIndicator: async () => {
|
||||
unreadIndicatorCalls += 1;
|
||||
},
|
||||
});
|
||||
|
||||
await coord.handleAuthLogin({
|
||||
pubkey: ACTIVE_PUBKEY,
|
||||
identityChanged: true,
|
||||
postLogin: { profile: { name: "tester" } },
|
||||
postLoginPromise: Promise.resolve({ profile: { name: "tester" } }),
|
||||
});
|
||||
|
||||
// Let every post-login promise chain (profile -> dm/nwc, feed sync) settle.
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
assert.equal(
|
||||
loadDmCalls,
|
||||
0,
|
||||
"login must NOT eagerly load DM history (it competes with feed lists for the nip-07 channel)",
|
||||
);
|
||||
assert.equal(
|
||||
coord.authLoadingState.dms,
|
||||
"deferred",
|
||||
"DM loading state should be reported as deferred at login",
|
||||
);
|
||||
assert.ok(
|
||||
unreadIndicatorCalls >= 1,
|
||||
"the lightweight unread DM indicator should still run so the badge stays accurate",
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user