diff options
Diffstat (limited to 'web/assets/js/model/xray.js')
| -rw-r--r-- | web/assets/js/model/xray.js | 1819 |
1 files changed, 1819 insertions, 0 deletions
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",
|
