# aardio 范例: WebView2 模拟手机设备 + 动态设置 SOCKS5 代理服务器

```aardio
//WebView2 模拟手机设备 + 动态设置 SOCKS5 代理服务器
import fonts.fontAwesome;
import web.view;
import win.ui;
/*DSG{{*/
var winform = win.form(text="SOCKS5 浏览器";right=959;bottom=640;bgcolor=0xFFFFFF)
winform.add(
btnGo={cls="plus";left=895;top=7;right=950;bottom=33;color=0x3C3C3C;disabled=1;dr=1;dt=1;font=LOGFONT(h=-13);iconStyle={font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF1D8';notify=1;z=4};
btnRefresh={cls="plus";left=241;top=7;right=271;bottom=33;color=0x3C3C3C;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={font=LOGFONT(h=-13;name='FontAwesome')};iconText='\uF021';notify=1;z=6};
comboProxy={cls="combobox";left=50;top=9;right=240;bottom=33;dl=1;dt=1;edge=1;items={};mode="dropdown";z=2};
custom={cls="custom";left=0;top=40;right=942;bottom=640;db=1;dl=1;dr=1;dt=1;z=1};
editUrl={cls="plus";left=489;top=7;right=893;bottom=33;align="right";border={bottom=1;color=0xFF969696};disabled=1;dr=1;dt=1;editable="edit";font=LOGFONT(h=-13);notify=1;textPadding={left=5;top=3;bottom=3};z=3};
labelProxy={cls="static";text="代理:";left=8;top=12;right=48;bottom=28;dl=1;dt=1;transparent=1;z=5};
labelStatus={cls="static";text="正在搜索代理...";left=277;top=12;right=441;bottom=28;dl=1;dt=1;transparent=1;z=7}
)
/*}}*/

// 创建 SOCKS5 中转代理服务器
import wsock.tcp.socks5Server;
var socks5Server = wsock.tcp.socks5Server.async("127.0.0.1",0);
socks5Server.start();

// 创建 WebView2 控件，模拟手机浏览器
var wb = web.view(winform.custom,{
	startArguments = {  
		acceptLang = "zh-CN";
		userAgent = "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166";
		proxyServer = socks5Server.getUrl();//使用 SOCKS5 代理
	}; 
	userDataDir = _STUDIO_INVOKED ? io.appData("/aardio/webview2/user-data/ide/socks5-browser")
}) 

// 启用 CDP 的 Emulation 域，模拟手机设备
wb.cdp("Emulation.enable");
wb.cdp("Emulation.setDeviceMetricsOverride", {
	mobile = true;
});

// 设置移动设备 HTTP 请求头
wb.cdp("Network.enable");
wb.cdp("Network.setExtraHTTPHeaders", {
	headers = {
		["Sec-CH-UA-Mobile"] = "?1";
		["Sec-CH-UA-Platform"] = '"Android"';
	}
});

wb.onDocumentComplete = function(url){ 
	
	if(!string.find(url,"^<chrome\-error>|<about>\:")){ 
		winform.editUrl.text = url;
	}
}

wb.html = $"/socks5.html";

// 应用代理设置
var applyProxy = function(proxy){ 
	if(#proxy){
		socks5Server.setUpstreamProxy(proxy);
		winform.text = "SOCKS5 浏览器 - 已切换到代理: " + proxy;
	}
}

// 代理下拉框选择事件
winform.comboProxy.onSelChange = function(){
	applyProxy(winform.comboProxy.selText);
}

import fsys.table;
var cfgTable = fsys.table(io.appData("/aardio/std/tools/socks5.webview.table"));
if(cfgTable.url){
	winform.editUrl.text = cfgTable.url;
}
else{
	winform.editUrl.text = "github.com"
}


// 打开网页
winform.btnGo.oncommand = function(){
	var proxy = winform.comboProxy.text;
	if(#proxy){
		applyProxy(winform.comboProxy.text);
		winform.text = "SOCKS5 浏览器 - 已切换到代理: " + proxy;
	}
	
	var url = winform.editUrl.text;
	if(#url){
		cfgTable.url = url;
		cfgTable.save();
		
		// 如果没有协议前缀，自动添加 https://
		if(!string.find(url,"^<<@@https@>|<@@http@>>?\://")){
			url = "https://" + url;
		}
		
		wb.html = "<body style='text-align: right;'>正在打开："+url+" <progress></progress> </body>"
		wb.go(url);
	}
	else{
		winform.editUrl.editBox.showErrorTip("请输入网址");
	}
	
}

// 地址栏回车事件
winform.editUrl.editBox.onOk = function(ctrl,alt,shift){ 
	winform.btnGo.oncommand();
	return false; 
}

// 线程通信
import thread.command;
var threadCommand = thread.command();

// 记录已找到的代理数量
var proxyFoundCount = 0;

// 启用网址输入框和打开按钮
var enableUrlInput = function(){
	winform.editUrl.disabled = false;
	winform.btnGo.disabled = false;
	winform.editUrl.setFocus();
}

// 接收代理服务器
threadCommand.$onProxyFound = function(proxyStr){
	winform.comboProxy.add(proxyStr);
	proxyFoundCount++;
	
	// 第一个代理自动选中并应用，同时启用网址输入
	if(proxyFoundCount == 1){
		winform.comboProxy.selIndex = 1;
		applyProxy(winform.comboProxy.selText);
		enableUrlInput();
	}
	
	winform.labelStatus.text = "已找到 " + proxyFoundCount + " 个代理";
}

// 搜索完成
threadCommand.$onSearchComplete = function(total){
	if(total == 0){
		winform.labelStatus.text = "未找到可用代理";
	}
	else {
		winform.labelStatus.text = "共 " + total + " 个可用代理";
	}
	
	winform.btnRefresh.disabledText = null;
	winform.comboProxy.disabled = false;
}

// 启动代理搜索线程
var startProxySearch = function(){
	proxyFoundCount = 0;
	winform.comboProxy.clear();
	winform.labelStatus.text = "正在搜索代理...";
	winform.btnRefresh.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250']
	winform.comboProxy.disabled = true;
	
	// 搜索时禁用网址输入
	winform.editUrl.disabled = true;
	winform.btnGo.disabled = true;
	
	thread.invoke(
		function(threadCommand){
			// 获取前 10 个可用代理
			var foundCount = 0;
			var maxCount = 10;
				
			var knownPorts = { [135] = true; [445] = true;[3389] = true;}
			var knownSocks5Ports = function (p) {
    			if( knownPorts[p] ) return false;
    			if( p < 1080 || p >= 49152 ) return false;
    			
    			return (
           			(p >= 1080 && p < 1090) 
        			|| (p >= 7890 && p < 7900) 
        			|| (p >= 10800 && p < 10810) 
        			|| (p >= 2080 && p <= 2081) 
        			|| (p >= 8888 && p <= 8890) 
        			|| (p == 9050 || p == 9150) 
        			|| (p == 2334 || p == 10085) 
        			|| (p == 51837) 
        			|| (p == 6153)  
        			|| (p == 20170) 
        			|| (p == 2801) 
    			);
			}

			import inet.stat;
			var stat = inet.stat();
			var listeningPorts = table.filter(
    			stat.tcp, 
    			lambda(conn) 
					conn.state == "LISTENING" 
        			&& knownSocks5Ports(conn.localPort)
        			&& (conn.local == "0.0.0.0" || conn.local = "127.0.0.1" || conn.local == "::") 
			);
			
			listeningPorts = table.map(listeningPorts,lambda(v) v.localPort );
			table.sort(listeningPorts);
			
			var localSock5Proxy;
			import wsock.tcp.socks5Client; 
			for i, port in listeningPorts { 
    			if( wsock.tcp.socks5Client.test("127.0.0.1",port)){
         			localSock5Proxy = "SOCKS5://127.0.0.1:"+port;
         			break
    			}
			}
			if(localSock5Proxy){
				foundCount++;
				threadCommand.$onProxyFound(localSock5Proxy);
			}
			
			import web.rest.github;
			import thread.works;
			
			// 创建 250 个检测线程
			var works = thread.works(250,
				function(proxy){
					import wsock.tcp.socks5Client;
					// 检测 SOCKS5 可用性，参数 3 必须指定域名
					if(wsock.tcp.socks5Client.testHttp(proxy.ip, proxy.port, "duck.ai")){
						return proxy;
					}
				}
			);
			
			// 从网络获取 SOCKS5 服务器列表
			var proxies = web.rest.github.getJson("proxifly","free-proxy-list","proxies/protocols/socks5/data.json");
			
			if(proxies){
				works.pushAll(proxies);
				 
				var timeout = 30000; // 30 秒超时
				var startTime = time.tick();
				
				while(foundCount < maxCount){
					var result = works.pop(1, true);
					if(result){
						var proxyStr = result.proxy;
						threadCommand.$onProxyFound(proxyStr);
						foundCount++;
					}
					
					// 超时检查
					if(time.tick() - startTime > timeout){
						break;
					}
				}
				
				threadCommand.$onSearchComplete(foundCount);
				works.quit(10);
			}
			else {
				threadCommand.$onSearchComplete(foundCount);
			}
		}, threadCommand
	)
}

// 刷新按钮点击事件
winform.btnRefresh.oncommand = function(){
	startProxySearch();
}

// 启动时自动搜索代理
startProxySearch();
winform.editUrl.editBox.onFocusGot = function(){ 
	owner.selectAll()
}

winform.btnGo.skin({
	color={
		active=0xFF00FF00;
		default=0xFF3C3C3C;
		disabled=0xFF6D6D6D;
		hover=0xFF0078D4
	}
})

winform.btnRefresh.skin({
	color={
		active=0xFF00FF00;
		default=0xFF3C3C3C;
		disabled=0xFF6D6D6D;
		hover=0xFF0078D4
	}
})

winform.show(3/*_SW_MAXIMIZE*/);
win.loopMessage();
```