Hide TCP VLESS tab by default

This commit is contained in:
Haitao Pan 2026-04-24 10:00:46 +08:00
parent 97e7a4a3bb
commit 081bedd637
2 changed files with 92 additions and 2 deletions

View File

@ -33,15 +33,40 @@ export type VlessQrCopy = {
interface VlessQrCardProps { interface VlessQrCardProps {
uuid: string | null | undefined uuid: string | null | undefined
copy: VlessQrCopy copy: VlessQrCopy
defaultTransport?: VlessTransport
visibleTransports?: VlessTransport[]
}
const DEFAULT_TRANSPORT: VlessTransport = 'xhttp'
const DEFAULT_VISIBLE_TRANSPORTS: VlessTransport[] = ['xhttp']
function normalizeVisibleTransports(transports?: VlessTransport[]): VlessTransport[] {
const visible = (transports ?? DEFAULT_VISIBLE_TRANSPORTS).filter(
(transport, index, values) => values.indexOf(transport) === index,
)
return visible.length > 0 ? visible : DEFAULT_VISIBLE_TRANSPORTS
}
function resolveInitialTransport(defaultTransport: VlessTransport | undefined, visibleTransports: VlessTransport[]): VlessTransport {
if (defaultTransport && visibleTransports.includes(defaultTransport)) {
return defaultTransport
}
return visibleTransports[0] ?? DEFAULT_TRANSPORT
} }
export default function VlessQrCard({ export default function VlessQrCard({
uuid, uuid,
copy, copy,
defaultTransport,
visibleTransports,
}: VlessQrCardProps) { }: VlessQrCardProps) {
const { data: allNodes, error: nodesError } = useSWR<VlessNode[]>('user-center-agent-nodes', fetchAgentNodes) const { data: allNodes, error: nodesError } = useSWR<VlessNode[]>('user-center-agent-nodes', fetchAgentNodes)
const transportOptions = useMemo(() => normalizeVisibleTransports(visibleTransports), [visibleTransports])
const nodes = useMemo(() => { const nodes = useMemo(() => {
return (allNodes ?? []).filter((node) => { return (allNodes ?? []).filter((node) => {
const name = (node.name || '').toLowerCase() const name = (node.name || '').toLowerCase()
@ -53,7 +78,9 @@ export default function VlessQrCard({
}) })
}, [allNodes]) }, [allNodes])
const [selectedNode, setSelectedNode] = useState<VlessNode | null>(null) const [selectedNode, setSelectedNode] = useState<VlessNode | null>(null)
const [preferredTransport, setPreferredTransport] = useState<VlessTransport>('tcp') const [preferredTransport, setPreferredTransport] = useState<VlessTransport>(() =>
resolveInitialTransport(defaultTransport, transportOptions),
)
const [isSelectorOpen, setIsSelectorOpen] = useState(false) const [isSelectorOpen, setIsSelectorOpen] = useState(false)
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null) const [qrDataUrl, setQrDataUrl] = useState<string | null>(null)
@ -61,6 +88,16 @@ export default function VlessQrCard({
const [generationError, setGenerationError] = useState<string | null>(null) const [generationError, setGenerationError] = useState<string | null>(null)
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
useEffect(() => {
setPreferredTransport((currentTransport) => {
if (transportOptions.includes(currentTransport)) {
return currentTransport
}
return resolveInitialTransport(defaultTransport, transportOptions)
})
}, [defaultTransport, transportOptions])
const rawNode = useMemo(() => { const rawNode = useMemo(() => {
if (selectedNode) return selectedNode if (selectedNode) return selectedNode
@ -238,7 +275,7 @@ export default function VlessQrCard({
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
{(['tcp', 'xhttp'] as const).map((transport) => ( {transportOptions.map((transport) => (
<button <button
key={transport} key={transport}
type="button" type="button"

View File

@ -0,0 +1,53 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import VlessQrCard, { type VlessQrCopy } from '../VlessQrCard'
vi.mock('next/image', () => ({
default: () => null,
}))
vi.mock('qrcode', () => ({
toDataURL: vi.fn(() => Promise.resolve('data:image/png;base64,test')),
}))
vi.mock('swr', () => ({
default: vi.fn(() => ({
data: [
{
name: 'JP-XHTTP.SVC.PLUS',
address: 'jp-xhttp.svc.plus',
port: 443,
xhttp_port: 443,
tcp_port: 1443,
uri_scheme_xhttp: 'vless://${UUID}@${DOMAIN}:443?type=xhttp&path=${PATH}#${TAG}',
uri_scheme_tcp: 'vless://${UUID}@${DOMAIN}:1443?type=tcp&flow=${FLOW}#${TAG}',
},
],
error: undefined,
})),
}))
const copy: VlessQrCopy = {
label: 'VLESS QR',
description: 'Scan to import VLESS config.',
linkLabel: 'VLESS link',
linkHelper: 'Copy link helper',
copyLink: 'Copy link',
copied: 'Copied',
downloadQr: 'Download QR',
generating: 'Generating',
error: 'Failed to generate',
missingUuid: 'Missing UUID',
qrAlt: 'VLESS QR code',
}
describe('VlessQrCard', () => {
it('only renders the XHTTP transport tab by default', () => {
render(<VlessQrCard uuid="11111111-1111-4111-8111-111111111111" copy={copy} />)
expect(screen.getByRole('button', { name: /xhttp/i })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: /tcp/i })).not.toBeInTheDocument()
})
})