Skip to content

Commit ae53d27

Browse files
committed
Integrate WebUI login flow and callbacks
Start the WebUI earlier and integrate it with the login process. Initialize WebUI before user login so the UI can control and display login status; set working environment and start InitWebUi asynchronously. Add login listener handlers to update WebUiDataRuntime for QR updates, session failures, login errors, connection events and history. Introduce registerWebUiLoginCallbacks to expose refresh QR, quick login, password/captcha/new-device login flows to the WebUI and update loginContext and UI state accordingly. Also set QQ data path after successful login.
1 parent 86884b6 commit ae53d27

File tree

1 file changed

+217
-9
lines changed

1 file changed

+217
-9
lines changed

packages/napcat-framework/napcat.ts

Lines changed: 217 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,27 @@ export async function NCoreInitFramework (
8484

8585
// 初始化 FFmpeg 服务
8686
await FFmpegService.init(pathWrapper.binaryPath, logger);
87+
88+
// 提前启动 WebUI,使其在登录前可用,支持通过 WebUI 操控登录过程
89+
WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
90+
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
91+
8792
// 直到登录成功后,执行下一步
88-
// const selfInfo = {
89-
// uid: 'u_FUSS0_x06S_9Tf4na_WpUg',
90-
// uin: '3684714082',
91-
// nick: '',
92-
// online: true
93-
// }
9493
const selfInfo = await new Promise<SelfInfo>((resolve) => {
94+
const loginContext = { isLogined: false };
95+
9596
const loginListener = new NodeIKernelLoginListener();
97+
98+
loginListener.onUserLoggedIn = (userid: string) => {
99+
const tips = `当前账号(${userid})已登录,无法重复登录`;
100+
logger.logError(tips);
101+
WebUiDataRuntime.setQQLoginError(tips);
102+
};
103+
96104
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
105+
loginContext.isLogined = true;
106+
WebUiDataRuntime.setQQLoginStatus(true);
107+
WebUiDataRuntime.setQQLoginError('');
97108
await new Promise<void>(resolve => {
98109
registerInitCallback(() => resolve());
99110
});
@@ -104,6 +115,47 @@ export async function NCoreInitFramework (
104115
online: true,
105116
});
106117
};
118+
119+
loginListener.onQRCodeGetPicture = ({ qrcodeUrl }) => {
120+
WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl);
121+
logger.log('[NapCat] [Framework] 二维码已更新, URL:', qrcodeUrl);
122+
};
123+
124+
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number) => {
125+
if (!loginContext.isLogined) {
126+
logger.logError('[NapCat] [Framework] [Login] QRCode Session Failed, ErrType:', errType, 'ErrCode:', errCode);
127+
if (errType === 1 && errCode === 3) {
128+
WebUiDataRuntime.setQQLoginError('二维码已过期,请刷新');
129+
}
130+
}
131+
};
132+
133+
loginListener.onLoginFailed = (...args) => {
134+
const errInfo = JSON.stringify(args);
135+
logger.logError('[NapCat] [Framework] [Login] Login Failed, ErrInfo:', errInfo);
136+
WebUiDataRuntime.setQQLoginError(`登录失败: ${errInfo}`);
137+
};
138+
139+
loginListener.onLoginConnected = () => {
140+
logger.log('[NapCat] [Framework] 登录服务已连接');
141+
// 注册 WebUI 登录操作回调
142+
registerWebUiLoginCallbacks(loginService, loginContext, logger);
143+
// 获取历史登录列表供 WebUI 使用
144+
loginService.getLoginList().then((res) => {
145+
const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin);
146+
WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString()));
147+
WebUiDataRuntime.setQQNewLoginList(list);
148+
}).catch(e => logger.logError('[NapCat] [Framework] 获取登录列表失败:', e));
149+
};
150+
151+
loginListener.onQRCodeSessionUserScaned = () => {
152+
logger.log('[NapCat] [Framework] 二维码已被扫描,等待确认...');
153+
};
154+
155+
loginListener.onPasswordLoginFailed = (...args) => {
156+
logger.logError('[NapCat] [Framework] 密码登录失败:', ...args);
157+
};
158+
107159
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
108160
});
109161
// 过早进入会导致addKernelMsgListener等Listener添加失败
@@ -116,10 +168,8 @@ export async function NCoreInitFramework (
116168
}
117169
await loaderObject.core.initCore();
118170

