Expand multiDomain to externalProxy #1300

This commit is contained in:
Alireza Ahmadi 2023-12-08 18:45:21 +01:00
parent bcc897640e
commit 5fbf8f0d53
18 changed files with 452 additions and 365 deletions

View file

@ -53,6 +53,7 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream) json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
stream["security"] = masterStream["security"] stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"] stream["tlsSettings"] = masterStream["tlsSettings"]
stream["externalProxy"] = masterStream["externalProxy"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ") modifiedStream, _ := json.MarshalIndent(stream, "", " ")
inbound.StreamSettings = string(modifiedStream) inbound.StreamSettings = string(modifiedStream)
} }
@ -96,7 +97,14 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
SELECT DISTINCT inbounds.id
FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
WHERE
protocol in ('vmess','vless','trojan','shadowsocks')
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
)`, subId, true).Find(&inbounds).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -196,7 +204,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
var domains []interface{}
obj["tls"] = security obj["tls"] = security
if security == "tls" { if security == "tls" {
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
@ -208,24 +215,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
} }
obj["alpn"] = strings.Join(alpn, ",") obj["alpn"] = strings.Join(alpn, ",")
} }
tlsSettings, _ := searchKey(tlsSetting, "settings") if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
obj["sni"], _ = sniValue.(string) obj["sni"], _ = sniValue.(string)
} }
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
obj["fp"], _ = fpValue.(string) obj["fp"], _ = fpValue.(string)
} }
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
obj["allowInsecure"], _ = insecure.(bool) obj["allowInsecure"], _ = insecure.(bool)
} }
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
obj["add"] = serverName
} }
} }
@ -239,16 +240,30 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
} }
obj["id"] = clients[clientIndex].ID obj["id"] = clients[clientIndex].ID
if len(domains) > 0 { externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := "" links := ""
for index, d := range domains { for index, externalProxy := range externalProxies {
domain := d.(map[string]interface{}) ep, _ := externalProxy.(map[string]interface{})
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string)) newSecurity, _ := ep["forceTls"].(string)
obj["add"] = domain["domain"].(string) newObj := map[string]interface{}{}
for key, value := range obj {
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
newObj[key] = value
}
}
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
newObj["add"] = ep["dest"].(string)
newObj["port"] = int(ep["port"].(float64))
if newSecurity != "same" {
newObj["tls"] = newSecurity
}
if index > 0 { if index > 0 {
links += "\n" links += "\n"
} }
jsonStr, _ := json.MarshalIndent(obj, "", " ") jsonStr, _ := json.MarshalIndent(newObj, "", " ")
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr) links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
} }
return links return links
@ -323,7 +338,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
var domains []interface{}
if security == "tls" { if security == "tls" {
params["security"] = "tls" params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
@ -335,11 +349,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if len(alpn) > 0 { if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",") params["alpn"] = strings.Join(alpn, ",")
} }
tlsSettings, _ := searchKey(tlsSetting, "settings") if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string) params["sni"], _ = sniValue.(string)
} }
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string) params["fp"], _ = fpValue.(string)
} }
@ -348,19 +363,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
} }
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = clients[clientIndex].Flow
} }
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
} }
if security == "reality" { if security == "reality" {
@ -389,11 +396,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["spx"] = spx params["spx"] = spx
} }
} }
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
}
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
@ -412,7 +414,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if len(alpn) > 0 { if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",") params["alpn"] = strings.Join(alpn, ",")
} }
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
xtlsSettings, _ := searchKey(xtlsSetting, "settings") xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil { if xtlsSetting != nil {
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
@ -423,25 +427,55 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
} }
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = clients[clientIndex].Flow
} }
serverName, _ := xtlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
} }
if security != "tls" && security != "reality" && security != "xtls" { if security != "tls" && security != "reality" && security != "xtls" {
params["security"] = "none" params["security"] = "none"
} }
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = security
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
q.Add(k, v)
}
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port) link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
url, _ := url.Parse(link) url, _ := url.Parse(link)
q := url.Query() q := url.Query()
@ -453,20 +487,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
// Set the new query values on the URL // Set the new query values on the URL
url.RawQuery = q.Encode() url.RawQuery = q.Encode()
if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
url.Fragment = s.genRemark(inbound, email, "") url.Fragment = s.genRemark(inbound, email, "")
return url.String() return url.String()
} }
@ -534,7 +554,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
var domains []interface{}
if security == "tls" { if security == "tls" {
params["security"] = "tls" params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
@ -546,11 +565,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if len(alpn) > 0 { if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",") params["alpn"] = strings.Join(alpn, ",")
} }
tlsSettings, _ := searchKey(tlsSetting, "settings") if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
if tlsSetting != nil {
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string) params["sni"], _ = sniValue.(string)
} }
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string) params["fp"], _ = fpValue.(string)
} }
@ -559,14 +578,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
} }
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
domains, _ = domainSettings.([]interface{})
}
}
serverName, _ := tlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
} }
} }
@ -596,11 +607,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["spx"] = spx params["spx"] = spx
} }
} }
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
if sname, ok := serverName.(string); ok && len(sname) > 0 {
address = sname
}
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
@ -619,6 +625,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if len(alpn) > 0 { if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",") params["alpn"] = strings.Join(alpn, ",")
} }
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
xtlsSettings, _ := searchKey(xtlsSetting, "settings") xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil { if xtlsSetting != nil {
@ -630,25 +639,55 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
} }
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = clients[clientIndex].Flow
} }
serverName, _ := xtlsSetting["serverName"].(string)
if serverName != "" {
address = serverName
}
} }
if security != "tls" && security != "reality" && security != "xtls" { if security != "tls" && security != "reality" && security != "xtls" {
params["security"] = "none" params["security"] = "none"
} }
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = security
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
q.Add(k, v)
}
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port) link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
url, _ := url.Parse(link) url, _ := url.Parse(link)
@ -661,20 +700,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
// Set the new query values on the URL // Set the new query values on the URL
url.RawQuery = q.Encode() url.RawQuery = q.Encode()
if len(domains) > 0 {
links := ""
for index, d := range domains {
domain := d.(map[string]interface{})
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
url.Fragment = s.genRemark(inbound, email, "") url.Fragment = s.genRemark(inbound, email, "")
return url.String() return url.String()
} }
@ -744,10 +769,78 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
} }
} }
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
}
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password) encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
if method[0] == '2' { if method[0] == '2' {
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password) encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
} }
externalProxies, _ := stream["externalProxy"].([]interface{})
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
if newSecurity != "same" {
params["security"] = newSecurity
} else {
params["security"] = security
}
url, _ := url.Parse(link)
q := url.Query()
for k, v := range params {
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
q.Add(k, v)
}
}
// Set the new query values on the URL
url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
if index > 0 {
links += "\n"
}
links += url.String()
}
return links
}
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port) link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
url, _ := url.Parse(link) url, _ := url.Parse(link)
q := url.Query() q := url.Query()
@ -758,6 +851,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
// Set the new query values on the URL // Set the new query values on the URL
url.RawQuery = q.Encode() url.RawQuery = q.Encode()
url.Fragment = s.genRemark(inbound, email, "") url.Fragment = s.genRemark(inbound, email, "")
return url.String() return url.String()
} }

View file

@ -578,27 +578,21 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
}; };
TlsStreamSettings.Settings = class extends XrayCommonClass { TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) { constructor(allowInsecure = false, fingerprint = '') {
super(); super();
this.allowInsecure = allowInsecure; this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
this.serverName = serverName;
this.domains = domains;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new TlsStreamSettings.Settings( return new TlsStreamSettings.Settings(
json.allowInsecure, json.allowInsecure,
json.fingerprint, json.fingerprint,
json.serverName,
json.domains,
); );
} }
toJson() { toJson() {
return { return {
allowInsecure: this.allowInsecure, allowInsecure: this.allowInsecure,
fingerprint: this.fingerprint, fingerprint: this.fingerprint,
serverName: this.serverName,
domains: this.domains,
}; };
} }
}; };
@ -692,21 +686,18 @@ XtlsStreamSettings.Cert = class extends XrayCommonClass {
}; };
XtlsStreamSettings.Settings = class extends XrayCommonClass { XtlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, serverName = '') { constructor(allowInsecure = false) {
super(); super();
this.allowInsecure = allowInsecure; this.allowInsecure = allowInsecure;
this.serverName = serverName;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new XtlsStreamSettings.Settings( return new XtlsStreamSettings.Settings(
json.allowInsecure, json.allowInsecure,
json.servername,
); );
} }
toJson() { toJson() {
return { return {
allowInsecure: this.allowInsecure, allowInsecure: this.allowInsecure,
serverName: this.serverName,
}; };
} }
}; };
@ -773,18 +764,16 @@ class RealityStreamSettings extends XrayCommonClass {
} }
RealityStreamSettings.Settings = class extends XrayCommonClass { RealityStreamSettings.Settings = class extends XrayCommonClass {
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') { constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, spiderX= '/') {
super(); super();
this.publicKey = publicKey; this.publicKey = publicKey;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
this.serverName = serverName;
this.spiderX = spiderX; this.spiderX = spiderX;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new RealityStreamSettings.Settings( return new RealityStreamSettings.Settings(
json.publicKey, json.publicKey,
json.fingerprint, json.fingerprint,
json.serverName,
json.spiderX, json.spiderX,
); );
} }
@ -792,7 +781,6 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
return { return {
publicKey: this.publicKey, publicKey: this.publicKey,
fingerprint: this.fingerprint, fingerprint: this.fingerprint,
serverName: this.serverName,
spiderX: this.spiderX, spiderX: this.spiderX,
}; };
} }
@ -829,6 +817,7 @@ class SockoptStreamSettings extends XrayCommonClass {
class StreamSettings extends XrayCommonClass { class StreamSettings extends XrayCommonClass {
constructor(network='tcp', constructor(network='tcp',
security='none', security='none',
externalProxy = [],
tlsSettings=new TlsStreamSettings(), tlsSettings=new TlsStreamSettings(),
xtlsSettings=new XtlsStreamSettings(), xtlsSettings=new XtlsStreamSettings(),
realitySettings = new RealityStreamSettings(), realitySettings = new RealityStreamSettings(),
@ -843,6 +832,7 @@ class StreamSettings extends XrayCommonClass {
super(); super();
this.network = network; this.network = network;
this.security = security; this.security = security;
this.externalProxy = externalProxy;
this.tls = tlsSettings; this.tls = tlsSettings;
this.xtls = xtlsSettings; this.xtls = xtlsSettings;
this.reality = realitySettings; this.reality = realitySettings;
@ -901,10 +891,10 @@ class StreamSettings extends XrayCommonClass {
} }
static fromJson(json={}) { static fromJson(json={}) {
return new StreamSettings( return new StreamSettings(
json.network, json.network,
json.security, json.security,
json.externalProxy,
TlsStreamSettings.fromJson(json.tlsSettings), TlsStreamSettings.fromJson(json.tlsSettings),
XtlsStreamSettings.fromJson(json.xtlsSettings), XtlsStreamSettings.fromJson(json.xtlsSettings),
RealityStreamSettings.fromJson(json.realitySettings), RealityStreamSettings.fromJson(json.realitySettings),
@ -923,6 +913,7 @@ class StreamSettings extends XrayCommonClass {
return { return {
network: network, network: network,
security: this.security, security: this.security,
externalProxy: this.externalProxy,
tlsSettings: this.isTls ? this.tls.toJson() : undefined, tlsSettings: this.isTls ? this.tls.toJson() : undefined,
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined, xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
realitySettings: this.isReality ? this.reality.toJson() : undefined, realitySettings: this.isReality ? this.reality.toJson() : undefined,
@ -982,6 +973,16 @@ class Inbound extends XrayCommonClass {
return this.clientStats; return this.clientStats;
} }
get clients() {
switch (this.protocol) {
case Protocols.VMESS: return this.settings.vmesses;
case Protocols.VLESS: return this.settings.vlesses;
case Protocols.TROJAN: return this.settings.trojans;
case Protocols.SHADOWSOCKS: return this.isSSMultiUser ? this.settings.shadowsockses : null;
default: return null;
}
}
get protocol() { get protocol() {
return this._protocol; return this._protocol;
} }
@ -1132,26 +1133,8 @@ class Inbound extends XrayCommonClass {
} }
isExpiry(index) { isExpiry(index) {
switch (this.protocol) { let exp = this.clients[index].expiryTime;
case Protocols.VMESS: return exp > 0 ? exp < new Date().getTime() : false;
if(this.settings.vmesses[index].expiryTime > 0)
return this.settings.vmesses[index].expiryTime < new Date().getTime();
return false
case Protocols.VLESS:
if(this.settings.vlesses[index].expiryTime > 0)
return this.settings.vlesses[index].expiryTime < new Date().getTime();
return false
case Protocols.TROJAN:
if(this.settings.trojans[index].expiryTime > 0)
return this.settings.trojans[index].expiryTime < new Date().getTime();
return false
case Protocols.SHADOWSOCKS:
if(this.settings.shadowsockses.length > 0 && this.settings.shadowsockses[index].expiryTime > 0)
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
return false
default:
return false;
}
} }
canEnableTls() { canEnableTls() {
@ -1195,19 +1178,20 @@ class Inbound extends XrayCommonClass {
this.sniffing = new Sniffing(); this.sniffing = new Sniffing();
} }
genVmessLink(address='', remark='', clientIndex=0) { genVmessLink(address='', port=this.port, forceTls, remark='', clientId) {
if (this.protocol !== Protocols.VMESS) { if (this.protocol !== Protocols.VMESS) {
return ''; return '';
} }
const security = forceTls == 'same' ? this.stream.security : forceTls;
let obj = { let obj = {
v: '2', v: '2',
ps: remark, ps: remark,
add: address, add: address,
port: this.port, port: port,
id: this.settings.vmesses[clientIndex].id, id: clientId,
net: this.stream.network, net: this.stream.network,
type: 'none', type: 'none',
tls: this.stream.security, tls: security,
}; };
let network = this.stream.network; let network = this.stream.network;
if (network === 'tcp') { if (network === 'tcp') {
@ -1247,12 +1231,9 @@ class Inbound extends XrayCommonClass {
} }
} }
if (this.stream.security === 'tls') { if (security === 'tls') {
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
obj.add = this.stream.tls.server; obj.sni = this.stream.tls.server;
}
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
obj.sni = this.stream.tls.settings.serverName;
} }
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){ if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
obj.fp = this.stream.tls.settings.fingerprint; obj.fp = this.stream.tls.settings.fingerprint;
@ -1268,11 +1249,10 @@ class Inbound extends XrayCommonClass {
return 'vmess://' + base64(JSON.stringify(obj, null, 2)); return 'vmess://' + base64(JSON.stringify(obj, null, 2));
} }
genVLESSLink(address = '', remark='', clientIndex=0) { genVLESSLink(address = '', port=this.port, forceTls, remark='', clientId, flow) {
const settings = this.settings; const uuid = clientId;
const uuid = settings.vlesses[clientIndex].id;
const port = this.port;
const type = this.stream.network; const type = this.stream.network;
const security = forceTls == 'same' ? this.stream.security : forceTls;
const params = new Map(); const params = new Map();
params.set("type", this.stream.network); params.set("type", this.stream.network);
switch (type) { switch (type) {
@ -1323,58 +1303,51 @@ class Inbound extends XrayCommonClass {
break; break;
} }
if (this.tls) { if (security === 'tls') {
params.set("security", "tls"); params.set("security", "tls");
if (this.stream.isTls){
params.set("fp" , this.stream.tls.settings.fingerprint); params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){ if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)){ if (!ObjectUtil.isEmpty(this.stream.tls.server)){
address = this.stream.tls.server; params.set("sni", this.stream.tls.server);
} }
if (this.stream.tls.settings.serverName !== ''){ if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
params.set("sni", this.stream.tls.settings.serverName); params.set("flow", flow);
} }
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
} }
else if (this.xtls) { else if (security === 'xtls') {
params.set("security", "xtls"); params.set("security", "xtls");
params.set("alpn", this.stream.xtls.alpn); params.set("alpn", this.stream.xtls.alpn);
if(this.stream.xtls.settings.allowInsecure){ if(this.stream.xtls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.xtls.server)){ if (!ObjectUtil.isEmpty(this.stream.xtls.server)){
address = this.stream.xtls.server; params.set("sni", this.stream.xtls.server);
}
if (this.stream.xtls.settings.serverName !== ''){
params.set("sni", this.stream.xtls.settings.serverName);
} }
params.set("flow", this.settings.vlesses[clientIndex].flow); params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
else if (this.reality) { else if (security === 'reality') {
params.set("security", "reality"); params.set("security", "reality");
params.set("fp", this.stream.reality.settings.fingerprint);
params.set("pbk", this.stream.reality.settings.publicKey); params.set("pbk", this.stream.reality.settings.publicKey);
params.set("fp", this.stream.reality.settings.fingerprint);
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("sni", this.stream.reality.serverNames.split(",")[0]); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
} }
if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
params.set("flow", this.settings.vlesses[clientIndex].flow);
}
if (this.stream.reality.shortIds.length > 0) { if (this.stream.reality.shortIds.length > 0) {
params.set("sid", this.stream.reality.shortIds.split(",")[0]); params.set("sid", this.stream.reality.shortIds.split(",")[0]);
} }
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
address = this.stream.reality.settings.serverName;
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) { if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
params.set("spx", this.stream.reality.settings.spiderX); params.set("spx", this.stream.reality.settings.spiderX);
} }
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
params.set("flow", flow);
}
} }
else { else {
@ -1390,10 +1363,10 @@ class Inbound extends XrayCommonClass {
return url.toString(); return url.toString();
} }
genSSLink(address='', remark='', clientIndex = 0) { genSSLink(address='', port=this.port, forceTls, remark='', clientPassword) {
let settings = this.settings; let settings = this.settings;
const port = this.port;
const type = this.stream.network; const type = this.stream.network;
const security = forceTls == 'same' ? this.stream.security : forceTls;
const params = new Map(); const params = new Map();
params.set("type", this.stream.network); params.set("type", this.stream.network);
switch (type) { switch (type) {
@ -1444,11 +1417,26 @@ class Inbound extends XrayCommonClass {
break; break;
} }
if (security === 'tls') {
params.set("security", "tls");
if (this.stream.isTls){
params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)){
params.set("sni", this.stream.tls.server);
}
}
}
let password = new Array(); let password = new Array();
if (this.isSS2022) password.push(settings.password); if (this.isSS2022) password.push(settings.password);
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password); if (this.isSSMultiUser) password.push(clientPassword);
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`; let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`;
const url = new URL(link); const url = new URL(link);
for (const [key, value] of params) { for (const [key, value] of params) {
url.searchParams.set(key, value) url.searchParams.set(key, value)
@ -1457,9 +1445,8 @@ class Inbound extends XrayCommonClass {
return url.toString(); return url.toString();
} }
genTrojanLink(address = '', remark = '', clientIndex = 0) { genTrojanLink(address = '', port=this.port, forceTls, remark = '', clientPassword) {
let settings = this.settings; const security = forceTls == 'same' ? this.stream.security : forceTls;
const port = this.port;
const type = this.stream.network; const type = this.stream.network;
const params = new Map(); const params = new Map();
params.set("type", this.stream.network); params.set("type", this.stream.network);
@ -1511,48 +1498,41 @@ class Inbound extends XrayCommonClass {
break; break;
} }
if (this.tls) { if (security === 'tls') {
params.set("security", "tls"); params.set("security", "tls");
if (this.stream.isTls){
params.set("fp" , this.stream.tls.settings.fingerprint); params.set("fp" , this.stream.tls.settings.fingerprint);
params.set("alpn", this.stream.tls.alpn); params.set("alpn", this.stream.tls.alpn);
if(this.stream.tls.settings.allowInsecure){ if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)){ if (!ObjectUtil.isEmpty(this.stream.tls.server)){
address = this.stream.tls.server; params.set("sni", this.stream.tls.server);
} }
if (this.stream.tls.settings.serverName !== ''){
params.set("sni", this.stream.tls.settings.serverName);
} }
} }
else if (this.reality) { else if (security === 'reality') {
params.set("security", "reality"); params.set("security", "reality");
params.set("fp", this.stream.reality.settings.fingerprint);
params.set("pbk", this.stream.reality.settings.publicKey); params.set("pbk", this.stream.reality.settings.publicKey);
params.set("fp", this.stream.reality.settings.fingerprint);
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("sni", this.stream.reality.serverNames.split(",")[0]); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
} }
if (this.stream.reality.shortIds.length > 0) { if (this.stream.reality.shortIds.length > 0) {
params.set("sid", this.stream.reality.shortIds.split(",")[0]); params.set("sid", this.stream.reality.shortIds.split(",")[0]);
} }
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
address = this.stream.reality.settings.serverName;
}
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) { if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
params.set("spx", this.stream.reality.settings.spiderX); params.set("spx", this.stream.reality.settings.spiderX);
} }
} }
else if (this.xtls) { else if (security === 'xtls') {
params.set("security", "xtls"); params.set("security", "xtls");
params.set("alpn", this.stream.xtls.alpn); params.set("alpn", this.stream.xtls.alpn);
if(this.stream.xtls.settings.allowInsecure){ if(this.stream.xtls.settings.allowInsecure){
params.set("allowInsecure", "1"); params.set("allowInsecure", "1");
} }
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
address = this.stream.xtls.server;
}
if (this.stream.xtls.settings.serverName !== ''){ if (this.stream.xtls.settings.serverName !== ''){
params.set("sni", this.stream.xtls.settings.serverName); params.set("sni", this.stream.xtls.settings.serverName);
} }
@ -1563,7 +1543,7 @@ class Inbound extends XrayCommonClass {
params.set("security", "none"); params.set("security", "none");
} }
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`; const link = `trojan://${clientPassword}@${address}:${port}`;
const url = new URL(link); const url = new URL(link);
for (const [key, value] of params) { for (const [key, value] of params) {
url.searchParams.set(key, value) url.searchParams.set(key, value)
@ -1572,38 +1552,55 @@ class Inbound extends XrayCommonClass {
return url.toString(); return url.toString();
} }
genLink(address='', remark='', clientIndex=0) { genLink(address='', port=this.port, forceTls='same', remark='', client) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
return this.genVmessLink(address, remark, clientIndex); return this.genVmessLink(address, port, forceTls, remark, client.id);
case Protocols.VLESS: case Protocols.VLESS:
return this.genVLESSLink(address, remark, clientIndex); return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow);
case Protocols.SHADOWSOCKS: case Protocols.SHADOWSOCKS:
return this.genSSLink(address, remark, clientIndex); return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : '');
case Protocols.TROJAN: case Protocols.TROJAN:
return this.genTrojanLink(address, remark, clientIndex); return this.genTrojanLink(address, port, forceTls, remark, client.password);
default: return ''; default: return '';
} }
} }
genInboundLinks(address = '', remark = '') { genAllLinks(remark='', client){
let link = ''; let result = [];
switch (this.protocol) { let email = client ? client.email : '';
case Protocols.VMESS: let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
case Protocols.VLESS: let port = this.port
case Protocols.TROJAN: if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
case Protocols.SHADOWSOCKS: let r = [remark, email].filter(x => x.length > 0).join('-');
JSON.parse(this.settings).clients.forEach((client,index) => { result.push({
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){ remark: r,
this.stream.tls.settings.domains.forEach((domain) => { link: this.genLink(addr, port, 'same', r, client)
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
}); });
} else { } else {
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n'; this.stream.externalProxy.forEach((ep) => {
} let r = [remark, email, ep.remark].filter(x => x.length > 0).join('-')
result.push({
remark: r,
link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client)
}); });
return link; });
default: return ''; }
return result;
}
genInboundLinks(remark = '') {
if(this.clients){
let links = [];
this.clients.forEach((client) => {
genAllLinks(remark,client).forEach(l => {
links.push(l.link);
})
});
return links.join('\r\n');
} else {
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, remark);
return '';
} }
} }

