From 19a31686da26c0c8a8f0ee255d3b0728524bb95f Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Wed, 30 Oct 2024 14:40:41 +0100 Subject: REALITY: SplitHTTP transport --- web/assets/js/model/inbound.js | 2531 +++++++++++++++++++++++++++++++++++++++ web/assets/js/model/outbound.js | 2 +- web/assets/js/model/xray.js | 2531 --------------------------------------- 3 files changed, 2532 insertions(+), 2532 deletions(-) create mode 100644 web/assets/js/model/inbound.js delete mode 100644 web/assets/js/model/xray.js (limited to 'web/assets') diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js new file mode 100644 index 00000000..ea6f56fa --- /dev/null +++ b/web/assets/js/model/inbound.js @@ -0,0 +1,2531 @@ +const Protocols = { + VMESS: 'vmess', + VLESS: 'vless', + TROJAN: 'trojan', + SHADOWSOCKS: 'shadowsocks', + DOKODEMO: 'dokodemo-door', + SOCKS: 'socks', + HTTP: 'http', + WIREGUARD: 'wireguard', +}; + +const SSMethods = { + AES_256_GCM: 'aes-256-gcm', + AES_128_GCM: 'aes-128-gcm', + CHACHA20_POLY1305: 'chacha20-poly1305', + CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305', + XCHACHA20_POLY1305: 'xchacha20-poly1305', + XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305', + BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm', + BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm', + BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305', +}; + +const TLS_FLOW_CONTROL = { + VISION: "xtls-rprx-vision", + VISION_UDP443: "xtls-rprx-vision-udp443", +}; + +const TLS_VERSION_OPTION = { + TLS10: "1.0", + TLS11: "1.1", + TLS12: "1.2", + TLS13: "1.3", +}; + +const TLS_CIPHER_OPTION = { + RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA", + RSA_AES_256_CBC: "TLS_RSA_WITH_AES_256_CBC_SHA", + RSA_AES_128_GCM: "TLS_RSA_WITH_AES_128_GCM_SHA256", + RSA_AES_256_GCM: "TLS_RSA_WITH_AES_256_GCM_SHA384", + AES_128_GCM: "TLS_AES_128_GCM_SHA256", + AES_256_GCM: "TLS_AES_256_GCM_SHA384", + CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256", + ECDHE_ECDSA_AES_128_CBC: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + ECDHE_ECDSA_AES_256_CBC: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + ECDHE_RSA_AES_128_CBC: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + ECDHE_RSA_AES_256_CBC: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + ECDHE_ECDSA_AES_128_GCM: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + ECDHE_ECDSA_AES_256_GCM: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + ECDHE_RSA_AES_128_GCM: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + ECDHE_RSA_AES_256_GCM: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + ECDHE_ECDSA_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + ECDHE_RSA_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", +}; + +const UTLS_FINGERPRINT = { + UTLS_CHROME: "chrome", + UTLS_FIREFOX: "firefox", + UTLS_SAFARI: "safari", + UTLS_IOS: "ios", + UTLS_android: "android", + UTLS_EDGE: "edge", + UTLS_360: "360", + UTLS_QQ: "qq", + UTLS_RANDOM: "random", + UTLS_RANDOMIZED: "randomized", +}; + +const ALPN_OPTION = { + H3: "h3", + H2: "h2", + HTTP1: "http/1.1", +}; + +const SNIFFING_OPTION = { + HTTP: "http", + TLS: "tls", + QUIC: "quic", + FAKEDNS: "fakedns" +}; + +const USAGE_OPTION = { + ENCIPHERMENT: "encipherment", + VERIFY: "verify", + ISSUE: "issue", +}; + +const DOMAIN_STRATEGY_OPTION = { + AS_IS: "AsIs", + USE_IP: "UseIP", + USE_IPV6V4: "UseIPv6v4", + USE_IPV6: "UseIPv6", + USE_IPV4V6: "UseIPv4v6", + USE_IPV4: "UseIPv4", + FORCE_IP: "ForceIP", + FORCE_IPV6V4: "ForceIPv6v4", + FORCE_IPV6: "ForceIPv6", + FORCE_IPV4V6: "ForceIPv4v6", + FORCE_IPV4: "ForceIPv4", +}; + +const TCP_CONGESTION_OPTION = { + BBR: "bbr", + CUBIC: "cubic", + RENO: "reno", +}; + +const USERS_SECURITY = { + AES_128_GCM: "aes-128-gcm", + CHACHA20_POLY1305: "chacha20-poly1305", + AUTO: "auto", + NONE: "none", + ZERO: "zero", +}; + +Object.freeze(Protocols); +Object.freeze(SSMethods); +Object.freeze(TLS_FLOW_CONTROL); +Object.freeze(TLS_VERSION_OPTION); +Object.freeze(TLS_CIPHER_OPTION); +Object.freeze(UTLS_FINGERPRINT); +Object.freeze(ALPN_OPTION); +Object.freeze(SNIFFING_OPTION); +Object.freeze(USAGE_OPTION); +Object.freeze(DOMAIN_STRATEGY_OPTION); +Object.freeze(TCP_CONGESTION_OPTION); +Object.freeze(USERS_SECURITY); + +class XrayCommonClass { + + static toJsonArray(arr) { + return arr.map(obj => obj.toJson()); + } + + static fromJson() { + return new XrayCommonClass(); + } + + toJson() { + return this; + } + + toString(format = true) { + return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson()); + } + + static toHeaders(v2Headers) { + let newHeaders = []; + if (v2Headers) { + Object.keys(v2Headers).forEach(key => { + let values = v2Headers[key]; + if (typeof (values) === 'string') { + newHeaders.push({ name: key, value: values }); + } else { + for (let i = 0; i < values.length; ++i) { + newHeaders.push({ name: key, value: values[i] }); + } + } + }); + } + return newHeaders; + } + + static toV2Headers(headers, arr = true) { + let v2Headers = {}; + for (let i = 0; i < headers.length; ++i) { + let name = headers[i].name; + let value = headers[i].value; + if (ObjectUtil.isEmpty(name) || ObjectUtil.isEmpty(value)) { + continue; + } + if (!(name in v2Headers)) { + v2Headers[name] = arr ? [value] : value; + } else { + if (arr) { + v2Headers[name].push(value); + } else { + v2Headers[name] = value; + } + } + } + return v2Headers; + } +} + +class TcpStreamSettings extends XrayCommonClass { + constructor( + acceptProxyProtocol = false, + type = 'none', + request = new TcpStreamSettings.TcpRequest(), + response = new TcpStreamSettings.TcpResponse(), + ) { + super(); + this.acceptProxyProtocol = acceptProxyProtocol; + this.type = type; + this.request = request; + this.response = response; + } + + static fromJson(json = {}) { + let header = json.header; + if (!header) { + header = {}; + } + return new TcpStreamSettings(json.acceptProxyProtocol, + header.type, + TcpStreamSettings.TcpRequest.fromJson(header.request), + TcpStreamSettings.TcpResponse.fromJson(header.response), + ); + } + + toJson() { + return { + acceptProxyProtocol: this.acceptProxyProtocol, + header: { + type: this.type, + request: this.type === 'http' ? this.request.toJson() : undefined, + response: this.type === 'http' ? this.response.toJson() : undefined, + }, + }; + } +} + +TcpStreamSettings.TcpRequest = class extends XrayCommonClass { + constructor( + version = '1.1', + method = 'GET', + path = ['/'], + headers = [], + ) { + super(); + this.version = version; + this.method = method; + this.path = path.length === 0 ? ['/'] : path; + this.headers = headers; + } + + addPath(path) { + this.path.push(path); + } + + removePath(index) { + this.path.splice(index, 1); + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json = {}) { + return new TcpStreamSettings.TcpRequest( + json.version, + json.method, + json.path, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + version: this.version, + method: this.method, + path: ObjectUtil.clone(this.path), + headers: XrayCommonClass.toV2Headers(this.headers), + }; + } +}; + +TcpStreamSettings.TcpResponse = class extends XrayCommonClass { + constructor( + version = '1.1', + status = '200', + reason = 'OK', + headers = [], + ) { + super(); + this.version = version; + this.status = status; + this.reason = reason; + this.headers = headers; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json = {}) { + return new TcpStreamSettings.TcpResponse( + json.version, + json.status, + json.reason, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + version: this.version, + status: this.status, + reason: this.reason, + headers: XrayCommonClass.toV2Headers(this.headers), + }; + } +}; + +class KcpStreamSettings extends XrayCommonClass { + constructor( + mtu = 1350, + tti = 50, + uplinkCapacity = 5, + downlinkCapacity = 20, + congestion = false, + readBufferSize = 2, + writeBufferSize = 2, + type = 'none', + seed = RandomUtil.randomSeq(10), + ) { + super(); + this.mtu = mtu; + this.tti = tti; + this.upCap = uplinkCapacity; + this.downCap = downlinkCapacity; + this.congestion = congestion; + this.readBuffer = readBufferSize; + this.writeBuffer = writeBufferSize; + this.type = type; + this.seed = seed; + } + + static fromJson(json = {}) { + return new KcpStreamSettings( + json.mtu, + json.tti, + json.uplinkCapacity, + json.downlinkCapacity, + json.congestion, + json.readBufferSize, + json.writeBufferSize, + ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type, + json.seed, + ); + } + + toJson() { + return { + mtu: this.mtu, + tti: this.tti, + uplinkCapacity: this.upCap, + downlinkCapacity: this.downCap, + congestion: this.congestion, + readBufferSize: this.readBuffer, + writeBufferSize: this.writeBuffer, + header: { + type: this.type, + }, + seed: this.seed, + }; + } +} + +class WsStreamSettings extends XrayCommonClass { + constructor( + acceptProxyProtocol = false, + path = '/', + host = '', + headers = [] + ) { + super(); + this.acceptProxyProtocol = acceptProxyProtocol; + this.path = path; + this.host = host; + this.headers = headers; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json = {}) { + return new WsStreamSettings( + json.acceptProxyProtocol, + json.path, + json.host, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + acceptProxyProtocol: this.acceptProxyProtocol, + path: this.path, + host: this.host, + headers: XrayCommonClass.toV2Headers(this.headers, false), + }; + } +} + +class HttpStreamSettings extends XrayCommonClass { + constructor( + path = '/', + host = [''], + ) { + super(); + this.path = path; + this.host = host.length === 0 ? [''] : host; + } + + addHost(host) { + this.host.push(host); + } + + removeHost(index) { + this.host.splice(index, 1); + } + + static fromJson(json = {}) { + return new HttpStreamSettings(json.path, json.host); + } + + toJson() { + let host = []; + for (let i = 0; i < this.host.length; ++i) { + if (!ObjectUtil.isEmpty(this.host[i])) { + host.push(this.host[i]); + } + } + return { + path: this.path, + host: host, + } + } +} + +class GrpcStreamSettings extends XrayCommonClass { + constructor( + serviceName = "", + authority = "", + multiMode = false, + ) { + super(); + this.serviceName = serviceName; + this.authority = authority; + this.multiMode = multiMode; + } + + static fromJson(json = {}) { + return new GrpcStreamSettings( + json.serviceName, + json.authority, + json.multiMode + ); + } + + toJson() { + return { + serviceName: this.serviceName, + authority: this.authority, + multiMode: this.multiMode, + } + } +} + +class HTTPUpgradeStreamSettings extends XrayCommonClass { + constructor( + acceptProxyProtocol = false, + path = '/', + host = '', + headers = [] + ) { + super(); + this.acceptProxyProtocol = acceptProxyProtocol; + this.path = path; + this.host = host; + this.headers = headers; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json = {}) { + return new HTTPUpgradeStreamSettings( + json.acceptProxyProtocol, + json.path, + json.host, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + acceptProxyProtocol: this.acceptProxyProtocol, + path: this.path, + host: this.host, + headers: XrayCommonClass.toV2Headers(this.headers, false), + }; + } +} + +class SplitHTTPStreamSettings extends XrayCommonClass { + constructor( + path = '/', + host = '', + headers = [], + scMaxConcurrentPosts = "100-200", + scMaxEachPostBytes = "1000000-2000000", + scMinPostsIntervalMs = "10-50", + noSSEHeader = false, + xPaddingBytes = "100-1000", + xmux = { + maxConcurrency: "16-32", + maxConnections: 0, + cMaxReuseTimes: "64-128", + cMaxLifetimeMs: 0 + } + ) { + super(); + this.path = path; + this.host = host; + this.headers = headers; + this.scMaxConcurrentPosts = scMaxConcurrentPosts; + this.scMaxEachPostBytes = scMaxEachPostBytes; + this.scMinPostsIntervalMs = scMinPostsIntervalMs; + this.noSSEHeader = noSSEHeader; + this.xPaddingBytes = xPaddingBytes; + this.xmux = xmux; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json = {}) { + return new SplitHTTPStreamSettings( + json.path, + json.host, + XrayCommonClass.toHeaders(json.headers), + json.scMaxConcurrentPosts, + json.scMaxEachPostBytes, + json.scMinPostsIntervalMs, + json.noSSEHeader, + json.xPaddingBytes, + json.xmux, + ); + } + + toJson() { + return { + path: this.path, + host: this.host, + headers: XrayCommonClass.toV2Headers(this.headers, false), + scMaxConcurrentPosts: this.scMaxConcurrentPosts, + scMaxEachPostBytes: this.scMaxEachPostBytes, + scMinPostsIntervalMs: this.scMinPostsIntervalMs, + noSSEHeader: this.noSSEHeader, + xPaddingBytes: this.xPaddingBytes, + xmux: { + maxConcurrency: this.xmux.maxConcurrency, + maxConnections: this.xmux.maxConnections, + cMaxReuseTimes: this.xmux.cMaxReuseTimes, + cMaxLifetimeMs: this.xmux.cMaxLifetimeMs + } + }; + } +} + +class TlsStreamSettings extends XrayCommonClass { + constructor( + serverName = '', + minVersion = TLS_VERSION_OPTION.TLS12, + maxVersion = TLS_VERSION_OPTION.TLS13, + cipherSuites = '', + rejectUnknownSni = false, + disableSystemRoot = false, + enableSessionResumption = false, + certificates = [new TlsStreamSettings.Cert()], + alpn = [ALPN_OPTION.H3, ALPN_OPTION.H2, ALPN_OPTION.HTTP1], + settings = new TlsStreamSettings.Settings() + ) { + super(); + this.sni = serverName; + this.minVersion = minVersion; + this.maxVersion = maxVersion; + this.cipherSuites = cipherSuites; + this.rejectUnknownSni = rejectUnknownSni; + this.disableSystemRoot = disableSystemRoot; + this.enableSessionResumption = enableSessionResumption; + this.certs = certificates; + this.alpn = alpn; + this.settings = settings; + } + + addCert() { + this.certs.push(new TlsStreamSettings.Cert()); + } + + removeCert(index) { + this.certs.splice(index, 1); + } + + static fromJson(json = {}) { + let certs; + let settings; + if (!ObjectUtil.isEmpty(json.certificates)) { + certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert)); + } + + if (!ObjectUtil.isEmpty(json.settings)) { + settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.serverName, json.settings.domains); + } + return new TlsStreamSettings( + json.serverName, + json.minVersion, + json.maxVersion, + json.cipherSuites, + json.rejectUnknownSni, + json.disableSystemRoot, + json.enableSessionResumption, + certs, + json.alpn, + settings, + ); + } + + toJson() { + return { + serverName: this.sni, + minVersion: this.minVersion, + maxVersion: this.maxVersion, + cipherSuites: this.cipherSuites, + rejectUnknownSni: this.rejectUnknownSni, + disableSystemRoot: this.disableSystemRoot, + enableSessionResumption: this.enableSessionResumption, + certificates: TlsStreamSettings.toJsonArray(this.certs), + alpn: this.alpn, + settings: this.settings, + }; + } +} + +TlsStreamSettings.Cert = class extends XrayCommonClass { + constructor( + useFile = true, + certificateFile = '', + keyFile = '', + certificate = '', + key = '', + ocspStapling = 3600, + oneTimeLoading = false, + usage = USAGE_OPTION.ENCIPHERMENT, + buildChain = false, + ) { + super(); + this.useFile = useFile; + this.certFile = certificateFile; + this.keyFile = keyFile; + this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate; + this.key = Array.isArray(key) ? key.join('\n') : key; + this.ocspStapling = ocspStapling; + this.oneTimeLoading = oneTimeLoading; + this.usage = usage; + this.buildChain = buildChain + } + + static fromJson(json = {}) { + if ('certificateFile' in json && 'keyFile' in json) { + return new TlsStreamSettings.Cert( + true, + json.certificateFile, + json.keyFile, '', '', + json.ocspStapling, + json.oneTimeLoading, + json.usage, + json.buildChain, + ); + } else { + return new TlsStreamSettings.Cert( + false, '', '', + json.certificate.join('\n'), + json.key.join('\n'), + json.ocspStapling, + json.oneTimeLoading, + json.usage, + json.buildChain, + ); + } + } + + toJson() { + if (this.useFile) { + return { + certificateFile: this.certFile, + keyFile: this.keyFile, + ocspStapling: this.ocspStapling, + oneTimeLoading: this.oneTimeLoading, + usage: this.usage, + buildChain: this.buildChain, + }; + } else { + return { + certificate: this.cert.split('\n'), + key: this.key.split('\n'), + ocspStapling: this.ocspStapling, + oneTimeLoading: this.oneTimeLoading, + usage: this.usage, + buildChain: this.buildChain, + }; + } + } +}; + +TlsStreamSettings.Settings = class extends XrayCommonClass { + constructor(allowInsecure = false, fingerprint = '') { + super(); + this.allowInsecure = allowInsecure; + this.fingerprint = fingerprint; + } + static fromJson(json = {}) { + return new TlsStreamSettings.Settings( + json.allowInsecure, + json.fingerprint, + ); + } + toJson() { + return { + allowInsecure: this.allowInsecure, + fingerprint: this.fingerprint, + }; + } +}; + + +class RealityStreamSettings extends XrayCommonClass { + constructor( + show = false, + xver = 0, + dest = 'yahoo.com:443', + serverNames = 'yahoo.com,www.yahoo.com', + privateKey = '', + minClient = '', + maxClient = '', + maxTimediff = 0, + shortIds = RandomUtil.randomShortIds(), + settings = new RealityStreamSettings.Settings() + ) { + super(); + this.show = show; + this.xver = xver; + this.dest = dest; + this.serverNames = Array.isArray(serverNames) ? serverNames.join(",") : serverNames; + this.privateKey = privateKey; + this.minClient = minClient; + this.maxClient = maxClient; + this.maxTimediff = maxTimediff; + this.shortIds = Array.isArray(shortIds) ? shortIds.join(",") : shortIds; + this.settings = settings; + } + + static fromJson(json = {}) { + let settings; + if (!ObjectUtil.isEmpty(json.settings)) { + settings = new RealityStreamSettings.Settings( + json.settings.publicKey, + json.settings.fingerprint, + json.settings.serverName, + json.settings.spiderX + ); + } + return new RealityStreamSettings( + json.show, + json.xver, + json.dest, + json.serverNames, + json.privateKey, + json.minClient, + json.maxClient, + json.maxTimediff, + json.shortIds, + json.settings, + ); + } + + toJson() { + return { + show: this.show, + xver: this.xver, + dest: this.dest, + serverNames: this.serverNames.split(","), + privateKey: this.privateKey, + minClient: this.minClient, + maxClient: this.maxClient, + maxTimediff: this.maxTimediff, + shortIds: this.shortIds.split(","), + settings: this.settings, + }; + } +} + +RealityStreamSettings.Settings = class extends XrayCommonClass { + constructor( + publicKey = '', + fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM, + serverName = '', + spiderX = '/' + ) { + super(); + this.publicKey = publicKey; + this.fingerprint = fingerprint; + this.serverName = serverName; + this.spiderX = spiderX; + } + static fromJson(json = {}) { + return new RealityStreamSettings.Settings( + json.publicKey, + json.fingerprint, + json.serverName, + json.spiderX, + ); + } + toJson() { + return { + publicKey: this.publicKey, + fingerprint: this.fingerprint, + serverName: this.serverName, + spiderX: this.spiderX, + }; + } +}; + +class SockoptStreamSettings extends XrayCommonClass { + constructor( + acceptProxyProtocol = false, + tcpFastOpen = false, + mark = 0, + tproxy = "off", + tcpMptcp = false, + tcpNoDelay = false, + domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP, + tcpMaxSeg = 1440, + dialerProxy = "", + tcpKeepAliveInterval = 0, + tcpKeepAliveIdle = 300, + tcpUserTimeout = 10000, + tcpcongestion = TCP_CONGESTION_OPTION.BBR, + V6Only = false, + tcpWindowClamp = 600, + interfaceName = "", + ) { + super(); + this.acceptProxyProtocol = acceptProxyProtocol; + this.tcpFastOpen = tcpFastOpen; + this.mark = mark; + this.tproxy = tproxy; + this.tcpMptcp = tcpMptcp; + this.tcpNoDelay = tcpNoDelay; + this.domainStrategy = domainStrategy; + this.tcpMaxSeg = tcpMaxSeg; + this.dialerProxy = dialerProxy; + this.tcpKeepAliveInterval = tcpKeepAliveInterval; + this.tcpKeepAliveIdle = tcpKeepAliveIdle; + this.tcpUserTimeout = tcpUserTimeout; + this.tcpcongestion = tcpcongestion; + this.V6Only = V6Only; + this.tcpWindowClamp = tcpWindowClamp; + this.interfaceName = interfaceName; + } + + static fromJson(json = {}) { + if (Object.keys(json).length === 0) return undefined; + return new SockoptStreamSettings( + json.acceptProxyProtocol, + json.tcpFastOpen, + json.mark, + json.tproxy, + json.tcpMptcp, + json.tcpNoDelay, + json.domainStrategy, + json.tcpMaxSeg, + json.dialerProxy, + json.tcpKeepAliveInterval, + json.tcpKeepAliveIdle, + json.tcpUserTimeout, + json.tcpcongestion, + json.V6Only, + json.tcpWindowClamp, + json.interface, + ); + } + + toJson() { + return { + acceptProxyProtocol: this.acceptProxyProtocol, + tcpFastOpen: this.tcpFastOpen, + mark: this.mark, + tproxy: this.tproxy, + tcpMptcp: this.tcpMptcp, + tcpNoDelay: this.tcpNoDelay, + domainStrategy: this.domainStrategy, + tcpMaxSeg: this.tcpMaxSeg, + dialerProxy: this.dialerProxy, + tcpKeepAliveInterval: this.tcpKeepAliveInterval, + tcpKeepAliveIdle: this.tcpKeepAliveIdle, + tcpUserTimeout: this.tcpUserTimeout, + tcpcongestion: this.tcpcongestion, + V6Only: this.V6Only, + tcpWindowClamp: this.tcpWindowClamp, + interface: this.interfaceName, + }; + } +} + +class StreamSettings extends XrayCommonClass { + constructor(network = 'tcp', + security = 'none', + externalProxy = [], + tlsSettings = new TlsStreamSettings(), + realitySettings = new RealityStreamSettings(), + tcpSettings = new TcpStreamSettings(), + kcpSettings = new KcpStreamSettings(), + wsSettings = new WsStreamSettings(), + httpSettings = new HttpStreamSettings(), + grpcSettings = new GrpcStreamSettings(), + httpupgradeSettings = new HTTPUpgradeStreamSettings(), + splithttpSettings = new SplitHTTPStreamSettings(), + sockopt = undefined, + ) { + super(); + this.network = network; + this.security = security; + this.externalProxy = externalProxy; + this.tls = tlsSettings; + this.reality = realitySettings; + this.tcp = tcpSettings; + this.kcp = kcpSettings; + this.ws = wsSettings; + this.http = httpSettings; + this.grpc = grpcSettings; + this.httpupgrade = httpupgradeSettings; + this.splithttp = splithttpSettings; + this.sockopt = sockopt; + } + + get isTls() { + return this.security === "tls"; + } + + set isTls(isTls) { + if (isTls) { + this.security = 'tls'; + } else { + this.security = 'none'; + } + } + + //for Reality + get isReality() { + return this.security === "reality"; + } + + set isReality(isReality) { + if (isReality) { + this.security = 'reality'; + } else { + this.security = 'none'; + } + } + + get sockoptSwitch() { + return this.sockopt != undefined; + } + + set sockoptSwitch(value) { + this.sockopt = value ? new SockoptStreamSettings() : undefined; + } + + static fromJson(json = {}) { + return new StreamSettings( + json.network, + json.security, + json.externalProxy, + TlsStreamSettings.fromJson(json.tlsSettings), + RealityStreamSettings.fromJson(json.realitySettings), + TcpStreamSettings.fromJson(json.tcpSettings), + KcpStreamSettings.fromJson(json.kcpSettings), + WsStreamSettings.fromJson(json.wsSettings), + HttpStreamSettings.fromJson(json.httpSettings), + GrpcStreamSettings.fromJson(json.grpcSettings), + HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings), + SplitHTTPStreamSettings.fromJson(json.splithttpSettings), + SockoptStreamSettings.fromJson(json.sockopt), + ); + } + + toJson() { + const network = this.network; + return { + network: network, + security: this.security, + externalProxy: this.externalProxy, + tlsSettings: this.isTls ? this.tls.toJson() : undefined, + realitySettings: this.isReality ? this.reality.toJson() : undefined, + tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, + kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined, + wsSettings: network === 'ws' ? this.ws.toJson() : undefined, + httpSettings: network === 'http' ? this.http.toJson() : undefined, + grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, + httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined, + splithttpSettings: network === 'splithttp' ? this.splithttp.toJson() : undefined, + sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined, + }; + } +} + +class Sniffing extends XrayCommonClass { + constructor( + enabled = false, + destOverride = ['http', 'tls', 'quic', 'fakedns'], + metadataOnly = false, + routeOnly = false) { + super(); + this.enabled = enabled; + this.destOverride = destOverride; + this.metadataOnly = metadataOnly; + this.routeOnly = routeOnly; + } + + static fromJson(json = {}) { + let destOverride = ObjectUtil.clone(json.destOverride); + if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) { + if (ObjectUtil.isEmpty(destOverride[0])) { + destOverride = ['http', 'tls', 'quic', 'fakedns']; + } + } + return new Sniffing( + !!json.enabled, + destOverride, + json.metadataOnly, + json.routeOnly, + ); + } +} + +class Allocate extends XrayCommonClass { + constructor( + strategy = "always", + refresh = 5, + concurrency = 3, + ) { + super(); + this.strategy = strategy; + this.refresh = refresh; + this.concurrency = concurrency; + } + + static fromJson(json = {}) { + return new Allocate( + json.strategy, + json.refresh, + json.concurrency, + ); + } +} + +class Inbound extends XrayCommonClass { + constructor( + port = RandomUtil.randomIntRange(10000, 60000), + listen = '', + protocol = Protocols.VLESS, + settings = null, + streamSettings = new StreamSettings(), + tag = '', + sniffing = new Sniffing(), + allocate = new Allocate(), + clientStats = '', + ) { + super(); + this.port = port; + this.listen = listen; + this._protocol = protocol; + this.settings = ObjectUtil.isEmpty(settings) ? Inbound.Settings.getSettings(protocol) : settings; + this.stream = streamSettings; + this.tag = tag; + this.sniffing = sniffing; + this.allocate = allocate; + this.clientStats = clientStats; + } + getClientStats() { + 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() { + return this._protocol; + } + + set protocol(protocol) { + this._protocol = protocol; + this.settings = Inbound.Settings.getSettings(protocol); + if (protocol === Protocols.TROJAN) { + this.tls = false; + } + } + + get network() { + return this.stream.network; + } + + set network(network) { + this.stream.network = network; + } + + get isTcp() { + return this.network === "tcp"; + } + + get isWs() { + return this.network === "ws"; + } + + get isKcp() { + return this.network === "kcp"; + } + + get isGrpc() { + return this.network === "grpc"; + } + + get isH2() { + return this.network === "http"; + } + + get isHttpupgrade() { + return this.network === "httpupgrade"; + } + + get isSplithttp() { + return this.network === "splithttp"; + } + + // Shadowsocks + get method() { + switch (this.protocol) { + case Protocols.SHADOWSOCKS: + return this.settings.method; + default: + return ""; + } + } + get isSSMultiUser() { + return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305; + } + get isSS2022() { + return this.method.substring(0, 4) === "2022"; + } + + get serverName() { + if (this.stream.isTls) return this.stream.tls.sni; + if (this.stream.isReality) return this.stream.reality.serverNames; + return ""; + } + + getHeader(obj, name) { + for (const header of obj.headers) { + if (header.name.toLowerCase() === name.toLowerCase()) { + return header.value; + } + } + return ""; + } + + get host() { + if (this.isTcp) { + return this.getHeader(this.stream.tcp.request, 'host'); + } else if (this.isWs) { + return this.stream.ws.host?.length > 0 ? this.stream.ws.host : this.getHeader(this.stream.ws, 'host'); + } else if (this.isH2) { + return this.stream.http.host[0]; + } else if (this.isHttpupgrade) { + return this.stream.httpupgrade.host?.length > 0 ? this.stream.httpupgrade.host : this.getHeader(this.stream.httpupgrade, 'host'); + } else if (this.isSplithttp) { + return this.stream.splithttp.host?.length > 0 ? this.stream.splithttp.host : this.getHeader(this.stream.splithttp, 'host'); + } + return null; + } + + get path() { + if (this.isTcp) { + return this.stream.tcp.request.path[0]; + } else if (this.isWs) { + return this.stream.ws.path; + } else if (this.isH2) { + return this.stream.http.path; + } else if (this.isHttpupgrade) { + return this.stream.httpupgrade.path; + } else if (this.isSplithttp) { + return this.stream.splithttp.path; + } + return null; + } + + get kcpType() { + return this.stream.kcp.type; + } + + get kcpSeed() { + return this.stream.kcp.seed; + } + + get serviceName() { + return this.stream.grpc.serviceName; + } + + isExpiry(index) { + let exp = this.clients[index].expiryTime; + return exp > 0 ? exp < new Date().getTime() : false; + } + + canEnableTls() { + if (![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false; + return ["tcp", "ws", "http", "grpc", "httpupgrade", "splithttp"].includes(this.network); + } + + //this is used for xtls-rprx-vision + canEnableTlsFlow() { + if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) { + return this.protocol === Protocols.VLESS; + } + return false; + } + + canEnableReality() { + if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false; + return ["tcp", "http", "grpc", "splithttp"].includes(this.network); + } + + canEnableStream() { + return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol); + } + + reset() { + this.port = RandomUtil.randomIntRange(10000, 60000); + this.listen = ''; + this.protocol = Protocols.VMESS; + this.settings = Inbound.Settings.getSettings(Protocols.VMESS); + this.stream = new StreamSettings(); + this.tag = ''; + this.sniffing = new Sniffing(); + this.allocate = new Allocate(); + } + + genVmessLink(address = '', port = this.port, forceTls, remark = '', clientId, security) { + if (this.protocol !== Protocols.VMESS) { + return ''; + } + const tls = forceTls == 'same' ? this.stream.security : forceTls; + let obj = { + v: '2', + ps: remark, + add: address, + port: port, + id: clientId, + scy: security, + net: this.stream.network, + type: 'none', + tls: tls, + }; + const network = this.stream.network; + if (network === 'tcp') { + const tcp = this.stream.tcp; + obj.type = tcp.type; + if (tcp.type === 'http') { + const request = tcp.request; + obj.path = request.path.join(','); + const host = this.getHeader(request, 'host'); + if (host) obj.host = host; + } + } else if (network === 'kcp') { + const kcp = this.stream.kcp; + obj.type = kcp.type; + obj.path = kcp.seed; + } else if (network === 'ws') { + const ws = this.stream.ws; + obj.path = ws.path; + obj.host = ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host'); + } else if (network === 'http') { + obj.net = 'h2'; + obj.path = this.stream.http.path; + obj.host = this.stream.http.host.join(','); + } else if (network === 'grpc') { + obj.path = this.stream.grpc.serviceName; + obj.authority = this.stream.grpc.authority; + if (this.stream.grpc.multiMode) { + obj.type = 'multi' + } + } else if (network === 'httpupgrade') { + const httpupgrade = this.stream.httpupgrade; + obj.path = httpupgrade.path; + obj.host = httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'); + } else if (network === 'splithttp') { + const splithttp = this.stream.splithttp; + obj.path = splithttp.path; + obj.host = splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host'); + } + + if (security === 'tls') { + if (!ObjectUtil.isEmpty(this.stream.tls.sni)) { + obj.sni = this.stream.tls.sni; + } + if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)) { + obj.fp = this.stream.tls.settings.fingerprint; + } + if (this.stream.tls.alpn.length > 0) { + obj.alpn = this.stream.tls.alpn.join(','); + } + if (this.stream.tls.settings.allowInsecure) { + obj.allowInsecure = this.stream.tls.settings.allowInsecure; + } + } + + return 'vmess://' + base64(JSON.stringify(obj, null, 2)); + } + + genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow) { + const uuid = clientId; + const type = this.stream.network; + const security = forceTls == 'same' ? this.stream.security : forceTls; + const params = new Map(); + params.set("type", this.stream.network); + switch (type) { + case "tcp": + const tcp = this.stream.tcp; + if (tcp.type === 'http') { + const request = tcp.request; + params.set("path", request.path.join(',')); + const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = request.headers[index].value; + params.set("host", host); + } + params.set("headerType", 'http'); + } + break; + case "kcp": + const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); + break; + case "ws": + const ws = this.stream.ws; + params.set("path", ws.path); + params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host')); + break; + case "http": + const http = this.stream.http; + params.set("path", http.path); + params.set("host", http.host); + break; + case "grpc": + const grpc = this.stream.grpc; + params.set("serviceName", grpc.serviceName); + params.set("authority", grpc.authority); + if (grpc.multiMode) { + params.set("mode", "multi"); + } + break; + case "httpupgrade": + const httpupgrade = this.stream.httpupgrade; + params.set("path", httpupgrade.path); + params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host')); + break; + case "splithttp": + const splithttp = this.stream.splithttp; + params.set("path", splithttp.path); + params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host')); + 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.sni)) { + params.set("sni", this.stream.tls.sni); + } + if (type == "tcp" && !ObjectUtil.isEmpty(flow)) { + params.set("flow", flow); + } + } + } + + else if (security === 'reality') { + params.set("security", "reality"); + params.set("pbk", this.stream.reality.settings.publicKey); + params.set("fp", this.stream.reality.settings.fingerprint); + if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { + params.set("sni", this.stream.reality.serverNames.split(",")[0]); + } + if (this.stream.reality.shortIds.length > 0) { + params.set("sid", this.stream.reality.shortIds.split(",")[0]); + } + if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) { + params.set("spx", this.stream.reality.settings.spiderX); + } + if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) { + params.set("flow", flow); + } + } + + else { + params.set("security", "none"); + } + + const link = `vless://${uuid}@${address}:${port}`; + const url = new URL(link); + for (const [key, value] of params) { + url.searchParams.set(key, value) + } + url.hash = encodeURIComponent(remark); + return url.toString(); + } + + genSSLink(address = '', port = this.port, forceTls, remark = '', clientPassword) { + let settings = this.settings; + const type = this.stream.network; + const security = forceTls == 'same' ? this.stream.security : forceTls; + const params = new Map(); + params.set("type", this.stream.network); + switch (type) { + case "tcp": + const tcp = this.stream.tcp; + if (tcp.type === 'http') { + const request = tcp.request; + params.set("path", request.path.join(',')); + const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = request.headers[index].value; + params.set("host", host); + } + params.set("headerType", 'http'); + } + break; + case "kcp": + const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); + break; + case "ws": + const ws = this.stream.ws; + params.set("path", ws.path); + params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host')); + break; + case "http": + const http = this.stream.http; + params.set("path", http.path); + params.set("host", http.host); + break; + case "grpc": + const grpc = this.stream.grpc; + params.set("serviceName", grpc.serviceName); + params.set("authority", grpc.authority); + if (grpc.multiMode) { + params.set("mode", "multi"); + } + break; + case "httpupgrade": + const httpupgrade = this.stream.httpupgrade; + params.set("path", httpupgrade.path); + params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host')); + break; + case "splithttp": + const splithttp = this.stream.splithttp; + params.set("path", splithttp.path); + params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host')); + 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.sni)) { + params.set("sni", this.stream.tls.sni); + } + } + } + + + let password = new Array(); + if (this.isSS2022) password.push(settings.password); + if (this.isSSMultiUser) password.push(clientPassword); + + let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`; + const url = new URL(link); + for (const [key, value] of params) { + url.searchParams.set(key, value) + } + url.hash = encodeURIComponent(remark); + return url.toString(); + } + + genTrojanLink(address = '', port = this.port, forceTls, remark = '', clientPassword) { + const security = forceTls == 'same' ? this.stream.security : forceTls; + const type = this.stream.network; + const params = new Map(); + params.set("type", this.stream.network); + switch (type) { + case "tcp": + const tcp = this.stream.tcp; + if (tcp.type === 'http') { + const request = tcp.request; + params.set("path", request.path.join(',')); + const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = request.headers[index].value; + params.set("host", host); + } + params.set("headerType", 'http'); + } + break; + case "kcp": + const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); + break; + case "ws": + const ws = this.stream.ws; + params.set("path", ws.path); + params.set("host", ws.host?.length > 0 ? ws.host : this.getHeader(ws, 'host')); + break; + case "http": + const http = this.stream.http; + params.set("path", http.path); + params.set("host", http.host); + break; + case "grpc": + const grpc = this.stream.grpc; + params.set("serviceName", grpc.serviceName); + params.set("authority", grpc.authority); + if (grpc.multiMode) { + params.set("mode", "multi"); + } + break; + case "httpupgrade": + const httpupgrade = this.stream.httpupgrade; + params.set("path", httpupgrade.path); + params.set("host", httpupgrade.host?.length > 0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host')); + break; + case "splithttp": + const splithttp = this.stream.splithttp; + params.set("path", splithttp.path); + params.set("host", splithttp.host?.length > 0 ? splithttp.host : this.getHeader(splithttp, 'host')); + 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.sni)) { + params.set("sni", this.stream.tls.sni); + } + } + } + + else if (security === 'reality') { + params.set("security", "reality"); + params.set("pbk", this.stream.reality.settings.publicKey); + params.set("fp", this.stream.reality.settings.fingerprint); + if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { + params.set("sni", this.stream.reality.serverNames.split(",")[0]); + } + if (this.stream.reality.shortIds.length > 0) { + params.set("sid", this.stream.reality.shortIds.split(",")[0]); + } + if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) { + params.set("spx", this.stream.reality.settings.spiderX); + } + } + + else { + params.set("security", "none"); + } + + const link = `trojan://${clientPassword}@${address}:${port}`; + const url = new URL(link); + for (const [key, value] of params) { + url.searchParams.set(key, value) + } + url.hash = encodeURIComponent(remark); + return url.toString(); + } + + getWireguardLink(address, port, remark, peerId) { + let txt = `[Interface]\n` + txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n` + txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n` + txt += `DNS = 1.1.1.1, 1.0.0.1\n` + if (this.settings.mtu) { + txt += `MTU = ${this.settings.mtu}\n` + } + txt += `\n# ${remark}\n` + txt += `[Peer]\n` + txt += `PublicKey = ${this.settings.pubKey}\n` + txt += `AllowedIPs = 0.0.0.0/0, ::/0\n` + txt += `Endpoint = ${address}:${port}` + if (this.settings.peers[peerId].psk) { + txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}` + } + if (this.settings.peers[peerId].keepAlive) { + txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n` + } + return txt; + } + + genLink(address = '', port = this.port, forceTls = 'same', remark = '', client) { + switch (this.protocol) { + case Protocols.VMESS: + return this.genVmessLink(address, port, forceTls, remark, client.id, client.security); + case Protocols.VLESS: + return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow); + case Protocols.SHADOWSOCKS: + return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : ''); + case Protocols.TROJAN: + return this.genTrojanLink(address, port, forceTls, remark, client.password); + default: return ''; + } + } + + genAllLinks(remark = '', remarkModel = '-ieo', client) { + let result = []; + let email = client ? client.email : ''; + let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname; + let port = this.port; + const separationChar = remarkModel.charAt(0); + const orderChars = remarkModel.slice(1); + let orders = { + 'i': remark, + 'e': email, + 'o': '', + }; + if (ObjectUtil.isArrEmpty(this.stream.externalProxy)) { + let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar); + result.push({ + remark: r, + link: this.genLink(addr, port, 'same', r, client) + }); + } else { + this.stream.externalProxy.forEach((ep) => { + orders['o'] = ep.remark; + let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar); + result.push({ + remark: r, + link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client) + }); + }); + } + return result; + } + + genInboundLinks(remark = '', remarkModel = '-ieo') { + let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname; + if (this.clients) { + let links = []; + this.clients.forEach((client) => { + this.genAllLinks(remark, remarkModel, client).forEach(l => { + links.push(l.link); + }) + }); + return links.join('\r\n'); + } else { + if (this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark); + if (this.protocol == Protocols.WIREGUARD) { + let links = []; + this.settings.peers.forEach((p, index) => { + links.push(this.getWireguardLink(addr, this.port, remark + remarkModel.charAt(0) + (index + 1), index)); + }); + return links.join('\r\n'); + } + return ''; + } + } + + static fromJson(json = {}) { + return new Inbound( + json.port, + json.listen, + json.protocol, + Inbound.Settings.fromJson(json.protocol, json.settings), + StreamSettings.fromJson(json.streamSettings), + json.tag, + Sniffing.fromJson(json.sniffing), + Allocate.fromJson(json.allocate), + json.clientStats + ) + } + + toJson() { + let streamSettings; + if (this.canEnableStream()) { + streamSettings = this.stream.toJson(); + } + return { + port: this.port, + listen: this.listen, + protocol: this.protocol, + settings: this.settings instanceof XrayCommonClass ? this.settings.toJson() : this.settings, + streamSettings: streamSettings, + tag: this.tag, + sniffing: this.sniffing.toJson(), + allocate: this.allocate.toJson(), + clientStats: this.clientStats + }; + } +} + +Inbound.Settings = class extends XrayCommonClass { + constructor(protocol) { + super(); + this.protocol = protocol; + } + + static getSettings(protocol) { + switch (protocol) { + case Protocols.VMESS: return new Inbound.VmessSettings(protocol); + case Protocols.VLESS: return new Inbound.VLESSSettings(protocol); + case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol); + case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol); + case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol); + case Protocols.SOCKS: return new Inbound.SocksSettings(protocol); + case Protocols.HTTP: return new Inbound.HttpSettings(protocol); + case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol); + default: return null; + } + } + + static fromJson(protocol, json) { + switch (protocol) { + case Protocols.VMESS: return Inbound.VmessSettings.fromJson(json); + case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json); + case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json); + case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json); + case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json); + case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json); + case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); + case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json); + default: return null; + } + } + + toJson() { + return {}; + } +}; + +Inbound.VmessSettings = class extends Inbound.Settings { + constructor(protocol, + vmesses = [new Inbound.VmessSettings.VMESS()]) { + super(protocol); + this.vmesses = vmesses; + } + + indexOfVmessById(id) { + return this.vmesses.findIndex(VMESS => VMESS.id === id); + } + + addVmess(VMESS) { + if (this.indexOfVmessById(VMESS.id) >= 0) { + return false; + } + this.vmesses.push(VMESS); + } + + delVmess(VMESS) { + const i = this.indexOfVmessById(VMESS.id); + if (i >= 0) { + this.vmesses.splice(i, 1); + } + } + + static fromJson(json = {}) { + return new Inbound.VmessSettings( + Protocols.VMESS, + json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)), + ); + } + + toJson() { + return { + clients: Inbound.VmessSettings.toJsonArray(this.vmesses), + }; + } +}; + +Inbound.VmessSettings.VMESS = class extends XrayCommonClass { + constructor( + id = RandomUtil.randomUUID(), + security = USERS_SECURITY.AUTO, + email = RandomUtil.randomLowerAndNum(8), + limitIp = 0, + totalGB = 0, + expiryTime = 0, + enable = true, + tgId = '', + subId = RandomUtil.randomLowerAndNum(16), + reset = 0 + ) { + super(); + this.id = id; + this.security = security; + this.email = email; + this.limitIp = limitIp; + this.totalGB = totalGB; + this.expiryTime = expiryTime; + this.enable = enable; + this.tgId = tgId; + this.subId = subId; + this.reset = reset; + } + + static fromJson(json = {}) { + return new Inbound.VmessSettings.VMESS( + json.id, + json.security, + json.email, + json.limitIp, + json.totalGB, + json.expiryTime, + json.enable, + json.tgId, + json.subId, + json.reset, + ); + } + get _expiryTime() { + if (this.expiryTime === 0 || this.expiryTime === "") { + return null; + } + if (this.expiryTime < 0) { + return this.expiryTime / -86400000; + } + return moment(this.expiryTime); + } + + set _expiryTime(t) { + if (t == null || t === "") { + this.expiryTime = 0; + } else { + this.expiryTime = t.valueOf(); + } + } + get _totalGB() { + return toFixed(this.totalGB / ONE_GB, 2); + } + + set _totalGB(gb) { + this.totalGB = toFixed(gb * ONE_GB, 0); + } + +}; + +Inbound.VLESSSettings = class extends Inbound.Settings { + constructor( + protocol, + vlesses = [new Inbound.VLESSSettings.VLESS()], + decryption = 'none', + fallbacks = [] + ) { + super(protocol); + this.vlesses = vlesses; + this.decryption = decryption; + this.fallbacks = fallbacks; + } + + addFallback() { + this.fallbacks.push(new Inbound.VLESSSettings.Fallback()); + } + + delFallback(index) { + this.fallbacks.splice(index, 1); + } + + // decryption should be set to static value + static fromJson(json = {}) { + return new Inbound.VLESSSettings( + Protocols.VLESS, + json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), + json.decryption || 'none', + Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),); + } + + toJson() { + return { + clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), + decryption: this.decryption, + fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks), + }; + } +}; + +Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { + constructor( + id = RandomUtil.randomUUID(), + flow = '', + email = RandomUtil.randomLowerAndNum(8), + limitIp = 0, + totalGB = 0, + expiryTime = 0, + enable = true, + tgId = '', + subId = RandomUtil.randomLowerAndNum(16), + reset = 0 + ) { + super(); + this.id = id; + this.flow = flow; + this.email = email; + this.limitIp = limitIp; + this.totalGB = totalGB; + this.expiryTime = expiryTime; + this.enable = enable; + this.tgId = tgId; + this.subId = subId; + this.reset = reset; + } + + static fromJson(json = {}) { + return new Inbound.VLESSSettings.VLESS( + json.id, + json.flow, + json.email, + json.limitIp, + json.totalGB, + json.expiryTime, + json.enable, + json.tgId, + json.subId, + json.reset, + ); + } + + get _expiryTime() { + if (this.