Improve new-device QR handling and bypass init

Refactor new-device QR flow and streamline bypass init:

- napcat-shell: stop verbose logging and removed check of enableAllBypasses return value; just invoke native enableAllBypasses when not disabled by env.
- backend (QQLogin): simplify extraction of tokens from jumpUrl (use sig and uin-token), return an error if missing, and send oidbRequest directly (removed nested try/catch and regex fallback).
- frontend (new_device_verify): accept result.str_url without requiring bytes_token and pass an empty string to polling when bytes_token is absent.
- frontend (password_login): change render order to show captcha modal before new-device verification UI.
- frontend (qq_manager): normalize GetNewDeviceQRCode response — derive bytes_token from str_url's str_url query param (base64) when bytes_token is missing, and preserve extra status/error fields in the returned object.

These changes improve robustness when OIDB responses omit bytes_token, reduce noisy logs, and ensure the UI and polling still function.
This commit is contained in:
手瓜一十雪
2026-02-21 13:24:56 +08:00
parent b71a4913eb
commit f9764c9559
5 changed files with 59 additions and 45 deletions

View File

@@ -66,10 +66,11 @@ const NewDeviceVerify: React.FC<NewDeviceVerifyProps> = ({
try {
const result = await QQManager.getNewDeviceQRCode(uin, jumpUrl);
if (!mountedRef.current) return;
if (result?.str_url && result?.bytes_token) {
if (result?.str_url) {
setQrUrl(result.str_url);
setStatus('waiting');
startPolling(result.bytes_token);
// bytes_token 用于轮询,如果 OIDB 未返回则用空字符串
startPolling(result.bytes_token || '');
} else {
setStatus('error');
setErrorMsg('获取二维码失败,请重试');

View File

@@ -52,14 +52,7 @@ const PasswordLogin: React.FC<PasswordLoginProps> = ({ onSubmit, onCaptchaSubmit
return (
<div className='flex flex-col gap-8'>
{newDeviceState?.needNewDevice && newDeviceState.jumpUrl ? (
<NewDeviceVerify
jumpUrl={newDeviceState.jumpUrl}
uin={newDeviceState.uin}
onVerified={(token) => onNewDeviceVerified?.(token)}
onCancel={onNewDeviceCancel}
/>
) : captchaState?.needCaptcha && captchaState.proofWaterUrl ? (
{captchaState?.needCaptcha && captchaState.proofWaterUrl ? (
<div className='flex flex-col gap-4 items-center'>
<p className='text-warning text-sm'></p>
<TencentCaptchaModal
@@ -78,6 +71,13 @@ const PasswordLogin: React.FC<PasswordLoginProps> = ({ onSubmit, onCaptchaSubmit
</Button>
</div>
) : newDeviceState?.needNewDevice && newDeviceState.jumpUrl ? (
<NewDeviceVerify
jumpUrl={newDeviceState.jumpUrl}
uin={newDeviceState.uin}
onVerified={(token) => onNewDeviceVerified?.(token)}
onCancel={onNewDeviceCancel}
/>
) : (
<>
<div className='flex justify-center'>

View File

@@ -141,11 +141,37 @@ export default class QQManager {
const data = await serverRequest.post<ServerResponse<{
str_url?: string;
bytes_token?: string;
uint32_guarantee_status?: number;
ActionStatus?: string;
ErrorCode?: number;
ErrorInfo?: string;
}>>('/QQLogin/GetNewDeviceQRCode', {
uin,
jumpUrl,
});
return data.data.data;
const result = data.data.data;
if (result?.str_url) {
let bytesToken = result.bytes_token || '';
if (!bytesToken && result.str_url) {
// 只对 str_url 参数值做 base64 编码
try {
const urlObj = new URL(result.str_url);
const strUrlParam = urlObj.searchParams.get('str_url') || '';
bytesToken = strUrlParam ? btoa(strUrlParam) : '';
} catch {
bytesToken = '';
}
}
return {
str_url: result.str_url,
bytes_token: bytesToken,
uint32_guarantee_status: result.uint32_guarantee_status,
ActionStatus: result.ActionStatus,
ErrorCode: result.ErrorCode,
ErrorInfo: result.ErrorInfo,
};
}
return result;
}
public static async pollNewDeviceQR (uin: string, bytesToken: string) {