Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMHSanaei <ho3ein.sanaei@gmail.com>2026-01-18 19:13:34 +0300
committerMHSanaei <ho3ein.sanaei@gmail.com>2026-01-18 19:13:34 +0300
commit2a76cec804ca28e1f128f99ec9c3c5b48474053c (patch)
tree3a1b5bd941e42aa283f181e92c857869bc9e2014 /web/assets
parent88eab032be7197dd87d0b4c9864af4b6b4fd820b (diff)
Add Hysteria2 outbound protocol support
Introduces support for the Hysteria2 protocol in outbound settings, including model, parsing, and form UI integration. Adds Hysteria2-specific stream and protocol settings, updates protocol selection, and enables configuration of Hysteria2 parameters in the outbound form.
Diffstat (limited to 'web/assets')
-rw-r--r--web/assets/js/model/outbound.js178
1 files changed, 175 insertions, 3 deletions
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 6fe34982..01f054ac 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -8,7 +8,8 @@ const Protocols = {
Shadowsocks: "shadowsocks",
Socks: "socks",
HTTP: "http",
- Wireguard: "wireguard"
+ Wireguard: "wireguard",
+ Hysteria: "hysteria"
};
const SSMethods = {
@@ -424,6 +425,86 @@ class RealityStreamSettings extends CommonClass {
};
}
};
+
+class HysteriaStreamSettings extends CommonClass {
+ constructor(
+ version = 2,
+ auth = '',
+ up = '0',
+ down = '0',
+ udphopPort = '',
+ udphopInterval = 30,
+ initStreamReceiveWindow = 8388608,
+ maxStreamReceiveWindow = 8388608,
+ initConnectionReceiveWindow = 20971520,
+ maxConnectionReceiveWindow = 20971520,
+ maxIdleTimeout = 30,
+ keepAlivePeriod = 0,
+ disablePathMTUDiscovery = false
+ ) {
+ super();
+ this.version = version;
+ this.auth = auth;
+ this.up = up;
+ this.down = down;
+ this.udphopPort = udphopPort;
+ this.udphopInterval = udphopInterval;
+ this.initStreamReceiveWindow = initStreamReceiveWindow;
+ this.maxStreamReceiveWindow = maxStreamReceiveWindow;
+ this.initConnectionReceiveWindow = initConnectionReceiveWindow;
+ this.maxConnectionReceiveWindow = maxConnectionReceiveWindow;
+ this.maxIdleTimeout = maxIdleTimeout;
+ this.keepAlivePeriod = keepAlivePeriod;
+ this.disablePathMTUDiscovery = disablePathMTUDiscovery;
+ }
+
+ static fromJson(json = {}) {
+ let udphopPort = '';
+ let udphopInterval = 30;
+ if (json.udphop) {
+ udphopPort = json.udphop.port || '';
+ udphopInterval = json.udphop.interval || 30;
+ }
+ return new HysteriaStreamSettings(
+ json.version,
+ json.auth,
+ json.up,
+ json.down,
+ udphopPort,
+ udphopInterval,
+ json.initStreamReceiveWindow,
+ json.maxStreamReceiveWindow,
+ json.initConnectionReceiveWindow,
+ json.maxConnectionReceiveWindow,
+ json.maxIdleTimeout,
+ json.keepAlivePeriod,
+ json.disablePathMTUDiscovery
+ );
+ }
+
+ toJson() {
+ const result = {
+ version: this.version,
+ auth: this.auth,
+ up: this.up,
+ down: this.down,
+ initStreamReceiveWindow: this.initStreamReceiveWindow,
+ maxStreamReceiveWindow: this.maxStreamReceiveWindow,
+ initConnectionReceiveWindow: this.initConnectionReceiveWindow,
+ maxConnectionReceiveWindow: this.maxConnectionReceiveWindow,
+ maxIdleTimeout: this.maxIdleTimeout,
+ keepAlivePeriod: this.keepAlivePeriod,
+ disablePathMTUDiscovery: this.disablePathMTUDiscovery
+ };
+ if (this.udphopPort) {
+ result.udphop = {
+ port: this.udphopPort,
+ interval: this.udphopInterval
+ };
+ }
+ return result;
+ }
+};
class SockoptStreamSettings extends CommonClass {
constructor(
dialerProxy = "",
@@ -485,6 +566,7 @@ class StreamSettings extends CommonClass {
grpcSettings = new GrpcStreamSettings(),
httpupgradeSettings = new HttpUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
+ hysteriaSettings = new HysteriaStreamSettings(),
sockopt = undefined,
) {
super();
@@ -498,6 +580,7 @@ class StreamSettings extends CommonClass {
this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.xhttp = xhttpSettings;
+ this.hysteria = hysteriaSettings;
this.sockopt = sockopt;
}
@@ -529,6 +612,7 @@ class StreamSettings extends CommonClass {
GrpcStreamSettings.fromJson(json.grpcSettings),
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
xHTTPStreamSettings.fromJson(json.xhttpSettings),
+ HysteriaStreamSettings.fromJson(json.hysteriaSettings),
SockoptStreamSettings.fromJson(json.sockopt),
);
}
@@ -546,6 +630,7 @@ class StreamSettings extends CommonClass {
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
+ hysteriaSettings: network === 'hysteria' ? this.hysteria.toJson() : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
}
@@ -634,7 +719,7 @@ class Outbound extends CommonClass {
}
canEnableStream() {
- return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
+ return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.Hysteria].includes(this.protocol);
}
canEnableMux() {
@@ -673,7 +758,8 @@ class Outbound extends CommonClass {
Protocols.Trojan,
Protocols.Shadowsocks,
Protocols.Socks,
- Protocols.HTTP
+ Protocols.HTTP,
+ Protocols.Hysteria
].includes(this.protocol);
}
@@ -722,6 +808,9 @@ class Outbound extends CommonClass {
case Protocols.Trojan:
case 'ss':
return this.fromParamLink(link);
+ case 'hysteria2':
+ case Protocols.Hysteria:
+ return this.fromHysteriaLink(link);
default:
return null;
}
@@ -842,6 +931,61 @@ class Outbound extends CommonClass {
remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port;
return new Outbound(remark, protocol, settings, stream);
}
+
+ static fromHysteriaLink(link) {
+ // Parse hysteria2://password@address:port[?param1=value1&param2=value2...][#remarks]
+ const regex = /^hysteria2?:\/\/([^@]+)@([^:?#]+):(\d+)([^#]*)(#.*)?$/;
+ const match = link.match(regex);
+
+ if (!match) return null;
+
+ let [, password, address, port, params, hash] = match;
+ port = parseInt(port);
+
+ // Parse URL parameters if present
+ let urlParams = new URLSearchParams(params);
+
+ // Create stream settings with hysteria network
+ let stream = new StreamSettings('hysteria', 'none');
+
+ // Set hysteria stream settings
+ stream.hysteria.auth = password;
+ stream.hysteria.up = urlParams.get('up') ?? '0';
+ stream.hysteria.down = urlParams.get('down') ?? '0';
+ stream.hysteria.udphopPort = urlParams.get('udphopPort') ?? '';
+ stream.hysteria.udphopInterval = parseInt(urlParams.get('udphopInterval') ?? '30');
+
+ // Optional QUIC parameters
+ if (urlParams.has('initStreamReceiveWindow')) {
+ stream.hysteria.initStreamReceiveWindow = parseInt(urlParams.get('initStreamReceiveWindow'));
+ }
+ if (urlParams.has('maxStreamReceiveWindow')) {
+ stream.hysteria.maxStreamReceiveWindow = parseInt(urlParams.get('maxStreamReceiveWindow'));
+ }
+ if (urlParams.has('initConnectionReceiveWindow')) {
+ stream.hysteria.initConnectionReceiveWindow = parseInt(urlParams.get('initConnectionReceiveWindow'));
+ }
+ if (urlParams.has('maxConnectionReceiveWindow')) {
+ stream.hysteria.maxConnectionReceiveWindow = parseInt(urlParams.get('maxConnectionReceiveWindow'));
+ }
+ if (urlParams.has('maxIdleTimeout')) {
+ stream.hysteria.maxIdleTimeout = parseInt(urlParams.get('maxIdleTimeout'));
+ }
+ if (urlParams.has('keepAlivePeriod')) {
+ stream.hysteria.keepAlivePeriod = parseInt(urlParams.get('keepAlivePeriod'));
+ }
+ if (urlParams.has('disablePathMTUDiscovery')) {
+ stream.hysteria.disablePathMTUDiscovery = urlParams.get('disablePathMTUDiscovery') === 'true';
+ }
+
+ // Create settings
+ let settings = new Outbound.HysteriaSettings(address, port, 2);
+
+ // Extract remark from hash
+ let remark = hash ? decodeURIComponent(hash.substring(1)) : `out-hysteria-${port}`;
+
+ return new Outbound(remark, Protocols.Hysteria, settings, stream);
+ }
}
Outbound.Settings = class extends CommonClass {
@@ -862,6 +1006,7 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Socks: return new Outbound.SocksSettings();
case Protocols.HTTP: return new Outbound.HttpSettings();
case Protocols.Wireguard: return new Outbound.WireguardSettings();
+ case Protocols.Hysteria: return new Outbound.HysteriaSettings();
default: return null;
}
}
@@ -878,6 +1023,7 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
+ case Protocols.Hysteria: return Outbound.HysteriaSettings.fromJson(json);
default: return null;
}
}
@@ -1324,4 +1470,30 @@ Outbound.WireguardSettings.Peer = class extends CommonClass {
keepAlive: this.keepAlive ?? undefined,
};
}
+};
+
+Outbound.HysteriaSettings = class extends CommonClass {
+ constructor(address = '', port = 443, version = 2) {
+ super();
+ this.address = address;
+ this.port = port;
+ this.version = version;
+ }
+
+ static fromJson(json = {}) {
+ if (Object.keys(json).length === 0) return new Outbound.HysteriaSettings();
+ return new Outbound.HysteriaSettings(
+ json.address,
+ json.port,
+ json.version
+ );
+ }
+
+ toJson() {
+ return {
+ address: this.address,
+ port: this.port,
+ version: this.version
+ };
+ }
}; \ No newline at end of file