From 0fcd0e80e07b85deeed24d85e09e62cbfdb2dfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Fri, 7 Aug 2026 17:45:31 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=99=BB=E5=BD=95=20|=20feat(login):=20automatically=20restore?= =?UTF-8?q?=20last=20logged-in=20account=20(#1980)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 31c45bf18e4ea2cc23f6492304fef88afb8e8302. --- packages/napcat-framework/napcat.ts | 28 +--- .../launcher-win10-user.bat | 4 +- .../napcat-shell-loader/launcher-win10.bat | 4 +- .../napcat-shell-loader/quickLoginExample.bat | 2 +- packages/napcat-shell/base.ts | 24 ++-- packages/napcat-test/autoLogin.test.ts | 51 ------- packages/napcat-webui-backend/index.ts | 129 ++++++++---------- .../src/helper/config-schema.ts | 37 ----- .../napcat-webui-backend/src/helper/config.ts | 43 ++++-- 9 files changed, 119 insertions(+), 203 deletions(-) delete mode 100644 packages/napcat-test/autoLogin.test.ts delete mode 100644 packages/napcat-webui-backend/src/helper/config-schema.ts diff --git a/packages/napcat-framework/napcat.ts b/packages/napcat-framework/napcat.ts index ced3d4d6..88f0b41d 100644 --- a/packages/napcat-framework/napcat.ts +++ b/packages/napcat-framework/napcat.ts @@ -87,13 +87,11 @@ export async function NCoreInitFramework ( // 提前启动 WebUI,使其在登录前可用,支持通过 WebUI 操控登录过程 WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework); - await InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription) - .catch(e => logger.logError(e)); + InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e)); // 直到登录成功后,执行下一步 const selfInfo = await new Promise((resolve) => { const loginContext = { isLogined: false }; - let autoLoginTriggered = false; const loginListener = new NodeIKernelLoginListener(); @@ -107,11 +105,6 @@ export async function NCoreInitFramework ( loginContext.isLogined = true; WebUiDataRuntime.setQQLoginStatus(true); WebUiDataRuntime.setQQLoginError(''); - try { - await WebUiConfig.UpdateLastLoginAccount(loginResult.uin); - } catch (error) { - logger.logWarn('[NapCat] [Framework] 保存最近登录账号失败:', error); - } await new Promise(resolve => { registerInitCallback(() => resolve()); }); @@ -147,19 +140,12 @@ export async function NCoreInitFramework ( logger.log('[NapCat] [Framework] 登录服务已连接'); // 注册 WebUI 登录操作回调 registerWebUiLoginCallbacks(loginService, loginContext, logger); - if (autoLoginTriggered) return; - autoLoginTriggered = true; - (async () => { - try { - const res = await loginService.getLoginList(); - const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin); - WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString())); - WebUiDataRuntime.setQQNewLoginList(list); - } catch (error) { - logger.logError('[NapCat] [Framework] 获取登录列表失败:', error); - } - await WebUiDataRuntime.runWebUiConfigQuickFunction(); - })().catch(error => logger.logError('[NapCat] [Framework] 自动登录执行失败:', error)); + // 获取历史登录列表供 WebUI 使用 + loginService.getLoginList().then((res) => { + const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin); + WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString())); + WebUiDataRuntime.setQQNewLoginList(list); + }).catch(e => logger.logError('[NapCat] [Framework] 获取登录列表失败:', e)); }; loginListener.onQRCodeSessionUserScaned = () => { diff --git a/packages/napcat-shell-loader/launcher-win10-user.bat b/packages/napcat-shell-loader/launcher-win10-user.bat index 97e3e2d2..6c615d12 100644 --- a/packages/napcat-shell-loader/launcher-win10-user.bat +++ b/packages/napcat-shell-loader/launcher-win10-user.bat @@ -28,7 +28,7 @@ echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LO "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %* -REM Optional: pass the QQ number as the first argument for quick login; omit it for QR code login -REM Example: "%~f0" 123456 +REM Optional: -q for quick login, omit for QR code login +REM Example: "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" -q 123456 pause diff --git a/packages/napcat-shell-loader/launcher-win10.bat b/packages/napcat-shell-loader/launcher-win10.bat index 17b4a751..271c6112 100644 --- a/packages/napcat-shell-loader/launcher-win10.bat +++ b/packages/napcat-shell-loader/launcher-win10.bat @@ -37,5 +37,5 @@ echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LO "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %* -REM Optional: pass the QQ number as the first argument for quick login; omit it for QR code login -REM Example: "%~f0" 123456 +REM Optional: -q for quick login, omit for QR code login +REM Example: "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" -q 123456 diff --git a/packages/napcat-shell-loader/quickLoginExample.bat b/packages/napcat-shell-loader/quickLoginExample.bat index 8d09cab6..95c401a6 100644 --- a/packages/napcat-shell-loader/quickLoginExample.bat +++ b/packages/napcat-shell-loader/quickLoginExample.bat @@ -1,6 +1,6 @@ @echo off REM 快速登录示例脚本 -REM QQ 号参数是可选的,作为启动脚本的第一个参数传入;不传则使用二维码登录 +REM -q 参数是可选的,不传则使用二维码登录 REM REM 使用方法(删掉对应系统那行的 REM): REM ./launcher-user.bat 123456 diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index 98d5fe2f..0b6f6eca 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -24,7 +24,7 @@ import { createHash } from 'node:crypto'; import { LoginListItem, NodeIKernelLoginService } from 'napcat-core/services'; import qrcode from 'napcat-qrcode/lib/main'; import { NapCatAdapterManager } from 'napcat-adapter'; -import { InitWebUi, WebUiConfig } from 'napcat-webui-backend/index'; +import { InitWebUi } from 'napcat-webui-backend/index'; import { WebUiDataRuntime } from 'napcat-webui-backend/src/helper/Data'; import { napCatVersion } from 'napcat-common/src/version'; import { NodeIO3MiscListener } from 'napcat-core/listeners/NodeIO3MiscListener'; @@ -142,11 +142,6 @@ async function handleLogin ( loginListener.onQRCodeLoginSucceed = async (loginResult) => { context.isLogined = true; WebUiDataRuntime.setQQLoginStatus(true); - try { - await WebUiConfig.UpdateLastLoginAccount(loginResult.uin); - } catch (error) { - logger.logWarn('[NapCat] [Login] 保存最近登录账号失败:', error); - } inner_resolve({ uid: loginResult.uid, uin: loginResult.uin, @@ -486,18 +481,28 @@ async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWr if (!success && !attempted && !context.isLogined) loginService.getQRCodePicture(); } } else { - logger.log('没有 -q 指令指定快速登录,将尝试配置账号或最近登录账号'); + logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式'); if (historyLoginList.length > 0) { logger.log(`可用于快速登录 of QQ:\n${historyLoginList .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`) .join('\n') }`); } + let hasAttemptedFallback = false; try { + // 检查是否配置了自动登录账号,并尝试 WebUi 快速/密码回退登录。 + // 这可以避免在已经有自动登录配置的情况下过早输出二维码。 + const quickLoginList = WebUiDataRuntime.getQQQuickLoginList() || []; + const hasAutoLogin = process.env['NAPCAT_QUICK_ACCOUNT'] || (quickLoginList.length > 0); // 假如有历史登录列表 + if (hasAutoLogin || process.env['NAPCAT_QUICK_PASSWORD'] || process.env['NAPCAT_QUICK_PASSWORD_MD5']) { + hasAttemptedFallback = true; + } await WebUiDataRuntime.runWebUiConfigQuickFunction(); } catch (error) { logger.logError('WebUi 快速登录失败 执行失败', error); - if (!context.isLogined) loginService.getQRCodePicture(); + } + if (!hasAttemptedFallback && !context.isLogined) { + loginService.getQRCodePicture(); } } @@ -643,8 +648,7 @@ export async function NCoreInitShell () { logger.log('[NapCat] [Core] NapCat.Core Version: ' + napCatVersion); WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Shell); - await InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription) - .catch(e => logger.logError(e)); + InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e)); const engine = wrapper.NodeIQQNTWrapperEngine.get(); const loginService = wrapper.NodeIKernelLoginService.get(); let session: NodeIQQNTWrapperSession; diff --git a/packages/napcat-test/autoLogin.test.ts b/packages/napcat-test/autoLogin.test.ts deleted file mode 100644 index 6dc0528e..00000000 --- a/packages/napcat-test/autoLogin.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Value } from '@sinclair/typebox/value'; -import { describe, expect, test } from 'vitest'; - -import { - resolveAutoLoginAccount, - WebUiConfigSchema, - type WebUiConfigType, -} from '../napcat-webui-backend/src/helper/config-schema'; - -describe('automatic login account selection', () => { - test('uses explicit sources before the last successful account', () => { - expect(resolveAutoLoginAccount('10001', '10002', '10003')).toBe('10001'); - expect(resolveAutoLoginAccount(undefined, '10002', '10003')).toBe('10002'); - expect(resolveAutoLoginAccount(undefined, undefined, '10003')).toBe('10003'); - }); - - test('ignores empty account values', () => { - expect(resolveAutoLoginAccount(' ', '', ' 10003 ')).toBe('10003'); - expect(resolveAutoLoginAccount(undefined, '', ' ')).toBeUndefined(); - }); -}); - -describe('WebUI login configuration migration', () => { - test('adds an empty last account to an existing configuration', () => { - const config = Value.Parse(WebUiConfigSchema, { - host: '127.0.0.1', - autoLoginAccount: '10002', - }) as WebUiConfigType; - - expect(config.host).toBe('127.0.0.1'); - expect(config.autoLoginAccount).toBe('10002'); - expect(config.lastLoginAccount).toBe(''); - }); - - test('preserves existing settings when the last account is updated', () => { - const current = Value.Parse(WebUiConfigSchema, { - host: '127.0.0.1', - port: 7000, - autoLoginAccount: '10002', - }) as WebUiConfigType; - const updated = Value.Parse(WebUiConfigSchema, { - ...current, - lastLoginAccount: '10003', - }) as WebUiConfigType; - - expect(updated.host).toBe('127.0.0.1'); - expect(updated.port).toBe(7000); - expect(updated.autoLoginAccount).toBe('10002'); - expect(updated.lastLoginAccount).toBe('10003'); - }); -}); diff --git a/packages/napcat-webui-backend/index.ts b/packages/napcat-webui-backend/index.ts index a1663195..01d83af1 100644 --- a/packages/napcat-webui-backend/index.ts +++ b/packages/napcat-webui-backend/index.ts @@ -8,7 +8,7 @@ import { createServer } from 'http'; import { createHash, randomUUID } from 'node:crypto'; import { createServer as createHttpsServer } from 'https'; import { NapCatPathWrapper } from 'napcat-common/src/path'; -import { resolveAutoLoginAccount, WebUiConfigWrapper } from '@/napcat-webui-backend/src/helper/config'; +import { WebUiConfigWrapper } from '@/napcat-webui-backend/src/helper/config'; import { ALLRouter } from '@/napcat-webui-backend/src/router'; import { cors } from '@/napcat-webui-backend/src/middleware/cors'; import { createUrl, getRandomToken } from '@/napcat-webui-backend/src/utils/url'; @@ -118,74 +118,6 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra WebUiConfig = new WebUiConfigWrapper(); let config = await WebUiConfig.GetWebUIConfig(); - // 自动登录与 WebUI 服务是否启用无关,需要在所有提前返回之前注册。 - WebUiDataRuntime.setWebUiConfigQuickFunction( - async () => { - const autoLoginAccount = resolveAutoLoginAccount( - process.env['NAPCAT_QUICK_ACCOUNT'], - WebUiConfig.getAutoLoginAccount(), - WebUiConfig.getLastLoginAccount() - ); - const resolveQuickPasswordMd5 = (): string | undefined => { - const quickPasswordMd5FromEnv = process.env['NAPCAT_QUICK_PASSWORD_MD5']?.trim(); - if (quickPasswordMd5FromEnv) { - if (/^[a-fA-F0-9]{32}$/.test(quickPasswordMd5FromEnv)) { - return quickPasswordMd5FromEnv.toLowerCase(); - } - console.log('[NapCat] [WebUi] NAPCAT_QUICK_PASSWORD_MD5 格式无效(需为 32 位 MD5)'); - } - - const quickPassword = process.env['NAPCAT_QUICK_PASSWORD']; - if (typeof quickPassword === 'string' && quickPassword.length > 0) { - console.log('[NapCat] [WebUi] 检测到 NAPCAT_QUICK_PASSWORD,已在内存中计算 MD5 用于回退登录'); - return createHash('md5').update(quickPassword, 'utf8').digest('hex'); - } - return undefined; - }; - if (!autoLoginAccount) { - await WebUiDataRuntime.refreshQRCode(); - return; - } - const quickPasswordMd5 = resolveQuickPasswordMd5(); - - try { - const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount); - if (result) { - console.log(`[NapCat] [WebUi] 自动快速登录成功: ${autoLoginAccount}`); - return; - } - console.log(`[NapCat] [WebUi] 自动快速登录失败: ${message || '未知错误'}`); - } catch (error) { - console.log('[NapCat] [WebUi] 自动快速登录异常:' + error); - } - - if (!quickPasswordMd5) { - console.log(`[NapCat] [WebUi] QQ ${autoLoginAccount} 未配置回退密码环境变量,建议优先使用 ACCOUNT + NAPCAT_QUICK_PASSWORD(NAPCAT_QUICK_PASSWORD_MD5 作为备用),保持二维码登录兜底`); - await WebUiDataRuntime.refreshQRCode(); - return; - } - - try { - const { result, message, needCaptcha, needNewDevice } = await WebUiDataRuntime.requestPasswordLogin(autoLoginAccount, quickPasswordMd5); - if (result) { - console.log(`[NapCat] [WebUi] 自动密码回退登录成功: ${autoLoginAccount}`); - return; - } - if (needCaptcha) { - console.log(`[NapCat] [WebUi] 自动密码回退登录需要验证码,请在登录页面继续完成: ${autoLoginAccount}`); - return; - } - if (needNewDevice) { - console.log(`[NapCat] [WebUi] 自动密码回退登录需要新设备验证,请在登录页面继续完成: ${autoLoginAccount}`); - return; - } - console.log(`[NapCat] [WebUi] 自动密码回退登录失败: ${message || '未知错误'}`); - } catch (error) { - console.log('[NapCat] [WebUi] 自动密码回退登录异常:' + error); - } - await WebUiDataRuntime.refreshQRCode(); - }); - // 检查是否禁用WebUI(若禁用则不进行密码检测) if (config.disableWebUI) { logger.log('[NapCat] [WebUi] WebUI is disabled by configuration.'); @@ -220,6 +152,65 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra logger.log('[NapCat] [WebUi] Current WebUi is not run.'); return; } + WebUiDataRuntime.setWebUiConfigQuickFunction( + async () => { + const autoLoginAccount = process.env['NAPCAT_QUICK_ACCOUNT'] || WebUiConfig.getAutoLoginAccount(); + const resolveQuickPasswordMd5 = (): string | undefined => { + const quickPasswordMd5FromEnv = process.env['NAPCAT_QUICK_PASSWORD_MD5']?.trim(); + if (quickPasswordMd5FromEnv) { + if (/^[a-fA-F0-9]{32}$/.test(quickPasswordMd5FromEnv)) { + return quickPasswordMd5FromEnv.toLowerCase(); + } + console.log('[NapCat] [WebUi] NAPCAT_QUICK_PASSWORD_MD5 格式无效(需为 32 位 MD5)'); + } + + const quickPassword = process.env['NAPCAT_QUICK_PASSWORD']; + if (typeof quickPassword === 'string' && quickPassword.length > 0) { + console.log('[NapCat] [WebUi] 检测到 NAPCAT_QUICK_PASSWORD,已在内存中计算 MD5 用于回退登录'); + return createHash('md5').update(quickPassword, 'utf8').digest('hex'); + } + return undefined; + }; + if (!autoLoginAccount) { + return; + } + const quickPasswordMd5 = resolveQuickPasswordMd5(); + + try { + const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount); + if (result) { + console.log(`[NapCat] [WebUi] 自动快速登录成功: ${autoLoginAccount}`); + return; + } + console.log(`[NapCat] [WebUi] 自动快速登录失败: ${message || '未知错误'}`); + } catch (error) { + console.log('[NapCat] [WebUi] 自动快速登录异常:' + error); + } + + if (!quickPasswordMd5) { + console.log(`[NapCat] [WebUi] QQ ${autoLoginAccount} 未配置回退密码环境变量,建议优先使用 ACCOUNT + NAPCAT_QUICK_PASSWORD(NAPCAT_QUICK_PASSWORD_MD5 作为备用),保持二维码登录兜底`); + return; + } + + try { + const { result, message, needCaptcha, needNewDevice } = await WebUiDataRuntime.requestPasswordLogin(autoLoginAccount, quickPasswordMd5); + if (result) { + console.log(`[NapCat] [WebUi] 自动密码回退登录成功: ${autoLoginAccount}`); + return; + } + if (needCaptcha) { + console.log(`[NapCat] [WebUi] 自动密码回退登录需要验证码,请在登录页面继续完成: ${autoLoginAccount}`); + return; + } + if (needNewDevice) { + console.log(`[NapCat] [WebUi] 自动密码回退登录需要新设备验证,请在登录页面继续完成: ${autoLoginAccount}`); + return; + } + console.log(`[NapCat] [WebUi] 自动密码回退登录失败: ${message || '未知错误'}`); + } catch (error) { + console.log('[NapCat] [WebUi] 自动密码回退登录异常:' + error); + } + }); // ------------注册中间件------------ // 使用express的json中间件 app.use(express.json()); diff --git a/packages/napcat-webui-backend/src/helper/config-schema.ts b/packages/napcat-webui-backend/src/helper/config-schema.ts deleted file mode 100644 index 75fecd3d..00000000 --- a/packages/napcat-webui-backend/src/helper/config-schema.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Static, Type } from '@sinclair/typebox'; - -import { themeType } from '../types/theme'; -import { getRandomToken } from '../utils/url'; - -export const WebUiConfigSchema = Type.Object({ - host: Type.String({ default: '::' }), - port: Type.Number({ default: 6099 }), - token: Type.String({ default: getRandomToken(12) }), - loginRate: Type.Number({ default: 10 }), - autoLoginAccount: Type.String({ default: '' }), - lastLoginAccount: Type.String({ default: '' }), - theme: themeType, - disableWebUI: Type.Boolean({ default: false }), - accessControlMode: Type.Union([ - Type.Literal('none'), - Type.Literal('whitelist'), - Type.Literal('blacklist'), - ], { default: 'none' }), - ipWhitelist: Type.Array(Type.String(), { default: [] }), - ipBlacklist: Type.Array(Type.String(), { default: [] }), - enableXForwardedFor: Type.Boolean({ default: false }), - enable2FA: Type.Boolean({ default: false }), - totpSecret: Type.String({ default: '' }), -}); - -export type WebUiConfigType = Static; - -export function resolveAutoLoginAccount ( - environmentAccount: string | undefined, - configuredAccount: string | undefined, - lastLoginAccount: string | undefined -): string | undefined { - return [environmentAccount, configuredAccount, lastLoginAccount] - .map(account => account?.trim()) - .find((account): account is string => Boolean(account)); -} diff --git a/packages/napcat-webui-backend/src/helper/config.ts b/packages/napcat-webui-backend/src/helper/config.ts index e916fa68..258b1544 100644 --- a/packages/napcat-webui-backend/src/helper/config.ts +++ b/packages/napcat-webui-backend/src/helper/config.ts @@ -1,4 +1,5 @@ import { webUiPathWrapper, getInitialWebUiToken } from '@/napcat-webui-backend/index'; +import { Type, Static } from '@sinclair/typebox'; import { TypeCompiler } from '@sinclair/typebox/compiler'; import { Value } from '@sinclair/typebox/value'; import fs, { constants } from 'node:fs/promises'; @@ -6,9 +7,39 @@ import fs, { constants } from 'node:fs/promises'; import { resolve } from 'node:path'; import { deepMerge } from '../utils/object'; -import { WebUiConfigSchema, type WebUiConfigType } from './config-schema'; +import { themeType } from '../types/theme'; +import { getRandomToken } from '../utils/url'; -export { resolveAutoLoginAccount, WebUiConfigSchema, type WebUiConfigType } from './config-schema'; +// 限制尝试端口的次数,避免死循环 +// 定义配置的类型 +const WebUiConfigSchema = Type.Object({ + host: Type.String({ default: '::' }), + port: Type.Number({ default: 6099 }), + token: Type.String({ default: getRandomToken(12) }), + loginRate: Type.Number({ default: 10 }), + autoLoginAccount: Type.String({ default: '' }), + theme: themeType, + // 是否关闭WebUI + disableWebUI: Type.Boolean({ default: false }), + // 网络访问控制模式: 'none' | 'whitelist' | 'blacklist' + accessControlMode: Type.Union([ + Type.Literal('none'), + Type.Literal('whitelist'), + Type.Literal('blacklist'), + ], { default: 'none' }), + // IP白名单列表 + ipWhitelist: Type.Array(Type.String(), { default: [] }), + // IP黑名单列表 + ipBlacklist: Type.Array(Type.String(), { default: [] }), + // 是否启用 X-Forwarded-For 获取真实IP + enableXForwardedFor: Type.Boolean({ default: false }), + // 是否启用双因素认证(2FA) + enable2FA: Type.Boolean({ default: false }), + // TOTP 密钥 + totpSecret: Type.String({ default: '' }), +}); + +export type WebUiConfigType = Static; // 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件 export class WebUiConfigWrapper { @@ -211,14 +242,6 @@ export class WebUiConfigWrapper { await this.UpdateWebUIConfig({ autoLoginAccount: uin }); } - getLastLoginAccount (): string | undefined { - return this.WebUiConfigData?.lastLoginAccount; - } - - async UpdateLastLoginAccount (uin: string): Promise { - await this.UpdateWebUIConfig({ lastLoginAccount: uin }); - } - // 获取主题内容 async GetTheme (): Promise { const config = await this.GetWebUIConfig();