2023-10-12 17:38:51 +08:00
/ *
* SPDX - License - Identifier : GPL - 3.0 - only
*
* Copyright ( C ) 2022 ImmortalWrt . org
* /
'use strict' ;
'require form' ;
'require fs' ;
'require uci' ;
'require ui' ;
'require view' ;
'require homeproxy as hp' ;
'require tools.widgets as widgets' ;
function allowInsecureConfirm ( ev , section _id , value ) {
if ( value === '1' && ! confirm ( _ ( 'Are you sure to allow insecure?' ) ) )
ev . target . firstElementChild . checked = null ;
}
function parseShareLink ( uri , features ) {
var config ;
uri = uri . split ( '://' ) ;
if ( uri [ 0 ] && uri [ 1 ] ) {
switch ( uri [ 0 ] ) {
case 'http' :
case 'https' :
var url = new URL ( 'http://' + uri [ 1 ] ) ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'http' ,
address : url . hostname ,
port : url . port || '80' ,
username : url . username ? decodeURIComponent ( url . username ) : null ,
password : url . password ? decodeURIComponent ( url . password ) : null ,
tls : ( uri [ 0 ] === 'https' ) ? '1' : '0'
} ;
break ;
case 'hysteria' :
/* https://github.com/HyNetwork/hysteria/wiki/URI-Scheme */
var url = new URL ( 'http://' + uri [ 1 ] ) ;
var params = url . searchParams ;
/* WeChat-Video / FakeTCP are unsupported by sing-box currently */
if ( ! features . with _quic || ( params . get ( 'protocol' ) && params . get ( 'protocol' ) !== 'udp' ) )
return null ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'hysteria' ,
address : url . hostname ,
port : url . port || '80' ,
hysteria _protocol : params . get ( 'protocol' ) || 'udp' ,
hysteria _auth _type : params . get ( 'auth' ) ? 'string' : null ,
hysteria _auth _payload : params . get ( 'auth' ) ,
hysteria _obfs _password : params . get ( 'obfsParam' ) ,
hysteria _down _mbps : params . get ( 'downmbps' ) ,
hysteria _up _mbps : params . get ( 'upmbps' ) ,
tls : '1' ,
tls _sni : params . get ( 'peer' ) ,
tls _alpn : params . get ( 'alpn' ) ,
tls _insecure : params . get ( 'insecure' ) ? '1' : '0'
} ;
break ;
case 'hysteria2' :
case 'hy2' :
/* https://v2.hysteria.network/docs/developers/URI-Scheme/ */
var url = new URL ( 'http://' + uri [ 1 ] ) ;
var params = url . searchParams ;
/* userpass auth is not supported by sing-box */
if ( ! features . with _quic || url . password )
return null ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'hysteria2' ,
address : url . hostname ,
port : url . port || '80' ,
password : url . username ? decodeURIComponent ( url . username ) : null ,
hysteria _obfs _type : params . get ( 'obfs' ) ,
hysteria _obfs _password : params . get ( 'obfs-password' ) ,
tls : '1' ,
tls _sni : params . get ( 'sni' ) ,
tls _insecure : params . get ( 'insecure' ) ? '1' : '0'
} ;
break ;
case 'socks' :
case 'socks4' :
case 'socks4a' :
case 'socsk5' :
case 'socks5h' :
var url = new URL ( 'http://' + uri [ 1 ] ) ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'socks' ,
address : url . hostname ,
port : url . port || '80' ,
username : url . username ? decodeURIComponent ( url . username ) : null ,
password : url . password ? decodeURIComponent ( url . password ) : null ,
socks _version : ( uri [ 0 ] . includes ( '4' ) ) ? '4' : '5'
} ;
break ;
case 'ss' :
try {
/* "Lovely" Shadowrocket format */
try {
var suri = uri [ 1 ] . split ( '#' ) , slabel = '' ;
if ( suri . length <= 2 ) {
if ( suri . length === 2 )
slabel = '#' + suri [ 1 ] ;
uri [ 1 ] = hp . decodeBase64Str ( suri [ 0 ] ) + slabel ;
}
} catch ( e ) { }
/* SIP002 format https://shadowsocks.org/guide/sip002.html */
var url = new URL ( 'http://' + uri [ 1 ] ) ;
var userinfo ;
if ( url . username && url . password )
/* User info encoded with URIComponent */
userinfo = [ url . username , decodeURIComponent ( url . password ) ] ;
else if ( url . username )
/* User info encoded with base64 */
userinfo = hp . decodeBase64Str ( decodeURIComponent ( url . username ) ) . split ( ':' ) ;
if ( ! hp . shadowsocks _encrypt _methods . includes ( userinfo [ 0 ] ) )
return null ;
var plugin , plugin _opts ;
if ( url . search && url . searchParams . get ( 'plugin' ) ) {
var plugin _info = url . searchParams . get ( 'plugin' ) . split ( ';' ) ;
plugin = plugin _info [ 0 ] ;
plugin _opts = plugin _info . slice ( 1 ) ? plugin _info . slice ( 1 ) . join ( ';' ) : null ;
}
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'shadowsocks' ,
address : url . hostname ,
port : url . port || '80' ,
shadowsocks _encrypt _method : userinfo [ 0 ] ,
password : userinfo [ 1 ] ,
shadowsocks _plugin : plugin ,
shadowsocks _plugin _opts : plugin _opts
} ;
} catch ( e ) {
/* Legacy format https://github.com/shadowsocks/shadowsocks-org/commit/78ca46cd6859a4e9475953ed34a2d301454f579e */
uri = uri [ 1 ] . split ( '@' ) ;
if ( uri . length < 2 )
return null ;
else if ( uri . length > 2 )
uri = [ uri . slice ( 0 , - 1 ) . join ( '@' ) , uri . slice ( - 1 ) . toString ( ) ] ;
config = {
type : 'shadowsocks' ,
address : uri [ 1 ] . split ( ':' ) [ 0 ] ,
port : uri [ 1 ] . split ( ':' ) [ 1 ] ,
shadowsocks _encrypt _method : uri [ 0 ] . split ( ':' ) [ 0 ] ,
password : uri [ 0 ] . split ( ':' ) . slice ( 1 ) . join ( ':' )
} ;
}
break ;
case 'ssr' :
/* https://coderschool.cn/2498.html */
uri = hp . decodeBase64Str ( uri [ 1 ] ) . split ( '/' ) ;
var userinfo = uri [ 0 ] . split ( ':' )
/* Check if method and password exist */
if ( ! features . with _shadowsocksr || ! userinfo [ 3 ] || ! userinfo [ 5 ] )
return null ;
var params = new URLSearchParams ( uri [ 1 ] ) ;
var protoparam = hp . decodeBase64Str ( params . get ( 'protoparam' ) ) ;
var obfsparam = hp . decodeBase64Str ( params . get ( 'obfsparam' ) ) ;
var remarks = hp . decodeBase64Str ( params . get ( 'remarks' ) ) ;
config = {
label : remarks ,
type : 'shadowsocksr' ,
address : userinfo [ 0 ] ,
port : userinfo [ 1 ] ,
shadowsocksr _encrypt _method : userinfo [ 3 ] ,
password : hp . decodeBase64Str ( userinfo [ 5 ] ) ,
shadowsocksr _protocol : userinfo [ 2 ] ,
shadowsocksr _protocol _param : protoparam ,
shadowsocksr _obfs : userinfo [ 4 ] ,
shadowsocksr _obfs _param : obfsparam
} ;
break ;
case 'trojan' :
/* https://p4gefau1t.github.io/trojan-go/developer/url/ */
var url = new URL ( 'http://' + uri [ 1 ] ) ;
var params = url . searchParams ;
/* Check if password exists */
if ( ! url . username )
return null ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'trojan' ,
address : url . hostname ,
port : url . port || '80' ,
password : decodeURIComponent ( url . username ) ,
transport : params . get ( 'type' ) !== 'tcp' ? params . get ( 'type' ) : null ,
tls : '1' ,
tls _sni : params . get ( 'sni' )
} ;
switch ( params . get ( 'type' ) ) {
case 'grpc' :
config . grpc _servicename = params . get ( 'serviceName' ) ;
break ;
case 'ws' :
/ * W e d o n ' t p a r s e " h o s t " p a r a m w h e n T L S i s e n a b l e d , a s s o m e p r o v i d e r s a r e a b u s i n g i t ( h o s t v s s n i )
* config . ws _host = params . get ( 'host' ) ? decodeURIComponent ( params . get ( 'host' ) ) : null ;
* /
config . ws _path = params . get ( 'path' ) ? decodeURIComponent ( params . get ( 'path' ) ) : null ;
if ( config . ws _path && config . ws _path . includes ( '?ed=' ) ) {
config . websocket _early _data _header = 'Sec-WebSocket-Protocol' ;
config . websocket _early _data = config . ws _path . split ( '?ed=' ) [ 1 ] ;
config . ws _path = config . ws _path . split ( '?ed=' ) [ 0 ] ;
}
break ;
}
break ;
case 'tuic' :
/* https://github.com/daeuniverse/dae/discussions/182 */
var url = new URL ( 'http://' + uri [ 1 ] ) ;
var params = url . searchParams ;
/* Check if uuid exists */
if ( ! url . username )
return null ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'tuic' ,
address : url . hostname ,
port : url . port || '80' ,
uuid : url . username ,
password : url . password ? decodeURIComponent ( url . password ) : null ,
tuic _congestion _control : params . get ( 'congestion_control' ) ,
tuic _udp _relay _mode : params . get ( 'udp_relay_mode' ) ,
tls : '1' ,
tls _sni : params . get ( 'sni' ) ,
tls _alpn : params . get ( 'alpn' ) ? decodeURIComponent ( params . get ( 'alpn' ) ) . split ( ',' ) : null
} ;
break ;
case 'vless' :
/* https://github.com/XTLS/Xray-core/discussions/716 */
var url = new URL ( 'http://' + uri [ 1 ] ) ;
var params = url . searchParams ;
/* Unsupported protocol */
if ( params . get ( 'type' ) === 'kcp' )
return null ;
else if ( params . get ( 'type' ) === 'quic' && ( ( params . get ( 'quicSecurity' ) && params . get ( 'quicSecurity' ) !== 'none' || ! features . with _quic ) ) )
return null ;
/* Check if uuid and type exist */
if ( ! url . username || ! params . get ( 'type' ) )
return null ;
config = {
label : url . hash ? decodeURIComponent ( url . hash . slice ( 1 ) ) : null ,
type : 'vless' ,
address : url . hostname ,
port : url . port || '80' ,
uuid : url . username ,
transport : params . get ( 'type' ) !== 'tcp' ? params . get ( 'type' ) : null ,
tls : [ 'tls' , 'xtls' , 'reality' ] . includes ( params . get ( 'security' ) ) ? '1' : '0' ,
tls _sni : params . get ( 'sni' ) ,
tls _alpn : params . get ( 'alpn' ) ? decodeURIComponent ( params . get ( 'alpn' ) ) . split ( ',' ) : null ,
tls _reality : ( params . get ( 'security' ) === 'reality' ) ? '1' : '0' ,
tls _reality _public _key : params . get ( 'pbk' ) ? decodeURIComponent ( params . get ( 'pbk' ) ) : null ,
tls _reality _short _id : params . get ( 'sid' ) ,
tls _utls : features . with _utls ? params . get ( 'fp' ) : null ,
vless _flow : [ 'tls' , 'reality' ] . includes ( params . get ( 'security' ) ) ? params . get ( 'flow' ) : null
} ;
switch ( params . get ( 'type' ) ) {
case 'grpc' :
config . grpc _servicename = params . get ( 'serviceName' ) ;
break ;
case 'http' :
case 'tcp' :
if ( config . transport === 'http' || params . get ( 'headerType' ) === 'http' ) {
config . http _host = params . get ( 'host' ) ? decodeURIComponent ( params . get ( 'host' ) ) . split ( ',' ) : null ;
config . http _path = params . get ( 'path' ) ? decodeURIComponent ( params . get ( 'path' ) ) : null ;
}
break ;
case 'ws' :
/* We don't parse "host" param when TLS is enabled, as some providers are abusing it (host vs sni) */
config . ws _host = ( config . tls !== '1' && params . get ( 'host' ) ) ? decodeURIComponent ( params . get ( 'host' ) ) : null ;
config . ws _path = params . get ( 'path' ) ? decodeURIComponent ( params . get ( 'path' ) ) : null ;
if ( config . ws _path && config . ws _path . includes ( '?ed=' ) ) {
config . websocket _early _data _header = 'Sec-WebSocket-Protocol' ;
config . websocket _early _data = config . ws _path . split ( '?ed=' ) [ 1 ] ;
config . ws _path = config . ws _path . split ( '?ed=' ) [ 0 ] ;
}
break ;
}
break ;
case 'vmess' :
/* "Lovely" shadowrocket format */
if ( uri . includes ( '&' ) )
return null ;
/* https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) */
uri = JSON . parse ( hp . decodeBase64Str ( uri [ 1 ] ) ) ;
if ( uri . v !== '2' )
return null ;
/* Unsupported protocols */
else if ( uri . net === 'kcp' )
return null ;
else if ( uri . net === 'quic' && ( ( uri . type && uri . type !== 'none' ) || ! features . with _quic ) )
return null ;
/* https:/ / www . v2fly . org / config / protocols / vmess . html # vmess - md5 - % E8 % AE % A4 % E8 % AF % 81 % E4 % BF % A1 % E6 % 81 % AF - % E6 % B7 % 98 % E6 % B1 % B0 % E6 % 9 C % BA % E5 % 88 % B6
* else if ( uri . aid && parseInt ( uri . aid ) !== 0 )
* return null ;
* /
config = {
label : uri . ps ,
type : 'vmess' ,
address : uri . add ,
port : uri . port ,
uuid : uri . id ,
vmess _alterid : uri . aid ,
vmess _encrypt : uri . scy || 'auto' ,
transport : ( uri . net !== 'tcp' ) ? uri . net : null ,
tls : uri . tls === 'tls' ? '1' : '0' ,
tls _sni : uri . sni || uri . host ,
tls _alpn : uri . alpn ? uri . alpn . split ( ',' ) : null
} ;
switch ( uri . net ) {
case 'grpc' :
config . grpc _servicename = uri . path ;
break ;
case 'h2' :
case 'tcp' :
if ( uri . net === 'h2' || uri . type === 'http' ) {
config . transport = 'http' ;
config . http _host = uri . host ? uri . host . split ( ',' ) : null ;
config . http _path = uri . path ;
}
break ;
case 'ws' :
/* We don't parse "host" param when TLS is enabled, as some providers are abusing it (host vs sni) */
config . ws _host = ( config . tls !== '1' ) ? uri . host : null ;
config . ws _path = uri . path ;
if ( config . ws _path && config . ws _path . includes ( '?ed=' ) ) {
config . websocket _early _data _header = 'Sec-WebSocket-Protocol' ;
config . websocket _early _data = config . ws _path . split ( '?ed=' ) [ 1 ] ;
config . ws _path = config . ws _path . split ( '?ed=' ) [ 0 ] ;
}
break ;
}
break ;
}
}
if ( config ) {
if ( ! config . address || ! config . port )
return null ;
else if ( ! config . label )
config . label = config . address + ':' + config . port ;
config . address = config . address . replace ( /\[|\]/g , '' ) ;
}
return config ;
}
return view . extend ( {
load : function ( ) {
return Promise . all ( [
uci . load ( 'homeproxy' ) ,
hp . getBuiltinFeatures ( )
] ) ;
} ,
render : function ( data ) {
var m , s , o , ss , so ;
var main _node = uci . get ( data [ 0 ] , 'config' , 'main_node' ) ;
var routing _mode = uci . get ( data [ 0 ] , 'config' , 'routing_mode' ) ;
var features = data [ 1 ] ;
m = new form . Map ( 'homeproxy' , _ ( 'Edit nodes' ) ) ;
s = m . section ( form . NamedSection , 'subscription' , 'homeproxy' ) ;
/* Nodes settings start */
s . tab ( 'node' , _ ( 'Nodes' ) ) ;
o = s . taboption ( 'node' , form . SectionValue , '_node' , form . GridSection , 'node' ) ;
ss = o . subsection ;
ss . addremove = true ;
ss . rowcolors = true ;
ss . sortable = true ;
ss . nodescriptions = true ;
ss . modaltitle = L . bind ( hp . loadModalTitle , this , _ ( 'Node' ) , _ ( 'Add a node' ) , data [ 0 ] ) ;
ss . sectiontitle = L . bind ( hp . loadDefaultLabel , this , data [ 0 ] ) ;
/* Import subscription links start */
/* Thanks to luci-app-shadowsocks-libev */
ss . handleLinkImport = function ( ) {
var textarea = new ui . Textarea ( ) ;
ui . showModal ( _ ( 'Import share links' ) , [
E ( 'p' , _ ( 'Support Hysteria, Shadowsocks(R), Trojan, v2rayN (VMess), and XTLS (VLESS) online configuration delivery standard.' ) ) ,
textarea . render ( ) ,
E ( 'div' , { class : 'right' } , [
E ( 'button' , {
class : 'btn' ,
click : ui . hideModal
} , [ _ ( 'Cancel' ) ] ) ,
'' ,
E ( 'button' , {
class : 'btn cbi-button-action' ,
click : ui . createHandlerFn ( this , function ( ) {
var input _links = textarea . getValue ( ) . trim ( ) . split ( '\n' ) ;
if ( input _links && input _links [ 0 ] ) {
/* Remove duplicate lines */
input _links = input _links . reduce ( ( pre , cur ) =>
( ! pre . includes ( cur ) && pre . push ( cur ) , pre ) , [ ] ) ;
var allow _insecure = uci . get ( data [ 0 ] , 'subscription' , 'allow_insecure' ) ;
var packet _encoding = uci . get ( data [ 0 ] , 'subscription' , 'packet_encoding' ) ;
var imported _node = 0 ;
input _links . forEach ( ( l ) => {
var config = parseShareLink ( l , features ) ;
if ( config ) {
if ( config . tls === '1' && allow _insecure === '1' )
config . tls _insecure = '1'
if ( [ 'vless' , 'vmess' ] . includes ( config . type ) )
config . packet _encoding = packet _encoding
var nameHash = hp . calcStringMD5 ( config . label ) ;
var sid = uci . add ( data [ 0 ] , 'node' , nameHash ) ;
Object . keys ( config ) . forEach ( ( k ) => {
uci . set ( data [ 0 ] , sid , k , config [ k ] ) ;
} ) ;
imported _node ++ ;
}
} ) ;
if ( imported _node === 0 )
ui . addNotification ( null , E ( 'p' , _ ( 'No valid share link found.' ) ) ) ;
else
ui . addNotification ( null , E ( 'p' , _ ( 'Successfully imported %s nodes of total %s.' ) . format (
imported _node , input _links . length ) ) ) ;
return uci . save ( )
. then ( L . bind ( this . map . load , this . map ) )
. then ( L . bind ( this . map . reset , this . map ) )
. then ( L . ui . hideModal )
. catch ( function ( ) { } ) ;
} else {
return ui . hideModal ( ) ;
}
} )
} , [ _ ( 'Import' ) ] )
] )
] )
}
ss . renderSectionAdd = function ( extra _class ) {
var el = form . GridSection . prototype . renderSectionAdd . apply ( this , arguments ) ,
nameEl = el . querySelector ( '.cbi-section-create-name' ) ;
ui . addValidator ( nameEl , 'uciname' , true , ( v ) => {
var button = el . querySelector ( '.cbi-section-create > .cbi-button-add' ) ;
var uciconfig = this . uciconfig || this . map . config ;
if ( ! v ) {
button . disabled = true ;
return true ;
} else if ( uci . get ( uciconfig , v ) ) {
button . disabled = true ;
return _ ( 'Expecting: %s' ) . format ( _ ( 'unique UCI identifier' ) ) ;
} else {
button . disabled = null ;
return true ;
}
} , 'blur' , 'keyup' ) ;
el . appendChild ( E ( 'button' , {
'class' : 'cbi-button cbi-button-add' ,
'title' : _ ( 'Import share links' ) ,
'click' : ui . createHandlerFn ( this , 'handleLinkImport' )
} , [ _ ( 'Import share links' ) ] ) ) ;
return el ;
}
/* Import subscription links end */
if ( routing _mode !== 'custom' ) {
so = ss . option ( form . Button , '_apply' , _ ( 'Apply' ) ) ;
so . editable = true ;
so . modalonly = false ;
so . inputstyle = 'apply' ;
so . inputtitle = function ( section _id ) {
if ( main _node == section _id ) {
this . readonly = true ;
return _ ( 'Applied' ) ;
} else {
this . readonly = false ;
return _ ( 'Apply' ) ;
}
}
so . onclick = function ( ev , section _id ) {
uci . set ( data [ 0 ] , 'config' , 'main_node' , section _id ) ;
ui . changes . apply ( true ) ;
return this . map . save ( null , true ) ;
}
}
so = ss . option ( form . Value , 'label' , _ ( 'Label' ) ) ;
so . load = L . bind ( hp . loadDefaultLabel , this , data [ 0 ] ) ;
so . validate = L . bind ( hp . validateUniqueValue , this , data [ 0 ] , 'node' , 'label' ) ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'type' , _ ( 'Type' ) ) ;
so . value ( 'direct' , _ ( 'Direct' ) ) ;
so . value ( 'http' , _ ( 'HTTP' ) ) ;
if ( features . with _quic ) {
so . value ( 'hysteria' , _ ( 'Hysteria' ) ) ;
so . value ( 'hysteria2' , _ ( 'Hysteria2' ) ) ;
}
so . value ( 'shadowsocks' , _ ( 'Shadowsocks' ) ) ;
if ( features . with _shadowsocksr )
so . value ( 'shadowsocksr' , _ ( 'ShadowsocksR' ) ) ;
so . value ( 'shadowtls' , _ ( 'ShadowTLS' ) ) ;
so . value ( 'socks' , _ ( 'Socks' ) ) ;
so . value ( 'trojan' , _ ( 'Trojan' ) ) ;
if ( features . with _quic )
so . value ( 'tuic' , _ ( 'Tuic' ) ) ;
if ( features . with _wireguard )
so . value ( 'wireguard' , _ ( 'WireGuard' ) ) ;
so . value ( 'vless' , _ ( 'VLESS' ) ) ;
so . value ( 'vmess' , _ ( 'VMess' ) ) ;
so . rmempty = false ;
so = ss . option ( form . Value , 'address' , _ ( 'Address' ) ) ;
so . datatype = 'host' ;
so . depends ( { 'type' : 'direct' , '!reverse' : true } ) ;
so . rmempty = false ;
so = ss . option ( form . Value , 'port' , _ ( 'Port' ) ) ;
so . datatype = 'port' ;
so . depends ( { 'type' : 'direct' , '!reverse' : true } ) ;
so . rmempty = false ;
so = ss . option ( form . Value , 'username' , _ ( 'Username' ) ) ;
so . depends ( 'type' , 'http' ) ;
so . depends ( 'type' , 'socks' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'password' , _ ( 'Password' ) ) ;
so . password = true ;
so . depends ( 'type' , 'http' ) ;
so . depends ( 'type' , 'hysteria2' ) ;
so . depends ( 'type' , 'shadowsocks' ) ;
so . depends ( 'type' , 'shadowsocksr' ) ;
so . depends ( 'type' , 'trojan' ) ;
so . depends ( 'type' , 'tuic' ) ;
so . depends ( { 'type' : 'shadowtls' , 'shadowtls_version' : '2' } ) ;
so . depends ( { 'type' : 'shadowtls' , 'shadowtls_version' : '3' } ) ;
so . depends ( { 'type' : 'socks' , 'socks_version' : '5' } ) ;
so . validate = function ( section _id , value ) {
if ( section _id ) {
var type = this . map . lookupOption ( 'type' , section _id ) [ 0 ] . formvalue ( section _id ) ;
var required _type = [ 'shadowsocks' , 'shadowsocksr' , 'shadowtls' , 'trojan' ] ;
if ( required _type . includes ( type ) ) {
if ( type === 'shadowsocks' ) {
var encmode = this . map . lookupOption ( 'shadowsocks_encrypt_method' , section _id ) [ 0 ] . formvalue ( section _id ) ;
if ( encmode === 'none' )
return true ;
else if ( encmode === '2022-blake3-aes-128-gcm' )
return hp . validateBase64Key ( 24 , section _id , value ) ;
else if ( [ '2022-blake3-aes-256-gcm' , '2022-blake3-chacha20-poly1305' ] . includes ( encmode ) )
return hp . validateBase64Key ( 44 , section _id , value ) ;
}
if ( ! value )
return _ ( 'Expecting: %s' ) . format ( _ ( 'non-empty value' ) ) ;
}
}
return true ;
}
so . modalonly = true ;
/* Direct config */
so = ss . option ( form . Value , 'override_address' , _ ( 'Override address' ) ,
_ ( 'Override the connection destination address.' ) ) ;
so . datatype = 'host' ;
so . depends ( 'type' , 'direct' ) ;
so = ss . option ( form . Value , 'override_port' , _ ( 'Override port' ) ,
_ ( 'Override the connection destination port.' ) ) ;
so . datatype = 'port' ;
so . depends ( 'type' , 'direct' ) ;
/* Hysteria (2) config start */
so = ss . option ( form . ListValue , 'hysteria_protocol' , _ ( 'Protocol' ) ) ;
so . value ( 'udp' ) ;
/* WeChat-Video / FakeTCP are unsupported by sing - box currently
* so . value ( 'wechat-video' ) ;
* so . value ( 'faketcp' ) ;
* /
so . default = 'udp' ;
so . depends ( 'type' , 'hysteria' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'hysteria_auth_type' , _ ( 'Authentication type' ) ) ;
so . value ( '' , _ ( 'Disable' ) ) ;
so . value ( 'base64' , _ ( 'Base64' ) ) ;
so . value ( 'string' , _ ( 'String' ) ) ;
so . depends ( 'type' , 'hysteria' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'hysteria_auth_payload' , _ ( 'Authentication payload' ) ) ;
so . depends ( { 'type' : 'hysteria' , 'hysteria_auth_type' : /[\s\S]/ } ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'hysteria_obfs_type' , _ ( 'Obfuscate type' ) ) ;
so . value ( '' , _ ( 'Disable' ) ) ;
so . value ( 'salamander' , _ ( 'Salamander' ) ) ;
so . depends ( 'type' , 'hysteria2' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'hysteria_obfs_password' , _ ( 'Obfuscate password' ) ) ;
so . depends ( 'type' , 'hysteria' ) ;
so . depends ( { 'type' : 'hysteria2' , 'hysteria_obfs_type' : /[\s\S]/ } ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'hysteria_down_mbps' , _ ( 'Max download speed' ) ,
_ ( 'Max download speed in Mbps.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'type' , 'hysteria' ) ;
so . depends ( 'type' , 'hysteria2' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'hysteria_up_mbps' , _ ( 'Max upload speed' ) ,
_ ( 'Max upload speed in Mbps.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'type' , 'hysteria' ) ;
so . depends ( 'type' , 'hysteria2' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'hysteria_recv_window_conn' , _ ( 'QUIC stream receive window' ) ,
_ ( 'The QUIC stream-level flow control window for receiving data.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'type' , 'hysteria' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'hysteria_revc_window' , _ ( 'QUIC connection receive window' ) ,
_ ( 'The QUIC connection-level flow control window for receiving data.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'type' , 'hysteria' ) ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'hysteria_disable_mtu_discovery' , _ ( 'Disable Path MTU discovery' ) ,
_ ( 'Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.' ) ) ;
so . default = so . disabled ;
so . depends ( 'type' , 'hysteria' ) ;
so . modalonly = true ;
/* Hysteria (2) config end */
/* Shadowsocks config start */
so = ss . option ( form . ListValue , 'shadowsocks_encrypt_method' , _ ( 'Encrypt method' ) ) ;
for ( var i of hp . shadowsocks _encrypt _methods )
so . value ( i ) ;
/* Stream ciphers */
so . value ( 'aes-128-ctr' ) ;
so . value ( 'aes-192-ctr' ) ;
so . value ( 'aes-256-ctr' ) ;
so . value ( 'aes-128-cfb' ) ;
so . value ( 'aes-192-cfb' ) ;
so . value ( 'aes-256-cfb' ) ;
so . value ( 'chacha20' ) ;
so . value ( 'chacha20-ietf' ) ;
so . value ( 'rc4-md5' ) ;
so . default = 'aes-128-gcm' ;
so . depends ( 'type' , 'shadowsocks' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'shadowsocks_plugin' , _ ( 'Plugin' ) ) ;
so . value ( '' , _ ( 'none' ) ) ;
so . value ( 'obfs-local' ) ;
so . value ( 'v2ray-plugin' ) ;
so . depends ( 'type' , 'shadowsocks' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'shadowsocks_plugin_opts' , _ ( 'Plugin opts' ) ) ;
so . depends ( 'shadowsocks_plugin' , 'obfs-local' ) ;
so . depends ( 'shadowsocks_plugin' , 'v2ray-plugin' ) ;
so . modalonly = true ;
/* Shadowsocks config end */
/* ShadowsocksR config start */
so = ss . option ( form . ListValue , 'shadowsocksr_encrypt_method' , _ ( 'Encrypt method' ) ) ;
so . value ( 'none' ) ;
so . value ( 'table' ) ;
so . value ( 'rc4' ) ;
so . value ( 'rc4-md5-6' ) ;
so . value ( 'rc4-md5' ) ;
so . value ( 'aes-128-cfb' ) ;
so . value ( 'aes-192-cfb' ) ;
so . value ( 'aes-256-cfb' ) ;
so . value ( 'aes-128-ctr' ) ;
so . value ( 'aes-192-ctr' ) ;
so . value ( 'aes-256-ctr' ) ;
so . value ( 'bf-cfb' ) ;
so . value ( 'camellia-128-cfb' ) ;
so . value ( 'camellia-192-cfb' ) ;
so . value ( 'camellia-256-cfb' ) ;
so . value ( 'cast5-cfb' ) ;
so . value ( 'des-cfb' ) ;
so . value ( 'idea-cfb' ) ;
so . value ( 'rc2-cfb' ) ;
so . value ( 'seed-cfb' ) ;
so . value ( 'salsa20' ) ;
so . value ( 'chacha20' ) ;
so . value ( 'chacha20-ietf' ) ;
so . depends ( 'type' , 'shadowsocksr' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'shadowsocksr_protocol' , _ ( 'Protocol' ) ) ;
so . value ( 'origin' ) ;
so . value ( 'verify_deflate' ) ;
so . value ( 'auth_sha1_v4' ) ;
so . value ( 'auth_aes128_sha1' ) ;
so . value ( 'auth_aes128_md5' ) ;
so . value ( 'auth_chain_a' ) ;
so . value ( 'auth_chain_b' ) ;
so . value ( 'auth_chain_c' ) ;
so . value ( 'auth_chain_d' ) ;
so . value ( 'auth_chain_e' ) ;
so . value ( 'auth_chain_f' ) ;
so . depends ( 'type' , 'shadowsocksr' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'shadowsocksr_protocol_param' , _ ( 'Protocol param' ) ) ;
so . depends ( 'type' , 'shadowsocksr' ) ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'shadowsocksr_obfs' , _ ( 'Obfs' ) ) ;
so . value ( 'plain' ) ;
so . value ( 'http_simple' ) ;
so . value ( 'http_post' ) ;
so . value ( 'random_head' ) ;
so . value ( 'tls1.2_ticket_auth' ) ;
so . depends ( 'type' , 'shadowsocksr' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'shadowsocksr_obfs_param' , _ ( 'Obfs param' ) ) ;
so . depends ( 'type' , 'shadowsocksr' ) ;
so . modalonly = true ;
/* ShadowsocksR config end */
/* ShadowTLS config */
so = ss . option ( form . ListValue , 'shadowtls_version' , _ ( 'ShadowTLS version' ) ) ;
so . value ( '1' , _ ( 'v1' ) ) ;
so . value ( '2' , _ ( 'v2' ) ) ;
so . value ( '3' , _ ( 'v3' ) ) ;
so . default = '1' ;
so . depends ( 'type' , 'shadowtls' ) ;
so . rmempty = false ;
so . modalonly = true ;
/* Socks config */
so = ss . option ( form . ListValue , 'socks_version' , _ ( 'Socks version' ) ) ;
so . value ( '4' , _ ( 'Socks4' ) ) ;
so . value ( '4a' , _ ( 'Socks4A' ) ) ;
so . value ( '5' , _ ( 'Socks5' ) ) ;
so . default = '5' ;
so . depends ( 'type' , 'socks' ) ;
so . rmempty = false ;
so . modalonly = true ;
/* TUIC config start */
so = ss . option ( form . Value , 'uuid' , _ ( 'UUID' ) ) ;
so . depends ( 'type' , 'tuic' ) ;
so . depends ( 'type' , 'vless' ) ;
so . depends ( 'type' , 'vmess' ) ;
so . validate = hp . validateUUID ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'tuic_congestion_control' , _ ( 'Congestion control algorithm' ) ,
_ ( 'QUIC congestion control algorithm.' ) ) ;
so . value ( 'cubic' , _ ( 'CUBIC' ) ) ;
so . value ( 'new_reno' , _ ( 'New Reno' ) ) ;
so . value ( 'bbr' , _ ( 'BBR' ) ) ;
so . default = 'cubic' ;
so . depends ( 'type' , 'tuic' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'tuic_udp_relay_mode' , _ ( 'UDP relay mode' ) ,
_ ( 'UDP packet relay mode.' ) ) ;
so . value ( '' , _ ( 'Default' ) ) ;
so . value ( 'native' , _ ( 'Native' ) ) ;
so . value ( 'quic' , _ ( 'QUIC' ) ) ;
so . depends ( 'type' , 'tuic' ) ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'tuic_udp_over_stream' , _ ( 'UDP over stream' ) ,
_ ( 'This is the TUIC port of the UDP over TCP protocol, designed to provide a QUIC stream based UDP relay mode that TUIC does not provide.' ) ) ;
so . default = so . disabled ;
so . depends ( { 'type' : 'tuic' , 'tuic_udp_relay_mode' : '' } ) ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'tuic_enable_zero_rtt' , _ ( 'Enable 0-RTT handshake' ) ,
_ ( 'Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed.<br/>' +
'Disabling this is highly recommended, as it is vulnerable to replay attacks.' ) ) ;
so . default = so . disabled ;
so . depends ( 'type' , 'tuic' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'tuic_heartbeat' , _ ( 'Heartbeat interval' ) ,
_ ( 'Interval for sending heartbeat packets for keeping the connection alive (in seconds).' ) ) ;
so . datatype = 'uinteger' ;
so . default = '10' ;
so . depends ( 'type' , 'tuic' ) ;
so . modalonly = true ;
/* Tuic config end */
/* VMess / VLESS config start */
so = ss . option ( form . ListValue , 'vless_flow' , _ ( 'Flow' ) ) ;
so . value ( '' , _ ( 'None' ) ) ;
so . value ( 'xtls-rprx-vision' ) ;
so . depends ( 'type' , 'vless' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'vmess_alterid' , _ ( 'Alter ID' ) ,
_ ( 'Legacy protocol support (VMess MD5 Authentication) is provided for compatibility purposes only, use of alterId > 1 is not recommended.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'type' , 'vmess' ) ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'vmess_encrypt' , _ ( 'Encrypt method' ) ) ;
so . value ( 'auto' ) ;
so . value ( 'none' ) ;
so . value ( 'zero' ) ;
so . value ( 'aes-128-gcm' ) ;
so . value ( 'chacha20-poly1305' ) ;
so . default = 'auto' ;
so . depends ( 'type' , 'vmess' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'vmess_global_padding' , _ ( 'Global padding' ) ,
_ ( 'Protocol parameter. Will waste traffic randomly if enabled (enabled by default in v2ray and cannot be disabled).' ) ) ;
so . default = so . enabled ;
so . depends ( 'type' , 'vmess' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'vmess_authenticated_length' , _ ( 'Authenticated length' ) ,
_ ( 'Protocol parameter. Enable length block encryption.' ) ) ;
so . default = so . disabled ;
so . depends ( 'type' , 'vmess' ) ;
so . modalonly = true ;
/* VMess config end */
/* Transport config start */
so = ss . option ( form . ListValue , 'transport' , _ ( 'Transport' ) ,
_ ( 'No TCP transport, plain HTTP is merged into the HTTP transport.' ) ) ;
so . value ( '' , _ ( 'None' ) ) ;
so . value ( 'grpc' , _ ( 'gRPC' ) ) ;
so . value ( 'http' , _ ( 'HTTP' ) ) ;
so . value ( 'quic' , _ ( 'QUIC' ) ) ;
so . value ( 'ws' , _ ( 'WebSocket' ) ) ;
so . depends ( 'type' , 'trojan' ) ;
so . depends ( 'type' , 'vless' ) ;
so . depends ( 'type' , 'vmess' ) ;
so . onchange = function ( ev , section _id , value ) {
var desc = this . map . findElement ( 'id' , 'cbid.homeproxy.%s.transport' . format ( section _id ) ) . nextElementSibling ;
if ( value === 'http' )
desc . innerHTML = _ ( 'TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used.' ) ;
else if ( value === 'quic' )
desc . innerHTML = _ ( 'No additional encryption support: It\'s basically duplicate encryption.' ) ;
else
desc . innerHTML = _ ( 'No TCP transport, plain HTTP is merged into the HTTP transport.' ) ;
var tls = this . map . findElement ( 'id' , 'cbid.homeproxy.%s.tls' . format ( section _id ) ) . firstElementChild ;
if ( ( value === 'http' && tls . checked ) || ( value === 'grpc' && ! features . with _grpc ) ) {
this . map . findElement ( 'id' , 'cbid.homeproxy.%s.http_idle_timeout' . format ( section _id ) ) . nextElementSibling . innerHTML =
_ ( 'Specifies the period of time (in seconds) after which a health check will be performed using a ping frame if no frames have been received on the connection.<br/>' +
'Please note that a ping response is considered a received frame, so if there is no other traffic on the connection, the health check will be executed every interval.' ) ;
this . map . findElement ( 'id' , 'cbid.homeproxy.%s.http_ping_timeout' . format ( section _id ) ) . nextElementSibling . innerHTML =
_ ( 'Specifies the timeout duration (in seconds) after sending a PING frame, within which a response must be received.<br/>' +
'If a response to the PING frame is not received within the specified timeout duration, the connection will be closed.' ) ;
} else if ( value === 'grpc' && features . with _grpc ) {
this . map . findElement ( 'id' , 'cbid.homeproxy.%s.http_idle_timeout' . format ( section _id ) ) . nextElementSibling . innerHTML =
_ ( 'If the transport doesn\'t see any activity after a duration of this time (in seconds), it pings the client to check if the connection is still active.' ) ;
this . map . findElement ( 'id' , 'cbid.homeproxy.%s.http_ping_timeout' . format ( section _id ) ) . nextElementSibling . innerHTML =
_ ( 'The timeout (in seconds) that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.' ) ;
}
}
so . modalonly = true ;
/* gRPC config start */
so = ss . option ( form . Value , 'grpc_servicename' , _ ( 'gRPC service name' ) ) ;
so . depends ( 'transport' , 'grpc' ) ;
so . modalonly = true ;
if ( features . with _grpc ) {
so = ss . option ( form . Flag , 'grpc_permit_without_stream' , _ ( 'gRPC permit without stream' ) ,
_ ( 'If enabled, the client transport sends keepalive pings even with no active connections.' ) ) ;
so . default = so . disabled ;
so . depends ( 'transport' , 'grpc' ) ;
so . modalonly = true ;
}
/* gRPC config end */
/* HTTP config start */
so = ss . option ( form . DynamicList , 'http_host' , _ ( 'Host' ) ) ;
so . datatype = 'hostname' ;
so . depends ( 'transport' , 'http' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'http_path' , _ ( 'Path' ) ) ;
so . depends ( 'transport' , 'http' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'http_method' , _ ( 'Method' ) ) ;
2023-10-23 21:52:05 +08:00
so . value ( 'GET' , _ ( 'GET' ) ) ;
so . value ( 'PUT' , _ ( 'PUT' ) ) ;
2023-10-12 17:38:51 +08:00
so . depends ( 'transport' , 'http' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'http_idle_timeout' , _ ( 'Idle timeout' ) ,
_ ( 'Specifies the period of time (in seconds) after which a health check will be performed using a ping frame if no frames have been received on the connection.<br/>' +
'Please note that a ping response is considered a received frame, so if there is no other traffic on the connection, the health check will be executed every interval.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'transport' , 'grpc' ) ;
so . depends ( { 'transport' : 'http' , 'tls' : '1' } ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'http_ping_timeout' , _ ( 'Ping timeout' ) ,
_ ( 'Specifies the timeout duration (in seconds) after sending a PING frame, within which a response must be received.<br/>' +
'If a response to the PING frame is not received within the specified timeout duration, the connection will be closed.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'transport' , 'grpc' ) ;
so . depends ( { 'transport' : 'http' , 'tls' : '1' } ) ;
so . modalonly = true ;
/* HTTP config end */
/* WebSocket config start */
so = ss . option ( form . Value , 'ws_host' , _ ( 'Host' ) ) ;
so . depends ( 'transport' , 'ws' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'ws_path' , _ ( 'Path' ) ) ;
so . depends ( 'transport' , 'ws' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'websocket_early_data' , _ ( 'Early data' ) ,
_ ( 'Allowed payload size is in the request.' ) ) ;
so . datatype = 'uinteger' ;
so . value ( '2048' ) ;
so . depends ( 'transport' , 'ws' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'websocket_early_data_header' , _ ( 'Early data header name' ) ) ;
so . value ( 'Sec-WebSocket-Protocol' ) ;
so . depends ( 'transport' , 'ws' ) ;
so . modalonly = true ;
/* WebSocket config end */
so = ss . option ( form . ListValue , 'packet_encoding' , _ ( 'Packet encoding' ) ) ;
so . value ( '' , _ ( 'none' ) ) ;
so . value ( 'packetaddr' , _ ( 'packet addr (v2ray-core v5+)' ) ) ;
so . value ( 'xudp' , _ ( 'Xudp (Xray-core)' ) ) ;
so . depends ( 'type' , 'vless' ) ;
so . depends ( 'type' , 'vmess' ) ;
so . modalonly = true ;
/* Transport config end */
/* Wireguard config start */
so = ss . option ( form . DynamicList , 'wireguard_local_address' , _ ( 'Local address' ) ,
_ ( 'List of IP (v4 or v6) addresses prefixes to be assigned to the interface.' ) ) ;
so . datatype = 'cidr' ;
so . depends ( 'type' , 'wireguard' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'wireguard_private_key' , _ ( 'Private key' ) ,
_ ( 'WireGuard requires base64-encoded private keys.' ) ) ;
so . password = true ;
so . depends ( 'type' , 'wireguard' ) ;
so . validate = L . bind ( hp . validateBase64Key , this , 44 ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'wireguard_peer_public_key' , _ ( 'Peer pubkic key' ) ,
_ ( 'WireGuard peer public key.' ) ) ;
so . depends ( 'type' , 'wireguard' ) ;
so . validate = L . bind ( hp . validateBase64Key , this , 44 ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'wireguard_pre_shared_key' , _ ( 'Pre-shared key' ) ,
_ ( 'WireGuard pre-shared key.' ) ) ;
so . password = true ;
so . depends ( 'type' , 'wireguard' ) ;
so . validate = L . bind ( hp . validateBase64Key , this , 44 ) ;
so . modalonly = true ;
so = ss . option ( form . DynamicList , 'wireguard_reserved' , _ ( 'Reserved field bytes' ) ) ;
so . datatype = 'integer' ;
so . depends ( 'type' , 'wireguard' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'wireguard_mtu' , _ ( 'MTU' ) ) ;
so . datatype = 'range(0,9000)' ;
so . default = '1408' ;
so . depends ( 'type' , 'wireguard' ) ;
so . rmempty = false ;
so . modalonly = true ;
/* Wireguard config end */
/* Mux config start */
so = ss . option ( form . Flag , 'multiplex' , _ ( 'Multiplex' ) ) ;
so . default = so . disabled ;
so . depends ( 'type' , 'shadowsocks' ) ;
so . depends ( 'type' , 'trojan' ) ;
so . depends ( 'type' , 'vless' ) ;
so . depends ( 'type' , 'vmess' ) ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'multiplex_protocol' , _ ( 'Protocol' ) ,
_ ( 'Multiplex protocol.' ) ) ;
so . value ( 'h2mux' ) ;
so . value ( 'smux' ) ;
so . value ( 'yamux' ) ;
so . default = 'h2mux' ;
so . depends ( 'multiplex' , '1' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'multiplex_max_connections' , _ ( 'Maximum connections' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'multiplex' , '1' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'multiplex_min_streams' , _ ( 'Minimum streams' ) ,
_ ( 'Minimum multiplexed streams in a connection before opening a new connection.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( 'multiplex' , '1' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'multiplex_max_streams' , _ ( 'Maximum streams' ) ,
_ ( 'Maximum multiplexed streams in a connection before opening a new connection.<br/>' +
'Conflict with <code>Maximum connections</code> and <code>Minimum streams</code>.' ) ) ;
so . datatype = 'uinteger' ;
so . depends ( { 'multiplex' : '1' , 'multiplex_max_connections' : '' , 'multiplex_min_streams' : '' } ) ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'multiplex_padding' , _ ( 'Enable padding' ) ) ;
so . default = so . disabled ;
so . depends ( 'multiplex' , '1' ) ;
so . modalonly = true ;
/* Mux config end */
/* TLS config start */
so = ss . option ( form . Flag , 'tls' , _ ( 'TLS' ) ) ;
so . default = so . disabled ;
so . depends ( 'type' , 'http' ) ;
so . depends ( 'type' , 'hysteria' ) ;
so . depends ( 'type' , 'hysteria2' ) ;
so . depends ( 'type' , 'shadowtls' ) ;
so . depends ( 'type' , 'trojan' ) ;
so . depends ( 'type' , 'tuic' ) ;
so . depends ( 'type' , 'vless' ) ;
so . depends ( 'type' , 'vmess' ) ;
so . validate = function ( section _id , value ) {
if ( section _id ) {
var type = this . map . lookupOption ( 'type' , section _id ) [ 0 ] . formvalue ( section _id ) ;
var tls = this . map . findElement ( 'id' , 'cbid.homeproxy.%s.tls' . format ( section _id ) ) . firstElementChild ;
if ( [ 'hysteria' , 'hysteria2' , 'shadowtls' , 'tuic' ] . includes ( type ) ) {
tls . checked = true ;
tls . disabled = true ;
} else {
tls . disabled = null ;
}
}
return true ;
}
so . modalonly = true ;
so = ss . option ( form . Value , 'tls_sni' , _ ( 'TLS SNI' ) ,
_ ( 'Used to verify the hostname on the returned certificates unless insecure is given.' ) ) ;
so . depends ( 'tls' , '1' ) ;
so . modalonly = true ;
so = ss . option ( form . DynamicList , 'tls_alpn' , _ ( 'TLS ALPN' ) ,
_ ( 'List of supported application level protocols, in order of preference.' ) ) ;
so . depends ( 'tls' , '1' ) ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'tls_insecure' , _ ( 'Allow insecure' ) ,
_ ( 'Allow insecure connection at TLS client.' ) +
'<br/>' +
_ ( 'This is <strong>DANGEROUS</strong>, your traffic is almost like <strong>PLAIN TEXT</strong>! Use at your own risk!' ) ) ;
so . default = so . disabled ;
so . depends ( 'tls' , '1' ) ;
so . onchange = allowInsecureConfirm ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'tls_min_version' , _ ( 'Minimum TLS version' ) ,
_ ( 'The minimum TLS version that is acceptable.' ) ) ;
so . value ( '' , _ ( 'default' ) ) ;
for ( var i of hp . tls _versions )
so . value ( i ) ;
so . depends ( 'tls' , '1' ) ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'tls_max_version' , _ ( 'Maximum TLS version' ) ,
_ ( 'The maximum TLS version that is acceptable.' ) ) ;
so . value ( '' , _ ( 'default' ) ) ;
for ( var i of hp . tls _versions )
so . value ( i ) ;
so . depends ( 'tls' , '1' ) ;
so . modalonly = true ;
so = ss . option ( form . MultiValue , 'tls_cipher_suites' , _ ( 'Cipher suites' ) ,
_ ( 'The elliptic curves that will be used in an ECDHE handshake, in preference order. If empty, the default will be used.' ) ) ;
for ( var i of hp . tls _cipher _suites )
so . value ( i ) ;
so . depends ( 'tls' , '1' ) ;
so . optional = true ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'tls_self_sign' , _ ( 'Append self-signed certificate' ) ,
_ ( 'If you have the root certificate, use this option instead of allowing insecure.' ) ) ;
so . default = so . disabled ;
so . depends ( 'tls_insecure' , '0' ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'tls_cert_path' , _ ( 'Certificate path' ) ,
_ ( 'The path to the server certificate, in PEM format.' ) ) ;
so . value ( '/etc/homeproxy/certs/client_ca.pem' ) ;
so . depends ( 'tls_self_sign' , '1' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Button , '_upload_cert' , _ ( 'Upload certificate' ) ,
_ ( '<strong>Save your configuration before uploading files!</strong>' ) ) ;
so . inputstyle = 'action' ;
so . inputtitle = _ ( 'Upload...' ) ;
so . depends ( { 'tls_self_sign' : '1' , 'tls_cert_path' : '/etc/homeproxy/certs/client_ca.pem' } ) ;
so . onclick = L . bind ( hp . uploadCertificate , this , _ ( 'certificate' ) , 'client_ca' ) ;
so . modalonly = true ;
if ( features . with _ech ) {
so = ss . option ( form . Flag , 'tls_ech' , _ ( 'Enable ECH' ) ,
_ ( 'ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello message.' ) ) ;
so . depends ( 'tls' , '1' ) ;
so . default = so . disabled ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'tls_ech_tls_disable_drs' , _ ( 'Disable dynamic record sizing' ) ) ;
so . depends ( 'tls_ech' , '1' ) ;
so . default = so . disabled ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'tls_ech_enable_pqss' , _ ( 'Enable PQ signature schemes' ) ) ;
so . depends ( 'tls_ech' , '1' ) ;
so . default = so . disabled ;
so . modalonly = true ;
so = ss . option ( form . Value , 'tls_ech_config' , _ ( 'ECH config' ) ) ;
so . depends ( 'tls_ech' , '1' ) ;
so . modalonly = true ;
}
if ( features . with _utls ) {
so = ss . option ( form . ListValue , 'tls_utls' , _ ( 'uTLS fingerprint' ) ,
_ ( 'uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.' ) ) ;
so . value ( '' , _ ( 'Disable' ) ) ;
so . value ( '360' , _ ( '360' ) ) ;
so . value ( 'android' , _ ( 'Android' ) ) ;
so . value ( 'chrome' , _ ( 'Chrome' ) ) ;
so . value ( 'edge' , _ ( 'Edge' ) ) ;
so . value ( 'firefox' , _ ( 'Firefox' ) ) ;
so . value ( 'ios' , _ ( 'iOS' ) ) ;
so . value ( 'qq' , _ ( 'QQ' ) ) ;
so . value ( 'random' , _ ( 'Random' ) ) ;
so . value ( 'randomized' , _ ( 'Randomized' ) ) ;
so . value ( 'safari' , _ ( 'Safari' ) ) ;
so . depends ( { 'tls' : '1' , 'type' : /^((?!hysteria2?$).)+$/ } ) ;
so . validate = function ( section _id , value ) {
if ( section _id ) {
let tls _reality = this . map . findElement ( 'id' , 'cbid.homeproxy.%s.tls_reality' . format ( section _id ) ) . firstElementChild ;
if ( tls _reality . checked && ! value )
return _ ( 'Expecting: %s' ) . format ( _ ( 'non-empty value' ) ) ;
let vless _flow = this . map . lookupOption ( 'vless_flow' , section _id ) [ 0 ] . formvalue ( section _id ) ;
if ( ( tls _reality . checked || vless _flow ) && [ '360' , 'android' ] . includes ( value ) )
return _ ( 'Unsupported fingerprint!' ) ;
}
return true ;
}
so . modalonly = true ;
so = ss . option ( form . Flag , 'tls_reality' , _ ( 'REALITY' ) ) ;
so . default = so . disabled ;
so . depends ( { 'tls' : '1' , 'type' : 'vless' } ) ;
so . modalonly = true ;
so = ss . option ( form . Value , 'tls_reality_public_key' , _ ( 'REALITY public key' ) ) ;
so . depends ( 'tls_reality' , '1' ) ;
so . rmempty = false ;
so . modalonly = true ;
so = ss . option ( form . Value , 'tls_reality_short_id' , _ ( 'REALITY short ID' ) ) ;
so . depends ( 'tls_reality' , '1' ) ;
so . modalonly = true ;
}
/* TLS config end */
/* Extra settings start */
so = ss . option ( form . Flag , 'tcp_fast_open' , _ ( 'TCP fast open' ) ) ;
so . default = so . disabled ;
so . modalonly = true ;
if ( features . has _mptcp ) {
so = ss . option ( form . Flag , 'tcp_multi_path' , _ ( 'MultiPath TCP' ) ) ;
so . default = so . disabled ;
so . modalonly = true ;
}
so = ss . option ( form . Flag , 'udp_fragment' , _ ( 'UDP Fragment' ) ,
_ ( 'Enable UDP fragmentation.' ) ) ;
so . default = so . disabled ;
so . modalonly = true ;
so = ss . option ( form . Flag , 'udp_over_tcp' , _ ( 'UDP over TCP' ) ,
_ ( 'Enable the SUoT protocol, requires server support. Conflict with multiplex.' ) ) ;
so . default = so . disabled ;
so . depends ( 'type' , 'socks' ) ;
so . depends ( { 'type' : 'shadowsocks' , 'multiplex' : '0' } ) ;
so . modalonly = true ;
so = ss . option ( form . ListValue , 'udp_over_tcp_version' , _ ( 'SUoT version' ) ) ;
so . value ( '1' , _ ( 'v1' ) ) ;
so . value ( '2' , _ ( 'v2' ) ) ;
so . default = '2' ;
so . depends ( 'udp_over_tcp' , '1' ) ;
so . modalonly = true ;
/* Extra settings end */
/* Nodes settings end */
/* Subscriptions settings start */
s . tab ( 'subscription' , _ ( 'Subscriptions' ) ) ;
o = s . taboption ( 'subscription' , form . Flag , 'auto_update' , _ ( 'Auto update' ) ,
_ ( 'Auto update subscriptions, GeoIP and GeoSite.' ) ) ;
o . default = o . disabled ;
o . rmempty = false ;
o = s . taboption ( 'subscription' , form . ListValue , 'auto_update_time' , _ ( 'Update time' ) ) ;
for ( var i = 0 ; i < 24 ; i ++ )
o . value ( i , i + ':00' ) ;
o . default = '2' ;
o . depends ( 'auto_update' , '1' ) ;
o = s . taboption ( 'subscription' , form . Flag , 'update_via_proxy' , _ ( 'Update via proxy' ) ,
_ ( 'Update subscriptions via proxy.' ) ) ;
o . default = o . disabled ;
o . rmempty = false ;
o = s . taboption ( 'subscription' , form . DynamicList , 'subscription_url' , _ ( 'Subscription URL-s' ) ,
_ ( 'Support Hysteria, Shadowsocks(R), Trojan, v2rayN (VMess), and XTLS (VLESS) online configuration delivery standard.' ) ) ;
o . validate = function ( section _id , value ) {
if ( section _id && value ) {
try {
var url = new URL ( value ) ;
if ( ! url . hostname )
return _ ( 'Expecting: %s' ) . format ( _ ( 'valid URL' ) ) ;
}
catch ( e ) {
return _ ( 'Expecting: %s' ) . format ( _ ( 'valid URL' ) ) ;
}
}
return true ;
}
o = s . taboption ( 'subscription' , form . ListValue , 'filter_nodes' , _ ( 'Filter nodes' ) ,
_ ( 'Drop/keep specific nodes from subscriptions.' ) ) ;
o . value ( 'disabled' , _ ( 'Disable' ) ) ;
o . value ( 'blacklist' , _ ( 'Blacklist mode' ) ) ;
o . value ( 'whitelist' , _ ( 'Whitelist mode' ) ) ;
o . default = 'disabled' ;
o . rmempty = false ;
o = s . taboption ( 'subscription' , form . DynamicList , 'filter_keywords' , _ ( 'Filter keywords' ) ,
_ ( 'Drop/keep nodes that contain the specific keywords. <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">Regex</a> is supported.' ) ) ;
o . depends ( { 'filter_nodes' : 'disabled' , '!reverse' : true } ) ;
o . rmempty = false ;
o = s . taboption ( 'subscription' , form . Flag , 'allow_insecure' , _ ( 'Allow insecure' ) ,
_ ( 'Allow insecure connection by default when add nodes from subscriptions.' ) +
'<br/>' +
_ ( 'This is <strong>DANGEROUS</strong>, your traffic is almost like <strong>PLAIN TEXT</strong>! Use at your own risk!' ) ) ;
o . default = o . disabled ;
o . rmempty = false ;
o . onchange = allowInsecureConfirm ;
o = s . taboption ( 'subscription' , form . ListValue , 'packet_encoding' , _ ( 'Default packet encoding' ) ) ;
o . value ( '' , _ ( 'none' ) ) ;
o . value ( 'packetaddr' , _ ( 'packet addr (v2ray-core v5+)' ) ) ;
o . value ( 'xudp' , _ ( 'Xudp (Xray-core)' ) ) ;
o = s . taboption ( 'subscription' , form . Button , '_save_subscriptions' , _ ( 'Save subscriptions settings' ) ,
_ ( 'NOTE: Save current settings before updating subscriptions.' ) ) ;
o . inputstyle = 'apply' ;
o . inputtitle = _ ( 'Save current settings' ) ;
o . onclick = function ( ) {
ui . changes . apply ( true ) ;
return this . map . save ( null , true ) ;
}
o = s . taboption ( 'subscription' , form . Button , '_update_subscriptions' , _ ( 'Update nodes from subscriptions' ) ) ;
o . inputstyle = 'apply' ;
o . inputtitle = function ( section _id ) {
var sublist = uci . get ( data [ 0 ] , section _id , 'subscription_url' ) || [ ] ;
if ( sublist . length > 0 ) {
return _ ( 'Update %s subscriptions' ) . format ( sublist . length ) ;
} else {
this . readonly = true ;
return _ ( 'No subscription available' )
}
}
o . onclick = function ( ) {
return fs . exec ( '/etc/homeproxy/scripts/update_subscriptions.uc' ) . then ( ( res ) => {
return location . reload ( ) ;
} ) . catch ( ( err ) => {
ui . addNotification ( null , E ( 'p' , _ ( 'An error occurred during updating subscriptions: %s' ) . format ( err ) ) ) ;
return this . map . reset ( ) ;
} ) ;
}
o = s . taboption ( 'subscription' , form . Button , '_remove_subscriptions' , _ ( 'Remove all nodes from subscriptions' ) ) ;
o . inputstyle = 'reset' ;
o . inputtitle = function ( ) {
var subnodes = [ ] ;
uci . sections ( data [ 0 ] , 'node' , ( res ) => {
if ( res . grouphash )
subnodes = subnodes . concat ( res [ '.name' ] )
} ) ;
if ( subnodes . length > 0 ) {
return _ ( 'Remove %s nodes' ) . format ( subnodes . length ) ;
} else {
this . readonly = true ;
return _ ( 'No subscription node' ) ;
}
}
o . onclick = function ( ) {
var subnodes = [ ] ;
uci . sections ( data [ 0 ] , 'node' , ( res ) => {
if ( res . grouphash )
subnodes = subnodes . concat ( res [ '.name' ] )
} ) ;
for ( var i in subnodes )
uci . remove ( data [ 0 ] , subnodes [ i ] ) ;
if ( subnodes . includes ( uci . get ( data [ 0 ] , 'config' , 'main_node' ) ) )
uci . set ( data [ 0 ] , 'config' , 'main_node' , 'nil' ) ;
if ( subnodes . includes ( uci . get ( data [ 0 ] , 'config' , 'main_udp_node' ) ) )
uci . set ( data [ 0 ] , 'config' , 'main_udp_node' , 'nil' ) ;
this . inputtitle = _ ( '%s nodes removed' ) . format ( subnodes . length ) ;
this . readonly = true ;
return this . map . save ( null , true ) ;
}
/* Subscriptions settings end */
return m . render ( ) ;
}
} ) ;