├── .github
├── ISSUE_TEMPLATE
│ ├── 1_BUG_REPORT.md
│ ├── 2_STYLE_ISSUE.md
│ ├── 3_CONFIG_ISSUES.md
│ ├── 4_FEATURE_REQUEST.md
│ └── 5_OTHERS.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── build-and-release.yml
│ ├── npm-run-electron.yml
│ └── test-and-upload.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── _script
├── 0、updateDependencies.bat
├── 1、setupEnv.bat
├── 2、installProject.bat
├── 3、buildAndRun.bat
├── 4.1、runTestCore.bat
├── 4.2、runTestMitmproxy.bat
└── 5、generateSetupFile.bat
├── doc
├── Firefox
│ ├── 1.png
│ ├── 2.png
│ └── 3.png
├── avatar1.png
├── avatar2.png
├── caroot.md
├── chatgpt2.png
├── clone-before.png
├── clone.png
├── crt-error.png
├── download-before.png
├── download.png
├── flow.jpg
├── gitee.png
├── index.png
├── linux.md
├── log.png
├── mac-proxy.png
├── me.png
├── other.md
├── proxy.png
├── qq_group.png
├── recover.md
├── setup.png
├── speed.png
└── wiki
│ ├── Home.md
│ ├── 加速服务使用说明.md
│ ├── 各平台安装说明.md
│ └── 解决Github访问不了或速度很慢的问题.md
├── eslint.config.js
├── package.json
├── packages
├── cli
│ ├── LICENSE
│ ├── cli.js
│ ├── package.json
│ └── src
│ │ ├── banner.txt
│ │ ├── index.js
│ │ ├── mitmproxy.js
│ │ └── user_config.json5
├── core
│ ├── LICENSE
│ ├── index.js
│ ├── package.json
│ ├── src
│ │ ├── config-api.js
│ │ ├── config
│ │ │ ├── index.js
│ │ │ ├── local-config-loader.js
│ │ │ └── remote_config.json5
│ │ ├── event.js
│ │ ├── expose.js
│ │ ├── index.js
│ │ ├── merge.js
│ │ ├── modules
│ │ │ ├── index.js
│ │ │ ├── plugin
│ │ │ │ ├── git
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── index.js
│ │ │ │ ├── node
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── overwall
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── index.js
│ │ │ │ └── pip
│ │ │ │ │ ├── config.js
│ │ │ │ │ └── index.js
│ │ │ ├── proxy
│ │ │ │ └── index.js
│ │ │ └── server
│ │ │ │ └── index.js
│ │ ├── shell
│ │ │ ├── index.js
│ │ │ ├── scripts
│ │ │ │ ├── enable-loopback.js
│ │ │ │ ├── extra-path
│ │ │ │ │ ├── EnableLoopback.exe
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── sysproxy.exe
│ │ │ │ ├── get-npm-env.js
│ │ │ │ ├── get-system-env.js
│ │ │ │ ├── kill-by-port.js
│ │ │ │ ├── set-npm-env.js
│ │ │ │ ├── set-system-env.js
│ │ │ │ ├── set-system-proxy
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── refresh-internet.js
│ │ │ │ └── setup-ca.js
│ │ │ └── shell.js
│ │ ├── status.js
│ │ └── utils
│ │ │ ├── util.date.js
│ │ │ ├── util.log-or-console.js
│ │ │ ├── util.log.core.js
│ │ │ ├── util.logger.js
│ │ │ └── util.version.js
│ └── test
│ │ ├── configTest.js
│ │ ├── httpsVerifyTest.js
│ │ ├── macProxyTest.js
│ │ ├── mergeTest.js
│ │ ├── regex.test.js
│ │ ├── requestTest.js
│ │ └── versionTest.js
├── gui
│ ├── .editorconfig
│ ├── .env
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── babel.config.js
│ ├── build
│ │ ├── icons
│ │ │ ├── 0x0.png
│ │ │ ├── 1024x1024.png
│ │ │ ├── 128x128.png
│ │ │ ├── 16x16.png
│ │ │ ├── 24x24.png
│ │ │ ├── 256x256.png
│ │ │ ├── 32x32.png
│ │ │ ├── 32x32@2x.png
│ │ │ ├── 48x48.png
│ │ │ ├── 512x512.png
│ │ │ ├── 64x64.png
│ │ │ ├── icon.icns
│ │ │ └── icon.ico
│ │ └── mac
│ │ │ ├── 0x0.png
│ │ │ ├── 1024x1024.png
│ │ │ ├── 128x128.png
│ │ │ ├── 16x16.png
│ │ │ ├── 24x24.png
│ │ │ ├── 256x256.png
│ │ │ ├── 32x32.png
│ │ │ ├── 48x48.png
│ │ │ ├── 512x512.png
│ │ │ ├── 64x64.png
│ │ │ ├── icon.icns
│ │ │ └── icon.ico
│ ├── extra
│ │ ├── EnableLoopback.exe
│ │ ├── favicon.ico
│ │ ├── icons
│ │ │ ├── 1024x1024.png
│ │ │ ├── 128x128.png
│ │ │ ├── 16x16-black.png
│ │ │ ├── 16x16.png
│ │ │ ├── 24x24.png
│ │ │ ├── 256x256.png
│ │ │ ├── 32x32.png
│ │ │ ├── 48x48.png
│ │ │ ├── 512x512.png
│ │ │ ├── 64x64.png
│ │ │ ├── icon.icns
│ │ │ ├── icon.ico
│ │ │ ├── tray-icon.png
│ │ │ └── tray
│ │ │ │ ├── icon-black.png
│ │ │ │ ├── icon-black@2x.png
│ │ │ │ ├── icon-black@3x.png
│ │ │ │ ├── icon-white.png
│ │ │ │ ├── icon-white@2x.png
│ │ │ │ ├── icon-white@3x.png
│ │ │ │ ├── icon.png
│ │ │ │ ├── icon@2x.png
│ │ │ │ └── icon@3x.png
│ │ ├── pac
│ │ │ └── pac.txt
│ │ ├── proxy
│ │ │ └── domestic-domain-allowlist.txt
│ │ ├── scripts
│ │ │ ├── github.script
│ │ │ ├── google.js
│ │ │ └── tampermonkey.script
│ │ └── sysproxy.exe
│ ├── package.json
│ ├── pkg
│ │ ├── after-all-artifact-build.js
│ │ └── after-pack.js
│ ├── public
│ │ ├── 256x256.png
│ │ ├── favicon.ico
│ │ ├── icon-1.png
│ │ ├── icon.png
│ │ ├── icon2.png
│ │ ├── index.html
│ │ ├── loading-spin.svg
│ │ ├── logo
│ │ │ ├── logo-1.svg
│ │ │ ├── logo-fff.svg
│ │ │ ├── logo-lang-light.svg
│ │ │ ├── logo-lang.svg
│ │ │ ├── logo-only.png
│ │ │ ├── logo-only.svg
│ │ │ ├── logo-only2.png
│ │ │ ├── logo-only4.png
│ │ │ ├── logo-simple-fan.svg
│ │ │ ├── logo-simple.svg
│ │ │ ├── logo-simple2.png
│ │ │ ├── logo-text.svg
│ │ │ ├── logo.svg
│ │ │ ├── mac.png
│ │ │ ├── mac.svg
│ │ │ ├── win-16.svg
│ │ │ ├── win-black.png
│ │ │ ├── win.png
│ │ │ └── win.svg
│ │ ├── loopback.png
│ │ ├── setup-linux.png
│ │ ├── setup-mac.png
│ │ └── setup.png
│ ├── src
│ │ ├── background.js
│ │ ├── background
│ │ │ └── powerMonitor.js
│ │ ├── bridge
│ │ │ ├── api
│ │ │ │ ├── backend.js
│ │ │ │ └── open-enable-loopback.js
│ │ │ ├── auto-start
│ │ │ │ ├── backend.js
│ │ │ │ └── front.js
│ │ │ ├── backend.js
│ │ │ ├── error
│ │ │ │ └── front.js
│ │ │ ├── file-selector
│ │ │ │ ├── backend.js
│ │ │ │ └── front.js
│ │ │ ├── front.js
│ │ │ ├── mitmproxy.js
│ │ │ ├── on-close
│ │ │ │ └── front.js
│ │ │ ├── tongji
│ │ │ │ ├── backend.js
│ │ │ │ └── front.js
│ │ │ └── update
│ │ │ │ ├── backend.js
│ │ │ │ └── front.js
│ │ ├── main.js
│ │ ├── utils
│ │ │ ├── util.apppath.js
│ │ │ └── util.log.gui.js
│ │ └── view
│ │ │ ├── App.vue
│ │ │ ├── api.js
│ │ │ ├── components
│ │ │ ├── container.vue
│ │ │ ├── mock-input.vue
│ │ │ ├── setup-ca.vue
│ │ │ └── tree-node.vue
│ │ │ ├── composables
│ │ │ └── theme.js
│ │ │ ├── index.js
│ │ │ ├── mixins
│ │ │ └── plugin.js
│ │ │ ├── pages
│ │ │ ├── help.vue
│ │ │ ├── index.vue
│ │ │ ├── plugin
│ │ │ │ ├── git.vue
│ │ │ │ ├── node.vue
│ │ │ │ ├── overwall.vue
│ │ │ │ └── pip.vue
│ │ │ ├── proxy.vue
│ │ │ ├── server.vue
│ │ │ └── setting.vue
│ │ │ ├── router
│ │ │ ├── index.js
│ │ │ └── menu.js
│ │ │ ├── status.js
│ │ │ └── style
│ │ │ ├── index.scss
│ │ │ └── theme
│ │ │ └── dark.scss
│ └── vue.config.js
└── mitmproxy
│ ├── LICENSE
│ ├── index.js
│ ├── package.json
│ ├── src
│ ├── index.js
│ ├── json.js
│ ├── lib
│ │ ├── choice
│ │ │ ├── RequestCounter.js
│ │ │ └── index.js
│ │ ├── dns
│ │ │ ├── base.js
│ │ │ ├── https.js
│ │ │ ├── index.js
│ │ │ ├── preset.js
│ │ │ ├── tcp.js
│ │ │ ├── tls.js
│ │ │ └── udp.js
│ │ ├── interceptor
│ │ │ ├── impl
│ │ │ │ ├── req
│ │ │ │ │ ├── OPTIONS.js
│ │ │ │ │ ├── abort.js
│ │ │ │ │ ├── baiduOcr.js
│ │ │ │ │ ├── cacheRequest.js
│ │ │ │ │ ├── proxy.js
│ │ │ │ │ ├── redirect.js
│ │ │ │ │ ├── requestReplace.js
│ │ │ │ │ ├── sni.js
│ │ │ │ │ ├── success.js
│ │ │ │ │ └── unVerifySsl.js
│ │ │ │ └── res
│ │ │ │ │ ├── AfterOPTIONSHeaders.js
│ │ │ │ │ ├── cacheResponse.js
│ │ │ │ │ ├── responseReplace.js
│ │ │ │ │ └── script.js
│ │ │ └── index.js
│ │ ├── monkey
│ │ │ └── index.js
│ │ ├── proxy
│ │ │ ├── common
│ │ │ │ ├── ProxyHttpAgent.js
│ │ │ │ ├── ProxyHttpsAgent.js
│ │ │ │ ├── config.js
│ │ │ │ └── util.js
│ │ │ ├── compatible
│ │ │ │ └── compatible.js
│ │ │ ├── index.js
│ │ │ ├── middleware
│ │ │ │ ├── InsertScriptMiddleware.js
│ │ │ │ ├── overwall.js
│ │ │ │ └── source
│ │ │ │ │ └── pac.js
│ │ │ ├── mitmproxy
│ │ │ │ ├── createConnectHandler.js
│ │ │ │ ├── createFakeServerCenter.js
│ │ │ │ ├── createRequestHandler.js
│ │ │ │ ├── createUpgradeHandler.js
│ │ │ │ ├── dnsLookup.js
│ │ │ │ └── index.js
│ │ │ └── tls
│ │ │ │ ├── CertAndKeyContainer.js
│ │ │ │ ├── FakeServersCenter.js
│ │ │ │ ├── sniUtil.js
│ │ │ │ └── tlsUtils.js
│ │ └── speed
│ │ │ ├── SpeedTester.js
│ │ │ ├── config.js
│ │ │ └── index.js
│ ├── options.js
│ └── utils
│ │ ├── util.js
│ │ ├── util.log.server.js
│ │ ├── util.match.js
│ │ └── util.process.js
│ └── test
│ ├── baiduOcrTest.js
│ ├── dnsSpeedTest.js
│ ├── dnsTest.mjs
│ ├── lodashTest.js
│ ├── matchTest.js
│ ├── matchUtilTest.js
│ ├── monkeyTest.js
│ ├── pacTest.js
│ ├── proxyTest.js
│ ├── responseReplaceTest.js
│ ├── sha256Test.js
│ └── utilTest.js
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── test
├── test.js
└── testDns.js
/.github/ISSUE_TEMPLATE/1_BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 问题上报
3 | about: 如果你在使用过程中发现问题,请使用此模板。
4 | labels: Bug
5 | ---
6 |
7 |
8 |
9 | - [ ] 你是否在现有 [Issue列表](/docmirror/dev-sidecar/issues) 中搜索过相同问题,但未找到?
10 |
11 | ### Ⅰ. 请说明操作系统及DS的版本号:
12 |
13 | 1. 操作系统:?
14 | 2. DS版本号:?
15 |
16 | ### Ⅱ. 问题描述:
17 |
18 | ### Ⅲ. 期望的结果:
19 |
20 | ### Ⅳ. 如何复现问题?
21 |
22 | 1. xxx
23 | 2. xxx
24 | 3. xxx
25 |
26 | ### Ⅴ. 请提供相关的错误日志,尽可能的详细:(日志文件在 `${user.home}/.dev-sidecar/logs/` 目录下)
27 |
28 |
29 | 点击查看日志
30 |
31 | ```log
32 |
33 | ```
34 |
35 |
36 | ### Ⅵ. 有必要时,请提供 `${user.home}/.dev-sidecar/running.json` 文件内容:
37 |
38 |
39 |
40 |
41 | 点击查看运行参数
42 |
43 | ```json
44 |
45 | ```
46 |
47 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2_STYLE_ISSUE.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 样式问题
3 | about: 如果你发现了一些页面样式问题,请使用此模板。
4 | labels: Style Issue
5 | ---
6 |
7 |
8 |
9 | - [ ] 你是否在现有 [Issue列表](/docmirror/dev-sidecar/issues) 中搜索过相同问题,但未找到?
10 |
11 | ### Ⅰ. 请说明操作系统及DS的版本号:
12 |
13 | 1. 操作系统:?
14 | 2. DS版本号:?
15 |
16 | ### Ⅱ. 样式问题描述:
17 |
18 | ### Ⅲ. 样式问题截图:
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3_CONFIG_ISSUES.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 配置问题
3 | about: 如果你不知道如何配置DS来访问某个网站,请使用这个模板。
4 | labels: Config Issue
5 | ---
6 |
7 | ### Ⅰ. 你对哪个功能的配置不了解?
8 |
9 |
10 |
11 | - [ ] 拦截设置:
12 | - [ ] redirect
13 | - [ ] proxy
14 | - [ ] sni
15 | - [ ] success
16 | - [ ] abort
17 | - [ ] cache
18 | - [ ] options
19 | - [ ] script
20 | - [ ] requestReplace
21 | - [ ] responseReplace
22 | - [ ] DNS设置和IP测速
23 | - [ ] 系统代理
24 | - [ ] 远程配置
25 | - [ ] 应用:
26 | - [ ] NPM加速
27 | - [ ] Git代理
28 | - [ ] PIP加速
29 | - [ ] 增强功能
30 |
31 | ### Ⅱ. 请详细描述你的问题:
32 |
33 | ### Ⅲ. 有必要时,请提供 `${user.home}/.dev-sidecar/running.json` 文件内容:
34 |
35 |
36 |
37 |
38 | 点击查看运行参数
39 |
40 | ```json
41 |
42 | ```
43 |
44 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/4_FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 提新需求
3 | about: 如果你想提出一个新需求,请使用此模板。
4 | labels: Feature Request
5 | ---
6 |
7 | ### Ⅰ. 请描述你想要的新功能:
8 |
9 |
10 |
11 | ### Ⅱ. 请描述你心目中新功能的样子:
12 |
13 |
14 |
15 | ### Ⅲ. 你希望该新功能修复哪个issue?
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/5_OTHERS.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 其他问题
3 | about: 如果不是以上问题,请使用此模板。
4 | ---
5 |
6 | ### 请详细描述你的问题、需求或建议:
7 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Ⅰ. 描述此PR的作用:
2 |
3 | ### Ⅱ. 此PR修复了哪个issue吗?
4 |
5 |
6 |
7 | ### Ⅲ. 界面变化截屏
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ project files
2 | .idea
3 | *.iml
4 |
5 | # vscode settings files
6 | .vscode
7 |
8 | # Mac
9 | .DS_Store
10 |
11 | # Node files
12 | node_modules/
13 | *.lock
14 | package-lock.json
15 |
16 | # Other files
17 | out
18 | gen
19 | *.log
20 | *.lnk
21 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 |
--------------------------------------------------------------------------------
/_script/0、updateDependencies.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | # 安装ncu
4 | # npm install -g npm-check-updates
5 |
6 | cd ../packages/core
7 | ncu -u
8 |
9 | # cd ../packages/gui
10 | # ncu -u
11 |
12 | # cd ../packages/mitmproxy
13 | # ncu -u
14 |
--------------------------------------------------------------------------------
/_script/1、setupEnv.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | cd ../
4 | npm install -g pnpm --registry=https://registry.npmmirror.com
5 |
--------------------------------------------------------------------------------
/_script/2、installProject.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | cd ../
4 | chcp 65001
5 | pnpm install
6 |
--------------------------------------------------------------------------------
/_script/3、buildAndRun.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | cd ../packages/gui
4 | chcp 65001
5 | npm run electron
6 |
--------------------------------------------------------------------------------
/_script/4.1、runTestCore.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | cd ../packages/core
4 | pnpm run test
5 |
--------------------------------------------------------------------------------
/_script/4.2、runTestMitmproxy.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | cd ../packages/mitmproxy
4 | pnpm run test
5 |
--------------------------------------------------------------------------------
/_script/5、generateSetupFile.bat:
--------------------------------------------------------------------------------
1 | node -v
2 |
3 | cd ../packages/gui
4 |
5 | if not exist "dist_electron" mkdir "dist_electron"
6 | start dist_electron
7 |
8 | npm run electron:build
9 |
--------------------------------------------------------------------------------
/doc/Firefox/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/Firefox/1.png
--------------------------------------------------------------------------------
/doc/Firefox/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/Firefox/2.png
--------------------------------------------------------------------------------
/doc/Firefox/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/Firefox/3.png
--------------------------------------------------------------------------------
/doc/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/avatar1.png
--------------------------------------------------------------------------------
/doc/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/avatar2.png
--------------------------------------------------------------------------------
/doc/caroot.md:
--------------------------------------------------------------------------------
1 | # 关于信任根证书的说明
2 |
3 | ## 一、为什么要信任根证书。
4 |
5 | 要回答这个问题需要先掌握下面两个知识点
6 |
7 | ### 知识点1:什么是根证书
8 |
9 | [百度百科-什么是根证书](https://baike.baidu.com/item/%E6%A0%B9%E8%AF%81%E4%B9%A6/9874620?fr=aladdin)
10 |
11 | 当访问目标网站是https协议时,服务器会发送一个由根证书签发的网站ssl证书给浏览器,让浏览器用这个ssl证书给数据加密。
12 | 浏览器需要先验证这个证书的真伪,之后才会使用证书加密。
13 | 证书的真伪是通过验证证书的签发机构的证书是否可信,一直追溯到最初始的签发机构的证书(根证书)。
14 | 浏览器只需信任根证书,间接的就信任了这条证书链下签发的所有证书。
15 |
16 | windows、mac、linux或者浏览器他们都内置了市面上可信的大型证书颁发机构的根证书。
17 |
18 | ### 知识点2:中间人攻击
19 |
20 | 本应用的实现原理如下图:
21 |
22 | 
23 |
24 | > 简单来说就是DevSidecar在本地启动了一个代理服务器帮你访问目标网站。
25 | > 实际上就是 [中间人攻击](https://baike.baidu.com/item/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB/1739730?fr=aladdin) 的原理,只是本应用没有用它来干坏事,而是帮助开发者加速目标网站的访问。
26 |
27 | ### 现在可以回答为什么要信任根证书
28 |
29 | 当目标网站不需要加速拦截时,直接走TCP转发,不需要中间人攻击,没有安全风险,在此不多做讨论。
30 |
31 | 当目标网站需要拦截时(例如github),就需要通过中间人攻击修改请求或者请求其他替代网站,从而达到加速的目的。
32 |
33 | 例如加速github就需要修改如下几处
34 |
35 | 1. 直连访问github需要修改tls握手时的sni域名,规避\*\*\*的sni阻断问题。
36 | 2. asserts.github.com等静态资源拦截替换成fastgit.org的镜像地址
37 |
38 | DevSidecar在第一次启动时会在本地随机生成一份根证书,当有用户访问github时,就用这份根证书来签发一份假的叫github.com的证书。
39 | 如果浏览器事先信任了这份根证书,那么就可以正常访问DevSidecar返回的网页内容了。
40 |
41 | ## 二、信任根证书有安全风险吗
42 |
43 | 1. 根证书是DevSidecar第一次启动时本地随机生成的,除了你这台电脑没人知道这份根证书的内容。
44 | 2. 代理请求目标网站时会校验目标网站的证书(除非关闭了`代理校验ssl`)。
45 |
46 | > 两段链路都是安全的,所以信任根证书没有问题。
47 | > 但如果应用本身来源不明,或者`拦截配置`里的替代网站作恶,则有安全风险。
48 |
49 | > 对于应用来源风险:
50 | > 请勿从未知网站下载DevSidecar应用,认准官方版本发布地址
51 | > [Github Release](https://github.com/docmirror/dev-sidecar/releases)
52 | >
53 | > 或者从源码自行编译安装
54 |
55 | > 对于拦截配置里的替代网站风险:
56 | >
57 | > 1. 尽量缩小替代配置的范围
58 | > 2. 不使用来源不明的镜像地址,尽量使用知名度较高的镜像地址
59 | > 3. 你甚至可以将其他拦截配置全部删除,只保留github相关配置
60 |
--------------------------------------------------------------------------------
/doc/chatgpt2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/chatgpt2.png
--------------------------------------------------------------------------------
/doc/clone-before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/clone-before.png
--------------------------------------------------------------------------------
/doc/clone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/clone.png
--------------------------------------------------------------------------------
/doc/crt-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/crt-error.png
--------------------------------------------------------------------------------
/doc/download-before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/download-before.png
--------------------------------------------------------------------------------
/doc/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/download.png
--------------------------------------------------------------------------------
/doc/flow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/flow.jpg
--------------------------------------------------------------------------------
/doc/gitee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/gitee.png
--------------------------------------------------------------------------------
/doc/index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/index.png
--------------------------------------------------------------------------------
/doc/linux.md:
--------------------------------------------------------------------------------
1 | # Linux 支持
2 |
3 | `Linux`使用说明,目前仅官方支持`Ubuntu x86_64 GNOME桌面版(原版)`,其他`Linux`未测试
4 |
5 | > 注意:需要开启 [sudo 免密支持](https://www.jianshu.com/p/5d02428f313d),否则请自行安装证书
6 |
7 | ## 一、安装
8 |
9 | ### 1.1. Ubuntu / Debian或其衍生版(未测试)
10 |
11 | - 下载`DevSidecar-x.x.x.deb`
12 | - 使用 root 执行命令安装 `dpkg -i DevSidecar-x.x.x.deb`
13 | - 去应用列表里面找到 dev-sidecar 应用,打开即可
14 |
15 | ### 1.2. 其他基于glibc的Linux系统(未测试)
16 |
17 | - 下载 `DevSidecar-x.x.x.AppImage`
18 | - 设置可执行权限 `chmod +x DevSidecar-x.x.x.AppImage`
19 | - 双击运行
20 |
21 | ### 1.3. 特殊的Linux系统(如Alpine和Chimera Linux)
22 |
23 | > 此处默认用户有较专业的Linux知识,故不详细描述,请参考并自行试验
24 | - 创建Debian(最方便且省空间)容器,可使用distrobox(推荐),接下来以此为例说明
25 | - 下载deb包并在容器内安装
26 | - 穿透系统设置:
27 | 在容器内 `/usr/bin/gsettings` 文件写入:
28 |
29 | ```bash
30 | #!/bin/sh
31 | distrobox-host-exec gsettings "$@"
32 | ```
33 | 并设置可执行权限
34 |
35 | 简化版命令(请在容器内执行):
36 | ```
37 | echo -e '#!/bin/sh\ndistrobox-host-exec gsettings "$@"' >/usr/bin/gsettings
38 | ```
39 | - 使用命令启动应用,使用“自动安装证书”功能,回到终端,找到输出里含有 `sudo` 的两句命令,复制到主系统执行,如失败(或使用其他证书系统),请自行安装证书,可参考 [议题 #204](https://github.com/docmirror/dev-sidecar/issues/204)
40 |
41 | ### 1.4. 版本选择
42 |
43 | 不同CPU架构,选择对应的版本,如果安装失败,请下载 `universal` 版本
44 |
45 |
46 | ## 二、证书安装
47 |
48 | 默认模式和增强模式需要系统信任CA证书。
49 | 由于Linux上火狐和Chrome都不走系统证书,所以除了安装系统证书之外,还需要给浏览器安装证书
50 |
51 | ### 2.1. 系统证书安装
52 |
53 | 根据弹出的提示:
54 |
55 | - 点击首页右上角“安装根证书”按钮
56 | - 点击“点此去安装”
57 | - 提示安装成功即可
58 |
59 | ### 2.2. 火狐浏览器安装证书
60 |
61 | - 火狐浏览器->选项->隐私与安全->证书->查看证书
62 | - 证书颁发机构->导入
63 | - 选择证书文件在 `~/.dev-sidecar` 目录下
64 | - 勾选信任由此证书颁发机构来标识网站,确定即可
65 |
66 | ### 2.3. Chrome浏览器安装证书
67 |
68 | 证书文件目录为 `~/.dev-sidecar`
69 |
70 | 
71 |
--------------------------------------------------------------------------------
/doc/log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/log.png
--------------------------------------------------------------------------------
/doc/mac-proxy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/mac-proxy.png
--------------------------------------------------------------------------------
/doc/me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/me.png
--------------------------------------------------------------------------------
/doc/other.md:
--------------------------------------------------------------------------------
1 | # 其他程序使用
2 |
3 | ## Java程序使用
4 |
5 | > 由 [Enaium](https://github.com/Enaium) 提供,未做验证,可供参考
6 |
7 | 1. 先通过keytool安装证书:
8 |
9 | ```shell
10 | keytool -import -alias dev-sidecar -keystore "jdk路径\security\cacerts" -file 用户目录\.dev-sidecar\dev-sidecar.ca.crt
11 | ```
12 | 默认密码为 `changeit`
13 |
14 | 2. 启动时还需要设置参数,例:
15 |
16 | ```shell
17 | java -Dhttp.proxyHost=localhost -Dhttp.proxyPort=31181 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=31181 -jar xxxx.jar
18 | ```
19 |
20 | 3. Gradle还需在`用户目录/.gradle/gradle.properties`创建配置文件:
21 |
22 | ```properties
23 | systemProp.http.proxyHost=localhost
24 | systemProp.http.proxyPort=31181
25 | systemProp.https.proxyHost=localhost
26 | systemProp.https.proxyPort=31181
27 | ```
28 |
--------------------------------------------------------------------------------
/doc/proxy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/proxy.png
--------------------------------------------------------------------------------
/doc/qq_group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/qq_group.png
--------------------------------------------------------------------------------
/doc/recover.md:
--------------------------------------------------------------------------------
1 | # 卸载与恢复网络
2 |
3 | 由于应用启动后会自动设置系统代理,正常退出时会关闭系统代理。
4 | 当应用意外关闭,或者未正常退出后被卸载,此时会因为系统代理没有恢复从而导致完全上不了网。
5 | 目前electron在windows系统上无法监听系统重启事件。更多相关资料 [electron issues](https://github.com/electron/electron/pull/24261)
6 |
7 | ## 恢复代理设置
8 |
9 | ### 1、windows 代理关闭
10 |
11 | 如何打开查看windows代理设置:
12 |
13 | - win10: 开始->设置->网络和Internet->最下方代理
14 | - win7: 开始->控制面板->网络和Internet->网络和共享中心->左下角Internet选项->连接选项卡->局域网设置
15 |
16 | 
17 |
18 | ### 2、mac 代理关闭
19 |
20 | 网络->网卡->代理->去掉http和https的两个勾
21 |
22 | 
23 |
24 | ### 3、Linux(Ubuntu)
25 |
26 | 网络->代理->选择禁用
27 |
--------------------------------------------------------------------------------
/doc/setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/setup.png
--------------------------------------------------------------------------------
/doc/speed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/doc/speed.png
--------------------------------------------------------------------------------
/doc/wiki/Home.md:
--------------------------------------------------------------------------------
1 | > **给作者打个广告:**
2 | > [https://github.com/certd/certd](https://github.com/certd/certd) 我的开源证书管理工具项目,全自动申请和部署证书,有需求的可以去试试,帮忙点个star
3 |
4 | > 注:Wiki还在完善中,敬请期待更多内容。
5 | > 说明:以下文档均以最新版本进行编写,请下载最新版DS后,再参考以下文档使用!
6 |
7 | # 一、下载安装:
8 |
9 | 访问 https://github.com/docmirror/dev-sidecar/releases 页面,下载对应操作系统的安装程序进行安装。
10 |
11 | 如安装有问题,请查看 [各平台安装说明](https://github.com/docmirror/dev-sidecar/wiki/%E5%90%84%E5%B9%B3%E5%8F%B0%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E)
12 |
13 | # 二、功能使用说明:
14 |
15 | 1. [`加速服务`使用说明](https://github.com/docmirror/dev-sidecar/wiki/%E5%8A%A0%E9%80%9F%E6%9C%8D%E5%8A%A1%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
16 | 2. 系统代理使用说明:
17 | 3. 通用功能使用说明:
18 | 1. 开机自启动:
19 | 2. 远程配置:
20 | 3. 主题设置:
21 | 4. 窗口设置:
22 | 5. 检查更新:
23 | 4. 应用使用说明:
24 | 1. NPM加速:
25 | 2. Git.exe加速:
26 | 3. PIP加速:
27 | 4. 彩蛋(功能增强):
28 | 5. 帮助中心
29 | 6. 反馈问题
30 |
31 | # 三、解决问题:
32 |
33 | 1. [解决Github访问不了或速度很慢的问题](https://github.com/docmirror/dev-sidecar/wiki/%E8%A7%A3%E5%86%B3Github%E8%AE%BF%E9%97%AE%E4%B8%8D%E4%BA%86%E6%88%96%E9%80%9F%E5%BA%A6%E5%BE%88%E6%85%A2%E7%9A%84%E9%97%AE%E9%A2%98)
34 | 2. [Linux安装证书失败的避坑](https://github.com/docmirror/dev-sidecar/issues/238)
35 | 3. [解决Linux(deb)系统下无法安装根证书的问题](https://github.com/docmirror/dev-sidecar/issues/135)
36 | 4. [在Arch/Fedora下的证书安装](https://github.com/docmirror/dev-sidecar/issues/204)
37 | 5. [Mac安装:`无法打开“dev-sidecar”,因为无法验证开发者。` 的解决方案](https://github.com/docmirror/dev-sidecar/issues/147)
38 | 6. [在 WSL 中的使用方法](https://github.com/docmirror/dev-sidecar/issues/73)
39 |
40 | [> 点击前往Issue区查找更多帮助信息](https://github.com/docmirror/dev-sidecar/issues)
41 |
42 | # 四、DevSidecar技术交流群
43 |
44 | - QQ 1群:[390691483](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=hIG_VClE1CU2gHuLSSTaazMlo6M760iL&authKey=5VUMMwzH5FeabLDbZNZJbqmZk1gfmB%2B%2FlotO%2Brszz%2BW3E8xwKD2hTg2%2FV2LJEKL7&noverify=0&group_code=390691483),人数:496 / 500
45 | - QQ 2群:[667666069](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=n4nksr4sji93vZtD5e8YEHRT6qbh6VyQ&authKey=XKBZnzmoiJrAFyOT4V%2BCrgX5c13ds59b84g%2FVRhXAIQd%2FlAiilsuwDRGWJct%2B570&noverify=0&group_code=667666069),人数:488 / 500
46 | - QQ 3群:[419807815](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=zRkm0eHUhRmWWJA5O35C7BOKPZ4_gmrz&authKey=X9JHezR1BOalcEmvV6If04TN%2BIbzjAayBDaOSiuOg1SPpPguA7RqoLSHVEeo7A4e&noverify=0&group_code=419807815),人数:494 / 500
47 | - QQ 4群:[438148299](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=i_NCBB5f_Bkm2JsEV1tLs2TkQ79UlCID&authKey=nMsVJbJ6P%2FGNO7Q6vsVUadXRKnULUURwR8zvUZJnP3IgzhHYPhYdcBCHvoOh8vYr&noverify=0&group_code=438148299),人数:295 / 1000
48 | - QQ 5群:[767622917](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=nAWi_Rxj7mM4Unp5LMiatmUWhGimtbcB&authKey=aswmlWGjbt3GIWXtvjB2GJqqAKuv7hWjk6UBs3MTb%2Biyvr%2Fsbb1kA9CjF6sK7Hgg&noverify=0&group_code=767622917),人数:068 / 200(new)
49 |
50 | # 五、版本更新日志
51 |
52 | https://github.com/docmirror/dev-sidecar/releases
53 |
--------------------------------------------------------------------------------
/doc/wiki/各平台安装说明.md:
--------------------------------------------------------------------------------
1 | |平台|安装说明 |
2 | |---|---|
3 | | 【Windows】 | 下载后提示无法验证发行者时,选择保留即可
注意:开着ds重启电脑会导致无法上网,你可以再次打开ds,然后右键小图标退出ds即可。[更多说明](https://github.com/docmirror/dev-sidecar/issues/109)|
4 | | 【Mac】 |安装时提示无法验证开发者时,请先取消
然后去系统偏好设置->安全与隐私->下方已阻止使用DevSidecar
选择仍要打开 |
5 | | 【Ubuntu】 | [安装说明](https://github.com/docmirror/dev-sidecar/blob/master/doc/linux.md)|
6 | |【其他Linux】| |
--------------------------------------------------------------------------------
/doc/wiki/解决Github访问不了或速度很慢的问题.md:
--------------------------------------------------------------------------------
1 | > 注:请使用 `v2.0.0-RC2` 及以上版本,下载地址:https://github.com/docmirror/dev-sidecar/releases
2 |
3 | 目前,Github通过预设置的IP来访问的,选取测速排在前的IP。
4 |
5 | 可是,虽然IP测速延迟很低,但是依然会存在不同地区访问部分预设IP不通或很慢的问题。
6 |
7 | 如果碰到此问题,可以通过将预设IP设置为 `false` 来禁用访问慢的IP,以此达到切换IP的目的,如下图:
8 | 如果访问还慢,再将测速排在第1的IP再禁用掉,以此循环,将访问慢的IP都禁掉,直到选取到的IP访问Github速度很快为止。
9 |
10 | > 假如:测速排第1的IP为 `20.27.177.113`,则将其配置为 `false`,或者删除该IP
11 | 
12 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default antfu(
4 | {
5 | vue: {
6 | vueVersion: 2,
7 | },
8 | rules: {
9 | 'style/brace-style': ['error', '1tbs'],
10 | 'style/space-before-function-paren': ['error', 'always'],
11 | 'import/newline-after-import': 'off',
12 | 'import/first': 'off',
13 | 'perfectionist/sort-imports': 'off',
14 | 'node/prefer-global/buffer': 'off',
15 | 'node/prefer-global/process': 'off',
16 | 'no-console': 'off',
17 | },
18 | ignores: [
19 | '**/build/*',
20 | '**/dist_electron',
21 | ],
22 | formatters: {
23 | css: true,
24 | html: true,
25 | markdown: 'prettier',
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dev-sidecar-parent",
3 | "type": "module",
4 | "private": false,
5 | "packageManager": "pnpm@9.13.2",
6 | "author": "Greper",
7 | "license": "MPL-2.0",
8 | "scripts": {
9 | "lint": "eslint .",
10 | "lint:fix": "eslint . --fix"
11 | },
12 | "devDependencies": {
13 | "@antfu/eslint-config": "^3.9.1",
14 | "eslint": "^9.15.0",
15 | "eslint-plugin-format": "^0.1.2"
16 | },
17 | "pnpm": {
18 | "supportedArchitectures": {
19 | "os": ["current"],
20 | "cpu": ["x64", "arm64", "ia32"]
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cli/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('./src')
4 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@docmirror/dev-sidecar-cli",
3 | "version": "2.0.0",
4 | "private": false,
5 | "description": "给开发者的加速代理工具",
6 | "author": "docmirror.cn",
7 | "license": "MPL-2.0",
8 | "keywords": [
9 | "dev-sidecar",
10 | "github加速",
11 | "google加速",
12 | "代理"
13 | ],
14 | "main": "src/index.js",
15 | "bin": "./cli.js",
16 | "scripts": {
17 | "start": "node ./src"
18 | },
19 | "dependencies": {
20 | "@docmirror/dev-sidecar": "workspace:*",
21 | "@docmirror/mitmproxy": "workspace:*"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cli/src/banner.txt:
--------------------------------------------------------------------------------
1 | ____ _____ _ __
2 | / __ \___ _ __ / ___/(_)___/ /__ _________ ______
3 | / / / / _ \ | / /_____\__ \/ / __ / _ \/ ___/ __ `/ ___/
4 | / /_/ / __/ |/ /_____/__/ / / /_/ / __/ /__/ /_/ / /
5 | /_____/\___/|___/ /____/_/\__,_/\___/\___/\__,_/_/
6 |
7 |
8 | ==================== 开发者边车 ====================
9 |
--------------------------------------------------------------------------------
/packages/cli/src/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const DevSidecar = require('@docmirror/dev-sidecar')
3 | const jsonApi = require('@docmirror/mitmproxy/src/json')
4 |
5 | // 启动服务
6 | const mitmproxyPath = './mitmproxy'
7 | async function startup () {
8 | const banner = fs.readFileSync('./banner.txt')
9 | console.log(banner.toString())
10 |
11 | const configPath = './user_config.json5'
12 | if (fs.existsSync(configPath)) {
13 | const file = fs.readFileSync(configPath)
14 | let userConfig
15 | try {
16 | userConfig = jsonApi.parse(file.toString())
17 | console.info(`读取和解析 user_config.json5 成功:${configPath}`)
18 | } catch (e) {
19 | console.error(`读取或解析 user_config.json5 失败: ${configPath}, error:`, e)
20 | userConfig = {}
21 | }
22 | DevSidecar.api.config.set(userConfig)
23 | }
24 |
25 | await DevSidecar.api.startup({ mitmproxyPath })
26 | console.log('dev-sidecar 已启动')
27 | }
28 |
29 | async function onClose () {
30 | console.log('on sigint ')
31 | await DevSidecar.api.shutdown()
32 | console.log('on closed ')
33 | process.exit(0)
34 | }
35 | process.on('SIGINT', onClose)
36 |
37 | startup()
38 |
--------------------------------------------------------------------------------
/packages/cli/src/mitmproxy.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const path = require('node:path')
3 | const server = require('@docmirror/mitmproxy')
4 | const jsonApi = require('@docmirror/mitmproxy/src/json')
5 | const log = require('@docmirror/mitmproxy/src/utils/util.log.server') // 当前脚本是在 server 的进程中执行的,所以使用 mitmproxy 中的logger
6 |
7 | const home = process.env.USER_HOME || process.env.HOME || 'C:/Users/Administrator/'
8 |
9 | let configPath
10 | if (process.argv && process.argv.length > 3) {
11 | configPath = process.argv[2]
12 | } else {
13 | configPath = path.join(home, '.dev-sidecar/running.json')
14 | }
15 |
16 | const configJson = fs.readFileSync(configPath)
17 | log.info('读取 running.json by cli 成功:', configPath)
18 | let config
19 | try {
20 | config = jsonApi.parse(configJson.toString())
21 | } catch (e) {
22 | log.error(`running.json 文件内容格式不正确,文件路径:${configPath},文件内容: ${configJson.toString()}, error:`, e)
23 | config = {}
24 | }
25 | // const scriptDir = '../../gui/extra/scripts/'
26 | // config.setting.script.defaultDir = path.join(__dirname, scriptDir)
27 | // const pacFilePath = '../../gui/extra/pac/pac.txt'
28 | // config.plugin.overwall.pac.customPacFilePath = path.join(__dirname, pacFilePath)
29 | config.setting.rootDir = path.join(__dirname, '../../gui/')
30 | log.info(`start mitmproxy by cli, configPath: ${configPath}`)
31 | server.start(config)
32 |
--------------------------------------------------------------------------------
/packages/cli/src/user_config.json5:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "autoStart": {
4 | "enabled": true
5 | },
6 | "mode": "default"
7 | },
8 | "plugin": {
9 | "node": {
10 | "setting": {
11 | "yarnRegistry": "null"
12 | }
13 | },
14 | "git": {
15 | "enabled": true
16 | },
17 | "overwall": {
18 | "enabled": false,
19 | "targets": {
20 | "*gagedigital.com": true,
21 | "*yonsz.net": true,
22 | "*bootstrapcdn.com": true,
23 | "*cloudflare.com": true,
24 | "help.yonsz.net": true
25 | }
26 | }
27 | },
28 | "server": {
29 | "intercepts": {
30 | "dev-sidecar.docmirror.cn": {
31 | ".*": {
32 | "proxy": "dev-sidecar-preview.docmirror.cn"
33 | }
34 | },
35 | "test1111.gagedigital.com": {
36 | ".*": {
37 | "proxy": "test1.gagedigital.com"
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/core/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src')
2 |
3 | // TODO 这是一个解谜游戏 ↓ ↓ ↓ ↓ ↓ ↓ ,如果你破解了它,请不要公开,好好用它来学习和查资料吧(特别注意:为了你的人身安全,请不要用它来查看和发表不当言论,你懂得)。
4 | /**
5 | \u0061\u0048\u0052\u0030\u0063\u0044\u006f\u0076\u004c\u0032\u0052\u006c\u0064\u0069\u0031\u007a\u0061\u0057\u0052\u006c\u0059\u0032\u0046\u0079\u004c\u006d\u0052\u0076\u0059\u0032\u0031\u0070\u0063\u006e\u004a\u0076\u0063\u0069\u0035\u006a\u0062\u0069\u0039\u0035\u0062\u0033\u0056\u006d\u0061\u0057\u0035\u006b\u0061\u0058\u0051\u0076\u0061\u0057\u0035\u006b\u005a\u0058\u0067\u0075\u0061\u0048\u0052\u0074\u0062\u0041\u003d\u003d
6 | */
7 | // 这个项目里有一点点解谜提示: https://github.com/fast-crud/fast-crud (打开拉到最下面)
8 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@docmirror/dev-sidecar",
3 | "version": "2.0.0",
4 | "private": false,
5 | "description": "给开发者的加速代理工具",
6 | "author": "docmirror.cn",
7 | "license": "MPL-2.0",
8 | "keywords": [
9 | "dev-sidecar",
10 | "github加速",
11 | "google加速",
12 | "代理"
13 | ],
14 | "main": "src/index.js",
15 | "scripts": {
16 | "test": "mocha"
17 | },
18 | "dependencies": {
19 | "@starknt/sysproxy": "^0.0.3",
20 | "@vscode/sudo-prompt": "^9.3.1",
21 | "fix-path": "^3.0.0",
22 | "iconv-lite": "^0.6.3",
23 | "lodash": "^4.17.21",
24 | "log4js": "^6.9.1",
25 | "node-powershell": "^4.0.0",
26 | "spawn-sync": "^2.0.0",
27 | "winreg": "^1.2.5"
28 | },
29 | "devDependencies": {
30 | "chai": "^4.3.4",
31 | "mocha": "^8.2.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/core/src/config/local-config-loader.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const path = require('node:path')
3 | const lodash = require('lodash')
4 | const jsonApi = require('@docmirror/mitmproxy/src/json')
5 | const mergeApi = require('../merge')
6 | const logOrConsole = require('../utils/util.log-or-console')
7 |
8 | function getUserBasePath (autoCreate = true) {
9 | const userHome = process.env.USERPROFILE || process.env.HOME || '/'
10 | const dir = path.resolve(userHome, './.dev-sidecar')
11 |
12 | // 自动创建目录
13 | if (autoCreate && !fs.existsSync(dir)) {
14 | fs.mkdirSync(dir)
15 | }
16 |
17 | return dir
18 | }
19 |
20 | function loadConfigFromFile (configFilePath) {
21 | if (configFilePath == null) {
22 | logOrConsole.error('配置文件地址为空')
23 | return {}
24 | }
25 |
26 | if (!fs.existsSync(configFilePath)) {
27 | logOrConsole.info('配置文件不存在:', configFilePath)
28 | return {} // 文件不存在,返回空配置
29 | }
30 |
31 | // 读取配置文件
32 | let configStr
33 | try {
34 | configStr = fs.readFileSync(configFilePath)
35 | } catch (e) {
36 | logOrConsole.error('读取配置文件失败:', configFilePath, ', error:', e)
37 | return {}
38 | }
39 |
40 | // 解析配置文件
41 | try {
42 | const config = jsonApi.parse(configStr)
43 | logOrConsole.info('读取配置文件成功:', configFilePath)
44 | return config
45 | } catch (e) {
46 | logOrConsole.error(`解析配置文件失败,文件内容格式不正确,文件路径: ${configFilePath},文件内容:${configStr},error:`, e)
47 | return {}
48 | }
49 | }
50 |
51 | function getUserConfigPath () {
52 | const dir = getUserBasePath()
53 |
54 | // 兼容1.7.3及以下版本的配置文件处理逻辑
55 | const newFilePath = path.join(dir, '/config.json')
56 | const oldFilePath = path.join(dir, '/config.json5')
57 | if (!fs.existsSync(newFilePath) && fs.existsSync(oldFilePath)) {
58 | return oldFilePath // 如果新文件不存在,但旧文件存在,则返回旧文件路径
59 | }
60 |
61 | return newFilePath
62 | }
63 |
64 | function getUserConfig () {
65 | const configFilePath = getUserConfigPath()
66 | return loadConfigFromFile(configFilePath)
67 | }
68 |
69 | function getRemoteConfigPath (suffix = '') {
70 | const dir = getUserBasePath()
71 | return path.join(dir, `/remote_config${suffix}.json5`)
72 | }
73 |
74 | function getRemoteConfig (suffix = '') {
75 | const remoteConfigFilePath = getRemoteConfigPath(suffix)
76 | return loadConfigFromFile(remoteConfigFilePath)
77 | }
78 |
79 | function getAutomaticCompatibleConfigPath () {
80 | const dir = getUserBasePath()
81 | return path.join(dir, '/automaticCompatibleConfig.json')
82 | }
83 |
84 | /**
85 | * 从文件读取配置
86 | *
87 | * @param userConfig 用户配置
88 | * @param defaultConfig 默认配置
89 | */
90 | function getConfigFromFiles (userConfig, defaultConfig) {
91 | const merged = userConfig != null ? lodash.cloneDeep(userConfig) : {}
92 |
93 | const personalRemoteConfig = getRemoteConfig('_personal')
94 | const shareRemoteConfig = getRemoteConfig()
95 |
96 | mergeApi.doMerge(merged, personalRemoteConfig) // 先合并一次个人远程配置,使配置顺序在前
97 | mergeApi.doMerge(merged, shareRemoteConfig) // 先合并一次共享远程配置,使配置顺序在前
98 | mergeApi.doMerge(merged, defaultConfig) // 合并默认配置,顺序排在最后
99 | mergeApi.doMerge(merged, shareRemoteConfig) // 再合并一次共享远程配置,使配置生效
100 | mergeApi.doMerge(merged, personalRemoteConfig) // 再合并一次个人远程配置,使配置生效
101 |
102 | if (userConfig != null) {
103 | mergeApi.doMerge(merged, userConfig) // 再合并一次用户配置,使用户配置重新生效
104 | }
105 |
106 | // 删除为null及[delete]的项
107 | mergeApi.deleteNullItems(merged)
108 |
109 | logOrConsole.info('加载及合并远程配置完成')
110 | return merged
111 | }
112 |
113 | module.exports = {
114 | getUserBasePath,
115 |
116 | loadConfigFromFile,
117 |
118 | getUserConfigPath,
119 | getUserConfig,
120 |
121 | getRemoteConfigPath,
122 | getRemoteConfig,
123 |
124 | getAutomaticCompatibleConfigPath,
125 |
126 | getConfigFromFiles,
127 | }
128 |
--------------------------------------------------------------------------------
/packages/core/src/event.js:
--------------------------------------------------------------------------------
1 | const listener = {}
2 | let index = 1
3 | function register (channel, handle, order = 10) {
4 | let handles = listener[channel]
5 | if (handles == null) {
6 | handles = listener[channel] = []
7 | }
8 | handles.push({ id: index, handle, order })
9 | handles.sort((a, b) => {
10 | return a.order - b.order
11 | })
12 | return index++
13 | }
14 | function fire (channel, event) {
15 | const handles = listener[channel]
16 | if (handles == null) {
17 | return
18 | }
19 | for (const item of handles) {
20 | item.handle(event)
21 | }
22 | }
23 |
24 | function unregister (id) {
25 | for (const key in listener) {
26 | const handlers = listener[key]
27 | for (let i = 0; i < handlers.length; i++) {
28 | const handle = handlers[i]
29 | if (handle.id === id) {
30 | handlers.splice(i)
31 | return
32 | }
33 | }
34 | }
35 | }
36 | const EventHub = {
37 | register,
38 | fire,
39 | unregister,
40 | }
41 | module.exports = EventHub
42 |
--------------------------------------------------------------------------------
/packages/core/src/expose.js:
--------------------------------------------------------------------------------
1 | const lodash = require('lodash')
2 | const config = require('./config-api')
3 | const event = require('./event')
4 | const modules = require('./modules')
5 | const shell = require('./shell')
6 | const status = require('./status')
7 | const log = require('./utils/util.log.core')
8 |
9 | const context = {
10 | config,
11 | shell,
12 | status,
13 | event,
14 | log,
15 | }
16 |
17 | function setupPlugin (key, plugin, context, config) {
18 | const pluginConfig = plugin.config
19 | const PluginClass = plugin.plugin
20 | const pluginStatus = plugin.status
21 | const api = PluginClass(context)
22 | config.addDefault(key, pluginConfig)
23 | if (pluginStatus) {
24 | lodash.set(status, key, pluginStatus)
25 | }
26 | return api
27 | }
28 |
29 | const proxy = setupPlugin('proxy', modules.proxy, context, config)
30 | const plugin = {}
31 | for (const key in modules.plugin) {
32 | const target = modules.plugin[key]
33 | const api = setupPlugin(`plugin.${key}`, target, context, config)
34 | plugin[key] = api
35 | }
36 | config.resetDefault()
37 | const server = modules.server
38 | const serverStart = server.start
39 |
40 | function newServerStart ({ mitmproxyPath }) {
41 | return serverStart({ mitmproxyPath, plugins: plugin })
42 | }
43 | server.start = newServerStart
44 | async function startup ({ mitmproxyPath }) {
45 | const conf = config.get()
46 | if (conf.server.enabled) {
47 | try {
48 | await server.start({ mitmproxyPath })
49 | } catch (err) {
50 | log.error('代理服务启动失败:', err)
51 | }
52 | }
53 | if (conf.proxy.enabled) {
54 | try {
55 | await proxy.start()
56 | } catch (err) {
57 | log.error('开启系统代理失败:', err)
58 | }
59 | }
60 | try {
61 | const plugins = []
62 | for (const key in plugin) {
63 | if (conf.plugin[key].enabled) {
64 | const start = async () => {
65 | try {
66 | await plugin[key].start()
67 | log.info(`插件【${key}】已启动`)
68 | } catch (err) {
69 | log.error(`插件【${key}】启动失败:`, err)
70 | }
71 | }
72 | plugins.push(start())
73 | }
74 | }
75 | if (plugins && plugins.length > 0) {
76 | await Promise.all(plugins)
77 | }
78 | } catch (err) {
79 | log.error('开启插件失败:', err)
80 | }
81 | }
82 |
83 | async function shutdown () {
84 | try {
85 | const plugins = []
86 | for (const key in plugin) {
87 | if (status.plugin[key] && status.plugin[key].enabled && plugin[key].close) {
88 | const close = async () => {
89 | try {
90 | await plugin[key].close()
91 | log.info(`插件【${key}】已关闭`)
92 | } catch (err) {
93 | log.error(`插件【${key}】关闭失败:`, err)
94 | }
95 | }
96 | plugins.push(close())
97 | }
98 | }
99 | if (plugins.length > 0) {
100 | await Promise.all(plugins)
101 | }
102 | } catch (error) {
103 | log.error('插件关闭失败:', error)
104 | }
105 |
106 | if (status.proxy.enabled) {
107 | try {
108 | await proxy.close()
109 | log.info('系统代理已关闭')
110 | } catch (err) {
111 | log.error('系统代理关闭失败:', err)
112 | }
113 | }
114 | if (status.server.enabled) {
115 | try {
116 | await server.close()
117 | log.info('代理服务已关闭')
118 | } catch (err) {
119 | log.error('代理服务关闭失败:', err)
120 | }
121 | }
122 | }
123 |
124 | const api = {
125 | startup,
126 | shutdown,
127 | status: {
128 | get () {
129 | return status
130 | },
131 | },
132 | config,
133 | event,
134 | shell,
135 | server,
136 | proxy,
137 | plugin,
138 | log,
139 | }
140 | module.exports = {
141 | status,
142 | api,
143 | }
144 |
--------------------------------------------------------------------------------
/packages/core/src/index.js:
--------------------------------------------------------------------------------
1 | const expose = require('./expose.js')
2 | const log = require('./utils/util.log.core')
3 | // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
4 |
5 | // 避免异常崩溃
6 | process.on('uncaughtException', (err) => {
7 | log.error('Process Uncaught Exception:', err)
8 | })
9 |
10 | process.on('unhandledRejection', (reason, p) => {
11 | log.error('Process Unhandled Rejection at: Promise:', p, ', reason:', reason)
12 | // application specific logging, throwing an error, or other logic here
13 | })
14 |
15 | module.exports = expose
16 |
--------------------------------------------------------------------------------
/packages/core/src/merge.js:
--------------------------------------------------------------------------------
1 | const lodash = require('lodash')
2 |
3 | /**
4 | * 找出 newObj 相对于 oldObj 有差异的部分
5 | *
6 | * @param oldObj
7 | * @param newObj
8 | * @returns {{}|*}
9 | */
10 | function doDiff (oldObj, newObj) {
11 | if (newObj == null) {
12 | return oldObj
13 | }
14 |
15 | // 临时的对象,用于找出被删除的数据
16 | const tempObj = { ...oldObj }
17 | // 删除空项,使差异对象更干净一些,体现出用户自定义内容
18 | deleteNullItems(tempObj)
19 |
20 | // 保存差异的对象
21 | const diffObj = {}
22 |
23 | // 读取新对象,并解析
24 | for (const key in newObj) {
25 | const newValue = newObj[key]
26 | const oldValue = oldObj[key]
27 |
28 | // 新值不为空,旧值为空时,直接取新值
29 | if (newValue != null && oldValue == null) {
30 | diffObj[key] = newValue
31 | continue
32 | }
33 | // 新旧值相等时,忽略
34 | if (lodash.isEqual(newValue, oldValue)) {
35 | delete tempObj[key]
36 | continue
37 | }
38 | // 新的值为数组时,直接取新值
39 | if (lodash.isArray(newValue)) {
40 | diffObj[key] = newValue
41 | delete tempObj[key]
42 | continue
43 | }
44 |
45 | // 新的值为对象时,递归合并
46 | if (lodash.isObject(newValue)) {
47 | diffObj[key] = doDiff(oldValue, newValue)
48 | delete tempObj[key]
49 | continue
50 | }
51 |
52 | // 基础类型,直接覆盖
53 | delete tempObj[key]
54 | diffObj[key] = newValue
55 | }
56 |
57 | // tempObj 里面剩下的是被删掉的数据
58 | lodash.forEach(tempObj, (oldValue, key) => {
59 | // 将被删除的属性设置为null,目的是为了merge时,将被删掉的对象设置为null,达到删除的目的
60 | diffObj[key] = null
61 | })
62 |
63 | return diffObj
64 | }
65 |
66 | function deleteNullItems (target) {
67 | lodash.forEach(target, (item, key) => {
68 | if (item == null || item === '[delete]') {
69 | delete target[key]
70 | }
71 | if (lodash.isObject(item)) {
72 | deleteNullItems(item)
73 | }
74 | })
75 | }
76 |
77 | module.exports = {
78 | doMerge (oldObj, newObj) {
79 | return lodash.mergeWith(oldObj, newObj, (objValue, srcValue) => {
80 | if (lodash.isArray(objValue)) {
81 | return srcValue
82 | }
83 | })
84 | },
85 | doDiff,
86 | deleteNullItems,
87 | }
88 |
--------------------------------------------------------------------------------
/packages/core/src/modules/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | server: require('./server'),
3 | proxy: require('./proxy'),
4 | plugin: require('./plugin'),
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/git/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Git.exe代理',
3 | enabled: false,
4 | tip: '如果你没有安装git命令行则不需要启动它',
5 | setting: {
6 | sslVerify: true, // Git.exe 是否关闭sslVerify,true=关闭 false=开启
7 | noProxyUrls: {
8 | 'https://gitee.com': true, // 码云
9 | 'https://e.coding.net': true, // Coding(腾讯云)
10 | 'https://codeup.aliyun.com': true, // 云效 Codeup (阿里云)
11 | },
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/git/index.js:
--------------------------------------------------------------------------------
1 | const pluginConfig = require('./config')
2 |
3 | const Plugin = function (context) {
4 | const { config, shell, event, log } = context
5 | const pluginApi = {
6 | async start () {
7 | const ip = '127.0.0.1'
8 | const port = config.get().server.port
9 | await pluginApi.setProxy(ip, port)
10 | return { ip, port }
11 | },
12 |
13 | async close () {
14 | return pluginApi.unsetProxy()
15 | },
16 |
17 | async restart () {
18 | await pluginApi.close()
19 | await pluginApi.start()
20 | },
21 |
22 | isEnabled () {
23 | return config.get().plugin.git.enabled
24 | },
25 |
26 | async save (newConfig) {
27 |
28 | },
29 |
30 | async setProxy (ip, port) {
31 | const cmds = [
32 | `git config --global http.proxy http://${ip}:${port - 1} `,
33 | `git config --global https.proxy http://${ip}:${port} `,
34 | ]
35 |
36 | if (config.get().plugin.git.setting.sslVerify === true) {
37 | cmds.push('git config --global http.sslVerify false ')
38 | }
39 |
40 | if (config.get().plugin.git.setting.noProxyUrls != null) {
41 | for (const url in config.get().plugin.git.setting.noProxyUrls) {
42 | cmds.push(`git config --global http."${url}".proxy "" `)
43 | }
44 | }
45 |
46 | const ret = await shell.exec(cmds, { type: 'cmd' })
47 | event.fire('status', { key: 'plugin.git.enabled', value: true })
48 | log.info('开启【Git】代理成功')
49 |
50 | return ret
51 | },
52 |
53 | // 当手动修改过 `~/.gitconfig` 时,`unset` 可能会执行失败,所以除了第一条命令外,其他命令都添加了try-catch,防止关闭Git代理失败
54 | async unsetProxy () {
55 | const ret = await shell.exec(['git config --global --unset http.proxy '], { type: 'cmd' })
56 |
57 | try {
58 | await shell.exec(['git config --global --unset https.proxy '], { type: 'cmd' })
59 | } catch {
60 | }
61 |
62 | if (config.get().plugin.git.setting.sslVerify === true) {
63 | try {
64 | await shell.exec(['git config --global --unset http.sslVerify '], { type: 'cmd' })
65 | } catch {
66 | }
67 | }
68 |
69 | if (config.get().plugin.git.setting.noProxyUrls != null) {
70 | for (const url in config.get().plugin.git.setting.noProxyUrls) {
71 | try {
72 | await shell.exec([`git config --global --unset http."${url}".proxy `], { type: 'cmd' })
73 | } catch {
74 | }
75 | }
76 | }
77 | event.fire('status', { key: 'plugin.git.enabled', value: false })
78 | log.info('关闭【Git】代理成功')
79 | return ret
80 | },
81 | }
82 | return pluginApi
83 | }
84 |
85 | module.exports = {
86 | key: 'git',
87 | config: pluginConfig,
88 | status: {
89 | enabled: false,
90 | },
91 | plugin: Plugin,
92 | }
93 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: require('./node'),
3 | git: require('./git'),
4 | pip: require('./pip'),
5 | overwall: require('./overwall'),
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/node/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'NPM加速',
3 | enabled: false,
4 | tip: '如果你没有安装nodejs则不需要启动它',
5 | startup: {
6 | variables: true,
7 | },
8 | setting: {
9 | 'command': 'npm',
10 | 'strict-ssl': true,
11 | 'cafile': false,
12 | 'NODE_EXTRA_CA_CERTS': false,
13 | 'NODE_TLS_REJECT_UNAUTHORIZED': false,
14 | 'yarnRegistry': 'default',
15 | 'registry': 'https://registry.npmjs.org', // 可以选择切换官方或者淘宝镜像
16 | },
17 | variables: {
18 | phantomjs_cdnurl: 'https://npmmirror.com/mirrors/phantomjs',
19 | chromedriver_cdnurl: 'https://npmmirror.com/mirrors/chromedriver',
20 | sass_binary_site: 'https://npmmirror.com/mirrors/node-sass',
21 | ELECTRON_MIRROR: 'https://npmmirror.com/mirrors/electron/',
22 | NVM_NODEJS_ORG_MIRROR: 'https://npmmirror.com/mirrors/node',
23 | CHROMEDRIVER_CDNURL: 'https://npmmirror.com/mirrors/chromedriver',
24 | OPERADRIVER: 'https://npmmirror.com/mirrors/operadriver',
25 | ELECTRON_BUILDER_BINARIES_MIRROR: 'https://npmmirror.com/mirrors/electron-builder-binaries/',
26 | PYTHON_MIRROR: 'https://npmmirror.com/mirrors/python',
27 | },
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/overwall/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: '梯子',
3 | enabled: false, // 默认关闭梯子
4 | server: {},
5 | serverDefault: {
6 | 'ow-prod.docmirror.top': {
7 | port: 443,
8 | path: 'X2dvX292ZXJfd2FsbF8',
9 | password: 'dev_sidecar_is_666',
10 | },
11 | },
12 | targets: {
13 | '*.github.com': true,
14 | '*github*.com': true,
15 | '*.wikimedia.org': true,
16 | '*.v2ex.com': true,
17 | '*.azureedge.net': true,
18 | '*.cloudfront.net': true,
19 | '*.bing.com': true,
20 | '*.discourse-cdn.com': true,
21 | '*.gravatar.com': true,
22 | '*.docker.com': true,
23 | '*.vueuse.org': true,
24 | '*.elastic.co': true,
25 | '*.optimizely.com': true,
26 | '*.stackpathcdn.com': true,
27 | '*.fastly.net': true,
28 | '*.cloudflare.com': true,
29 | '*.233v2.com': true,
30 | '*.v2fly.org': true,
31 | '*.telegram.org': true,
32 | '*.amazon.com': true,
33 | '*.googleapis.com': true,
34 | '*.google-analytics.com': true,
35 | '*.cloudflareinsights.com': true,
36 | '*.intlify.dev': true,
37 | '*.segment.io': true,
38 | '*.shields.io': true,
39 | '*.jsdelivr.net': true,
40 | },
41 | pac: {
42 | enabled: true,
43 | autoUpdate: true,
44 | pacFileUpdateUrl: 'https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt',
45 | pacFileAbsolutePath: null, // 自定义 pac.txt 文件位置,可以是本地文件路径
46 | pacFilePath: './extra/pac/pac.txt', // 内置 pac.txt 文件路径
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/overwall/index.js:
--------------------------------------------------------------------------------
1 | const pluginConfig = require('./config')
2 |
3 | const Plugin = function (context) {
4 | const { config, shell, event, log } = context
5 | const api = {
6 | async start () {
7 | // event.fire('status', { key: 'plugin.overwall.enabled', value: true })
8 | },
9 |
10 | async close () {
11 | // event.fire('status', { key: 'plugin.overwall.enabled', value: false })
12 | },
13 |
14 | async restart () {
15 | await api.close()
16 | await api.start()
17 | },
18 |
19 | async overrideRunningConfig_bak (serverConfig) {
20 | const conf = config.get().plugin.overwall
21 | if (!conf || !conf.enabled || !conf.targets) {
22 | return
23 | }
24 | const server = conf.server
25 | let i = 0
26 | let main
27 | const backup = []
28 | for (const key in server) {
29 | if (i === 0) {
30 | main = key
31 | } else {
32 | backup.push(key)
33 | }
34 | i++
35 | }
36 | for (const key in conf.targets) {
37 | serverConfig.intercepts[key] = {
38 | '.*': {
39 | proxy: `${main}/\${host}`,
40 | backup,
41 | },
42 | }
43 | }
44 | },
45 | }
46 | return api
47 | }
48 |
49 | module.exports = {
50 | key: 'overwall',
51 | config: pluginConfig,
52 | plugin: Plugin,
53 | }
54 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/pip/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'PIP加速',
3 | statusOff: true,
4 | enabled: null, // 没有开关
5 | tip: '如果你没有安装pip则不需要启动它',
6 | startup: {
7 | },
8 | setting: {
9 | command: 'pip',
10 | trustedHost: 'pypi.org',
11 | registry: 'https://pypi.org/simple/', // 可以选择切换官方或者淘宝镜像
12 | },
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/modules/plugin/pip/index.js:
--------------------------------------------------------------------------------
1 | const pipConfig = require('./config')
2 |
3 | const PipPlugin = function (context) {
4 | const { config, shell, event, log } = context
5 | const api = {
6 | async start () {
7 | await api.setRegistry({ registry: config.get().plugin.pip.setting.registry })
8 | await api.setTrustedHost(config.get().plugin.pip.setting.trustedHost)
9 | },
10 |
11 | async close () {
12 | },
13 |
14 | async restart () {
15 | await api.close()
16 | await api.start()
17 | },
18 |
19 | async save (newConfig) {
20 | await api.setVariables()
21 | },
22 | async getPipEnv () {
23 | const command = config.get().plugin.pip.setting.command
24 | let ret = await shell.exec([`${command} config list`], { type: 'cmd' })
25 | if (ret != null) {
26 | ret = ret.trim()
27 | const lines = ret.split('\n')
28 | const vars = {}
29 | for (const line of lines) {
30 | if (!line.startsWith('global')) {
31 | continue
32 | }
33 | const key = line.substring(0, line.indexOf('='))
34 | let value = line.substring(line.indexOf('=') + 1)
35 | if (value.startsWith('\'')) {
36 | value = value.startsWith(1, value.length - 1)
37 | }
38 | vars[key] = value
39 | }
40 | return vars
41 | }
42 | return {}
43 | },
44 |
45 | async setPipEnv (list) {
46 | const command = config.get().plugin.pip.setting.command
47 | const cmds = []
48 | for (const item of list) {
49 | if (item.value != null) {
50 | cmds.push(`${command} config set global.${item.key} ${item.value}`)
51 | } else {
52 | cmds.push(`${command} config unset global.${item.key}`)
53 | }
54 | }
55 | return await shell.exec(cmds, { type: 'cmd' })
56 | },
57 |
58 | async unsetPipEnv (list) {
59 | const command = config.get().plugin.pip.setting.command
60 | const cmds = []
61 | for (const item of list) {
62 | cmds.push(`${command} config unset global.${item} `)
63 | }
64 | return await shell.exec(cmds, { type: 'cmd' })
65 | },
66 |
67 | async setRegistry ({ registry }) {
68 | await api.setPipEnv([{ key: 'index-url', value: registry }])
69 | return true
70 | },
71 |
72 | async setTrustedHost (host) {
73 | await api.setPipEnv([{ key: 'trusted-host', value: host }])
74 | return true
75 | },
76 |
77 | async setProxy (ip, port) {
78 |
79 | },
80 |
81 | async unsetProxy () {
82 |
83 | },
84 | }
85 | return api
86 | }
87 |
88 | module.exports = {
89 | key: 'pip',
90 | config: pipConfig,
91 | status: {
92 | enabled: false,
93 | },
94 | plugin: PipPlugin,
95 | }
96 |
--------------------------------------------------------------------------------
/packages/core/src/shell/index.js:
--------------------------------------------------------------------------------
1 | const enableLoopback = require('./scripts/enable-loopback')
2 | const extraPath = require('./scripts/extra-path')
3 | const getNpmEnv = require('./scripts/get-npm-env')
4 | const getSystemEnv = require('./scripts/get-system-env')
5 | const killByPort = require('./scripts/kill-by-port')
6 | const setNpmEnv = require('./scripts/set-npm-env')
7 | const setSystemEnv = require('./scripts/set-system-env')
8 | const setSystemProxy = require('./scripts/set-system-proxy')
9 | const setupCa = require('./scripts/setup-ca')
10 | const shell = require('./shell')
11 |
12 | module.exports = {
13 | killByPort,
14 | setupCa,
15 | getSystemEnv,
16 | setSystemEnv,
17 | getNpmEnv,
18 | setNpmEnv,
19 | setSystemProxy,
20 | enableLoopback,
21 | extraPath,
22 | async exec (cmds, args) {
23 | return shell.getSystemShell().exec(cmds, args)
24 | },
25 | getSystemPlatform: shell.getSystemPlatform,
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/enable-loopback.js:
--------------------------------------------------------------------------------
1 | /**
2 | */
3 | const Shell = require('../shell')
4 | const extraPath = require('./extra-path')
5 | const sudoPrompt = require('@vscode/sudo-prompt')
6 | const log = require('../../utils/util.log.core')
7 | const execute = Shell.execute
8 |
9 | const executor = {
10 | windows (exec) {
11 | const loopbackPath = extraPath.getEnableLoopbackPath()
12 | const sudoCommand = [`"${loopbackPath}"`]
13 |
14 | const options = {
15 | name: 'EnableLoopback',
16 | }
17 | return new Promise((resolve, reject) => {
18 | sudoPrompt.exec(
19 | sudoCommand.join(' '),
20 | options,
21 | (error, _, stderr) => {
22 | if (stderr) {
23 | log.error(`[sudo-prompt] 发生错误: ${stderr}`)
24 | }
25 |
26 | if (error) {
27 | reject(error)
28 | } else {
29 | resolve(undefined)
30 | }
31 | },
32 | )
33 | })
34 | },
35 | async linux (exec, { port }) {
36 | throw new Error('不支持此操作')
37 | },
38 | async mac (exec, { port }) {
39 | throw new Error('不支持此操作')
40 | },
41 | }
42 |
43 | module.exports = async function (args) {
44 | return execute(executor, args)
45 | }
46 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/extra-path/EnableLoopback.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/core/src/shell/scripts/extra-path/EnableLoopback.exe
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/extra-path/index.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path')
2 | const log = require('../../../utils/util.log.core')
3 |
4 | function getExtraPath () {
5 | let extraPath = process.env.DS_EXTRA_PATH
6 | log.info('extraPath:', extraPath)
7 | if (!extraPath) {
8 | extraPath = __dirname
9 | }
10 | return extraPath
11 | }
12 |
13 | function getProxyExePath () {
14 | const extraPath = getExtraPath()
15 | return path.join(extraPath, 'sysproxy.exe')
16 | }
17 |
18 | function getEnableLoopbackPath () {
19 | const extraPath = getExtraPath()
20 | return path.join(extraPath, 'EnableLoopback.exe')
21 | }
22 |
23 | module.exports = {
24 | getProxyExePath,
25 | getEnableLoopbackPath,
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/extra-path/sysproxy.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/core/src/shell/scripts/extra-path/sysproxy.exe
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/get-npm-env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取环境变量
3 | */
4 | const jsonApi = require('@docmirror/mitmproxy/src/json')
5 | const Shell = require('../shell')
6 |
7 | const execute = Shell.execute
8 |
9 | const executor = {
10 | async windows (exec) {
11 | const ret = await exec(['npm config list --json'], { type: 'cmd' })
12 | if (ret != null) {
13 | const json = ret.substring(ret.indexOf('{'))
14 | return jsonApi.parse(json)
15 | }
16 | return {}
17 | },
18 | async linux (exec, { port }) {
19 | throw new Error('暂未实现此功能')
20 | },
21 | async mac (exec, { port }) {
22 | throw new Error('暂未实现此功能')
23 | },
24 | }
25 |
26 | module.exports = async function (args) {
27 | return execute(executor, args)
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/get-system-env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取环境变量
3 | */
4 | const Shell = require('../shell')
5 |
6 | const execute = Shell.execute
7 |
8 | const executor = {
9 | async windows (exec) {
10 | const ret = await exec(['set'], { type: 'cmd' })
11 | const map = {}
12 | if (ret != null) {
13 | const lines = ret.split('\r\n')
14 | for (const item of lines) {
15 | const kv = item.split('=')
16 | if (kv.length > 1) {
17 | map[kv[0].trim()] = kv[1].trim()
18 | }
19 | }
20 | }
21 | return map
22 | },
23 | async linux (exec, { port }) {
24 | throw new Error('暂未实现此功能')
25 | },
26 | async mac (exec, { port }) {
27 | throw new Error('暂未实现此功能')
28 | },
29 | }
30 |
31 | module.exports = async function (args) {
32 | return execute(executor, args)
33 | }
34 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/kill-by-port.js:
--------------------------------------------------------------------------------
1 | const Shell = require('../shell')
2 |
3 | const execute = Shell.execute
4 |
5 | const executor = {
6 | async windows (exec, { port }) {
7 | const cmds = [`for /f "tokens=5" %a in ('netstat -aon ^| find ":${port}" ^| find "LISTENING"') do (taskkill /f /pid %a & exit /B) `]
8 | await exec(cmds, { type: 'cmd' })
9 | return true
10 | },
11 | async linux (exec, { port }) {
12 | await exec(`kill \`lsof -i:${port} |grep 'dev-sidecar\\|electron\\|@docmirro' |awk '{print $2}'\``)
13 | return true
14 | },
15 | async mac (exec, { port }) {
16 | await exec(`kill \`lsof -i:${port} |grep 'dev-side\\|Elect' |awk '{print $2}'\``)
17 | return true
18 | },
19 | }
20 |
21 | module.exports = async function (args) {
22 | return execute(executor, args)
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/set-npm-env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 设置环境变量
3 | */
4 | const Shell = require('../shell')
5 |
6 | const execute = Shell.execute
7 |
8 | const executor = {
9 | async windows (exec, { list }) {
10 | const cmds = []
11 | for (const item of list) {
12 | cmds.push(`npm config set ${item.key} ${item.value}`)
13 | }
14 | return await exec(cmds, { type: 'cmd' })
15 | },
16 | async linux (exec, { port }) {
17 | throw new Error('暂未实现此功能')
18 | },
19 | async mac (exec, { port }) {
20 | throw new Error('暂未实现此功能')
21 | },
22 | }
23 |
24 | module.exports = async function (args) {
25 | return execute(executor, args)
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/set-system-env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 设置环境变量
3 | */
4 | const Shell = require('../shell')
5 |
6 | const execute = Shell.execute
7 |
8 | const executor = {
9 | async windows (exec, { list }) {
10 | const cmds = []
11 | for (const item of list) {
12 | // [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
13 | cmds.push(`[Environment]::SetEnvironmentVariable('${item.key}', '${item.value}', 'Machine')`)
14 | }
15 | const ret = await exec(cmds, { type: 'ps' })
16 |
17 | const cmds2 = []
18 | for (const item of list) {
19 | // [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
20 | cmds2.push(`set ${item.key}=""`)
21 | }
22 | await exec(cmds2, { type: 'cmd' })
23 | return ret
24 | },
25 | async linux (exec, { port }) {
26 | throw new Error('暂未实现此功能')
27 | },
28 | async mac (exec, { port }) {
29 | throw new Error('暂未实现此功能')
30 | },
31 | }
32 |
33 | module.exports = async function (args) {
34 | return execute(executor, args)
35 | }
36 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/set-system-proxy/refresh-internet.js:
--------------------------------------------------------------------------------
1 | const script = `
2 | $signature = @'
3 | [DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
4 | public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
5 | '@
6 |
7 | $INTERNET_OPTION_SETTINGS_CHANGED = 39
8 | $INTERNET_OPTION_REFRESH = 37
9 | $type = Add-Type -MemberDefinition $signature -Name wininet -Namespace pinvoke -PassThru
10 | $a = $type::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
11 | $b = $type::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
12 | $a -and $b
13 | `
14 | module.exports = script
15 |
--------------------------------------------------------------------------------
/packages/core/src/shell/scripts/setup-ca.js:
--------------------------------------------------------------------------------
1 | const Shell = require('../shell')
2 |
3 | const execute = Shell.execute
4 |
5 | const executor = {
6 | async windows (exec, { certPath }) {
7 | const cmds = [`start "" "${certPath}"`]
8 | await exec(cmds, { type: 'cmd' })
9 | return true
10 | },
11 | async linux (exec, { certPath }) {
12 | const cmds = [`sudo cp ${certPath} /usr/local/share/ca-certificates`, 'sudo update-ca-certificates ']
13 | await exec(cmds)
14 | return true
15 | },
16 | async mac (exec, { certPath }) {
17 | const cmds = [`open "${certPath}"`]
18 | await exec(cmds, { type: 'cmd' })
19 | return true
20 | },
21 | }
22 |
23 | module.exports = async function (args) {
24 | return execute(executor, args)
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/status.js:
--------------------------------------------------------------------------------
1 | const lodash = require('lodash')
2 | const event = require('./event')
3 | const log = require('./utils/util.log.core')
4 |
5 | const status = {
6 | server: { enabled: false },
7 | proxy: {},
8 | plugin: {},
9 | }
10 |
11 | event.register('status', (event) => {
12 | lodash.set(status, event.key, event.value)
13 | log.info('status changed:', event)
14 | }, -999)
15 |
16 | module.exports = status
17 |
--------------------------------------------------------------------------------
/packages/core/src/utils/util.date.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | format (date, needMill = true) {
4 | if (date == null) {
5 | return 'null'
6 | }
7 |
8 | const year = date.getFullYear() // 获取年份
9 | const month = (date.getMonth() + 1).toString().padStart(2, '0') // 获取月份(注意月份从 0 开始计数)
10 | const day = date.getDate().toString().padStart(2, '0') // 获取天数
11 | const hours = date.getHours().toString().padStart(2, '0') // 获取小时
12 | const minutes = date.getMinutes().toString().padStart(2, '0') // 获取分钟
13 | const seconds = date.getSeconds().toString().padStart(2, '0') // 获取秒数
14 | const milliseconds = needMill ? `.${date.getMilliseconds().toString().padStart(3, '0')}` : '' // 获取毫秒
15 |
16 | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}${milliseconds}`
17 | },
18 |
19 | now (needMill = true) {
20 | return this.format(new Date(), needMill)
21 | },
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/utils/util.log-or-console.js:
--------------------------------------------------------------------------------
1 | const dateUtil = require('./util.date')
2 |
3 | let log = console
4 |
5 | // 将console中的日志缓存起来,当setLogger时,将控制台的日志写入日志文件
6 | let backupLogs = []
7 |
8 | function backup (fun, args) {
9 | if (backupLogs === null) {
10 | return
11 | }
12 |
13 | try {
14 | backupLogs.push({
15 | fun,
16 | args,
17 | time: dateUtil.format(new Date()),
18 | })
19 |
20 | // 最多缓存 100 条
21 | if (backupLogs.length > 100) {
22 | backupLogs = backupLogs.slice(1)
23 | }
24 | } catch {
25 | }
26 | }
27 |
28 | function printBackups () {
29 | if (backupLogs === null || log === console) {
30 | return
31 | }
32 |
33 | try {
34 | const backups = backupLogs
35 | backupLogs = null // 先置空历史消息对象,再记录日志
36 |
37 | for (const item of backups) {
38 | log[item.fun](...[`[${item.time}] console -`, ...item.args])
39 | }
40 | } catch {
41 | }
42 | }
43 |
44 | function _doLog (fun, args) {
45 | if (log === console) {
46 | log[fun](...[`[${fun.toUpperCase()}]`, ...args])
47 | backup(fun, args) // 控制台日志备份起来
48 | } else {
49 | log[fun](...args)
50 | }
51 | }
52 |
53 | module.exports = {
54 | setLogger (logger) {
55 | if (logger == null) {
56 | log.error('logger 不能为空')
57 | return
58 | }
59 |
60 | if (logger === log) {
61 | return
62 | }
63 |
64 | log = logger
65 |
66 | if (log !== console) {
67 | try {
68 | if (backupLogs && backupLogs.length > 0) {
69 | log.info('[util.log-or-console.js] 日志系统已初始化完成,现开始将历史控制台信息记录到日志文件中:')
70 | printBackups()
71 | }
72 | } catch {
73 | }
74 | }
75 | },
76 |
77 | debug (...args) {
78 | _doLog('debug', args)
79 | },
80 | info (...args) {
81 | _doLog('info', args)
82 | },
83 | warn (...args) {
84 | _doLog('warn', args)
85 | },
86 | error (...args) {
87 | _doLog('error', args)
88 | },
89 | }
90 |
--------------------------------------------------------------------------------
/packages/core/src/utils/util.log.core.js:
--------------------------------------------------------------------------------
1 | const loggerFactory = require('./util.logger')
2 |
3 | const logger = loggerFactory.getLogger('core')
4 |
5 | module.exports = logger
6 |
--------------------------------------------------------------------------------
/packages/core/src/utils/util.logger.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path')
2 | const log4js = require('log4js')
3 | const logOrConsole = require('./util.log-or-console')
4 | const defaultConfig = require('../config/index.js')
5 | const configFromFiles = defaultConfig.configFromFiles
6 |
7 | // 日志级别
8 | const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'
9 |
10 | function getDefaultConfigBasePath () {
11 | if (configFromFiles.app.logFileSavePath) {
12 | let logFileSavePath = configFromFiles.app.logFileSavePath
13 | if (logFileSavePath.endsWith('/') || logFileSavePath.endsWith('\\')) {
14 | logFileSavePath = logFileSavePath.slice(0, -1)
15 | }
16 | // eslint-disable-next-line no-template-curly-in-string
17 | return logFileSavePath.replace('${userBasePath}', configFromFiles.server.setting.userBasePath)
18 | } else {
19 | return path.join(configFromFiles.server.setting.userBasePath, '/logs')
20 | }
21 | }
22 |
23 | // 日志文件目录
24 | const basePath = getDefaultConfigBasePath()
25 |
26 | // 通用日志配置
27 | const appenderConfig = {
28 | type: 'file',
29 | pattern: 'yyyy-MM-dd',
30 | compress: true, // 压缩日志文件
31 | keepFileExt: true, // 保留日志文件扩展名为 .log
32 | backups: Math.ceil(configFromFiles.app.keepLogFileCount) || defaultConfig.app.keepLogFileCount, // 保留日志文件数
33 | maxLogSize: Math.ceil((configFromFiles.app.maxLogFileSize || defaultConfig.app.maxLogFileSize) * 1024 * 1024 * (configFromFiles.app.maxLogFileSizeUnit === 'GB' ? 1024 : 1)), // 目前单位只有GB和MB
34 | }
35 |
36 | let log = null
37 |
38 | // 设置一组日志配置
39 | function log4jsConfigure (categories) {
40 | if (log != null) {
41 | log.error('当前进程已经设置过日志配置,无法再设置更多日志配置:', categories)
42 | return
43 | }
44 |
45 | const config = {
46 | appenders: {
47 | std: { type: 'stdout' },
48 | },
49 | categories: {
50 | default: { appenders: ['std'], level },
51 | },
52 | }
53 |
54 | for (const category of categories) {
55 | config.appenders[category] = { ...appenderConfig, filename: path.join(basePath, `/${category}.log`) }
56 | config.categories[category] = { appenders: [category, 'std'], level }
57 | }
58 |
59 | log4js.configure(config)
60 |
61 | // 拿第一个日志类型来logger并设置到log变量中
62 | log = log4js.getLogger(categories[0])
63 | logOrConsole.setLogger(log)
64 |
65 | log.info(`设置日志配置完成,进程ID: ${process.pid},categories:[${categories}],config:`, JSON.stringify(config))
66 | }
67 |
68 | module.exports = {
69 | getLogger (category) {
70 | if (!category) {
71 | if (log) {
72 | log.error('未指定日志类型,无法配置并获取日志对象!!!')
73 | }
74 | throw new Error('未指定日志类型,无法配置并获取日志对象!!!')
75 | }
76 |
77 | if (category === 'core' || category === 'gui') {
78 | // core 和 gui 的日志配置,因为它们在同一进程中,所以一起配置,且只能配置一次
79 | if (log == null) {
80 | log4jsConfigure(['core', 'gui'])
81 | }
82 |
83 | return log4js.getLogger(category)
84 | } else {
85 | if (log == null) {
86 | log4jsConfigure([category])
87 | } else if (category !== log.category) {
88 | log.error(`当前进程已经设置过日志配置,无法再设置 "${category}" 的配置,先临时返回 "${log.category}" 的 log 进行日志记录。如果与其他类型的日志在同一进程中写入,请参照 core 和 gui 一起配置`)
89 | }
90 |
91 | return log
92 | }
93 | },
94 | }
95 |
--------------------------------------------------------------------------------
/packages/core/src/utils/util.version.js:
--------------------------------------------------------------------------------
1 | function parseVersion (version) {
2 | const matched = version.match(/^v?(\d{1,2}(?:\.\d{1,2})*)(.*)$/)
3 | return {
4 | versions: matched[1].split('.'), // 版本号数组
5 | pre: matched[2], // 预发布版本号
6 | }
7 | }
8 |
9 | /**
10 | * 比较版本号
11 | *
12 | * @param onlineVersion 线上版本号
13 | * @param currentVersion 当前版本号
14 | * @param log 日志对象
15 | * @returns {number} 比较线上版本号是否为更新的版本,大于0=是|0=相等|小于0=否|-999=出现异常,比较结果未知
16 | */
17 | export function isNewVersion (onlineVersion, currentVersion, log = console) {
18 | if (onlineVersion === currentVersion) {
19 | return 0
20 | }
21 |
22 | try {
23 | const onlineVersionObj = parseVersion(onlineVersion)
24 | const curVersionObj = parseVersion(currentVersion)
25 |
26 | const { versions: versions1 } = onlineVersionObj
27 | const { versions: versions2 } = curVersionObj
28 |
29 | if (versions1.length !== versions2.length) {
30 | // 短的数组补0
31 | if (versions1.length < versions2.length) {
32 | for (let i = versions1.length; i < versions2.length; i++) {
33 | versions1.push('0')
34 | }
35 | } else if (versions1.length > versions2.length) {
36 | for (let i = versions2.length; i < versions1.length; i++) {
37 | versions2.push('0')
38 | }
39 | }
40 | }
41 |
42 | // 版本数组比对
43 | for (let i = 0; i < versions1.length; i++) {
44 | if (versions1[i] > versions2[i]) {
45 | return i + 1 // 为新版本,需要更新
46 | } else if (versions1[i] < versions2[i]) {
47 | return -(i + 1) // 为旧版本,无需更新
48 | }
49 | }
50 |
51 | // 版本号相同,继续比对预发布版本号
52 | if (onlineVersionObj.pre && curVersionObj.pre) {
53 | // 都为预发布版本时,直接比较预发布版本号字符串的大小
54 | if (onlineVersionObj.pre > curVersionObj.pre) {
55 | return 101
56 | } else if (onlineVersionObj.pre < curVersionObj.pre) {
57 | return -101
58 | }
59 | } else if (!onlineVersionObj.pre && curVersionObj.pre) {
60 | // 线上为正式版本,当前版本为预发布版本,需要更新
61 | return 102
62 | } else if (onlineVersionObj.pre && !curVersionObj.pre) {
63 | // 线上为预发布版本,当前版本为正式版本,无需更新
64 | return -102
65 | }
66 |
67 | return 0 // 相同版本,无需更新
68 | } catch (e) {
69 | (log || console).error(`比对版本失败,当前版本号:${currentVersion},线上版本号:${onlineVersion}, error:`, e)
70 | return -999 // 比对异常
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/packages/core/test/configTest.js:
--------------------------------------------------------------------------------
1 | // const config = require('../src/config-api')
2 | //
3 | // config.set({
4 | // server: {
5 | // intercepts: {
6 | // 'github1.githubassets.com': {
7 | // '.*': {
8 | // redirect: 'assets.fastgit.org',
9 | // test: 'https://github.githubassets.com/favicons/favicon.svg',
10 | // desc: '静态资源加速'
11 | // }
12 | // },
13 | // 'github.githubassets.com': null
14 | // }
15 | // }
16 | // })
17 | //
18 | // console.log(config.get())
19 | //
20 | // config.reload()
21 |
--------------------------------------------------------------------------------
/packages/core/test/httpsVerifyTest.js:
--------------------------------------------------------------------------------
1 | // const https = require('node:https')
2 | //
3 | // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'
4 | //
5 | // function request () {
6 | // return new Promise((resolve, reject) => {
7 | // const options = {
8 | // hostname: 'test1.gagedigital.com',
9 | // port: 443,
10 | // path: '/ssltest.php',
11 | // method: 'GET',
12 | // rejectUnauthorized: true,
13 | // }
14 | // console.log('ssl test: gagedigital')
15 | // const req = https.request(options, (res) => {
16 | // console.log('statusCode:', res.statusCode)
17 | // console.log('headers:', res.headers)
18 | //
19 | // res.on('data', (d) => {
20 | // process.stdout.write(d)
21 | // resolve()
22 | // })
23 | // })
24 | //
25 | // req.on('error', (e) => {
26 | // console.error(e)
27 | // reject(e)
28 | // })
29 | // req.end()
30 | // })
31 | // }
32 | // // eslint-disable-next-line no-undef
33 | // describe('ssl.verify', () => {
34 | // // eslint-disable-next-line no-undef
35 | // it('regex.test.js', async () => {
36 | // // https.request('https://test1.gagedigital.com/ssltest.php')
37 | // await request()
38 | //
39 | // // expect(ret).be.ok
40 | // })
41 | // })
42 |
--------------------------------------------------------------------------------
/packages/core/test/macProxyTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 |
3 | // const childProcess = require('child_process')
4 | // const util = require('util')
5 | // const exec = util.promisify(childProcess.exec)
6 | //
7 | // async function test () {
8 | // const wifiAdaptor = (await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 | cut -d \' \' -f2"')).stdout.trim()
9 | //
10 | // await exec(`networksetup -setwebproxystate '${wifiAdaptor}' off`)
11 | // return await exec(`networksetup -setsecurewebproxystate '${wifiAdaptor}' off`)
12 | // }
13 | // test().then((ret) => {
14 | // console.log('haha', ret)
15 | // })
16 | let wifiAdaptor = '(151) test'
17 | wifiAdaptor = wifiAdaptor.substring(wifiAdaptor.indexOf(' ')).trim()
18 | console.log(wifiAdaptor)
19 | assert.strictEqual(wifiAdaptor, 'test')
20 |
--------------------------------------------------------------------------------
/packages/core/test/mergeTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const lodash = require('lodash')
3 | const mergeApi = require('../src/merge.js')
4 |
5 | // 默认配置
6 | const defConfig = {
7 | a: {
8 | aa: { value: 1 },
9 | bb: { value: 2 },
10 | },
11 | b: { c: 2 },
12 | c: 1,
13 | d: [1, 2, 3],
14 | e: {
15 | aa: 2,
16 | ee: 5,
17 | },
18 | f: {
19 | x: 1,
20 | },
21 | g: [1, 2],
22 | h: null,
23 | i: null,
24 | }
25 |
26 | // 自定义配置
27 | const customConfig = {
28 | a: {
29 | bb: { value: 2 },
30 | cc: { value: 3 },
31 | },
32 | b: { c: 2 },
33 | c: null,
34 | d: [1, 2, 3, 4],
35 | e: {
36 | aa: 2,
37 | ee: 5,
38 | ff: 6,
39 | },
40 | f: {},
41 | g: [1, 2],
42 | h: null,
43 | }
44 |
45 | // doDiff
46 | const doDiffResult = mergeApi.doDiff(defConfig, customConfig)
47 | console.log('doDiffResult:', JSON.stringify(doDiffResult, null, 2))
48 | console.log('\r')
49 | // 校验doDiff结果
50 | const doDiffExpect = {
51 | a: {
52 | aa: null,
53 | cc: { value: 3 },
54 | },
55 | c: null,
56 | d: [1, 2, 3, 4],
57 | e: {
58 | ff: 6,
59 | },
60 | f: {
61 | x: null,
62 | },
63 | }
64 | console.log('check diff result:', lodash.isEqual(doDiffResult, doDiffExpect))
65 | console.log('\r')
66 |
67 | // doMerge
68 | const doMergeResult = mergeApi.doMerge(defConfig, doDiffResult)
69 | // delete null item
70 | mergeApi.deleteNullItems(doMergeResult)
71 | console.log('running:', JSON.stringify(doMergeResult, null, 2))
72 | // 校验doMerge结果
73 | const doMergeExpect = {
74 | a: {
75 | bb: { value: 2 },
76 | cc: { value: 3 },
77 | },
78 | b: { c: 2 },
79 | d: [1, 2, 3, 4],
80 | e: {
81 | aa: 2,
82 | ee: 5,
83 | ff: 6,
84 | },
85 | f: {},
86 | g: [1, 2],
87 | }
88 |
89 | const result = lodash.isEqual(doMergeResult, doMergeExpect)
90 | console.log('check merge result:', result)
91 | console.log('\r')
92 | assert.strictEqual(result, true)
93 |
--------------------------------------------------------------------------------
/packages/core/test/regex.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const expect = require('chai').expect
3 | // eslint-disable-next-line no-undef
4 | describe('test', () => {
5 | // eslint-disable-next-line no-undef
6 | it('regexp', () => {
7 | const test = '^/[^/]+/[^/]+(?:/releases(?:/.*)?)?$'
8 | const reg = new RegExp(test)
9 |
10 | const ret = reg.test('/docmirror/dev-sidecar/releases/tag')
11 | console.log(ret)
12 | assert.strictEqual(ret, true)
13 |
14 | expect(ret).be.ok
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/packages/core/test/requestTest.js:
--------------------------------------------------------------------------------
1 | const HttpsAgent = require('@docmirror/mitmproxy/src/lib/proxy/common/ProxyHttpsAgent')
2 | const request = require('request')
3 |
4 | const options = {
5 | url: 'https://raw.githubusercontent.com/docmirror/dev-sidecar/refs/heads/master/packages/core/src/config/remote_config.json5',
6 | // url: 'https://gitee.com/wangliang181230/dev-sidecar/raw/docmirror2.x/packages/core/src/config/remote_config.json',
7 | servername: 'baidu.com',
8 | agent: new HttpsAgent({
9 | keepAlive: true,
10 | timeout: 20000,
11 | keepAliveTimeout: 30000,
12 | rejectUnauthorized: false,
13 | }),
14 | }
15 | if (options.agent.options) {
16 | options.agent.options.rejectUnauthorized = false
17 | console.info('options.agent.options.rejectUnauthorized = false')
18 | }
19 |
20 | request(options, (error, response, body) => {
21 | console.info('error:', error, '\n---------------------------------------------------------------------------\n'
22 | + 'response:', response, '\n---------------------------------------------------------------------------\n'
23 | + 'body:', body)
24 | })
25 |
--------------------------------------------------------------------------------
/packages/core/test/versionTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const { isNewVersion } = require('../src/utils/util.version.js')
3 |
4 | function testIsNewVersion (onlineVersion, currentVersion, expected) {
5 | const ret = isNewVersion(onlineVersion, currentVersion)
6 | console.log(ret >= 0 ? ` ${ret}` : `${ret}`)
7 | assert.strictEqual(ret, expected)
8 | }
9 |
10 | testIsNewVersion('2.0.0', '2.0.0', 0)
11 |
12 | testIsNewVersion('2.0.0', '1.0.0', 1)
13 | testIsNewVersion('1.0.0', '2.0.0', -1)
14 |
15 | testIsNewVersion('2.1.0', '2.0.0', 2)
16 | testIsNewVersion('2.0.0', '2.1.0', -2)
17 |
18 | testIsNewVersion('2.0.1', '2.0.0', 3)
19 | testIsNewVersion('2.0.0', '2.0.1', -3)
20 |
21 | testIsNewVersion('2.0.0.1', '2.0.0', 4)
22 | testIsNewVersion('2.0.0', '2.0.0.1', -4)
23 |
24 | testIsNewVersion('2.0.0.9.1', '2.0.0.9', 5)
25 | testIsNewVersion('2.0.0.9', '2.0.0.9.1', -5)
26 |
27 | testIsNewVersion('2.0.0-RC2', '2.0.0-RC1', 101)
28 | testIsNewVersion('2.0.0-RC1', '2.0.0-RC2', -101)
29 |
30 | testIsNewVersion('2.0.0', '2.0.0-RC1', 102)
31 | testIsNewVersion('2.0.0-RC1', '2.0.0', -102)
32 |
33 | testIsNewVersion('2.0.0.0', '2.0.0', 0)
34 |
35 | testIsNewVersion('x', 'v', -999)
36 |
--------------------------------------------------------------------------------
/packages/gui/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/packages/gui/.env:
--------------------------------------------------------------------------------
1 | VUE_APP_PUBLISH_URL=http://dev-sidecar.docmirror.cn/update/
2 | VUE_APP_PUBLISH_PROVIDER=generic
3 |
--------------------------------------------------------------------------------
/packages/gui/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 | *.lock
25 | *.log
26 | #Electron-builder output
27 | /dist_electron
28 | /config
29 |
--------------------------------------------------------------------------------
/packages/gui/README.md:
--------------------------------------------------------------------------------
1 | # dev-sidecar-gui
2 |
3 | ## Project setup
4 |
5 | ```
6 | yarn install
7 | ```
8 |
9 | ### Compiles and hot-reloads for development
10 |
11 | ```
12 | yarn serve
13 | ```
14 |
15 | ### Compiles and minifies for production
16 |
17 | ```
18 | yarn build
19 | ```
20 |
21 | ### Lints and fixes files
22 |
23 | ```
24 | yarn lint
25 | ```
26 |
27 | ### Customize configuration
28 |
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/packages/gui/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/babel-preset-jsx',
4 | ],
5 | }
6 |
--------------------------------------------------------------------------------
/packages/gui/build/icons/0x0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/0x0.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/1024x1024.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/128x128.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/16x16.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/24x24.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/256x256.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/32x32.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/32x32@2x.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/48x48.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/512x512.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/64x64.png
--------------------------------------------------------------------------------
/packages/gui/build/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/icon.icns
--------------------------------------------------------------------------------
/packages/gui/build/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/icons/icon.ico
--------------------------------------------------------------------------------
/packages/gui/build/mac/0x0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/0x0.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/1024x1024.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/128x128.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/16x16.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/24x24.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/256x256.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/32x32.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/48x48.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/512x512.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/64x64.png
--------------------------------------------------------------------------------
/packages/gui/build/mac/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/icon.icns
--------------------------------------------------------------------------------
/packages/gui/build/mac/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/build/mac/icon.ico
--------------------------------------------------------------------------------
/packages/gui/extra/EnableLoopback.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/EnableLoopback.exe
--------------------------------------------------------------------------------
/packages/gui/extra/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/favicon.ico
--------------------------------------------------------------------------------
/packages/gui/extra/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/1024x1024.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/128x128.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/16x16-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/16x16-black.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/16x16.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/24x24.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/256x256.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/32x32.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/48x48.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/512x512.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/64x64.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/icon.icns
--------------------------------------------------------------------------------
/packages/gui/extra/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/icon.ico
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray-icon.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon-black.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon-black@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon-black@2x.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon-black@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon-black@3x.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon-white.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon-white@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon-white@2x.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon-white@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon-white@3x.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon@2x.png
--------------------------------------------------------------------------------
/packages/gui/extra/icons/tray/icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/icons/tray/icon@3x.png
--------------------------------------------------------------------------------
/packages/gui/extra/scripts/google.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name google增强
3 | // @version 1.2.4
4 | // @author Greper
5 | // @description 去除ping链接
6 | // @match https://www.google.com/*/*
7 | // @icon https://www.google.com/favicon.ico
8 | // @license GPL-3.0 License
9 | // @run-at document-end
10 | // @namespace
11 | // ==/UserScript==
12 |
13 | (function () {
14 | console.log('google script loaded')
15 | const aList = document.getElementsByTagName('a')
16 | for (let i = 0; i <= aList.length; i++) {
17 | console.log(aList[i].href)
18 | aList[i].ping = undefined
19 | }
20 | })()
21 |
--------------------------------------------------------------------------------
/packages/gui/extra/sysproxy.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/extra/sysproxy.exe
--------------------------------------------------------------------------------
/packages/gui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@docmirror/dev-sidecar-gui",
3 | "version": "2.0.0",
4 | "private": false,
5 | "author": {
6 | "email": "xiaojunnuo@qq.com",
7 | "name": "Greper"
8 | },
9 | "license": "MPL-2.0",
10 | "homepage": "https://github.com/docmirror/dev-sidecar",
11 | "scripts": {
12 | "serve": "vue-cli-service serve",
13 | "lint": "vue-cli-service lint",
14 | "electron:build": "vue-cli-service electron:build",
15 | "electron": "vue-cli-service electron:serve",
16 | "postinstall": "electron-builder install-app-deps",
17 | "postuninstall": "electron-builder install-app-deps",
18 | "electron:icons": "electron-icon-builder --input=./public/logo/win.png --output=build --flatten",
19 | "electron:icons-mac": "electron-icon-builder --input=./public/logo/mac.png --output=build --flatten",
20 | "electron:icons-black": "electron-icon-builder --input=./public/logo/win-black.png --output=build/black --flatten"
21 | },
22 | "dependencies": {
23 | "@docmirror/dev-sidecar": "workspace:*",
24 | "@docmirror/mitmproxy": "workspace:*",
25 | "@starknt/shutdown-handler-napi": "^0.0.3",
26 | "@starknt/sysproxy": "^0.0.3",
27 | "@vscode/sudo-prompt": "^9.3.1",
28 | "adm-zip": "^0.5.16",
29 | "ant-design-vue": "^1.7.8",
30 | "electron-baidu-tongji": "^1.0.5",
31 | "electron-updater": "^6.3.9",
32 | "json5": "^2.2.3",
33 | "lodash": "^4.17.21",
34 | "request-progress": "^3.0.0",
35 | "sass": "^1.81.0",
36 | "sass-loader": "^16.0.3",
37 | "search-bar-vue2": "^1.0.0",
38 | "vue": "^2.7.16",
39 | "vue-json-editor-fix-cn": "^1.4.3",
40 | "vue-router": "^3.6.5"
41 | },
42 | "devDependencies": {
43 | "@babel/plugin-syntax-jsx": "^7.25.9",
44 | "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
45 | "@vue/babel-preset-jsx": "^1.4.0",
46 | "@vue/cli-plugin-babel": "^5.0.8",
47 | "@vue/cli-service": "^5.0.8",
48 | "electron": "^19.1.9",
49 | "electron-builder": "^25.1.8",
50 | "electron-icon-builder": "^2.0.1",
51 | "json5-loader": "^4.0.1",
52 | "vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
53 | },
54 | "browserslist": [
55 | "> 1%",
56 | "last 2 versions",
57 | "not dead"
58 | ]
59 | }
60 |
--------------------------------------------------------------------------------
/packages/gui/pkg/after-all-artifact-build.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const path = require('node:path')
3 | const pkg = require('../package.json')
4 |
5 | function appendIntro (context, systemType, latest) {
6 | const version = pkg.version
7 | const partUpdateFile = `update-${systemType}-${version}.zip`
8 |
9 | const partUpdateUrl = context.configuration.publish.url + partUpdateFile
10 |
11 | const latestFilePath = path.join(context.outDir, latest)
12 | fs.appendFile(latestFilePath, `partPackage: ${partUpdateUrl}
13 | partMiniVersion: 1.7.0
14 | releaseNotes:
15 | - 升级日志
16 | - https://download.fastgit.org/docmirror/dev-sidecar/releases/download/v${version}/DevSidecar-${version}.exe
17 | `, (err) => {
18 | if (err) {
19 | console.log('修改latest 失败')
20 | }
21 | })
22 | }
23 | exports.default = async function (context) {
24 | console.log('after-all-artifact-build')
25 | appendIntro(context, 'mac', 'latest-mac.yml')
26 | appendIntro(context, 'win', 'latest.yml')
27 | appendIntro(context, 'linux', 'latest-linux.yml')
28 | }
29 |
--------------------------------------------------------------------------------
/packages/gui/pkg/after-pack.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const path = require('node:path')
3 | const AdmZip = require('adm-zip')
4 | const pkg = require('../package.json')
5 |
6 | function writeAppUpdateYmlForLinux () {
7 | const publishUrl = process.env.VUE_APP_PUBLISH_URL
8 | const publishProvider = process.env.VUE_APP_PUBLISH_PROVIDER
9 | // provider: generic
10 | // url: 'http://dev-sidecar.docmirror.cn/update/preview/'
11 | // updaterCacheDirName: '@docmirrordev-sidecar-gui-updater'
12 | const fileContent = `provider: ${publishProvider}
13 | url: '${publishUrl}'
14 | updaterCacheDirName: '@docmirrordev-sidecar-gui-updater'
15 | `
16 | console.log('write linux app-update.yml,updateUrl:', publishUrl)
17 | const filePath = path.resolve('./dist_electron/linux-unpacked/resources/app-update.yml')
18 | fs.writeFileSync(filePath, fileContent)
19 | }
20 | exports.default = async function (context) {
21 | let targetPath
22 | let systemType
23 | if (context.packager.platform.nodeName === 'darwin') {
24 | targetPath = path.join(context.appOutDir, `${context.packager.appInfo.productName}.app/Contents/Resources`)
25 | systemType = 'mac'
26 | } else if (context.packager.platform.nodeName === 'linux') {
27 | targetPath = path.join(context.appOutDir, './resources')
28 | systemType = 'linux'
29 | writeAppUpdateYmlForLinux()
30 | } else {
31 | targetPath = path.join(context.appOutDir, './resources')
32 | systemType = 'win'
33 | }
34 | const zip = new AdmZip()
35 | zip.addLocalFolder(targetPath)
36 | const partUpdateFile = `update-${systemType}-${pkg.version}.zip`
37 | zip.writeZip(path.join(context.outDir, partUpdateFile))
38 | }
39 |
--------------------------------------------------------------------------------
/packages/gui/public/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/256x256.png
--------------------------------------------------------------------------------
/packages/gui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/favicon.ico
--------------------------------------------------------------------------------
/packages/gui/public/icon-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/icon-1.png
--------------------------------------------------------------------------------
/packages/gui/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/icon.png
--------------------------------------------------------------------------------
/packages/gui/public/icon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/icon2.png
--------------------------------------------------------------------------------
/packages/gui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
12 |
13 |
14 |
15 |
16 |

17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/packages/gui/public/loading-spin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-fff.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/logo-only.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-only.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-only2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/logo-only2.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-only4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/logo-only4.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-simple-fan.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-simple.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/packages/gui/public/logo/logo-simple2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/logo-simple2.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/mac.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/win-16.svg:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/packages/gui/public/logo/win-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/win-black.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/win.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/logo/win.png
--------------------------------------------------------------------------------
/packages/gui/public/logo/win.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/gui/public/loopback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/loopback.png
--------------------------------------------------------------------------------
/packages/gui/public/setup-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/setup-linux.png
--------------------------------------------------------------------------------
/packages/gui/public/setup-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/setup-mac.png
--------------------------------------------------------------------------------
/packages/gui/public/setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docmirror/dev-sidecar/4d6be465c7c56ddfc5cbf25349ec95c003c7b001/packages/gui/public/setup.png
--------------------------------------------------------------------------------
/packages/gui/src/background/powerMonitor.js:
--------------------------------------------------------------------------------
1 | import { acquireShutdownBlock, insertWndProcHook, releaseShutdownBlock, removeWndProcHook, setMainWindowHandle } from '@starknt/shutdown-handler-napi'
2 | import { powerMonitor as _powerMonitor } from 'electron'
3 |
4 | class PowerMonitor {
5 | constructor () {
6 | this.setup = false
7 | this._listeners = []
8 | this._shutdownCallback = null
9 | }
10 |
11 | /**
12 | * @param {import('electron').BrowserWindow} window
13 | */
14 | setupMainWindow (window) {
15 | if (!this.setup) {
16 | setMainWindowHandle(window.getNativeWindowHandle())
17 | this.setup = true
18 | }
19 | }
20 |
21 | addListener (event, listener) {
22 | return this.on(event, listener)
23 | }
24 |
25 | removeListener (event, listener) {
26 | return this.off(event, listener)
27 | }
28 |
29 | removeAllListeners (event) {
30 | if (event === 'shutdown' && process.platform === 'win32') {
31 | this._listeners = []
32 | if (this._shutdownCallback) {
33 | removeWndProcHook()
34 | releaseShutdownBlock()
35 | this._shutdownCallback = null
36 | }
37 | } else {
38 | return _powerMonitor.removeAllListeners(event)
39 | }
40 | }
41 |
42 | on (event, listener) {
43 | if (event === 'shutdown' && process.platform === 'win32') {
44 | if (!this._shutdownCallback) {
45 | this._shutdownCallback = async () => {
46 | await Promise.all(this._listeners.map(fn => fn()))
47 | releaseShutdownBlock()
48 | }
49 | insertWndProcHook(this._shutdownCallback)
50 | acquireShutdownBlock('正在停止 DevSidecar 代理')
51 | }
52 | this._listeners.push(listener)
53 | } else {
54 | return _powerMonitor.on(event, listener)
55 | }
56 | }
57 |
58 | off (event, listener) {
59 | if (event === 'shutdown' && process.platform === 'win32') {
60 | this._listeners = this._listeners.filter(fn => fn !== listener)
61 | } else {
62 | return _powerMonitor.off(event, listener)
63 | }
64 | }
65 |
66 | once (event, listener) {
67 | if (event === 'shutdown' && process.platform === 'win32') {
68 | return this.on(event, listener)
69 | } else {
70 | return _powerMonitor.once(event, listener)
71 | }
72 | }
73 |
74 | emit (event, ...args) {
75 | return _powerMonitor.emit(event, ...args)
76 | }
77 |
78 | eventNames () {
79 | return _powerMonitor.eventNames()
80 | }
81 |
82 | getMaxListeners () {
83 | return _powerMonitor.getMaxListeners()
84 | }
85 |
86 | listeners (event) {
87 | return _powerMonitor.listeners(event)
88 | }
89 |
90 | rawListeners (event) {
91 | return _powerMonitor.rawListeners(event)
92 | }
93 |
94 | listenerCount (event, listener) {
95 | return _powerMonitor.listenerCount(event, listener)
96 | }
97 |
98 | /**
99 | * @returns {boolean}
100 | */
101 | get onBatteryPower () {
102 | return _powerMonitor.onBatteryPower
103 | }
104 |
105 | /**
106 | * @param {number} idleThreshold
107 | * @returns {'active'|'idle'|'locked'|'unknown'}
108 | */
109 | getSystemIdleState (idleThreshold) {
110 | return _powerMonitor.getSystemIdleState(idleThreshold)
111 | }
112 |
113 | /**
114 | * @returns {number}
115 | */
116 | getSystemIdleTime () {
117 | return _powerMonitor.getSystemIdleTime()
118 | }
119 |
120 | /**
121 | * @returns {'unknown'|'nominal'|'fair'|'serious'|'critical'}
122 | */
123 | getCurrentThermalState () {
124 | return _powerMonitor.getCurrentThermalState()
125 | }
126 |
127 | /**
128 | * @returns {boolean}
129 | */
130 | isOnBatteryPower () {
131 | return _powerMonitor.isOnBatteryPower()
132 | }
133 | }
134 |
135 | export const powerMonitor = new PowerMonitor()
136 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/api/open-enable-loopback.js:
--------------------------------------------------------------------------------
1 | /* global __static */
2 | import DevSidecar from '@docmirror/dev-sidecar'
3 | import sudoPrompt from '@vscode/sudo-prompt'
4 | import { join } from 'node:path'
5 | import log from '../../utils/util.log.gui'
6 |
7 | export default {
8 | open () {
9 | const options = {
10 | name: 'EnableLoopback',
11 | icns: process.platform === 'darwin' ? join(__static, 'icon.icns') : undefined,
12 | env: { PARAM: 'VALUE' },
13 | }
14 | const exeFile = DevSidecar.api.shell.extraPath.getEnableLoopbackPath()
15 | const sudoCommand = [`"${exeFile}"`]
16 |
17 | return new Promise((resolve, reject) => {
18 | sudoPrompt.exec(
19 | sudoCommand.join(' '),
20 | options,
21 | (error, _, stderr) => {
22 | if (stderr) {
23 | log.error(`[sudo-prompt] 发生错误: ${stderr}`)
24 | }
25 |
26 | if (error) {
27 | reject(error)
28 | } else {
29 | resolve(undefined)
30 | }
31 | },
32 | )
33 | })
34 | },
35 | }
36 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/auto-start/backend.js:
--------------------------------------------------------------------------------
1 | import DevSidecar from '@docmirror/dev-sidecar'
2 |
3 | async function setAutoStartForLinux (app, enable = true) {
4 | const path = app.getPath('exe')
5 | if (enable) {
6 | const cmd = `
7 | mkdir -p ~/.config/autostart/
8 | cat >> ~/.config/autostart/dev-sidecar.desktop < {
35 | console.log('auto start', message)
36 | const isLinux = DevSidecar.api.shell.getSystemPlatform() === 'linux'
37 | if (message.value) {
38 | if (isLinux) {
39 | await setAutoStartForLinux(app, true)
40 | } else {
41 | app.setLoginItemSettings({
42 | openAtLogin: true,
43 | openAsHidden: true,
44 | args: [
45 | '--hideWindow',
46 | '"true"',
47 | ],
48 | })
49 | }
50 |
51 | event.sender.send('auto-start', { key: 'enabled', value: true })
52 | } else {
53 | if (isLinux) {
54 | await setAutoStartForLinux(app, false)
55 | } else {
56 | app.setLoginItemSettings({
57 | openAtLogin: false,
58 | openAsHidden: false,
59 | args: [],
60 | })
61 | }
62 |
63 | event.sender.send('auto-start', { key: 'enabled', value: false })
64 | }
65 | })
66 | },
67 | }
68 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/auto-start/front.js:
--------------------------------------------------------------------------------
1 | function install (app, api) {
2 | api.ipc.on('auto-start', (event, message) => {
3 | if (message.value === true) {
4 | app.$message.info('已添加开机自启')
5 | } else {
6 | app.$message.info('已取消开机自启')
7 | }
8 | })
9 | api.autoStart = {
10 | async enabled (value) {
11 | api.ipc.send('auto-start', { key: 'enabled', value })
12 | },
13 | }
14 | }
15 |
16 | export default {
17 | install,
18 | }
19 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/backend.js:
--------------------------------------------------------------------------------
1 | import api from './api/backend'
2 | import autoStart from './auto-start/backend'
3 | import fileSelector from './file-selector/backend'
4 | import tongji from './tongji/backend'
5 | import update from './update/backend'
6 | import log from '../utils/util.log.gui'
7 |
8 | const modules = {
9 | api, // 核心接口模块
10 | fileSelector, // 文件选择模块
11 | tongji, // 统计模块
12 | update, // 自动更新
13 | autoStart,
14 | }
15 | export default {
16 | install (context) {
17 | for (const module in modules) {
18 | log.info('install module:', module)
19 | modules[module].install(context)
20 | }
21 | },
22 | ...modules,
23 | }
24 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/error/front.js:
--------------------------------------------------------------------------------
1 | function install (app, api) {
2 | api.ipc.on('error.core', (event, message) => {
3 | console.error('view on error', message)
4 | const key = message.key
5 | if (key === 'server') {
6 | handleServerStartError(message, message.error, app, api)
7 | }
8 | })
9 | api.ipc.on('error', (event, message) => {
10 | console.error('error', event, message)
11 | })
12 | }
13 |
14 | function handleServerStartError (message, err, app, api) {
15 | if (message.value === 'EADDRINUSE') {
16 | app.$confirm({
17 | title: '端口被占用,代理服务启动失败',
18 | content: '是否要杀掉占用进程?您也可以点击取消,然后前往加速服务->基本设置中修改代理端口',
19 | onOk () {
20 | // TODO 杀掉进程
21 | api.config.get().then((config) => {
22 | console.log('config:', config)
23 | api.shell.killByPort({ port: config.server.port }).then((ret) => {
24 | app.$message.info('杀掉进程成功,请重试开启代理服务')
25 | })
26 | })
27 | },
28 | onCancel () {
29 | console.log('Cancel')
30 | },
31 | })
32 | } else {
33 | app.$message.error(`加速服务启动失败:${message.message}`)
34 | }
35 | }
36 |
37 | export default {
38 | install,
39 | }
40 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/file-selector/backend.js:
--------------------------------------------------------------------------------
1 | export default {
2 | install (context) {
3 | const { ipcMain, dialog, log } = context
4 | ipcMain.on('file-selector', (event, message) => {
5 | if (message.key === 'open') {
6 | /**
7 | * @type {Electron.OpenDialogOptions}
8 | */
9 | const options = message.options || {}
10 | if (options.properties == null || options.properties.length === 0) {
11 | options.properties = ['openFile']
12 | }
13 |
14 | dialog.showOpenDialog(options).then((result) => {
15 | if (result.canceled) {
16 | event.sender.send('file-selector', { key: 'canceled' })
17 | } else {
18 | event.sender.send('file-selector', { key: 'selected', value: result.filePaths })
19 | }
20 | }).catch((err) => {
21 | log.error('选择文件失败:', err)
22 | event.sender.send('file-selector', { key: 'error', error: err })
23 | })
24 | }
25 | })
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/file-selector/front.js:
--------------------------------------------------------------------------------
1 | function install (app, api) {
2 | api.fileSelector = {
3 |
4 | /**
5 | * 打开文件选择框
6 | *
7 | * 支持传参方式:
8 | * 1. open(String defaultPath)
9 | * 2. open(String defaultPath, String properties)
10 | * 3. open(null, String properties)
11 | * 4. open(String defaultPath, Object options)
12 | * 5. open(Object options)
13 | *
14 | * @param value
15 | * @param {Electron.OpenDialogOptions} options
16 | * @returns {Promise} promise
17 | */
18 | open (value = null, options = null) {
19 | if (options == null && value && typeof value !== 'string') {
20 | options = { ...value }
21 | value = null
22 | } else {
23 | if (typeof options === 'string') {
24 | if (options === 'dir') {
25 | options = 'openDirectory'
26 | } else if (options === 'file') {
27 | options = 'openFile'
28 | }
29 |
30 | options = { properties: [options] } // options 为字符串时,视为 properties 属性的值
31 | } else {
32 | options = options || {}
33 | }
34 | }
35 |
36 | // 如果没有 defaultPath,则使用 value 作为 defaultPath
37 | if (!options.defaultPath && value && typeof value === 'string') {
38 | options.defaultPath = value
39 | }
40 |
41 | return new Promise((resolve, reject) => {
42 | api.ipc.send('file-selector', { key: 'open', options })
43 | api.ipc.on('file-selector', (event, message) => {
44 | console.log('selector', message)
45 | if (message.key === 'selected') {
46 | resolve(message.value)
47 | } else if (message.key === 'canceled') {
48 | resolve('') // 没有选择文件
49 | } else if (message.key === 'error') {
50 | reject(message.error)
51 | } else {
52 | reject(new Error('未知的响应'))
53 | }
54 | api.ipc.on('file-selector', () => {})
55 | })
56 | })
57 | },
58 | }
59 | }
60 |
61 | export default {
62 | install,
63 | }
64 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/front.js:
--------------------------------------------------------------------------------
1 | // import api from './api/front'
2 | import autoStart from './auto-start/front'
3 | import error from './error/front'
4 | import fileSelector from './file-selector/front'
5 | import onClose from './on-close/front'
6 | import tongji from './tongji/front'
7 | import update from './update/front'
8 |
9 | const modules = {
10 | // api, // 核心接口模块
11 | error,
12 | fileSelector, // 文件选择模块
13 | tongji, // 统计模块
14 | update, // 自动更新
15 | autoStart,
16 | onClose,
17 | }
18 | export default {
19 | install (app, api, router) {
20 | for (const module in modules) {
21 | modules[module].install(app, api, router)
22 | }
23 | },
24 | ...modules,
25 | }
26 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/mitmproxy.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const path = require('node:path')
3 | const server = require('@docmirror/mitmproxy')
4 | const jsonApi = require('@docmirror/mitmproxy/src/json')
5 | const log = require('@docmirror/mitmproxy/src/utils/util.log.server') // 当前脚本是在 server 的进程中执行的,所以使用 mitmproxy 中的logger
6 |
7 | const configPath = process.argv[2]
8 | const configJson = fs.readFileSync(configPath)
9 | log.info('读取 running.json by gui bridge 成功:', configPath)
10 | let config
11 | try {
12 | config = jsonApi.parse(configJson.toString())
13 | } catch (e) {
14 | log.error(`running.json 文件内容格式不正确,文件路径:${configPath},文件内容: ${configJson.toString()}, error:`, e)
15 | config = {}
16 | }
17 | // const scriptDir = '../extra/scripts/'
18 | // config.setting.script.defaultDir = path.join(__dirname, scriptDir)
19 | // const pacFilePath = '../extra/pac/pac.txt'
20 | // config.plugin.overwall.pac.customPacFilePath = path.join(__dirname, pacFilePath)
21 | config.setting.rootDir = path.join(__dirname, '../')
22 | log.info(`start mitmproxy by gui bridge, configPath: ${configPath}`)
23 | server.start(config)
24 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/on-close/front.js:
--------------------------------------------------------------------------------
1 | let closeType = 2
2 | let doSave = false
3 |
4 | function install (app, api) {
5 | api.ipc.on('close.showTip', (event, message) => {
6 | console.info('ipc channel: "close.showTip", event:', event, ', message:', message)
7 | function onRadioChange (event) {
8 | closeType = event.target.value
9 | }
10 | function onCheckChange (event) {
11 | doSave = event.target.checked
12 | }
13 | app.$confirm({
14 | title: '关闭策略',
15 | content: (h) => (
16 |
17 |
18 |
19 | 直接关闭
20 | 最小化到系统托盘
21 |
22 |
23 |
24 |
25 | 记住本次选择,不再提示
26 |
27 |
28 |
29 | 提示:打开窗口的快捷键为
30 | {message.showHideShortcut || '无'}
31 |
32 |
33 | ),
34 | async onOk () {
35 | console.log('OK. closeType=', closeType, ', doSave:', doSave)
36 | if (doSave) {
37 | await api.config.update({ app: { closeStrategy: closeType } })
38 | }
39 | api.ipc.send('close', { key: 'selected', value: closeType })
40 | },
41 | onCancel () {
42 | console.log('Cancel. closeType=', closeType)
43 | },
44 | })
45 | })
46 | }
47 |
48 | export default {
49 | install,
50 | }
51 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/tongji/backend.js:
--------------------------------------------------------------------------------
1 | /**
2 | * first step
3 | * @param {*} ipcMain
4 | */
5 | function ebtMain (ipcMain) {
6 | const isDevelopment = process.env.NODE_ENV !== 'production'
7 | const request = require('request')
8 | /* istanbul ignore else */
9 | if (!(ipcMain && ipcMain.on)) {
10 | throw new TypeError('require ipcMain')
11 | }
12 |
13 | // step 2
14 | ipcMain.on('electron-baidu-tongji-message', (event, arg) => {
15 | // electron 生产模式下是直接请求文件系统,没有 http 地址
16 | // 前台拿不到 hm.js 的内容
17 | request({
18 | url: `https://hm.baidu.com/hm.js?${arg}`,
19 | method: 'GET',
20 | headers: {
21 | Referer: 'https://hm.baidu.com/',
22 | },
23 | }, (err, response, body) => {
24 | if (err) {
25 | console.error('百度统计请求出错', err)
26 | return
27 | }
28 | const rource = '(h.c.b.su=h.c.b.u||document.location.href),h.c.b.u=f.protocol+"//"+document.location.host+'
29 | /* istanbul ignore else */
30 | if (body && body.includes(rource)) {
31 | // step 3
32 | let text = body
33 |
34 | /* istanbul ignore else */
35 | if (!isDevelopment) {
36 | // 百度统计可能改规则了,不统计 file:// 开始的请求
37 | // 这里强制替换为 https
38 | const target = '(h.c.b.su=h.c.b.u||"https://"+c.dm[0]+a[1]),h.c.b.u="https://"+c.dm[0]+'
39 | const target2 = '"https://"+c.dm[0]+window.location.pathname+window.location.hash'
40 | text = body.replace(rource, target).replace(/window.location.href/g, target2)
41 | }
42 | console.log('baidu tonji: ret')
43 | event.sender.send('electron-baidu-tongji-reply', { text, isDevelopment })
44 | }
45 | })
46 | })
47 | }
48 |
49 | export default {
50 | install (context) {
51 | ebtMain(context.ipcMain)
52 | },
53 | }
54 |
--------------------------------------------------------------------------------
/packages/gui/src/bridge/tongji/front.js:
--------------------------------------------------------------------------------
1 | /**
2 | * second step
3 | * @param {*} ipcRenderer
4 | * @param {*} siteId
5 | * @param {*} router
6 | */
7 | function ebtRenderer (ipcRenderer, siteId, router) {
8 | /* istanbul ignore else */
9 | if (!(ipcRenderer && ipcRenderer.on && ipcRenderer.send)) {
10 | throw new TypeError('require ipcRenderer')
11 | }
12 |
13 | /* istanbul ignore else */
14 | if (!(siteId && typeof siteId === 'string')) {
15 | throw new TypeError('require siteId')
16 | }
17 |
18 | // step 4
19 | ipcRenderer.on('electron-baidu-tongji-reply', (_, { text, isDevelopment }) => {
20 | console.log('electron-baidu-tongji-reply')
21 | /* istanbul ignore else */
22 | if (isDevelopment) {
23 | document.body.classList.add('electron-baidu-tongji_dev')
24 | }
25 |
26 | window._hmt = window._hmt || []
27 |
28 | const hm = document.createElement('script')
29 | hm.text = text
30 |
31 | const head = document.getElementsByTagName('head')[0]
32 | head.appendChild(hm)
33 |
34 | // Vue单页应用时,监听router的每次变化
35 | // 把虚拟的url地址赋给百度统计的API接口
36 |
37 | /* istanbul ignore else */
38 | if (router && router.beforeEach) {
39 | router.beforeEach((to, _, next) => {
40 | /* istanbul ignore else */
41 | if (to.path) {
42 | window._hmt.push(['_trackPageview', `/#${to.fullPath}`])
43 | console.log('baidu trace', to.fullPath)
44 | }
45 |
46 | next()
47 | })
48 | }
49 | })
50 |
51 | // step 1
52 | ipcRenderer.send('electron-baidu-tongji-message', siteId)
53 | }
54 |
55 | export default {
56 | install (app, api, router) {
57 | const BAIDU_SITE_ID = 'f2d170ce560aef0005b689f28697f852'
58 | // 百度统计
59 | const { ipcRenderer } = require('electron')
60 | ebtRenderer(ipcRenderer, BAIDU_SITE_ID, router)
61 | },
62 | ebtRenderer,
63 | }
64 |
--------------------------------------------------------------------------------
/packages/gui/src/main.js:
--------------------------------------------------------------------------------
1 | import antd from 'ant-design-vue'
2 | import Vue from 'vue'
3 | import VueRouter from 'vue-router'
4 | import SearchBar from 'search-bar-vue2'
5 | import { ipcRenderer } from 'electron'
6 | import view from './view'
7 | import App from './view/App.vue'
8 | import DsContainer from './view/components/container'
9 | import routes from './view/router'
10 | import 'ant-design-vue/dist/antd.css'
11 | import './view/style/index.scss'
12 | import './view/style/theme/dark.scss' // 暗色主题
13 |
14 | try {
15 | window.onerror = (message, source, lineno, colno, error) => {
16 | ipcRenderer.send(`[ERROR] JavaScript脚本异常:Error in ${source} at line ${lineno}: ${message}`, error)
17 | }
18 | } catch (e) {
19 | console.error('监听 window.onerror 出现异常:', e)
20 | }
21 |
22 | try {
23 | console.info('main.js start')
24 | ipcRenderer.send('main.js start')
25 |
26 | Vue.config.productionTip = false
27 | Vue.use(antd)
28 | Vue.use(VueRouter)
29 | Vue.use(SearchBar)
30 | Vue.component(DsContainer)
31 | // 3. 创建 router 实例,然后传 `routes` 配置
32 | // 你还可以传别的配置参数, 不过先这么简单着吧。
33 | const router = new VueRouter({
34 | routes, // (缩写) 相当于 routes: routes
35 | })
36 | const app = new Vue({
37 | router,
38 | render: h => h(App),
39 | })
40 | view.initApi(app).then(async (api) => {
41 | // 初始化status
42 | try {
43 | await view.initPre(Vue, api)
44 | app.$mount('#app')
45 | view.initModules(app, router)
46 | } catch (e) {
47 | console.error('view初始化出现未知异常:', e)
48 | ipcRenderer.send('view初始化出现未知异常:', e)
49 | }
50 | })
51 |
52 | // fix vue-router NavigationDuplicated
53 | const VueRouterPush = VueRouter.prototype.push
54 | VueRouter.prototype.push = function push (location) {
55 | return VueRouterPush.call(this, location).catch(err => err)
56 | }
57 | const VueRouterReplace = VueRouter.prototype.replace
58 | VueRouter.prototype.replace = function replace (location) {
59 | return VueRouterReplace.call(this, location).catch(err => err)
60 | }
61 |
62 | console.info('main.js finished')
63 | ipcRenderer.send('main.js finished')
64 | } catch (e) {
65 | console.error('页面加载出现未知异常:', e)
66 | ipcRenderer.send('[ERROR] 页面加载出现未知异常:', e)
67 | }
68 |
--------------------------------------------------------------------------------
/packages/gui/src/utils/util.apppath.js:
--------------------------------------------------------------------------------
1 | import os from 'node:os'
2 | import path from 'node:path'
3 | import log from './util.log.gui'
4 |
5 | function getSystemPlatform (throwIfUnknown = false) {
6 | switch (os.platform()) {
7 | case 'darwin':
8 | return 'mac'
9 | case 'linux':
10 | return 'linux'
11 | case 'win32':
12 | return 'windows'
13 | case 'win64':
14 | return 'windows'
15 | default:
16 | log.error(`UNKNOWN OS TYPE: ${os.platform()}`)
17 | if (throwIfUnknown) {
18 | throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
19 | } else {
20 | return 'unknown-os'
21 | }
22 | }
23 | }
24 |
25 | export default {
26 | getAppRootPath (app) {
27 | const exePath = app.getPath('exe')
28 | if (getSystemPlatform() === 'mac') {
29 | return path.join(exePath, '../../')
30 | }
31 | return path.join(exePath, '../')
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/packages/gui/src/utils/util.log.gui.js:
--------------------------------------------------------------------------------
1 | const loggerFactory = require('@docmirror/dev-sidecar/src/utils/util.logger')
2 |
3 | const logger = loggerFactory.getLogger('gui')
4 |
5 | module.exports = logger
6 |
--------------------------------------------------------------------------------
/packages/gui/src/view/api.js:
--------------------------------------------------------------------------------
1 | import { ipcRenderer, shell } from 'electron'
2 | import lodash from 'lodash'
3 | import path from 'node:path'
4 |
5 | let inited = false
6 | let apiObj = null
7 | export function apiInit (app) {
8 | const invoke = (api, args) => {
9 | return ipcRenderer.invoke('apiInvoke', [api, args]).catch((e) => {
10 | app.$notification.error({
11 | message: 'Api invoke error',
12 | description: e.message,
13 | })
14 | })
15 | }
16 | const send = (channel, message) => {
17 | console.log('ipcRenderer.send, channel=', channel, ', message=', message)
18 | return ipcRenderer.send(channel, message)
19 | }
20 |
21 | apiObj = {
22 | ipc: {
23 | on (channel, callback) {
24 | ipcRenderer.on(channel, callback)
25 | },
26 | removeAllListeners (channel) {
27 | ipcRenderer.removeAllListeners(channel)
28 | },
29 | invoke,
30 | postMessage (channel, ...args) {
31 | ipcRenderer.postMessage(channel, ...args)
32 | },
33 | send,
34 | async openExternal (href) {
35 | await shell.openExternal(href)
36 | },
37 | openPath (file) {
38 | shell.openPath(path.resolve(file))
39 | },
40 | },
41 | }
42 |
43 | const bindApi = (api, param1) => {
44 | lodash.set(apiObj, api, (param2) => {
45 | return invoke(api, param2 || param1)
46 | })
47 | }
48 |
49 | if (!inited) {
50 | return invoke('getApiList').then((list) => {
51 | inited = true
52 | for (const item of list) {
53 | bindApi(item)
54 | }
55 | console.log('api inited:', apiObj)
56 | return apiObj
57 | })
58 | }
59 |
60 | return new Promise((resolve) => {
61 | resolve(apiObj)
62 | })
63 | }
64 | export function useApi () {
65 | return apiObj
66 | }
67 |
--------------------------------------------------------------------------------
/packages/gui/src/view/components/container.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
60 |
--------------------------------------------------------------------------------
/packages/gui/src/view/components/mock-input.vue:
--------------------------------------------------------------------------------
1 |
5 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/packages/gui/src/view/components/setup-ca.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
69 |
70 | {{ title }}
71 |
72 | 点此去安装
73 |
74 |
75 | 为什么要安装证书?
76 |
77 |
78 |
79 | 本应用在非“安全模式”下必须安装和信任CA根证书,该证书是应用启动时本地随机生成的
80 |
81 |
82 | 1、点击右上角“点此去安装按钮”,打开钥匙串,选择”系统“
83 | 2、然后按如下图步骤将随机生成的根证书设置为始终信任
84 | 3、可能需要重新启动应用和浏览器才能生效
85 | 4、注意:如果出现无法导入提示时,先点一下钥匙串的左边切换到“系统”栏,然后再重新安装证书即可
86 |
87 |
88 | 1、点击右上角“点此去安装按钮”,将自动安装到系统证书库中
89 | 2、火狐、chrome等浏览器不走系统证书,需要手动安装(下图以chrome为例安装根证书)
90 |
91 |
92 | 1、点击右上角“点此去安装按钮”,打开证书
93 | 2、然后按如下图步骤将根证书添加到信任的根证书颁发机构
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/packages/gui/src/view/components/tree-node.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
27 |
28 |
--------------------------------------------------------------------------------
/packages/gui/src/view/composables/theme.js:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 |
3 | export const colorTheme = ref('dark')
4 |
--------------------------------------------------------------------------------
/packages/gui/src/view/index.js:
--------------------------------------------------------------------------------
1 | import modules from '../bridge/front'
2 | import { apiInit, useApi } from './api'
3 | import status from './status'
4 |
5 | export default {
6 | initApi: apiInit,
7 | async initPre (Vue, api) {
8 | Vue.prototype.$api = api
9 | const setting = await api.setting.load()
10 | Vue.prototype.$global = {
11 | setting,
12 | config: await api.config.get(),
13 | }
14 | await status.install(api)
15 | },
16 | initModules (app, router) {
17 | const api = useApi()
18 | modules.install(app, api, router)
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/packages/gui/src/view/pages/help.vue:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
27 | 帮助中心
28 |
29 |
30 | 反馈问题
31 | 查看日志
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/packages/gui/src/view/router/index.js:
--------------------------------------------------------------------------------
1 | import Index from '../pages/index'
2 | import Git from '../pages/plugin/git'
3 | import Node from '../pages/plugin/node'
4 | import Overwall from '../pages/plugin/overwall'
5 | import Pip from '../pages/plugin/pip'
6 | import Proxy from '../pages/proxy'
7 | import Server from '../pages/server'
8 | import Setting from '../pages/setting'
9 | import Help from '../pages/help'
10 |
11 | const routes = [
12 | { path: '/', redirect: '/index' },
13 | { path: '/index', component: Index },
14 | { path: '/server', component: Server },
15 | { path: '/proxy', component: Proxy },
16 | { path: '/setting', component: Setting },
17 | { path: '/help', component: Help },
18 | { path: '/plugin/node', component: Node },
19 | { path: '/plugin/git', component: Git },
20 | { path: '/plugin/pip', component: Pip },
21 | { path: '/plugin/overwall', component: Overwall },
22 | ]
23 |
24 | export default routes
25 |
--------------------------------------------------------------------------------
/packages/gui/src/view/router/menu.js:
--------------------------------------------------------------------------------
1 | export default function createMenus (app) {
2 | const plugins = [
3 | { title: 'NPM加速', path: '/plugin/node', icon: 'like' },
4 | { title: 'Git.exe代理', path: '/plugin/git', icon: 'github' },
5 | { title: 'PIP加速', path: '/plugin/pip', icon: 'bulb' },
6 | ]
7 | const menus = [
8 | { title: '首页', path: '/index', icon: 'home' },
9 | { title: '加速服务', path: '/server', icon: 'thunderbolt' },
10 | { title: '系统代理', path: '/proxy', icon: 'deployment-unit' },
11 | { title: '设置', path: '/setting', icon: 'setting' },
12 | { title: '帮助中心', path: '/help', icon: 'star' },
13 | {
14 | title: '应用',
15 | path: '/plugin',
16 | icon: 'api',
17 | children: plugins,
18 | },
19 | ]
20 | if (app.$global && app.$global.setting && app.$global.setting.overwall) {
21 | plugins.push({ title: '增强功能', path: '/plugin/overwall', icon: 'global' })
22 | }
23 | return menus
24 | }
25 |
--------------------------------------------------------------------------------
/packages/gui/src/view/status.js:
--------------------------------------------------------------------------------
1 | import lodash from 'lodash'
2 | import Vue from 'vue'
3 |
4 | const status = {
5 | server: {
6 | enabled: false,
7 | },
8 | proxy: {
9 | enabled: false,
10 | },
11 | plugin: {
12 | node: {},
13 | },
14 | }
15 | async function install (api) {
16 | api.ipc.on('status', (event, message) => {
17 | console.log('view on status', event, message)
18 | const value = message.value
19 | const key = message.key
20 | lodash.set(status, key, value)
21 | })
22 | const basicStatus = await api.status.get()
23 | lodash.merge(status, basicStatus)
24 | Vue.prototype.$status = status
25 | return status
26 | }
27 | export default {
28 | install,
29 | status,
30 | }
31 |
--------------------------------------------------------------------------------
/packages/gui/src/view/style/index.scss:
--------------------------------------------------------------------------------
1 | .footer-bar {
2 | padding: 10px;
3 | text-align: right;
4 | border-top: #eee 1px solid;
5 | }
6 |
7 | .flex-l-r {
8 | align-content: center;
9 | display: flex;
10 | justify-content: flex-end;
11 | align-items: center;
12 |
13 | & > a {
14 | align-content: center;
15 | display: flex;
16 | align-items: center;
17 | }
18 | }
19 |
20 | span.ant-input {
21 | white-space: nowrap;
22 | overflow: hidden;
23 | vertical-align: middle;
24 | }
25 |
26 | .mr10 {
27 | margin-right: 10px;
28 | }
29 |
30 | .mt-1 {
31 | margin-top: -1px;
32 | }
33 | .mt-2 {
34 | margin-top: -2px;
35 | }
36 | .mt10 {
37 | margin-top: 10px;
38 | }
39 | .mt20 {
40 | margin-top: 20px;
41 | }
42 |
43 | .ml5 {
44 | margin-left: 5px;
45 | }
46 | .ml10 {
47 | margin-left: 10px;
48 | }
49 |
50 | ol {
51 | margin-block-start: 0em;
52 | margin-block-end: 0em;
53 | padding-inline-start: 20px;
54 | }
55 |
56 | .form-help {
57 | font-size: 12px;
58 | line-height: 15px;
59 | color: #a1a1a1;
60 |
61 | i {
62 | font-family: 'Microsoft YaHei', serif;
63 | font-style: normal;
64 | font-weight: bold;
65 | }
66 |
67 | code {
68 | padding: 0 0.4em;
69 | }
70 | }
71 |
72 | code {
73 | font-size: 85%;
74 | font-style: normal;
75 | border-radius: 6px;
76 | color: #888;
77 | background-color: #f1f1f1;
78 | margin-left: 0.2em;
79 | margin-right: 0.2em;
80 | padding: 0.2em 0.4em;
81 | white-space: break-spaces;
82 | }
83 |
84 | .ace_search_form .ace_searchbtn {
85 | width: auto;
86 | min-width: 27px;
87 | }
88 |
89 | .ant-radio-button-wrapper {
90 | margin-bottom: 3px;
91 | }
92 |
93 | .ant-form-item-control {
94 | line-height: 37px;
95 | }
96 |
97 | hr {
98 | border-width: 2px 0 0 0;
99 | border-style: solid;
100 | border-color: #eee;
101 | width: 100%;
102 | }
103 |
104 | .ant-modal-content {
105 | background-color: #fbfbfb;
106 | }
107 |
108 | .restore-factory-settings {
109 | div {
110 | padding-left: 1em;
111 | }
112 | span {
113 | display: inline-block;
114 | background-color: #eee;
115 | padding: 2px 5px;
116 | margin: 0 5px 5px 5px;
117 | }
118 | }
119 |
120 | .help-list {
121 | ul {
122 | padding-left: 10px;
123 |
124 | li {
125 | list-style: none;
126 | line-height: 30px;
127 |
128 | div {
129 | white-space: nowrap;
130 | overflow: hidden;
131 | text-overflow: ellipsis;
132 | font-size: 14px;
133 | }
134 |
135 | a {
136 | color: #3990e0;
137 | }
138 | a:hover {
139 | text-decoration: underline;
140 | color: #2c9be5;
141 | }
142 | }
143 |
144 | // 嵌套列表
145 | ul {
146 | padding-left: 20px;
147 | }
148 | }
149 |
150 | ul:first-child li:first-child div:first-child.title1 {
151 | margin-top: 0;
152 | }
153 |
154 | .title1 {
155 | font-size: 18px;
156 | font-weight: bold;
157 | border-bottom: 1px solid #eee;
158 | margin-top: 12px;
159 | margin-bottom: 5px;
160 | padding-bottom: 5px;
161 | padding-left: 5px;
162 | }
163 |
164 | .title2 {
165 | font-size: 16px;
166 | font-weight: bold;
167 | margin-top: 10px;
168 | }
169 |
170 | .console {
171 | font-family: Consolas, arial, serif;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/packages/mitmproxy/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src')
2 |
--------------------------------------------------------------------------------
/packages/mitmproxy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@docmirror/mitmproxy",
3 | "version": "2.0.0",
4 | "private": false,
5 | "description": "",
6 | "author": "docmirror.cn",
7 | "license": "MPL-2.0",
8 | "keywords": [
9 | "dev-sidecar"
10 | ],
11 | "main": "src/index.js",
12 | "scripts": {
13 | "test": "mocha"
14 | },
15 | "dependencies": {
16 | "@docmirror/dev-sidecar": "workspace:*",
17 | "agentkeepalive": "^4.5.0",
18 | "axios": "^1.7.7",
19 | "baidu-aip-sdk": "^4.16.16",
20 | "dns-over-http": "^0.2.0",
21 | "dns-over-tls": "^0.0.9",
22 | "is-browser": "^2.1.0",
23 | "json5": "^2.2.3",
24 | "lodash": "^4.17.21",
25 | "lru-cache": "^7.15.0",
26 | "node-forge": "^1.3.1",
27 | "stream-throttle": "^0.1.3",
28 | "through2": "^4.0.2",
29 | "tunnel-agent": "^0.6.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/json.js:
--------------------------------------------------------------------------------
1 | const logOrConsole = require('@docmirror/dev-sidecar/src/utils/util.log-or-console')
2 | let JSON5 = require('json5')
3 | if (JSON5.default) {
4 | JSON5 = JSON5.default
5 | }
6 |
7 | module.exports = {
8 | parse (str, defaultValue) {
9 | if (str == null || str.length < 2) {
10 | return defaultValue || {}
11 | }
12 |
13 | str = str.toString()
14 |
15 | if (defaultValue != null) {
16 | try {
17 | return JSON5.parse(str)
18 | } catch (e) {
19 | logOrConsole.error(`JSON5解析失败: ${e.message},JSON内容:\r\n`, str)
20 | return defaultValue
21 | }
22 | } else {
23 | return JSON5.parse(str)
24 | }
25 | },
26 | stringify (obj) {
27 | return JSON.stringify(obj, null, '\t')
28 | },
29 |
30 | // 仅用于记录日志时使用
31 | stringify2 (obj) {
32 | try {
33 | return JSON.stringify(obj)
34 | } catch {
35 | try {
36 | return JSON5.stringify(obj)
37 | } catch {
38 | return obj
39 | }
40 | }
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/choice/RequestCounter.js:
--------------------------------------------------------------------------------
1 | const { ChoiceCache } = require('./index')
2 |
3 | module.exports = new ChoiceCache()
4 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/dns/https.js:
--------------------------------------------------------------------------------
1 | const { promisify } = require('node:util')
2 | const doh = require('dns-over-http')
3 | const BaseDNS = require('./base')
4 |
5 | const dohQueryAsync = promisify(doh.query)
6 |
7 | module.exports = class DNSOverHTTPS extends BaseDNS {
8 | constructor (dnsName, cacheSize, preSetIpList, dnsServer) {
9 | super(dnsName, 'HTTPS', cacheSize, preSetIpList)
10 | this.dnsServer = dnsServer
11 | }
12 |
13 | async _doDnsQuery (hostname) {
14 | return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/dns/index.js:
--------------------------------------------------------------------------------
1 | const matchUtil = require('../../utils/util.match')
2 | const DNSOverPreSetIpList = require('./preset.js')
3 | const DNSOverHTTPS = require('./https.js')
4 | const DNSOverTLS = require('./tls.js')
5 | const DNSOverTCP = require('./tcp.js')
6 | const DNSOverUDP = require('./udp.js')
7 |
8 | module.exports = {
9 | initDNS (dnsProviders, preSetIpList) {
10 | const dnsMap = {}
11 |
12 | // 创建普通的DNS
13 | for (const provider in dnsProviders) {
14 | const conf = dnsProviders[provider]
15 |
16 | // 获取DNS服务器
17 | let server = conf.server || conf.host
18 | if (server != null) {
19 | server = server.replace(/\s+/, '')
20 | }
21 | if (!server) {
22 | continue
23 | }
24 |
25 | // 获取DNS类型
26 | let type = conf.type
27 | if (type == null) {
28 | if (server.startsWith('https://') || server.startsWith('http://')) {
29 | type = 'https'
30 | } else if (server.startsWith('tls://')) {
31 | type = 'tls'
32 | } else if (server.startsWith('tcp://')) {
33 | type = 'tcp'
34 | } else if (server.includes('://') && !server.startsWith('udp://')) {
35 | throw new Error(`Unknown type DNS: ${server}, provider: ${provider}`)
36 | } else {
37 | type = 'udp'
38 | }
39 | } else {
40 | type = type.replace(/\s+/, '').toLowerCase()
41 | }
42 |
43 | // 创建DNS对象
44 | if (type === 'https' || type === 'doh' || type === 'dns-over-https') {
45 | if (!server.includes('/')) {
46 | server = `https://${server}/dns-query`
47 | }
48 |
49 | // 基于 https
50 | dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server)
51 | } else {
52 | // 获取DNS端口
53 | let port = conf.port
54 |
55 | // 处理带协议的DNS服务地址
56 | if (server.includes('://')) {
57 | server = server.split('://')[1]
58 | }
59 | // 处理带端口的DNS服务地址
60 | if (port == null && server.includes(':')) {
61 | [server, port] = server.split(':')
62 | }
63 |
64 | if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
65 | // 基于 tls
66 | dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername)
67 | } else if (type === 'tcp' || type === 'dns-over-tcp') {
68 | // 基于 tcp
69 | dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
70 | } else {
71 | // 基于 udp
72 | dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port)
73 | }
74 | }
75 | }
76 |
77 | // 创建预设IP的DNS
78 | dnsMap.PreSet = new DNSOverPreSetIpList(preSetIpList)
79 |
80 | return dnsMap
81 | },
82 | hasDnsLookup (dnsConfig, hostname) {
83 | // 先匹配 预设IP配置
84 | const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList(hasDnsLookup)')
85 | if (hostnamePreSetIpList) {
86 | return dnsConfig.dnsMap.PreSet
87 | }
88 |
89 | // 再匹配 DNS映射配置
90 | const providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName')
91 |
92 | // 由于DNS中的usa已重命名为cloudflare,所以做以下处理,为了向下兼容
93 | if (providerName === 'usa' && dnsConfig.dnsMap.usa == null && dnsConfig.dnsMap.cloudflare != null) {
94 | return dnsConfig.dnsMap.cloudflare
95 | }
96 |
97 | if (providerName) {
98 | return dnsConfig.dnsMap[providerName]
99 | }
100 | },
101 | }
102 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/dns/preset.js:
--------------------------------------------------------------------------------
1 | const BaseDNS = require('./base')
2 |
3 | module.exports = class DNSOverPreSetIpList extends BaseDNS {
4 | constructor (preSetIpList) {
5 | super('PreSet', 'PreSet', null, preSetIpList)
6 | }
7 |
8 | async _lookup (_hostname) {
9 | return []
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/dns/tcp.js:
--------------------------------------------------------------------------------
1 | const net = require('node:net')
2 | const { Buffer } = require('node:buffer')
3 | const dnsPacket = require('dns-packet')
4 | const randi = require('random-int')
5 | const BaseDNS = require('./base')
6 |
7 | const defaultPort = 53 // UDP类型的DNS服务默认端口号
8 |
9 | module.exports = class DNSOverTCP extends BaseDNS {
10 | constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
11 | super(dnsName, 'TCP', cacheSize, preSetIpList)
12 | this.dnsServer = dnsServer
13 | this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
14 | }
15 |
16 | _doDnsQuery (hostname) {
17 | return new Promise((resolve, reject) => {
18 | // 构造 DNS 查询报文
19 | const packet = dnsPacket.encode({
20 | flags: dnsPacket.RECURSION_DESIRED,
21 | type: 'query',
22 | id: randi(0x0, 0xFFFF),
23 | questions: [{
24 | type: 'A',
25 | name: hostname,
26 | }],
27 | })
28 |
29 | // --- TCP 查询 ---
30 | const tcpClient = net.createConnection({
31 | host: this.dnsServer,
32 | port: this.dnsServerPort,
33 | }, () => {
34 | // TCP DNS 报文前需添加 2 字节长度头
35 | const lengthBuffer = Buffer.alloc(2)
36 | lengthBuffer.writeUInt16BE(packet.length)
37 | tcpClient.write(Buffer.concat([lengthBuffer, packet]))
38 | })
39 |
40 | tcpClient.once('data', (data) => {
41 | const length = data.readUInt16BE(0)
42 | const response = dnsPacket.decode(data.subarray(2, 2 + length))
43 | resolve(response)
44 | tcpClient.end()
45 | })
46 |
47 | tcpClient.once('error', (err) => {
48 | reject(err)
49 | tcpClient.end()
50 | })
51 | })
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/dns/tls.js:
--------------------------------------------------------------------------------
1 | const dnstls = require('dns-over-tls')
2 | const BaseDNS = require('./base')
3 |
4 | const defaultPort = 853
5 |
6 | module.exports = class DNSOverTLS extends BaseDNS {
7 | constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) {
8 | super(dnsName, 'TLS', cacheSize, preSetIpList)
9 | this.dnsServer = dnsServer
10 | this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
11 | this.dnsServerName = dnsServerName
12 | }
13 |
14 | async _doDnsQuery (hostname) {
15 | const options = {
16 | host: this.dnsServer,
17 | port: this.dnsServerPort,
18 | servername: this.dnsServerName || this.dnsServer,
19 |
20 | name: hostname,
21 | klass: 'IN',
22 | type: 'A',
23 | }
24 |
25 | return await dnstls.query(options)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/dns/udp.js:
--------------------------------------------------------------------------------
1 | const dgram = require('node:dgram')
2 | const dnsPacket = require('dns-packet')
3 | const randi = require('random-int')
4 | const BaseDNS = require('./base')
5 |
6 | const defaultPort = 53 // UDP类型的DNS服务默认端口号
7 |
8 | module.exports = class DNSOverUDP extends BaseDNS {
9 | constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
10 | super(dnsName, 'UDP', cacheSize, preSetIpList)
11 | this.dnsServer = dnsServer
12 | this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
13 |
14 | this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']')
15 | this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
16 | }
17 |
18 | _doDnsQuery (hostname) {
19 | return new Promise((resolve, reject) => {
20 | // 构造 DNS 查询报文
21 | const packet = dnsPacket.encode({
22 | flags: dnsPacket.RECURSION_DESIRED,
23 | type: 'query',
24 | id: randi(0x0, 0xFFFF),
25 | questions: [{
26 | type: 'A',
27 | name: hostname,
28 | }],
29 | })
30 |
31 | // 创建客户端
32 | const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
33 | const response = dnsPacket.decode(msg)
34 | resolve(response)
35 | udpClient.close()
36 | })
37 |
38 | // 发送 UDP 查询
39 | udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
40 | if (err) {
41 | reject(err)
42 | udpClient.close()
43 | }
44 | })
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/OPTIONS.js:
--------------------------------------------------------------------------------
1 | const defaultAllowHeaders = '*'
2 | const defaultAllowMethods = 'GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH' // CONNECT、TRACE被认为是不安全的请求,通常不建议允许跨域
3 |
4 | function readConfig (config, defaultConfig) {
5 | if (config) {
6 | if (Object.isArray(config)) {
7 | config = config.join(',')
8 | }
9 | } else {
10 | config = defaultConfig
11 | }
12 | return config
13 | }
14 |
15 | module.exports = {
16 | name: 'options',
17 | priority: 101,
18 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
19 | const { rOptions, log } = context
20 |
21 | // 不是 OPTIONS 请求,或请求头中不含 origin 时,跳过当前拦截器
22 | if (rOptions.method !== 'OPTIONS' || rOptions.headers.origin == null) {
23 | return
24 | }
25 |
26 | // 从请求头中获取跨域相关信息;如果不存在,则从配置中获取的值;如果还不存在,则使用默认值
27 | const allowHeaders = rOptions.headers['access-control-request-headers'] || readConfig(interceptOpt.optionsAllowHeaders, defaultAllowHeaders)
28 | const allowMethods = rOptions.headers['access-control-request-method'] || readConfig(interceptOpt.optionsAllowMethods, defaultAllowMethods)
29 |
30 | const headers = {
31 | // 允许跨域
32 | 'DS-Interceptor': 'options',
33 | 'Access-Control-Allow-Origin': rOptions.headers.origin,
34 | 'Access-Control-Allow-Headers': allowHeaders,
35 | 'Access-Control-Allow-Methods': allowMethods,
36 | 'Access-Control-Max-Age': interceptOpt.optionsMaxAge > 0 ? interceptOpt.optionsMaxAge : 2592000, // 默认有效一个月
37 | 'Date': new Date().toUTCString(),
38 | }
39 |
40 | // 判断是否允许
41 | if (interceptOpt.optionsCredentials !== false && interceptOpt.optionsCredentials !== 'false') {
42 | headers['Access-Control-Allow-Credentials'] = 'true'
43 | }
44 |
45 | res.writeHead(200, headers)
46 | res.end()
47 |
48 | log.info('options intercept:', (rOptions.original || rOptions).url)
49 | return true // true代表请求结束
50 | },
51 | is (interceptOpt) {
52 | return !!interceptOpt.options
53 | },
54 | }
55 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/abort.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'abort',
3 | priority: 103,
4 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
5 | const { rOptions, log } = context
6 |
7 | if (interceptOpt.abort === true || interceptOpt.abort === 'true') {
8 | res.writeHead(403, {
9 | 'Content-Type': 'text/plain; charset=utf-8',
10 | 'DS-Interceptor': 'abort',
11 | })
12 | res.write(
13 | 'DevSidecar 403: Request abort.\n\n'
14 | + ' This request is matched by abort intercept.\n\n'
15 | + ' 因配置abort拦截器,本请求直接返回403禁止访问。',
16 | )
17 | res.end()
18 |
19 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
20 | log.info('abort intercept:', url)
21 | return true // true代表请求结束
22 | } else {
23 | const response = interceptOpt.abort
24 |
25 | // status
26 | const status = response.status || 403
27 | response.status = status
28 |
29 | // body
30 | const body = response.html || response.json || response.script || response.css || response.text || response.body
31 | || `DevSidecar ${status}: Request abort.\n\n`
32 | + ' This request is matched by abort intercept.\n\n'
33 | + ` 因配置abort拦截器,本请求直接返回${status}禁止访问。`
34 |
35 | // headers
36 | const headers = response.headers || {}
37 | response.headers = headers
38 | headers['DS-Interceptor'] = 'abort'
39 | // headers.Content-Type
40 | if (status !== 204) {
41 | // (1)如果没有Content-Type,根据response的内容自动设置
42 | if (!headers['Content-Type']) {
43 | if (response.html != null) {
44 | headers['Content-Type'] = 'text/html'
45 | } else if (response.json != null) {
46 | headers['Content-Type'] = 'application/json'
47 | } else if (response.script != null) {
48 | headers['Content-Type'] = 'application/javascript'
49 | } else if (response.css != null) {
50 | headers['Content-Type'] = 'text/css'
51 | } else {
52 | headers['Content-Type'] = 'text/plain'
53 | }
54 | }
55 | // (2)如果Content-Type没有charset,自动设置为utf-8
56 | if (headers['Content-Type'] != null && !headers['Content-Type'].includes('charset')) {
57 | headers['Content-Type'] += '; charset=utf-8'
58 | }
59 | }
60 | // headers.Access-Control-Allow-*:避免跨域问题
61 | if (rOptions.headers.origin && !headers['Access-Control-Allow-Origin']) {
62 | headers['Access-Control-Allow-Credentials'] = 'true'
63 | headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
64 | }
65 |
66 | res.writeHead(status, headers)
67 | if (status !== 204) {
68 | res.write(body)
69 | }
70 | res.end()
71 |
72 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
73 | log.info('abort intercept:', url, ', response:', JSON.stringify(response))
74 | return true // true代表请求结束
75 | }
76 | },
77 | is (interceptOpt) {
78 | return !!interceptOpt.abort
79 | },
80 | }
81 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/cacheRequest.js:
--------------------------------------------------------------------------------
1 | function getMaxAge (interceptOpt) {
2 | // 秒
3 | if (interceptOpt.cacheSeconds > 0 || interceptOpt.cacheMaxAge > 0 || interceptOpt.cache > 0) {
4 | return interceptOpt.cacheSeconds || interceptOpt.cacheMaxAge || interceptOpt.cache
5 | }
6 | // 分钟
7 | if (interceptOpt.cacheMinutes > 0) {
8 | return interceptOpt.cacheMinutes * 60 // 60:1分钟
9 | }
10 | // 小时
11 | if (interceptOpt.cacheHours > 0) {
12 | return interceptOpt.cacheHours * 3600 // 60 * 60 一小时
13 | }
14 | // 天
15 | if (interceptOpt.cacheDays > 0) {
16 | return interceptOpt.cacheDays * 86400 // 60 * 60 * 24 一天
17 | }
18 | // 星期
19 | if (interceptOpt.cacheWeeks > 0) {
20 | return interceptOpt.cacheWeeks * 604800 // 60 * 60 * 24 * 7 一周
21 | }
22 | // 月
23 | if (interceptOpt.cacheMonths > 0) {
24 | return interceptOpt.cacheMonths * 2592000 // 60 * 60 * 24 * 30 一个月
25 | }
26 | // 年
27 | if (interceptOpt.cacheYears > 0) {
28 | return interceptOpt.cacheYears * 31536000 // 60 * 60 * 24 * 365 一年
29 | }
30 |
31 | return null
32 | }
33 |
34 | // 获取 lastModifiedTime 的方法
35 | function getLastModifiedTimeFromIfModifiedSince (rOptions, log) {
36 | // 获取 If-Modified-Since 和 If-None-Match 用于判断是否命中缓存
37 | const lastModified = rOptions.headers['if-modified-since']
38 | if (lastModified == null || lastModified.length === 0) {
39 | return null // 没有lastModified,返回null
40 | }
41 |
42 | try {
43 | // 尝试解析 lastModified,并获取time
44 | return new Date(lastModified).getTime()
45 | } catch (e) {
46 | // 为数字时,直接返回
47 | if (/\\d+/.test(lastModified)) {
48 | return lastModified - 0
49 | }
50 |
51 | log.warn(`cache intercept: 解析 if-modified-since 失败: '${lastModified}', error:`, e)
52 | }
53 |
54 | return null
55 | }
56 |
57 | module.exports = {
58 | name: 'cacheRequest',
59 | priority: 104,
60 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
61 | const { rOptions, log } = context
62 |
63 | if (rOptions.method !== 'GET') {
64 | return // 非GET请求,不拦截
65 | }
66 |
67 | // 获取 Cache-Control 用于判断是否禁用缓存
68 | const cacheControl = rOptions.headers['cache-control']
69 | if (cacheControl && (cacheControl.includes('no-cache') || cacheControl.includes('no-store'))) {
70 | return // 当前请求指定要禁用缓存,跳过当前拦截器
71 | }
72 | // 获取 Pragma 用于判断是否禁用缓存
73 | const pragma = rOptions.headers.pragma
74 | if (pragma && (pragma.includes('no-cache') || pragma.includes('no-store'))) {
75 | return // 当前请求指定要禁用缓存,跳过当前拦截器
76 | }
77 |
78 | // 最近编辑时间
79 | const lastModifiedTime = getLastModifiedTimeFromIfModifiedSince(rOptions, log)
80 | if (lastModifiedTime == null) {
81 | return // 没有 lastModified,不拦截
82 | }
83 |
84 | // 获取maxAge配置
85 | const maxAge = getMaxAge(interceptOpt)
86 | // 判断缓存是否已过期
87 | const passTime = Date.now() - lastModifiedTime
88 | if (passTime > maxAge * 1000) {
89 | return // 缓存已过期,不拦截
90 | }
91 |
92 | // 缓存未过期,直接拦截请求并响应304
93 | res.writeHead(304, {
94 | 'DS-Interceptor': `cache: ${maxAge}`,
95 | })
96 | res.end()
97 |
98 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
99 | log.info('cache intercept:', url)
100 | return true
101 | },
102 | is (interceptOpt) {
103 | const maxAge = getMaxAge(interceptOpt)
104 | return maxAge != null && maxAge > 0
105 | },
106 | getMaxAge,
107 | }
108 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js:
--------------------------------------------------------------------------------
1 | const proxyApi = require('./proxy')
2 |
3 | module.exports = {
4 | name: 'redirect',
5 | priority: 105,
6 | requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
7 | const { rOptions, log } = context
8 |
9 | // 获取重定向目标地址
10 | const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched)
11 |
12 | res.writeHead(302, {
13 | 'Location': redirect,
14 | 'DS-Interceptor': 'redirect',
15 | })
16 | res.end()
17 |
18 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
19 | log.info(`redirect intercept: ${url} ➜ ${redirect}`)
20 | return true // true代表请求结束
21 | },
22 | is (interceptOpt) {
23 | return interceptOpt.redirect // 如果配置中有redirect,那么这个配置是需要redirect拦截的
24 | },
25 | }
26 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/requestReplace.js:
--------------------------------------------------------------------------------
1 | const REMOVE = '[remove]'
2 |
3 | function replaceRequestHeaders (rOptions, headers, log) {
4 | for (const key in headers) {
5 | let value = headers[key]
6 | if (value === REMOVE) {
7 | value = null
8 | }
9 |
10 | if (value) {
11 | log.debug(`[DS-RequestReplace-Interceptor] replace '${key}': '${rOptions.headers[key.toLowerCase()]}' -> '${value}'`)
12 | rOptions.headers[key.toLowerCase()] = value
13 | } else if (rOptions.headers[key.toLowerCase()]) {
14 | log.debug(`[DS-RequestReplace-Interceptor] remove '${key}': '${rOptions.headers[key.toLowerCase()]}'`)
15 | delete rOptions.headers[key.toLowerCase()]
16 | }
17 | }
18 |
19 | log.debug(`[DS-RequestReplace-Interceptor] 最终headers: \r\n${JSON.stringify(rOptions.headers, null, '\t')}`)
20 | }
21 |
22 | module.exports = {
23 | name: 'requestReplace',
24 | priority: 111,
25 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
26 | const { rOptions, log } = context
27 |
28 | const requestReplaceConfig = interceptOpt.requestReplace
29 |
30 | let actions = ''
31 |
32 | // 替换请求头
33 | if (requestReplaceConfig.headers) {
34 | replaceRequestHeaders(rOptions, requestReplaceConfig.headers, log)
35 | actions += `${actions ? ',' : ''}headers`
36 | }
37 |
38 | // 替换下载文件请求的请求地址(此功能主要是为了方便拦截配置)
39 | // 注:要转换为下载请求,需要 responseReplace 拦截器的配合使用。
40 | if (requestReplaceConfig.doDownload && rOptions.path.match(/DS_DOWNLOAD/i)) {
41 | rOptions.doDownload = true
42 | rOptions.path = rOptions.path.replace(/[?&/]?DS_DOWNLOAD(=[^?&/]+)?$/gi, '')
43 | actions += `${actions ? ',' : ''}path:remove-DS_DOWNLOAD`
44 | }
45 |
46 | res.setHeader('DS-RequestReplace-Interceptor', actions)
47 |
48 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
49 | log.info('requestReplace intercept:', url)
50 | },
51 | is (interceptOpt) {
52 | return !!interceptOpt.requestReplace
53 | },
54 | }
55 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/sni.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'sni',
3 | priority: 123,
4 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
5 | const { rOptions, log } = context
6 |
7 | let unVerifySsl = rOptions.agent.options.rejectUnauthorized === false
8 |
9 | rOptions.servername = interceptOpt.sni
10 | if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
11 | // rOptions.agent.options.rejectUnauthorized = false // 不能直接在agent上进行修改属性值,因为它采用了单例模式,所有请求共用这个对象的
12 | rOptions.agent = rOptions.agent.unVerifySslAgent
13 | unVerifySsl = true
14 | }
15 |
16 | const unVerifySslStr = unVerifySsl ? ', unVerifySsl' : ''
17 | res.setHeader('DS-Interceptor', `sni: ${interceptOpt.sni}${unVerifySslStr}`)
18 |
19 | log.info(`sni intercept: sni replace servername: ${rOptions.hostname} ➜ ${rOptions.servername}${unVerifySslStr}`)
20 | return true
21 | },
22 | is (interceptOpt) {
23 | return !!interceptOpt.sni && !interceptOpt.proxy // proxy生效时,sni不需要生效,因为proxy中也会使用sni覆盖 rOptions.servername
24 | },
25 | }
26 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/success.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'success',
3 | priority: 102,
4 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
5 | const { rOptions, log } = context
6 |
7 | if (interceptOpt.success === true || interceptOpt.success === 'true') {
8 | res.writeHead(200, {
9 | 'Content-Type': 'text/plain; charset=utf-8',
10 | 'DS-Interceptor': 'success',
11 | })
12 | res.write(
13 | 'DevSidecar 200: Request success.\n\n'
14 | + ' This request is matched by success intercept.\n\n'
15 | + ' 因配置success拦截器,本请求直接返回200成功。',
16 | )
17 | res.end()
18 |
19 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
20 | log.info('success intercept:', url)
21 | return true // true代表请求结束
22 | } else {
23 | const response = interceptOpt.success
24 |
25 | // status
26 | const status = response.status || 200
27 | response.status = status
28 |
29 | // body
30 | const body = response.html || response.json || response.script || response.css || response.text || response.body
31 | || `DevSidecar ${status}: Request success.\n\n`
32 | + ' This request is matched by success intercept.\n\n'
33 | + ` 因配置success拦截器,本请求直接返回${status}成功。`
34 |
35 | // headers
36 | const headers = response.headers || {}
37 | response.headers = headers
38 | headers['DS-Interceptor'] = 'success'
39 | // headers.Content-Type
40 | if (status !== 204) {
41 | // (1)如果没有Content-Type,根据response的内容自动设置
42 | if (!headers['Content-Type']) {
43 | if (response.html != null) {
44 | headers['Content-Type'] = 'text/html'
45 | } else if (response.json != null) {
46 | headers['Content-Type'] = 'application/json'
47 | } else if (response.script != null) {
48 | headers['Content-Type'] = 'application/javascript'
49 | } else if (response.css != null) {
50 | headers['Content-Type'] = 'text/css'
51 | } else {
52 | headers['Content-Type'] = 'text/plain'
53 | }
54 | }
55 | // (2)如果Content-Type没有charset,自动设置为utf-8
56 | if (headers['Content-Type'] != null && !headers['Content-Type'].includes('charset')) {
57 | headers['Content-Type'] += '; charset=utf-8'
58 | }
59 | }
60 | // headers.Access-Control-Allow-*:避免跨域问题
61 | if (rOptions.headers.origin && !headers['Access-Control-Allow-Origin']) {
62 | headers['Access-Control-Allow-Credentials'] = 'true'
63 | headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
64 | }
65 |
66 | res.writeHead(status, headers)
67 | if (status !== 204) {
68 | res.write(body)
69 | }
70 | res.end()
71 |
72 | const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
73 | log.info('success intercept:', url, ', response:', JSON.stringify(response))
74 | return true // true代表请求结束
75 | }
76 | },
77 | is (interceptOpt) {
78 | return !!interceptOpt.success
79 | },
80 | }
81 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/req/unVerifySsl.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'unVerifySsl',
3 | priority: 124,
4 | requestIntercept (context, interceptOpt, req, res, ssl, next) {
5 | const { rOptions, log } = context
6 |
7 | if (rOptions.agent.options.rejectUnauthorized && rOptions.agent.unVerifySslAgent) {
8 | rOptions.agent = rOptions.agent.unVerifySslAgent
9 | log.info(`unVerifySsl intercept: ${rOptions.hostname}, unVerifySsl`)
10 | res.setHeader('DS-Interceptor', 'unVerifySsl')
11 | } else {
12 | log.info(`unVerifySsl intercept: ${rOptions.hostname}, already unVerifySsl`)
13 | res.setHeader('DS-Interceptor', 'already unVerifySsl')
14 | }
15 |
16 | return true
17 | },
18 | is (interceptOpt) {
19 | return interceptOpt.unVerifySsl === true || interceptOpt.ssl === false
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/res/AfterOPTIONSHeaders.js:
--------------------------------------------------------------------------------
1 | const responseReplaceApi = require('./responseReplace')
2 |
3 | module.exports = {
4 | name: 'AfterOPTIONSHeaders',
5 | desc: '开启了options.js功能时,正常请求时,会需要增加响应头 `Access-Control-Allow-Origin: xxx`',
6 | priority: 201,
7 | responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
8 | const { rOptions, log } = context
9 |
10 | if (rOptions.method === 'OPTIONS') {
11 | return
12 | }
13 |
14 | const headers = {
15 | 'Access-Control-Allow-Credentials': 'true',
16 | 'Access-Control-Allow-Origin': '*',
17 | 'Cross-Origin-Resource-Policy': interceptOpt.optionsCrossPolicy || 'cross-origin',
18 | }
19 |
20 | // 替换响应头
21 | if (responseReplaceApi.replaceResponseHeaders({ ...headers }, res, proxyRes)) {
22 | log.info('AfterOPTIONSHeaders intercept:', JSON.stringify(headers))
23 | res.setHeader('DS-AfterOPTIONSHeaders-Interceptor', '1')
24 | } else {
25 | res.setHeader('DS-AfterOPTIONSHeaders-Interceptor', '0')
26 | }
27 | },
28 | is (interceptOpt) {
29 | return !!interceptOpt.options
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/impl/res/responseReplace.js:
--------------------------------------------------------------------------------
1 | const lodash = require('lodash')
2 | const cacheReq = require('../req/cacheRequest')
3 |
4 | const REMOVE = '[remove]'
5 |
6 | // 替换响应头
7 | function replaceResponseHeaders (newHeaders, res, proxyRes) {
8 | if (!newHeaders || lodash.isEmpty(newHeaders)) {
9 | return null
10 | }
11 |
12 | // 响应头Key统一转小写
13 | for (const headerKey in newHeaders) {
14 | if (headerKey === headerKey.toLowerCase()) {
15 | continue
16 | }
17 |
18 | const value = newHeaders[headerKey]
19 | delete newHeaders[headerKey]
20 | newHeaders[headerKey.toLowerCase()] = value
21 | }
22 |
23 | // 原先响应头
24 | const preHeaders = {}
25 |
26 | // 替换响应头
27 | const needDeleteKeys = []
28 | for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
29 | const headerKey = proxyRes.rawHeaders[i].toLowerCase()
30 |
31 | const newHeaderValue = newHeaders[headerKey]
32 | if (newHeaderValue) {
33 | if (newHeaderValue !== proxyRes.rawHeaders[i + 1]) {
34 | preHeaders[headerKey] = proxyRes.rawHeaders[i + 1] // 先保存原先响应头
35 | if (newHeaderValue === REMOVE) { // 由于拦截配置中不允许配置null,会被删,所以配置一个 "[remove]",当作删除响应头的意思
36 | proxyRes.rawHeaders[i + 1] = ''
37 | } else {
38 | proxyRes.rawHeaders[i + 1] = newHeaderValue
39 | }
40 | }
41 | needDeleteKeys.push(headerKey)
42 | }
43 | }
44 | // 处理删除响应头
45 | for (const headerKey of needDeleteKeys) {
46 | delete newHeaders[headerKey]
47 | }
48 | // 新增响应头
49 | for (const headerKey in newHeaders) {
50 | const headerValue = newHeaders[headerKey]
51 | if (headerValue == null || headerValue === REMOVE) {
52 | continue
53 | }
54 |
55 | res.setHeader(headerKey, newHeaders[headerKey])
56 | preHeaders[headerKey] = null // 标记原先响应头为null
57 | }
58 |
59 | if (lodash.isEmpty(preHeaders)) {
60 | return null
61 | }
62 | // 返回原先响应头
63 | return preHeaders
64 | }
65 |
66 | module.exports = {
67 | name: 'responseReplace',
68 | priority: 203,
69 | replaceResponseHeaders,
70 | responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
71 | const { rOptions, log } = context
72 |
73 | if (proxyRes.statusCode !== 200) {
74 | return
75 | }
76 |
77 | const responseReplaceConfig = interceptOpt.responseReplace
78 |
79 | let actions = ''
80 |
81 | const replaceHeaders = responseReplaceConfig.headers || {}
82 |
83 | // 处理文件下载请求
84 | if (responseReplaceConfig.doDownload || rOptions.doDownload) {
85 | const filename = (rOptions.path.match('^.*/([^/?]+)/?(\\?.*)?$') || [])[1] || 'UNKNOWN_FILENAME'
86 | // 设置文件下载响应头
87 | replaceHeaders['content-disposition'] = `attachment; filename="${encodeURIComponent(filename)}"`
88 | // 设置文件类型
89 | if (replaceHeaders['content-type'] == null) {
90 | replaceHeaders['content-type'] = 'application/octet-stream'
91 | }
92 | // 如果未手动配置需要缓存,则不允许使用缓存
93 | const maxAge = cacheReq.getMaxAge(interceptOpt)
94 | if (maxAge == null || maxAge <= 0) {
95 | replaceHeaders['cache-control'] = '[remove]'
96 | replaceHeaders['last-modified'] = '[remove]'
97 | replaceHeaders.expires = '[remove]'
98 | }
99 |
100 | actions += `${actions ? ',' : ''}download:${filename}`
101 | }
102 |
103 | // 替换响应头
104 | if (replaceResponseHeaders(replaceHeaders, res, proxyRes)) {
105 | actions += `${actions ? ',' : ''}headers`
106 | }
107 |
108 | if (actions) {
109 | res.setHeader('DS-ResponseReplace-Interceptor', actions)
110 | log.info(`response intercept: ${actions}`)
111 | }
112 | },
113 | is (interceptOpt) {
114 | return !!interceptOpt.responseReplace
115 | },
116 | }
117 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/interceptor/index.js:
--------------------------------------------------------------------------------
1 | // request interceptor impls
2 | const OPTIONS = require('./impl/req/OPTIONS.js')
3 | const success = require('./impl/req/success')
4 | const abort = require('./impl/req/abort')
5 | const cacheRequest = require('./impl/req/cacheRequest')
6 | const redirect = require('./impl/req/redirect')
7 |
8 | const requestReplace = require('./impl/req/requestReplace')
9 |
10 | const proxy = require('./impl/req/proxy')
11 | const sni = require('./impl/req/sni')
12 | const unVerifySsl = require('./impl/req/unVerifySsl')
13 |
14 | const baiduOcr = require('./impl/req/baiduOcr')
15 |
16 | // response interceptor impls
17 | const AfterOPTIONSHeaders = require('./impl/res/AfterOPTIONSHeaders')
18 | const cacheResponse = require('./impl/res/cacheResponse')
19 | const responseReplace = require('./impl/res/responseReplace')
20 |
21 | const script = require('./impl/res/script')
22 |
23 | module.exports = [
24 | // request interceptor impls
25 | OPTIONS, success, abort, cacheRequest, redirect,
26 | requestReplace,
27 | proxy, sni, unVerifySsl,
28 | baiduOcr,
29 |
30 | // response interceptor impls
31 | AfterOPTIONSHeaders, cacheResponse, responseReplace,
32 | script,
33 | ]
34 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/common/ProxyHttpAgent.js:
--------------------------------------------------------------------------------
1 | const AgentOrigin = require('agentkeepalive')
2 |
3 | module.exports = class Agent extends AgentOrigin {
4 | // Hacky
5 | getName (option) {
6 | let name = AgentOrigin.prototype.getName.call(this, option)
7 | name += ':'
8 | if (option.customSocketId) {
9 | name += option.customSocketId
10 | }
11 | return name
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/common/ProxyHttpsAgent.js:
--------------------------------------------------------------------------------
1 | const HttpsAgentOrigin = require('agentkeepalive').HttpsAgent
2 |
3 | module.exports = class HttpsAgent extends HttpsAgentOrigin {
4 | // Hacky
5 | getName (option) {
6 | let name = HttpsAgentOrigin.prototype.getName.call(this, option)
7 | name += ':'
8 | if (option.customSocketId) {
9 | name += option.customSocketId
10 | }
11 | return name
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/common/config.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path')
2 |
3 | const config = exports
4 |
5 | config.defaultHost = '127.0.0.1'
6 | config.defaultPort = 31181
7 | config.defaultMaxLength = 100
8 |
9 | config.caCertFileName = 'dev-sidecar.ca.crt'
10 | config.caKeyFileName = 'dev-sidecar.ca.key.pem'
11 | config.caName = 'DevSidecar - This certificate is generated locally'
12 | config.caBasePath = buildDefaultCABasePath()
13 |
14 | config.getDefaultCABasePath = function () {
15 | return config.caBasePath
16 | }
17 | config.setDefaultCABasePath = function (path) {
18 | config.caBasePath = path
19 | }
20 | function buildDefaultCABasePath () {
21 | const userHome = process.env.USERPROFILE || process.env.HOME || '/'
22 | return path.resolve(userHome, './.dev-sidecar')
23 | }
24 |
25 | config.getDefaultCACertPath = function () {
26 | return path.resolve(config.getDefaultCABasePath(), config.caCertFileName)
27 | }
28 |
29 | config.getDefaultCAKeyPath = function () {
30 | return path.resolve(config.getDefaultCABasePath(), config.caKeyFileName)
31 | }
32 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/index.js:
--------------------------------------------------------------------------------
1 | // require('babel-polyfill')
2 | module.exports = require('./mitmproxy')
3 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/mitmproxy/createFakeServerCenter.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 | const forge = require('node-forge')
3 | const log = require('../../../utils/util.log.server')
4 | const FakeServersCenter = require('../tls/FakeServersCenter')
5 |
6 | module.exports = function createFakeServerCenter ({
7 | maxLength,
8 | caCertPath,
9 | caKeyPath,
10 | requestHandler,
11 | upgradeHandler,
12 | getCertSocketTimeout,
13 | }) {
14 | let caCert
15 | let caKey
16 | try {
17 | fs.accessSync(caCertPath, fs.F_OK)
18 | fs.accessSync(caKeyPath, fs.F_OK)
19 | const caCertPem = fs.readFileSync(caCertPath)
20 | const caKeyPem = fs.readFileSync(caKeyPath)
21 | caCert = forge.pki.certificateFromPem(caCertPem)
22 | caKey = forge.pki.privateKeyFromPem(caKeyPem)
23 | } catch (e) {
24 | log.error('Can not find `CA certificate` or `CA key`:', e)
25 | process.exit(1)
26 | }
27 |
28 | return new FakeServersCenter({
29 | caCert,
30 | caKey,
31 | maxLength,
32 | requestHandler,
33 | upgradeHandler,
34 | getCertSocketTimeout,
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/mitmproxy/createUpgradeHandler.js:
--------------------------------------------------------------------------------
1 | const http = require('node:http')
2 | const https = require('node:https')
3 | const log = require('../../../utils/util.log.server')
4 | const util = require('../common/util')
5 |
6 | // copy from node-http-proxy. ^_^
7 |
8 | // create connectHandler function
9 | module.exports = function createUpgradeHandler (serverSetting) {
10 | // return
11 | return function upgradeHandler (req, cltSocket, head, ssl) {
12 | const clientOptions = util.getOptionsFromRequest(req, ssl, null, serverSetting)
13 | const proxyReq = (ssl ? https : http).request(clientOptions)
14 | proxyReq.on('error', (e) => {
15 | log.error('upgradeHandler error:', e)
16 | })
17 | proxyReq.on('response', (res) => {
18 | // if upgrade event isn't going to happen, close the socket
19 | if (!res.upgrade) {
20 | cltSocket.end()
21 | }
22 | })
23 |
24 | proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
25 | proxySocket.on('error', (e) => {
26 | log.error('upgrade error:', e)
27 | })
28 |
29 | cltSocket.on('error', (e) => {
30 | log.error('upgrade socket error:', e)
31 | proxySocket.end()
32 | })
33 |
34 | proxySocket.setTimeout(0)
35 | proxySocket.setNoDelay(true)
36 |
37 | proxySocket.setKeepAlive(true, 0)
38 |
39 | if (proxyHead && proxyHead.length) {
40 | proxySocket.unshift(proxyHead)
41 | }
42 |
43 | cltSocket.write(
44 | `${Object.keys(proxyRes.headers).reduce((head, key) => {
45 | const value = proxyRes.headers[key]
46 |
47 | if (!Array.isArray(value)) {
48 | head.push(`${key}: ${value}`)
49 | return head
50 | }
51 |
52 | for (let i = 0; i < value.length; i++) {
53 | head.push(`${key}: ${value[i]}`)
54 | }
55 | return head
56 | }, ['HTTP/1.1 101 Switching Protocols']).join('\r\n')}\r\n\r\n`,
57 | )
58 |
59 | proxySocket.pipe(cltSocket).pipe(proxySocket)
60 | })
61 | proxyReq.end()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js:
--------------------------------------------------------------------------------
1 | const defaultDns = require('node:dns')
2 | const log = require('../../../utils/util.log.server')
3 | const speedTest = require('../../speed')
4 |
5 | module.exports = {
6 | createLookupFunc (res, dns, action, target, isDnsIntercept) {
7 | target = target ? (`, target: ${target}`) : ''
8 |
9 | return (hostname, options, callback) => {
10 | const tester = speedTest.getSpeedTester(hostname)
11 | if (tester) {
12 | const aliveIpObj = tester.pickFastAliveIpObj()
13 | if (aliveIpObj) {
14 | log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`)
15 | if (res) {
16 | res.setHeader('DS-DNS-Lookup', `IpTester: ${aliveIpObj.host} ${aliveIpObj.dns === '预设IP' ? 'PreSet' : aliveIpObj.dns}`)
17 | }
18 | callback(null, aliveIpObj.host, 4)
19 | return
20 | } else {
21 | log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
22 | }
23 | }
24 | dns.lookup(hostname).then((ip) => {
25 | if (isDnsIntercept) {
26 | isDnsIntercept.dns = dns
27 | isDnsIntercept.hostname = hostname
28 | isDnsIntercept.ip = ip
29 | }
30 |
31 | if (ip !== hostname) {
32 | // 判断是否为测速失败的IP,如果是,则不使用当前IP
33 | let isTestFailedIp = false
34 | if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) {
35 | for (let i = 0; i < tester.backupList.length; i++) {
36 | const item = tester.backupList[i]
37 | if (item.host === ip) {
38 | if (item.time == null) {
39 | isTestFailedIp = true
40 | }
41 | break
42 | }
43 | }
44 | }
45 | if (isTestFailedIp === false) {
46 | log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
47 | if (res) {
48 | res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
49 | }
50 | callback(null, ip, 4)
51 | return
52 | } else {
53 | // 使用默认dns
54 | log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options)
55 | }
56 | } else {
57 | // 使用默认dns
58 | log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
59 | }
60 | defaultDns.lookup(hostname, options, callback)
61 | })
62 | }
63 | },
64 | }
65 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/proxy/tls/CertAndKeyContainer.js:
--------------------------------------------------------------------------------
1 | const tlsUtils = require('./tlsUtils')
2 | // const https = require('https')
3 | const log = require('../../../utils/util.log.server')
4 |
5 | module.exports = class CertAndKeyContainer {
6 | constructor ({
7 | maxLength = 1000,
8 | // getCertSocketTimeout = 2 * 1000,
9 | caCert,
10 | caKey,
11 | }) {
12 | this.queue = []
13 | this.maxLength = maxLength
14 | // this.getCertSocketTimeout = getCertSocketTimeout
15 | this.caCert = caCert
16 | this.caKey = caKey
17 | }
18 |
19 | addCertPromise (certPromiseObj) {
20 | if (this.queue.length >= this.maxLength) {
21 | const delCertObj = this.queue.shift()
22 | log.info(`超过最大证书数量${this.maxLength},删除旧证书。delCertObj:`, delCertObj)
23 | }
24 | this.queue.push(certPromiseObj)
25 | return certPromiseObj
26 | }
27 |
28 | getCertPromise (hostname, port, dnsName, mappingHostNames) {
29 | for (let i = 0; i < this.queue.length; i++) {
30 | const _certPromiseObj = this.queue[i]
31 | const mappingHostNames = _certPromiseObj.mappingHostNames
32 | for (let j = 0; j < mappingHostNames.length; j++) {
33 | const DNSName = mappingHostNames[j]
34 | if (DNSName === dnsName || tlsUtils.isMappingHostName(DNSName, hostname)) {
35 | this.reRankCert(i)
36 | log.info(`Load fakeCertPromise from cache, hostname: ${hostname}:${port}, certPromiseObj: {"mappingHostNames":${JSON.stringify(_certPromiseObj.mappingHostNames)}}`)
37 | return _certPromiseObj.promise
38 | }
39 | }
40 | }
41 |
42 | const certPromiseObj = {
43 | mappingHostNames,
44 | }
45 |
46 | const promise = new Promise((resolve, _reject) => {
47 | log.info(`【CreateFakeCertificate】dnsName: ${dnsName}, hostname: ${hostname}:${port}`)
48 |
49 | const certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, dnsName, mappingHostNames)
50 | resolve(certObj)
51 | })
52 |
53 | certPromiseObj.promise = promise
54 | this.addCertPromise(certPromiseObj)
55 |
56 | return promise
57 | }
58 |
59 | reRankCert (index) {
60 | // index ==> queue foot
61 | this.queue.push((this.queue.splice(index, 1))[0])
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/speed/config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | notify () {},
3 | dnsMap: {},
4 | }
5 | module.exports = {
6 | getConfig () {
7 | return config
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/lib/speed/index.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const log = require('../../utils/util.log.server')
3 | const config = require('./config')
4 | const SpeedTester = require('./SpeedTester.js')
5 |
6 | const SpeedTestPool = {
7 | }
8 |
9 | function initSpeedTest (runtimeConfig) {
10 | const { enabled, hostnameList } = runtimeConfig
11 | const conf = config.getConfig()
12 | _.merge(conf, runtimeConfig)
13 | if (!enabled) {
14 | return
15 | }
16 | _.forEach(hostnameList, (hostname) => {
17 | SpeedTestPool[hostname] = new SpeedTester({ hostname })
18 | })
19 | log.info('[speed] enabled')
20 | }
21 |
22 | function getAllSpeedTester () {
23 | const allSpeed = {}
24 | if (!config.getConfig().enabled) {
25 | return allSpeed
26 | }
27 | _.forEach(SpeedTestPool, (item, key) => {
28 | allSpeed[key] = {
29 | hostname: key,
30 | alive: item.alive,
31 | backupList: item.backupList,
32 | }
33 | })
34 | return allSpeed
35 | }
36 |
37 | function getSpeedTester (hostname) {
38 | if (!config.getConfig().enabled) {
39 | return
40 | }
41 | let instance = SpeedTestPool[hostname]
42 | if (instance == null) {
43 | instance = new SpeedTester({ hostname })
44 | SpeedTestPool[hostname] = instance
45 | }
46 | return instance
47 | }
48 |
49 | function registerNotify (notify) {
50 | config.notify = notify
51 | }
52 |
53 | function reSpeedTest () {
54 | _.forEach(SpeedTestPool, (item, key) => {
55 | item.test()
56 | })
57 | }
58 |
59 | // action调用
60 | function action (event) {
61 | if (event.key === 'reTest') {
62 | reSpeedTest()
63 | } else if (event.key === 'getList') {
64 | process.send({ type: 'speed', event: { key: 'getList', value: getAllSpeedTester() } })
65 | }
66 | }
67 | module.exports = {
68 | SpeedTester,
69 | initSpeedTest,
70 | getSpeedTester,
71 | getAllSpeedTester,
72 | registerNotify,
73 | reSpeedTest,
74 | action,
75 | }
76 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/utils/util.js:
--------------------------------------------------------------------------------
1 | // const os = require('os')
2 | const log = require('./util.log.server')
3 |
4 | const util = {
5 | getNodeVersion () {
6 | const version = process.version
7 | log.info(version)
8 | },
9 | }
10 | util.getNodeVersion()
11 | module.exports = util
12 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/utils/util.log.server.js:
--------------------------------------------------------------------------------
1 | const loggerFactory = require('@docmirror/dev-sidecar/src/utils/util.logger')
2 |
3 | const logger = loggerFactory.getLogger('server')
4 |
5 | module.exports = logger
6 |
--------------------------------------------------------------------------------
/packages/mitmproxy/src/utils/util.process.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | fireError (e) {
3 | if (process.send) {
4 | process.send({ type: 'error', event: e, message: e.message })
5 | }
6 | },
7 | fireStatus (status) {
8 | if (process.send) {
9 | process.send({ type: 'status', event: status })
10 | }
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/dnsSpeedTest.js:
--------------------------------------------------------------------------------
1 | const dns = require('../src/lib/dns/index.js')
2 | const SpeedTest = require('../src/lib/speed/index.js')
3 | const SpeedTester = require('../src/lib/speed/SpeedTester.js')
4 |
5 | const dnsMap = dns.initDNS({
6 | cloudflare: {
7 | type: 'https',
8 | server: 'https://1.1.1.1/dns-query',
9 | cacheSize: 1000,
10 | },
11 | // py233: { //污染
12 | // type: 'https',
13 | // server: ' https://i.233py.com/dns-query',
14 | // cacheSize: 1000
15 | // }
16 | // google: { //不可用
17 | // type: 'https',
18 | // server: 'https://8.8.8.8/dns-query',
19 | // cacheSize: 1000
20 | // },
21 | // dnsSB: { //不可用
22 | // type: 'https',
23 | // server: 'https://doh.dns.sb/dns-query',
24 | // cacheSize: 1000
25 | // }
26 | })
27 |
28 | SpeedTest.initSpeedTest({ hostnameList: {}, dnsMap })
29 |
30 | const tester = new SpeedTester({ hostname: 'github.com' })
31 | tester.test().then(() => {
32 | console.log('github.com tester.alive = ', tester.alive)
33 | })
34 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/lodashTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const lodash = require('lodash')
3 |
4 | // test lodash.isEqual
5 | const arr1 = [1, 2, 3]
6 | const arr2 = [1, 2, 3]
7 | const arr3 = [3, 2, 1]
8 | assert.strictEqual(lodash.isEqual(arr1, arr2), true)
9 | assert.strictEqual(lodash.isEqual(arr1.sort(), arr3.sort()), true)
10 |
11 | // test lodash.isEmpty
12 |
13 | function isEmpty (obj) {
14 | return obj == null || (lodash.isObject(obj) && lodash.isEmpty(obj))
15 | }
16 |
17 | // true
18 | assert.strictEqual(isEmpty(null), true)
19 | assert.strictEqual(isEmpty({}), true)
20 | assert.strictEqual(isEmpty([]), true)
21 | // false
22 | assert.strictEqual(isEmpty(true), false)
23 | assert.strictEqual(isEmpty(false), false)
24 | assert.strictEqual(isEmpty(1), false)
25 | assert.strictEqual(isEmpty(0), false)
26 | assert.strictEqual(isEmpty(-1), false)
27 | assert.strictEqual(isEmpty(''), false)
28 | assert.strictEqual(isEmpty('1'), false)
29 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/matchTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 |
3 | const name = '/docmirror/dev-sidecar/raw/master/doc/index.png'
4 | // https://raw.fastgit.org/docmirror/dev-sidecar/master/doc/index.png
5 | const ret = name.replace(/^(.+)\/raw\/(.+)$/, 'raw.fastgit.org$1/$2')
6 | console.log(ret)
7 | assert.strictEqual(ret, 'raw.fastgit.org/docmirror/dev-sidecar/master/doc/index.png')
8 |
9 | const reg = /^\/[^/]+\/[^/]+$/
10 | console.log('/greper/d2-crud-plus/blob/master/.eslintignore'.match(reg))
11 | assert.strictEqual('/greper/d2-crud-plus/blob/master/.eslintignore'.match(reg), null)
12 |
13 | const chunk = Buffer.from('')
14 | const script = ''
15 | const index = chunk.indexOf('')
16 | const scriptBuf = Buffer.from(script)
17 | const chunkNew = Buffer.alloc(chunk.length + scriptBuf.length)
18 | chunk.copy(chunkNew, 0, 0, index)
19 | scriptBuf.copy(chunkNew, index, 0)
20 | chunk.copy(chunkNew, index + scriptBuf.length, index)
21 | console.log(chunkNew.toString())
22 | assert.strictEqual(chunkNew.toString(), '')
23 |
24 | const reg2 = /aaaa/i
25 | console.log(reg2.test('aaaa')) // true
26 | assert.strictEqual(reg2.test('aaaa'), true)
27 |
28 | const reg3 = '/aaaa/i'
29 | console.log(new RegExp(reg3).test('aaaa')) // false
30 | assert.strictEqual(new RegExp(reg3).test('aaaa'), false)
31 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/matchUtilTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const matchUtil = require('../src/utils/util.match')
3 |
4 | const hostMap = matchUtil.domainMapRegexply({
5 | 'aaa.com': true,
6 | '*bbb.com': true,
7 | '*.ccc.com': true,
8 | '^.{1,3}ddd.com$': true,
9 | '*.cn': true,
10 | '.github.com': true,
11 |
12 | '*.eee.com': true,
13 | '.eee.com': false, // 此配置将被忽略,因为有 '*.eee.com' 了,优先级更高
14 | })
15 |
16 | console.log(hostMap)
17 | assert.strictEqual(hostMap['^.*bbb\\.com$'], true)
18 | assert.strictEqual(hostMap['^.*\\.ccc\\.com$'], true)
19 | assert.strictEqual(hostMap['^.{1,3}ddd.com$'], true)
20 | assert.strictEqual(hostMap['^.*\\.cn$'], true)
21 | assert.strictEqual(hostMap['^.*\\.github\\.com$'], true)
22 | assert.strictEqual(hostMap['^.*\\.github\\.com$'], true)
23 | assert.strictEqual(hostMap['^.*\\.eee\\.com$'], true)
24 |
25 | const origin = hostMap.origin
26 | assert.strictEqual(origin['aaa.com'], true)
27 | assert.strictEqual(origin['*bbb.com'], true)
28 | assert.strictEqual(origin['*.ccc.com'], true)
29 | assert.strictEqual(origin['*.cn'], true)
30 | assert.strictEqual(origin['*.github.com'], true)
31 | assert.strictEqual(origin['.eee.com'], undefined)
32 |
33 | const value11 = matchUtil.matchHostname(hostMap, 'aaa.com', 'test1.1')
34 | const value12 = matchUtil.matchHostname(hostMap, 'aaaa.com', 'test1.2')
35 | const value13 = matchUtil.matchHostname(hostMap, 'aaaa.comx', 'test1.3')
36 | console.log('test1: aaa.com')
37 | assert.strictEqual(value11, true)
38 | assert.strictEqual(value12, undefined)
39 | assert.strictEqual(value13, undefined)
40 |
41 | const value21 = matchUtil.matchHostname(hostMap, 'bbb.com', 'test2.1')
42 | const value22 = matchUtil.matchHostname(hostMap, 'xbbb.com', 'test2.2')
43 | const value23 = matchUtil.matchHostname(hostMap, 'bbb.comx', 'test2.3')
44 | const value24 = matchUtil.matchHostname(hostMap, 'x.bbb.com', 'test2.4')
45 | console.log('test2: *bbb.com')
46 | assert.strictEqual(value21, true)
47 | assert.strictEqual(value22, true)
48 | assert.strictEqual(value23, undefined)
49 | assert.strictEqual(value24, true)
50 |
51 | const value31 = matchUtil.matchHostname(hostMap, 'ccc.com', 'test3.1')
52 | const value32 = matchUtil.matchHostname(hostMap, 'x.ccc.com', 'test3.2')
53 | const value33 = matchUtil.matchHostname(hostMap, 'xccc.com', 'test3.3')
54 | console.log('test3: *.ccc.com')
55 | assert.strictEqual(value31, true)
56 | assert.strictEqual(value32, true)
57 | assert.strictEqual(value33, undefined)
58 |
59 | const value41 = matchUtil.matchHostname(hostMap, 'ddd.com', 'test4.1')
60 | const value42 = matchUtil.matchHostname(hostMap, 'x.ddd.com', 'test4.2')
61 | const value43 = matchUtil.matchHostname(hostMap, 'xddd.com', 'test4.3')
62 | console.log('test4: ^.{1,3}ddd.com$')
63 | assert.strictEqual(value41, undefined)
64 | assert.strictEqual(value42, true)
65 | assert.strictEqual(value43, true)
66 |
67 | const value51 = matchUtil.matchHostname(hostMap, 'zzz.cn', 'test5.1')
68 | const value52 = matchUtil.matchHostname(hostMap, 'x.zzz.cn', 'test5.2')
69 | const value53 = matchUtil.matchHostname(hostMap, 'zzz.cnet.com', 'test5.3')
70 | console.log('test5: *.cn')
71 | assert.strictEqual(value51, true)
72 | assert.strictEqual(value52, true)
73 | assert.strictEqual(value53, undefined)
74 |
75 | const value61 = matchUtil.matchHostname(hostMap, 'github.com', 'test6.1')
76 | const value62 = matchUtil.matchHostname(hostMap, 'api.github.com', 'test6.2')
77 | const value63 = matchUtil.matchHostname(hostMap, 'aa.bb.github.com', 'test6.3')
78 | const value64 = matchUtil.matchHostname(hostMap, 'aaagithub.com', 'test6.4')
79 | console.log('test6: .github.com')
80 | assert.strictEqual(value61, true)
81 | assert.strictEqual(value62, true)
82 | assert.strictEqual(value63, true)
83 | assert.strictEqual(value64, undefined)
84 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/monkeyTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const monkey = require('../src/lib/monkey')
3 |
4 | let scripts
5 | try {
6 | scripts = monkey.load('../gui/extra/scripts/') // 相对于 mitmproxy 目录的相对路径,而不是当前 test 目录的。
7 | } catch {
8 | scripts = monkey.load('../../gui/extra/scripts/') // 相对于 当前 test 目录的相对路径
9 | }
10 |
11 | // console.log(scripts)
12 | assert.strictEqual(scripts.github != null, true)
13 | assert.strictEqual(scripts.google != null, true)
14 | assert.strictEqual(scripts.tampermonkey != null, true)
15 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/pacTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const pac = require('../src/lib/proxy/middleware/source/pac')
3 |
4 | const pacClient = pac.createPacClient('../gui/extra/pac/pac.txt') // 相对于 mitmproxy 目录的相对路径,而不是当前 test 目录的。
5 |
6 | const string = pacClient.FindProxyForURL('https://www.facebook.com', 'www.facebook.com')
7 | console.log(`facebook: ${string}`)
8 | assert.strictEqual(string, pacClient.proxyUrl)
9 |
10 | const string2 = pacClient.FindProxyForURL('https://http2.golang.org', 'http2.golang.org')
11 | console.log(`golang: ${string2}`)
12 | assert.strictEqual(string2, 'DIRECT;')
13 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/proxyTest.js:
--------------------------------------------------------------------------------
1 | // const http = require('node:http')
2 | //
3 | // const options = {
4 | // headers: {
5 | // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
6 | // },
7 | // lookup (hostname, options, callback) {
8 | // const ip = '106.52.191.148'
9 | // console.log('lookup')
10 | // callback(null, ip, 4)
11 | // },
12 | // }
13 | //
14 | // const request = http.get('http://test.target/', options, (response) => {
15 | // response.on('data', (data) => {
16 | // process.stdout.write(data)
17 | // })
18 | // })
19 | //
20 | // request.on('error', (error) => {
21 | // console.log(error)
22 | // })
23 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/responseReplaceTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const responseReplace = require('../src/lib/interceptor/impl/res/responseReplace')
3 |
4 | const headers = {}
5 | const res = {
6 | setHeader: (key, value) => {
7 | headers[key] = value
8 | },
9 | }
10 |
11 | const proxyRes = {
12 | rawHeaders: [
13 | 'Content-Type', 'application/json; charset=utf-8',
14 | 'Content-Length', '2',
15 | 'ETag', 'W/"2"',
16 | 'Date', 'Thu, 01 Jan 1970 00:00:00 GMT',
17 | 'Connection', 'keep-alive',
18 | ],
19 | }
20 |
21 | const newHeaders = {
22 | 'Content-Type': 'application/json; charset=utf-8',
23 | 'Content-Length': '3',
24 | 'xxx': 1,
25 | 'Date': '[remove]',
26 | 'yyy': '[remove]',
27 | }
28 |
29 | const result = responseReplace.replaceResponseHeaders(newHeaders, res, proxyRes)
30 | console.log(proxyRes.rawHeaders)
31 | console.log(headers)
32 | console.log(result)
33 |
34 | assert.deepStrictEqual(proxyRes.rawHeaders, [
35 | 'Content-Type', 'application/json; charset=utf-8',
36 | 'Content-Length', '3',
37 | 'ETag', 'W/"2"',
38 | 'Date', '',
39 | 'Connection', 'keep-alive'
40 | ])
41 | assert.deepStrictEqual(headers, {
42 | xxx: 1,
43 | })
44 | assert.deepStrictEqual(result, {
45 | 'content-length': '2',
46 | 'date': 'Thu, 01 Jan 1970 00:00:00 GMT',
47 | 'xxx': null,
48 | })
49 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/sha256Test.js:
--------------------------------------------------------------------------------
1 | // 需要时,在 package.json 中添加以下依赖:
2 | // "devDependencies": {
3 | // "crypto-js": "^4.2.0"
4 | // }
5 |
6 | // const CryptoJs = require('crypto-js')
7 | //
8 | // const ret = CryptoJs.SHA256('111111111111')
9 | // console.log(ret.toString(CryptoJs.enc.Base64))
10 | // console.log(1 / 2)
11 |
--------------------------------------------------------------------------------
/packages/mitmproxy/test/utilTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('node:assert')
2 | const util = require('../src/lib/proxy/common/util')
3 |
4 | let arr
5 |
6 | arr = util.parseHostnameAndPort('www.baidu.com')
7 | console.log('arr1:', arr)
8 | assert.strictEqual(arr.length === 1, true) // true
9 | assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
10 |
11 | arr = util.parseHostnameAndPort('www.baidu.com', 80)
12 | console.log('arr2:', arr)
13 | assert.strictEqual(arr.length === 2, true) // true
14 | assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
15 | assert.strictEqual(arr[1] === 80, true) // true
16 |
17 | arr = util.parseHostnameAndPort('www.baidu.com:8080')
18 | console.log('arr3:', arr)
19 | assert.strictEqual(arr.length === 2, true) // true
20 | assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
21 | assert.strictEqual(arr[1] === 8080, true) // true
22 |
23 | arr = util.parseHostnameAndPort('www.baidu.com:8080', 8080)
24 | console.log('arr4:', arr)
25 | assert.strictEqual(arr.length === 2, true) // true
26 | assert.strictEqual(arr[0] === 'www.baidu.com', true) // true
27 | assert.strictEqual(arr[1] === 8080, true) // true
28 |
29 | arr = util.parseHostnameAndPort('[2001:abcd::1]')
30 | console.log('arr5:', arr)
31 | assert.strictEqual(arr.length === 1, true) // true
32 | assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // ture
33 |
34 | arr = util.parseHostnameAndPort('[2001:abcd::1]', 80)
35 | console.log('arr6:', arr)
36 | assert.strictEqual(arr.length === 2, true) // true
37 | assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // ture
38 | assert.strictEqual(arr[1] === 80, true) // ture
39 |
40 | arr = util.parseHostnameAndPort('[2001:abcd::1]:8080')
41 | console.log('arr7:', arr)
42 | assert.strictEqual(arr.length === 2, true) // true
43 | assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // true
44 | assert.strictEqual(arr[1] === 8080, true) // ture
45 |
46 | arr = util.parseHostnameAndPort('[2001:abcd::1]:8080', 8080)
47 | console.log('arr8:', arr)
48 | assert.strictEqual(arr.length === 2, true) // true
49 | assert.strictEqual(arr[0] === '[2001:abcd::1]', true) // true
50 | assert.strictEqual(arr[1] === 8080, true) // ture
51 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in subdirectories of packages/ and components/
3 | - packages/*
4 | # exclude packages that are inside test directories
5 | - '!**/test/**'
6 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | // const cmd1 = require('node-cmd')
2 | // cmd1.get('set',
3 | // function (err, data, stderr) {
4 | // console.log('cmd complete:', err, data, stderr)
5 | // if (err) {
6 | // console.error('cmd 命令执行错误:', err, stderr)
7 | // } else {
8 | // console.log('cmd 命令执行结果:', data)
9 | // }
10 | // }
11 | // )
12 |
13 | // const process = require('child_process')
14 | //
15 | // const cmd = 'set'
16 | // process.exec(cmd, function (error, stdout, stderr) {
17 | // console.log('error:' + error)
18 | // console.log('stdout:' + stdout)
19 | // console.log('stderr:' + stderr)
20 | // })
21 |
22 | // const fs = require('fs')
23 | // const content = fs.readFileSync('C:\\Users\\Administrator\\.dev-sidecar\\dev-sidecar.ca.crt')
24 | // console.log('content:',JSON.stringify(content.toString().replace(new RegExp('\r\n','g'),'\n')))
25 |
26 | // function testCa () {
27 | // const https = require('https')
28 | // const fs = require('fs')
29 | // process.env.NODE_EXTRA_CA_CERTS = 'C:\\Users\\Administrator\\.dev-sidecar\\dev-sidecar.ca.crt'
30 | // process.env.GLOBAL_AGENT_HTTP_PROXY = "http://127.0.0.1:31181"
31 | // process.env.GLOBAL_AGENT_HTTPS_PROXY = "http://127.0.0.1:31181"
32 | // fs.readFileSync(process.env.NODE_EXTRA_CA_CERTS)
33 | //
34 | // const options = {
35 | // agent : new https.Agent({
36 | // proxy: "http://127.0.0.1:31181"
37 | // })
38 | // }
39 | // console.log('options', options)
40 | //
41 | // https.get('https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js',options, (res) => {
42 | // console.log('状态码:', res.statusCode)
43 | // console.log('请求头:', res.headers)
44 | //
45 | // res.on('data', (d) => {
46 | // process.stdout.write(d)
47 | // })
48 | // }).on('error', (e) => {
49 | // console.error(e)
50 | // })
51 | // }
52 |
53 | function testRequest () {
54 | // process.env.NODE_EXTRA_CA_CERTS='C:\\Users\\Administrator\\.dev-sidecar\\dev-sidecar.ca.crt'
55 | console.log(process.env.NODE_EXTRA_CA_CERTS)
56 | const request = require('request').defaults({
57 | proxy: 'http://127.0.0.1:31181',
58 | })
59 | request('https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js', (error, response, body) => {
60 | if (error) {
61 | console.error(error)
62 | } else {
63 | console.log(body)
64 | }
65 | })
66 | }
67 |
68 | // testCa()
69 |
70 | testRequest()
71 |
--------------------------------------------------------------------------------
/test/testDns.js:
--------------------------------------------------------------------------------
1 | console.log('www.baidu.com'.match('.*.baidu.com'))
2 |
--------------------------------------------------------------------------------