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:
MHSanaei
2026-05-07 14:44:33 +02:00
parent 3349dcbc13
commit 79a7e7a5b5
5 changed files with 173 additions and 102 deletions
+16 -8
View File
@@ -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;
}
}