@@ -20,7 +20,6 @@ import { TcpHeaderObfuscationForm } from '@/features/core-editor/components/shar
2020import { pruneSockoptObject , XrayStreamSockoptInboundAccordion } from '@/features/core-editor/components/shared/xray-stream-sockopt-editor'
2121import { InboundTlsFallbacksEditor } from '@/features/core-editor/components/xray/inbound-tls-fallbacks-editor'
2222import { 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'
2423import { inferParityFieldMode , outboundSettingToString , parseOutboundSettingValue } from '@/features/core-editor/kit/xray-parity-value'
2524import { useSectionHeaderAddPulseEffect , type SectionHeaderAddPulse } from '@/features/core-editor/hooks/use-section-header-add-pulse'
2625import { 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+
238266interface 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