View file

@ -22,39 +22,25 @@
const qrModal = { const qrModal = {
title: '', title: '',
clientIndex: 0,
inbound: new Inbound(),
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
client: null, client: null,
qrcodes: [], qrcodes: [],
clipboard: null, clipboard: null,
visible: false, visible: false,
subId: '', subId: '',
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) { show: function (title = '', dbInbound, client) {
this.title = title; this.title = title;
this.clientIndex = clientIndex;
this.dbInbound = dbInbound; this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
settings = JSON.parse(this.inbound.settings); this.client = client;
this.client = settings.clients[clientIndex];
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
address = this.dbInbound.address;
this.subId = ''; this.subId = '';
this.qrcodes = []; this.qrcodes = [];
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) { this.inbound.genAllLinks(this.dbInbound.remark, client).forEach(l => {
this.inbound.stream.tls.settings.domains.forEach((domain) => {
remarkText = [remark, domain.remark].filter(Boolean).join('-');
this.qrcodes.push({ this.qrcodes.push({
remark: remarkText, remark: l.remark,
link: this.inbound.genLink(domain.domain, remarkText, clientIndex) link: l.link
}); });
}); });
} else {
this.qrcodes.push({
remark: remark,
link: this.inbound.genLink(address, remark, clientIndex)
});
}
this.visible = true; this.visible = true;
}, },
close: function () { close: function () {

View file

@ -38,7 +38,7 @@
this.isEdit = isEdit; this.isEdit = isEdit;
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings); this.clients = this.inbound.clients;
this.index = index === null ? this.clients.length : index; this.index = index === null ? this.clients.length : index;
this.delayedStart = false; this.delayedStart = false;
if (isEdit) { if (isEdit) {
@ -52,15 +52,6 @@
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email); this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
this.confirm = confirm; this.confirm = confirm;
}, },
getClients(protocol, clientSettings) {
switch (protocol) {
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
getClientId(protocol, client) { getClientId(protocol, client) {
switch (protocol) { switch (protocol) {
case Protocols.TROJAN: return client.password; case Protocols.TROJAN: return client.password;

View file

@ -96,6 +96,7 @@
<!-- stream settings --> <!-- stream settings -->
<template v-if="inbound.canEnableStream()"> <template v-if="inbound.canEnableStream()">
{{template "form/streamSettings"}} {{template "form/streamSettings"}}
{{template "form/externalProxy" }}
</template> </template>
<!-- tls settings --> <!-- tls settings -->

View file

@ -0,0 +1,32 @@
{{define "form/externalProxy"}}
<a-form layout="inline">
<a-divider style="margin:0;"></a-divider>
<a-form-item label="External Proxy">
<a-switch v-model="externalProxy"></a-switch>
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
</a-form-item>
<table width="100%" class="ant-table-tbody" v-if="externalProxy" style="margin-bottom:5px">
<tr style="line-height: 40px;">
<td width="100%">
<a-input-group style="margin: 0 5px;" compact v-for="(row, index) in inbound.stream.externalProxy">
<template>
<a-tooltip title="Force TLS">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
<a-select-option value="tls">TLS</a-select-option>
</a-select>
</a-tooltip>
</template>
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip>
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
</a-input-group>
</td>
</tr>
</table>
</a-form>
{{end}}

View file

@ -24,26 +24,6 @@
<!-- tls settings --> <!-- tls settings -->
<a-form v-if="inbound.tls" layout="inline"> <a-form v-if="inbound.tls" layout="inline">
<a-form-item label='Multi Domain'>
<a-switch v-model="multiDomain"></a-switch>
</a-form-item>
<a-form-item v-if="multiDomain">
<a-row>
<span>Domains:</span>
<a-button v-if="multiDomain" type="primary" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})" style="margin-left: 10px">+</a-button>
</a-row>
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
<template slot="addonAfter">
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item v-else label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
</a-form-item>
<a-form-item label="CipherSuites"> <a-form-item label="CipherSuites">
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">auto</a-select-option> <a-select-option value="">auto</a-select-option>
@ -61,7 +41,7 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input> <a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint" <a-select v-model="inbound.stream.tls.settings.fingerprint"
@ -122,11 +102,8 @@
<!-- xtls settings --> <!-- xtls settings -->
<a-form v-else-if="inbound.xtls" layout="inline"> <a-form v-else-if="inbound.xtls" layout="inline">
<a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
</a-form-item>
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.xtls.settings.serverName" style="width: 250px"></a-input> <a-input v-model.trim="inbound.stream.xtls.server" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Alpn"> <a-form-item label="Alpn">
<a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px"> <a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px">
@ -179,9 +156,6 @@
style="width: 135px" :dropdown-class-name="themeSwitcher.currentTheme"> style="width: 135px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item>
<a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Dest"> <a-form-item label="Dest">
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input> <a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>

View file

@ -265,27 +265,10 @@
this.index = index; this.index = index;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.dbInbound = new DBInbound(dbInbound); this.dbInbound = new DBInbound(dbInbound);
this.settings = JSON.parse(this.inbound.settings); this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null; this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
this.isExpired = this.inbound.isExpiry(index); this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : []; this.links = this.inbound.genAllLinks(this.dbInbound.remark, this.clientSettings);
remark = [this.dbInbound.remark, ( this.clientSettings ? this.clientSettings.email : '')].filter(Boolean).join('-');
address = this.dbInbound.address;
this.links = [];
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
this.inbound.stream.tls.settings.domains.forEach((domain) => {
remarkText = [remark, domain.remark].filter(Boolean).join('-');
this.links.push({
remark: remarkText,
link: this.inbound.genLink(domain.domain, remarkText, index)
});
});
} else {
this.links.push({
remark: remark,
link: this.inbound.genLink(address, remark, index)
});
}
if (this.clientSettings) { if (this.clientSettings) {
if (this.clientSettings.subId) { if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId); this.subLink = this.genSubLink(this.clientSettings.subId);

View file

@ -43,15 +43,6 @@
loading(loading) { loading(loading) {
inModal.confirmLoading = loading; inModal.confirmLoading = loading;
}, },
getClients(protocol, clientSettings) {
switch (protocol) {
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
}; };
new Vue({ new Vue({
@ -70,7 +61,7 @@
return inModal.isEdit; return inModal.isEdit;
}, },
get client() { get client() {
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0]; return inModal.inbound.clients[0];
}, },
get delayedExpireDays() { get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
@ -78,16 +69,19 @@
set delayedExpireDays(days) { set delayedExpireDays(days) {
this.client.expiryTime = -86400000 * days; this.client.expiryTime = -86400000 * days;
}, },
get multiDomain() { get externalProxy() {
return this.inbound.stream.tls.settings.domains.length > 0; return this.inbound.stream.externalProxy.length > 0;
}, },
set multiDomain(value) { set externalProxy(value) {
if (value) { if (value) {
inModal.inbound.stream.tls.server = ""; inModal.inbound.stream.externalProxy = [{
inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }]; forceTls: "same",
dest: window.location.hostname,
port: inModal.inbound.port,
remark: ""
}];
} else { } else {
inModal.inbound.stream.tls.server = ""; inModal.inbound.stream.externalProxy = [];
inModal.inbound.stream.tls.settings.domains = [];
} }
} }
}, },

View file

@ -618,7 +618,7 @@
}, },
getClientCounts(dbInbound, inbound) { getClientCounts(dbInbound, inbound) {
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = []; let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [];
clients = this.getClients(dbInbound.protocol, inbound.settings); clients = inbound.clients;
clientStats = dbInbound.clientStats clientStats = dbInbound.clientStats
now = new Date().getTime() now = new Date().getTime()
if (clients) { if (clients) {
@ -968,15 +968,6 @@
this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`); this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
} }
}, },
getClients(protocol, clientSettings) {
switch (protocol) {
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
getClientId(protocol, client) { getClientId(protocol, client) {
switch (protocol) { switch (protocol) {
case Protocols.TROJAN: return client.password; case Protocols.TROJAN: return client.password;
@ -996,8 +987,9 @@
newDbInbound.listen = rootInbound.listen; newDbInbound.listen = rootInbound.listen;
newDbInbound.port = rootInbound.port; newDbInbound.port = rootInbound.port;
newInbound = newDbInbound.toInbound(); newInbound = newDbInbound.toInbound();
newInbound.stream.security = 'tls'; newInbound.stream.security = rootInbound.stream.security;
newInbound.stream.tls = rootInbound.stream.tls; newInbound.stream.tls = rootInbound.stream.tls;
newInbound.stream.externalProxy = rootInbound.stream.externalProxy;
newDbInbound.streamSettings = newInbound.stream.toString(); newDbInbound.streamSettings = newInbound.stream.toString();
} }
} }
@ -1005,17 +997,17 @@
}, },
showQrcode(dbInboundId, client) { showQrcode(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
inbound = dbInbound.toInbound();
clients = this.getClients(dbInbound.protocol, inbound.settings);
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, index); qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
}, },
showInfo(dbInboundId, client) { showInfo(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
index=0;
if (dbInbound.isMultiUser()){
inbound = dbInbound.toInbound(); inbound = dbInbound.toInbound();
clients = this.getClients(dbInbound.protocol, inbound.settings); clients = inbound.clients;
index = this.findIndexOfClient(dbInbound.protocol, clients, client); index = this.findIndexOfClient(dbInbound.protocol, clients, client);
}
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
infoModal.show(newDbInbound, index); infoModal.show(newDbInbound, index);
}, },
@ -1027,7 +1019,7 @@
this.loading() this.loading()
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
inbound = dbInbound.toInbound(); inbound = dbInbound.toInbound();
clients = this.getClients(dbInbound.protocol, inbound.settings); clients = inbound.clients;
index = this.findIndexOfClient(dbInbound.protocol, clients, client); index = this.findIndexOfClient(dbInbound.protocol, clients, client);
clients[index].enable = !clients[index].enable; clients[index].enable = !clients[index].enable;
clientId = this.getClientId(dbInbound.protocol, clients[index]); clientId = this.getClientId(dbInbound.protocol, clients[index]);
@ -1041,15 +1033,7 @@
} }
}, },
getInboundClients(dbInbound) { getInboundClients(dbInbound) {
if (dbInbound.protocol == Protocols.VLESS) { return dbInbound.toInbound().clients;
return dbInbound.toInbound().settings.vlesses;
} else if (dbInbound.protocol == Protocols.VMESS) {
return dbInbound.toInbound().settings.vmesses;
} else if (dbInbound.protocol == Protocols.TROJAN) {
return dbInbound.toInbound().settings.trojans;
} else if (dbInbound.protocol == Protocols.SHADOWSOCKS) {
return dbInbound.toInbound().settings.shadowsockses;
}
}, },
resetClientTraffic(client, dbInboundId, confirmation = true) { resetClientTraffic(client, dbInboundId, confirmation = true) {
if (confirmation){ if (confirmation){
@ -1179,11 +1163,11 @@
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark); txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
}, },
exportAllLinks() { exportAllLinks() {
let copyText = ''; let copyText = [];
for (const dbInbound of this.dbInbounds) { for (const dbInbound of this.dbInbounds) {
copyText += dbInbound.genInboundLinks; copyText.push(dbInbound.genInboundLinks);
} }
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds'); txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
}, },
async startDataRefreshLoop() { async startDataRefreshLoop() {
while (this.isRefreshEnabled) { while (this.isRefreshEnabled) {

View file

@ -1795,6 +1795,46 @@ func (s *InboundService) MigrationRequirements() {
// Remove orphaned traffics // Remove orphaned traffics
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{}) tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
// Migrate old MultiDomain to External Proxy
var externalProxy []struct {
Id int
Port int
StreamSettings []byte
}
err = tx.Raw(`select id, port, stream_settings
from inbounds
WHERE protocol in ('vmess','vless','trojan')
AND json_extract(stream_settings, '$.security') = 'tls'
AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`).Scan(&externalProxy).Error
if err != nil || len(externalProxy) == 0 {
return
}
for _, ep := range externalProxy {
var reverses interface{}
var stream map[string]interface{}
json.Unmarshal(ep.StreamSettings, &stream)
if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok {
if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok {
if domains, ok := settings["domains"].([]interface{}); ok {
for _, domain := range domains {
if domainMap, ok := domain.(map[string]interface{}); ok {
domainMap["forceTls"] = "same"
domainMap["port"] = ep.Port
domainMap["dest"] = domainMap["domain"].(string)
delete(domainMap, "domain")
}
}
}
reverses = settings["domains"]
delete(settings, "domains")
}
}
stream["externalProxy"] = reverses
newStream, _ := json.MarshalIndent(stream, " ", " ")
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
}
} }
func (s *InboundService) MigrateDB() { func (s *InboundService) MigrateDB() {

View file

@ -135,6 +135,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
inbound.Settings = string(modifiedSettings) inbound.Settings = string(modifiedSettings)
} }
if len(inbound.StreamSettings) > 0 {
// Unmarshal stream JSON // Unmarshal stream JSON
var stream map[string]interface{} var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream) json.Unmarshal([]byte(inbound.StreamSettings), &stream)
@ -148,6 +149,10 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
} else if ok2 { } else if ok2 {
delete(realitySettings, "settings") delete(realitySettings, "settings")
} }
}
delete(stream, "externalProxy")
newStream, err := json.MarshalIndent(stream, "", " ") newStream, err := json.MarshalIndent(stream, "", " ")
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -176,6 +176,7 @@
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )" "telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations" "subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
"info" = "Info" "info" = "Info"
"same" = "Same"
[pages.client] [pages.client]
"add" = "Add Client" "add" = "Add Client"

View file

@ -176,6 +176,7 @@
"telegramDesc" = "Utiliza el ID de Telegram sin @ o los IDs de chat (puedes obtenerlo aquí @userinfobot o usando el comando '/id' en el bot)." "telegramDesc" = "Utiliza el ID de Telegram sin @ o los IDs de chat (puedes obtenerlo aquí @userinfobot o usando el comando '/id' en el bot)."
"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones." "subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones."
"info" = "Info" "info" = "Info"
"same" = "misma"
[pages.client] [pages.client]
"add" = "Agregar Cliente" "add" = "Agregar Cliente"

View file

@ -176,6 +176,7 @@
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)" "telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)"
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید" "subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
"info" = "اطلاعات" "info" = "اطلاعات"
"same" = "همسان"
[pages.client] [pages.client]
"add" = "کاربر جدید" "add" = "کاربر جدید"

View file

@ -176,6 +176,7 @@
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)" "telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций" "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций"
"info" = "Информация" "info" = "Информация"
"same" = "Тот же"
[pages.client] [pages.client]
"add" = "Добавить пользователя" "add" = "Добавить пользователя"

View file

@ -176,6 +176,7 @@
"telegramDesc" = "Sử dụng Telegram ID mà không cần ký hiệu @ hoặc chat IDs (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)" "telegramDesc" = "Sử dụng Telegram ID mà không cần ký hiệu @ hoặc chat IDs (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)"
"subscriptionDesc" = "Bạn có thể tìm liên kết đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau" "subscriptionDesc" = "Bạn có thể tìm liên kết đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau"
"info" = "Thông tin" "info" = "Thông tin"
"same" = "Giống nhau"
[pages.client] [pages.client]
"add" = "Thêm Client" "add" = "Thêm Client"

View file

@ -176,6 +176,7 @@
"telegramDesc" = "使用 Telegram ID不包含 @ 符号或聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)" "telegramDesc" = "使用 Telegram ID不包含 @ 符号或聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称" "subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
"info" = "信息" "info" = "信息"
"same" = "相同"
[pages.client] [pages.client]
"add" = "添加客户端" "add" = "添加客户端"