mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-17 16:00:35 +08:00
Merge remote-tracking branch 'origin/dev' into refactor/single-instance
This commit is contained in:
commit
3bada1393e
18
.github/workflows/autobuild.yml
vendored
18
.github/workflows/autobuild.yml
vendored
@ -77,20 +77,20 @@ jobs:
|
|||||||
|
|
||||||
### Windows (不再支持Win7)
|
### Windows (不再支持Win7)
|
||||||
#### 正常版本(推荐)
|
#### 正常版本(推荐)
|
||||||
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe)
|
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe)
|
||||||
|
|
||||||
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
||||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
|
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64_darwin.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_darwin.dmg)
|
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64.dmg)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
#### DEB包(Debian系) 使用 apt ./路径 安装
|
#### DEB包(Debian系) 使用 apt ./路径 安装
|
||||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
||||||
|
|
||||||
#### RPM包(Redhat系) 使用 dnf ./路径 安装
|
#### RPM包(Redhat系) 使用 dnf ./路径 安装
|
||||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
||||||
@ -109,7 +109,7 @@ jobs:
|
|||||||
body_path: release.txt
|
body_path: release.txt
|
||||||
prerelease: true
|
prerelease: true
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
generate_release_notes: true
|
generate_release_notes: false
|
||||||
|
|
||||||
clean_old_assets:
|
clean_old_assets:
|
||||||
name: Clean Old Release Assets
|
name: Clean Old Release Assets
|
||||||
@ -566,20 +566,20 @@ jobs:
|
|||||||
|
|
||||||
### Windows (不再支持Win7)
|
### Windows (不再支持Win7)
|
||||||
#### 正常版本(推荐)
|
#### 正常版本(推荐)
|
||||||
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup_windows.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup_windows.exe)
|
- [64位(常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64-setup.exe) | [ARM64(不常用)](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64-setup.exe)
|
||||||
|
|
||||||
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
#### 内置Webview2版(体积较大,仅在企业版系统或无法安装webview2时使用)
|
||||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
|
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_fixed_webview2-setup.exe) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64_fixed_webview2-setup.exe)
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64_darwin.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64_darwin.dmg)
|
- [Apple M芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_aarch64.dmg) | [Intel芯片](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_x64.dmg)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
#### DEB包(Debian系) 使用 apt ./路径 安装
|
#### DEB包(Debian系) 使用 apt ./路径 安装
|
||||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64_linux.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_amd64.deb) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_arm64.deb) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge_${{ env.VERSION }}_armhf.deb)
|
||||||
|
|
||||||
#### RPM包(Redhat系) 使用 dnf ./路径 安装
|
#### RPM包(Redhat系) 使用 dnf ./路径 安装
|
||||||
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64_linux.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
- [64位](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.x86_64.rpm) | [ARM64](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.aarch64.rpm) | [ARMv7](${{ env.DOWNLOAD_URL }}/Clash.Verge-${{ env.VERSION }}-1.armhfp.rpm)
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
|
||||||
|
|||||||
@ -40,4 +40,3 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[pre-push] All checks passed."
|
echo "[pre-push] All checks passed."
|
||||||
exit 0
|
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
### 🐞 修复问题
|
### 🐞 修复问题
|
||||||
|
|
||||||
- Linux 无法切换 TUN 堆栈
|
- Linux 无法切换 TUN 堆栈
|
||||||
|
- macOS service 启动项显示名称(试验性修改)
|
||||||
|
- macOS 非预期 Tproxy 端口设置
|
||||||
|
- 流量图缩放异常
|
||||||
|
- PAC 自动代理脚本内容无法动态调整
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||||
|
|||||||
10
package.json
10
package.json
@ -71,9 +71,9 @@
|
|||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-error-boundary": "6.0.0",
|
"react-error-boundary": "6.0.0",
|
||||||
"react-hook-form": "^7.66.0",
|
"react-hook-form": "^7.66.0",
|
||||||
"react-i18next": "16.3.1",
|
"react-i18next": "16.3.3",
|
||||||
"react-markdown": "10.1.0",
|
"react-markdown": "10.1.0",
|
||||||
"react-router": "^7.9.5",
|
"react-router": "^7.9.6",
|
||||||
"react-virtuoso": "^4.14.1",
|
"react-virtuoso": "^4.14.1",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#main",
|
"tauri-plugin-mihomo-api": "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#main",
|
||||||
@ -87,7 +87,7 @@
|
|||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "19.2.4",
|
"@types/react": "19.2.5",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"@vitejs/plugin-legacy": "^7.2.1",
|
"@vitejs/plugin-legacy": "^7.2.1",
|
||||||
"@vitejs/plugin-react-swc": "^4.2.2",
|
"@vitejs/plugin-react-swc": "^4.2.2",
|
||||||
@ -109,7 +109,7 @@
|
|||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"lint-staged": "^16.2.6",
|
"lint-staged": "^16.2.6",
|
||||||
"meta-json-schema": "^1.19.14",
|
"meta-json-schema": "^1.19.16",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"sass": "^1.94.0",
|
"sass": "^1.94.0",
|
||||||
@ -120,7 +120,7 @@
|
|||||||
"vite": "^7.2.2",
|
"vite": "^7.2.2",
|
||||||
"vite-plugin-monaco-editor-esm": "^2.0.2",
|
"vite-plugin-monaco-editor-esm": "^2.0.2",
|
||||||
"vite-plugin-svgr": "^4.5.0",
|
"vite-plugin-svgr": "^4.5.0",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.0.9"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx,js,jsx}": [
|
"*.{ts,tsx,js,jsx}": [
|
||||||
|
|||||||
266
pnpm-lock.yaml
generated
266
pnpm-lock.yaml
generated
@ -19,10 +19,10 @@ importers:
|
|||||||
version: 3.2.2(react@19.2.0)
|
version: 3.2.2(react@19.2.0)
|
||||||
'@emotion/react':
|
'@emotion/react':
|
||||||
specifier: ^11.14.0
|
specifier: ^11.14.0
|
||||||
version: 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
version: 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/styled':
|
'@emotion/styled':
|
||||||
specifier: ^11.14.1
|
specifier: ^11.14.1
|
||||||
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@juggle/resize-observer':
|
'@juggle/resize-observer':
|
||||||
specifier: ^3.4.0
|
specifier: ^3.4.0
|
||||||
version: 3.4.0
|
version: 3.4.0
|
||||||
@ -31,16 +31,16 @@ importers:
|
|||||||
version: 4.7.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 4.7.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@mui/icons-material':
|
'@mui/icons-material':
|
||||||
specifier: ^7.3.5
|
specifier: ^7.3.5
|
||||||
version: 7.3.5(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
version: 7.3.5(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/lab':
|
'@mui/lab':
|
||||||
specifier: 7.0.0-beta.17
|
specifier: 7.0.0-beta.17
|
||||||
version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@mui/material':
|
'@mui/material':
|
||||||
specifier: ^7.3.5
|
specifier: ^7.3.5
|
||||||
version: 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@mui/x-data-grid':
|
'@mui/x-data-grid':
|
||||||
specifier: ^8.18.0
|
specifier: ^8.18.0
|
||||||
version: 8.18.0(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 8.18.0(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@tauri-apps/api':
|
'@tauri-apps/api':
|
||||||
specifier: 2.9.0
|
specifier: 2.9.0
|
||||||
version: 2.9.0
|
version: 2.9.0
|
||||||
@ -114,14 +114,14 @@ importers:
|
|||||||
specifier: ^7.66.0
|
specifier: ^7.66.0
|
||||||
version: 7.66.0(react@19.2.0)
|
version: 7.66.0(react@19.2.0)
|
||||||
react-i18next:
|
react-i18next:
|
||||||
specifier: 16.3.1
|
specifier: 16.3.3
|
||||||
version: 16.3.1(i18next@25.6.2(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
version: 16.3.3(i18next@25.6.2(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||||
react-markdown:
|
react-markdown:
|
||||||
specifier: 10.1.0
|
specifier: 10.1.0
|
||||||
version: 10.1.0(@types/react@19.2.4)(react@19.2.0)
|
version: 10.1.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
react-router:
|
react-router:
|
||||||
specifier: ^7.9.5
|
specifier: ^7.9.6
|
||||||
version: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
react-virtuoso:
|
react-virtuoso:
|
||||||
specifier: ^4.14.1
|
specifier: ^4.14.1
|
||||||
version: 4.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 4.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
@ -157,11 +157,11 @@ importers:
|
|||||||
specifier: ^24.10.1
|
specifier: ^24.10.1
|
||||||
version: 24.10.1
|
version: 24.10.1
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: 19.2.4
|
specifier: 19.2.5
|
||||||
version: 19.2.4
|
version: 19.2.5
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: 19.2.3
|
specifier: 19.2.3
|
||||||
version: 19.2.3(@types/react@19.2.4)
|
version: 19.2.3(@types/react@19.2.5)
|
||||||
'@vitejs/plugin-legacy':
|
'@vitejs/plugin-legacy':
|
||||||
specifier: ^7.2.1
|
specifier: ^7.2.1
|
||||||
version: 7.2.1(terser@5.44.1)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))
|
version: 7.2.1(terser@5.44.1)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))
|
||||||
@ -223,8 +223,8 @@ importers:
|
|||||||
specifier: ^16.2.6
|
specifier: ^16.2.6
|
||||||
version: 16.2.6
|
version: 16.2.6
|
||||||
meta-json-schema:
|
meta-json-schema:
|
||||||
specifier: ^1.19.14
|
specifier: ^1.19.16
|
||||||
version: 1.19.14
|
version: 1.19.16
|
||||||
node-fetch:
|
node-fetch:
|
||||||
specifier: ^3.3.2
|
specifier: ^3.3.2
|
||||||
version: 3.3.2
|
version: 3.3.2
|
||||||
@ -256,8 +256,8 @@ importers:
|
|||||||
specifier: ^4.5.0
|
specifier: ^4.5.0
|
||||||
version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))
|
version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^4.0.8
|
specifier: ^4.0.9
|
||||||
version: 4.0.8(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1)
|
version: 4.0.9(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@ -1850,8 +1850,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': '*'
|
||||||
|
|
||||||
'@types/react@19.2.4':
|
'@types/react@19.2.5':
|
||||||
resolution: {integrity: sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A==}
|
resolution: {integrity: sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==}
|
||||||
|
|
||||||
'@types/unist@2.0.11':
|
'@types/unist@2.0.11':
|
||||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||||
@ -2029,11 +2029,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4 || ^5 || ^6 || ^7
|
vite: ^4 || ^5 || ^6 || ^7
|
||||||
|
|
||||||
'@vitest/expect@4.0.8':
|
'@vitest/expect@4.0.9':
|
||||||
resolution: {integrity: sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==}
|
resolution: {integrity: sha512-C2vyXf5/Jfj1vl4DQYxjib3jzyuswMi/KHHVN2z+H4v16hdJ7jMZ0OGe3uOVIt6LyJsAofDdaJNIFEpQcrSTFw==}
|
||||||
|
|
||||||
'@vitest/mocker@4.0.8':
|
'@vitest/mocker@4.0.9':
|
||||||
resolution: {integrity: sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==}
|
resolution: {integrity: sha512-PUyaowQFHW+9FKb4dsvvBM4o025rWMlEDXdWRxIOilGaHREYTi5Q2Rt9VCgXgPy/hHZu1LeuXtrA/GdzOatP2g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
msw: ^2.4.9
|
msw: ^2.4.9
|
||||||
vite: ^6.0.0 || ^7.0.0-0
|
vite: ^6.0.0 || ^7.0.0-0
|
||||||
@ -2043,20 +2043,20 @@ packages:
|
|||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vitest/pretty-format@4.0.8':
|
'@vitest/pretty-format@4.0.9':
|
||||||
resolution: {integrity: sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==}
|
resolution: {integrity: sha512-Hor0IBTwEi/uZqB7pvGepyElaM8J75pYjrrqbC8ZYMB9/4n5QA63KC15xhT+sqHpdGWfdnPo96E8lQUxs2YzSQ==}
|
||||||
|
|
||||||
'@vitest/runner@4.0.8':
|
'@vitest/runner@4.0.9':
|
||||||
resolution: {integrity: sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==}
|
resolution: {integrity: sha512-aF77tsXdEvIJRkj9uJZnHtovsVIx22Ambft9HudC+XuG/on1NY/bf5dlDti1N35eJT+QZLb4RF/5dTIG18s98w==}
|
||||||
|
|
||||||
'@vitest/snapshot@4.0.8':
|
'@vitest/snapshot@4.0.9':
|
||||||
resolution: {integrity: sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==}
|
resolution: {integrity: sha512-r1qR4oYstPbnOjg0Vgd3E8ADJbi4ditCzqr+Z9foUrRhIy778BleNyZMeAJ2EjV+r4ASAaDsdciC9ryMy8xMMg==}
|
||||||
|
|
||||||
'@vitest/spy@4.0.8':
|
'@vitest/spy@4.0.9':
|
||||||
resolution: {integrity: sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==}
|
resolution: {integrity: sha512-J9Ttsq0hDXmxmT8CUOWUr1cqqAj2FJRGTdyEjSR+NjoOGKEqkEWj+09yC0HhI8t1W6t4Ctqawl1onHgipJve1A==}
|
||||||
|
|
||||||
'@vitest/utils@4.0.8':
|
'@vitest/utils@4.0.9':
|
||||||
resolution: {integrity: sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==}
|
resolution: {integrity: sha512-cEol6ygTzY4rUPvNZM19sDf7zGa35IYTm9wfzkHoT/f5jX10IOY7QleWSOh5T0e3I3WVozwK5Asom79qW8DiuQ==}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
@ -3299,8 +3299,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
meta-json-schema@1.19.14:
|
meta-json-schema@1.19.16:
|
||||||
resolution: {integrity: sha512-A+NSHAfXWn2T225dawVuLXVXrSWhxjRNiG+nS+Cet1Zovslrq2lMqvkIrXhdaK6Gv+VYrEV8rAkYcqAz2pxKMw==}
|
resolution: {integrity: sha512-Py3XR3VRXs3tAMg3sy7fmex8IU4p4FTxVbF86WTtssWpFcSNbBUjk0QjpdhGrh+9qPMSwCJY1drXnvgDq9XQ7Q==}
|
||||||
engines: {node: '>=18', pnpm: '>=9'}
|
engines: {node: '>=18', pnpm: '>=9'}
|
||||||
|
|
||||||
micromark-core-commonmark@2.0.3:
|
micromark-core-commonmark@2.0.3:
|
||||||
@ -3634,8 +3634,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
|
||||||
react-i18next@16.3.1:
|
react-i18next@16.3.3:
|
||||||
resolution: {integrity: sha512-HbYaBeA58Hg38OzdEvJp4kLIvk10rp9F9Jq+wNkqtqxDXObtdYMSsQnegWgdUVcpZjZuK9ZxehM+Z9BW2Vqgqw==}
|
resolution: {integrity: sha512-IaY2W+ueVd/fe7H6Wj2S4bTuLNChnajFUlZFfCTrTHWzGcOrUHlVzW55oXRSl+J51U8Onn6EvIhQ+Bar9FUcjw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
i18next: '>= 25.6.2'
|
i18next: '>= 25.6.2'
|
||||||
react: '>= 16.8.0'
|
react: '>= 16.8.0'
|
||||||
@ -3662,8 +3662,8 @@ packages:
|
|||||||
'@types/react': '>=18'
|
'@types/react': '>=18'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
|
|
||||||
react-router@7.9.5:
|
react-router@7.9.6:
|
||||||
resolution: {integrity: sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==}
|
resolution: {integrity: sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
@ -4198,18 +4198,18 @@ packages:
|
|||||||
yaml:
|
yaml:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vitest@4.0.8:
|
vitest@4.0.9:
|
||||||
resolution: {integrity: sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==}
|
resolution: {integrity: sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg==}
|
||||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@edge-runtime/vm': '*'
|
'@edge-runtime/vm': '*'
|
||||||
'@types/debug': ^4.1.12
|
'@types/debug': ^4.1.12
|
||||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||||
'@vitest/browser-playwright': 4.0.8
|
'@vitest/browser-playwright': 4.0.9
|
||||||
'@vitest/browser-preview': 4.0.8
|
'@vitest/browser-preview': 4.0.9
|
||||||
'@vitest/browser-webdriverio': 4.0.8
|
'@vitest/browser-webdriverio': 4.0.9
|
||||||
'@vitest/ui': 4.0.8
|
'@vitest/ui': 4.0.9
|
||||||
happy-dom: '*'
|
happy-dom: '*'
|
||||||
jsdom: '*'
|
jsdom: '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -5077,7 +5077,7 @@ snapshots:
|
|||||||
|
|
||||||
'@emotion/memoize@0.9.0': {}
|
'@emotion/memoize@0.9.0': {}
|
||||||
|
|
||||||
'@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0)':
|
'@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@emotion/babel-plugin': 11.13.5
|
'@emotion/babel-plugin': 11.13.5
|
||||||
@ -5089,7 +5089,7 @@ snapshots:
|
|||||||
hoist-non-react-statics: 3.3.2
|
hoist-non-react-statics: 3.3.2
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -5103,18 +5103,18 @@ snapshots:
|
|||||||
|
|
||||||
'@emotion/sheet@1.4.0': {}
|
'@emotion/sheet@1.4.0': {}
|
||||||
|
|
||||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)':
|
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@emotion/babel-plugin': 11.13.5
|
'@emotion/babel-plugin': 11.13.5
|
||||||
'@emotion/is-prop-valid': 1.3.1
|
'@emotion/is-prop-valid': 1.3.1
|
||||||
'@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/serialize': 1.3.3
|
'@emotion/serialize': 1.3.3
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0)
|
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0)
|
||||||
'@emotion/utils': 1.4.2
|
'@emotion/utils': 1.4.2
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -5397,39 +5397,39 @@ snapshots:
|
|||||||
|
|
||||||
'@mui/core-downloads-tracker@7.3.5': {}
|
'@mui/core-downloads-tracker@7.3.5': {}
|
||||||
|
|
||||||
'@mui/icons-material@7.3.5(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)':
|
'@mui/icons-material@7.3.5(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
'@mui/lab@7.0.0-beta.17(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/types': 7.4.8(@types/react@19.2.4)
|
'@mui/types': 7.4.8(@types/react@19.2.5)
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
'@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/core-downloads-tracker': 7.3.5
|
'@mui/core-downloads-tracker': 7.3.5
|
||||||
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/types': 7.4.8(@types/react@19.2.4)
|
'@mui/types': 7.4.8(@types/react@19.2.5)
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@popperjs/core': 2.11.8
|
'@popperjs/core': 2.11.8
|
||||||
'@types/react-transition-group': 4.4.12(@types/react@19.2.4)
|
'@types/react-transition-group': 4.4.12(@types/react@19.2.5)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
@ -5438,20 +5438,20 @@ snapshots:
|
|||||||
react-is: 19.2.0
|
react-is: 19.2.0
|
||||||
react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/private-theming@7.3.5(@types/react@19.2.4)(react@19.2.0)':
|
'@mui/private-theming@7.3.5(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/styled-engine@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(react@19.2.0)':
|
'@mui/styled-engine@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@emotion/cache': 11.14.0
|
'@emotion/cache': 11.14.0
|
||||||
@ -5461,77 +5461,77 @@ snapshots:
|
|||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
|
|
||||||
'@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)':
|
'@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/private-theming': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/private-theming': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/styled-engine': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(react@19.2.0)
|
'@mui/styled-engine': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(react@19.2.0)
|
||||||
'@mui/types': 7.4.8(@types/react@19.2.4)
|
'@mui/types': 7.4.8(@types/react@19.2.5)
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/types@7.4.8(@types/react@19.2.4)':
|
'@mui/types@7.4.8(@types/react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/utils@7.3.5(@types/react@19.2.4)(react@19.2.0)':
|
'@mui/utils@7.3.5(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/types': 7.4.8(@types/react@19.2.4)
|
'@mui/types': 7.4.8(@types/react@19.2.5)
|
||||||
'@types/prop-types': 15.7.15
|
'@types/prop-types': 15.7.15
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-is: 19.2.0
|
react-is: 19.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@mui/x-data-grid@8.18.0(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
'@mui/x-data-grid@8.18.0(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@mui/material@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
'@mui/material': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@mui/system': 7.3.5(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/x-internals': 8.18.0(@types/react@19.2.4)(react@19.2.0)
|
'@mui/x-internals': 8.18.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/x-virtualizer': 0.2.8(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
'@mui/x-virtualizer': 0.2.8(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
use-sync-external-store: 1.6.0(react@19.2.0)
|
use-sync-external-store: 1.6.0(react@19.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/react': 11.14.0(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/react': 11.14.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.4)(react@19.2.0))(@types/react@19.2.4)(react@19.2.0)
|
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.5)(react@19.2.0))(@types/react@19.2.5)(react@19.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
||||||
'@mui/x-internals@8.18.0(@types/react@19.2.4)(react@19.2.0)':
|
'@mui/x-internals@8.18.0(@types/react@19.2.5)(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
reselect: 5.1.1
|
reselect: 5.1.1
|
||||||
use-sync-external-store: 1.6.0(react@19.2.0)
|
use-sync-external-store: 1.6.0(react@19.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
||||||
'@mui/x-virtualizer@0.2.8(@types/react@19.2.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
'@mui/x-virtualizer@0.2.8(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
'@mui/utils': 7.3.5(@types/react@19.2.4)(react@19.2.0)
|
'@mui/utils': 7.3.5(@types/react@19.2.5)(react@19.2.0)
|
||||||
'@mui/x-internals': 8.18.0(@types/react@19.2.4)(react@19.2.0)
|
'@mui/x-internals': 8.18.0(@types/react@19.2.5)(react@19.2.0)
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -6007,15 +6007,15 @@ snapshots:
|
|||||||
|
|
||||||
'@types/prop-types@15.7.15': {}
|
'@types/prop-types@15.7.15': {}
|
||||||
|
|
||||||
'@types/react-dom@19.2.3(@types/react@19.2.4)':
|
'@types/react-dom@19.2.3(@types/react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@types/react-transition-group@4.4.12(@types/react@19.2.4)':
|
'@types/react-transition-group@4.4.12(@types/react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
|
|
||||||
'@types/react@19.2.4':
|
'@types/react@19.2.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
|
|
||||||
@ -6204,43 +6204,43 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@swc/helpers'
|
- '@swc/helpers'
|
||||||
|
|
||||||
'@vitest/expect@4.0.8':
|
'@vitest/expect@4.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/spec': 1.0.0
|
'@standard-schema/spec': 1.0.0
|
||||||
'@types/chai': 5.2.2
|
'@types/chai': 5.2.2
|
||||||
'@vitest/spy': 4.0.8
|
'@vitest/spy': 4.0.9
|
||||||
'@vitest/utils': 4.0.8
|
'@vitest/utils': 4.0.9
|
||||||
chai: 6.2.0
|
chai: 6.2.0
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
'@vitest/mocker@4.0.8(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))':
|
'@vitest/mocker@4.0.9(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 4.0.8
|
'@vitest/spy': 4.0.9
|
||||||
estree-walker: 3.0.3
|
estree-walker: 3.0.3
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1)
|
vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1)
|
||||||
|
|
||||||
'@vitest/pretty-format@4.0.8':
|
'@vitest/pretty-format@4.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
'@vitest/runner@4.0.8':
|
'@vitest/runner@4.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/utils': 4.0.8
|
'@vitest/utils': 4.0.9
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
'@vitest/snapshot@4.0.8':
|
'@vitest/snapshot@4.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 4.0.8
|
'@vitest/pretty-format': 4.0.9
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
|
|
||||||
'@vitest/spy@4.0.8': {}
|
'@vitest/spy@4.0.9': {}
|
||||||
|
|
||||||
'@vitest/utils@4.0.8':
|
'@vitest/utils@4.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/pretty-format': 4.0.8
|
'@vitest/pretty-format': 4.0.9
|
||||||
tinyrainbow: 3.0.3
|
tinyrainbow: 3.0.3
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||||
@ -7795,7 +7795,7 @@ snapshots:
|
|||||||
|
|
||||||
merge2@1.4.1: {}
|
merge2@1.4.1: {}
|
||||||
|
|
||||||
meta-json-schema@1.19.14: {}
|
meta-json-schema@1.19.16: {}
|
||||||
|
|
||||||
micromark-core-commonmark@2.0.3:
|
micromark-core-commonmark@2.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8200,7 +8200,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
|
|
||||||
react-i18next@16.3.1(i18next@25.6.2(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3):
|
react-i18next@16.3.3(i18next@25.6.2(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
html-parse-stringify: 3.0.1
|
html-parse-stringify: 3.0.1
|
||||||
@ -8215,11 +8215,11 @@ snapshots:
|
|||||||
|
|
||||||
react-is@19.2.0: {}
|
react-is@19.2.0: {}
|
||||||
|
|
||||||
react-markdown@10.1.0(@types/react@19.2.4)(react@19.2.0):
|
react-markdown@10.1.0(@types/react@19.2.5)(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
'@types/react': 19.2.4
|
'@types/react': 19.2.5
|
||||||
devlop: 1.1.0
|
devlop: 1.1.0
|
||||||
hast-util-to-jsx-runtime: 2.3.6
|
hast-util-to-jsx-runtime: 2.3.6
|
||||||
html-url-attributes: 3.0.1
|
html-url-attributes: 3.0.1
|
||||||
@ -8233,7 +8233,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
cookie: 1.0.2
|
cookie: 1.0.2
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
@ -8884,15 +8884,15 @@ snapshots:
|
|||||||
terser: 5.44.1
|
terser: 5.44.1
|
||||||
yaml: 2.8.1
|
yaml: 2.8.1
|
||||||
|
|
||||||
vitest@4.0.8(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1):
|
vitest@4.0.9(@types/debug@4.1.12)(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/expect': 4.0.8
|
'@vitest/expect': 4.0.9
|
||||||
'@vitest/mocker': 4.0.8(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))
|
'@vitest/mocker': 4.0.9(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(sass@1.94.0)(terser@5.44.1)(yaml@2.8.1))
|
||||||
'@vitest/pretty-format': 4.0.8
|
'@vitest/pretty-format': 4.0.9
|
||||||
'@vitest/runner': 4.0.8
|
'@vitest/runner': 4.0.9
|
||||||
'@vitest/snapshot': 4.0.8
|
'@vitest/snapshot': 4.0.9
|
||||||
'@vitest/spy': 4.0.8
|
'@vitest/spy': 4.0.9
|
||||||
'@vitest/utils': 4.0.8
|
'@vitest/utils': 4.0.9
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
es-module-lexer: 1.7.0
|
es-module-lexer: 1.7.0
|
||||||
expect-type: 1.2.2
|
expect-type: 1.2.2
|
||||||
|
|||||||
202
src-tauri/Cargo.lock
generated
202
src-tauri/Cargo.lock
generated
@ -145,7 +145,7 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"parking_lot 0.12.5",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
"wl-clipboard-rs",
|
"wl-clipboard-rs",
|
||||||
@ -967,12 +967,6 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "castaway"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "castaway"
|
name = "castaway"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@ -1128,19 +1122,19 @@ dependencies = [
|
|||||||
"criterion",
|
"criterion",
|
||||||
"deelevate",
|
"deelevate",
|
||||||
"delay_timer",
|
"delay_timer",
|
||||||
|
"draft",
|
||||||
"dunce",
|
"dunce",
|
||||||
"flexi_logger",
|
"flexi_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"gethostname",
|
"gethostname",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"isahc",
|
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"nanoid",
|
"nanoid",
|
||||||
"network-interface",
|
"network-interface",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open",
|
"open",
|
||||||
"parking_lot 0.12.5",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"port_scanner",
|
"port_scanner",
|
||||||
"regex",
|
"regex",
|
||||||
@ -1258,7 +1252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1277,7 +1271,7 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"castaway 0.2.4",
|
"castaway",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"itoa",
|
"itoa",
|
||||||
"rkyv",
|
"rkyv",
|
||||||
@ -1649,36 +1643,6 @@ dependencies = [
|
|||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "curl"
|
|
||||||
version = "0.4.49"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc"
|
|
||||||
dependencies = [
|
|
||||||
"curl-sys",
|
|
||||||
"libc",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"socket2 0.6.1",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "curl-sys"
|
|
||||||
version = "0.4.83+curl-8.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"libz-sys",
|
|
||||||
"openssl-sys",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.21.3"
|
version = "0.21.3"
|
||||||
@ -1724,7 +1688,7 @@ dependencies = [
|
|||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core 0.9.12",
|
"parking_lot_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1738,7 +1702,7 @@ dependencies = [
|
|||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core 0.9.12",
|
"parking_lot_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2054,6 +2018,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "draft"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"criterion",
|
||||||
|
"parking_lot",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dtoa"
|
name = "dtoa"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
@ -3416,7 +3390,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3731,34 +3705,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "isahc"
|
|
||||||
version = "1.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
|
|
||||||
dependencies = [
|
|
||||||
"async-channel 1.9.0",
|
|
||||||
"castaway 0.1.2",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"curl",
|
|
||||||
"curl-sys",
|
|
||||||
"encoding_rs",
|
|
||||||
"event-listener 2.5.3",
|
|
||||||
"futures-lite 1.13.0",
|
|
||||||
"http 0.2.12",
|
|
||||||
"log",
|
|
||||||
"mime",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot 0.11.2",
|
|
||||||
"polling 2.8.0",
|
|
||||||
"slab",
|
|
||||||
"sluice",
|
|
||||||
"tracing",
|
|
||||||
"tracing-futures",
|
|
||||||
"url",
|
|
||||||
"waker-fn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -3911,7 +3857,7 @@ dependencies = [
|
|||||||
"httparse",
|
"httparse",
|
||||||
"interprocess",
|
"interprocess",
|
||||||
"libc",
|
"libc",
|
||||||
"parking_lot 0.12.5",
|
"parking_lot",
|
||||||
"path-tree",
|
"path-tree",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
@ -4009,7 +3955,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4021,18 +3967,6 @@ dependencies = [
|
|||||||
"zlib-rs",
|
"zlib-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libz-sys"
|
|
||||||
version = "1.1.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
@ -4918,7 +4852,7 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parking_lot_core 0.9.12",
|
"parking_lot_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5031,7 +4965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5079,17 +5013,6 @@ version = "2.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
|
||||||
dependencies = [
|
|
||||||
"instant",
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core 0.8.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@ -5097,21 +5020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core 0.9.12",
|
"parking_lot_core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.8.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"instant",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall 0.2.16",
|
|
||||||
"smallvec",
|
|
||||||
"winapi",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5122,7 +5031,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
@ -6039,15 +5948,6 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@ -6417,7 +6317,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6994,17 +6894,6 @@ version = "0.4.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sluice"
|
|
||||||
version = "0.5.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
|
|
||||||
dependencies = [
|
|
||||||
"async-channel 1.9.0",
|
|
||||||
"futures-core",
|
|
||||||
"futures-io",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "small_btree"
|
name = "small_btree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -7085,7 +6974,7 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-quartz-core 0.2.2",
|
"objc2-quartz-core 0.2.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@ -7142,7 +7031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"new_debug_unreachable",
|
"new_debug_unreachable",
|
||||||
"parking_lot 0.12.5",
|
"parking_lot",
|
||||||
"phf_shared 0.11.3",
|
"phf_shared 0.11.3",
|
||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
"serde",
|
"serde",
|
||||||
@ -7274,13 +7163,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysproxy"
|
name = "sysproxy"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#ea6e5b5bcef32025e1df914d663eea8558afacb2"
|
source = "git+https://github.com/clash-verge-rev/sysproxy-rs#0f844dd2639b0ac74da4548b1325335844947420"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"interfaces",
|
"interfaces",
|
||||||
"iptools",
|
"iptools",
|
||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"windows 0.62.2",
|
"windows 0.62.2",
|
||||||
"winreg 0.55.0",
|
"winreg 0.55.0",
|
||||||
@ -7355,7 +7245,7 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot 0.12.5",
|
"parking_lot",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
@ -7403,9 +7293,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.9.2"
|
version = "2.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5"
|
checksum = "9e492485dd390b35f7497401f67694f46161a2a00ffd800938d5dd3c898fb9d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -7457,9 +7347,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "2.5.1"
|
version = "2.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a924b6c50fe83193f0f8b14072afa7c25b7a72752a2a73d9549b463f5fe91a38"
|
checksum = "87d6f8cafe6a75514ce5333f115b7b1866e8e68d9672bf4ca89fc0f35697ea9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@ -7479,9 +7369,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190"
|
checksum = "b7ef707148f0755110ca54377560ab891d722de4d53297595380a748026f139f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
@ -7506,9 +7396,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f"
|
checksum = "71664fd715ee6e382c05345ad258d6d1d50f90cf1b58c0aa726638b33c2a075d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -8186,7 +8076,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot 0.12.5",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.6.1",
|
"socket2 0.6.1",
|
||||||
@ -8606,16 +8496,6 @@ dependencies = [
|
|||||||
"valuable",
|
"valuable",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-futures"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
|
|
||||||
dependencies = [
|
|
||||||
"pin-project",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-log"
|
name = "tracing-log"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -9338,7 +9218,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -14,11 +14,14 @@ rust-version = "1.91"
|
|||||||
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
identifier = "io.github.clash-verge-rev.clash-verge-rev"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.5.1", features = [] }
|
tauri-build = { version = "2.5.2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
parking_lot = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
draft = { workspace = true }
|
||||||
warp = { version = "0.4.2", features = ["server"] }
|
warp = { version = "0.4.2", features = ["server"] }
|
||||||
anyhow = "1.0.100"
|
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
@ -31,19 +34,14 @@ serde_yaml_ng = "0.10.0"
|
|||||||
once_cell = { version = "1.21.3", features = ["parking_lot"] }
|
once_cell = { version = "1.21.3", features = ["parking_lot"] }
|
||||||
port_scanner = "0.1.5"
|
port_scanner = "0.1.5"
|
||||||
delay_timer = "0.11.6"
|
delay_timer = "0.11.6"
|
||||||
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
|
|
||||||
percent-encoding = "2.3.2"
|
percent-encoding = "2.3.2"
|
||||||
tokio = { version = "1.48.0", features = [
|
|
||||||
"rt-multi-thread",
|
|
||||||
"macros",
|
|
||||||
"time",
|
|
||||||
"sync",
|
|
||||||
] }
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
reqwest = { version = "0.12.24", features = ["json", "cookies"] }
|
reqwest = { version = "0.12.24", features = ["json", "cookies"] }
|
||||||
regex = "1.12.2"
|
regex = "1.12.2"
|
||||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", features = [
|
||||||
tauri = { version = "2.9.2", features = [
|
"guard",
|
||||||
|
] }
|
||||||
|
tauri = { version = "2.9.3", features = [
|
||||||
"protocol-asset",
|
"protocol-asset",
|
||||||
"devtools",
|
"devtools",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
@ -70,10 +68,6 @@ gethostname = "1.1.0"
|
|||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
tauri-plugin-notification = "2.3.3"
|
tauri-plugin-notification = "2.3.3"
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
isahc = { version = "1.7.2", default-features = false, features = [
|
|
||||||
"text-decoding",
|
|
||||||
"parking_lot",
|
|
||||||
] }
|
|
||||||
backoff = { version = "0.4.0", features = ["tokio"] }
|
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||||
compact_str = { version = "0.9.0", features = ["serde"] }
|
compact_str = { version = "0.9.0", features = ["serde"] }
|
||||||
tauri-plugin-http = "2.5.4"
|
tauri-plugin-http = "2.5.4"
|
||||||
@ -123,6 +117,28 @@ tauri-plugin-global-shortcut = "2.3.1"
|
|||||||
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
|
||||||
tauri-plugin-updater = "2.9.0"
|
tauri-plugin-updater = "2.9.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = { workspace = true }
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
draft = { path = "crates/draft" }
|
||||||
|
parking_lot = { version = "0.12.5", features = [
|
||||||
|
"hardware-lock-elision",
|
||||||
|
"send_guard",
|
||||||
|
] }
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
criterion = { version = "0.7.0", features = ["async_tokio"] }
|
||||||
|
tokio = { version = "1.48.0", features = [
|
||||||
|
"rt-multi-thread",
|
||||||
|
"macros",
|
||||||
|
"time",
|
||||||
|
"sync",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["crates/*"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["custom-protocol"]
|
default = ["custom-protocol"]
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
@ -132,11 +148,6 @@ tokio-trace = ["console-subscriber"]
|
|||||||
clippy = ["tauri/test"]
|
clippy = ["tauri/test"]
|
||||||
tracing = []
|
tracing = []
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "draft_benchmark"
|
|
||||||
path = "benches/draft_benchmark.rs"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
@ -170,9 +181,6 @@ strip = false
|
|||||||
name = "app_lib"
|
name = "app_lib"
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = { version = "0.7.0", features = ["async_tokio"] }
|
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
# Core categories - most important for code safety and correctness
|
# Core categories - most important for code safety and correctness
|
||||||
correctness = { level = "deny", priority = -1 }
|
correctness = { level = "deny", priority = -1 }
|
||||||
@ -259,3 +267,4 @@ cloned_instead_of_copied = "deny"
|
|||||||
unnecessary_self_imports = "deny"
|
unnecessary_self_imports = "deny"
|
||||||
unused_trait_names = "deny"
|
unused_trait_names = "deny"
|
||||||
wildcard_imports = "deny"
|
wildcard_imports = "deny"
|
||||||
|
unnecessary_wraps = "deny"
|
||||||
|
|||||||
17
src-tauri/crates/draft/Cargo.toml
Normal file
17
src-tauri/crates/draft/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "draft"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "draft_bench"
|
||||||
|
path = "bench/benche_me.rs"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
parking_lot = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
@ -3,17 +3,30 @@ use std::hint::black_box;
|
|||||||
use std::process;
|
use std::process;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use app_lib::config::IVerge;
|
use draft::Draft;
|
||||||
use app_lib::utils::Draft as DraftNew;
|
|
||||||
|
|
||||||
/// 创建测试数据
|
#[derive(Clone, Debug)]
|
||||||
fn make_draft() -> DraftNew<IVerge> {
|
struct IVerge {
|
||||||
|
enable_auto_launch: Option<bool>,
|
||||||
|
enable_tun_mode: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IVerge {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enable_auto_launch: None,
|
||||||
|
enable_tun_mode: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_draft() -> Draft<IVerge> {
|
||||||
let verge = IVerge {
|
let verge = IVerge {
|
||||||
enable_auto_launch: Some(true),
|
enable_auto_launch: Some(true),
|
||||||
enable_tun_mode: Some(false),
|
enable_tun_mode: Some(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
DraftNew::new(verge)
|
Draft::new(verge)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bench_draft(c: &mut Criterion) {
|
pub fn bench_draft(c: &mut Criterion) {
|
||||||
102
src-tauri/crates/draft/src/lib.rs
Normal file
102
src-tauri/crates/draft/src/lib.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use parking_lot::RwLock;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub type SharedBox<T> = Arc<Box<T>>;
|
||||||
|
type DraftInner<T> = (SharedBox<T>, Option<SharedBox<T>>);
|
||||||
|
|
||||||
|
/// Draft 管理:committed 与 optional draft 都以 Arc<Box<T>> 存储,
|
||||||
|
// (committed_snapshot, optional_draft_snapshot)
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Draft<T: Clone> {
|
||||||
|
inner: Arc<RwLock<DraftInner<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Draft<T> {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(data: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new((Arc::new(Box::new(data)), None))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc)
|
||||||
|
#[inline]
|
||||||
|
pub fn data_arc(&self) -> SharedBox<T> {
|
||||||
|
let guard = self.inner.read();
|
||||||
|
Arc::clone(&guard.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
|
||||||
|
/// 这也是零拷贝:只 clone Arc,不 clone T
|
||||||
|
#[inline]
|
||||||
|
pub fn latest_arc(&self) -> SharedBox<T> {
|
||||||
|
let guard = self.inner.read();
|
||||||
|
guard.1.clone().unwrap_or_else(|| Arc::clone(&guard.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T)
|
||||||
|
/// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T;
|
||||||
|
/// - 若草稿被其他读者共享,Arc::make_mut 会做一次 T.clone(最小必要拷贝)。
|
||||||
|
#[inline]
|
||||||
|
pub fn edit_draft<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> R,
|
||||||
|
{
|
||||||
|
// 先获得写锁以创建或取出草稿 Arc 的可变引用位置
|
||||||
|
let mut guard = self.inner.write();
|
||||||
|
let mut draft_arc = if guard.1.is_none() {
|
||||||
|
Arc::clone(&guard.0)
|
||||||
|
} else {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
guard.1.take().unwrap()
|
||||||
|
};
|
||||||
|
drop(guard);
|
||||||
|
// Arc::make_mut: 如果只有一个引用则返回可变引用;否则会克隆底层 Box<T>(要求 T: Clone)
|
||||||
|
let boxed = Arc::make_mut(&mut draft_arc); // &mut Box<T>
|
||||||
|
// 对 Box<T> 解引用得到 &mut T
|
||||||
|
let result = f(&mut **boxed);
|
||||||
|
// 恢复修改后的草稿 Arc
|
||||||
|
self.inner.write().1 = Some(draft_arc);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将草稿提交到已提交位置(替换),并清除草稿
|
||||||
|
#[inline]
|
||||||
|
pub fn apply(&self) {
|
||||||
|
let mut guard = self.inner.write();
|
||||||
|
if let Some(d) = guard.1.take() {
|
||||||
|
guard.0 = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 丢弃草稿(如果存在)
|
||||||
|
#[inline]
|
||||||
|
pub fn discard(&self) {
|
||||||
|
let mut guard = self.inner.write();
|
||||||
|
guard.1 = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
|
||||||
|
/// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
|
||||||
|
#[inline]
|
||||||
|
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
|
||||||
|
where
|
||||||
|
T: Send + Sync + 'static,
|
||||||
|
F: FnOnce(Box<T>) -> Fut + Send,
|
||||||
|
Fut: std::future::Future<Output = Result<(Box<T>, R), anyhow::Error>> + Send,
|
||||||
|
{
|
||||||
|
// 读取已提交快照(cheap Arc clone, 然后得到 Box<T> 所有权 via clone)
|
||||||
|
// 注意:为了让闭包接收 Box<T> 所有权,我们需要 clone 底层 T(不可避免)
|
||||||
|
let local: Box<T> = {
|
||||||
|
let guard = self.inner.read();
|
||||||
|
// 将 Arc<Box<T>> 的 Box<T> clone 出来(会调用 T: Clone)
|
||||||
|
(*guard.0).clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (new_local, res) = f(local).await?;
|
||||||
|
|
||||||
|
// 将新的 Box<T> 放到已提交位置(包进 Arc)
|
||||||
|
self.inner.write().0 = Arc::new(new_local);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,110 +1,7 @@
|
|||||||
use parking_lot::RwLock;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub type SharedBox<T> = Arc<Box<T>>;
|
|
||||||
type DraftInner<T> = (SharedBox<T>, Option<SharedBox<T>>);
|
|
||||||
|
|
||||||
/// Draft 管理:committed 与 optional draft 都以 Arc<Box<T>> 存储,
|
|
||||||
// (committed_snapshot, optional_draft_snapshot)
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Draft<T: Clone> {
|
|
||||||
inner: Arc<RwLock<DraftInner<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Clone> Draft<T> {
|
|
||||||
#[inline]
|
|
||||||
pub fn new(data: T) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Arc::new(RwLock::new((Arc::new(Box::new(data)), None))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc)
|
|
||||||
#[inline]
|
|
||||||
pub fn data_arc(&self) -> SharedBox<T> {
|
|
||||||
let guard = self.inner.read();
|
|
||||||
Arc::clone(&guard.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
|
|
||||||
/// 这也是零拷贝:只 clone Arc,不 clone T
|
|
||||||
#[inline]
|
|
||||||
pub fn latest_arc(&self) -> SharedBox<T> {
|
|
||||||
let guard = self.inner.read();
|
|
||||||
guard.1.clone().unwrap_or_else(|| Arc::clone(&guard.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T)
|
|
||||||
/// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T;
|
|
||||||
/// - 若草稿被其他读者共享,Arc::make_mut 会做一次 T.clone(最小必要拷贝)。
|
|
||||||
#[inline]
|
|
||||||
pub fn edit_draft<F, R>(&self, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut T) -> R,
|
|
||||||
{
|
|
||||||
// 先获得写锁以创建或取出草稿 Arc 的可变引用位置
|
|
||||||
let mut guard = self.inner.write();
|
|
||||||
let mut draft_arc = if guard.1.is_none() {
|
|
||||||
Arc::clone(&guard.0)
|
|
||||||
} else {
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
guard.1.take().unwrap()
|
|
||||||
};
|
|
||||||
drop(guard);
|
|
||||||
// Arc::make_mut: 如果只有一个引用则返回可变引用;否则会克隆底层 Box<T>(要求 T: Clone)
|
|
||||||
let boxed = Arc::make_mut(&mut draft_arc); // &mut Box<T>
|
|
||||||
// 对 Box<T> 解引用得到 &mut T
|
|
||||||
let result = f(&mut **boxed);
|
|
||||||
// 恢复修改后的草稿 Arc
|
|
||||||
self.inner.write().1 = Some(draft_arc);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 将草稿提交到已提交位置(替换),并清除草稿
|
|
||||||
#[inline]
|
|
||||||
pub fn apply(&self) {
|
|
||||||
let mut guard = self.inner.write();
|
|
||||||
if let Some(d) = guard.1.take() {
|
|
||||||
guard.0 = d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 丢弃草稿(如果存在)
|
|
||||||
#[inline]
|
|
||||||
pub fn discard(&self) {
|
|
||||||
let mut guard = self.inner.write();
|
|
||||||
guard.1 = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
|
|
||||||
/// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
|
|
||||||
#[inline]
|
|
||||||
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
|
|
||||||
where
|
|
||||||
T: Send + Sync + 'static,
|
|
||||||
F: FnOnce(Box<T>) -> Fut + Send,
|
|
||||||
Fut: std::future::Future<Output = Result<(Box<T>, R), anyhow::Error>> + Send,
|
|
||||||
{
|
|
||||||
// 读取已提交快照(cheap Arc clone, 然后得到 Box<T> 所有权 via clone)
|
|
||||||
// 注意:为了让闭包接收 Box<T> 所有权,我们需要 clone 底层 T(不可避免)
|
|
||||||
let local: Box<T> = {
|
|
||||||
let guard = self.inner.read();
|
|
||||||
// 将 Arc<Box<T>> 的 Box<T> clone 出来(会调用 T: Clone)
|
|
||||||
(*guard.0).clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (new_local, res) = f(local).await?;
|
|
||||||
|
|
||||||
// 将新的 Box<T> 放到已提交位置(包进 Arc)
|
|
||||||
self.inner.write().0 = Arc::new(new_local);
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use draft::Draft;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||||
10
src-tauri/packages/macos/info_merge.plist
Normal file
10
src-tauri/packages/macos/info_merge.plist
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>AssociatedBundleIdentifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>io.github.clash-verge-rev.clash-verge-rev.service</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -84,8 +84,8 @@ pub async fn restart_app() -> CmdResult<()> {
|
|||||||
|
|
||||||
/// 获取便携版标识
|
/// 获取便携版标识
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_portable_flag() -> CmdResult<bool> {
|
pub fn get_portable_flag() -> bool {
|
||||||
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
|
*dirs::PORTABLE_FLAG.get().unwrap_or(&false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取应用目录
|
/// 获取应用目录
|
||||||
@ -241,16 +241,14 @@ pub async fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<Stri
|
|||||||
|
|
||||||
/// 通知UI已准备就绪
|
/// 通知UI已准备就绪
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn notify_ui_ready() -> CmdResult<()> {
|
pub fn notify_ui_ready() {
|
||||||
logging!(info, Type::Cmd, "前端UI已准备就绪");
|
logging!(info, Type::Cmd, "前端UI已准备就绪");
|
||||||
ui::mark_ui_ready();
|
ui::mark_ui_ready();
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UI加载阶段
|
/// UI加载阶段
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn update_ui_stage(stage: UiReadyStage) -> CmdResult<()> {
|
pub fn update_ui_stage(stage: UiReadyStage) {
|
||||||
logging!(info, Type::Cmd, "UI加载阶段更新: {:?}", &stage);
|
logging!(info, Type::Cmd, "UI加载阶段更新: {:?}", &stage);
|
||||||
ui::update_ui_ready_stage(stage);
|
ui::update_ui_ready_stage(stage);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::cmd::StringifyErr as _;
|
use crate::cmd::StringifyErr as _;
|
||||||
use crate::core::{EventDrivenProxyManager, async_proxy_query::AsyncProxyQuery};
|
|
||||||
use crate::process::AsyncHandler;
|
|
||||||
use crate::{logging, utils::logging::Type};
|
use crate::{logging, utils::logging::Type};
|
||||||
|
use gethostname::gethostname;
|
||||||
use network_interface::NetworkInterface;
|
use network_interface::NetworkInterface;
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
|
|
||||||
@ -11,23 +10,23 @@ use serde_yaml_ng::Mapping;
|
|||||||
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
||||||
logging!(debug, Type::Network, "异步获取系统代理配置");
|
logging!(debug, Type::Network, "异步获取系统代理配置");
|
||||||
|
|
||||||
let current = AsyncProxyQuery::get_system_proxy().await;
|
let sys_proxy = sysproxy::Sysproxy::get_system_proxy().stringify_err()?;
|
||||||
|
|
||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
map.insert("enable".into(), current.enable.into());
|
map.insert("enable".into(), sys_proxy.enable.into());
|
||||||
map.insert(
|
map.insert(
|
||||||
"server".into(),
|
"server".into(),
|
||||||
format!("{}:{}", current.host, current.port).into(),
|
format!("{}:{}", sys_proxy.host, sys_proxy.port).into(),
|
||||||
);
|
);
|
||||||
map.insert("bypass".into(), current.bypass.into());
|
map.insert("bypass".into(), sys_proxy.bypass.into());
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Network,
|
Type::Network,
|
||||||
"返回系统代理配置: enable={}, {}:{}",
|
"返回系统代理配置: enable={}, {}:{}",
|
||||||
current.enable,
|
sys_proxy.enable,
|
||||||
current.host,
|
sys_proxy.host,
|
||||||
current.port
|
sys_proxy.port
|
||||||
);
|
);
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
@ -35,37 +34,27 @@ pub async fn get_sys_proxy() -> CmdResult<Mapping> {
|
|||||||
/// 获取自动代理配置
|
/// 获取自动代理配置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_auto_proxy() -> CmdResult<Mapping> {
|
pub async fn get_auto_proxy() -> CmdResult<Mapping> {
|
||||||
logging!(debug, Type::Network, "开始获取自动代理配置(事件驱动)");
|
let auto_proxy = sysproxy::Autoproxy::get_auto_proxy().stringify_err()?;
|
||||||
|
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
|
||||||
|
|
||||||
let current = proxy_manager.get_auto_proxy_cached().await;
|
|
||||||
// 异步请求更新,立即返回缓存数据
|
|
||||||
AsyncHandler::spawn(move || async move {
|
|
||||||
let _ = proxy_manager.get_auto_proxy_async().await;
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
map.insert("enable".into(), current.enable.into());
|
map.insert("enable".into(), auto_proxy.enable.into());
|
||||||
map.insert("url".into(), current.url.clone().into());
|
map.insert("url".into(), auto_proxy.url.clone().into());
|
||||||
|
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Network,
|
Type::Network,
|
||||||
"返回自动代理配置(缓存): enable={}, url={}",
|
"返回自动代理配置(缓存): enable={}, url={}",
|
||||||
current.enable,
|
auto_proxy.enable,
|
||||||
current.url
|
auto_proxy.url
|
||||||
);
|
);
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取系统主机名
|
/// 获取系统主机名
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_system_hostname() -> CmdResult<String> {
|
pub fn get_system_hostname() -> String {
|
||||||
use gethostname::gethostname;
|
|
||||||
|
|
||||||
// 获取系统主机名,处理可能的非UTF-8字符
|
// 获取系统主机名,处理可能的非UTF-8字符
|
||||||
let hostname = match gethostname().into_string() {
|
match gethostname().into_string() {
|
||||||
Ok(name) => name,
|
Ok(name) => name,
|
||||||
Err(os_string) => {
|
Err(os_string) => {
|
||||||
// 对于包含非UTF-8的主机名,使用调试格式化
|
// 对于包含非UTF-8的主机名,使用调试格式化
|
||||||
@ -73,9 +62,7 @@ pub fn get_system_hostname() -> CmdResult<String> {
|
|||||||
// 去掉可能存在的引号
|
// 去掉可能存在的引号
|
||||||
fallback.trim_matches('"').to_string()
|
fallback.trim_matches('"').to_string()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(hostname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取网络接口列表
|
/// 获取网络接口列表
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
cmd::StringifyErr as _,
|
cmd::StringifyErr as _, config::Config, core::CoreManager, logging_error, utils::logging::Type,
|
||||||
config::{Config, ConfigType},
|
|
||||||
core::CoreManager,
|
|
||||||
log_err,
|
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, anyhow};
|
use anyhow::{Context as _, anyhow};
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
@ -104,14 +101,9 @@ pub async fn update_proxy_chain_config_in_runtime(
|
|||||||
{
|
{
|
||||||
let runtime = Config::runtime().await;
|
let runtime = Config::runtime().await;
|
||||||
runtime.edit_draft(|d| d.update_proxy_chain_config(proxy_chain_config));
|
runtime.edit_draft(|d| d.update_proxy_chain_config(proxy_chain_config));
|
||||||
runtime.apply();
|
// 我们需要在 CoreManager 中验证并应用配置,这里不应该直接调用 runtime.apply()
|
||||||
}
|
}
|
||||||
|
logging_error!(Type::Core, CoreManager::global().update_config().await);
|
||||||
// 生成新的运行配置文件并通知 Clash 核心重新加载
|
|
||||||
let run_path = Config::generate_file(ConfigType::Run)
|
|
||||||
.await
|
|
||||||
.stringify_err()?;
|
|
||||||
log_err!(CoreManager::global().put_configs_force(run_path).await);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,12 +53,12 @@ pub async fn get_running_mode() -> Result<Arc<RunningMode>, String> {
|
|||||||
|
|
||||||
/// 获取应用的运行时间(毫秒)
|
/// 获取应用的运行时间(毫秒)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_app_uptime() -> CmdResult<u128> {
|
pub fn get_app_uptime() -> u128 {
|
||||||
Ok(APP_START_TIME.elapsed().as_millis())
|
APP_START_TIME.elapsed().as_millis()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查应用是否以管理员身份运行
|
/// 检查应用是否以管理员身份运行
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn is_admin() -> CmdResult<bool> {
|
pub fn is_admin() -> bool {
|
||||||
Ok(*APPS_RUN_AS_ADMIN)
|
*APPS_RUN_AS_ADMIN
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ mod platform {
|
|||||||
mod platform {
|
mod platform {
|
||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
pub const fn invoke_uwp_tool() -> CmdResult {
|
pub const fn invoke_uwp_tool() -> CmdResult {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{cmd::StringifyErr as _, config::IVerge, feat, utils::draft::SharedBox};
|
use crate::{cmd::StringifyErr as _, config::IVerge, feat};
|
||||||
|
use draft::SharedBox;
|
||||||
|
|
||||||
/// 获取Verge配置
|
/// 获取Verge配置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@ -5,10 +5,11 @@ use crate::{
|
|||||||
constants::{files, timing},
|
constants::{files, timing},
|
||||||
core::{CoreManager, handle, service, tray, validate::CoreConfigValidator},
|
core::{CoreManager, handle, service, tray, validate::CoreConfigValidator},
|
||||||
enhance, logging, logging_error,
|
enhance, logging, logging_error,
|
||||||
utils::{Draft, dirs, help, logging::Type},
|
utils::{dirs, help, logging::Type},
|
||||||
};
|
};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use backoff::{Error as BackoffError, ExponentialBackoff};
|
use backoff::{Error as BackoffError, ExponentialBackoff};
|
||||||
|
use draft::Draft;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
@ -57,9 +58,7 @@ impl Config {
|
|||||||
Self::ensure_default_profile_items().await?;
|
Self::ensure_default_profile_items().await?;
|
||||||
|
|
||||||
// init Tun mode
|
// init Tun mode
|
||||||
if !cmd::system::is_admin().unwrap_or_default()
|
if !cmd::system::is_admin() && service::is_service_available().await.is_err() {
|
||||||
&& service::is_service_available().await.is_err()
|
|
||||||
{
|
|
||||||
let verge = Self::verge().await;
|
let verge = Self::verge().await;
|
||||||
verge.edit_draft(|d| {
|
verge.edit_draft(|d| {
|
||||||
d.enable_tun_mode = Some(false);
|
d.enable_tun_mode = Some(false);
|
||||||
|
|||||||
@ -581,6 +581,7 @@ impl PrfItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 向前兼容,默认为订阅启用自动更新
|
// 向前兼容,默认为订阅启用自动更新
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
const fn default_allow_auto_update() -> Option<bool> {
|
const fn default_allow_auto_update() -> Option<bool> {
|
||||||
Some(true)
|
Some(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub mod network {
|
pub mod network {
|
||||||
pub const DEFAULT_PROXY_HOST: &str = "127.0.0.1";
|
|
||||||
pub const DEFAULT_EXTERNAL_CONTROLLER: &str = "127.0.0.1:9097";
|
pub const DEFAULT_EXTERNAL_CONTROLLER: &str = "127.0.0.1:9097";
|
||||||
|
|
||||||
pub mod ports {
|
pub mod ports {
|
||||||
@ -20,23 +19,10 @@ pub mod network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod bypass {
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub const DEFAULT: &str = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;<local>";
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub const DEFAULT: &str =
|
|
||||||
"localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,::1";
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub const DEFAULT: &str = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,<local>";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod timing {
|
pub mod timing {
|
||||||
use super::Duration;
|
use super::Duration;
|
||||||
|
|
||||||
pub const CONFIG_UPDATE_DEBOUNCE: Duration = Duration::from_millis(500);
|
pub const CONFIG_UPDATE_DEBOUNCE: Duration = Duration::from_millis(300);
|
||||||
pub const CONFIG_RELOAD_DELAY: Duration = Duration::from_millis(300);
|
|
||||||
pub const EVENT_EMIT_DELAY: Duration = Duration::from_millis(20);
|
pub const EVENT_EMIT_DELAY: Duration = Duration::from_millis(20);
|
||||||
pub const STARTUP_ERROR_DELAY: Duration = Duration::from_secs(2);
|
pub const STARTUP_ERROR_DELAY: Duration = Duration::from_secs(2);
|
||||||
pub const ERROR_BATCH_DELAY: Duration = Duration::from_millis(300);
|
pub const ERROR_BATCH_DELAY: Duration = Duration::from_millis(300);
|
||||||
@ -58,15 +44,6 @@ pub mod files {
|
|||||||
pub const WINDOW_STATE: &str = "window_state.json";
|
pub const WINDOW_STATE: &str = "window_state.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod error_patterns {
|
|
||||||
pub const CONNECTION_ERRORS: &[&str] = &[
|
|
||||||
"Failed to create connection",
|
|
||||||
"The system cannot find the file specified",
|
|
||||||
"operation timed out",
|
|
||||||
"connection refused",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod tun {
|
pub mod tun {
|
||||||
pub const DEFAULT_STACK: &str = "gvisor";
|
pub const DEFAULT_STACK: &str = "gvisor";
|
||||||
|
|
||||||
|
|||||||
@ -1,565 +0,0 @@
|
|||||||
#[cfg(target_os = "windows")]
|
|
||||||
use crate::process::AsyncHandler;
|
|
||||||
use crate::{logging, utils::logging::Type};
|
|
||||||
use anyhow::Result;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tokio::time::{Duration, timeout};
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use anyhow::anyhow;
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
||||||
pub struct AsyncAutoproxy {
|
|
||||||
pub enable: bool,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct AsyncSysproxy {
|
|
||||||
pub enable: bool,
|
|
||||||
pub host: String,
|
|
||||||
pub port: u16,
|
|
||||||
pub bypass: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AsyncSysproxy {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enable: false,
|
|
||||||
host: "127.0.0.1".into(),
|
|
||||||
port: 7897,
|
|
||||||
bypass: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AsyncProxyQuery;
|
|
||||||
|
|
||||||
impl AsyncProxyQuery {
|
|
||||||
/// 异步获取自动代理配置(PAC)
|
|
||||||
pub async fn get_auto_proxy() -> AsyncAutoproxy {
|
|
||||||
match timeout(Duration::from_secs(3), Self::get_auto_proxy_impl()).await {
|
|
||||||
Ok(Ok(proxy)) => {
|
|
||||||
logging!(
|
|
||||||
debug,
|
|
||||||
Type::Network,
|
|
||||||
"异步获取自动代理成功: enable={}, url={}",
|
|
||||||
proxy.enable,
|
|
||||||
proxy.url
|
|
||||||
);
|
|
||||||
proxy
|
|
||||||
}
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
logging!(warn, Type::Network, "Warning: 异步获取自动代理失败: {e}");
|
|
||||||
AsyncAutoproxy::default()
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
logging!(warn, Type::Network, "Warning: 异步获取自动代理超时");
|
|
||||||
AsyncAutoproxy::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 异步获取系统代理配置
|
|
||||||
pub async fn get_system_proxy() -> AsyncSysproxy {
|
|
||||||
match timeout(Duration::from_secs(3), Self::get_system_proxy_impl()).await {
|
|
||||||
Ok(Ok(proxy)) => {
|
|
||||||
logging!(
|
|
||||||
debug,
|
|
||||||
Type::Network,
|
|
||||||
"异步获取系统代理成功: enable={}, {}:{}",
|
|
||||||
proxy.enable,
|
|
||||||
proxy.host,
|
|
||||||
proxy.port
|
|
||||||
);
|
|
||||||
proxy
|
|
||||||
}
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
logging!(warn, Type::Network, "Warning: 异步获取系统代理失败: {e}");
|
|
||||||
AsyncSysproxy::default()
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
logging!(warn, Type::Network, "Warning: 异步获取系统代理超时");
|
|
||||||
AsyncSysproxy::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
|
|
||||||
// Windows: 从注册表读取PAC配置
|
|
||||||
AsyncHandler::spawn_blocking(move || -> Result<AsyncAutoproxy> {
|
|
||||||
Self::get_pac_config_from_registry()
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn get_pac_config_from_registry() -> Result<AsyncAutoproxy> {
|
|
||||||
use std::ptr;
|
|
||||||
use winapi::shared::minwindef::{DWORD, HKEY};
|
|
||||||
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
|
|
||||||
use winapi::um::winreg::{HKEY_CURRENT_USER, RegCloseKey, RegOpenKeyExW, RegQueryValueExW};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
|
|
||||||
.encode_utf16()
|
|
||||||
.collect::<Vec<u16>>();
|
|
||||||
|
|
||||||
let mut hkey: HKEY = ptr::null_mut();
|
|
||||||
let result =
|
|
||||||
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
|
|
||||||
|
|
||||||
if result != 0 {
|
|
||||||
logging!(debug, Type::Network, "无法打开注册表项");
|
|
||||||
return Ok(AsyncAutoproxy::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 检查自动配置是否启用 (AutoConfigURL 存在且不为空即表示启用)
|
|
||||||
let auto_config_url_name = "AutoConfigURL\0".encode_utf16().collect::<Vec<u16>>();
|
|
||||||
let mut url_buffer = vec![0u16; 1024];
|
|
||||||
let mut url_buffer_size: DWORD = (url_buffer.len() * 2) as DWORD;
|
|
||||||
let mut url_value_type: DWORD = 0;
|
|
||||||
|
|
||||||
let url_query_result = RegQueryValueExW(
|
|
||||||
hkey,
|
|
||||||
auto_config_url_name.as_ptr(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
&mut url_value_type,
|
|
||||||
url_buffer.as_mut_ptr() as *mut u8,
|
|
||||||
&mut url_buffer_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut pac_url = String::new();
|
|
||||||
if url_query_result == 0 && url_value_type == REG_SZ && url_buffer_size > 0 {
|
|
||||||
let end_pos = url_buffer
|
|
||||||
.iter()
|
|
||||||
.position(|&x| x == 0)
|
|
||||||
.unwrap_or(url_buffer.len());
|
|
||||||
pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]);
|
|
||||||
logging!(debug, Type::Network, "从注册表读取到PAC URL: {pac_url}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 检查自动检测设置是否启用
|
|
||||||
let auto_detect_name = "AutoDetect\0".encode_utf16().collect::<Vec<u16>>();
|
|
||||||
let mut auto_detect: DWORD = 0;
|
|
||||||
let mut detect_buffer_size: DWORD = 4;
|
|
||||||
let mut detect_value_type: DWORD = 0;
|
|
||||||
|
|
||||||
let detect_query_result = RegQueryValueExW(
|
|
||||||
hkey,
|
|
||||||
auto_detect_name.as_ptr(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
&mut detect_value_type,
|
|
||||||
&mut auto_detect as *mut DWORD as *mut u8,
|
|
||||||
&mut detect_buffer_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
RegCloseKey(hkey);
|
|
||||||
|
|
||||||
// PAC 启用的条件:AutoConfigURL 不为空,或 AutoDetect 被启用
|
|
||||||
let pac_enabled = !pac_url.is_empty()
|
|
||||||
|| (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0);
|
|
||||||
|
|
||||||
if pac_enabled {
|
|
||||||
logging!(
|
|
||||||
debug,
|
|
||||||
Type::Network,
|
|
||||||
"PAC配置启用: URL={pac_url}, AutoDetect={auto_detect}"
|
|
||||||
);
|
|
||||||
|
|
||||||
if pac_url.is_empty() && auto_detect != 0 {
|
|
||||||
pac_url = "auto-detect".into();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AsyncAutoproxy {
|
|
||||||
enable: true,
|
|
||||||
url: pac_url,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
logging!(debug, Type::Network, "PAC配置未启用");
|
|
||||||
Ok(AsyncAutoproxy::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
|
|
||||||
// macOS: 使用 scutil --proxy 命令
|
|
||||||
let output = Command::new("scutil").args(["--proxy"]).output().await?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Ok(AsyncAutoproxy::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
crate::logging!(
|
|
||||||
debug,
|
|
||||||
crate::utils::logging::Type::Network,
|
|
||||||
"scutil output: {stdout}"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut pac_enabled = false;
|
|
||||||
let mut pac_url = String::new();
|
|
||||||
|
|
||||||
// 解析 scutil 输出
|
|
||||||
for line in stdout.lines() {
|
|
||||||
let line = line.trim();
|
|
||||||
if line.contains("ProxyAutoConfigEnable") && line.contains("1") {
|
|
||||||
pac_enabled = true;
|
|
||||||
} else if line.contains("ProxyAutoConfigURLString") {
|
|
||||||
// 正确解析包含冒号的URL
|
|
||||||
// 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac"
|
|
||||||
if let Some(colon_pos) = line.find(" : ") {
|
|
||||||
pac_url = line[colon_pos + 3..].trim().into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::logging!(
|
|
||||||
debug,
|
|
||||||
crate::utils::logging::Type::Network,
|
|
||||||
"解析结果: pac_enabled={pac_enabled}, pac_url={pac_url}"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(AsyncAutoproxy {
|
|
||||||
enable: pac_enabled && !pac_url.is_empty(),
|
|
||||||
url: pac_url,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
|
|
||||||
// Linux: 检查环境变量和GNOME设置
|
|
||||||
|
|
||||||
// 首先检查环境变量
|
|
||||||
if let Ok(auto_proxy) = std::env::var("auto_proxy")
|
|
||||||
&& !auto_proxy.is_empty()
|
|
||||||
{
|
|
||||||
return Ok(AsyncAutoproxy {
|
|
||||||
enable: true,
|
|
||||||
url: auto_proxy,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试使用 gsettings 获取 GNOME 代理设置
|
|
||||||
let output = Command::new("gsettings")
|
|
||||||
.args(["get", "org.gnome.system.proxy", "mode"])
|
|
||||||
.output()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(output) = output
|
|
||||||
&& output.status.success()
|
|
||||||
{
|
|
||||||
let mode: String = String::from_utf8_lossy(&output.stdout).trim().into();
|
|
||||||
if mode.contains("auto") {
|
|
||||||
// 获取 PAC URL
|
|
||||||
let pac_output = Command::new("gsettings")
|
|
||||||
.args(["get", "org.gnome.system.proxy", "autoconfig-url"])
|
|
||||||
.output()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(pac_output) = pac_output
|
|
||||||
&& pac_output.status.success()
|
|
||||||
{
|
|
||||||
let pac_url: String = String::from_utf8_lossy(&pac_output.stdout)
|
|
||||||
.trim()
|
|
||||||
.trim_matches('\'')
|
|
||||||
.trim_matches('"')
|
|
||||||
.into();
|
|
||||||
|
|
||||||
if !pac_url.is_empty() {
|
|
||||||
return Ok(AsyncAutoproxy {
|
|
||||||
enable: true,
|
|
||||||
url: pac_url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AsyncAutoproxy::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
|
|
||||||
// Windows: 使用注册表直接读取代理设置
|
|
||||||
AsyncHandler::spawn_blocking(move || -> Result<AsyncSysproxy> {
|
|
||||||
Self::get_system_proxy_from_registry()
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn get_system_proxy_from_registry() -> Result<AsyncSysproxy> {
|
|
||||||
use std::ptr;
|
|
||||||
use winapi::shared::minwindef::{DWORD, HKEY};
|
|
||||||
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
|
|
||||||
use winapi::um::winreg::{HKEY_CURRENT_USER, RegCloseKey, RegOpenKeyExW, RegQueryValueExW};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
|
|
||||||
.encode_utf16()
|
|
||||||
.collect::<Vec<u16>>();
|
|
||||||
|
|
||||||
let mut hkey: HKEY = ptr::null_mut();
|
|
||||||
let result =
|
|
||||||
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
|
|
||||||
|
|
||||||
if result != 0 {
|
|
||||||
return Ok(AsyncSysproxy::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查代理是否启用
|
|
||||||
let proxy_enable_name = "ProxyEnable\0".encode_utf16().collect::<Vec<u16>>();
|
|
||||||
let mut proxy_enable: DWORD = 0;
|
|
||||||
let mut buffer_size: DWORD = 4;
|
|
||||||
let mut value_type: DWORD = 0;
|
|
||||||
|
|
||||||
let enable_result = RegQueryValueExW(
|
|
||||||
hkey,
|
|
||||||
proxy_enable_name.as_ptr(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
&mut value_type,
|
|
||||||
&mut proxy_enable as *mut DWORD as *mut u8,
|
|
||||||
&mut buffer_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
if enable_result != 0 || value_type != REG_DWORD || proxy_enable == 0 {
|
|
||||||
RegCloseKey(hkey);
|
|
||||||
return Ok(AsyncSysproxy::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取代理服务器设置
|
|
||||||
let proxy_server_name = "ProxyServer\0".encode_utf16().collect::<Vec<u16>>();
|
|
||||||
let mut buffer = vec![0u16; 1024];
|
|
||||||
let mut buffer_size: DWORD = (buffer.len() * 2) as DWORD;
|
|
||||||
let mut value_type: DWORD = 0;
|
|
||||||
|
|
||||||
let server_result = RegQueryValueExW(
|
|
||||||
hkey,
|
|
||||||
proxy_server_name.as_ptr(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
&mut value_type,
|
|
||||||
buffer.as_mut_ptr() as *mut u8,
|
|
||||||
&mut buffer_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
let proxy_server = if server_result == 0 && value_type == REG_SZ && buffer_size > 0 {
|
|
||||||
let end_pos = buffer.iter().position(|&x| x == 0).unwrap_or(buffer.len());
|
|
||||||
String::from_utf16_lossy(&buffer[..end_pos])
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 读取代理绕过列表
|
|
||||||
let proxy_override_name = "ProxyOverride\0".encode_utf16().collect::<Vec<u16>>();
|
|
||||||
let mut bypass_buffer = vec![0u16; 1024];
|
|
||||||
let mut bypass_buffer_size: DWORD = (bypass_buffer.len() * 2) as DWORD;
|
|
||||||
let mut bypass_value_type: DWORD = 0;
|
|
||||||
|
|
||||||
let override_result = RegQueryValueExW(
|
|
||||||
hkey,
|
|
||||||
proxy_override_name.as_ptr(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
&mut bypass_value_type,
|
|
||||||
bypass_buffer.as_mut_ptr() as *mut u8,
|
|
||||||
&mut bypass_buffer_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
let bypass_list =
|
|
||||||
if override_result == 0 && bypass_value_type == REG_SZ && bypass_buffer_size > 0 {
|
|
||||||
let end_pos = bypass_buffer
|
|
||||||
.iter()
|
|
||||||
.position(|&x| x == 0)
|
|
||||||
.unwrap_or(bypass_buffer.len());
|
|
||||||
String::from_utf16_lossy(&bypass_buffer[..end_pos])
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
RegCloseKey(hkey);
|
|
||||||
|
|
||||||
if !proxy_server.is_empty() {
|
|
||||||
// 解析服务器地址和端口
|
|
||||||
let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') {
|
|
||||||
let host = proxy_server[..colon_pos].into();
|
|
||||||
let port = proxy_server[colon_pos + 1..].parse::<u16>().unwrap_or(8080);
|
|
||||||
(host, port)
|
|
||||||
} else {
|
|
||||||
(proxy_server, 8080)
|
|
||||||
};
|
|
||||||
|
|
||||||
logging!(
|
|
||||||
debug,
|
|
||||||
Type::Network,
|
|
||||||
"从注册表读取到代理设置: {host}:{port}, bypass: {bypass_list}"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(AsyncSysproxy {
|
|
||||||
enable: true,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
bypass: bypass_list,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(AsyncSysproxy::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
|
|
||||||
let output = Command::new("scutil").args(["--proxy"]).output().await?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Ok(AsyncSysproxy::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
logging!(debug, Type::Network, "scutil proxy output: {stdout}");
|
|
||||||
|
|
||||||
let mut http_enabled = false;
|
|
||||||
let mut http_host = String::new();
|
|
||||||
let mut http_port = 8080u16;
|
|
||||||
let mut exceptions: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
for line in stdout.lines() {
|
|
||||||
let line = line.trim();
|
|
||||||
if line.contains("HTTPEnable") && line.contains("1") {
|
|
||||||
http_enabled = true;
|
|
||||||
} else if line.contains("HTTPProxy") && !line.contains("Port") {
|
|
||||||
if let Some(host_part) = line.split(':').nth(1) {
|
|
||||||
http_host = host_part.trim().into();
|
|
||||||
}
|
|
||||||
} else if line.contains("HTTPPort") {
|
|
||||||
if let Some(port_part) = line.split(':').nth(1)
|
|
||||||
&& let Ok(port) = port_part.trim().parse::<u16>()
|
|
||||||
{
|
|
||||||
http_port = port;
|
|
||||||
}
|
|
||||||
} else if line.contains("ExceptionsList") {
|
|
||||||
// 解析异常列表
|
|
||||||
if let Some(list_part) = line.split(':').nth(1) {
|
|
||||||
let list = list_part.trim();
|
|
||||||
if !list.is_empty() {
|
|
||||||
exceptions.push(list.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AsyncSysproxy {
|
|
||||||
enable: http_enabled && !http_host.is_empty(),
|
|
||||||
host: http_host,
|
|
||||||
port: http_port,
|
|
||||||
bypass: exceptions.join(","),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
|
|
||||||
// Linux: 检查环境变量和桌面环境设置
|
|
||||||
|
|
||||||
// 首先检查环境变量
|
|
||||||
if let Ok(http_proxy) = std::env::var("http_proxy")
|
|
||||||
&& let Ok(proxy_info) = Self::parse_proxy_url(&http_proxy)
|
|
||||||
{
|
|
||||||
return Ok(proxy_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(https_proxy) = std::env::var("https_proxy")
|
|
||||||
&& let Ok(proxy_info) = Self::parse_proxy_url(&https_proxy)
|
|
||||||
{
|
|
||||||
return Ok(proxy_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试使用 gsettings 获取 GNOME 代理设置
|
|
||||||
let mode_output = Command::new("gsettings")
|
|
||||||
.args(["get", "org.gnome.system.proxy", "mode"])
|
|
||||||
.output()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(mode_output) = mode_output
|
|
||||||
&& mode_output.status.success()
|
|
||||||
{
|
|
||||||
let mode: String = String::from_utf8_lossy(&mode_output.stdout).trim().into();
|
|
||||||
if mode.contains("manual") {
|
|
||||||
// 获取HTTP代理设置
|
|
||||||
let host_result = Command::new("gsettings")
|
|
||||||
.args(["get", "org.gnome.system.proxy.http", "host"])
|
|
||||||
.output()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let port_result = Command::new("gsettings")
|
|
||||||
.args(["get", "org.gnome.system.proxy.http", "port"])
|
|
||||||
.output()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let (Ok(host_output), Ok(port_output)) = (host_result, port_result)
|
|
||||||
&& host_output.status.success()
|
|
||||||
&& port_output.status.success()
|
|
||||||
{
|
|
||||||
let host: String = String::from_utf8_lossy(&host_output.stdout)
|
|
||||||
.trim()
|
|
||||||
.trim_matches('\'')
|
|
||||||
.trim_matches('"')
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let port = String::from_utf8_lossy(&port_output.stdout)
|
|
||||||
.trim()
|
|
||||||
.parse::<u16>()
|
|
||||||
.unwrap_or(8080);
|
|
||||||
|
|
||||||
if !host.is_empty() {
|
|
||||||
return Ok(AsyncSysproxy {
|
|
||||||
enable: true,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
bypass: String::new(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AsyncSysproxy::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn parse_proxy_url(proxy_url: &str) -> Result<AsyncSysproxy> {
|
|
||||||
// 解析形如 "http://proxy.example.com:8080" 的URL
|
|
||||||
let url = proxy_url.trim();
|
|
||||||
|
|
||||||
// 移除协议前缀
|
|
||||||
let url = if let Some(stripped) = url.strip_prefix("http://") {
|
|
||||||
stripped
|
|
||||||
} else if let Some(stripped) = url.strip_prefix("https://") {
|
|
||||||
stripped
|
|
||||||
} else {
|
|
||||||
url
|
|
||||||
};
|
|
||||||
|
|
||||||
// 解析主机和端口
|
|
||||||
let (host, port) = if let Some(colon_pos) = url.rfind(':') {
|
|
||||||
let host: String = url[..colon_pos].into();
|
|
||||||
let port = url[colon_pos + 1..].parse::<u16>().unwrap_or(8080);
|
|
||||||
(host, port)
|
|
||||||
} else {
|
|
||||||
(url.into(), 8080)
|
|
||||||
};
|
|
||||||
|
|
||||||
if host.is_empty() {
|
|
||||||
return Err(anyhow!("无效的代理URL"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AsyncSysproxy {
|
|
||||||
enable: true,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
bypass: std::env::var("no_proxy").unwrap_or_default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,548 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tokio::sync::{mpsc, oneshot};
|
|
||||||
use tokio::time::{Duration, sleep, timeout};
|
|
||||||
use tokio_stream::{StreamExt as _, wrappers::UnboundedReceiverStream};
|
|
||||||
|
|
||||||
use crate::config::{Config, IVerge};
|
|
||||||
use crate::core::{async_proxy_query::AsyncProxyQuery, handle};
|
|
||||||
use crate::process::AsyncHandler;
|
|
||||||
use crate::{logging, utils::logging::Type};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use smartstring::alias::String;
|
|
||||||
use sysproxy::{Autoproxy, Sysproxy};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ProxyEvent {
|
|
||||||
/// 配置变更事件
|
|
||||||
ConfigChanged,
|
|
||||||
/// 应用启动事件
|
|
||||||
AppStarted,
|
|
||||||
/// 应用关闭事件
|
|
||||||
AppStopping,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ProxyState {
|
|
||||||
pub sys_enabled: bool,
|
|
||||||
pub pac_enabled: bool,
|
|
||||||
pub auto_proxy: Autoproxy,
|
|
||||||
pub sys_proxy: Sysproxy,
|
|
||||||
pub last_updated: std::time::Instant,
|
|
||||||
pub is_healthy: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ProxyState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
sys_enabled: false,
|
|
||||||
pac_enabled: false,
|
|
||||||
auto_proxy: Autoproxy {
|
|
||||||
enable: false,
|
|
||||||
url: "".into(),
|
|
||||||
},
|
|
||||||
sys_proxy: Sysproxy {
|
|
||||||
enable: false,
|
|
||||||
host: "127.0.0.1".into(),
|
|
||||||
port: 7897,
|
|
||||||
bypass: "".into(),
|
|
||||||
},
|
|
||||||
last_updated: std::time::Instant::now(),
|
|
||||||
is_healthy: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventDrivenProxyManager {
|
|
||||||
state: Arc<RwLock<ProxyState>>,
|
|
||||||
event_sender: mpsc::UnboundedSender<ProxyEvent>,
|
|
||||||
query_sender: mpsc::UnboundedSender<QueryRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct QueryRequest {
|
|
||||||
response_tx: oneshot::Sender<Autoproxy>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配置结构体移到外部
|
|
||||||
struct ProxyConfig {
|
|
||||||
sys_enabled: bool,
|
|
||||||
pac_enabled: bool,
|
|
||||||
guard_enabled: bool,
|
|
||||||
guard_duration: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
static PROXY_MANAGER: Lazy<EventDrivenProxyManager> = Lazy::new(EventDrivenProxyManager::new);
|
|
||||||
|
|
||||||
impl EventDrivenProxyManager {
|
|
||||||
pub fn global() -> &'static Self {
|
|
||||||
&PROXY_MANAGER
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
let state = Arc::new(RwLock::new(ProxyState::default()));
|
|
||||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
|
||||||
let (query_tx, query_rx) = mpsc::unbounded_channel();
|
|
||||||
|
|
||||||
let state_clone = Arc::clone(&state);
|
|
||||||
AsyncHandler::spawn(move || Self::start_event_loop(state_clone, event_rx, query_rx));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
state,
|
|
||||||
event_sender: event_tx,
|
|
||||||
query_sender: query_tx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取自动代理配置(缓存)
|
|
||||||
pub async fn get_auto_proxy_cached(&self) -> Autoproxy {
|
|
||||||
self.state.read().await.auto_proxy.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 异步获取最新的自动代理配置
|
|
||||||
pub async fn get_auto_proxy_async(&self) -> Autoproxy {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
let query = QueryRequest { response_tx: tx };
|
|
||||||
|
|
||||||
if self.query_sender.send(query).is_err() {
|
|
||||||
logging!(error, Type::Network, "发送查询请求失败,返回缓存数据");
|
|
||||||
return self.get_auto_proxy_cached().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
match timeout(Duration::from_secs(5), rx).await {
|
|
||||||
Ok(Ok(result)) => result,
|
|
||||||
_ => {
|
|
||||||
logging!(warn, Type::Network, "Warning: 查询超时,返回缓存数据");
|
|
||||||
self.get_auto_proxy_cached().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 通知配置变更
|
|
||||||
pub fn notify_config_changed(&self) {
|
|
||||||
self.send_event(ProxyEvent::ConfigChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 通知应用启动
|
|
||||||
pub fn notify_app_started(&self) {
|
|
||||||
self.send_event(ProxyEvent::AppStarted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 通知应用即将关闭
|
|
||||||
pub fn notify_app_stopping(&self) {
|
|
||||||
self.send_event(ProxyEvent::AppStopping);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_event(&self, event: ProxyEvent) {
|
|
||||||
if let Err(e) = self.event_sender.send(event) {
|
|
||||||
logging!(error, Type::Network, "发送代理事件失败: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start_event_loop(
|
|
||||||
state: Arc<RwLock<ProxyState>>,
|
|
||||||
event_rx: mpsc::UnboundedReceiver<ProxyEvent>,
|
|
||||||
query_rx: mpsc::UnboundedReceiver<QueryRequest>,
|
|
||||||
) {
|
|
||||||
logging!(info, Type::Network, "事件驱动代理管理器启动");
|
|
||||||
|
|
||||||
// 将 mpsc 接收器包装成 Stream,避免每次循环创建 future
|
|
||||||
let mut event_stream = UnboundedReceiverStream::new(event_rx);
|
|
||||||
let mut query_stream = UnboundedReceiverStream::new(query_rx);
|
|
||||||
|
|
||||||
// 初始化定时器,用于周期性检查代理设置
|
|
||||||
let config = Self::get_proxy_config().await;
|
|
||||||
let mut guard_interval = tokio::time::interval(Duration::from_secs(config.guard_duration));
|
|
||||||
// 防止首次立即触发
|
|
||||||
guard_interval.tick().await;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
Some(event) = event_stream.next() => {
|
|
||||||
logging!(debug, Type::Network, "处理代理事件: {event:?}");
|
|
||||||
let event_clone = event.clone(); // 保存一份副本用于后续检查
|
|
||||||
Self::handle_event(&state, event).await;
|
|
||||||
|
|
||||||
// 检查是否是配置变更事件,如果是,则可能需要更新定时器
|
|
||||||
if matches!(event_clone, ProxyEvent::ConfigChanged | ProxyEvent::AppStarted) {
|
|
||||||
let new_config = Self::get_proxy_config().await;
|
|
||||||
// 重新设置定时器间隔
|
|
||||||
guard_interval = tokio::time::interval(Duration::from_secs(new_config.guard_duration));
|
|
||||||
// 防止首次立即触发
|
|
||||||
guard_interval.tick().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(query) = query_stream.next() => {
|
|
||||||
let result = Self::handle_query(&state).await;
|
|
||||||
let _ = query.response_tx.send(result);
|
|
||||||
}
|
|
||||||
_ = guard_interval.tick() => {
|
|
||||||
// 定时检查代理设置
|
|
||||||
let config = Self::get_proxy_config().await;
|
|
||||||
if config.guard_enabled && config.sys_enabled {
|
|
||||||
logging!(debug, Type::Network, "定时检查代理设置");
|
|
||||||
Self::check_and_restore_proxy(&state).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else => {
|
|
||||||
// 两个通道都关闭时退出
|
|
||||||
logging!(info, Type::Network, "事件或查询通道关闭,代理管理器停止");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_event(state: &Arc<RwLock<ProxyState>>, event: ProxyEvent) {
|
|
||||||
match event {
|
|
||||||
ProxyEvent::ConfigChanged => {
|
|
||||||
Self::update_proxy_config(state).await;
|
|
||||||
}
|
|
||||||
ProxyEvent::AppStarted => {
|
|
||||||
Self::initialize_proxy_state(state).await;
|
|
||||||
}
|
|
||||||
ProxyEvent::AppStopping => {
|
|
||||||
logging!(info, Type::Network, "清理代理状态");
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.sys_enabled = false;
|
|
||||||
s.pac_enabled = false;
|
|
||||||
s.is_healthy = false;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_query(state: &Arc<RwLock<ProxyState>>) -> Autoproxy {
|
|
||||||
let auto_proxy = Self::get_auto_proxy_with_timeout().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.auto_proxy = auto_proxy.clone();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
auto_proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn initialize_proxy_state(state: &Arc<RwLock<ProxyState>>) {
|
|
||||||
logging!(info, Type::Network, "初始化代理状态");
|
|
||||||
|
|
||||||
let config = Self::get_proxy_config().await;
|
|
||||||
let auto_proxy = Self::get_auto_proxy_with_timeout().await;
|
|
||||||
let sys_proxy = Self::get_sys_proxy_with_timeout().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.sys_enabled = config.sys_enabled;
|
|
||||||
s.pac_enabled = config.pac_enabled;
|
|
||||||
s.auto_proxy = auto_proxy;
|
|
||||||
s.sys_proxy = sys_proxy;
|
|
||||||
s.is_healthy = true;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Network,
|
|
||||||
"代理状态初始化完成: sys={}, pac={}",
|
|
||||||
config.sys_enabled,
|
|
||||||
config.pac_enabled
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update_proxy_config(state: &Arc<RwLock<ProxyState>>) {
|
|
||||||
logging!(debug, Type::Network, "更新代理配置");
|
|
||||||
|
|
||||||
let config = Self::get_proxy_config().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.sys_enabled = config.sys_enabled;
|
|
||||||
s.pac_enabled = config.pac_enabled;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if config.guard_enabled && config.sys_enabled {
|
|
||||||
Self::check_and_restore_proxy(state).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_and_restore_proxy(state: &Arc<RwLock<ProxyState>>) {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
logging!(debug, Type::Network, "应用正在退出,跳过系统代理守卫检查");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let (sys_enabled, pac_enabled) = {
|
|
||||||
let s = state.read().await;
|
|
||||||
(s.sys_enabled, s.pac_enabled)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !sys_enabled {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logging!(debug, Type::Network, "检查代理状态");
|
|
||||||
|
|
||||||
if pac_enabled {
|
|
||||||
Self::check_and_restore_pac_proxy(state).await;
|
|
||||||
} else {
|
|
||||||
Self::check_and_restore_sys_proxy(state).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_and_restore_pac_proxy(state: &Arc<RwLock<ProxyState>>) {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
logging!(debug, Type::Network, "应用正在退出,跳过PAC代理恢复检查");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current = Self::get_auto_proxy_with_timeout().await;
|
|
||||||
let expected = Self::get_expected_pac_config().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.auto_proxy = current.clone();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if !current.enable || current.url != expected.url {
|
|
||||||
logging!(info, Type::Network, "PAC代理设置异常,正在恢复...");
|
|
||||||
if let Err(e) = Self::restore_pac_proxy(&expected.url).await {
|
|
||||||
logging!(error, Type::Network, "恢复PAC代理失败: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(500)).await;
|
|
||||||
let restored = Self::get_auto_proxy_with_timeout().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.is_healthy = restored.enable && restored.url == expected.url;
|
|
||||||
s.auto_proxy = restored;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn check_and_restore_sys_proxy(state: &Arc<RwLock<ProxyState>>) {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
logging!(debug, Type::Network, "应用正在退出,跳过系统代理恢复检查");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current = Self::get_sys_proxy_with_timeout().await;
|
|
||||||
let expected = Self::get_expected_sys_proxy().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.sys_proxy = current.clone();
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if !current.enable || current.host != expected.host || current.port != expected.port {
|
|
||||||
logging!(info, Type::Network, "系统代理设置异常,正在恢复...");
|
|
||||||
if let Err(e) = Self::restore_sys_proxy(&expected).await {
|
|
||||||
logging!(error, Type::Network, "恢复系统代理失败: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(500)).await;
|
|
||||||
let restored = Self::get_sys_proxy_with_timeout().await;
|
|
||||||
|
|
||||||
Self::update_state_timestamp(state, |s| {
|
|
||||||
s.is_healthy = restored.enable
|
|
||||||
&& restored.host == expected.host
|
|
||||||
&& restored.port == expected.port;
|
|
||||||
s.sys_proxy = restored;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_auto_proxy_with_timeout() -> Autoproxy {
|
|
||||||
let async_proxy = AsyncProxyQuery::get_auto_proxy().await;
|
|
||||||
|
|
||||||
// 转换为兼容的结构
|
|
||||||
Autoproxy {
|
|
||||||
enable: async_proxy.enable,
|
|
||||||
url: async_proxy.url,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_sys_proxy_with_timeout() -> Sysproxy {
|
|
||||||
let async_proxy = AsyncProxyQuery::get_system_proxy().await;
|
|
||||||
|
|
||||||
// 转换为兼容的结构
|
|
||||||
Sysproxy {
|
|
||||||
enable: async_proxy.enable,
|
|
||||||
host: async_proxy.host,
|
|
||||||
port: async_proxy.port,
|
|
||||||
bypass: async_proxy.bypass,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统一的状态更新方法
|
|
||||||
async fn update_state_timestamp<F>(state: &Arc<RwLock<ProxyState>>, update_fn: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut ProxyState),
|
|
||||||
{
|
|
||||||
let mut state_guard = state.write().await;
|
|
||||||
update_fn(&mut state_guard);
|
|
||||||
state_guard.last_updated = std::time::Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_proxy_config() -> ProxyConfig {
|
|
||||||
let (sys_enabled, pac_enabled, guard_enabled, guard_duration) = {
|
|
||||||
let verge_config = Config::verge().await;
|
|
||||||
let verge = verge_config.latest_arc();
|
|
||||||
(
|
|
||||||
verge.enable_system_proxy.unwrap_or(false),
|
|
||||||
verge.proxy_auto_config.unwrap_or(false),
|
|
||||||
verge.enable_proxy_guard.unwrap_or(false),
|
|
||||||
verge.proxy_guard_duration.unwrap_or(30), // 默认30秒
|
|
||||||
)
|
|
||||||
};
|
|
||||||
ProxyConfig {
|
|
||||||
sys_enabled,
|
|
||||||
pac_enabled,
|
|
||||||
guard_enabled,
|
|
||||||
guard_duration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_expected_pac_config() -> Autoproxy {
|
|
||||||
let proxy_host = {
|
|
||||||
let verge_config = Config::verge().await;
|
|
||||||
let verge = verge_config.latest_arc();
|
|
||||||
verge
|
|
||||||
.proxy_host
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| "127.0.0.1".into())
|
|
||||||
};
|
|
||||||
let pac_port = IVerge::get_singleton_port();
|
|
||||||
Autoproxy {
|
|
||||||
enable: true,
|
|
||||||
url: format!("http://{proxy_host}:{pac_port}/commands/pac"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_expected_sys_proxy() -> Sysproxy {
|
|
||||||
use crate::constants::network;
|
|
||||||
|
|
||||||
let (verge_mixed_port, proxy_host) = {
|
|
||||||
let verge_config = Config::verge().await;
|
|
||||||
let verge_ref = verge_config.latest_arc();
|
|
||||||
(verge_ref.verge_mixed_port, verge_ref.proxy_host.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let default_port = {
|
|
||||||
let clash_config = Config::clash().await;
|
|
||||||
clash_config.latest_arc().get_mixed_port()
|
|
||||||
};
|
|
||||||
|
|
||||||
let port = verge_mixed_port.unwrap_or(default_port);
|
|
||||||
let host = proxy_host
|
|
||||||
.unwrap_or_else(|| network::DEFAULT_PROXY_HOST.into())
|
|
||||||
.into();
|
|
||||||
|
|
||||||
Sysproxy {
|
|
||||||
enable: true,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
bypass: Self::get_bypass_config().await.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_bypass_config() -> String {
|
|
||||||
use crate::constants::bypass;
|
|
||||||
|
|
||||||
let verge_config = Config::verge().await;
|
|
||||||
let verge = verge_config.latest_arc();
|
|
||||||
let use_default = verge.use_default_bypass.unwrap_or(true);
|
|
||||||
let custom = verge.system_proxy_bypass.as_deref().unwrap_or("");
|
|
||||||
|
|
||||||
match (use_default, custom.is_empty()) {
|
|
||||||
(_, true) => bypass::DEFAULT.into(),
|
|
||||||
(true, false) => format!("{},{}", bypass::DEFAULT, custom).into(),
|
|
||||||
(false, false) => custom.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
async fn restore_pac_proxy(expected_url: &str) -> Result<(), anyhow::Error> {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
logging!(debug, Type::Network, "应用正在退出,跳过PAC代理恢复");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Self::execute_sysproxy_command(&["pac", expected_url]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unused_async)]
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
async fn restore_pac_proxy(expected_url: &str) -> Result<(), anyhow::Error> {
|
|
||||||
{
|
|
||||||
let new_autoproxy = Autoproxy {
|
|
||||||
enable: true,
|
|
||||||
url: expected_url.to_string(),
|
|
||||||
};
|
|
||||||
// logging_error!(Type::System, true, new_autoproxy.set_auto_proxy());
|
|
||||||
new_autoproxy
|
|
||||||
.set_auto_proxy()
|
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to set auto proxy: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
async fn restore_sys_proxy(expected: &Sysproxy) -> Result<(), anyhow::Error> {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
logging!(debug, Type::Network, "应用正在退出,跳过系统代理恢复");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let address = format!("{}:{}", expected.host, expected.port);
|
|
||||||
Self::execute_sysproxy_command(&["global", &address, &expected.bypass]).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unused_async)]
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
async fn restore_sys_proxy(expected: &Sysproxy) -> Result<(), anyhow::Error> {
|
|
||||||
{
|
|
||||||
// logging_error!(Type::System, true, expected.set_system_proxy());
|
|
||||||
expected
|
|
||||||
.set_system_proxy()
|
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to set system proxy: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
async fn execute_sysproxy_command(args: &[&str]) -> Result<(), anyhow::Error> {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
logging!(
|
|
||||||
debug,
|
|
||||||
Type::Network,
|
|
||||||
"应用正在退出,取消调用 sysproxy.exe,参数: {:?}",
|
|
||||||
args
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::utils::dirs;
|
|
||||||
#[allow(unused_imports)] // creation_flags必须
|
|
||||||
use std::os::windows::process::CommandExt as _;
|
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
let binary_path = match dirs::service_path() {
|
|
||||||
Ok(path) => path,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Network, "获取服务路径失败: {e}");
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let sysproxy_exe = binary_path.with_file_name("sysproxy.exe");
|
|
||||||
if !sysproxy_exe.exists() {
|
|
||||||
logging!(error, Type::Network, "sysproxy.exe 不存在");
|
|
||||||
}
|
|
||||||
anyhow::ensure!(sysproxy_exe.exists(), "sysproxy.exe does not exist");
|
|
||||||
|
|
||||||
let _output = Command::new(sysproxy_exe)
|
|
||||||
.args(args)
|
|
||||||
.creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口
|
|
||||||
.output()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -10,7 +10,6 @@ use anyhow::{Result, anyhow};
|
|||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::{path::PathBuf, time::Instant};
|
use std::{path::PathBuf, time::Instant};
|
||||||
use tauri_plugin_mihomo::Error as MihomoError;
|
use tauri_plugin_mihomo::Error as MihomoError;
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
impl CoreManager {
|
impl CoreManager {
|
||||||
pub async fn use_default_config(&self, error_key: &str, error_msg: &str) -> Result<()> {
|
pub async fn use_default_config(&self, error_key: &str, error_msg: &str) -> Result<()> {
|
||||||
@ -37,25 +36,25 @@ impl CoreManager {
|
|||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.should_update_config()? {
|
if !self.should_update_config() {
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.perform_config_update().await
|
self.perform_config_update().await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_update_config(&self) -> Result<bool> {
|
fn should_update_config(&self) -> bool {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let last = self.get_last_update();
|
let last = self.get_last_update();
|
||||||
|
|
||||||
if let Some(last_time) = last
|
if let Some(last_time) = last
|
||||||
&& now.duration_since(*last_time) < timing::CONFIG_UPDATE_DEBOUNCE
|
&& now.duration_since(*last_time) < timing::CONFIG_UPDATE_DEBOUNCE
|
||||||
{
|
{
|
||||||
return Ok(false);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_last_update(now);
|
self.set_last_update(now);
|
||||||
Ok(true)
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn perform_config_update(&self) -> Result<(bool, String)> {
|
async fn perform_config_update(&self) -> Result<(bool, String)> {
|
||||||
@ -78,22 +77,14 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn put_configs_force(&self, path: PathBuf) -> Result<()> {
|
async fn apply_config(&self, path: PathBuf) -> Result<()> {
|
||||||
self.apply_config(path).await
|
let path = dirs::path_to_str(&path)?;
|
||||||
}
|
match self.reload_config(path).await {
|
||||||
|
|
||||||
pub(super) async fn apply_config(&self, path: PathBuf) -> Result<()> {
|
|
||||||
let path_str = dirs::path_to_str(&path)?;
|
|
||||||
|
|
||||||
match self.reload_config(path_str).await {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
Config::runtime().await.apply();
|
Config::runtime().await.apply();
|
||||||
logging!(info, Type::Core, "Configuration applied");
|
logging!(info, Type::Core, "Configuration applied");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(err) if Self::should_restart_on_error(&err) => {
|
|
||||||
self.retry_with_restart(path_str).await
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
Config::runtime().await.discard();
|
Config::runtime().await.discard();
|
||||||
Err(anyhow!("Failed to apply config: {}", err))
|
Err(anyhow!("Failed to apply config: {}", err))
|
||||||
@ -101,54 +92,10 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn retry_with_restart(&self, config_path: &str) -> Result<()> {
|
|
||||||
if handle::Handle::global().is_exiting() {
|
|
||||||
return Err(anyhow!("Application exiting"));
|
|
||||||
}
|
|
||||||
|
|
||||||
logging!(warn, Type::Core, "Restarting core for config reload");
|
|
||||||
self.restart_core().await?;
|
|
||||||
sleep(timing::CONFIG_RELOAD_DELAY).await;
|
|
||||||
|
|
||||||
self.reload_config(config_path).await?;
|
|
||||||
Config::runtime().await.apply();
|
|
||||||
logging!(info, Type::Core, "Configuration applied after restart");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn reload_config(&self, path: &str) -> Result<(), MihomoError> {
|
async fn reload_config(&self, path: &str) -> Result<(), MihomoError> {
|
||||||
handle::Handle::mihomo()
|
handle::Handle::mihomo()
|
||||||
.await
|
.await
|
||||||
.reload_config(true, path)
|
.reload_config(true, path)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_restart_on_error(err: &MihomoError) -> bool {
|
|
||||||
match err {
|
|
||||||
MihomoError::ConnectionFailed | MihomoError::ConnectionLost => true,
|
|
||||||
MihomoError::Io(io_err) => Self::is_connection_io_error(io_err.kind()),
|
|
||||||
MihomoError::Reqwest(req_err) => {
|
|
||||||
req_err.is_connect()
|
|
||||||
|| req_err.is_timeout()
|
|
||||||
|| Self::contains_error_pattern(&req_err.to_string())
|
|
||||||
}
|
|
||||||
MihomoError::FailedResponse(msg) => Self::contains_error_pattern(msg),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn is_connection_io_error(kind: std::io::ErrorKind) -> bool {
|
|
||||||
matches!(
|
|
||||||
kind,
|
|
||||||
std::io::ErrorKind::ConnectionAborted
|
|
||||||
| std::io::ErrorKind::ConnectionRefused
|
|
||||||
| std::io::ErrorKind::ConnectionReset
|
|
||||||
| std::io::ErrorKind::NotFound
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_error_pattern(text: &str) -> bool {
|
|
||||||
use crate::constants::error_patterns::CONNECTION_ERRORS;
|
|
||||||
CONNECTION_ERRORS.iter().any(|p| text.contains(p))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::{CoreManager, RunningMode};
|
use super::{CoreManager, RunningMode};
|
||||||
use crate::config::{Config, ConfigType, IVerge};
|
use crate::cmd::StringifyErr as _;
|
||||||
|
use crate::config::{Config, IVerge};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{
|
core::{
|
||||||
logger::CLASH_LOGGER,
|
logger::CLASH_LOGGER,
|
||||||
@ -26,7 +27,10 @@ impl CoreManager {
|
|||||||
|
|
||||||
match *self.get_running_mode() {
|
match *self.get_running_mode() {
|
||||||
RunningMode::Service => self.stop_core_by_service().await,
|
RunningMode::Service => self.stop_core_by_service().await,
|
||||||
RunningMode::Sidecar => self.stop_core_by_sidecar(),
|
RunningMode::Sidecar => {
|
||||||
|
self.stop_core_by_sidecar();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
RunningMode::NotRunning => Ok(()),
|
RunningMode::NotRunning => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,13 +59,8 @@ impl CoreManager {
|
|||||||
let verge_data = Config::verge().await.latest_arc();
|
let verge_data = Config::verge().await.latest_arc();
|
||||||
verge_data.save_file().await.map_err(|e| e.to_string())?;
|
verge_data.save_file().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let run_path = Config::generate_file(ConfigType::Run)
|
self.update_config().await.stringify_err()?;
|
||||||
.await
|
Ok(())
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
self.apply_config(run_path)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string().into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_startup(&self) -> Result<()> {
|
async fn prepare_startup(&self) -> Result<()> {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ impl CoreManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn stop_core_by_sidecar(&self) -> Result<()> {
|
pub(super) fn stop_core_by_sidecar(&self) {
|
||||||
logging!(info, Type::Core, "Stopping sidecar");
|
logging!(info, Type::Core, "Stopping sidecar");
|
||||||
defer! {
|
defer! {
|
||||||
self.set_running_mode(RunningMode::NotRunning);
|
self.set_running_mode(RunningMode::NotRunning);
|
||||||
@ -106,7 +106,6 @@ impl CoreManager {
|
|||||||
drop(child);
|
drop(child);
|
||||||
logging!(trace, Type::Core, "Sidecar stopped (PID: {:?})", pid);
|
logging!(trace, Type::Core, "Sidecar stopped (PID: {:?})", pid);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn start_core_by_service(&self) -> Result<()> {
|
pub(super) async fn start_core_by_service(&self) -> Result<()> {
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
pub mod async_proxy_query;
|
|
||||||
pub mod backup;
|
pub mod backup;
|
||||||
pub mod event_driven_proxy;
|
|
||||||
pub mod handle;
|
pub mod handle;
|
||||||
pub mod hotkey;
|
pub mod hotkey;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
@ -13,4 +11,4 @@ pub mod tray;
|
|||||||
pub mod validate;
|
pub mod validate;
|
||||||
pub mod win_uwp;
|
pub mod win_uwp;
|
||||||
|
|
||||||
pub use self::{event_driven_proxy::EventDrivenProxyManager, manager::CoreManager, timer::Timer};
|
pub use self::{manager::CoreManager, timer::Timer};
|
||||||
|
|||||||
@ -136,11 +136,28 @@ async fn uninstall_service() -> Result<()> {
|
|||||||
let status = if linux_running_as_root() {
|
let status = if linux_running_as_root() {
|
||||||
StdCommand::new(&uninstall_path).status()?
|
StdCommand::new(&uninstall_path).status()?
|
||||||
} else {
|
} else {
|
||||||
StdCommand::new(elevator)
|
let result = StdCommand::new(&elevator)
|
||||||
.arg("sh")
|
.arg("sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(uninstall_shell)
|
.arg(&uninstall_shell)
|
||||||
.status()?
|
.status()?;
|
||||||
|
|
||||||
|
// 如果 pkexec 执行失败,回退到 sudo
|
||||||
|
if !result.success() && elevator.contains("pkexec") {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Service,
|
||||||
|
"pkexec failed with code {}, falling back to sudo",
|
||||||
|
result.code().unwrap_or(-1)
|
||||||
|
);
|
||||||
|
StdCommand::new("sudo")
|
||||||
|
.arg("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&uninstall_shell)
|
||||||
|
.status()?
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
};
|
};
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
@ -177,11 +194,28 @@ async fn install_service() -> Result<()> {
|
|||||||
let status = if linux_running_as_root() {
|
let status = if linux_running_as_root() {
|
||||||
StdCommand::new(&install_path).status()?
|
StdCommand::new(&install_path).status()?
|
||||||
} else {
|
} else {
|
||||||
StdCommand::new(elevator)
|
let result = StdCommand::new(&elevator)
|
||||||
.arg("sh")
|
.arg("sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(install_shell)
|
.arg(&install_shell)
|
||||||
.status()?
|
.status()?;
|
||||||
|
|
||||||
|
// 如果 pkexec 执行失败,回退到 sudo
|
||||||
|
if !result.success() && elevator.contains("pkexec") {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Service,
|
||||||
|
"pkexec failed with code {}, falling back to sudo",
|
||||||
|
result.code().unwrap_or(-1)
|
||||||
|
);
|
||||||
|
StdCommand::new("sudo")
|
||||||
|
.arg("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&install_shell)
|
||||||
|
.status()?
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
};
|
};
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
@ -456,12 +490,12 @@ impl ServiceManager {
|
|||||||
Self(ServiceStatus::Unavailable("Need Checks".into()))
|
Self(ServiceStatus::Unavailable("Need Checks".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn config() -> Option<clash_verge_service_ipc::IpcConfig> {
|
pub const fn config() -> clash_verge_service_ipc::IpcConfig {
|
||||||
Some(clash_verge_service_ipc::IpcConfig {
|
clash_verge_service_ipc::IpcConfig {
|
||||||
default_timeout: Duration::from_millis(30),
|
default_timeout: Duration::from_millis(30),
|
||||||
retry_delay: Duration::from_millis(250),
|
retry_delay: Duration::from_millis(250),
|
||||||
max_retries: 6,
|
max_retries: 6,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(&mut self) -> Result<()> {
|
pub async fn init(&mut self) -> Result<()> {
|
||||||
|
|||||||
@ -2,22 +2,43 @@
|
|||||||
use crate::utils::autostart as startup_shortcut;
|
use crate::utils::autostart as startup_shortcut;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge},
|
config::{Config, IVerge},
|
||||||
core::{EventDrivenProxyManager, handle::Handle},
|
core::handle::Handle,
|
||||||
logging, logging_error, singleton_lazy,
|
logging, logging_error, singleton_lazy,
|
||||||
utils::logging::Type,
|
utils::logging::Type,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use parking_lot::RwLock;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::{
|
||||||
#[cfg(not(target_os = "windows"))]
|
sync::{
|
||||||
use sysproxy::{Autoproxy, Sysproxy};
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use sysproxy::{Autoproxy, GuardMonitor, GuardType, Sysproxy};
|
||||||
use tauri_plugin_autostart::ManagerExt as _;
|
use tauri_plugin_autostart::ManagerExt as _;
|
||||||
|
|
||||||
pub struct Sysopt {
|
pub struct Sysopt {
|
||||||
initialed: AtomicBool,
|
initialed: AtomicBool,
|
||||||
update_sysproxy: AtomicBool,
|
update_sysproxy: AtomicBool,
|
||||||
reset_sysproxy: AtomicBool,
|
reset_sysproxy: AtomicBool,
|
||||||
|
guard: Arc<RwLock<GuardMonitor>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Sysopt {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
initialed: AtomicBool::new(false),
|
||||||
|
update_sysproxy: AtomicBool::new(false),
|
||||||
|
reset_sysproxy: AtomicBool::new(false),
|
||||||
|
guard: Arc::new(RwLock::new(GuardMonitor::new(
|
||||||
|
GuardType::None,
|
||||||
|
Duration::from_secs(30),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@ -82,16 +103,6 @@ async fn execute_sysproxy_command(args: Vec<std::string::String>) -> Result<()>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sysopt {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
initialed: AtomicBool::new(false),
|
|
||||||
update_sysproxy: AtomicBool::new(false),
|
|
||||||
reset_sysproxy: AtomicBool::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use simplified singleton_lazy macro
|
// Use simplified singleton_lazy macro
|
||||||
singleton_lazy!(Sysopt, SYSOPT, Sysopt::default);
|
singleton_lazy!(Sysopt, SYSOPT, Sysopt::default);
|
||||||
|
|
||||||
@ -100,31 +111,64 @@ impl Sysopt {
|
|||||||
self.initialed.load(Ordering::SeqCst)
|
self.initialed.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_guard_sysproxy(&self) -> Result<()> {
|
fn access_guard(&self) -> Arc<RwLock<GuardMonitor>> {
|
||||||
// 使用事件驱动代理管理器
|
Arc::clone(&self.guard)
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
}
|
||||||
proxy_manager.notify_app_started();
|
|
||||||
|
|
||||||
logging!(info, Type::Core, "已启用事件驱动代理守卫");
|
pub async fn refresh_guard(&self) {
|
||||||
Ok(())
|
logging!(info, Type::Core, "Refreshing system proxy guard...");
|
||||||
|
let verge = Config::verge().await.latest_arc();
|
||||||
|
if !verge.enable_system_proxy.unwrap_or(false) {
|
||||||
|
logging!(info, Type::Core, "System proxy is disabled.");
|
||||||
|
self.access_guard().write().stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !verge.enable_proxy_guard.unwrap_or(false) {
|
||||||
|
logging!(info, Type::Core, "System proxy guard is disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Core,
|
||||||
|
"Updating system proxy with duration: {} seconds",
|
||||||
|
verge.proxy_guard_duration.unwrap_or(30)
|
||||||
|
);
|
||||||
|
{
|
||||||
|
let guard = self.access_guard();
|
||||||
|
guard.write().set_interval(Duration::from_secs(
|
||||||
|
verge.proxy_guard_duration.unwrap_or(30),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
logging!(info, Type::Core, "Starting system proxy guard...");
|
||||||
|
{
|
||||||
|
let guard = self.access_guard();
|
||||||
|
guard.write().start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// init the sysproxy
|
/// init the sysproxy
|
||||||
pub async fn update_sysproxy(&self) -> Result<()> {
|
pub async fn update_sysproxy(&self) -> Result<()> {
|
||||||
self.initialed.store(true, Ordering::SeqCst);
|
self.initialed.store(true, Ordering::SeqCst);
|
||||||
|
if self.update_sysproxy.load(Ordering::Acquire) {
|
||||||
|
logging!(info, Type::Core, "Sysproxy update is already in progress.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
if self
|
if self
|
||||||
.update_sysproxy
|
.update_sysproxy
|
||||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
|
logging!(info, Type::Core, "Sysproxy update is already in progress.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
defer! {
|
defer! {
|
||||||
self.update_sysproxy.store(false, Ordering::SeqCst);
|
logging!(info, Type::Core, "Sysproxy update completed.");
|
||||||
|
self.update_sysproxy.store(false, Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let verge = Config::verge().await.latest_arc();
|
||||||
let port = {
|
let port = {
|
||||||
let verge_port = Config::verge().await.latest_arc().verge_mixed_port;
|
let verge_port = verge.verge_mixed_port;
|
||||||
match verge_port {
|
match verge_port {
|
||||||
Some(port) => port,
|
Some(port) => port,
|
||||||
None => Config::clash().await.latest_arc().get_mixed_port(),
|
None => Config::clash().await.latest_arc().get_mixed_port(),
|
||||||
@ -133,8 +177,6 @@ impl Sysopt {
|
|||||||
let pac_port = IVerge::get_singleton_port();
|
let pac_port = IVerge::get_singleton_port();
|
||||||
|
|
||||||
let (sys_enable, pac_enable, proxy_host) = {
|
let (sys_enable, pac_enable, proxy_host) = {
|
||||||
let verge = Config::verge().await;
|
|
||||||
let verge = verge.latest_arc();
|
|
||||||
(
|
(
|
||||||
verge.enable_system_proxy.unwrap_or(false),
|
verge.enable_system_proxy.unwrap_or(false),
|
||||||
verge.proxy_auto_config.unwrap_or(false),
|
verge.proxy_auto_config.unwrap_or(false),
|
||||||
@ -161,8 +203,9 @@ impl Sysopt {
|
|||||||
if !sys_enable {
|
if !sys_enable {
|
||||||
sys.set_system_proxy()?;
|
sys.set_system_proxy()?;
|
||||||
auto.set_auto_proxy()?;
|
auto.set_auto_proxy()?;
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
self.access_guard()
|
||||||
proxy_manager.notify_config_changed();
|
.write()
|
||||||
|
.set_guard_type(GuardType::Sysproxy(sys));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,8 +214,9 @@ impl Sysopt {
|
|||||||
auto.enable = true;
|
auto.enable = true;
|
||||||
sys.set_system_proxy()?;
|
sys.set_system_proxy()?;
|
||||||
auto.set_auto_proxy()?;
|
auto.set_auto_proxy()?;
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
self.access_guard()
|
||||||
proxy_manager.notify_config_changed();
|
.write()
|
||||||
|
.set_guard_type(GuardType::Autoproxy(auto));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,33 +225,47 @@ impl Sysopt {
|
|||||||
sys.enable = true;
|
sys.enable = true;
|
||||||
auto.set_auto_proxy()?;
|
auto.set_auto_proxy()?;
|
||||||
sys.set_system_proxy()?;
|
sys.set_system_proxy()?;
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
self.access_guard()
|
||||||
proxy_manager.notify_config_changed();
|
.write()
|
||||||
|
.set_guard_type(GuardType::Sysproxy(sys));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
if !sys_enable {
|
if !sys_enable {
|
||||||
let result = self.reset_sysproxy().await;
|
self.access_guard().write().set_guard_type(GuardType::None);
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
return self.reset_sysproxy().await;
|
||||||
proxy_manager.notify_config_changed();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let args: Vec<std::string::String> = if pac_enable {
|
let (args, guard_type): (Vec<std::string::String>, GuardType) = if pac_enable {
|
||||||
let address = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
let address = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
||||||
vec!["pac".into(), address]
|
(
|
||||||
|
vec!["pac".into(), address.clone()],
|
||||||
|
GuardType::Autoproxy(Autoproxy {
|
||||||
|
enable: true,
|
||||||
|
url: address,
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let address = format!("{proxy_host}:{port}");
|
let address = format!("{proxy_host}:{port}");
|
||||||
let bypass = get_bypass().await;
|
let bypass = get_bypass().await;
|
||||||
vec!["global".into(), address, bypass.into()]
|
let bypass_for_guard = bypass.as_str().to_owned();
|
||||||
|
(
|
||||||
|
vec!["global".into(), address.clone(), bypass.into()],
|
||||||
|
GuardType::Sysproxy(Sysproxy {
|
||||||
|
enable: true,
|
||||||
|
host: proxy_host.clone().into(),
|
||||||
|
port,
|
||||||
|
bypass: bypass_for_guard,
|
||||||
|
}),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
execute_sysproxy_command(args).await?;
|
execute_sysproxy_command(args).await?;
|
||||||
|
self.access_guard().write().set_guard_type(guard_type);
|
||||||
}
|
}
|
||||||
let proxy_manager = EventDrivenProxyManager::global();
|
|
||||||
proxy_manager.notify_config_changed();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config, core::sysopt::Sysopt, feat, logging, logging_error, singleton,
|
config::Config,
|
||||||
|
core::{CoreManager, manager::RunningMode, sysopt::Sysopt},
|
||||||
|
feat, logging, logging_error, singleton,
|
||||||
utils::logging::Type,
|
utils::logging::Type,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
@ -392,6 +394,7 @@ impl Timer {
|
|||||||
.spawn_async_routine(move || {
|
.spawn_async_routine(move || {
|
||||||
let uid = uid.clone();
|
let uid = uid.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
|
Self::wait_untile_core_manager(Duration::from_millis(1000)).await;
|
||||||
Self::wait_until_sysopt(Duration::from_millis(1000)).await;
|
Self::wait_until_sysopt(Duration::from_millis(1000)).await;
|
||||||
Self::async_task(&uid).await;
|
Self::async_task(&uid).await;
|
||||||
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send>>
|
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send>>
|
||||||
@ -523,10 +526,28 @@ impl Timer {
|
|||||||
Self::emit_update_event(uid, false);
|
Self::emit_update_event(uid, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_untile_core_manager(max_wait: Duration) {
|
||||||
|
let _ = timeout(max_wait, async {
|
||||||
|
while *CoreManager::global().get_running_mode() != RunningMode::NotRunning {
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Timer,
|
||||||
|
"Waiting for CoreManager to be initialized..."
|
||||||
|
);
|
||||||
|
sleep(Duration::from_millis(30)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn wait_until_sysopt(max_wait: Duration) {
|
async fn wait_until_sysopt(max_wait: Duration) {
|
||||||
let _ = timeout(max_wait, async {
|
let _ = timeout(max_wait, async {
|
||||||
while !Sysopt::global().is_initialed() {
|
while !Sysopt::global().is_initialed() {
|
||||||
logging!(warn, Type::Timer, "Waiting for Sysopt to be initialized...");
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Timer,
|
||||||
|
"Waiting for Sysopt to be initialized..."
|
||||||
|
);
|
||||||
sleep(Duration::from_millis(30)).await;
|
sleep(Duration::from_millis(30)).await;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -301,8 +301,8 @@ impl Tray {
|
|||||||
let verge = Config::verge().await.latest_arc();
|
let verge = Config::verge().await.latest_arc();
|
||||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||||
let tun_mode_available = cmd::system::is_admin().unwrap_or_default()
|
let tun_mode_available =
|
||||||
|| service::is_service_available().await.is_ok();
|
cmd::system::is_admin() || service::is_service_available().await.is_ok();
|
||||||
let mode = {
|
let mode = {
|
||||||
Config::clash()
|
Config::clash()
|
||||||
.await
|
.await
|
||||||
@ -640,7 +640,7 @@ fn create_subcreate_proxy_menu_item(
|
|||||||
current_profile_selected: &[PrfSelected],
|
current_profile_selected: &[PrfSelected],
|
||||||
proxy_group_order_map: Option<HashMap<String, usize>>,
|
proxy_group_order_map: Option<HashMap<String, usize>>,
|
||||||
proxy_nodes_data: Result<Proxies>,
|
proxy_nodes_data: Result<Proxies>,
|
||||||
) -> Result<Vec<Submenu<Wry>>> {
|
) -> Vec<Submenu<Wry>> {
|
||||||
let proxy_submenus: Vec<Submenu<Wry>> = {
|
let proxy_submenus: Vec<Submenu<Wry>> = {
|
||||||
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = Vec::new();
|
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = Vec::new();
|
||||||
|
|
||||||
@ -767,7 +767,7 @@ fn create_subcreate_proxy_menu_item(
|
|||||||
.map(|(_, _, submenu)| submenu)
|
.map(|(_, _, submenu)| submenu)
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
Ok(proxy_submenus)
|
proxy_submenus
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_proxy_menu_item(
|
fn create_proxy_menu_item(
|
||||||
@ -955,7 +955,7 @@ async fn create_tray_menu(
|
|||||||
¤t_profile_selected,
|
¤t_profile_selected,
|
||||||
proxy_group_order_map,
|
proxy_group_order_map,
|
||||||
proxy_nodes_data.map_err(anyhow::Error::from),
|
proxy_nodes_data.map_err(anyhow::Error::from),
|
||||||
)?;
|
);
|
||||||
|
|
||||||
let (proxies_menu, inline_proxy_items) = create_proxy_menu_item(
|
let (proxies_menu, inline_proxy_items) = create_proxy_menu_item(
|
||||||
app_handle,
|
app_handle,
|
||||||
|
|||||||
@ -425,7 +425,7 @@ async fn merge_default_config(
|
|||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
if key.as_str() == Some("redir-port") || key.as_str() == Some("tproxy-port") {
|
if key.as_str() == Some("redir-port") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,6 +443,13 @@ async fn merge_default_config(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
if key.as_str() == Some("tproxy-port") {
|
||||||
|
config.remove("tproxy-port");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
// 处理 external-controller 键的开关逻辑
|
// 处理 external-controller 键的开关逻辑
|
||||||
if key.as_str() == Some("external-controller") {
|
if key.as_str() == Some("external-controller") {
|
||||||
let enable_external_controller = Config::verge()
|
let enable_external_controller = Config::verge()
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge},
|
config::{Config, IVerge},
|
||||||
core::backup,
|
core::backup,
|
||||||
logging, logging_error,
|
logging,
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{
|
utils::{
|
||||||
dirs::{PathBufExec as _, app_home_dir, local_backup_dir},
|
dirs::{PathBufExec as _, app_home_dir, local_backup_dir, verge_path},
|
||||||
|
help,
|
||||||
logging::Type,
|
logging::Type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -24,6 +25,38 @@ pub struct LocalBackupFile {
|
|||||||
pub content_length: u64,
|
pub content_length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load restored verge.yaml from disk, merge back WebDAV creds, save, and sync memory.
|
||||||
|
async fn finalize_restored_verge_config(
|
||||||
|
webdav_url: Option<String>,
|
||||||
|
webdav_username: Option<String>,
|
||||||
|
webdav_password: Option<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Do NOT silently fallback to defaults; a broken/missing verge.yaml means restore failed.
|
||||||
|
// Propagate the error so the UI/user can react accordingly.
|
||||||
|
let mut restored = help::read_yaml::<IVerge>(&verge_path()?).await?;
|
||||||
|
restored.webdav_url = webdav_url;
|
||||||
|
restored.webdav_username = webdav_username;
|
||||||
|
restored.webdav_password = webdav_password;
|
||||||
|
restored.save_file().await?;
|
||||||
|
|
||||||
|
let verge_draft = Config::verge().await;
|
||||||
|
verge_draft.edit_draft(|d| {
|
||||||
|
*d = restored.clone();
|
||||||
|
});
|
||||||
|
verge_draft.apply();
|
||||||
|
|
||||||
|
// Ensure side-effects (flags, tray, sysproxy, hotkeys, auto-backup refresh, etc.) run.
|
||||||
|
// Use not_save_file = true to avoid extra I/O (we already persisted the restored file).
|
||||||
|
if let Err(err) = super::patch_verge(&restored, true).await {
|
||||||
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Backup,
|
||||||
|
"Failed to apply restored verge config: {err:#?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a backup and upload to WebDAV
|
/// Create a backup and upload to WebDAV
|
||||||
pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||||
let (file_name, temp_file_path) = backup::create_backup().await.map_err(|err| {
|
let (file_name, temp_file_path) = backup::create_backup().await.map_err(|err| {
|
||||||
@ -103,22 +136,10 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
|||||||
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&value)).await??;
|
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&value)).await??;
|
||||||
let mut zip = zip::ZipArchive::new(file)?;
|
let mut zip = zip::ZipArchive::new(file)?;
|
||||||
zip.extract(app_home_dir()?)?;
|
zip.extract(app_home_dir()?)?;
|
||||||
logging_error!(
|
let res = finalize_restored_verge_config(webdav_url, webdav_username, webdav_password).await;
|
||||||
Type::Backup,
|
// Finally remove the temp file (attempt cleanup even if finalize fails)
|
||||||
super::patch_verge(
|
let _ = backup_storage_path.remove_if_exists().await;
|
||||||
&IVerge {
|
res
|
||||||
webdav_url,
|
|
||||||
webdav_username,
|
|
||||||
webdav_password,
|
|
||||||
..IVerge::default()
|
|
||||||
},
|
|
||||||
false
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
// 最后删除临时文件
|
|
||||||
backup_storage_path.remove_if_exists().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a backup and save to local storage
|
/// Create a backup and save to local storage
|
||||||
@ -264,19 +285,7 @@ pub async fn restore_local_backup(filename: String) -> Result<()> {
|
|||||||
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&target_path)).await??;
|
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&target_path)).await??;
|
||||||
let mut zip = zip::ZipArchive::new(file)?;
|
let mut zip = zip::ZipArchive::new(file)?;
|
||||||
zip.extract(app_home_dir()?)?;
|
zip.extract(app_home_dir()?)?;
|
||||||
logging_error!(
|
finalize_restored_verge_config(webdav_url, webdav_username, webdav_password).await?;
|
||||||
Type::Backup,
|
|
||||||
super::patch_verge(
|
|
||||||
&IVerge {
|
|
||||||
webdav_url,
|
|
||||||
webdav_username,
|
|
||||||
webdav_password,
|
|
||||||
..IVerge::default()
|
|
||||||
},
|
|
||||||
false
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IVerge},
|
config::{Config, IVerge},
|
||||||
core::{CoreManager, handle, hotkey, sysopt, tray},
|
core::{CoreManager, handle, hotkey, sysopt, tray},
|
||||||
logging_error,
|
logging, logging_error,
|
||||||
module::{auto_backup::AutoBackupManager, lightweight},
|
module::{auto_backup::AutoBackupManager, lightweight},
|
||||||
utils::{draft::SharedBox, logging::Type},
|
utils::logging::Type,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use draft::SharedBox;
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
|
|
||||||
/// Patch Clash configuration
|
/// Patch Clash configuration
|
||||||
@ -107,6 +108,8 @@ fn determine_update_flags(patch: &IVerge) -> i32 {
|
|||||||
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
|
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
|
||||||
let enable_external_controller = patch.enable_external_controller;
|
let enable_external_controller = patch.enable_external_controller;
|
||||||
let tray_inline_proxy_groups = patch.tray_inline_proxy_groups;
|
let tray_inline_proxy_groups = patch.tray_inline_proxy_groups;
|
||||||
|
let enable_proxy_guard = patch.enable_proxy_guard;
|
||||||
|
let proxy_guard_duration = patch.proxy_guard_duration;
|
||||||
|
|
||||||
if tun_mode.is_some() {
|
if tun_mode.is_some() {
|
||||||
update_flags |= UpdateFlags::ClashConfig as i32;
|
update_flags |= UpdateFlags::ClashConfig as i32;
|
||||||
@ -144,7 +147,12 @@ fn determine_update_flags(patch: &IVerge) -> i32 {
|
|||||||
update_flags |= UpdateFlags::SystrayIcon as i32;
|
update_flags |= UpdateFlags::SystrayIcon as i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
|
if proxy_bypass.is_some()
|
||||||
|
|| pac_content.is_some()
|
||||||
|
|| pac.is_some()
|
||||||
|
|| enable_proxy_guard.is_some()
|
||||||
|
|| proxy_guard_duration.is_some()
|
||||||
|
{
|
||||||
update_flags |= UpdateFlags::SysProxy as i32;
|
update_flags |= UpdateFlags::SysProxy as i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +215,7 @@ async fn process_terminated_flags(update_flags: i32, patch: &IVerge) -> Result<(
|
|||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
|
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
|
||||||
sysopt::Sysopt::global().update_sysproxy().await?;
|
sysopt::Sysopt::global().update_sysproxy().await?;
|
||||||
|
sysopt::Sysopt::global().refresh_guard().await;
|
||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0
|
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0
|
||||||
&& let Some(hotkeys) = &patch.hotkeys
|
&& let Some(hotkeys) = &patch.hotkeys
|
||||||
@ -258,6 +267,7 @@ pub async fn patch_verge(patch: &IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
if !not_save_file {
|
if !not_save_file {
|
||||||
// 分离数据获取和异步调用
|
// 分离数据获取和异步调用
|
||||||
let verge_data = Config::verge().await.data_arc();
|
let verge_data = Config::verge().await.data_arc();
|
||||||
|
logging!(info, Type::Setup, "Saving Verge configuration to file...");
|
||||||
verge_data.save_file().await?;
|
verge_data.save_file().await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::core::event_driven_proxy::EventDrivenProxyManager;
|
|
||||||
use crate::core::{CoreManager, handle, sysopt};
|
use crate::core::{CoreManager, handle, sysopt};
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
@ -24,7 +23,6 @@ pub async fn quit() {
|
|||||||
// 获取应用句柄并设置退出标志
|
// 获取应用句柄并设置退出标志
|
||||||
let app_handle = handle::Handle::app_handle();
|
let app_handle = handle::Handle::app_handle();
|
||||||
handle::Handle::global().set_is_exiting();
|
handle::Handle::global().set_is_exiting();
|
||||||
EventDrivenProxyManager::global().notify_app_stopping();
|
|
||||||
|
|
||||||
logging!(info, Type::System, "开始异步清理资源");
|
logging!(info, Type::System, "开始异步清理资源");
|
||||||
let cleanup_result = clean_async().await;
|
let cleanup_result = clean_async().await;
|
||||||
|
|||||||
@ -13,11 +13,7 @@ pub mod utils;
|
|||||||
use crate::constants::files;
|
use crate::constants::files;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use crate::utils::linux;
|
use crate::utils::linux;
|
||||||
use crate::{
|
use crate::{core::handle, process::AsyncHandler, utils::resolve};
|
||||||
core::{EventDrivenProxyManager, handle},
|
|
||||||
process::AsyncHandler,
|
|
||||||
utils::resolve,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use rust_i18n::i18n;
|
use rust_i18n::i18n;
|
||||||
@ -85,9 +81,9 @@ mod app_init {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Setup deep link handling
|
/// Setup deep link handling
|
||||||
pub fn setup_deep_links(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup_deep_links(app: &tauri::App) {
|
||||||
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||||
app.deep_link().register_all()?;
|
let _ = app.deep_link().register_all();
|
||||||
|
|
||||||
app.deep_link().on_open_url(|event| {
|
app.deep_link().on_open_url(|event| {
|
||||||
let urls = event.urls();
|
let urls = event.urls();
|
||||||
@ -99,8 +95,6 @@ mod app_init {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setup autostart plugin
|
/// Setup autostart plugin
|
||||||
@ -242,9 +236,7 @@ pub fn run() {
|
|||||||
logging!(error, Type::Setup, "Failed to setup autostart: {}", e);
|
logging!(error, Type::Setup, "Failed to setup autostart: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = app_init::setup_deep_links(app) {
|
app_init::setup_deep_links(app);
|
||||||
logging!(error, Type::Setup, "Failed to setup deep links: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = app_init::setup_window_state(app) {
|
if let Err(e) = app_init::setup_window_state(app) {
|
||||||
logging!(error, Type::Setup, "Failed to setup window state: {}", e);
|
logging!(error, Type::Setup, "Failed to setup window state: {}", e);
|
||||||
@ -440,7 +432,6 @@ pub fn run() {
|
|||||||
let handle = core::handle::Handle::global();
|
let handle = core::handle::Handle::global();
|
||||||
if !handle.is_exiting() {
|
if !handle.is_exiting() {
|
||||||
handle.set_is_exiting();
|
handle.set_is_exiting();
|
||||||
EventDrivenProxyManager::global().notify_app_stopping();
|
|
||||||
feat::clean();
|
feat::clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
core::{handle, timer::Timer, tray::Tray},
|
core::{handle, timer::Timer, tray::Tray},
|
||||||
log_err, logging,
|
logging,
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::logging::Type,
|
utils::logging::Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
use crate::logging_error;
|
use crate::logging_error;
|
||||||
|
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
@ -184,7 +183,7 @@ fn cancel_window_close_listener() {
|
|||||||
fn setup_webview_focus_listener() {
|
fn setup_webview_focus_listener() {
|
||||||
if let Some(window) = handle::Handle::get_window() {
|
if let Some(window) = handle::Handle::get_window() {
|
||||||
let handler_id = window.listen("tauri://focus", move |_event| {
|
let handler_id = window.listen("tauri://focus", move |_event| {
|
||||||
log_err!(cancel_light_weight_timer());
|
logging_error!(Type::Lightweight, cancel_light_weight_timer());
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
Type::Lightweight,
|
Type::Lightweight,
|
||||||
|
|||||||
@ -44,7 +44,7 @@ impl PlatformSpecification {
|
|||||||
// 使用默认值避免在同步上下文中执行异步操作
|
// 使用默认值避免在同步上下文中执行异步操作
|
||||||
let running_mode = "NotRunning".to_string();
|
let running_mode = "NotRunning".to_string();
|
||||||
|
|
||||||
let is_admin = system::is_admin().unwrap_or_default();
|
let is_admin = system::is_admin();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
system_name,
|
system_name,
|
||||||
|
|||||||
@ -1,22 +1,12 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use tauri_plugin_shell::process::CommandChild;
|
use tauri_plugin_shell::process::CommandChild;
|
||||||
|
|
||||||
use crate::{logging, utils::logging::Type};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CommandChildGuard(Option<CommandChild>);
|
pub struct CommandChildGuard(Option<CommandChild>);
|
||||||
|
|
||||||
impl Drop for CommandChildGuard {
|
impl Drop for CommandChildGuard {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Err(err) = self.kill() {
|
self.kill();
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Service,
|
|
||||||
"Failed to kill child process: {}",
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,11 +17,10 @@ impl CommandChildGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn kill(&mut self) -> Result<()> {
|
pub fn kill(&mut self) {
|
||||||
if let Some(child) = self.0.take() {
|
if let Some(child) = self.0.take() {
|
||||||
let _ = child.kill();
|
let _ = child.kill();
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@ -75,7 +75,9 @@ pub async fn save_yaml<T: Serialize + Sync>(
|
|||||||
let path_str = path.as_os_str().to_string_lossy().to_string();
|
let path_str = path.as_os_str().to_string_lossy().to_string();
|
||||||
tokio::fs::write(path, yaml_str.as_bytes())
|
tokio::fs::write(path, yaml_str.as_bytes())
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("failed to save file \"{path_str}\""))
|
.with_context(|| format!("failed to save file \"{path_str}\""))?;
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALPHABET: [char; 62] = [
|
const ALPHABET: [char; 62] = [
|
||||||
|
|||||||
@ -68,11 +68,18 @@ pub async fn init_logger() -> Result<()> {
|
|||||||
Cleanup::KeepLogFiles(log_max_count),
|
Cleanup::KeepLogFiles(log_max_count),
|
||||||
);
|
);
|
||||||
#[cfg(not(feature = "tracing"))]
|
#[cfg(not(feature = "tracing"))]
|
||||||
let logger = logger.filter(Box::new(NoModuleFilter(&["wry", "tauri"])));
|
let logger = logger.filter(Box::new(NoModuleFilter(&[
|
||||||
|
"wry",
|
||||||
|
"tauri",
|
||||||
|
"tokio_tungstenite",
|
||||||
|
"tungstenite",
|
||||||
|
])));
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let logger = logger.filter(Box::new(NoModuleFilter(&[
|
let logger = logger.filter(Box::new(NoModuleFilter(&[
|
||||||
"wry",
|
"wry",
|
||||||
"tauri_plugin_mihomo",
|
"tauri_plugin_mihomo",
|
||||||
|
"tokio_tungstenite",
|
||||||
|
"tungstenite",
|
||||||
"kode_bridge",
|
"kode_bridge",
|
||||||
])));
|
])));
|
||||||
|
|
||||||
|
|||||||
@ -65,21 +65,6 @@ macro_rules! error {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! log_err {
|
|
||||||
($result: expr) => {
|
|
||||||
if let Err(err) = $result {
|
|
||||||
log::error!(target: "app", "{err}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
($result: expr, $err_str: expr) => {
|
|
||||||
if let Err(_) = $result {
|
|
||||||
log::error!(target: "app", "{}", $err_str);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// wrap the anyhow error
|
/// wrap the anyhow error
|
||||||
/// transform the error to String
|
/// transform the error to String
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
pub mod autostart;
|
pub mod autostart;
|
||||||
pub mod dirs;
|
pub mod dirs;
|
||||||
pub mod draft;
|
|
||||||
pub mod format;
|
pub mod format;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
pub mod i18n;
|
pub mod i18n;
|
||||||
@ -15,5 +14,3 @@ pub mod server;
|
|||||||
pub mod singleton;
|
pub mod singleton;
|
||||||
pub mod tmpl;
|
pub mod tmpl;
|
||||||
pub mod window_manager;
|
pub mod window_manager;
|
||||||
|
|
||||||
pub use draft::Draft;
|
|
||||||
|
|||||||
@ -1,22 +1,15 @@
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
use isahc::config::DnsCache;
|
use reqwest::{
|
||||||
use isahc::prelude::*;
|
Client, Proxy, StatusCode,
|
||||||
use isahc::{HttpClient, config::SslOption};
|
header::{HeaderMap, HeaderValue, USER_AGENT},
|
||||||
use isahc::{
|
|
||||||
config::RedirectPolicy,
|
|
||||||
http::{
|
|
||||||
StatusCode, Uri,
|
|
||||||
header::{HeaderMap, HeaderValue, USER_AGENT},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use sysproxy::Sysproxy;
|
use sysproxy::Sysproxy;
|
||||||
use tauri::Url;
|
use tauri::Url;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::timeout;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HttpResponse {
|
pub struct HttpResponse {
|
||||||
@ -55,9 +48,9 @@ pub enum ProxyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct NetworkManager {
|
pub struct NetworkManager {
|
||||||
self_proxy_client: Mutex<Option<HttpClient>>,
|
self_proxy_client: Mutex<Option<Client>>,
|
||||||
system_proxy_client: Mutex<Option<HttpClient>>,
|
system_proxy_client: Mutex<Option<Client>>,
|
||||||
no_proxy_client: Mutex<Option<HttpClient>>,
|
no_proxy_client: Mutex<Option<Client>>,
|
||||||
last_connection_error: Mutex<Option<(Instant, String)>>,
|
last_connection_error: Mutex<Option<(Instant, String)>>,
|
||||||
connection_error_count: Mutex<usize>,
|
connection_error_count: Mutex<usize>,
|
||||||
}
|
}
|
||||||
@ -111,41 +104,42 @@ impl NetworkManager {
|
|||||||
|
|
||||||
fn build_client(
|
fn build_client(
|
||||||
&self,
|
&self,
|
||||||
proxy_uri: Option<Uri>,
|
proxy_url: Option<std::string::String>,
|
||||||
default_headers: HeaderMap,
|
default_headers: HeaderMap,
|
||||||
accept_invalid_certs: bool,
|
accept_invalid_certs: bool,
|
||||||
timeout_secs: Option<u64>,
|
timeout_secs: Option<u64>,
|
||||||
) -> Result<HttpClient> {
|
) -> Result<Client> {
|
||||||
{
|
let mut builder = Client::builder()
|
||||||
let mut builder = HttpClient::builder();
|
.redirect(reqwest::redirect::Policy::limited(10))
|
||||||
|
.tcp_keepalive(Duration::from_secs(60))
|
||||||
|
.pool_max_idle_per_host(0)
|
||||||
|
.pool_idle_timeout(None);
|
||||||
|
|
||||||
builder = match proxy_uri {
|
// 设置代理
|
||||||
Some(uri) => builder.proxy(Some(uri)),
|
if let Some(proxy_str) = proxy_url {
|
||||||
None => builder.proxy(None),
|
let proxy = Proxy::all(proxy_str)?;
|
||||||
};
|
builder = builder.proxy(proxy);
|
||||||
|
} else {
|
||||||
for (name, value) in default_headers.iter() {
|
builder = builder.no_proxy();
|
||||||
builder = builder.default_header(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if accept_invalid_certs {
|
|
||||||
builder = builder.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(secs) = timeout_secs {
|
|
||||||
builder = builder.timeout(Duration::from_secs(secs));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder = builder.redirect_policy(RedirectPolicy::Follow);
|
|
||||||
|
|
||||||
// 禁用缓存,不关心连接复用
|
|
||||||
builder = builder.connection_cache_size(0);
|
|
||||||
|
|
||||||
// 禁用 DNS 缓存,避免因 DNS 变化导致的问题
|
|
||||||
builder = builder.dns_cache(DnsCache::Disable);
|
|
||||||
|
|
||||||
Ok(builder.build()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder = builder.default_headers(default_headers);
|
||||||
|
|
||||||
|
// SSL/TLS
|
||||||
|
if accept_invalid_certs {
|
||||||
|
builder = builder
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
.danger_accept_invalid_hostnames(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超时设置
|
||||||
|
if let Some(secs) = timeout_secs {
|
||||||
|
builder = builder
|
||||||
|
.timeout(Duration::from_secs(secs))
|
||||||
|
.connect_timeout(Duration::from_secs(secs.min(30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(builder.build()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_request(
|
pub async fn create_request(
|
||||||
@ -154,8 +148,8 @@ impl NetworkManager {
|
|||||||
timeout_secs: Option<u64>,
|
timeout_secs: Option<u64>,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
accept_invalid_certs: bool,
|
accept_invalid_certs: bool,
|
||||||
) -> Result<HttpClient> {
|
) -> Result<Client> {
|
||||||
let proxy_uri = match proxy_type {
|
let proxy_url: Option<std::string::String> = match proxy_type {
|
||||||
ProxyType::None => None,
|
ProxyType::None => None,
|
||||||
ProxyType::Localhost => {
|
ProxyType::Localhost => {
|
||||||
let port = {
|
let port = {
|
||||||
@ -165,13 +159,11 @@ impl NetworkManager {
|
|||||||
None => Config::clash().await.data_arc().get_mixed_port(),
|
None => Config::clash().await.data_arc().get_mixed_port(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
Some(format!("http://127.0.0.1:{port}"))
|
||||||
proxy_scheme.parse::<Uri>().ok()
|
|
||||||
}
|
}
|
||||||
ProxyType::System => {
|
ProxyType::System => {
|
||||||
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
|
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
|
||||||
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
Some(format!("http://{}:{}", p.host, p.port))
|
||||||
proxy_scheme.parse::<Uri>().ok()
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -179,16 +171,18 @@ impl NetworkManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(
|
|
||||||
USER_AGENT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&user_agent.unwrap_or_else(|| {
|
|
||||||
format!("clash-verge/v{}", env!("CARGO_PKG_VERSION")).into()
|
|
||||||
}),
|
|
||||||
)?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let client = self.build_client(proxy_uri, headers, accept_invalid_certs, timeout_secs)?;
|
// 设置 User-Agent
|
||||||
|
if let Some(ua) = user_agent {
|
||||||
|
headers.insert(USER_AGENT, HeaderValue::from_str(ua.as_str())?);
|
||||||
|
} else {
|
||||||
|
headers.insert(
|
||||||
|
USER_AGENT,
|
||||||
|
HeaderValue::from_str(&format!("clash-verge/v{}", env!("CARGO_PKG_VERSION")))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = self.build_client(proxy_url, headers, accept_invalid_certs, timeout_secs)?;
|
||||||
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
@ -226,37 +220,37 @@ impl NetworkManager {
|
|||||||
no_auth.to_string()
|
no_auth.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 创建请求
|
||||||
let client = self
|
let client = self
|
||||||
.create_request(proxy_type, timeout_secs, user_agent, accept_invalid_certs)
|
.create_request(proxy_type, timeout_secs, user_agent, accept_invalid_certs)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let timeout_duration = Duration::from_secs(timeout_secs.unwrap_or(20));
|
let mut request_builder = client.get(&clean_url);
|
||||||
let response = match timeout(timeout_duration, async {
|
|
||||||
let mut req = isahc::Request::get(&clean_url);
|
|
||||||
|
|
||||||
for (k, v) in extra_headers.iter() {
|
for (key, value) in extra_headers.iter() {
|
||||||
req = req.header(k, v);
|
request_builder = request_builder.header(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = client.send_async(req.body(())?).await?;
|
let response = match request_builder.send().await {
|
||||||
let status = response.status();
|
Ok(resp) => resp,
|
||||||
let headers = response.headers().clone();
|
Err(e) => {
|
||||||
let body = response.text().await?;
|
self.record_connection_error(&format!("Request failed: {}", e))
|
||||||
Ok::<_, anyhow::Error>(HttpResponse::new(status, headers, body.into()))
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => res?,
|
|
||||||
Err(_) => {
|
|
||||||
self.record_connection_error(&format!("Request interrupted: {}", url))
|
|
||||||
.await;
|
.await;
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!("Request failed: {}", e));
|
||||||
"Request interrupted after {}s",
|
|
||||||
timeout_duration.as_secs()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(response)
|
let status = response.status();
|
||||||
|
let headers = response.headers().clone();
|
||||||
|
let body = match response.text().await {
|
||||||
|
Ok(text) => text.into(),
|
||||||
|
Err(e) => {
|
||||||
|
self.record_connection_error(&format!("Failed to read response body: {}", e))
|
||||||
|
.await;
|
||||||
|
return Err(anyhow::anyhow!("Failed to read response body: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::new(status, headers, body))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ pub fn resolve_setup_async() {
|
|||||||
init_service_manager().await;
|
init_service_manager().await;
|
||||||
init_core_manager().await;
|
init_core_manager().await;
|
||||||
init_system_proxy().await;
|
init_system_proxy().await;
|
||||||
AsyncHandler::spawn_blocking(init_system_proxy_guard);
|
init_system_proxy_guard().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
let tray_init = async {
|
let tray_init = async {
|
||||||
@ -148,7 +148,7 @@ pub(super) async fn init_verge_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn init_service_manager() {
|
pub(super) async fn init_service_manager() {
|
||||||
clash_verge_service_ipc::set_config(ServiceManager::config()).await;
|
clash_verge_service_ipc::set_config(Some(ServiceManager::config())).await;
|
||||||
if !is_service_ipc_path_exists() {
|
if !is_service_ipc_path_exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -168,8 +168,8 @@ pub(super) async fn init_system_proxy() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn init_system_proxy_guard() {
|
pub(super) async fn init_system_proxy_guard() {
|
||||||
logging_error!(Type::Setup, sysopt::Sysopt::global().init_guard_sysproxy());
|
sysopt::Sysopt::global().refresh_guard().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn refresh_tray_menu() {
|
pub(super) async fn refresh_tray_menu() {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ pub fn embed_server() {
|
|||||||
.expect("failed to set shutdown signal for embedded server");
|
.expect("failed to set shutdown signal for embedded server");
|
||||||
let port = IVerge::get_singleton_port();
|
let port = IVerge::get_singleton_port();
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
let pac = warp::path!("commands" / "pac").and_then(|| async move {
|
||||||
let verge_config = Config::verge().await;
|
let verge_config = Config::verge().await;
|
||||||
let clash_config = Config::clash().await;
|
let clash_config = Config::clash().await;
|
||||||
|
|
||||||
@ -35,15 +35,16 @@ pub fn embed_server() {
|
|||||||
.data_arc()
|
.data_arc()
|
||||||
.verge_mixed_port
|
.verge_mixed_port
|
||||||
.unwrap_or_else(|| clash_config.data_arc().get_mixed_port());
|
.unwrap_or_else(|| clash_config.data_arc().get_mixed_port());
|
||||||
|
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));
|
||||||
let pac = warp::path!("commands" / "pac").map(move || {
|
Ok::<_, warp::Rejection>(
|
||||||
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));
|
|
||||||
warp::http::Response::builder()
|
warp::http::Response::builder()
|
||||||
.header("Content-Type", "application/x-ns-proxy-autoconfig")
|
.header("Content-Type", "application/x-ns-proxy-autoconfig")
|
||||||
.body(processed_content)
|
.body(processed_content)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default(),
|
||||||
});
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncHandler::spawn(move || async move {
|
||||||
warp::serve(pac)
|
warp::serve(pac)
|
||||||
.bind(([127, 0, 0, 1], port))
|
.bind(([127, 0, 0, 1], port))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -28,7 +28,8 @@
|
|||||||
"x": 200,
|
"x": 200,
|
||||||
"y": 180
|
"y": 180
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"infoPlist": "packages/macos/info_merge.plist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -795,32 +795,43 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
// Canvas尺寸设置
|
// Compute CSS size and pixel buffer size.
|
||||||
|
// Note: WebView2 on Windows may return fractional CSS sizes after maximize.
|
||||||
|
// We round pixel buffer to integers to avoid 1px gaps/cropping artifacts.
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
const width = rect.width;
|
const cssWidth = rect.width;
|
||||||
const height = rect.height;
|
const cssHeight = rect.height;
|
||||||
|
const pixelWidth = Math.max(1, Math.floor(cssWidth * dpr));
|
||||||
|
const pixelHeight = Math.max(1, Math.floor(cssHeight * dpr));
|
||||||
|
|
||||||
// 只在尺寸变化时重新设置Canvas
|
// Keep CSS-driven sizing so the canvas stretches with its container (e.g., on maximize).
|
||||||
if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
|
if (canvas.style.width !== "100%") {
|
||||||
canvas.width = width * dpr;
|
canvas.style.width = "100%";
|
||||||
canvas.height = height * dpr;
|
}
|
||||||
ctx.scale(dpr, dpr);
|
if (canvas.style.height !== "100%") {
|
||||||
canvas.style.width = width + "px";
|
canvas.style.height = "100%";
|
||||||
canvas.style.height = height + "px";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空画布
|
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
|
||||||
ctx.clearRect(0, 0, width, height);
|
canvas.width = pixelWidth;
|
||||||
|
canvas.height = pixelHeight;
|
||||||
|
// Reset transform before scaling to avoid cumulative scaling offsets.
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
ctx.scale(dpr, dpr); // map CSS units to device pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear using CSS dimensions; context is already scaled by DPR.
|
||||||
|
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
||||||
|
|
||||||
// 绘制Y轴刻度线(背景层)
|
// 绘制Y轴刻度线(背景层)
|
||||||
drawYAxis(ctx, width, height, displayData);
|
drawYAxis(ctx, cssWidth, cssHeight, displayData);
|
||||||
|
|
||||||
// 绘制网格
|
// 绘制网格
|
||||||
drawGrid(ctx, width, height);
|
drawGrid(ctx, cssWidth, cssHeight);
|
||||||
|
|
||||||
// 绘制时间轴
|
// 绘制时间轴
|
||||||
drawTimeAxis(ctx, width, height, displayData);
|
drawTimeAxis(ctx, cssWidth, cssHeight, displayData);
|
||||||
|
|
||||||
// 提取流量数据
|
// 提取流量数据
|
||||||
const upValues = displayData.map((d) => d.up);
|
const upValues = displayData.map((d) => d.up);
|
||||||
@ -830,8 +841,8 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
drawTrafficLine(
|
drawTrafficLine(
|
||||||
ctx,
|
ctx,
|
||||||
downValues,
|
downValues,
|
||||||
width,
|
cssWidth,
|
||||||
height,
|
cssHeight,
|
||||||
colors.down,
|
colors.down,
|
||||||
true,
|
true,
|
||||||
displayData,
|
displayData,
|
||||||
@ -841,8 +852,8 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
drawTrafficLine(
|
drawTrafficLine(
|
||||||
ctx,
|
ctx,
|
||||||
upValues,
|
upValues,
|
||||||
width,
|
cssWidth,
|
||||||
height,
|
cssHeight,
|
||||||
colors.up,
|
colors.up,
|
||||||
true,
|
true,
|
||||||
displayData,
|
displayData,
|
||||||
@ -851,7 +862,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
// 绘制悬浮高亮线
|
// 绘制悬浮高亮线
|
||||||
if (tooltipData.visible && tooltipData.dataIndex >= 0) {
|
if (tooltipData.visible && tooltipData.dataIndex >= 0) {
|
||||||
const padding = GRAPH_CONFIG.padding;
|
const padding = GRAPH_CONFIG.padding;
|
||||||
const effectiveWidth = width - padding.left - padding.right;
|
const effectiveWidth = cssWidth - padding.left - padding.right;
|
||||||
const dataX =
|
const dataX =
|
||||||
padding.left +
|
padding.left +
|
||||||
(tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
|
(tooltipData.dataIndex / (displayData.length - 1)) * effectiveWidth;
|
||||||
@ -865,13 +876,13 @@ export const EnhancedCanvasTrafficGraph = memo(
|
|||||||
// 绘制垂直指示线
|
// 绘制垂直指示线
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(dataX, padding.top);
|
ctx.moveTo(dataX, padding.top);
|
||||||
ctx.lineTo(dataX, height - padding.bottom);
|
ctx.lineTo(dataX, cssHeight - padding.bottom);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 绘制水平指示线(高亮Y轴位置)
|
// 绘制水平指示线(高亮Y轴位置)
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(padding.left, tooltipData.highlightY);
|
ctx.moveTo(padding.left, tooltipData.highlightY);
|
||||||
ctx.lineTo(width - padding.right, tooltipData.highlightY);
|
ctx.lineTo(cssWidth - padding.right, tooltipData.highlightY);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|||||||
@ -47,6 +47,7 @@ import { RuleItem } from "@/components/profile/rule-item";
|
|||||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
|
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
|
||||||
import { BaseSearchBox } from "../base/base-search-box";
|
import { BaseSearchBox } from "../base/base-search-box";
|
||||||
@ -249,13 +250,14 @@ const RULE_TYPE_LABEL_KEYS: Record<string, string> = Object.fromEntries(
|
|||||||
|
|
||||||
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
|
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
|
||||||
|
|
||||||
const PROXY_POLICY_LABEL_KEYS: Record<string, string> =
|
const PROXY_POLICY_LABEL_KEYS: Record<string, TranslationKey> =
|
||||||
builtinProxyPolicies.reduce(
|
builtinProxyPolicies.reduce(
|
||||||
(acc, policy) => {
|
(acc, policy) => {
|
||||||
acc[policy] = `proxy.policies.${policy}`;
|
acc[policy] =
|
||||||
|
`proxies.components.enums.policies.${policy}` as TranslationKey;
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{} as Record<string, string>,
|
{} as Record<string, TranslationKey>,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RulesEditorViewer = (props: Props) => {
|
export const RulesEditorViewer = (props: Props) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user