@@ -99,6 +99,89 @@ describe('Proxy structured producers', function () {
9999 } ) ;
100100 } ) ;
101101
102+ it ( 'keeps only websocket shadowsocks v2ray-plugin modes for Mihomo and Stash by default' , function ( ) {
103+ const buildProxy = ( name , mode ) => ( {
104+ type : 'ss' ,
105+ name,
106+ server : 'ss.example.com' ,
107+ port : 8388 ,
108+ cipher : 'aes-128-gcm' ,
109+ password : 'secret' ,
110+ plugin : 'v2ray-plugin' ,
111+ 'plugin-opts' : {
112+ mode,
113+ host : 'cdn.example.com' ,
114+ path : '/socket' ,
115+ tls : true ,
116+ } ,
117+ } ) ;
118+
119+ const proxies = [
120+ buildProxy ( 'WS' , 'websocket' ) ,
121+ buildProxy ( 'QUIC' , 'quic' ) ,
122+ ] ;
123+
124+ for ( const platform of [ 'Mihomo' , 'Stash' ] ) {
125+ const internal = produceInternal ( platform , proxies ) ;
126+ const external = loadProducedYaml ( platform , proxies , {
127+ 'include-unsupported-proxy' : true ,
128+ } ) ;
129+
130+ expect ( internal , platform ) . to . have . length ( 1 ) ;
131+ expect ( internal [ 0 ] . name , platform ) . to . equal ( 'WS' ) ;
132+ expect ( external . proxies . map ( ( proxy ) => proxy . name ) , platform ) . to
133+ . deep . equal ( [ 'WS' , 'QUIC' ] ) ;
134+ }
135+ } ) ;
136+
137+ it ( 'keeps only supported shadowsocks v2ray-plugin modes for Shadowrocket by default' , function ( ) {
138+ const buildProxy = ( name , mode ) => ( {
139+ type : 'ss' ,
140+ name,
141+ server : 'ss.example.com' ,
142+ port : 8388 ,
143+ cipher : 'aes-128-gcm' ,
144+ password : 'secret' ,
145+ plugin : 'v2ray-plugin' ,
146+ 'plugin-opts' : {
147+ mode,
148+ host : 'cdn.example.com' ,
149+ path : '/socket' ,
150+ tls : true ,
151+ } ,
152+ } ) ;
153+
154+ const proxies = [
155+ buildProxy ( 'WS' , 'websocket' ) ,
156+ buildProxy ( 'QUIC' , 'quic' ) ,
157+ buildProxy ( 'HTTP2' , 'http2' ) ,
158+ buildProxy ( 'MKCP' , 'mkcp' ) ,
159+ buildProxy ( 'GRPC' , 'grpc' ) ,
160+ buildProxy ( 'TLS' , 'tls' ) ,
161+ ] ;
162+
163+ const internal = produceInternal ( 'Shadowrocket' , proxies ) ;
164+ const external = loadProducedYaml ( 'Shadowrocket' , proxies , {
165+ 'include-unsupported-proxy' : true ,
166+ } ) ;
167+
168+ expect ( internal . map ( ( proxy ) => proxy . name ) ) . to . deep . equal ( [
169+ 'WS' ,
170+ 'QUIC' ,
171+ 'HTTP2' ,
172+ 'MKCP' ,
173+ 'GRPC' ,
174+ ] ) ;
175+ expect ( external . proxies . map ( ( proxy ) => proxy . name ) ) . to . deep . equal ( [
176+ 'WS' ,
177+ 'QUIC' ,
178+ 'HTTP2' ,
179+ 'MKCP' ,
180+ 'GRPC' ,
181+ 'TLS' ,
182+ ] ) ;
183+ } ) ;
184+
102185 it ( 'adds Clash.Meta reality defaults and preserves websocket early data' , function ( ) {
103186 const proxy = {
104187 type : 'vless' ,
@@ -701,6 +784,47 @@ describe('Proxy structured producers', function () {
701784 } ) ;
702785 } ) ;
703786
787+ it ( 'maps shadowsocks obfs plugin fields into Egern nested structures' , function ( ) {
788+ const proxy = {
789+ type : 'ss' ,
790+ name : 'Egern SS Obfs TLS' ,
791+ server : 'ss.example.com' ,
792+ port : 8388 ,
793+ cipher : 'aes-128-gcm' ,
794+ password : 'secret' ,
795+ plugin : 'obfs' ,
796+ 'plugin-opts' : {
797+ mode : 'tls' ,
798+ host : 'legacy.example.com' ,
799+ path : '/legacy' ,
800+ } ,
801+ } ;
802+
803+ const internal = produceInternal ( 'Egern' , proxy ) [ 0 ] ;
804+ const external = loadProducedYaml ( 'Egern' , proxy ) ;
805+
806+ expectSubset ( internal , {
807+ shadowsocks : {
808+ name : 'Egern SS Obfs TLS' ,
809+ method : 'aes-128-gcm' ,
810+ server : 'ss.example.com' ,
811+ port : 8388 ,
812+ password : 'secret' ,
813+ obfs : 'tls' ,
814+ obfs_host : 'legacy.example.com' ,
815+ obfs_uri : '/legacy' ,
816+ } ,
817+ } ) ;
818+ expectSubset ( external . proxies [ 0 ] , {
819+ shadowsocks : {
820+ name : 'Egern SS Obfs TLS' ,
821+ obfs : 'tls' ,
822+ obfs_host : 'legacy.example.com' ,
823+ obfs_uri : '/legacy' ,
824+ } ,
825+ } ) ;
826+ } ) ;
827+
704828 it ( 'preserves numeric v2ray-plugin mux values across Clash-family YAML producers' , function ( ) {
705829 const buildProxy = ( name , mux ) => ( {
706830 type : 'ss' ,
@@ -955,6 +1079,51 @@ describe('Proxy structured producers', function () {
9551079 } ) ;
9561080 } ) ;
9571081
1082+ it ( 'skips Egern shadowsocks proxies with unsupported plugins and keeps the rest of the subscription' , function ( ) {
1083+ const proxies = [
1084+ {
1085+ type : 'ss' ,
1086+ name : 'Invalid SS Plugin' ,
1087+ server : 'invalid.example.com' ,
1088+ port : 8388 ,
1089+ cipher : 'aes-128-gcm' ,
1090+ password : 'secret' ,
1091+ plugin : 'v2ray-plugin' ,
1092+ 'plugin-opts' : {
1093+ mode : 'websocket' ,
1094+ host : 'cdn.example.com' ,
1095+ path : '/socket' ,
1096+ tls : true ,
1097+ } ,
1098+ } ,
1099+ {
1100+ type : 'ss' ,
1101+ name : 'Healthy SS' ,
1102+ server : 'ss.example.com' ,
1103+ port : 8388 ,
1104+ cipher : 'aes-128-gcm' ,
1105+ password : 'secret' ,
1106+ } ,
1107+ ] ;
1108+
1109+ const internal = produceInternal ( 'Egern' , proxies ) ;
1110+ const external = loadProducedYaml ( 'Egern' , proxies ) ;
1111+
1112+ expect ( internal ) . to . have . length ( 1 ) ;
1113+ expect ( external . proxies ) . to . have . length ( 1 ) ;
1114+ expectSubset ( internal [ 0 ] , {
1115+ shadowsocks : {
1116+ name : 'Healthy SS' ,
1117+ method : 'aes-128-gcm' ,
1118+ } ,
1119+ } ) ;
1120+ expectSubset ( external . proxies [ 0 ] , {
1121+ shadowsocks : {
1122+ name : 'Healthy SS' ,
1123+ } ,
1124+ } ) ;
1125+ } ) ;
1126+
9581127 it ( 'keeps numeric v2ray-plugin mux state in sing-box plugin opts' , function ( ) {
9591128 const buildProxy = ( name , mux ) => ( {
9601129 type : 'ss' ,
0 commit comments