Skip to content

Commit 9cab918

Browse files
committed
feat(core-editor): refactor VLESS inbound flow validation logic
1 parent 590cf4f commit 9cab918

1 file changed

Lines changed: 58 additions & 12 deletions

File tree

dashboard/src/features/core-editor/components/xray/xray-inbounds-section.tsx

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { TcpHeaderObfuscationForm } from '@/features/core-editor/components/shar
2020
import { pruneSockoptObject, XrayStreamSockoptInboundAccordion } from '@/features/core-editor/components/shared/xray-stream-sockopt-editor'
2121
import { InboundTlsFallbacksEditor } from '@/features/core-editor/components/xray/inbound-tls-fallbacks-editor'
2222
import { getInboundSecuritySelectOptions, getInboundTransportSelectOptions, transportCompatibleWithReality } from '@/features/core-editor/kit/inbound-form-options'
23-
import { outboundVlessVisionFlowAllowed, vlessVisionFlowIncompatibleWithStreamSecurity, VLESS_VISION_FLOW_VALUES } from '@/features/core-editor/kit/outbound-stream-dynamic'
2423
import { inferParityFieldMode, outboundSettingToString, parseOutboundSettingValue } from '@/features/core-editor/kit/xray-parity-value'
2524
import { useSectionHeaderAddPulseEffect, type SectionHeaderAddPulse } from '@/features/core-editor/hooks/use-section-header-add-pulse'
2625
import { useXrayPersistModifyGuard } from '@/features/core-editor/hooks/use-xray-persist-modify-guard'
@@ -222,8 +221,10 @@ function getInboundSecurityRecord(inbound: Inbound): Record<string, unknown> | n
222221
return inbound.security as unknown as Record<string, unknown>
223222
}
224223

