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 { NapcatLoginStateStoreService } from '../../infrastructure/persistence/napcat-login-state-store.service';
|
||||||
import { QqbotNapcatContainerService } from '../../infrastructure/integration/container/qqbot-napcat-container.service';
|
import { QqbotNapcatContainerService } from '../../infrastructure/integration/container/qqbot-napcat-container.service';
|
||||||
|
|
||||||
|
type PendingQrcodeUpdateOptions = {
|
||||||
|
clearStaleQrcode?: boolean;
|
||||||
|
requireFresh?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotNapcatLoginService {
|
export class QqbotNapcatLoginService {
|
||||||
private readonly fallbackLoginSessionStore =
|
private readonly fallbackLoginSessionStore =
|
||||||
@ -155,7 +160,10 @@ export class QqbotNapcatLoginService {
|
|||||||
*/
|
*/
|
||||||
async startRefresh(accountId: string) {
|
async startRefresh(accountId: string) {
|
||||||
const activeSession = this.findActiveRefreshSession(accountId);
|
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];
|
const runningTask = this.refreshStartTasks[accountId];
|
||||||
if (runningTask) return runningTask;
|
if (runningTask) return runningTask;
|
||||||
@ -413,6 +421,7 @@ export class QqbotNapcatLoginService {
|
|||||||
session.errorMessage = status.loginError || undefined;
|
session.errorMessage = status.loginError || undefined;
|
||||||
if (
|
if (
|
||||||
status.qrcodeurl &&
|
status.qrcodeurl &&
|
||||||
|
session.mode !== 'refresh' &&
|
||||||
!this.toolsService.isNapcatExpiredQrcodeStatus(status)
|
!this.toolsService.isNapcatExpiredQrcodeStatus(status)
|
||||||
) {
|
) {
|
||||||
const qrcodeChanged = session.qrcode !== status.qrcodeurl;
|
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;
|
session.qrcode = undefined;
|
||||||
} else if (!this.toolsService.isNapcatExpiredQrcodeStatus(status)) {
|
} 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);
|
this.persistLoginSession(session);
|
||||||
return this.toResult(session);
|
return this.toResult(session);
|
||||||
@ -1916,15 +1928,18 @@ export class QqbotNapcatLoginService {
|
|||||||
* @param container - container 输入;驱动 `this.getQrcode()` 的 NapCat步骤。
|
* @param container - container 输入;驱动 `this.getQrcode()` 的 NapCat步骤。
|
||||||
* @param session - session 输入;使用 `qrcode`、`errorMessage` 字段生成结果。
|
* @param session - session 输入;使用 `qrcode`、`errorMessage` 字段生成结果。
|
||||||
* @param status - NapCat列表;使用 `qrcodeurl`、`loginError` 字段生成结果。
|
* @param status - NapCat列表;使用 `qrcodeurl`、`loginError` 字段生成结果。
|
||||||
|
* @param options - refresh 会话要求二维码必须区别于已知旧码,失败时避免继续把旧码回传给 Admin。
|
||||||
*/
|
*/
|
||||||
private async tryUpdatePendingQrcode(
|
private async tryUpdatePendingQrcode(
|
||||||
container: QqbotNapcatRuntime,
|
container: QqbotNapcatRuntime,
|
||||||
session: QqbotLoginScanSession,
|
session: QqbotLoginScanSession,
|
||||||
status: NapcatLoginStatus,
|
status: NapcatLoginStatus,
|
||||||
|
options: PendingQrcodeUpdateOptions = {},
|
||||||
) {
|
) {
|
||||||
|
const requireFresh = options.requireFresh ?? !!session.qrcode;
|
||||||
try {
|
try {
|
||||||
const qrcode = await this.getQrcode(container, false, {
|
const qrcode = await this.getQrcode(container, false, {
|
||||||
requireFresh: !!session.qrcode,
|
requireFresh,
|
||||||
staleQrcode: session.qrcode || status.qrcodeurl,
|
staleQrcode: session.qrcode || status.qrcodeurl,
|
||||||
});
|
});
|
||||||
if (qrcode) {
|
if (qrcode) {
|
||||||
@ -1942,6 +1957,9 @@ export class QqbotNapcatLoginService {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!this.toolsService.isNapcatTemporaryError(err)) throw err;
|
if (!this.toolsService.isNapcatTemporaryError(err)) throw err;
|
||||||
|
if (options.clearStaleQrcode || requireFresh) {
|
||||||
|
session.qrcode = undefined;
|
||||||
|
}
|
||||||
session.errorMessage =
|
session.errorMessage =
|
||||||
session.errorMessage || 'NapCat 正在重新生成二维码,请稍后';
|
session.errorMessage || 'NapCat 正在重新生成二维码,请稍后';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -276,6 +276,43 @@ describe('QqbotNapcatLoginService', () => {
|
|||||||
expect(prepareReloginQrcode).toHaveBeenCalledTimes(1);
|
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 () => {
|
it('shares an in-flight refresh task before the first session is created', async () => {
|
||||||
const account = {
|
const account = {
|
||||||
id: 'account-1',
|
id: 'account-1',
|
||||||
@ -453,6 +490,9 @@ describe('QqbotNapcatLoginService', () => {
|
|||||||
isLogin: false,
|
isLogin: false,
|
||||||
qrcodeurl: 'fresh-qrcode-after-restart',
|
qrcodeurl: 'fresh-qrcode-after-restart',
|
||||||
});
|
});
|
||||||
|
jest
|
||||||
|
.spyOn(refreshService as any, 'getQrcode')
|
||||||
|
.mockResolvedValue('fresh-qrcode-after-restart');
|
||||||
|
|
||||||
const result = await refreshService.status(session.id);
|
const result = await refreshService.status(session.id);
|
||||||
|
|
||||||
@ -3405,6 +3445,9 @@ describe('QqbotNapcatLoginService', () => {
|
|||||||
isLogin: false,
|
isLogin: false,
|
||||||
qrcodeurl: 'status-qrcode',
|
qrcodeurl: 'status-qrcode',
|
||||||
});
|
});
|
||||||
|
jest
|
||||||
|
.spyOn(syncService as any, 'getQrcode')
|
||||||
|
.mockResolvedValue('status-qrcode');
|
||||||
|
|
||||||
const result = await syncService.status('session-sync-qrcode-status');
|
const result = await syncService.status('session-sync-qrcode-status');
|
||||||
|
|
||||||
@ -3701,6 +3744,42 @@ describe('QqbotNapcatLoginService', () => {
|
|||||||
).rejects.toThrow('NapCat WebUI 登录态仍阻止生成新二维码');
|
).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 () => {
|
it('does not replace current qrcode with expired status qrcode', async () => {
|
||||||
(service as any).sessions.set('session-2', {
|
(service as any).sessions.set('session-2', {
|
||||||
containerId: 'container-2',
|
containerId: 'container-2',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user