@@ -26,6 +26,49 @@ function isJsonValue(value: unknown): value is JsonValue {
2626 return Object . values ( value ) . every ( v => v === undefined || isJsonValue ( v ) )
2727}
2828
29+ function asRecord ( value : unknown ) : Record < string , unknown > | null {
30+ return isRecord ( value ) ? value : null
31+ }
32+
33+ function inboundSettingString ( rawInbound : unknown , key : string ) : string | undefined {
34+ const settings = asRecord ( asRecord ( rawInbound ) ?. settings )
35+ const value = settings ?. [ key ]
36+ return typeof value === 'string' ? value : undefined
37+ }
38+
39+ function patchVlessInboundEncryptionFromRaw ( profile : Profile , raw : unknown ) : Profile {
40+ if ( ! isRecord ( raw ) ) return profile
41+ const rawInbounds = raw . inbounds
42+ if ( ! Array . isArray ( rawInbounds ) ) return profile
43+
44+ const inbounds = profile . inbounds . map ( ( inbound , index ) => {
45+ if ( inbound . protocol !== 'vless' ) return inbound
46+ const rawInbound = rawInbounds [ index ]
47+ const encryption = inboundSettingString ( rawInbound , 'encryption' )
48+ if ( encryption === undefined ) return inbound
49+ return { ...inbound , encryption } as typeof inbound
50+ } )
51+
52+ return { ...profile , inbounds }
53+ }
54+
55+ function applyVlessInboundEncryptionToCompiledConfig ( profile : Profile , config : Record < string , unknown > ) : Record < string , unknown > {
56+ if ( ! Array . isArray ( config . inbounds ) ) return config
57+ const inbounds = config . inbounds . map ( ( compiledInbound , index ) => {
58+ if ( ! isRecord ( compiledInbound ) || compiledInbound . protocol !== 'vless' ) return compiledInbound
59+ const profileInbound = profile . inbounds ?. [ index ]
60+ if ( profileInbound ?. protocol !== 'vless' ) return compiledInbound
61+ const encryption = typeof profileInbound . encryption === 'string' ? profileInbound . encryption : undefined
62+ if ( encryption === undefined ) return compiledInbound
63+ const normalizedEncryption = encryption . trim ( )
64+ if ( normalizedEncryption === '' || normalizedEncryption === 'none' ) return compiledInbound
65+ const settings = isRecord ( compiledInbound . settings ) ? { ...compiledInbound . settings } : { }
66+ settings . encryption = encryption
67+ return { ...compiledInbound , settings }
68+ } )
69+ return { ...config , inbounds }
70+ }
71+
2972const UNMODELED_TOP_LEVEL_KEYS_TO_PRESERVE = [
3073 'policy' ,
3174 'api' ,
@@ -94,7 +137,7 @@ export type XrayPersistValidationResult =
94137export function importRawToProfile ( raw : unknown ) : { profile : Profile ; issues : Issue [ ] } {
95138 const imported = importXrayConfig ( raw )
96139 const profile = preserveUnmodeledTopLevelSections (
97- sanitizeProfileInbounds ( normalizeProfile ( imported . profile ) ) ,
140+ patchVlessInboundEncryptionFromRaw ( sanitizeProfileInbounds ( normalizeProfile ( imported . profile ) ) , raw ) ,
98141 raw ,
99142 )
100143
@@ -104,7 +147,10 @@ export function importRawToProfile(raw: unknown): { profile: Profile; issues: Is
104147export function profileToPersistedConfig ( profile : Profile ) : Record < string , unknown > {
105148 const prepared = prepareProfileForKit ( profile )
106149 const { config } = buildXrayConfig ( prepared , { mode : 'permissive' } )
107- const result = applyInboundSockoptToCompiledConfig ( prepared , config as Record < string , unknown > )
150+ const result = applyVlessInboundEncryptionToCompiledConfig (
151+ prepared ,
152+ applyInboundSockoptToCompiledConfig ( prepared , config as Record < string , unknown > ) ,
153+ )
108154
109155 return result
110156}
@@ -116,16 +162,19 @@ export function validateProfileForSave(profile: Profile) {
116162
117163/**
118164 * Persist validation: strict-mode Xray compile blockers from xray-config-kit plus core-kit checks on permissive JSON
119- * (inbound clients noise filtered out).
165+ * (inbound clients noise filtered out). Warnings and info-level issues do not block save.
120166 */
121167export function validateProfileForPersist ( profile : Profile ) : XrayPersistValidationResult {
122168 const strictBlockers = getXrayStrictCompileBlockers ( profile )
123169 const config = profileToPersistedConfig ( profile )
124170 const r = validateCoreConfig ( 'xray' , config )
125171 const coreKitIssues = r . ok ? [ ] : filterCoreKitIssuesHidingInboundClients ( [ ...r . issues ] )
126172
127- if ( strictBlockers . length > 0 || coreKitIssues . length > 0 ) {
128- return { ok : false , strictBlockers, coreKitIssues }
173+ const blockingStrict = strictBlockers . filter ( i => i . severity !== 'warning' && i . severity !== 'info' )
174+ const blockingCoreKit = coreKitIssues . filter ( i => i . severity !== 'warning' && i . severity !== 'info' )
175+
176+ if ( blockingStrict . length > 0 || blockingCoreKit . length > 0 ) {
177+ return { ok : false , strictBlockers : blockingStrict , coreKitIssues : blockingCoreKit }
129178 }
130179 return { ok : true , config }
131180}
0 commit comments