From b73e4173a3c1e69e02ad6b4e3b43e425e57a5be9 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 9 Feb 2023 22:48:06 +0330 Subject: 3x-ui --- web/assets/js/model/models.js | 185 +++++ web/assets/js/model/xray.js | 1819 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2004 insertions(+) create mode 100644 web/assets/js/model/models.js create mode 100644 web/assets/js/model/xray.js (limited to 'web/assets/js/model') diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js new file mode 100644 index 00000000..768bf4de --- /dev/null +++ b/web/assets/js/model/models.js @@ -0,0 +1,185 @@ +class User { + + constructor() { + this.username = ""; + this.password = ""; + } +} + +class Msg { + + constructor(success, msg, obj) { + this.success = false; + this.msg = ""; + this.obj = null; + + if (success != null) { + this.success = success; + } + if (msg != null) { + this.msg = msg; + } + if (obj != null) { + this.obj = obj; + } + } +} + +class DBInbound { + + constructor(data) { + this.id = 0; + this.userId = 0; + this.up = 0; + this.down = 0; + this.total = 0; + this.remark = ""; + this.enable = true; + this.expiryTime = 0; + + this.listen = ""; + this.port = 0; + this.protocol = ""; + this.settings = ""; + this.streamSettings = ""; + this.tag = ""; + this.sniffing = ""; + this.clientStats = "" + if (data == null) { + return; + } + ObjectUtil.cloneProps(this, data); + } + + get totalGB() { + return toFixed(this.total / ONE_GB, 2); + } + + set totalGB(gb) { + this.total = toFixed(gb * ONE_GB, 0); + } + + get isVMess() { + return this.protocol === Protocols.VMESS; + } + + get isVLess() { + return this.protocol === Protocols.VLESS; + } + + get isTrojan() { + return this.protocol === Protocols.TROJAN; + } + + get isSS() { + return this.protocol === Protocols.SHADOWSOCKS; + } + + get isSocks() { + return this.protocol === Protocols.SOCKS; + } + + get isHTTP() { + return this.protocol === Protocols.HTTP; + } + + get address() { + let address = location.hostname; + if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") { + address = this.listen; + } + return address; + } + + get _expiryTime() { + if (this.expiryTime === 0) { + return null; + } + return moment(this.expiryTime); + } + + set _expiryTime(t) { + if (t == null) { + this.expiryTime = 0; + } else { + this.expiryTime = t.valueOf(); + } + } + + get isExpiry() { + return this.expiryTime < new Date().getTime(); + } + + toInbound() { + let settings = {}; + if (!ObjectUtil.isEmpty(this.settings)) { + settings = JSON.parse(this.settings); + } + + let streamSettings = {}; + if (!ObjectUtil.isEmpty(this.streamSettings)) { + streamSettings = JSON.parse(this.streamSettings); + } + + let sniffing = {}; + if (!ObjectUtil.isEmpty(this.sniffing)) { + sniffing = JSON.parse(this.sniffing); + } + + const config = { + port: this.port, + listen: this.listen, + protocol: this.protocol, + settings: settings, + streamSettings: streamSettings, + tag: this.tag, + sniffing: sniffing, + clientStats: this.clientStats, + }; + return Inbound.fromJson(config); + } + + hasLink() { + switch (this.protocol) { + case Protocols.VMESS: + case Protocols.VLESS: + case Protocols.TROJAN: + case Protocols.SHADOWSOCKS: + return true; + default: + return false; + } + } + + genLink() { + const inbound = this.toInbound(); + return inbound.genLink(this.address, this.remark); + } +} + +class AllSetting { + + constructor(data) { + this.webListen = ""; + this.webPort = 54321; + this.webCertFile = ""; + this.webKeyFile = ""; + this.webBasePath = "/"; + this.tgBotEnable = false; + this.tgBotToken = ""; + this.tgBotChatId = 0; + this.tgRunTime = ""; + this.xrayTemplateConfig = ""; + + this.timeLocation = "Asia/Shanghai"; + + if (data == null) { + return + } + ObjectUtil.cloneProps(this, data); + } + + equals(other) { + return ObjectUtil.equals(this, other); + } +} \ No newline at end of file diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js new file mode 100644 index 00000000..78af69b6 --- /dev/null +++ b/web/assets/js/model/xray.js @@ -0,0 +1,1819 @@ +const Protocols = { + VMESS: 'vmess', + VLESS: 'vless', + TROJAN: 'trojan', + SHADOWSOCKS: 'shadowsocks', + DOKODEMO: 'dokodemo-door', + MTPROTO: 'mtproto', + SOCKS: 'socks', + HTTP: 'http', +}; + +const VmessMethods = { + AES_128_GCM: 'aes-128-gcm', + CHACHA20_POLY1305: 'chacha20-poly1305', + AUTO: 'auto', + NONE: 'none', +}; + +const SSMethods = { + // AES_256_CFB: 'aes-256-cfb', + // AES_128_CFB: 'aes-128-cfb', + // CHACHA20: 'chacha20', + // CHACHA20_IETF: 'chacha20-ietf', + CHACHA20_POLY1305: 'chacha20-poly1305', + AES_256_GCM: 'aes-256-gcm', + AES_128_GCM: 'aes-128-gcm', + 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 RULE_IP = { + PRIVATE: 'geoip:private', + CN: 'geoip:cn', +}; + +const RULE_DOMAIN = { + ADS: 'geosite:category-ads', + ADS_ALL: 'geosite:category-ads-all', + CN: 'geosite:cn', + GOOGLE: 'geosite:google', + FACEBOOK: 'geosite:facebook', + SPEEDTEST: 'geosite:speedtest', +}; + +const XTLS_FLOW_CONTROL = { + ORIGIN: "xtls-rprx-origin", + DIRECT: "xtls-rprx-direct", +}; + +const TLS_FLOW_CONTROL = { + VISION: "xtls-rprx-vision", +}; + +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", +}; + +Object.freeze(Protocols); +Object.freeze(VmessMethods); +Object.freeze(SSMethods); +Object.freeze(RULE_IP); +Object.freeze(RULE_DOMAIN); +Object.freeze(XTLS_FLOW_CONTROL); +Object.freeze(TLS_FLOW_CONTROL); +Object.freeze(TLS_VERSION_OPTION); +Object.freeze(TLS_CIPHER_OPTION); + +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( + type = 'none', + acceptProxyProtocol = false, + request = new TcpStreamSettings.TcpRequest(), + response = new TcpStreamSettings.TcpResponse(), + ) { + super(); + this.type = type; + this.request = request; + this.response = response; + this.acceptProxyProtocol = acceptProxyProtocol; + } + + static fromJson(json = {}) { + let header = json.header; + if (!header) { + header = {}; + } + return new TcpStreamSettings( + header.type, + json.acceptProxyProtocol, + TcpStreamSettings.TcpRequest.fromJson(header.request), + TcpStreamSettings.TcpResponse.fromJson(header.response), + ); + } + + toJson() { + return { + header: { + type: this.type, + request: this.type === 'http' ? this.request.toJson() : undefined, + response: this.type === 'http' ? this.response.toJson() : undefined, + }, + acceptProxyProtocol: this.acceptProxyProtocol, + }; + } +} + +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 }); + } + + getHeader(name) { + for (const header of this.headers) { + if (header.name.toLowerCase() === name.toLowerCase()) { + return header.value; + } + } + return null; + } + + 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 { + 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=20, + 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='/', headers=[]) { + super(); + this.acceptProxyProtocol = acceptProxyProtocol; + this.path = path; + this.headers = headers; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + getHeader(name) { + for (const header of this.headers) { + if (header.name.toLowerCase() === name.toLowerCase()) { + return header.value; + } + } + return null; + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json={}) { + return new WsStreamSettings( + json.acceptProxyProtocol, + json.path, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + acceptProxyProtocol: this.acceptProxyProtocol, + path: this.path, + 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 QuicStreamSettings extends XrayCommonClass { + constructor(security=VmessMethods.NONE, + key='', type='none') { + super(); + this.security = security; + this.key = key; + this.type = type; + } + + static fromJson(json={}) { + return new QuicStreamSettings( + json.security, + json.key, + json.header ? json.header.type : 'none', + ); + } + + toJson() { + return { + security: this.security, + key: this.key, + header: { + type: this.type, + } + } + } +} + +class GrpcStreamSettings extends XrayCommonClass { + constructor(serviceName="") { + super(); + this.serviceName = serviceName; + } + + static fromJson(json={}) { + return new GrpcStreamSettings(json.serviceName); + } + + toJson() { + return { + serviceName: this.serviceName, + } + } +} + +class TlsStreamSettings extends XrayCommonClass { + constructor(serverName = '', minVersion = TLS_VERSION_OPTION.TLS12, maxVersion = TLS_VERSION_OPTION.TLS13, + cipherSuites = '', + certificates = [new TlsStreamSettings.Cert()], alpn = ["h2", "http/1.1"]) { + super(); + this.server = serverName; + this.minVersion = minVersion; + this.maxVersion = maxVersion; + this.cipherSuites = cipherSuites; + this.certs = certificates; + this.alpn = alpn; + } + + addCert(cert) { + this.certs.push(cert); + } + + removeCert(index) { + this.certs.splice(index, 1); + } + + static fromJson(json={}) { + let certs; + if (!ObjectUtil.isEmpty(json.certificates)) { + certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert)); + } + + return new TlsStreamSettings( + json.serverName, + json.minVersion, + json.maxVersion, + json.cipherSuites, + certs, + json.alpn, + ); + } + + toJson() { + return { + serverName: this.server, + minVersion: this.minVersion, + maxVersion: this.maxVersion, + cipherSuites: this.cipherSuites, + certificates: TlsStreamSettings.toJsonArray(this.certs), + alpn: this.alpn + }; + } +} + +TlsStreamSettings.Cert = class extends XrayCommonClass { + constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') { + super(); + this.useFile = useFile; + this.certFile = certificateFile; + this.keyFile = keyFile; + this.cert = certificate instanceof Array ? certificate.join('\n') : certificate; + this.key = key instanceof Array ? key.join('\n') : key; + } + + static fromJson(json={}) { + if ('certificateFile' in json && 'keyFile' in json) { + return new TlsStreamSettings.Cert( + true, + json.certificateFile, + json.keyFile, + ); + } else { + return new TlsStreamSettings.Cert( + false, '', '', + json.certificate.join('\n'), + json.key.join('\n'), + ); + } + } + + toJson() { + if (this.useFile) { + return { + certificateFile: this.certFile, + keyFile: this.keyFile, + }; + } else { + return { + certificate: this.cert.split('\n'), + key: this.key.split('\n'), + }; + } + } +}; + +class StreamSettings extends XrayCommonClass { + constructor(network='tcp', + security='none', + tlsSettings=new TlsStreamSettings(), + tcpSettings=new TcpStreamSettings(), + kcpSettings=new KcpStreamSettings(), + wsSettings=new WsStreamSettings(), + httpSettings=new HttpStreamSettings(), + quicSettings=new QuicStreamSettings(), + grpcSettings=new GrpcStreamSettings(), + ) { + super(); + this.network = network; + this.security = security; + this.tls = tlsSettings; + this.tcp = tcpSettings; + this.kcp = kcpSettings; + this.ws = wsSettings; + this.http = httpSettings; + this.quic = quicSettings; + this.grpc = grpcSettings; + } + + get isTls() { + return this.security === 'tls'; + } + + set isTls(isTls) { + if (isTls) { + this.security = 'tls'; + } else { + this.security = 'none'; + } + } + + get isXTls() { + return this.security === "xtls"; + } + + set isXTls(isXTls) { + if (isXTls) { + this.security = 'xtls'; + } else { + this.security = 'none'; + } + } + + static fromJson(json={}) { + let tls; + if (json.security === "xtls") { + tls = TlsStreamSettings.fromJson(json.xtlsSettings); + } else { + tls = TlsStreamSettings.fromJson(json.tlsSettings); + } + return new StreamSettings( + json.network, + json.security, + tls, + TcpStreamSettings.fromJson(json.tcpSettings), + KcpStreamSettings.fromJson(json.kcpSettings), + WsStreamSettings.fromJson(json.wsSettings), + HttpStreamSettings.fromJson(json.httpSettings), + QuicStreamSettings.fromJson(json.quicSettings), + GrpcStreamSettings.fromJson(json.grpcSettings), + ); + } + + toJson() { + const network = this.network; + return { + network: network, + security: this.security, + tlsSettings: this.isTls ? this.tls.toJson() : undefined, + xtlsSettings: this.isXTls ? this.tls.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, + quicSettings: network === 'quic' ? this.quic.toJson() : undefined, + grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, + }; + } +} + +class Sniffing extends XrayCommonClass { + constructor(enabled=true, destOverride=['http', 'tls']) { + super(); + this.enabled = enabled; + this.destOverride = destOverride; + } + + static fromJson(json={}) { + let destOverride = ObjectUtil.clone(json.destOverride); + if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) { + if (ObjectUtil.isEmpty(destOverride[0])) { + destOverride = ['http', 'tls']; + } + } + return new Sniffing( + !!json.enabled, + destOverride, + ); + } +} + +class Inbound extends XrayCommonClass { + constructor(port=RandomUtil.randomIntRange(10000, 60000), + listen='', + protocol=Protocols.VMESS, + settings=null, + streamSettings=new StreamSettings(), + tag='', + sniffing=new Sniffing(), + 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.clientStats = clientStats; + } + getClientStats() { + return this.clientStats; + } + + get protocol() { + return this._protocol; + } + + set protocol(protocol) { + this._protocol = protocol; + this.settings = Inbound.Settings.getSettings(protocol); + if (protocol === Protocols.TROJAN) { + this.tls = true; + } + } + get tls() { + return this.stream.security === 'tls'; + } + + set tls(isTls) { + if (isTls) { + this.stream.security = 'tls'; + } else { + this.stream.security = 'none'; + } + } + + get xtls() { + return this.stream.security === 'xtls'; + } + + set xtls(isXTls) { + if (isXTls) { + this.stream.security = 'xtls'; + } else { + this.stream.security = 'none'; + } + } + + 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 isQuic() { + return this.network === "quic" + } + + get isGrpc() { + return this.network === "grpc"; + } + + get isH2() { + return this.network === "http"; + } + + isInboundEmpty() { + if (this.protocol == Protocols.VMESS && this.settings.vmesses.length == 0) { + return true; + } else if (this.protocol == Protocols.VLESS && this.settings.vlesses.length == 0) { + return true; + } else if (this.protocol == Protocols.TROJAN && this.settings.trojans.length == 0) { + return true; + } else { + return false; + } + } + + // VMess & VLess + get uuid() { + switch (this.protocol) { + case Protocols.VMESS: + return this.settings.vmesses[0].id; + case Protocols.VLESS: + return this.settings.vlesses[0].id; + default: + return ""; + } + } + + // VLess & Trojan + get flow() { + switch (this.protocol) { + case Protocols.VLESS: + return this.settings.vlesses[0].flow; + case Protocols.TROJAN: + return this.settings.trojans[0].flow; + default: + return ""; + } + } + + // VMess + get alterId() { + switch (this.protocol) { + case Protocols.VMESS: + return this.settings.vmesses[0].alterId; + default: + return ""; + } + } + + // Socks & HTTP + get username() { + switch (this.protocol) { + case Protocols.SOCKS: + case Protocols.HTTP: + return this.settings.accounts[0].user; + default: + return ""; + } + } + + // Trojan & Shadowsocks & Socks & HTTP + get password() { + switch (this.protocol) { + case Protocols.TROJAN: + return this.settings.trojans[0].password; + case Protocols.SHADOWSOCKS: + return this.settings.password; + case Protocols.SOCKS: + case Protocols.HTTP: + return this.settings.accounts[0].pass; + default: + return ""; + } + } + + // Shadowsocks + get method() { + switch (this.protocol) { + case Protocols.SHADOWSOCKS: + return this.settings.method; + default: + return ""; + } + } + + get serverName() { + if (this.stream.isTls || this.stream.isXTls) { + return this.stream.tls.server; + } + return ""; + } + + get host() { + if (this.isTcp) { + return this.stream.tcp.request.getHeader("Host"); + } else if (this.isWs) { + return this.stream.ws.getHeader("Host"); + } else if (this.isH2) { + return this.stream.http.host[0]; + } + 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[0]; + } + return null; + } + + get quicSecurity() { + return this.stream.quic.security; + } + + get quicKey() { + return this.stream.quic.key; + } + + get quicType() { + return this.stream.quic.type; + } + + get kcpType() { + return this.stream.kcp.type; + } + + get kcpSeed() { + return this.stream.kcp.seed; + } + + get serviceName() { + return this.stream.grpc.serviceName; + } + + isExpiry(index) { + switch (this.protocol) { + case Protocols.VMESS: + if(this.settings.vmesses[index]._expiryTime != null) + return this.settings.vmesses[index]._expiryTime < new Date().getTime(); + return false + case Protocols.VLESS: + if(this.settings.vlesses[index]._expiryTime != null) + return this.settings.vlesses[index]._expiryTime < new Date().getTime(); + return false + case Protocols.TROJAN: + if(this.settings.trojans[index]._expiryTime != null) + return this.settings.trojans[index]._expiryTime < new Date().getTime(); + return false + default: + return false; + } + } + + canEnableTls() { + switch (this.protocol) { + case Protocols.VMESS: + case Protocols.VLESS: + case Protocols.TROJAN: + case Protocols.SHADOWSOCKS: + break; + default: + return false; + } + + switch (this.network) { + case "tcp": + case "ws": + case "http": + case "quic": + case "grpc": + return true; + default: + return false; + } + } + + //this is used for xtls-rprx-vison + canEnableTlsFlow() { + if ((this.stream.security === 'tls') && (this.network === "tcp")) { + switch (this.protocol) { + case Protocols.VLESS: + return true; + default: + return false; + } + } + return false; + } + + canSetTls() { + return this.canEnableTls(); + } + + canEnableXTls() { + switch (this.protocol) { + case Protocols.VLESS: + case Protocols.TROJAN: + break; + default: + return false; + } + return this.network === "tcp"; + } + + canEnableStream() { + switch (this.protocol) { + case Protocols.VMESS: + case Protocols.VLESS: + case Protocols.TROJAN: + case Protocols.SHADOWSOCKS: + return true; + default: + return false; + } + } + + canSniffing() { + switch (this.protocol) { + case Protocols.VMESS: + case Protocols.VLESS: + case Protocols.TROJAN: + case Protocols.SHADOWSOCKS: + return true; + default: + return false; + } + } + + 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(); + } + + genVmessLink(address='', remark='', clientIndex=0) { + if (this.protocol !== Protocols.VMESS) { + return ''; + } + let network = this.stream.network; + let type = 'none'; + let host = ''; + let path = ''; + if (network === 'tcp') { + let tcp = this.stream.tcp; + type = tcp.type; + if (type === 'http') { + let request = tcp.request; + path = request.path.join(','); + let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + host = request.headers[index].value; + } + } + } else if (network === 'kcp') { + let kcp = this.stream.kcp; + type = kcp.type; + path = kcp.seed; + } else if (network === 'ws') { + let ws = this.stream.ws; + path = ws.path; + let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + host = ws.headers[index].value; + } + } else if (network === 'http') { + network = 'h2'; + path = this.stream.http.path; + host = this.stream.http.host.join(','); + } else if (network === 'quic') { + type = this.stream.quic.type; + host = this.stream.quic.security; + path = this.stream.quic.key; + } else if (network === 'grpc') { + path = this.stream.grpc.serviceName; + } + + if (this.stream.security === 'tls') { + if (!ObjectUtil.isEmpty(this.stream.tls.server)) { + address = this.stream.tls.server; + } + } + + let obj = { + v: '2', + ps: remark, + add: address, + port: this.port, + id: this.settings.vmesses[clientIndex].id, + aid: this.settings.vmesses[clientIndex].alterId, + net: network, + type: type, + host: host, + path: path, + tls: this.stream.security, + }; + return 'vmess://' + base64(JSON.stringify(obj, null, 2)); + } + + genVLESSLink(address = '', remark='', clientIndex=0) { + const settings = this.settings; + const uuid = settings.vlesses[clientIndex].id; + const port = this.port; + const type = this.stream.network; + const params = new Map(); + params.set("type", this.stream.network); + if (this.xtls) { + params.set("security", "xtls"); + address = this.stream.tls.server; + } else { + params.set("security", this.stream.security); + } + 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); + const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = ws.headers[index].value; + params.set("host", host); + } + break; + case "http": + const http = this.stream.http; + params.set("path", http.path); + params.set("host", http.host); + break; + case "quic": + const quic = this.stream.quic; + params.set("quicSecurity", quic.security); + params.set("key", quic.key); + params.set("headerType", quic.type); + break; + case "grpc": + const grpc = this.stream.grpc; + params.set("serviceName", grpc.serviceName); + break; + } + + if (this.stream.security === 'tls') { + if (!ObjectUtil.isEmpty(this.stream.tls.server)) { + address = this.stream.tls.server; + params.set("sni", address); + } + params.set("flow", this.settings.vlesses[clientIndex].flow); + } + + if (this.xtls) { + params.set("flow", this.settings.vlesses[clientIndex].flow); + } + + 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 = '', remark = '') { + let settings = this.settings; + const server = this.stream.tls.server; + if (!ObjectUtil.isEmpty(server)) { + address = server; + } + if (settings.method == SSMethods.BLAKE3_AES_128_GCM || settings.method == SSMethods.BLAKE3_AES_256_GCM || settings.method == SSMethods.BLAKE3_CHACHA20_POLY1305) { + return `ss://${settings.method}:${settings.password}@${address}:${this.port}#${encodeURIComponent(remark)}`; + } else { + return 'ss://' + safeBase64(settings.method + ':' + settings.password + '@' + address + ':' + this.port) + + '#' + encodeURIComponent(remark); + } + } + + genTrojanLink(address = '', remark = '', clientIndex = 0) { + let settings = this.settings; + const port = this.port; + const type = this.stream.network; + const params = new Map(); + params.set("type", this.stream.network); + if (this.xtls) { + params.set("security", "xtls"); + } else { + params.set("security", this.stream.security); + } + 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); + const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = ws.headers[index].value; + params.set("host", host); + } + break; + case "http": + const http = this.stream.http; + params.set("path", http.path); + params.set("host", http.host); + break; + case "quic": + const quic = this.stream.quic; + params.set("quicSecurity", quic.security); + params.set("key", quic.key); + params.set("headerType", quic.type); + break; + case "grpc": + const grpc = this.stream.grpc; + params.set("serviceName", grpc.serviceName); + break; + } + + if (this.stream.security === 'tls') { + if (!ObjectUtil.isEmpty(this.stream.tls.server)) { + address = this.stream.tls.server; + params.set("sni", address); + } + params.set("flow", this.settings.trojans[clientIndex].flow); + } + if (this.xtls) { + params.set("flow", this.settings.trojans[clientIndex].flow); + } + const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark+"-"+settings.trojans[clientIndex].email)}`; + const url = new URL(link); + for (const [key, value] of params) { + url.searchParams.set(key, value) + } + url.hash = encodeURIComponent(remark); + return url.toString(); + } + + genLink(address='', remark='', clientIndex=0) { + switch (this.protocol) { + case Protocols.VMESS: return this.genVmessLink(address, remark, clientIndex); + case Protocols.VLESS: return this.genVLESSLink(address, remark, clientIndex); + case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark); + case Protocols.TROJAN: return this.genTrojanLink(address, remark, clientIndex); + default: 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), + json.clientStats + ) + } + + toJson() { + let streamSettings; + if (this.canEnableStream() || this.protocol === Protocols.TROJAN) { + 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(), + 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.MTPROTO: return new Inbound.MtprotoSettings(protocol); + case Protocols.SOCKS: return new Inbound.SocksSettings(protocol); + case Protocols.HTTP: return new Inbound.HttpSettings(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.MTPROTO: return Inbound.MtprotoSettings.fromJson(json); + case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json); + case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); + default: return null; + } + } + + toJson() { + return {}; + } +}; + +Inbound.VmessSettings = class extends Inbound.Settings { + constructor(protocol, + vmesses=[new Inbound.VmessSettings.Vmess()], + disableInsecureEncryption=false) { + super(protocol); + this.vmesses = vmesses; + this.disableInsecure = disableInsecureEncryption; + } + + 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)), + ObjectUtil.isEmpty(json.disableInsecureEncryption) ? false : json.disableInsecureEncryption, + ); + } + + toJson() { + return { + clients: Inbound.VmessSettings.toJsonArray(this.vmesses), + disableInsecureEncryption: this.disableInsecure, + }; + } +}; +Inbound.VmessSettings.Vmess = class extends XrayCommonClass { + constructor(id=RandomUtil.randomUUID(), alterId=0, email='', totalGB=0, expiryTime='') { + super(); + this.id = id; + this.alterId = alterId; + this.email = email; + this.totalGB = totalGB; + this.expiryTime = expiryTime; + } + + static fromJson(json={}) { + return new Inbound.VmessSettings.Vmess( + json.id, + json.alterId, + json.email, + json.totalGB, + json.expiryTime, + + ); + } + get _expiryTime() { + if (this.expiryTime === 0 || this.expiryTime === "") { + return null; + } + 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); + } + + static fromJson(json={}) { + return new Inbound.VLESSSettings( + Protocols.VLESS, + json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), + json.decryption, + 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='', totalGB=0, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, expiryTime='') { + super(); + this.id = id; + this.flow = flow; + this.email = email; + this.totalGB = totalGB; + this.fingerprint = fingerprint; + this.expiryTime = expiryTime; + + } + + static fromJson(json={}) { + return new Inbound.VLESSSettings.VLESS( + json.id, + json.flow, + json.email, + json.totalGB, + json.fingerprint, + json.expiryTime, + + ); + } + + get _expiryTime() { + if (this.expiryTime === 0 || this.expiryTime === "") { + return null; + } + 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.Fallback = class extends XrayCommonClass { + constructor(name="", alpn='', path='', dest='', xver=0) { + super(); + this.name = name; + this.alpn = alpn; + this.path = path; + this.dest = dest; + this.xver = xver; + } + + toJson() { + let xver = this.xver; + if (!Number.isInteger(xver)) { + xver = 0; + } + return { + name: this.name, + alpn: this.alpn, + path: this.path, + dest: this.dest, + xver: xver, + } + } + + static fromJson(json=[]) { + const fallbacks = []; + for (let fallback of json) { + fallbacks.push(new Inbound.VLESSSettings.Fallback( + fallback.name, + fallback.alpn, + fallback.path, + fallback.dest, + fallback.xver, + )) + } + return fallbacks; + } +}; + +Inbound.TrojanSettings = class extends Inbound.Settings { + constructor(protocol, + trojans=[new Inbound.TrojanSettings.Trojan()], + fallbacks=[],) { + super(protocol); + this.trojans = trojans; + this.fallbacks = fallbacks; + } + + addTrojanFallback() { + this.fallbacks.push(new Inbound.TrojanSettings.Fallback()); + } + + delTrojanFallback(index) { + this.fallbacks.splice(index, 1); + } + + static fromJson(json={}) { + return new Inbound.TrojanSettings( + Protocols.TROJAN, + json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)), + Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),); + } + + toJson() { + return { + clients: Inbound.TrojanSettings.toJsonArray(this.trojans), + fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks), + }; + } +}; +Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { + constructor(password=RandomUtil.randomSeq(10), flow = XTLS_FLOW_CONTROL.DIRECT, email='', totalGB=0, expiryTime='') { + super(); + this.password = password; + this.flow = flow; + this.email = email; + this.totalGB = totalGB; + this.expiryTime = expiryTime; + } + + toJson() { + return { + password: this.password, + flow: this.flow, + email: this.email, + totalGB: this.totalGB, + expiryTime: this.expiryTime, + }; + } + + static fromJson(json={}) { + return new Inbound.TrojanSettings.Trojan( + json.password, + json.flow, + json.email, + json.totalGB, + json.expiryTime, + + ); + } + + get _expiryTime() { + if (this.expiryTime === 0 || this.expiryTime === "") { + return null; + } + 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.TrojanSettings.Fallback = class extends XrayCommonClass { + constructor(name="", alpn='', path='', dest='', xver=0) { + super(); + this.name = name; + this.alpn = alpn; + this.path = path; + this.dest = dest; + this.xver = xver; + } + + toJson() { + let xver = this.xver; + if (!Number.isInteger(xver)) { + xver = 0; + } + return { + name: this.name, + alpn: this.alpn, + path: this.path, + dest: this.dest, + xver: xver, + } + } + + static fromJson(json=[]) { + const fallbacks = []; + for (let fallback of json) { + fallbacks.push(new Inbound.TrojanSettings.Fallback( + fallback.name, + fallback.alpn, + fallback.path, + fallback.dest, + fallback.xver, + )) + } + return fallbacks; + } +}; + +Inbound.ShadowsocksSettings = class extends Inbound.Settings { + constructor(protocol, + method = SSMethods.BLAKE3_AES_256_GCM, + password = RandomUtil.randomSeq(44), + network = 'tcp,udp' + ) { + super(protocol); + this.method = method; + this.password = password; + this.network = network; + } + + static fromJson(json = {}) { + return new Inbound.ShadowsocksSettings( + Protocols.SHADOWSOCKS, + json.method, + json.password, + json.network, + ); + } + + toJson() { + return { + method: this.method, + password: this.password, + network: this.network, + }; + } +}; + +Inbound.DokodemoSettings = class extends Inbound.Settings { + constructor(protocol, address, port, network='tcp,udp') { + super(protocol); + this.address = address; + this.port = port; + this.network = network; + } + + static fromJson(json={}) { + return new Inbound.DokodemoSettings( + Protocols.DOKODEMO, + json.address, + json.port, + json.network, + ); + } + + toJson() { + return { + address: this.address, + port: this.port, + network: this.network, + }; + } +}; + +Inbound.MtprotoSettings = class extends Inbound.Settings { + constructor(protocol, users=[new Inbound.MtprotoSettings.MtUser()]) { + super(protocol); + this.users = users; + } + + static fromJson(json={}) { + return new Inbound.MtprotoSettings( + Protocols.MTPROTO, + json.users.map(user => Inbound.MtprotoSettings.MtUser.fromJson(user)), + ); + } + + toJson() { + return { + users: XrayCommonClass.toJsonArray(this.users), + }; + } +}; +Inbound.MtprotoSettings.MtUser = class extends XrayCommonClass { + constructor(secret=RandomUtil.randomMTSecret()) { + super(); + this.secret = secret; + } + + static fromJson(json={}) { + return new Inbound.MtprotoSettings.MtUser(json.secret); + } +}; + +Inbound.SocksSettings = class extends Inbound.Settings { + constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') { + super(protocol); + this.auth = auth; + this.accounts = accounts; + this.udp = udp; + this.ip = ip; + } + + addAccount(account) { + this.accounts.push(account); + } + + delAccount(index) { + this.accounts.splice(index, 1); + } + + static fromJson(json={}) { + let accounts; + if (json.auth === 'password') { + accounts = json.accounts.map( + account => Inbound.SocksSettings.SocksAccount.fromJson(account) + ) + } + return new Inbound.SocksSettings( + Protocols.SOCKS, + json.auth, + accounts, + json.udp, + json.ip, + ); + } + + toJson() { + return { + auth: this.auth, + accounts: this.auth === 'password' ? this.accounts.map(account => account.toJson()) : undefined, + udp: this.udp, + ip: this.ip, + }; + } +}; +Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass { + constructor(user=RandomUtil.randomSeq(10), pass=RandomUtil.randomSeq(10)) { + super(); + this.user = user; + this.pass = pass; + } + + static fromJson(json={}) { + return new Inbound.SocksSettings.SocksAccount(json.user, json.pass); + } +}; + +Inbound.HttpSettings = class extends Inbound.Settings { + constructor(protocol, accounts=[new Inbound.HttpSettings.HttpAccount()]) { + super(protocol); + this.accounts = accounts; + } + + addAccount(account) { + this.accounts.push(account); + } + + delAccount(index) { + this.accounts.splice(index, 1); + } + + static fromJson(json={}) { + return new Inbound.HttpSettings( + Protocols.HTTP, + json.accounts.map(account => Inbound.HttpSettings.HttpAccount.fromJson(account)), + ); + } + + toJson() { + return { + accounts: Inbound.HttpSettings.toJsonArray(this.accounts), + }; + } +}; + +Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass { + constructor(user=RandomUtil.randomSeq(10), pass=RandomUtil.randomSeq(10)) { + super(); + this.user = user; + this.pass = pass; + } + + static fromJson(json={}) { + return new Inbound.HttpSettings.HttpAccount(json.user, json.pass); + } +}; -- cgit v1.2.3