feat: implement lazy login to eliminate startup network request

Replace eager login verification on startup with assumeLoggedIn(),
which sets login state from saved credentials without any network call.
Real verification is deferred until sync (which needs the actual user ID)
or when the settings page is opened.

- UserComponent: add verified flag, assumeLoggedIn(), isVerified()
- main.ts: replace onLayoutReady(login) with assumeLoggedIn(); fix
  inverted condition bug in checkLogin() (!needLogin -> needLogin)
- DoubanSyncModal: remove redundant login check before checkLogin()
- LoginSettingsHelper: handle assumed-but-unverified state in constructLoginUI()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
YuBai 2026-03-05 20:38:48 +08:00
parent 4e8d3f1318
commit 7ba1a7be0c
4 changed files with 41 additions and 19 deletions

@ -169,9 +169,6 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
const syncButton = new ButtonComponent(controls)
.setButtonText(i18nHelper.getMessage('110007'))
.onClick(async () => {
if (!this.plugin.userComponent.isLogin()) {
await this.plugin.userComponent.login();
}
if(!await this.plugin.checkLogin(this.context)) {
return;
}

@ -10,16 +10,16 @@ export function constructLoginUI(containerEl: HTMLElement, manager: SettingsMana
// containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') });
const userComponent = manager.plugin.userComponent;
if (userComponent.needLogin()) {
try {
if (userComponent.isLogin() && !userComponent.isVerified()) {
// Assumed login — verify to get user ID/name for settings display
userComponent.login()
.then(() => {
constructDoubanLoginSettingsUI(containerEl, manager);
});
}catch (e) {
log.debug(i18nHelper.getMessage('100101'));
constructDoubanLoginSettingsUI(containerEl, manager);
}
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
} else if (userComponent.needLogin()) {
// Has credentials but not yet logged in
userComponent.login()
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
} else {
constructDoubanLoginSettingsUI(containerEl, manager);
}

@ -15,6 +15,7 @@ import {DoubanHttpUtil} from "../../utils/DoubanHttpUtil";
export default class UserComponent {
private settingsManager: SettingsManager;
private user: User;
private verified: boolean = false;
constructor(settingsManager: SettingsManager) {
this.settingsManager = settingsManager;
@ -39,11 +40,26 @@ export default class UserComponent {
this.user.login = false;
}
this.user = null;
this.verified = false;
this.settingsManager.updateSetting('loginCookiesContent', '');
this.settingsManager.updateSetting('loginHeadersContent', '');
}
assumeLoggedIn(): void {
const headers: any = this.settingsManager.getSetting('loginHeadersContent');
const cookies: any = this.settingsManager.getSetting('loginCookiesContent');
if (headers || cookies) {
this.user = new User();
this.user.login = true;
this.verified = false;
}
}
isVerified(): boolean {
return this.verified;
}
needLogin() {
@ -68,6 +84,7 @@ export default class UserComponent {
this.settingsManager.debug(`配置界面:loginCookie:豆瓣headers信息正常${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
});
if(this.user) {
this.verified = true;
this.settingsManager.updateSetting('loginHeadersContent', JSON.stringify(headers));
}
return this.user;
@ -139,6 +156,9 @@ export default class UserComponent {
this.user = user;
this.settingsManager.debug(`主界面:loginByCookie:豆瓣cookies信息正常${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
});
if (this.user && this.user.id) {
this.verified = true;
}
return this.user;
}
}

@ -285,8 +285,8 @@ export default class DoubanPlugin extends Plugin {
this.settingsManager = new SettingsManager(this.app, this);
// this.fetchOnlineData(this.settingsManager);
this.userComponent = new UserComponent(this.settingsManager);
await this.userComponent.login();
this.netFileHandler = new NetFileHandler(this.fileHandler);
this.userComponent.assumeLoggedIn();
this.settingTab = new DoubanSettingTab(this.app, this);
this.addSettingTab(this.settingTab);
@ -356,11 +356,16 @@ export default class DoubanPlugin extends Plugin {
async checkLogin(context: HandleContext):Promise<boolean> {
this.settingsManager.debug('主界面:同步时的登录状态检测');
if (!context.userComponent.needLogin()) {
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 无用户信息, 尝试获取用户信息');
await context.userComponent.login();
const uc = context.userComponent;
// If assumed-logged-in but not verified, verify now (sync needs real user ID)
if (uc.isLogin() && !uc.isVerified()) {
await uc.login();
}
if (!context.userComponent.isLogin()) {
// If has saved credentials but not logged in, try login
if (uc.needLogin()) {
await uc.login();
}
if (!uc.isLogin()) {
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 尝试获取用户信息失败');
new Notice(i18nHelper.getMessage('140303'));
return false;