224+
const VLESS_INBOUND_FLOW_VALUES = ['xtls-rprx-vision'] as const
225+
225226
/** TLS/REALITY on inbound, else form `security` (Flow sits above inbound.security in the grid until moved). */
226-
function effectiveSecurityTypeForVlessVisionFlow(inbound: Inbound | undefined, formSecurity: unknown): string {
227+
function effectiveSecurityTypeForVlessInboundFlow(inbound: Inbound | undefined, formSecurity: unknown): string {
227228
if (inbound && 'security' in inbound && inbound.security && typeof inbound.security === 'object') {
228229
const ibType = String((inbound.security as { type?: unknown }).type ?? '')
229230
.trim()
@@ -235,6 +236,33 @@ function effectiveSecurityTypeForVlessVisionFlow(inbound: Inbound | undefined, f
235236
return 'none'
236237
}
237238

239+
function effectiveTransportTypeForVlessInboundFlow(inbound: Inbound | undefined): string {
240+
if (!inbound || !('transport' in inbound) || !inbound.transport) return 'tcp'
241+
return String((inbound.transport as { type?: unknown }).type ?? 'tcp')
242+
.trim()
243+
.toLowerCase() || 'tcp'
244+
}
245+
246+
function vlessInboundEncryptionEnabled(raw: unknown): boolean {
247+
if (typeof raw !== 'string') return false
248+
const v = raw.trim().toLowerCase()
249+
return v !== '' && v !== 'none'
250+
}
251+
252+
function vlessInboundFlowAllowed(input: { securityType: string | undefined; transportType: string | undefined; encryption: unknown }): boolean {
253+
if (vlessInboundEncryptionEnabled(input.encryption)) return true
254+
const security = String(input.securityType ?? 'none').trim().toLowerCase()
255+
const transport = String(input.transportType ?? 'tcp').trim().toLowerCase() || 'tcp'
256+
return transport === 'tcp' && (security === 'tls' || security === 'reality')
257+
}
258+
259+
function vlessInboundFlowIncompatible(input: { securityType: string | undefined; transportType: string | undefined; encryption: unknown; flow: string | undefined }): boolean {
260+
const flow = String(input.flow ?? '').trim()
261+
if (!flow) return false
262+
if (!(VLESS_INBOUND_FLOW_VALUES as readonly string[]).includes(flow)) return true
263+
return !vlessInboundFlowAllowed(input)
264+
}
265+
238266
interface TlsCertificateUiItem {
239267
mode: 'path' | 'content'
240268
certificateFile: string
@@ -1071,8 +1099,18 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
10711099
const inboundSecurityType = useMemo(() => (typeof inboundSecurity?.type === 'string' ? inboundSecurity.type : undefined), [inboundSecurity])
10721100
const vlessStoredFlow = useMemo(() => (inbound && inbound.protocol === 'vless' ? vlessInboundFlowForForm(inbound) : ''), [inbound])
10731101
const watchedInboundSecurity = form.watch('security')
1074-
const securityForVlessVisionFlow = useMemo(() => effectiveSecurityTypeForVlessVisionFlow(inbound, watchedInboundSecurity), [inbound, watchedInboundSecurity])
1075-
const vlessInboundVisionFlowsOk = useMemo(() => outboundVlessVisionFlowAllowed(securityForVlessVisionFlow), [securityForVlessVisionFlow])
1102+
const watchedVlessEncryption = form.watch('encryption')
1103+
const securityForVlessInboundFlow = useMemo(() => effectiveSecurityTypeForVlessInboundFlow(inbound, watchedInboundSecurity), [inbound, watchedInboundSecurity])
1104+
const transportForVlessInboundFlow = useMemo(() => effectiveTransportTypeForVlessInboundFlow(inbound), [inbound])
1105+
const vlessInboundFlowsOk = useMemo(
1106+
() =>
1107+
vlessInboundFlowAllowed({
1108+
securityType: securityForVlessInboundFlow,
1109+
transportType: transportForVlessInboundFlow,
1110+
encryption: watchedVlessEncryption,
1111+
}),
1112+
[securityForVlessInboundFlow, transportForVlessInboundFlow, watchedVlessEncryption],
1113+
)
10761114
const tlsCertificates = useMemo(
10771115
() => (inboundSecurityType === 'tls' ? tlsCertificatesForUi((inboundSecurity as Record<string, unknown> | null)?.certificates) : []),
10781116
[inboundSecurityType, inboundSecurity],
@@ -1637,8 +1675,16 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
16371675

16381676
useEffect(() => {
16391677
if (!detailOpen || inbound?.protocol !== 'vless') return
1640-
const sec = effectiveSecurityTypeForVlessVisionFlow(inbound, form.getValues('security'))
1641-
if (!vlessVisionFlowIncompatibleWithStreamSecurity(sec, vlessStoredFlow)) return
1678+
if (
1679+
!vlessInboundFlowIncompatible({
1680+
securityType: effectiveSecurityTypeForVlessInboundFlow(inbound, form.getValues('security')),
1681+
transportType: effectiveTransportTypeForVlessInboundFlow(inbound),
1682+
encryption: form.getValues('encryption'),
1683+
flow: vlessStoredFlow,
1684+
})
1685+
) {
1686+
return
1687+
}
16421688
form.setValue('vlessFlow', '')
16431689
patchInboundRef.current?.({ flow: '' } as Partial<Inbound>)
16441690
}, [detailOpen, inbound, vlessStoredFlow, form])
@@ -3275,7 +3321,7 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
32753321
<FormLabel className="text-muted-foreground text-xs font-semibold tracking-wide">{t('coreEditor.field.flow', { defaultValue: 'Flow' })}</FormLabel>
32763322
<Select
32773323
dir="ltr"
3278-
value={vlessInboundVisionFlowsOk || !(VLESS_VISION_FLOW_VALUES as readonly string[]).includes(flow) ? flow || '__none' : '__none'}
3324+
value={vlessInboundFlowsOk && (VLESS_INBOUND_FLOW_VALUES as readonly string[]).includes(flow) ? flow : '__none'}
32793325
onValueChange={v => {
32803326
const next = v === '__none' ? '' : v
32813327
field.onChange(next)
@@ -3289,20 +3335,20 @@ export function XrayInboundsSection({ headerAddPulse, headerAddEpoch }: XrayInbo
32893335
</SelectTrigger>
32903336
</FormControl>
32913337
<SelectContent>
3292-
<SelectItem value="__none">{t('coreEditor.inbound.vlessFlowDefault', { defaultValue: 'Default (none)' })}</SelectItem>
3293-
{vlessInboundVisionFlowsOk
3294-
? VLESS_VISION_FLOW_VALUES.map(f => (
3338+
<SelectItem value="__none">{t('coreEditor.inbound.vlessFlowDefault', { defaultValue: 'Default (standard TLS proxy)' })}</SelectItem>
3339+
{vlessInboundFlowsOk
3340+
? VLESS_INBOUND_FLOW_VALUES.map(f => (
32953341
<SelectItem key={f} value={f}>
32963342
{f}
32973343
</SelectItem>
32983344
))
32993345
: null}
33003346
</SelectContent>
33013347
</Select>
3302-
{!vlessInboundVisionFlowsOk ? (
3348+
{!vlessInboundFlowsOk ? (
33033349
<p className="text-muted-foreground text-xs">
33043350
{t('coreEditor.inbound.vlessFlowVisionRequiresTls', {
3305-
defaultValue: 'Vision flows require inbound security TLS or REALITY (not “none”).',
3351+
defaultValue: 'XTLS Vision requires TCP with TLS/REALITY, or VLESS Encryption.',
33063352
})}
33073353
</p>
33083354
) : null}

0 commit comments

Comments
 (0)