mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-24 21:46:07 +00:00
fix(vless): scope testseed to xtls-rprx-vision flow
testseed is only meaningful for the exact xtls-rprx-vision flow, but the panel was emitting it for any non-empty flow (including the UDP variant) and keeping it on the inbound after the flow was cleared via the client modal. Tighten the gate end-to-end: - VLESSSettings.toJson (inbound + outbound) now only emits testseed when the flow is exactly xtls-rprx-vision and the array is 4 positive ints; default state is empty so unmodified inbounds omit the field entirely. - canEnableVisionSeed drops the udp443 variant per spec. - Form adds a tooltip + theme-aware help text and an inline error when the user partially fills the four inputs; submit is blocked in that state. Reset clears to empty (= use server defaults). - UpdateInboundClient strips a now-orphaned testseed when the spliced client no longer leaves any XRV flow in the inbound. - MigrationRequirements cleans up legacy rows where testseed lingered after flow changes or was saved for non-XRV flows by older versions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1763,12 +1763,13 @@ class Inbound extends XrayCommonClass {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vision seed applies only when vision flow is selected
|
||||
// Vision seed applies only when the XTLS Vision (TCP/TLS) flow is selected.
|
||||
// Excludes the UDP variant per spec.
|
||||
canEnableVisionSeed() {
|
||||
if (!this.canEnableTlsFlow()) return false;
|
||||
const clients = this.settings?.vlesses;
|
||||
if (!Array.isArray(clients)) return false;
|
||||
return clients.some(c => c?.flow === TLS_FLOW_CONTROL.VISION || c?.flow === TLS_FLOW_CONTROL.VISION_UDP443);
|
||||
return clients.some(c => c?.flow === TLS_FLOW_CONTROL.VISION);
|
||||
}
|
||||
|
||||
canEnableReality() {
|
||||
@@ -2543,7 +2544,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
encryption = "none",
|
||||
fallbacks = [],
|
||||
selectedAuth = undefined,
|
||||
testseed = [900, 500, 900, 256],
|
||||
testseed = [],
|
||||
) {
|
||||
super(protocol);
|
||||
this.vlesses = vlesses;
|
||||
@@ -2562,12 +2563,23 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
this.fallbacks.splice(index, 1);
|
||||
}
|
||||
|
||||
// Empty array means "use server defaults" (won't be sent).
|
||||
// Anything else must be exactly 4 positive integers.
|
||||
static isValidTestseed(arr) {
|
||||
if (!Array.isArray(arr) || arr.length === 0) return true;
|
||||
if (arr.length !== 4) return false;
|
||||
return arr.every(v => Number.isInteger(v) && v > 0);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
// Ensure testseed is always initialized as an array
|
||||
let testseed = [900, 500, 900, 256];
|
||||
if (json.testseed && Array.isArray(json.testseed) && json.testseed.length >= 4) {
|
||||
testseed = json.testseed;
|
||||
}
|
||||
// Preserve a saved testseed only if it's a valid 4-positive-int array; otherwise leave empty
|
||||
// so toJson omits it and the form falls back to placeholder defaults.
|
||||
const saved = json.testseed;
|
||||
const testseed = (Array.isArray(saved)
|
||||
&& saved.length === 4
|
||||
&& saved.every(v => Number.isInteger(v) && v > 0))
|
||||
? saved
|
||||
: [];
|
||||
|
||||
const obj = new Inbound.VLESSSettings(
|
||||
Protocols.VLESS,
|
||||
@@ -2576,7 +2588,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
json.encryption,
|
||||
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []),
|
||||
json.selectedAuth,
|
||||
testseed
|
||||
testseed,
|
||||
);
|
||||
return obj;
|
||||
}
|
||||
@@ -2602,9 +2614,14 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
json.selectedAuth = this.selectedAuth;
|
||||
}
|
||||
|
||||
// Only include testseed if at least one client has a flow set
|
||||
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
|
||||
if (hasFlow && this.testseed && this.testseed.length >= 4) {
|
||||
// testseed is only meaningful for the exact xtls-rprx-vision flow, and only when
|
||||
// the user supplied a complete 4-positive-int array. Otherwise omit and let the
|
||||
// backend fall back to its safe defaults.
|
||||
const hasVisionFlow = this.vlesses && this.vlesses.some(v => v.flow === TLS_FLOW_CONTROL.VISION);
|
||||
if (hasVisionFlow
|
||||
&& Array.isArray(this.testseed)
|
||||
&& this.testseed.length === 4
|
||||
&& this.testseed.every(v => Number.isInteger(v) && v > 0)) {
|
||||
json.testseed = this.testseed;
|
||||
}
|
||||
|
||||
|
||||
@@ -1139,11 +1139,11 @@ class Outbound extends CommonClass {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vision seed applies only when vision flow is selected
|
||||
// Vision seed applies only when the XTLS Vision (TCP/TLS) flow is selected.
|
||||
// Excludes the UDP variant per spec.
|
||||
canEnableVisionSeed() {
|
||||
if (!this.canEnableTlsFlow()) return false;
|
||||
const flow = this.settings?.flow;
|
||||
return flow === TLS_FLOW_CONTROL.VISION || flow === TLS_FLOW_CONTROL.VISION_UDP443;
|
||||
return this.settings?.flow === TLS_FLOW_CONTROL.VISION;
|
||||
}
|
||||
|
||||
canEnableReality() {
|
||||
@@ -1799,7 +1799,7 @@ Outbound.VmessSettings = class extends CommonClass {
|
||||
}
|
||||
};
|
||||
Outbound.VLESSSettings = class extends CommonClass {
|
||||
constructor(address, port, id, flow, encryption, reverseTag = '', reverseSniffing = new ReverseSniffing(), testpre = 0, testseed = [900, 500, 900, 256]) {
|
||||
constructor(address, port, id, flow, encryption, reverseTag = '', reverseSniffing = new ReverseSniffing(), testpre = 0, testseed = []) {
|
||||
super();
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
@@ -1814,6 +1814,12 @@ Outbound.VLESSSettings = class extends CommonClass {
|
||||
|
||||
static fromJson(json = {}) {
|
||||
if (ObjectUtil.isEmpty(json.address) || ObjectUtil.isEmpty(json.port)) return new Outbound.VLESSSettings();
|
||||
const saved = json.testseed;
|
||||
const testseed = (Array.isArray(saved)
|
||||
&& saved.length === 4
|
||||
&& saved.every(v => Number.isInteger(v) && v > 0))
|
||||
? saved
|
||||
: [];
|
||||
return new Outbound.VLESSSettings(
|
||||
json.address,
|
||||
json.port,
|
||||
@@ -1823,7 +1829,7 @@ Outbound.VLESSSettings = class extends CommonClass {
|
||||
json.reverse?.tag || '',
|
||||
ReverseSniffing.fromJson(json.reverse?.sniffing || {}),
|
||||
json.testpre || 0,
|
||||
json.testseed && json.testseed.length >= 4 ? json.testseed : [900, 500, 900, 256]
|
||||
testseed,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1843,12 +1849,14 @@ Outbound.VLESSSettings = class extends CommonClass {
|
||||
sniffing: JSON.stringify(reverseSniffing) === JSON.stringify(defaultReverseSniffing) ? {} : reverseSniffing,
|
||||
};
|
||||
}
|
||||
// Only include Vision settings when flow is set
|
||||
if (this.flow && this.flow !== '') {
|
||||
// Vision-specific knobs are only meaningful for the exact xtls-rprx-vision flow.
|
||||
if (this.flow === TLS_FLOW_CONTROL.VISION) {
|
||||
if (this.testpre > 0) {
|
||||
result.testpre = this.testpre;
|
||||
}
|
||||
if (this.testseed && this.testseed.length >= 4) {
|
||||
if (Array.isArray(this.testseed)
|
||||
&& this.testseed.length === 4
|
||||
&& this.testseed.every(v => Number.isInteger(v) && v > 0)) {
|
||||
result.testseed = this.testseed;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user