119-
// 启动WebUi
120-
WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
171+
// 登录成功后设置数据路径
121172
WebUiDataRuntime.setQQDataPath(loaderObject.core.dataPath);
122-
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
123173

124174
// // 测试 DatabaseApi
125175
// if (dbPassphrase) {
@@ -147,6 +197,164 @@ export async function NCoreInitFramework (
147197
}
148198
}
149199

200+
function registerWebUiLoginCallbacks (
201+
loginService: NodeIKernelLoginService,
202+
loginContext: { isLogined: boolean; },
203+
logger: LogWrapper
204+
) {
205+
// 刷新二维码
206+
WebUiDataRuntime.setRefreshQRCodeCallback(async () => {
207+
loginService.getQRCodePicture();
208+
});
209+
210+
// 快速登录
211+
WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
212+
return await new Promise((resolve) => {
213+
if (!uin) {
214+
return resolve({ result: false, message: '快速登录失败' });
215+
}
216+
logger.log('[NapCat] [Framework] 正在快速登录', uin);
217+
loginService.quickLoginWithUin(uin).then(res => {
218+
const success = res.result === '0' && !res.loginErrorInfo?.errMsg;
219+
if (!success) {
220+
const errMsg = res.loginErrorInfo?.errMsg || `快速登录失败,错误码: ${res.result}`;
221+
WebUiDataRuntime.setQQLoginError(errMsg);
222+
resolve({ result: false, message: errMsg });
223+
} else {
224+
loginContext.isLogined = true;
225+
WebUiDataRuntime.setQQLoginStatus(true);
226+
WebUiDataRuntime.setQQLoginError('');
227+
resolve({ result: true, message: '' });
228+
}
229+
}).catch((e) => {
230+
logger.logError('[NapCat] [Framework] 快速登录异常:', e);
231+
WebUiDataRuntime.setQQLoginError('快速登录发生错误');
232+
resolve({ result: false, message: '快速登录发生错误' });
233+
});
234+
});
235+
});
236+
237+
// 密码登录
238+
WebUiDataRuntime.setPasswordLoginCall(async (uin: string, passwordMd5: string) => {
239+
return await new Promise((resolve) => {
240+
if (!uin || !passwordMd5) {
241+
return resolve({ result: false, message: '密码登录失败:参数不完整' });
242+
}
243+
logger.log('[NapCat] [Framework] 正在密码登录', uin);
244+
loginService.passwordLogin({
245+
uin,
246+
passwordMd5,
247+
step: 0,
248+
newDeviceLoginSig: new Uint8Array(),
249+
proofWaterSig: new Uint8Array(),
250+
proofWaterRand: new Uint8Array(),
251+
proofWaterSid: new Uint8Array(),
252+
unusualDeviceCheckSig: new Uint8Array(),
253+
}).then(res => {
254+
if (res.result === '140022008') {
255+
const proofWaterUrl = res.loginErrorInfo?.proofWaterUrl || '';
256+
resolve({ result: false, message: '需要验证码', needCaptcha: true, proofWaterUrl });
257+
} else if (res.result === '140022010' || res.result === '140022011') {
258+
const jumpUrl = res.loginErrorInfo?.jumpUrl || '';
259+
const newDevicePullQrCodeSig = res.loginErrorInfo?.newDevicePullQrCodeSig || '';
260+
resolve({ result: false, message: '新设备需要扫码验证', needNewDevice: true, jumpUrl, newDevicePullQrCodeSig });
261+
} else if (res.result !== '0') {
262+
const errMsg = res.loginErrorInfo?.errMsg || '密码登录失败';
263+
WebUiDataRuntime.setQQLoginError(errMsg);
264+
resolve({ result: false, message: errMsg });
265+
} else {
266+
loginContext.isLogined = true;
267+
WebUiDataRuntime.setQQLoginStatus(true);
268+
WebUiDataRuntime.setQQLoginError('');
269+
resolve({ result: true, message: '' });
270+
}
271+
}).catch((e) => {
272+
logger.logError('[NapCat] [Framework] 密码登录异常:', e);
273+
WebUiDataRuntime.setQQLoginError('密码登录发生错误');
274+
resolve({ result: false, message: '密码登录发生错误' });
275+
});
276+
});
277+
});
278+
279+
// 验证码登录(密码登录需要验证码时的第二步)
280+
WebUiDataRuntime.setCaptchaLoginCall(async (uin: string, passwordMd5: string, ticket: string, randstr: string, sid: string) => {
281+
return await new Promise((resolve) => {
282+
if (!uin || !passwordMd5 || !ticket) {
283+
return resolve({ result: false, message: '验证码登录失败:参数不完整' });
284+
}
285+
logger.log('[NapCat] [Framework] 正在验证码登录', uin);
286+
loginService.passwordLogin({
287+
uin,
288+
passwordMd5,
289+
step: 1,
290+
newDeviceLoginSig: new Uint8Array(),
291+
proofWaterSig: new TextEncoder().encode(ticket),
292+
proofWaterRand: new TextEncoder().encode(randstr),
293+
proofWaterSid: new TextEncoder().encode(sid),
294+
unusualDeviceCheckSig: new Uint8Array(),
295+
}).then(res => {
296+
if (res.result === '140022010' || res.result === '140022011') {
297+
const jumpUrl = res.loginErrorInfo?.jumpUrl || '';
298+
const newDevicePullQrCodeSig = res.loginErrorInfo?.newDevicePullQrCodeSig || '';
299+
resolve({ result: false, message: '新设备需要扫码验证', needNewDevice: true, jumpUrl, newDevicePullQrCodeSig });
300+
} else if (res.result !== '0') {
301+
const errMsg = res.loginErrorInfo?.errMsg || '验证码登录失败';
302+
WebUiDataRuntime.setQQLoginError(errMsg);
303+
resolve({ result: false, message: errMsg });
304+
} else {
305+
loginContext.isLogined = true;
306+
WebUiDataRuntime.setQQLoginStatus(true);
307+
WebUiDataRuntime.setQQLoginError('');
308+
resolve({ result: true, message: '' });
309+
}
310+
}).catch((e) => {
311+
logger.logError('[NapCat] [Framework] 验证码登录异常:', e);
312+
WebUiDataRuntime.setQQLoginError('验证码登录发生错误');
313+
resolve({ result: false, message: '验证码登录发生错误' });
314+
});
315+
});
316+
});
317+
318+
// 新设备验证登录(密码登录需要新设备验证时的第二步)
319+
WebUiDataRuntime.setNewDeviceLoginCall(async (uin: string, passwordMd5: string, newDevicePullQrCodeSig: string) => {
320+
return await new Promise((resolve) => {
321+
if (!uin || !passwordMd5 || !newDevicePullQrCodeSig) {
322+
return resolve({ result: false, message: '新设备验证登录失败:参数不完整' });
323+
}
324+
logger.log('[NapCat] [Framework] 正在新设备验证登录', uin);
325+
loginService.passwordLogin({
326+
uin,
327+
passwordMd5,
328+
step: 2,
329+
newDeviceLoginSig: new TextEncoder().encode(newDevicePullQrCodeSig),
330+
proofWaterSig: new Uint8Array(),
331+
proofWaterRand: new Uint8Array(),
332+
proofWaterSid: new Uint8Array(),
333+
unusualDeviceCheckSig: new Uint8Array(),
334+
}).then(res => {
335+
if (res.result === '140022011') {
336+
const jumpUrl = res.loginErrorInfo?.jumpUrl || '';
337+
const sig = res.loginErrorInfo?.newDevicePullQrCodeSig || '';
338+
resolve({ result: false, message: '异常设备需要验证', needNewDevice: true, jumpUrl, newDevicePullQrCodeSig: sig });
339+
} else if (res.result !== '0') {
340+
const errMsg = res.loginErrorInfo?.errMsg || '新设备验证登录失败';
341+
WebUiDataRuntime.setQQLoginError(errMsg);
342+
resolve({ result: false, message: errMsg });
343+
} else {
344+
loginContext.isLogined = true;
345+
WebUiDataRuntime.setQQLoginStatus(true);
346+
WebUiDataRuntime.setQQLoginError('');
347+
resolve({ result: true, message: '' });
348+
}
349+
}).catch((e) => {
350+
logger.logError('[NapCat] [Framework] 新设备验证登录异常:', e);
351+
WebUiDataRuntime.setQQLoginError('新设备验证登录发生错误');
352+
resolve({ result: false, message: '新设备验证登录发生错误' });
353+
});
354+
});
355+
});
356+
}
357+
150358
export class NapCatFramework {
151359
public core: NapCatCore;
152360
context: InstanceContext;

0 commit comments

Comments
 (0)