diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index 7a0fae8a..a30cf81f 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -944,3 +944,10 @@ export function genWireguardConfigs(input: GenWireguardFanoutInput): string { })) .join('\r\n'); } + +export function isPostQuantumLink(link: string): boolean { + if (/[?&]pqv=/.test(link)) return true; + if (link.includes('mlkem768') || link.includes('mldsa65')) return true; + if (link.includes('ML-KEM-768')) return true; + return false; +} diff --git a/frontend/src/pages/clients/ClientInfoModal.tsx b/frontend/src/pages/clients/ClientInfoModal.tsx index fdd20b5a..b0049bef 100644 --- a/frontend/src/pages/clients/ClientInfoModal.tsx +++ b/frontend/src/pages/clients/ClientInfoModal.tsx @@ -6,6 +6,7 @@ import { CopyOutlined, QrcodeOutlined } from '@ant-design/icons'; import { ClipboardManager, HttpUtil, IntlUtil, SizeFormatter } from '@/utils'; import { useDatepicker } from '@/hooks/useDatepicker'; import type { ClientRecord, InboundOption } from '@/hooks/useClients'; +import { isPostQuantumLink } from '@/lib/xray/inbound-link'; import QrPanel from '@/pages/inbounds/QrPanel'; import './ClientInfoModal.css'; @@ -33,20 +34,6 @@ const INBOUND_PROTOCOL_COLORS: Record = { const INBOUND_CHIP_LIMIT = 1; -// Post-quantum keys blow up the encoded URL past what a single QR can -// hold. In VLESS share links the algorithm names don't appear as plain -// text — they ride inside query params: -// - mldsa65Verify becomes `pqv=` (sub/subService.go:841) -// - ML-KEM-768 becomes `encryption=mlkem768x25519plus.<...>` -// We also keep the literal substrings so configs that DO embed them -// directly (e.g. wireguard config text) still match. -function isPostQuantumLink(link: string): boolean { - if (/[?&]pqv=/.test(link)) return true; - if (link.includes('mlkem768') || link.includes('mldsa65')) return true; - if (link.includes('ML-KEM-768')) return true; - return false; -} - // 3x-ui's genRemark concatenates inbound remark + client email (and an // optional extra) using a configurable separator. The email half is // redundant in the row title — the modal already names the client by diff --git a/frontend/src/pages/clients/ClientQrModal.tsx b/frontend/src/pages/clients/ClientQrModal.tsx index 6263414a..14f2c950 100644 --- a/frontend/src/pages/clients/ClientQrModal.tsx +++ b/frontend/src/pages/clients/ClientQrModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Collapse, Modal, Spin } from 'antd'; import { HttpUtil } from '@/utils'; +import { isPostQuantumLink } from '@/lib/xray/inbound-link'; import QrPanel from '@/pages/inbounds/QrPanel'; import type { ClientRecord } from '@/hooks/useClients'; @@ -93,7 +94,13 @@ export default function ClientQrModal({ out.push({ key: `l${idx}`, label: `${t('pages.clients.link')} ${idx + 1}`, - children: , + children: ( + + ), }); }); return out; diff --git a/frontend/src/pages/inbounds/QrCodeModal.tsx b/frontend/src/pages/inbounds/QrCodeModal.tsx index edfb2e71..3894d543 100644 --- a/frontend/src/pages/inbounds/QrCodeModal.tsx +++ b/frontend/src/pages/inbounds/QrCodeModal.tsx @@ -8,6 +8,7 @@ import { genAllLinks, genWireguardConfigs, genWireguardLinks, + isPostQuantumLink, } from '@/lib/xray/inbound-link'; import { inboundFromDb, type DbInboundLike } from '@/lib/xray/inbound-from-db'; import QrPanel from './QrPanel'; @@ -140,7 +141,7 @@ export default function QrCodeModal({ value={item.value} remark={item.header} downloadName={item.downloadName || ''} - showQr={!item.value.includes('mldsa65') && !item.value.includes('ML-KEM-768')} + showQr={!isPostQuantumLink(item.value)} /> ), })), diff --git a/frontend/src/pages/sub/SubPage.tsx b/frontend/src/pages/sub/SubPage.tsx index c99b7cff..10ff9865 100644 --- a/frontend/src/pages/sub/SubPage.tsx +++ b/frontend/src/pages/sub/SubPage.tsx @@ -31,6 +31,7 @@ import { } from '@ant-design/icons'; import { ClipboardManager, IntlUtil, LanguageManager } from '@/utils'; +import { isPostQuantumLink } from '@/lib/xray/inbound-link'; import { setMessageInstance } from '@/utils/messageBus'; import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme'; import SubUsageSummary from './SubUsageSummary'; @@ -90,19 +91,6 @@ function trimEmail(remark: string, email: string): string { .trim(); } -// Post-quantum keys blow up the encoded URL past what a single QR can -// hold. The algorithm names don't appear as plain text in the URL — -// they ride inside query params: mldsa65Verify → `pqv=`, -// ML-KEM-768 → `encryption=mlkem768x25519plus.<...>`. The literal -// substrings are also matched in case a config (e.g. wireguard) embeds -// them directly. -function isPostQuantumLink(link: string): boolean { - if (/[?&]pqv=/.test(link)) return true; - if (link.includes('mlkem768') || link.includes('mldsa65')) return true; - if (link.includes('ML-KEM-768')) return true; - return false; -} - // Decode a base64 string as UTF-8. atob() returns a binary string where // each char holds one raw byte (Latin-1 interpretation), which mangles // any multi-byte UTF-8 sequence in the payload — most commonly the