mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
feat(socks): add IsSocksLike helper, info-modal display, and tests
Second-pass on the SOCKS5 inbound scaffold (PR #4452). This commit ticks off two of the 'help wanted' items from the scaffold's TODO list and tightens the existing dispatcher so that adding a new protocol-without-link in the future is a one-line change instead of an audit through every switch. Backend (Go) ------------ * database/model/model.go: new IsSocksLike(p) helper that returns true for both 'socks' and 'mixed'. Mirrors the existing IsHysteria pattern (one helper, two underlying constants) so call sites don't have to re-list both protocols every time they need to treat 'this inbound speaks SOCKS5' uniformly. * database/model/model_test.go: - TestSocksProtocolConstant pins the wire value 'socks' so a future refactor can't silently rename it (which would orphan every stored inbound row). - TestIsSocksLike covers Socks, Mixed, every other declared protocol, the empty Protocol, and a wrong-case input. * sub/subService.go GetLink: - Replace bare string literals ('vmess', 'vless', …) with the typed model.* constants so a typo can't silently fall through. - Add an explicit link-less case for Socks/Mixed/HTTP/Tunnel/WireGuard with a comment explaining why we don't emit 'socks://…' URLs (follow-up #1 in the scaffold PR description). Frontend (JS/Vue) ----------------- * frontend/src/models/dbinbound.js: add 'isSocks' getter (pure SOCKS5 inbound) and 'isSocksLike' getter (Socks OR Mixed), matching the pattern used by isMixed/isHTTP/isWireguard above. * frontend/src/pages/inbounds/InboundInfoModal.vue: the existing 'Mixed' info block (auth/UDP/IP/accounts) now also renders for the new SOCKS protocol via isSocksLike, since Xray's mixed and socks inbounds accept the exact same settings keys. Without this, opening the info modal for a SOCKS inbound would show an empty body. Header comment updated to list SOCKS alongside Mixed/HTTP/Tunnel. Still outstanding from the scaffold's TODO list: - Xray runtime AddUser hooks (web/service/inbound.go) - Translations for the 'socks' label across all 13 locales - Routing UI protocol == socks helper
This commit is contained in:
parent
4f8b0b90c8
commit
b0d9fe156b
5 changed files with 83 additions and 8 deletions
|
|
@ -37,6 +37,18 @@ func IsHysteria(p Protocol) bool {
|
||||||
return p == Hysteria || p == Hysteria2
|
return p == Hysteria || p == Hysteria2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSocksLike returns true for both the dedicated "socks" inbound and
|
||||||
|
// the combined "mixed" inbound, since Mixed exposes SOCKS5 alongside
|
||||||
|
// HTTP on the same port and accepts the exact same settings shape
|
||||||
|
// (auth/accounts/udp/ip) that the pure Socks inbound does.
|
||||||
|
//
|
||||||
|
// Use this helper anywhere routing, sub-link generation, or UI code
|
||||||
|
// needs to treat "this inbound speaks SOCKS5" uniformly without
|
||||||
|
// re-listing both constants at every call site.
|
||||||
|
func IsSocksLike(p Protocol) bool {
|
||||||
|
return p == Socks || p == Mixed
|
||||||
|
}
|
||||||
|
|
||||||
// User represents a user account in the 3x-ui panel.
|
// User represents a user account in the 3x-ui panel.
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,42 @@ func TestIsHysteria(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSocksProtocolConstant pins the wire value of the SOCKS5 protocol
|
||||||
|
// constant. It must stay "socks" because that's the literal Xray expects
|
||||||
|
// in inbound.protocol JSON (see https://xtls.github.io/config/inbounds/socks.html);
|
||||||
|
// changing it would silently break every stored inbound row.
|
||||||
|
func TestSocksProtocolConstant(t *testing.T) {
|
||||||
|
if got, want := string(Socks), "socks"; got != want {
|
||||||
|
t.Errorf("Socks protocol constant = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if Socks == Mixed {
|
||||||
|
t.Error("Socks and Mixed must be distinct protocols")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsSocksLike(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
in Protocol
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{Socks, true},
|
||||||
|
{Mixed, true},
|
||||||
|
{HTTP, false},
|
||||||
|
{VLESS, false},
|
||||||
|
{VMESS, false},
|
||||||
|
{Trojan, false},
|
||||||
|
{Shadowsocks, false},
|
||||||
|
{WireGuard, false},
|
||||||
|
{Hysteria, false},
|
||||||
|
{Hysteria2, false},
|
||||||
|
{Tunnel, false},
|
||||||
|
{Protocol(""), false},
|
||||||
|
{Protocol("SOCKS"), false}, // case-sensitive: must match the stored lowercase value
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := IsSocksLike(c.in); got != c.want {
|
||||||
|
t.Errorf("IsSocksLike(%q) = %v, want %v", c.in, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,20 @@ export class DBInbound {
|
||||||
return this.protocol === Protocols.MIXED;
|
return this.protocol === Protocols.MIXED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure SOCKS5 inbound (Xray "socks" protocol, RFC 1928). Distinct
|
||||||
|
// from `isMixed`, which is HTTP+SOCKS on the same port. Use
|
||||||
|
// `isSocksLike` when you want either one.
|
||||||
|
get isSocks() {
|
||||||
|
return this.protocol === Protocols.SOCKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True for both the dedicated SOCKS5 inbound and the combined
|
||||||
|
// Mixed inbound, since both speak SOCKS5 and share the same
|
||||||
|
// settings shape (auth/accounts/udp/ip).
|
||||||
|
get isSocksLike() {
|
||||||
|
return this.protocol === Protocols.SOCKS || this.protocol === Protocols.MIXED;
|
||||||
|
}
|
||||||
|
|
||||||
get isHTTP() {
|
get isHTTP() {
|
||||||
return this.protocol === Protocols.HTTP;
|
return this.protocol === Protocols.HTTP;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const { datepicker } = useDatepicker();
|
||||||
// client row + share links
|
// client row + share links
|
||||||
// • SS single-user → connection details + share link
|
// • SS single-user → connection details + share link
|
||||||
// • WireGuard → secret/peers + per-peer config download
|
// • WireGuard → secret/peers + per-peer config download
|
||||||
// • Mixed/HTTP/Tunnel → connection details only
|
// • Mixed/SOCKS/HTTP/Tunnel → connection details only
|
||||||
//
|
//
|
||||||
// We display links via QrPanel — each link gets its own QR + copy +
|
// We display links via QrPanel — each link gets its own QR + copy +
|
||||||
// (for WireGuard configs) download button.
|
// (for WireGuard configs) download button.
|
||||||
|
|
@ -640,8 +640,9 @@ const showSubscriptionTab = computed(
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<!-- Mixed -->
|
<!-- Mixed / SOCKS: share the same auth/udp/ip + accounts shape
|
||||||
<dl v-if="dbInbound.isMixed" class="info-list info-list-block">
|
(Xray's mixed and socks inbounds accept the same keys). -->
|
||||||
|
<dl v-if="dbInbound.isSocksLike" class="info-list info-list-block">
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<dt>Auth</dt>
|
<dt>Auth</dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|
|
||||||
|
|
@ -209,18 +209,27 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
||||||
// pair. Returns "" when the inbound's protocol doesn't produce a subscription URL
|
// pair. Returns "" when the inbound's protocol doesn't produce a subscription URL
|
||||||
// (socks, http, mixed, wireguard, dokodemo, tunnel). The returned string may
|
// (socks, http, mixed, wireguard, dokodemo, tunnel). The returned string may
|
||||||
// contain multiple `\n`-separated URLs when the inbound has externalProxy set.
|
// contain multiple `\n`-separated URLs when the inbound has externalProxy set.
|
||||||
|
//
|
||||||
|
// SOCKS5 and Mixed are deliberately link-less here. A `socks://user:pass@host:port`
|
||||||
|
// form exists in some client ecosystems but is not standardised across the
|
||||||
|
// xray/v2ray clients we target, and emitting one would mislead users into
|
||||||
|
// pasting it into clients that silently ignore it. Tracked as follow-up #1
|
||||||
|
// in the SOCKS5 scaffold PR description.
|
||||||
func (s *SubService) GetLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) GetLink(inbound *model.Inbound, email string) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case model.VMESS:
|
||||||
return s.genVmessLink(inbound, email)
|
return s.genVmessLink(inbound, email)
|
||||||
case "vless":
|
case model.VLESS:
|
||||||
return s.genVlessLink(inbound, email)
|
return s.genVlessLink(inbound, email)
|
||||||
case "trojan":
|
case model.Trojan:
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email)
|
||||||
case "shadowsocks":
|
case model.Shadowsocks:
|
||||||
return s.genShadowsocksLink(inbound, email)
|
return s.genShadowsocksLink(inbound, email)
|
||||||
case "hysteria", "hysteria2":
|
case model.Hysteria, model.Hysteria2:
|
||||||
return s.genHysteriaLink(inbound, email)
|
return s.genHysteriaLink(inbound, email)
|
||||||
|
case model.Socks, model.Mixed, model.HTTP, model.Tunnel, model.WireGuard:
|
||||||
|
// Tunnel-style or proxy-listener protocols: no per-client URL.
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue