fix: 阻止NapCat更新登录复用旧二维码
This commit is contained in:
parent
2eea7b254b
commit
4942cfd010
@ -30,6 +30,11 @@ import { QqbotAccountService } from '@/modules/qqbot/core/application/account/qq
|
||||
import { NapcatLoginStateStoreService } from '../../infrastructure/persistence/napcat-login-state-store.service';
|
||||
import { QqbotNapcatContainerService } from '../../infrastructure/integration/container/qqbot-napcat-container.service';
|
||||
|
||||
type PendingQrcodeUpdateOptions = {
|
||||
clearStaleQrcode?: boolean;
|
||||
requireFresh?: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class QqbotNapcatLoginService {
|
||||
private readonly fallbackLoginSessionStore =
|
||||
@ -155,7 +160,10 @@ export class QqbotNapcatLoginService {
|
||||
*/
|
||||
async startRefresh(accountId: string) {
|
||||
const activeSession = this.findActiveRefreshSession(accountId);
|
||||
if (activeSession) return this.toResult(activeSession);
|
||||
if (activeSession) {
|
||||
if (activeSession.qrcode) return this.refreshQrcode(activeSession.id);
|
||||
return this.toResult(activeSession);
|
||||
}
|
||||
|
||||
const runningTask = this.refreshStartTasks[accountId];
|
||||
if (runningTask) return runningTask;
|
||||
@ -413,6 +421,7 @@ export class QqbotNapcatLoginService {
|
||||
session.errorMessage = status.loginError || undefined;
|
||||
if (
|
||||
status.qrcodeurl &&
|
||||
session.mode !== 'refresh' &&
|
||||
!this.toolsService.isNapcatExpiredQrcodeStatus(status)
|
||||
) {
|
||||
const qrcodeChanged = session.qrcode !== status.qrcodeurl;
|
||||
@ -425,10 +434,13 @@ export class QqbotNapcatLoginService {
|
||||
'登录二维码已生成',
|
||||
);
|
||||
}
|
||||
} else if (status.isOffline) {
|
||||
} else if (status.isOffline && session.mode !== 'refresh') {
|
||||
session.qrcode = undefined;
|
||||
} else if (!this.toolsService.isNapcatExpiredQrcodeStatus(status)) {
|
||||
await this.tryUpdatePendingQrcode(container, session, status);
|
||||
await this.tryUpdatePendingQrcode(container, session, status, {
|
||||
clearStaleQrcode: session.mode === 'refresh',
|
||||
requireFresh: session.mode === 'refresh',
|
||||
});
|
||||
}
|
||||
this.persistLoginSession(session);
|
||||
return this.toResult(session);
|
||||
@ -1916,15 +1928,18 @@ export class QqbotNapcatLoginService {
|
||||
* @param container - container 输入;驱动 `this.getQrcode()` 的 NapCat步骤。
|
||||
* @param session - session 输入;使用 `qrcode`、`errorMessage` 字段生成结果。
|
||||
* @param status - NapCat列表;使用 `qrcodeurl`、`loginError` 字段生成结果。
|
||||
* @param options - refresh 会话要求二维码必须区别于已知旧码,失败时避免继续把旧码回传给 Admin。
|
||||
*/
|
||||
private async tryUpdatePendingQrcode(
|
||||
container: QqbotNapcatRuntime,
|
||||
session: QqbotLoginScanSession,
|
||||
status: NapcatLoginStatus,
|
||||
options: PendingQrcodeUpdateOptions = {},
|
||||
) {
|
||||
const requireFresh = options.requireFresh ?? !!session.qrcode;
|
||||
try {
|
||||
const qrcode = await this.getQrcode(container, false, {
|
||||
requireFresh: !!session.qrcode,
|
||||
requireFresh,
|
||||
staleQrcode: session.qrcode || status.qrcodeurl,
|
||||
});
|
||||
if (qrcode) {
|
||||
@ -1942,6 +1957,9 @@ export class QqbotNapcatLoginService {
|
||||
}
|
||||
} catch (err) {
|
||||
if (!this.toolsService.isNapcatTemporaryError(err)) throw err;
|
||||
if (options.clearStaleQrcode || requireFresh) {
|
||||
session.qrcode = undefined;
|
||||
}
|
||||
session.errorMessage =
|
||||
session.errorMessage || 'NapCat 正在重新生成二维码,请稍后';
|
||||
}
|
||||
|
||||
@ -276,6 +276,43 @@ describe('QqbotNapcatLoginService', () => {
|
||||
expect(prepareReloginQrcode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not return a stale qrcode when reusing an active refresh session', async () => {
|
||||
const refreshService = new QqbotNapcatLoginService(
|
||||
{ get: jest.fn() } as unknown as ConfigService,
|
||||
{} as QqbotAccountService,
|
||||
{} as QqbotNapcatContainerService,
|
||||
new ToolsService(),
|
||||
);
|
||||
(refreshService as any).sessions.set('session-active-stale-qrcode', {
|
||||
accountId: 'account-1',
|
||||
containerId: 'container-1',
|
||||
containerName: 'napcat-1',
|
||||
createdAt: Date.now(),
|
||||
expiresAt: Date.now() + 60_000,
|
||||
id: 'session-active-stale-qrcode',
|
||||
mode: 'refresh',
|
||||
qrcode: 'old-qrcode',
|
||||
status: 'pending',
|
||||
webuiPort: 6101,
|
||||
});
|
||||
const refreshQrcode = jest
|
||||
.spyOn(refreshService, 'refreshQrcode')
|
||||
.mockResolvedValue({
|
||||
errorMessage: 'NapCat 正在重新生成二维码,请稍后刷新或等待自动更新',
|
||||
mode: 'refresh',
|
||||
qrcode: undefined,
|
||||
sessionId: 'session-active-stale-qrcode',
|
||||
status: 'pending',
|
||||
});
|
||||
|
||||
const result = await refreshService.startRefresh('account-1');
|
||||
|
||||
expect(result.sessionId).toBe('session-active-stale-qrcode');
|
||||
expect(result.qrcode).toBeUndefined();
|
||||
expect(result.errorMessage).toContain('正在重新生成二维码');
|
||||
expect(refreshQrcode).toHaveBeenCalledWith('session-active-stale-qrcode');
|
||||
});
|
||||
|
||||
it('shares an in-flight refresh task before the first session is created', async () => {
|
||||
const account = {
|
||||
id: 'account-1',
|
||||
@ -453,6 +490,9 @@ describe('QqbotNapcatLoginService', () => {
|
||||
isLogin: false,
|
||||
qrcodeurl: 'fresh-qrcode-after-restart',
|
||||
});
|
||||
jest
|
||||
.spyOn(refreshService as any, 'getQrcode')
|
||||
.mockResolvedValue('fresh-qrcode-after-restart');
|
||||
|
||||
const result = await refreshService.status(session.id);
|
||||
|
||||
@ -3405,6 +3445,9 @@ describe('QqbotNapcatLoginService', () => {
|
||||
isLogin: false,
|
||||
qrcodeurl: 'status-qrcode',
|
||||
});
|
||||
jest
|
||||
.spyOn(syncService as any, 'getQrcode')
|
||||
.mockResolvedValue('status-qrcode');
|
||||
|
||||
const result = await syncService.status('session-sync-qrcode-status');
|
||||
|
||||
@ -3701,6 +3744,42 @@ describe('QqbotNapcatLoginService', () => {
|
||||
).rejects.toThrow('NapCat WebUI 登录态仍阻止生成新二维码');
|
||||
});
|
||||
|
||||
it('keeps refresh status pending instead of reusing stale WebUI qrcode', async () => {
|
||||
(service as any).sessions.set('session-status-stale-qrcode', {
|
||||
accountId: 'account-1',
|
||||
containerId: 'container-status-stale',
|
||||
containerName: 'napcat-status-stale',
|
||||
createdAt: Date.now(),
|
||||
expiresAt: Date.now() + 60_000,
|
||||
id: 'session-status-stale-qrcode',
|
||||
mode: 'refresh',
|
||||
qrcode: 'old-qrcode',
|
||||
status: 'pending',
|
||||
webuiPort: 6101,
|
||||
});
|
||||
const container = { id: 'container-status-stale' };
|
||||
jest
|
||||
.spyOn(service as any, 'getSessionContainer')
|
||||
.mockResolvedValue(container);
|
||||
jest.spyOn(service as any, 'getLoginStatus').mockResolvedValue({
|
||||
isLogin: false,
|
||||
qrcodeurl: 'old-qrcode',
|
||||
});
|
||||
const getQrcode = jest
|
||||
.spyOn(service as any, 'getQrcode')
|
||||
.mockRejectedValue(new Error('NapCat 二维码仍未刷新'));
|
||||
|
||||
const result = await service.status('session-status-stale-qrcode');
|
||||
|
||||
expect(result.status).toBe('pending');
|
||||
expect(result.qrcode).toBeUndefined();
|
||||
expect(result.errorMessage).toContain('正在重新生成二维码');
|
||||
expect(getQrcode).toHaveBeenCalledWith(container, false, {
|
||||
requireFresh: true,
|
||||
staleQrcode: 'old-qrcode',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not replace current qrcode with expired status qrcode', async () => {
|
||||
(service as any).sessions.set('session-2', {
|
||||
containerId: 'container-2',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user