3x-ui/sub/links.go
MHSanaei 32966c1257
feat(inbounds): native fallbacks on VLESS/Trojan TCP-TLS, with working child links
A VLESS or Trojan inbound on TCP with TLS or Reality can now act as a
fallback master: pick existing inbounds as children and the panel auto-
fills the SNI / ALPN / path / xver routing fields from each child's
transport, auto-builds settings.fallbacks at config-gen time, and
rewrites the child's client-share link so it advertises the master's
reachable endpoint and TLS state instead of the child's loopback listen.

Layout matches the Xray All-in-One Nginx example: master at :443 with
clients + TLS, each child on 127.0.0.1 with its own transport+clients.
Order matters (Xray walks fallbacks top-to-bottom) — reorder via the
per-row up/down arrows. Path / SNI / ALPN are exposed under a per-row
Edit toggle for the rare cases where the auto-derivation needs
overriding; otherwise just pick a child and you're done.

Backend: new InboundFallback table + FallbackService (GetByMaster /
SetByMaster / GetParentForChild / BuildFallbacksJSON); two routes
(GET / POST /panel/api/inbounds/:id/fallbacks); xray.GetXrayConfig
injects settings.fallbacks for any VLESS/Trojan TCP-TLS/Reality
inbound; GetInbounds annotates each child with FallbackParent so the
frontend can rewrite links without an extra round-trip.

Link projection covers every emission path — clients-page QR/links,
per-inbound Get URL, raw subscription, sub-JSON, sub-Clash, and the
inbounds-page link/info/QR — via a shared projectThroughFallbackMaster
on the backend and a shared projectChildThroughMaster on the frontend
that both handle the panel-tracked relationship and the legacy
unix-socket (@vless-ws) convention.

Strings translated into all 12 non-English locales.
2026-05-19 01:45:57 +02:00

60 lines
1.3 KiB
Go

package sub
import (
"strings"
"github.com/mhsanaei/3x-ui/v3/database/model"
"github.com/mhsanaei/3x-ui/v3/web/service"
)
type LinkProvider struct {
settingService service.SettingService
}
func NewLinkProvider() *LinkProvider {
return &LinkProvider{}
}
func (p *LinkProvider) build(host string) *SubService {
showInfo, _ := p.settingService.GetSubShowInfo()
rModel, err := p.settingService.GetRemarkModel()
if err != nil {
rModel = "-ieo"
}
svc := NewSubService(showInfo, rModel)
svc.PrepareForRequest(host)
return svc
}
func (p *LinkProvider) SubLinksForSubId(host, subId string) ([]string, error) {
svc := p.build(host)
links, _, _, err := svc.GetSubs(subId, host)
if err != nil {
return nil, err
}
out := make([]string, 0, len(links))
for _, l := range links {
out = append(out, splitLinkLines(l)...)
}
return out, nil
}
func (p *LinkProvider) LinksForClient(host string, inbound *model.Inbound, email string) []string {
svc := p.build(host)
svc.projectThroughFallbackMaster(inbound)
return splitLinkLines(svc.GetLink(inbound, email))
}
func splitLinkLines(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, "\n")
out := make([]string, 0, len(parts))
for _, p := range parts {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}