init
commit
b7654d6ab0
|
@ -0,0 +1,6 @@
|
|||
# IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
node_modules/
|
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,105 @@
|
|||
# dev-sidecar
|
||||
开发者边车,命名取自service-mesh的service-sidecar,意为为开发者打辅助的边车工具
|
||||
通过本地代理的方式将请求代理到一些国内的加速通道上
|
||||
解决一些网站和库无法访问或访问速度慢的问题
|
||||
|
||||
## 特性
|
||||
### 1、 解决git push某些情况下需要临时输入账号密码的问题
|
||||
|
||||
### 2、 github的release、source、zip下载加速
|
||||
可解决npm install 时某些安装包下载不下来的问题
|
||||
|
||||
### 3、 github的源代码查看(raw/blame查看)
|
||||
|
||||
### 4、 Stack Overflow 加速
|
||||
|
||||
### 5、 google cdn 加速
|
||||
|
||||
### 6、 gist.github.com 加速
|
||||
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1、安装与启动
|
||||
```shell
|
||||
git clone https://gitee.com/docmirror/dev-sidecar.git
|
||||
cd ./dev-sidecar/packages/core
|
||||
npm install
|
||||
npm run start
|
||||
|
||||
#或使用cnpm
|
||||
cnpm install
|
||||
npm run start
|
||||
|
||||
#或使用yarn
|
||||
yarn install
|
||||
npm run start
|
||||
|
||||
```
|
||||
|
||||
输出
|
||||
```
|
||||
CA Cert saved in: C:\Users\Administrator\.dev-sidecar\dev-sidecar.ca.crt
|
||||
CA private key saved in: C:\Users\Administrator\.dev-sidecar\dev-sidecar.ca.key.pem
|
||||
|
||||
dev-sidecar启动端口: 1181
|
||||
代理已开启, 127.0.0.1 1181
|
||||
|
||||
```
|
||||
启动后会自动设置系统代理、npm代理
|
||||
|
||||
### 2、设置信任根证书
|
||||
|
||||
第一次启动时会本地随机生成一个根证书放在此目录下(由于此证书是本地随机生成的,所以信任它是安全的)
|
||||
```
|
||||
# 你的Home路径如果有修改,输出会不一样,请按照实际日志输出路径查看
|
||||
CA Cert saved in: C:\Users\Administrator\.dev-sidecar\dev-sidecar.ca.crt
|
||||
```
|
||||
|
||||
windows用户安装根证书
|
||||
```
|
||||
start %HOMEPATH%/.dev-sidecar/dev-sidecar.ca.crt
|
||||
或者
|
||||
打开`C:\Users\Administrator\.dev-sidecar\`文件夹,双击`dev-sidecar.ca.crt`
|
||||
```
|
||||
依次点击安装证书->所有用户->将所有证书都放入下列存储->受信任的根证书颁发机构->确定,下一步,确定即可
|
||||
|
||||
![](./doc/setup.png)
|
||||
|
||||
Mac 用户安装根证书
|
||||
```
|
||||
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ~/.dev-sidecar/dev-sidecar.ca.crt
|
||||
```
|
||||
|
||||
### 开始加速吧
|
||||
去github上`Download ZIP`、`Release` 下载试试,体验秒下的感觉
|
||||
|
||||
比如去下载它: https://github.com/greper/d2-crud-plus/archive/master.zip
|
||||
|
||||
|
||||
|
||||
## 最佳实践
|
||||
|
||||
把dev-sidecar一直开着就行了
|
||||
|
||||
### npm加速
|
||||
1. yarn 设置淘宝镜像registry
|
||||
2. npm设置官方registry。
|
||||
3. 项目install使用yarn,publish用npm,互不影响
|
||||
|
||||
### 其他加速
|
||||
1. git clone 加速 [fgit-go](https://github.com/FastGitORG/fgit-go)
|
||||
2. github.com代理网站(不能登录) [hub.fastgit.org](https://hub.fastgit.org/)
|
||||
|
||||
|
||||
## 开发计划
|
||||
1. 桌面端,右下角小图标
|
||||
2. √ google cdn加速
|
||||
|
||||
## 感谢
|
||||
本项目参考如下开源项目
|
||||
* [node-mitmproxy](https://github.com/wuchangming/node-mitmproxy)
|
||||
* [ReplaceGoogleCDN](https://github.com/justjavac/ReplaceGoogleCDN)
|
||||
|
||||
本项目加速资源由如下组织提供
|
||||
* [fastgit](https://fastgit.org/)
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
server: {
|
||||
port: 1181
|
||||
},
|
||||
"intercepts": {
|
||||
'notify3.note.youdao.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://localhost:99999'
|
||||
}
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"mapping": {
|
||||
//"avatars*.githubusercontent.com": "usa"
|
||||
}
|
||||
},
|
||||
// setting: {
|
||||
// startup: {
|
||||
// // 开机启动
|
||||
// server: true,
|
||||
// proxy: {
|
||||
// system: true,
|
||||
// npm: true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"command": {
|
||||
"publish": {
|
||||
"ignoreChanges": [
|
||||
"*.md",
|
||||
"config",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
"bootstrap": {
|
||||
"ignore": [
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "dev-sidecar-parent",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"lerna": "^3.22.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('./src')
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"name": "@docmirror/dev-sidecar",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"depedencies": {},
|
||||
"keywords": [],
|
||||
"author": "docmirror.cn",
|
||||
"license": "MPL2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"start": "node start.js",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"agentkeepalive": "^2.1.1",
|
||||
"babel-core": "^6.8.0",
|
||||
"babel-plugin-transform-async-to-generator": "^6.7.4",
|
||||
"babel-polyfill": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.8.0",
|
||||
"charset": "^1.0.0",
|
||||
"child_process": "^1.0.2",
|
||||
"colors": "^1.1.2",
|
||||
"commander": "^2.9.0",
|
||||
"core-js": "^3.6.5",
|
||||
"debug": "^4.1.1",
|
||||
"dns-over-http": "^0.2.0",
|
||||
"dns-over-tls": "^0.0.8",
|
||||
"iconv-lite": "^0.4.13",
|
||||
"is-browser": "^2.1.0",
|
||||
"jschardet": "^1.4.1",
|
||||
"json5": "^2.1.3",
|
||||
"lodash": "^4.7.0",
|
||||
"lru-cache": "^6.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-cmd": "^3.0.0",
|
||||
"node-forge": "^0.8.2",
|
||||
"node-mitmproxy": "^3.1.1",
|
||||
"node-powershell": "^4.0.0",
|
||||
"require-context": "^1.1.0",
|
||||
"through2": "^2.0.1",
|
||||
"tunnel-agent": "^0.4.3",
|
||||
"util": "^0.12.3",
|
||||
"validator": "^13.1.17",
|
||||
"vue": "^2.6.11",
|
||||
"winreg": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
const lodash = require('lodash')
|
||||
const defConfig = require('./config/index.js')
|
||||
let configTarget = defConfig
|
||||
module.exports = {
|
||||
get () {
|
||||
return configTarget
|
||||
},
|
||||
set (newConfig) {
|
||||
const clone = lodash.cloneDeep(defConfig)
|
||||
lodash.merge(clone, newConfig)
|
||||
configTarget = clone
|
||||
return configTarget
|
||||
},
|
||||
getDefault () {
|
||||
return defConfig
|
||||
},
|
||||
resetDefault () {
|
||||
configTarget = defConfig
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
module.exports = {
|
||||
server: {
|
||||
port: 1181
|
||||
},
|
||||
intercepts: {
|
||||
'github.com': [
|
||||
{
|
||||
// "release archive 下载链接替换",
|
||||
regexp: [
|
||||
'/.*/.*/releases/download/',
|
||||
'/.*/.*/archive/'
|
||||
],
|
||||
redirect: 'https://download.fastgit.org'
|
||||
},
|
||||
{
|
||||
regexp: [
|
||||
'/.*/.*/raw/',
|
||||
'/.*/.*/blame/'
|
||||
],
|
||||
redirect: 'https://hub.fastgit.org'
|
||||
}
|
||||
],
|
||||
// 'codeload.github.com': [
|
||||
// {
|
||||
// regexp: '.*',
|
||||
// redirect:"https://download.fastgit.org"
|
||||
// }
|
||||
// ],
|
||||
'raw.githubusercontent.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://raw.fastgit.org'
|
||||
}
|
||||
],
|
||||
'github.githubassets.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://assets.fastgit.org'
|
||||
}
|
||||
],
|
||||
'customer-stories-feed.github.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://customer-stories-feed.fastgit.org'
|
||||
}
|
||||
],
|
||||
// google cdn
|
||||
'ajax.googleapis.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://ajax.loli.net'
|
||||
}
|
||||
],
|
||||
'fonts.googleapis.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://fonts.loli.net'
|
||||
}
|
||||
],
|
||||
'themes.googleapis.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://themes.loli.net'
|
||||
}
|
||||
],
|
||||
'fonts.gstatic.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://gstatic.loli.net'
|
||||
}
|
||||
],
|
||||
'www.google.com': [
|
||||
{
|
||||
regexp: '/recaptcha/.*',
|
||||
proxy: 'https://www.recaptcha.net'
|
||||
}
|
||||
],
|
||||
'secure.gravatar.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://gravatar.loli.net'
|
||||
}
|
||||
],
|
||||
'clients*.google.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://localhost:99999'
|
||||
}
|
||||
],
|
||||
'lh*.googleusercontent.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://localhost:99999'
|
||||
}
|
||||
]
|
||||
},
|
||||
dns: {
|
||||
providers: {
|
||||
aliyun: {
|
||||
type: 'https',
|
||||
server: 'https://dns.alidns.com/dns-query',
|
||||
cacheSize: 1000
|
||||
},
|
||||
usa: {
|
||||
type: 'https',
|
||||
server: 'https://cloudflare-dns.com/dns-query',
|
||||
cacheSize: 1000
|
||||
}
|
||||
},
|
||||
mapping: {
|
||||
// "解决push的时候需要输入密码的问题",
|
||||
'api.github.com': 'usa',
|
||||
'gist.github.com': 'usa'
|
||||
// "avatars*.githubusercontent.com": "usa"
|
||||
}
|
||||
},
|
||||
setting: {
|
||||
startup: { // 开机启动
|
||||
server: true,
|
||||
proxy: {
|
||||
system: true,
|
||||
npm: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
const listener = {}
|
||||
let index = 1
|
||||
function register (channel, handle, order = 10) {
|
||||
let handles = listener[channel]
|
||||
if (handles == null) {
|
||||
handles = listener[channel] = []
|
||||
}
|
||||
handles.push({ id: index, handle, order })
|
||||
handles.sort((a, b) => { return a.order - b.order })
|
||||
return index++
|
||||
}
|
||||
function fire (channel, event) {
|
||||
const handles = listener[channel]
|
||||
if (handles == null) {
|
||||
return
|
||||
}
|
||||
for (const item of handles) {
|
||||
item.handle(event)
|
||||
}
|
||||
}
|
||||
|
||||
function unregister (id) {
|
||||
for (const key in listener) {
|
||||
const handlers = listener[key]
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
const handle = handlers[i]
|
||||
if (handle.id === id) {
|
||||
handlers.splice(i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const EventHub = {
|
||||
register,
|
||||
fire,
|
||||
unregister
|
||||
}
|
||||
module.exports = EventHub
|
|
@ -0,0 +1,53 @@
|
|||
const server = require('./server/index.js')
|
||||
const proxy = require('./switch/proxy/index.js')
|
||||
const status = require('./status')
|
||||
const config = require('./config')
|
||||
const event = require('./event')
|
||||
|
||||
async function proxyStartup ({ ip, port }) {
|
||||
for (const key in proxy) {
|
||||
if (config.get().setting.startup.proxy[key]) {
|
||||
await proxy[key].open({ ip, port })
|
||||
}
|
||||
}
|
||||
}
|
||||
async function proxyShutdown () {
|
||||
for (const key in proxy) {
|
||||
console.log('status', status)
|
||||
if (status.proxy[key] === false) {
|
||||
continue
|
||||
}
|
||||
await proxy[key].close()
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
status,
|
||||
api: {
|
||||
server,
|
||||
proxy,
|
||||
config,
|
||||
startup: async (newConfig) => {
|
||||
config.set(newConfig)
|
||||
try {
|
||||
if (config.get().setting.startup.server) {
|
||||
server.start(newConfig)
|
||||
}
|
||||
await proxyStartup({ ip: '127.0.0.1', port: config.get().server.port })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
shutdown: async () => {
|
||||
try {
|
||||
await proxyShutdown()
|
||||
return new Promise(resolve => {
|
||||
server.close()
|
||||
resolve()
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
event
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const expose = require('./expose.js')
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
// 避免异常崩溃
|
||||
process.on('uncaughtException', function (err) {
|
||||
if (err.code === 'ECONNABORTED') {
|
||||
// console.error(err.errno)
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason)
|
||||
// application specific logging, throwing an error, or other logic here
|
||||
})
|
||||
|
||||
module.exports = expose
|
|
@ -0,0 +1,45 @@
|
|||
const LRU = require('lru-cache')
|
||||
const { isIP } = require('validator')
|
||||
const getLogger = require('../utils/logger')
|
||||
|
||||
const logger = getLogger('dns')
|
||||
const cacheSize = 1024
|
||||
function _isIP (v) {
|
||||
return v && isIP(v)
|
||||
}
|
||||
|
||||
module.exports = class BaseDNS {
|
||||
constructor () {
|
||||
this.cache = new LRU(cacheSize)
|
||||
}
|
||||
|
||||
async lookup (hostname) {
|
||||
try {
|
||||
let ip = this.cache.get(hostname)
|
||||
if (ip) {
|
||||
return ip
|
||||
}
|
||||
|
||||
const t = new Date()
|
||||
|
||||
ip = hostname
|
||||
for (let depth = 0; !_isIP(ip) && depth < 5; depth++) {
|
||||
ip = await this._lookup(ip).catch(error => {
|
||||
logger.debug(error)
|
||||
return ip
|
||||
})
|
||||
}
|
||||
|
||||
if (!_isIP(ip)) {
|
||||
throw new Error(`BAD IP FORMAT (${ip})`)
|
||||
}
|
||||
|
||||
logger.debug(`[DNS] ${hostname} -> ${ip} (${new Date() - t} ms)`)
|
||||
this.cache.set(hostname, ip)
|
||||
return ip
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
logger.debug(`[DNS] cannot resolve hostname ${hostname} (${error})`)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
const { promisify } = require('util')
|
||||
const doh = require('dns-over-http')
|
||||
const BaseDNS = require('./base')
|
||||
|
||||
const dohQueryAsync = promisify(doh.query)
|
||||
|
||||
module.exports = class DNSOverHTTPS extends BaseDNS {
|
||||
constructor (dnsServer) {
|
||||
super()
|
||||
this.dnsServer = dnsServer
|
||||
}
|
||||
|
||||
async _lookup (hostname) {
|
||||
const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
||||
return result.answers[0].data
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
const DNSOverTLS = require('./tls.js')
|
||||
const DNSOverHTTPS = require('./https.js')
|
||||
module.exports = {
|
||||
initDNS (dnsProviders) {
|
||||
const dnsMap = {}
|
||||
for (const provider in dnsProviders) {
|
||||
const conf = dnsProviders[provider]
|
||||
dnsMap[provider] = conf.type === 'https' ? new DNSOverHTTPS(conf.server) : new DNSOverTLS(conf.server)
|
||||
}
|
||||
return dnsMap
|
||||
},
|
||||
hasDnsLookup (dnsConfig, hostname) {
|
||||
let providerName = dnsConfig.mapping[hostname]
|
||||
if (!providerName) {
|
||||
for (const target in dnsConfig.mapping) {
|
||||
if (target.indexOf('*') < 0) {
|
||||
continue
|
||||
}
|
||||
const regexp = target.replace('.', '\\.')
|
||||
.replace('*', '.*')
|
||||
if (hostname.match(regexp)) {
|
||||
providerName = dnsConfig.mapping[target]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (providerName) {
|
||||
console.log('匹配到dns:', providerName, hostname)
|
||||
return dnsConfig.providers[providerName]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
const dnstls = require('dns-over-tls')
|
||||
const BaseDNS = require('./base')
|
||||
|
||||
module.exports = class DNSOverTLS extends BaseDNS {
|
||||
async _lookup (hostname) {
|
||||
const { answers } = await dnstls.query(hostname)
|
||||
|
||||
const answer = answers.find(answer => answer.type === 'A' && answer.class === 'IN')
|
||||
|
||||
if (answer) {
|
||||
return answer.data
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
const url = require('url')
|
||||
module.exports = {
|
||||
requestInterceptor (interceptOpt, rOptions, req, res, ssl) {
|
||||
req.abort()
|
||||
console.log('abort:', rOptions.hostname, req.url)
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return !!interceptOpt.abort
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const url = require('url')
|
||||
module.exports = {
|
||||
requestInterceptor (interceptOpt, rOptions, req, res, ssl) {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const URL = url.parse(interceptOpt.proxy)
|
||||
rOptions.protocol = URL.protocol
|
||||
rOptions.hostname = URL.host
|
||||
rOptions.host = URL.host
|
||||
rOptions.headers.host = URL.host
|
||||
if (URL.port == null) {
|
||||
rOptions.port = rOptions.protocol === 'https:' ? 443 : 80
|
||||
}
|
||||
|
||||
console.log('proxy:', rOptions.hostname, req.url, interceptOpt.proxy)
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return !!interceptOpt.proxy
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
requestInterceptor (interceptOpt, rOptions, req, res, ssl) {
|
||||
const url = req.url
|
||||
let redirect
|
||||
if (typeof interceptOpt.redirect === 'string') {
|
||||
redirect = interceptOpt.redirect + url
|
||||
} else {
|
||||
redirect = interceptOpt.redirect(url)
|
||||
}
|
||||
console.log('请求重定向:', rOptions.hostname, url, redirect)
|
||||
res.writeHead(302, { Location: redirect })
|
||||
res.end()
|
||||
return true
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return interceptOpt.redirect // 如果配置中有redirect,那么这个配置是需要redirect拦截的
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
const proxy = require('./impl/proxy')
|
||||
const redirect = require('./impl/redirect')
|
||||
|
||||
const modules = [proxy, redirect]
|
||||
|
||||
module.exports = modules
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env node
|
||||
require('babel-polyfill')
|
||||
const mitmproxy = require('../mitmproxy')
|
||||
const program = require('commander')
|
||||
const packageJson = require('../../package.json')
|
||||
// const tlsUtils = require('../tls/tlsUtils')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const colors = require('colors')
|
||||
|
||||
fs.existsSync = fs.existsSync || path.existsSync
|
||||
|
||||
program
|
||||
.version(packageJson.version)
|
||||
.option('-c, --config [value]', 'config file path')
|
||||
.parse(process.argv)
|
||||
|
||||
console.log(program.config)
|
||||
|
||||
const configPath = path.resolve(program.config)
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
const configObject = require(configPath)
|
||||
|
||||
if (typeof configObject !== 'object') {
|
||||
console.error(colors.red(`Config Error in ${configPath}`))
|
||||
} else {
|
||||
mitmproxy.createProxy(configObject)
|
||||
}
|
||||
} else {
|
||||
console.error(colors.red(`Can not find \`config file\` file: ${configPath}`))
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
const AgentOrigin = require('agentkeepalive')
|
||||
|
||||
module.exports = class Agent extends AgentOrigin {
|
||||
// Hacky
|
||||
getName (option) {
|
||||
let name = AgentOrigin.prototype.getName.call(this, option)
|
||||
name += ':'
|
||||
if (option.customSocketId) {
|
||||
name += option.customSocketId
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
const HttpsAgentOrigin = require('agentkeepalive').HttpsAgent
|
||||
|
||||
module.exports = class HttpsAgent extends HttpsAgentOrigin {
|
||||
// Hacky
|
||||
getName (option) {
|
||||
let name = HttpsAgentOrigin.prototype.getName.call(this, option)
|
||||
name += ':'
|
||||
if (option.customSocketId) {
|
||||
name += option.customSocketId
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
const path = require('path')
|
||||
|
||||
const config = exports
|
||||
|
||||
config.caCertFileName = 'dev-sidecar.ca.crt'
|
||||
|
||||
config.caKeyFileName = 'dev-sidecar.ca.key.pem'
|
||||
|
||||
config.defaultPort = 1181
|
||||
|
||||
config.caName = 'Dev-Sidecar CA'
|
||||
|
||||
config.getDefaultCABasePath = function () {
|
||||
const userHome = process.env.HOME || process.env.USERPROFILE
|
||||
return path.resolve(userHome, './.dev-sidecar')
|
||||
}
|
||||
|
||||
config.getDefaultCACertPath = function () {
|
||||
return path.resolve(config.getDefaultCABasePath(), config.caCertFileName)
|
||||
}
|
||||
|
||||
config.getDefaultCAKeyPath = function () {
|
||||
return path.resolve(config.getDefaultCABasePath(), config.caKeyFileName)
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
const url = require('url')
|
||||
const Agent = require('./ProxyHttpAgent')
|
||||
const HttpsAgent = require('./ProxyHttpsAgent')
|
||||
const tunnelAgent = require('tunnel-agent')
|
||||
|
||||
const util = exports
|
||||
const httpsAgent = new HttpsAgent({
|
||||
keepAlive: true,
|
||||
timeout: 60000,
|
||||
keepAliveTimeout: 30000, // free socket keepalive for 30 seconds
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
const httpAgent = new Agent({
|
||||
keepAlive: true,
|
||||
timeout: 60000,
|
||||
keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
|
||||
})
|
||||
let socketId = 0
|
||||
|
||||
let httpsOverHttpAgent, httpOverHttpsAgent, httpsOverHttpsAgent
|
||||
|
||||
util.getOptionsFormRequest = (req, ssl, externalProxy = null) => {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const urlObject = url.parse(req.url)
|
||||
const defaultPort = ssl ? 443 : 80
|
||||
const protocol = ssl ? 'https:' : 'http:'
|
||||
const headers = Object.assign({}, req.headers)
|
||||
let externalProxyUrl = null
|
||||
|
||||
if (externalProxy) {
|
||||
if (typeof externalProxy === 'string') {
|
||||
externalProxyUrl = externalProxy
|
||||
} else if (typeof externalProxy === 'function') {
|
||||
try {
|
||||
externalProxyUrl = externalProxy(req, ssl)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete headers['proxy-connection']
|
||||
let agent = false
|
||||
if (!externalProxyUrl) {
|
||||
// keepAlive
|
||||
if (headers.connection !== 'close') {
|
||||
if (protocol === 'https:') {
|
||||
agent = httpsAgent
|
||||
} else {
|
||||
agent = httpAgent
|
||||
}
|
||||
headers.connection = 'keep-alive'
|
||||
}
|
||||
} else {
|
||||
agent = util.getTunnelAgent(protocol === 'https:', externalProxyUrl)
|
||||
}
|
||||
|
||||
const options = {
|
||||
protocol: protocol,
|
||||
hostname: req.headers.host.split(':')[0],
|
||||
method: req.method,
|
||||
port: req.headers.host.split(':')[1] || defaultPort,
|
||||
path: urlObject.path,
|
||||
headers: req.headers,
|
||||
agent: agent
|
||||
}
|
||||
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
if (protocol === 'http:' && externalProxyUrl && (url.parse(externalProxyUrl)).protocol === 'http:') {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const externalURL = url.parse(externalProxyUrl)
|
||||
options.hostname = externalURL.hostname
|
||||
options.port = externalURL.port
|
||||
// support non-transparent proxy
|
||||
options.path = `http://${urlObject.host}${urlObject.path}`
|
||||
}
|
||||
|
||||
// mark a socketId for Agent to bind socket for NTLM
|
||||
if (req.socket.customSocketId) {
|
||||
options.customSocketId = req.socket.customSocketId
|
||||
} else if (headers.authorization) {
|
||||
options.customSocketId = req.socket.customSocketId = socketId++
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
util.getTunnelAgent = (requestIsSSL, externalProxyUrl) => {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const urlObject = url.parse(externalProxyUrl)
|
||||
const protocol = urlObject.protocol || 'http:'
|
||||
let port = urlObject.port
|
||||
if (!port) {
|
||||
port = protocol === 'http:' ? 80 : 443
|
||||
}
|
||||
const hostname = urlObject.hostname || 'localhost'
|
||||
|
||||
if (requestIsSSL) {
|
||||
if (protocol === 'http:') {
|
||||
if (!httpsOverHttpAgent) {
|
||||
httpsOverHttpAgent = tunnelAgent.httpsOverHttp({
|
||||
proxy: {
|
||||
host: hostname,
|
||||
port: port
|
||||
}
|
||||
})
|
||||
}
|
||||
return httpsOverHttpAgent
|
||||
} else {
|
||||
if (!httpsOverHttpsAgent) {
|
||||
httpsOverHttpsAgent = tunnelAgent.httpsOverHttps({
|
||||
proxy: {
|
||||
host: hostname,
|
||||
port: port
|
||||
}
|
||||
})
|
||||
}
|
||||
return httpsOverHttpsAgent
|
||||
}
|
||||
} else {
|
||||
if (protocol === 'http:') {
|
||||
// if (!httpOverHttpAgent) {
|
||||
// httpOverHttpAgent = tunnelAgent.httpOverHttp({
|
||||
// proxy: {
|
||||
// host: hostname,
|
||||
// port: port
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
return false
|
||||
} else {
|
||||
if (!httpOverHttpsAgent) {
|
||||
httpOverHttpsAgent = tunnelAgent.httpOverHttps({
|
||||
proxy: {
|
||||
host: hostname,
|
||||
port: port
|
||||
}
|
||||
})
|
||||
}
|
||||
return httpOverHttpsAgent
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
require('babel-polyfill')
|
||||
module.exports = require('./mitmproxy')
|
|
@ -0,0 +1,98 @@
|
|||
const through = require('through2')
|
||||
const zlib = require('zlib')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const url = require('url')
|
||||
|
||||
const httpUtil = {}
|
||||
httpUtil.isGzip = function (res) {
|
||||
const contentEncoding = res.headers['content-encoding']
|
||||
return !!(contentEncoding && contentEncoding.toLowerCase() === 'gzip')
|
||||
}
|
||||
httpUtil.isHtml = function (res) {
|
||||
const contentType = res.headers['content-type']
|
||||
return (typeof contentType !== 'undefined') && /text\/html|application\/xhtml\+xml/.test(contentType)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function injectContentIntoHtmlHead (html, content) {
|
||||
html = html.replace(/(<\/head>)/i, function (match) {
|
||||
return content + match
|
||||
})
|
||||
return html
|
||||
}
|
||||
function injectScriptIntoHtmlHead (html, content) {
|
||||
return html
|
||||
}
|
||||
function injectContentIntoHtmlBody (html, content) {
|
||||
html = html.replace(/(<\/body>)/i, function (match) {
|
||||
return content + match
|
||||
})
|
||||
return html
|
||||
}
|
||||
|
||||
function chunkReplace (_this, chunk, enc, callback, headContent, bodyContent) {
|
||||
let chunkString = chunk.toString()
|
||||
if (headContent) {
|
||||
chunkString = injectScriptIntoHtmlHead(chunkString, headContent)
|
||||
}
|
||||
if (bodyContent) {
|
||||
chunkString = injectContentIntoHtmlBody(chunkString, bodyContent)
|
||||
}
|
||||
_this.push(Buffer.alloc(chunkString))
|
||||
callback()
|
||||
}
|
||||
|
||||
module.exports = class InjectHtmlPlugin {
|
||||
constructor ({
|
||||
head,
|
||||
body
|
||||
}) {
|
||||
this.head = head
|
||||
this.body = body
|
||||
}
|
||||
|
||||
responseInterceptor (req, res, proxyReq, proxyRes, ssl, next) {
|
||||
if (!this.head && !this.body) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const isHtml = httpUtil.isHtml(proxyRes)
|
||||
const contentLengthIsZero = (() => {
|
||||
return proxyRes.headers['content-length'] === 0
|
||||
})()
|
||||
if (!isHtml || contentLengthIsZero) {
|
||||
next()
|
||||
} else {
|
||||
Object.keys(proxyRes.headers).forEach(function (key) {
|
||||
if (proxyRes.headers[key] !== undefined) {
|
||||
let newkey = key.replace(/^[a-z]|-[a-z]/g, (match) => {
|
||||
return match.toUpperCase()
|
||||
})
|
||||
newkey = key
|
||||
if (isHtml && key === 'content-length') {
|
||||
// do nothing
|
||||
} else {
|
||||
res.setHeader(newkey, proxyRes.headers[key])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
res.writeHead(proxyRes.statusCode)
|
||||
|
||||
const isGzip = httpUtil.isGzip(proxyRes)
|
||||
|
||||
if (isGzip) {
|
||||
proxyRes.pipe(new zlib.Gunzip())
|
||||
.pipe(through(function (chunk, enc, callback) {
|
||||
chunkReplace(this, chunk, enc, callback, this.head, this.body)
|
||||
})).pipe(new zlib.Gzip()).pipe(res)
|
||||
} else {
|
||||
proxyRes.pipe(through(function (chunk, enc, callback) {
|
||||
chunkReplace(this, chunk, enc, callback, this.head, this.body)
|
||||
})).pipe(res)
|
||||
}
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
const net = require('net')
|
||||
const url = require('url')
|
||||
// const colors = require('colors')
|
||||
const DnsUtil = require('../../dns/index')
|
||||
const localIP = '127.0.0.1'
|
||||
// create connectHandler function
|
||||
module.exports = function createConnectHandler (sslConnectInterceptor, fakeServerCenter, dnsConfig) {
|
||||
// return
|
||||
return function connectHandler (req, cltSocket, head) {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const srvUrl = url.parse(`https://${req.url}`)
|
||||
|
||||
const hostname = srvUrl.hostname
|
||||
if (typeof sslConnectInterceptor === 'function' && sslConnectInterceptor(req, cltSocket, head)) {
|
||||
fakeServerCenter.getServerPromise(hostname, srvUrl.port).then((serverObj) => {
|
||||
connect(req, cltSocket, head, localIP, serverObj.port)
|
||||
}, (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
} else {
|
||||
if (dnsConfig) {
|
||||
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
|
||||
if (dns) {
|
||||
dns.lookup(hostname).then(ip => {
|
||||
connect(req, cltSocket, head, ip, srvUrl.port)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
connect(req, cltSocket, head, hostname, srvUrl.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function connect (req, cltSocket, head, hostname, port) {
|
||||
// tunneling https
|
||||
// console.log('connect:', hostname, port)
|
||||
try {
|
||||
const proxySocket = net.connect(port, hostname, () => {
|
||||
cltSocket.write('HTTP/1.1 200 Connection Established\r\n' +
|
||||
'Proxy-agent: dev-sidecar\r\n' +
|
||||
'\r\n')
|
||||
|
||||
proxySocket.write(head)
|
||||
proxySocket.pipe(cltSocket)
|
||||
|
||||
cltSocket.pipe(proxySocket)
|
||||
})
|
||||
proxySocket.on('error', (e) => {
|
||||
// 连接失败,可能被GFW拦截,或者服务端拥挤
|
||||
console.error('代理连接失败:', e.errno, hostname, port)
|
||||
cltSocket.destroy()
|
||||
})
|
||||
return proxySocket
|
||||
} catch (error) {
|
||||
console.log('err', error)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
const fs = require('fs')
|
||||
const forge = require('node-forge')
|
||||
const FakeServersCenter = require('../tls/FakeServersCenter')
|
||||
const colors = require('colors')
|
||||
|
||||
module.exports = function createFakeServerCenter ({
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
requestHandler,
|
||||
upgradeHandler,
|
||||
getCertSocketTimeout
|
||||
}) {
|
||||
let caCert
|
||||
let caKey
|
||||
try {
|
||||
fs.accessSync(caCertPath, fs.F_OK)
|
||||
fs.accessSync(caKeyPath, fs.F_OK)
|
||||
const caCertPem = fs.readFileSync(caCertPath)
|
||||
const caKeyPem = fs.readFileSync(caKeyPath)
|
||||
caCert = forge.pki.certificateFromPem(caCertPem)
|
||||
caKey = forge.pki.privateKeyFromPem(caKeyPem)
|
||||
} catch (e) {
|
||||
console.log(colors.red('Can not find `CA certificate` or `CA key`.'), e)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return new FakeServersCenter({
|
||||
caCert,
|
||||
caKey,
|
||||
maxLength: 100,
|
||||
requestHandler,
|
||||
upgradeHandler,
|
||||
getCertSocketTimeout
|
||||
})
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
const http = require('http')
|
||||
const https = require('https')
|
||||
const commonUtil = require('../common/util')
|
||||
// const upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i
|
||||
const DnsUtil = require('../../dns/index')
|
||||
|
||||
// create requestHandler function
|
||||
module.exports = function createRequestHandler (requestInterceptor, responseInterceptor, middlewares, externalProxy, dnsConfig) {
|
||||
// return
|
||||
return function requestHandler (req, res, ssl) {
|
||||
let proxyReq
|
||||
|
||||
const rOptions = commonUtil.getOptionsFormRequest(req, ssl, externalProxy)
|
||||
|
||||
if (rOptions.headers.connection === 'close') {
|
||||
req.socket.setKeepAlive(false)
|
||||
} else if (rOptions.customSocketId != null) { // for NTLM
|
||||
req.socket.setKeepAlive(true, 60 * 60 * 1000)
|
||||
} else {
|
||||
req.socket.setKeepAlive(true, 30000)
|
||||
}
|
||||
|
||||
const requestInterceptorPromise = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const next = () => {
|
||||
resolve()
|
||||
}
|
||||
try {
|
||||
if (typeof requestInterceptor === 'function') {
|
||||
requestInterceptor(rOptions, req, res, ssl, next)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const proxyRequestPromise = async () => {
|
||||
rOptions.host = rOptions.hostname || rOptions.host || 'localhost'
|
||||
if (dnsConfig) {
|
||||
const dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.host)
|
||||
if (dns) {
|
||||
const ip = await dns.lookup(rOptions.host)
|
||||
console.log('使用自定义dns:', rOptions.host, ip, dns.dnsServer)
|
||||
rOptions.host = ip
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// use the binded socket for NTLM
|
||||
if (rOptions.agent && rOptions.customSocketId != null && rOptions.agent.getName) {
|
||||
const socketName = rOptions.agent.getName(rOptions)
|
||||
const bindingSocket = rOptions.agent.sockets[socketName]
|
||||
if (bindingSocket && bindingSocket.length > 0) {
|
||||
bindingSocket[0].once('free', onFree)
|
||||
return
|
||||
}
|
||||
}
|
||||
onFree()
|
||||
|
||||
function onFree () {
|
||||
const url = `${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
|
||||
const start = new Date().getTime()
|
||||
if (rOptions.protocol === 'https:') {
|
||||
console.log('代理请求:', url)
|
||||
}
|
||||
|
||||
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
|
||||
const end = new Date().getTime()
|
||||
if (rOptions.protocol === 'https:') {
|
||||
console.log('代理请求返回:', url, (end - start) + 'ms')
|
||||
}
|
||||
resolve(proxyRes)
|
||||
})
|
||||
|
||||
proxyReq.on('timeout', () => {
|
||||
console.error('代理请求超时', rOptions.host, rOptions.path)
|
||||
reject(new Error(`${rOptions.host}:${rOptions.port}, 代理请求超时`))
|
||||
})
|
||||
|
||||
proxyReq.on('error', (e, req, res) => {
|
||||
console.error('代理请求错误', e.errno, rOptions.host, rOptions.path)
|
||||
reject(e)
|
||||
if (res) {
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
|
||||
proxyReq.on('aborted', () => {
|
||||
console.error('代理请求被取消', rOptions.host, rOptions.path)
|
||||
reject(new Error('代理请求被取消'))
|
||||
req.abort()
|
||||
})
|
||||
|
||||
req.on('aborted', function () {
|
||||
console.error('请求被取消', rOptions.host, rOptions.path)
|
||||
proxyReq.abort()
|
||||
reject(new Error('请求被取消'))
|
||||
})
|
||||
req.on('error', function (e, req, res) {
|
||||
console.error('请求错误:', e.errno, rOptions.host, rOptions.path)
|
||||
reject(e)
|
||||
if (res) {
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
req.on('timeout', () => {
|
||||
console.error('请求超时', rOptions.host, rOptions.path)
|
||||
reject(new Error(`${rOptions.host}:${rOptions.port}, 请求超时`))
|
||||
})
|
||||
req.pipe(proxyReq)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// workflow control
|
||||
(async () => {
|
||||
await requestInterceptorPromise()
|
||||
|
||||
if (res.finished) {
|
||||
return false
|
||||
}
|
||||
|
||||
const proxyRes = await proxyRequestPromise()
|
||||
|
||||
const responseInterceptorPromise = new Promise((resolve, reject) => {
|
||||
const next = () => {
|
||||
resolve()
|
||||
}
|
||||
try {
|
||||
if (typeof responseInterceptor === 'function') {
|
||||
responseInterceptor(req, res, proxyReq, proxyRes, ssl, next)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
|
||||
await responseInterceptorPromise
|
||||
|
||||
if (res.finished) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!res.headersSent) { // prevent duplicate set headers
|
||||
Object.keys(proxyRes.headers).forEach(function (key) {
|
||||
if (proxyRes.headers[key] !== undefined) {
|
||||
// https://github.com/nodejitsu/node-http-proxy/issues/362
|
||||
if (/^www-authenticate$/i.test(key)) {
|
||||
if (proxyRes.headers[key]) {
|
||||
proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',')
|
||||
}
|
||||
key = 'www-authenticate'
|
||||
}
|
||||
res.setHeader(key, proxyRes.headers[key])
|
||||
}
|
||||
})
|
||||
|
||||
res.writeHead(proxyRes.statusCode)
|
||||
proxyRes.pipe(res)
|
||||
}
|
||||
})().then(
|
||||
(flag) => {
|
||||
// do nothing
|
||||
},
|
||||
(e) => {
|
||||
if (!res.finished) {
|
||||
res.writeHead(500)
|
||||
res.write(`Dev-Sidecar Warning:\n\n ${e.toString()}`)
|
||||
res.end()
|
||||
}
|
||||
console.error(e)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
const http = require('http')
|
||||
const https = require('https')
|
||||
const util = require('../common/util')
|
||||
|
||||
// copy from node-http-proxy. ^_^
|
||||
|
||||
// create connectHandler function
|
||||
module.exports = function createUpgradeHandler () {
|
||||
// return
|
||||
return function upgradeHandler (req, cltSocket, head, ssl) {
|
||||
const clientOptions = util.getOptionsFormRequest(req, ssl)
|
||||
const proxyReq = (ssl ? https : http).request(clientOptions)
|
||||
proxyReq.on('error', (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
proxyReq.on('response', function (res) {
|
||||
// if upgrade event isn't going to happen, close the socket
|
||||
if (!res.upgrade) cltSocket.end()
|
||||
})
|
||||
|
||||
proxyReq.on('upgrade', function (proxyRes, proxySocket, proxyHead) {
|
||||
proxySocket.on('error', (e) => {
|
||||
console.log('error-----1111')
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
cltSocket.on('error', function () {
|
||||
console.log('error-----2222')
|
||||
proxySocket.end()
|
||||
})
|
||||
|
||||
proxySocket.setTimeout(0)
|
||||
proxySocket.setNoDelay(true)
|
||||
|
||||
proxySocket.setKeepAlive(true, 0)
|
||||
|
||||
if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead)
|
||||
|
||||
cltSocket.write(
|
||||
Object.keys(proxyRes.headers).reduce(function (head, key) {
|
||||
const value = proxyRes.headers[key]
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
head.push(key + ': ' + value)
|
||||
return head
|
||||
}
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
head.push(key + ': ' + value[i])
|
||||
}
|
||||
return head
|
||||
}, ['HTTP/1.1 101 Switching Protocols'])
|
||||
.join('\r\n') + '\r\n\r\n'
|
||||
)
|
||||
|
||||
proxySocket.pipe(cltSocket).pipe(proxySocket)
|
||||
})
|
||||
proxyReq.end()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
const tlsUtils = require('../tls/tlsUtils')
|
||||
const http = require('http')
|
||||
const config = require('../common/config')
|
||||
const colors = require('colors')
|
||||
const createRequestHandler = require('./createRequestHandler')
|
||||
const createConnectHandler = require('./createConnectHandler')
|
||||
const createFakeServerCenter = require('./createFakeServerCenter')
|
||||
const createUpgradeHandler = require('./createUpgradeHandler')
|
||||
|
||||
module.exports = {
|
||||
createProxy ({
|
||||
port = config.defaultPort,
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
sslConnectInterceptor,
|
||||
requestInterceptor,
|
||||
responseInterceptor,
|
||||
getCertSocketTimeout = 1 * 1000,
|
||||
middlewares = [],
|
||||
externalProxy,
|
||||
dnsConfig
|
||||
}, callback) {
|
||||
// Don't reject unauthorized
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
if (!caCertPath && !caKeyPath) {
|
||||
const rs = this.createCA()
|
||||
caCertPath = rs.caCertPath
|
||||
caKeyPath = rs.caKeyPath
|
||||
if (rs.create) {
|
||||
console.log(colors.cyan(`CA Cert saved in: ${caCertPath}`))
|
||||
console.log(colors.cyan(`CA private key saved in: ${caKeyPath}`))
|
||||
}
|
||||
}
|
||||
|
||||
port = ~~port
|
||||
const requestHandler = createRequestHandler(
|
||||
requestInterceptor,
|
||||
responseInterceptor,
|
||||
middlewares,
|
||||
externalProxy,
|
||||
dnsConfig
|
||||
)
|
||||
|
||||
const upgradeHandler = createUpgradeHandler()
|
||||
|
||||
const fakeServersCenter = createFakeServerCenter({
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
requestHandler,
|
||||
upgradeHandler,
|
||||
getCertSocketTimeout
|
||||
})
|
||||
|
||||
const connectHandler = createConnectHandler(
|
||||
sslConnectInterceptor,
|
||||
fakeServersCenter,
|
||||
dnsConfig
|
||||
)
|
||||
|
||||
const server = new http.Server()
|
||||
server.listen(port, () => {
|
||||
console.log(colors.green(`dev-sidecar启动端口: ${port}`))
|
||||
server.on('error', (e) => {
|
||||
console.error(colors.red(e))
|
||||
})
|
||||
server.on('request', (req, res) => {
|
||||
const ssl = false
|
||||
requestHandler(req, res, ssl)
|
||||
})
|
||||
// tunneling for https
|
||||
server.on('connect', (req, cltSocket, head) => {
|
||||
connectHandler(req, cltSocket, head)
|
||||
})
|
||||
// TODO: handler WebSocket
|
||||
server.on('upgrade', function (req, socket, head) {
|
||||
const ssl = false
|
||||
upgradeHandler(req, socket, head, ssl)
|
||||
})
|
||||
|
||||
if (callback) {
|
||||
callback(server)
|
||||
}
|
||||
})
|
||||
return server
|
||||
},
|
||||
createCA (caBasePath = config.getDefaultCABasePath()) {
|
||||
return tlsUtils.initCA(caBasePath)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
const _ = require('lodash')
|
||||
module.exports = (middlewares) => {
|
||||
if (middlewares) {
|
||||
if (Object.prototype.toString.call(middlewares) !== '[object Array]') {
|
||||
throw new TypeError('middlewares must be a array')
|
||||
}
|
||||
}
|
||||
//
|
||||
// const sslConnectInterceptors = []
|
||||
// const requestInterceptors = []
|
||||
// const responseInterceptors = []
|
||||
|
||||
_.each(middlewares, (m) => {
|
||||
if (m.buildIn === false || m.buildIn === 'false') {
|
||||
|
||||
} else {
|
||||
// m.name
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
const tlsUtils = require('./tlsUtils')
|
||||
const https = require('https')
|
||||
|
||||
module.exports = class CertAndKeyContainer {
|
||||
constructor ({
|
||||
maxLength = 1000,
|
||||
getCertSocketTimeout = 2 * 1000,
|
||||
caCert,
|
||||
caKey
|
||||
}) {
|
||||
this.queue = []
|
||||
this.maxLength = maxLength
|
||||
this.getCertSocketTimeout = getCertSocketTimeout
|
||||
this.caCert = caCert
|
||||
this.caKey = caKey
|
||||
}
|
||||
|
||||
addCertPromise (certPromiseObj) {
|
||||
if (this.queue.length >= this.maxLength) {
|
||||
this.queue.shift()
|
||||
}
|
||||
this.queue.push(certPromiseObj)
|
||||
return certPromiseObj
|
||||
}
|
||||
|
||||
getCertPromise (hostname, port) {
|
||||
for (let i = 0; i < this.queue.length; i++) {
|
||||
const _certPromiseObj = this.queue[i]
|
||||
const mappingHostNames = _certPromiseObj.mappingHostNames
|
||||
for (let j = 0; j < mappingHostNames.length; j++) {
|
||||
const DNSName = mappingHostNames[j]
|
||||
if (tlsUtils.isMappingHostName(DNSName, hostname)) {
|
||||
this.reRankCert(i)
|
||||
return _certPromiseObj.promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const certPromiseObj = {
|
||||
mappingHostNames: [hostname] // temporary hostname
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
let once = true
|
||||
const _resolve = (_certObj) => {
|
||||
if (once) {
|
||||
once = false
|
||||
const mappingHostNames = tlsUtils.getMappingHostNamesFormCert(_certObj.cert)
|
||||
certPromiseObj.mappingHostNames = mappingHostNames // change
|
||||
resolve(_certObj)
|
||||
}
|
||||
}
|
||||
let certObj
|
||||
const fast = true
|
||||
if (fast) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
_resolve(certObj)
|
||||
} else {
|
||||
// 这个太慢了
|
||||
const preReq = https.request({
|
||||
port: port,
|
||||
hostname: hostname,
|
||||
path: '/',
|
||||
method: 'HEAD'
|
||||
}, (preRes) => {
|
||||
try {
|
||||
const realCert = preRes.socket.getPeerCertificate()
|
||||
if (realCert) {
|
||||
try {
|
||||
certObj = tlsUtils.createFakeCertificateByCA(this.caKey, this.caCert, realCert)
|
||||
} catch (error) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
}
|
||||
} else {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
}
|
||||
_resolve(certObj)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
preReq.setTimeout(~~this.getCertSocketTimeout, () => {
|
||||
if (!certObj) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
_resolve(certObj)
|
||||
}
|
||||
})
|
||||
preReq.on('error', (e) => {
|
||||
if (!certObj) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
_resolve(certObj)
|
||||
}
|
||||
})
|
||||
preReq.end()
|
||||
}
|
||||
})
|
||||
|
||||
certPromiseObj.promise = promise
|
||||
|
||||
return (this.addCertPromise(certPromiseObj)).promise
|
||||
}
|
||||
|
||||
reRankCert (index) {
|
||||
// index ==> queue foot
|
||||
this.queue.push((this.queue.splice(index, 1))[0])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
const https = require('https')
|
||||
const tlsUtils = require('./tlsUtils')
|
||||
const CertAndKeyContainer = require('./CertAndKeyContainer')
|
||||
const forge = require('node-forge')
|
||||
const pki = forge.pki
|
||||
// const colors = require('colors')
|
||||
const tls = require('tls')
|
||||
|
||||
module.exports = class FakeServersCenter {
|
||||
constructor ({ maxLength = 256, requestHandler, upgradeHandler, caCert, caKey, getCertSocketTimeout }) {
|
||||
this.queue = []
|
||||
this.maxLength = maxLength
|
||||
this.requestHandler = requestHandler
|
||||
this.upgradeHandler = upgradeHandler
|
||||
this.certAndKeyContainer = new CertAndKeyContainer({
|
||||
getCertSocketTimeout,
|
||||
caCert,
|
||||
caKey
|
||||
})
|
||||
}
|
||||
|
||||
addServerPromise (serverPromiseObj) {
|
||||
if (this.queue.length >= this.maxLength) {
|
||||
const delServerObj = this.queue.shift()
|
||||
try {
|
||||
console.log('超过最大服务数量,删除旧服务', delServerObj)
|
||||
delServerObj.serverObj.server.close()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
console.log('add server promise:', serverPromiseObj)
|
||||
this.queue.push(serverPromiseObj)
|
||||
return serverPromiseObj
|
||||
}
|
||||
|
||||
getServerPromise (hostname, port) {
|
||||
for (let i = 0; i < this.queue.length; i++) {
|
||||
const serverPromiseObj = this.queue[i]
|
||||
const mappingHostNames = serverPromiseObj.mappingHostNames
|
||||
for (let j = 0; j < mappingHostNames.length; j++) {
|
||||
const DNSName = mappingHostNames[j]
|
||||
if (tlsUtils.isMappingHostName(DNSName, hostname)) {
|
||||
this.reRankServer(i)
|
||||
return serverPromiseObj.promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverPromiseObj = {
|
||||
mappingHostNames: [hostname] // temporary hostname
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const certObj = await this.certAndKeyContainer.getCertPromise(hostname, port)
|
||||
const cert = certObj.cert
|
||||
const key = certObj.key
|
||||
const certPem = pki.certificateToPem(cert)
|
||||
const keyPem = pki.privateKeyToPem(key)
|
||||
const fakeServer = new https.Server({
|
||||
key: keyPem,
|
||||
cert: certPem,
|
||||
SNICallback: (hostname, done) => {
|
||||
(async () => {
|
||||
const certObj = await this.certAndKeyContainer.getCertPromise(hostname, port)
|
||||
done(null, tls.createSecureContext({
|
||||
key: pki.privateKeyToPem(certObj.key),
|
||||
cert: pki.certificateToPem(certObj.cert)
|
||||
}))
|
||||
})()
|
||||
}
|
||||
})
|
||||
const serverObj = {
|
||||
cert,
|
||||
key,
|
||||
server: fakeServer,
|
||||
port: 0 // if prot === 0 ,should listen server's `listening` event.
|
||||
}
|
||||
serverPromiseObj.serverObj = serverObj
|
||||
fakeServer.listen(0, () => {
|
||||
const address = fakeServer.address()
|
||||
serverObj.port = address.port
|
||||
})
|
||||
fakeServer.on('request', (req, res) => {
|
||||
const ssl = true
|
||||
this.requestHandler(req, res, ssl)
|
||||
})
|
||||
fakeServer.on('error', (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
fakeServer.on('listening', () => {
|
||||
const mappingHostNames = tlsUtils.getMappingHostNamesFormCert(certObj.cert)
|
||||
serverPromiseObj.mappingHostNames = mappingHostNames
|
||||
resolve(serverObj)
|
||||
})
|
||||
fakeServer.on('upgrade', (req, socket, head) => {
|
||||
const ssl = true
|
||||
this.upgradeHandler(req, socket, head, ssl)
|
||||
})
|
||||
})()
|
||||
})
|
||||
|
||||
serverPromiseObj.promise = promise
|
||||
|
||||
return (this.addServerPromise(serverPromiseObj)).promise
|
||||
}
|
||||
|
||||
reRankServer (index) {
|
||||
// index ==> queue foot
|
||||
this.queue.push((this.queue.splice(index, 1))[0])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
const forge = require('node-forge')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const config = require('../common/config')
|
||||
const _ = require('lodash')
|
||||
const mkdirp = require('mkdirp')
|
||||
// const colors = require('colors')
|
||||
|
||||
const utils = exports
|
||||
const pki = forge.pki
|
||||
|
||||
utils.createCA = function (CN) {
|
||||
const keys = pki.rsa.generateKeyPair(2046)
|
||||
const cert = pki.createCertificate()
|
||||
cert.publicKey = keys.publicKey
|
||||
cert.serialNumber = (new Date()).getTime() + ''
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 5)
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20)
|
||||
const attrs = [{
|
||||
name: 'commonName',
|
||||
value: CN
|
||||
}, {
|
||||
name: 'countryName',
|
||||
value: 'CN'
|
||||
}, {
|
||||
shortName: 'ST',
|
||||
value: 'GuangDong'
|
||||
}, {
|
||||
name: 'localityName',
|
||||
value: 'ShenZhen'
|
||||
}, {
|
||||
name: 'organizationName',
|
||||
value: 'dev-sidecar'
|
||||
}, {
|
||||
shortName: 'OU',
|
||||
value: 'https://github.com/docmirror/dev-sidecar'
|
||||
}]
|
||||
cert.setSubject(attrs)
|
||||
cert.setIssuer(attrs)
|
||||
cert.setExtensions([{
|
||||
name: 'basicConstraints',
|
||||
critical: true,
|
||||
cA: true
|
||||
}, {
|
||||
name: 'keyUsage',
|
||||
critical: true,
|
||||
keyCertSign: true
|
||||
}, {
|
||||
name: 'subjectKeyIdentifier'
|
||||
}])
|
||||
|
||||
// self-sign certificate
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create())
|
||||
|
||||
return {
|
||||
key: keys.privateKey,
|
||||
cert: cert
|
||||
}
|
||||
}
|
||||
|
||||
utils.covertNodeCertToForgeCert = function (originCertificate) {
|
||||
const obj = forge.asn1.fromDer(originCertificate.raw.toString('binary'))
|
||||
return forge.pki.certificateFromAsn1(obj)
|
||||
}
|
||||
|
||||
utils.createFakeCertificateByDomain = function (caKey, caCert, domain) {
|
||||
const keys = pki.rsa.generateKeyPair(2046)
|
||||
const cert = pki.createCertificate()
|
||||
cert.publicKey = keys.publicKey
|
||||
|
||||
cert.serialNumber = (new Date()).getTime() + ''
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1)
|
||||
const attrs = [{
|
||||
name: 'commonName',
|
||||
value: domain
|
||||
}, {
|
||||
name: 'countryName',
|
||||
value: 'CN'
|
||||
}, {
|
||||
shortName: 'ST',
|
||||
value: 'GuangDong'
|
||||
}, {
|
||||
name: 'localityName',
|
||||
value: 'ShengZhen'
|
||||
}, {
|
||||
name: 'organizationName',
|
||||
value: 'dev-sidecar'
|
||||
}, {
|
||||
shortName: 'OU',
|
||||
value: 'https://github.com/docmirror/dev-sidecar'
|
||||
}]
|
||||
|
||||
cert.setIssuer(caCert.subject.attributes)
|
||||
cert.setSubject(attrs)
|
||||
|
||||
cert.setExtensions([{
|
||||
name: 'basicConstraints',
|
||||
critical: true,
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
critical: true,
|
||||
digitalSignature: true,
|
||||
contentCommitment: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true,
|
||||
keyAgreement: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true,
|
||||
encipherOnly: true,
|
||||
decipherOnly: true
|
||||
},
|
||||
{
|
||||
name: 'subjectAltName',
|
||||
altNames: [{
|
||||
type: 2,
|
||||
value: domain
|
||||
}]
|
||||
},
|
||||
{
|
||||
name: 'subjectKeyIdentifier'
|
||||
},
|
||||
{
|
||||
name: 'extKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true,
|
||||
codeSigning: true,
|
||||
emailProtection: true,
|
||||
timeStamping: true
|
||||
},
|
||||
{
|
||||
name: 'authorityKeyIdentifier'
|
||||
}])
|
||||
cert.sign(caKey, forge.md.sha256.create())
|
||||
|
||||
return {
|
||||
key: keys.privateKey,
|
||||
cert: cert
|
||||
}
|
||||
}
|
||||
|
||||
utils.createFakeCertificateByCA = function (caKey, caCert, originCertificate) {
|
||||
const certificate = utils.covertNodeCertToForgeCert(originCertificate)
|
||||
|
||||
const keys = pki.rsa.generateKeyPair(2046)
|
||||
const cert = pki.createCertificate()
|
||||
cert.publicKey = keys.publicKey
|
||||
|
||||
cert.serialNumber = certificate.serialNumber
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1)
|
||||
|
||||
cert.setSubject(certificate.subject.attributes)
|
||||
cert.setIssuer(caCert.subject.attributes)
|
||||
|
||||
certificate.subjectaltname && (cert.subjectaltname = certificate.subjectaltname)
|
||||
|
||||
const subjectAltName = _.find(certificate.extensions, { name: 'subjectAltName' })
|
||||
cert.setExtensions([{
|
||||
name: 'basicConstraints',
|
||||
critical: true,
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
critical: true,
|
||||
digitalSignature: true,
|
||||
contentCommitment: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true,
|
||||
keyAgreement: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true,
|
||||
encipherOnly: true,
|
||||
decipherOnly: true
|
||||
},
|
||||
{
|
||||
name: 'subjectAltName',
|
||||
altNames: subjectAltName.altNames
|
||||
},
|
||||
{
|
||||
name: 'subjectKeyIdentifier'
|
||||
},
|
||||
{
|
||||
name: 'extKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true,
|
||||
codeSigning: true,
|
||||
emailProtection: true,
|
||||
timeStamping: true
|
||||
},
|
||||
{
|
||||
name: 'authorityKeyIdentifier'
|
||||
}])
|
||||
cert.sign(caKey, forge.md.sha256.create())
|
||||
|
||||
return {
|
||||
key: keys.privateKey,
|
||||
cert: cert
|
||||
}
|
||||
}
|
||||
|
||||
utils.isBrowserRequest = function (userAgent) {
|
||||
return /Mozilla/i.test(userAgent)
|
||||
}
|
||||
//
|
||||
// /^[^.]+\.a\.com$/.test('c.a.com')
|
||||
//
|
||||
utils.isMappingHostName = function (DNSName, hostname) {
|
||||
let reg = DNSName.replace(/\./g, '\\.').replace(/\*/g, '[^.]+')
|
||||
reg = '^' + reg + '$'
|
||||
return (new RegExp(reg)).test(hostname)
|
||||
}
|
||||
|
||||
utils.getMappingHostNamesFormCert = function (cert) {
|
||||
let mappingHostNames = []
|
||||
mappingHostNames.push(cert.subject.getField('CN') ? cert.subject.getField('CN').value : '')
|
||||
const altNames = cert.getExtension('subjectAltName') ? cert.getExtension('subjectAltName').altNames : []
|
||||
mappingHostNames = mappingHostNames.concat(_.map(altNames, 'value'))
|
||||
return mappingHostNames
|
||||
}
|
||||
|
||||
// sync
|
||||
utils.initCA = function (basePath = config.getDefaultCABasePath()) {
|
||||
const caCertPath = path.resolve(basePath, config.caCertFileName)
|
||||
const caKeyPath = path.resolve(basePath, config.caKeyFileName)
|
||||
|
||||
try {
|
||||
fs.accessSync(caCertPath, fs.F_OK)
|
||||
fs.accessSync(caKeyPath, fs.F_OK)
|
||||
|
||||
// has exist
|
||||
return {
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
create: false
|
||||
}
|
||||
} catch (e) {
|
||||
const caObj = utils.createCA(config.caName)
|
||||
|
||||
const caCert = caObj.cert
|
||||
const cakey = caObj.key
|
||||
|
||||
const certPem = pki.certificateToPem(caCert)
|
||||
const keyPem = pki.privateKeyToPem(cakey)
|
||||
|
||||
mkdirp.sync(path.dirname(caCertPath))
|
||||
fs.writeFileSync(caCertPath, certPem)
|
||||
fs.writeFileSync(caKeyPath, keyPem)
|
||||
}
|
||||
return {
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
create: true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
const cmd = require('node-cmd')
|
||||
const util = require('util')
|
||||
const winExec = util.promisify(cmd.get, { multiArgs: true, context: cmd })
|
||||
const os = require('os')
|
||||
|
||||
class SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
throw new Error('You have to implement the method setProxy!')
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
throw new Error('You have to implement the method unsetProxy!')
|
||||
}
|
||||
}
|
||||
|
||||
class DarwinSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
class LinuxSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
|
||||
class WindowsSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
let ret = await winExec(`yarn config set proxy=http://${ip}:${port}`)
|
||||
console.log('yarn http proxy set success', ret)
|
||||
|
||||
ret = await winExec(`yarn config set https-proxy=http://${ip}:${port}`)
|
||||
console.log('yarn https proxy set success', ret)
|
||||
|
||||
// ret = await winExec(`yarn config set cafile ${config.getDefaultCAKeyPath()}`)
|
||||
// console.log('yarn cafile set success', ret)
|
||||
|
||||
ret = await winExec('yarn config set strict-ssl false')
|
||||
console.log('yarn strict-ssl false success', ret)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
await winExec('yarn config delete proxy')
|
||||
console.log('yarn https proxy unset success')
|
||||
await winExec('yarn config delete https-proxy')
|
||||
console.log('yarn https proxy unset success')
|
||||
|
||||
// await winExec(`yarn config delete cafile`)
|
||||
// console.log('yarn cafile unset success')
|
||||
await winExec(' yarn config delete strict-ssl')
|
||||
console.log('yarn strict-ssl true success')
|
||||
}
|
||||
|
||||
static _asyncRegSet (regKey, name, type, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
regKey.set(name, type, value, e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemProxy () {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return DarwinSystemProxy
|
||||
case 'linux':
|
||||
return LinuxSystemProxy
|
||||
case 'win32':
|
||||
case 'win64':
|
||||
return WindowsSystemProxy
|
||||
case 'unknown os':
|
||||
default:
|
||||
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async setProxy (ip, port) {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.setProxy(ip, port)
|
||||
},
|
||||
async unsetProxy () {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.unsetProxy()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
const debug = require('debug')
|
||||
|
||||
module.exports = function getLogger (name) {
|
||||
return {
|
||||
debug: debug(`dev-sidecar:${name}:debug`),
|
||||
info: debug(`dev-sidecar:${name}:info`),
|
||||
error: debug(`dev-sidecar:${name}:error`)
|
||||
}
|
||||
}
|
||||
|
||||
debug.enable('dev-sidecar:*')
|
|
@ -0,0 +1,33 @@
|
|||
const ProxyOptions = require('./options')
|
||||
const mitmproxy = require('../lib/proxy')
|
||||
const getLogger = require('../lib/utils/logger')
|
||||
const logger = getLogger('proxy')
|
||||
const config = require('../config')
|
||||
const event = require('../event')
|
||||
let server
|
||||
module.exports = {
|
||||
async start (newConfig) {
|
||||
config.set(newConfig)
|
||||
const proxyOptions = ProxyOptions(config.get())
|
||||
server = mitmproxy.createProxy(proxyOptions, () => {
|
||||
event.fire('status', { key: 'server', value: true })
|
||||
server.on('close', () => {
|
||||
event.fire('status', { key: 'server', value: false })
|
||||
})
|
||||
})
|
||||
server.config = config.get()
|
||||
return server.config
|
||||
},
|
||||
close () {
|
||||
try {
|
||||
if (server) {
|
||||
server.close()
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
}
|
||||
},
|
||||
getServer () {
|
||||
return server
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
const getLogger = require('../lib/utils/logger')
|
||||
const logger = getLogger('proxy')
|
||||
const interceptors = require('../lib/interceptor')
|
||||
const dnsUtil = require('../lib/dns')
|
||||
function matchHostname (intercepts, hostname) {
|
||||
const interceptOpts = intercepts[hostname]
|
||||
if (interceptOpts) {
|
||||
return interceptOpts
|
||||
}
|
||||
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
|
||||
for (const target in intercepts) {
|
||||
if (target.indexOf('*') < 0) {
|
||||
continue
|
||||
}
|
||||
// 正则表达式匹配
|
||||
const regexp = target.replace('.', '\\.').replace('*', '.*')
|
||||
if (hostname.match(regexp)) {
|
||||
return intercepts[target]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMatched (url, regexp) {
|
||||
return url.match(regexp)
|
||||
}
|
||||
|
||||
module.exports = (config) => {
|
||||
console.log('config', config)
|
||||
return {
|
||||
port: config.server.port,
|
||||
dnsConfig: {
|
||||
providers: dnsUtil.initDNS(config.dns.providers), mapping: config.dns.mapping
|
||||
},
|
||||
sslConnectInterceptor: (req, cltSocket, head) => {
|
||||
const hostname = req.url.split(':')[0]
|
||||
return !!matchHostname(config.intercepts, hostname) // 配置了拦截的域名,将会被代理
|
||||
},
|
||||
requestInterceptor: (rOptions, req, res, ssl, next) => {
|
||||
const hostname = rOptions.hostname
|
||||
const interceptOpts = matchHostname(config.intercepts, hostname)
|
||||
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
for (const interceptOpt of interceptOpts) { // 遍历拦截配置
|
||||
let regexpList
|
||||
if (interceptOpt.regexp instanceof Array) {
|
||||
regexpList = interceptOpt.regexp
|
||||
} else {
|
||||
regexpList = [interceptOpt.regexp]
|
||||
}
|
||||
|
||||
for (const regexp of regexpList) { // 遍历regexp配置
|
||||
if (!isMatched(req.url, regexp)) {
|
||||
continue
|
||||
}
|
||||
for (const interceptImpl of interceptors) {
|
||||
// 根据拦截配置挑选合适的拦截器来处理
|
||||
if (!interceptImpl.is(interceptOpt) && interceptImpl.requestInterceptor) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const result = interceptImpl.requestInterceptor(interceptOpt, rOptions, req, res, ssl)
|
||||
if (result) { // 拦截成功,其他拦截器就不处理了
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
// 拦截失败
|
||||
logger.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next()
|
||||
},
|
||||
responseInterceptor: (req, res, proxyReq, proxyRes, ssl, next) => {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
const event = require('./event')
|
||||
const lodash = require('lodash')
|
||||
const status = {
|
||||
server: false,
|
||||
proxy: {
|
||||
system: false,
|
||||
npm: false,
|
||||
git: false
|
||||
}
|
||||
}
|
||||
|
||||
event.register('status', (event) => {
|
||||
lodash.set(status, event.key, event.value)
|
||||
console.log('status changed:', event)
|
||||
}, -999)
|
||||
module.exports = status
|
|
@ -0,0 +1,87 @@
|
|||
const cmd = require('node-cmd')
|
||||
const util = require('util')
|
||||
const winExec = util.promisify(cmd.get, { multiArgs: true, context: cmd })
|
||||
const os = require('os')
|
||||
|
||||
class SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
throw new Error('You have to implement the method setProxy!')
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
throw new Error('You have to implement the method unsetProxy!')
|
||||
}
|
||||
}
|
||||
|
||||
class DarwinSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
class LinuxSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
|
||||
class WindowsSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
let ret = await winExec(`npm config set proxy=http://${ip}:${port}`)
|
||||
console.log('npm http proxy set success', ret)
|
||||
|
||||
ret = await winExec(`npm config set https-proxy=http://${ip}:${port}`)
|
||||
console.log('npm https proxy set success', ret)
|
||||
|
||||
// ret = await winExec(`npm config set cafile ${config.getDefaultCAKeyPath()}`)
|
||||
// console.log('npm cafile set success', ret)
|
||||
|
||||
ret = await winExec('npm config set strict-ssl false')
|
||||
console.log('npm strict-ssl false success', ret)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
await winExec('npm config delete proxy')
|
||||
console.log('npm https proxy unset success')
|
||||
await winExec('npm config delete https-proxy')
|
||||
console.log('npm https proxy unset success')
|
||||
|
||||
// await winExec(`npm config delete cafile`)
|
||||
// console.log('npm cafile unset success')
|
||||
await winExec(' npm config delete strict-ssl')
|
||||
console.log('npm strict-ssl true success')
|
||||
}
|
||||
|
||||
static _asyncRegSet (regKey, name, type, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
regKey.set(name, type, value, e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemProxy () {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return DarwinSystemProxy
|
||||
case 'linux':
|
||||
return LinuxSystemProxy
|
||||
case 'win32':
|
||||
case 'win64':
|
||||
return WindowsSystemProxy
|
||||
case 'unknown os':
|
||||
default:
|
||||
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async setProxy (ip, port) {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.setProxy(ip, port)
|
||||
},
|
||||
async unsetProxy () {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.unsetProxy()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
const script = `
|
||||
$signature = @'
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
|
||||
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
|
||||
'@
|
||||
|
||||
$INTERNET_OPTION_SETTINGS_CHANGED = 39
|
||||
$INTERNET_OPTION_REFRESH = 37
|
||||
$type = Add-Type -MemberDefinition $signature -Name wininet -Namespace pinvoke -PassThru
|
||||
$a = $type::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
|
||||
$b = $type::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
|
||||
$a -and $b
|
||||
`
|
||||
module.exports = script
|
|
@ -0,0 +1,61 @@
|
|||
const script = `
|
||||
Function Set-InternetProxy
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
|
||||
[String[]]$Proxy,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
|
||||
[AllowEmptyString()]
|
||||
[String[]]$acs
|
||||
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
|
||||
$regKey="HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
|
||||
|
||||
}
|
||||
|
||||
Process
|
||||
{
|
||||
|
||||
Set-ItemProperty -path $regKey ProxyEnable -value 1
|
||||
|
||||
Set-ItemProperty -path $regKey ProxyServer -value $proxy
|
||||
|
||||
if($acs)
|
||||
{
|
||||
|
||||
Set-ItemProperty -path $regKey AutoConfigURL -Value $acs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
|
||||
Write-Output "Proxy is now enabled"
|
||||
|
||||
Write-Output "Proxy Server : $proxy"
|
||||
|
||||
if ($acs)
|
||||
{
|
||||
|
||||
Write-Output "Automatic Configuration Script : $acs"
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Write-Output "Automatic Configuration Script : Not Defined"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
module.exports = script
|
|
@ -0,0 +1,196 @@
|
|||
const util = require('util')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const childProcess = require('child_process')
|
||||
const _exec = childProcess.exec
|
||||
const spawn = childProcess.spawn
|
||||
const Registry = require('winreg')
|
||||
// const cmd = require('node-cmd')
|
||||
console.log('childProcess', childProcess)
|
||||
const exec = util.promisify(_exec)
|
||||
const setproxyPs = require('./set-internet-proxy')
|
||||
const refreshInternetPs = require('./refresh-internet')
|
||||
const Shell = require('node-powershell')
|
||||
|
||||
const _lanIP = [
|
||||
'localhost',
|
||||
'127.*',
|
||||
'10.*',
|
||||
'172.16.*',
|
||||
'172.17.*',
|
||||
'172.18.*',
|
||||
'172.19.*',
|
||||
'172.20.*',
|
||||
'172.21.*',
|
||||
'172.22.*',
|
||||
'172.23.*',
|
||||
'172.24.*',
|
||||
'172.25.*',
|
||||
'172.26.*',
|
||||
'172.27.*',
|
||||
'172.28.*',
|
||||
'172.29.*',
|
||||
'172.30.*',
|
||||
'172.31.*',
|
||||
'192.168.*',
|
||||
'<local>'
|
||||
]
|
||||
|
||||
class SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
throw new Error('You have to implement the method setProxy!')
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
throw new Error('You have to implement the method unsetProxy!')
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add path http_proxy and https_proxy
|
||||
// TODO: Support for non-gnome
|
||||
class LinuxSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
await exec('gsettings set org.gnome.system.proxy mode manual')
|
||||
await exec(`gsettings set org.gnome.system.proxy.http host ${ip}`)
|
||||
await exec(`gsettings set org.gnome.system.proxy.http port ${port}`)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
await exec('gsettings set org.gnome.system.proxy mode none')
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Support for lan connections too
|
||||
// TODO: move scripts to ../scripts/darwin
|
||||
class DarwinSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
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()
|
||||
|
||||
await exec(`networksetup -setwebproxy '${wifiAdaptor}' ${ip} ${port}`)
|
||||
await exec(`networksetup -setsecurewebproxy '${wifiAdaptor}' ${ip} ${port}`)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
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()
|
||||
|
||||
await exec(`networksetup -setwebproxystate '${wifiAdaptor}' off`)
|
||||
await exec(`networksetup -setsecurewebproxystate '${wifiAdaptor}' off`)
|
||||
}
|
||||
}
|
||||
|
||||
class WindowsSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
const regKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
|
||||
})
|
||||
|
||||
let lanIpStr = ''
|
||||
for (const string of _lanIP) {
|
||||
lanIpStr += string + ';'
|
||||
}
|
||||
console.log('lanIps:', lanIpStr, ip, port)
|
||||
await Promise.all([
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'MigrateProxy', Registry.REG_DWORD, 1),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyEnable', Registry.REG_DWORD, 1),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyHttp1.1', Registry.REG_DWORD, 0),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyServer', Registry.REG_SZ, `${ip}:${port}`),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyOverride', Registry.REG_SZ, lanIpStr)
|
||||
])
|
||||
await WindowsSystemProxy._resetWininetProxySettings('echo refreshing') // 要执行以下这个才能生效
|
||||
await WindowsSystemProxy._resetWininetProxySettings(refreshInternetPs)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
const regKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyEnable', Registry.REG_DWORD, 0),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyServer', Registry.REG_SZ, '')
|
||||
])
|
||||
await WindowsSystemProxy._resetWininetProxySettings(refreshInternetPs)
|
||||
}
|
||||
|
||||
static _asyncRegSet (regKey, name, type, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
regKey.set(name, type, value, e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static _resetWininetProxySettings (script) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ps = new Shell({
|
||||
executionPolicy: 'Bypass',
|
||||
noProfile: true
|
||||
})
|
||||
// ps.addCommand(setproxyPs)
|
||||
// ps.addCommand(`Set-InternetProxy -Proxy "${ip}:${port}"`)
|
||||
|
||||
ps.addCommand(script)
|
||||
|
||||
ps.invoke()
|
||||
.then(output => {
|
||||
console.log(output)
|
||||
resolve()
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
reject(err)
|
||||
})
|
||||
|
||||
// const scriptPath = path.join(__dirname, '..', 'scripts', 'windows', 'wininet-reset-settings.ps1')
|
||||
// const child = spawn('powershell.exe', [scriptPath])
|
||||
// child.stdout.setEncoding('utf8')
|
||||
// child.stdout.on('data', (data) => {
|
||||
// console.log('data', data)
|
||||
// if (data.includes('True')) {
|
||||
// resolve()
|
||||
// } else {
|
||||
// reject(data)
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// child.stderr.on('data', (err) => {
|
||||
// console.log('data', err)
|
||||
// reject(err)
|
||||
// })
|
||||
//
|
||||
// child.stdin.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemProxy () {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return DarwinSystemProxy
|
||||
case 'linux':
|
||||
return LinuxSystemProxy
|
||||
case 'win32':
|
||||
case 'win64':
|
||||
return WindowsSystemProxy
|
||||
case 'unknown os':
|
||||
default:
|
||||
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async setProxy (ip, port) {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.setProxy(ip, port)
|
||||
},
|
||||
async unsetProxy () {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.unsetProxy()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
const systemProxy = require('./impl/system-proxy')
|
||||
const npmProxy = require('./impl/npm-proxy')
|
||||
const event = require('../../event')
|
||||
const config = require('../../config')
|
||||
function createProxyApi (type, impl) {
|
||||
return {
|
||||
async open (conf = { ip: '127.0.0.1', port: config.get().server.port }) {
|
||||
try {
|
||||
const { ip, port } = conf
|
||||
await impl.setProxy(ip, port)
|
||||
event.fire('status', { key: 'proxy.' + type, value: true })
|
||||
console.info(`开启【${type}】代理成功`)
|
||||
} catch (e) {
|
||||
console.error(`开启【${type}】代理失败`, e)
|
||||
}
|
||||
},
|
||||
async close () {
|
||||
try {
|
||||
await impl.unsetProxy()
|
||||
event.fire('status', { key: 'proxy.' + type, value: false })
|
||||
console.info(`关闭【${type}】代理成功`)
|
||||
} catch (e) {
|
||||
console.error(`关闭【${type}】代理失败`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
system: createProxyApi('system', systemProxy),
|
||||
npm: createProxyApi('npm', npmProxy)
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<#
|
||||
.Synopsis
|
||||
This function will set the proxy settings provided as input to the cmdlet.
|
||||
.Description
|
||||
This function will set the proxy server and (optinal) Automatic configuration script.
|
||||
.Parameter ProxyServer
|
||||
This parameter is set as the proxy for the system.
|
||||
Data from. This parameter is Mandatory
|
||||
.Example
|
||||
Setting proxy information
|
||||
Set-InternetProxy -proxy "proxy:7890"
|
||||
.Example
|
||||
Setting proxy information and (optinal) Automatic Configuration Script
|
||||
Set-InternetProxy -proxy "proxy:7890" -acs "http://proxy:7892"
|
||||
#>
|
||||
|
||||
|
||||
Function Set-InternetProxy
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
|
||||
[String[]]$Proxy,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
|
||||
[AllowEmptyString()]
|
||||
[String[]]$acs
|
||||
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
|
||||
$regKey="HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
|
||||
|
||||
}
|
||||
|
||||
Process
|
||||
{
|
||||
|
||||
Set-ItemProperty -path $regKey ProxyEnable -value 1
|
||||
|
||||
Set-ItemProperty -path $regKey ProxyServer -value $proxy
|
||||
|
||||
if($acs)
|
||||
{
|
||||
|
||||
Set-ItemProperty -path $regKey AutoConfigURL -Value $acs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
|
||||
Write-Output "Proxy is now enabled"
|
||||
|
||||
Write-Output "Proxy Server : $proxy"
|
||||
|
||||
if ($acs)
|
||||
{
|
||||
|
||||
Write-Output "Automatic Configuration Script : $acs"
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Write-Output "Automatic Configuration Script : Not Defined"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
$signature = @'
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
|
||||
public static extern bool InternetSetOption(IntPtr hInternet, int
|
||||
dwOption, IntPtr lpBuffer, int dwBufferLength);
|
||||
'@
|
||||
|
||||
$interopHelper = Add-Type -MemberDefinition $signature -Name MyInteropHelper -PassThru
|
||||
$INTERNET_OPTION_SETTINGS_CHANGED = 39
|
||||
$INTERNET_OPTION_REFRESH = 37
|
||||
|
||||
$result1 = $interopHelper::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
|
||||
$result2 = $interopHelper::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
|
||||
|
||||
$result1 -and $result2
|
|
@ -0,0 +1,12 @@
|
|||
const DevSidercar = require('.')
|
||||
require('json5/lib/register')
|
||||
const config = require('../../config/index.json5')
|
||||
// 启动服务
|
||||
DevSidercar.api.startup(config)
|
||||
async function onClose () {
|
||||
console.log('on sigint ')
|
||||
await DevSidercar.api.shutdown()
|
||||
console.log('on closed ')
|
||||
process.exit(0)
|
||||
}
|
||||
process.on('SIGINT', onClose)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
|
@ -0,0 +1,26 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
#Electron-builder output
|
||||
/dist_electron
|
|
@ -0,0 +1,24 @@
|
|||
# dev-sidecar-gui
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "dev-sidecar-gui",
|
||||
"version": "1.0.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron": "vue-cli-service electron:serve",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^1.6.5",
|
||||
"core-js": "^3.6.5",
|
||||
"lodash": "^4.17.20",
|
||||
"vue": "^2.6.11",
|
||||
"vue-json-editor": "^1.4.2",
|
||||
"dev-sidecar": "1.0.0",
|
||||
"json5": "^2.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"electron": "^10.1.3",
|
||||
"electron-devtools-installer": "^3.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"json5-loader": "^4.0.1",
|
||||
"vue-cli-plugin-electron-builder": "^2.0.0-rc.4",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,71 @@
|
|||
<svg id="svg_canvas" viewBox="0 0 300 180" width="300" height="180" version="1.1" xmlns="http://www.w3.org/2000/svg" >
|
||||
<g transform="translate(100,-20) scale(1,1)">
|
||||
<path fill="#368FB1" d="M102.423,165.089H62.778c-2.534,0-4.589-2.056-4.589-4.589s2.055-4.589,4.589-4.589h39.645
|
||||
c15.916,0,32.379-11.18,32.379-29.892c0-22.1-21.58-25.871-22.498-26.02l-4.244-0.693l0.397-4.281
|
||||
c0.686-7.096-1.867-14.598-6.831-20.063c-5.45-6.002-13.351-9.175-22.85-9.175c-23.706,0-27.28,23.842-27.42,24.858l-0.565,4.081
|
||||
l-4.122-0.111c-23.445-0.729-30.104,8.127-30.168,8.216c-1.457,2.02-4.294,2.558-6.348,1.147c-2.054-1.407-2.644-4.15-1.287-6.238
|
||||
c0.837-1.288,8.623-12.082,34.18-12.339c2.982-11.745,13.044-28.793,35.731-28.793c15.36,0,24.6,6.624,29.645,12.184
|
||||
c5.737,6.319,9.076,14.694,9.309,23.038c10.814,3.128,26.25,12.961,26.25,34.189C143.98,150.476,122.851,165.089,102.423,165.089z
|
||||
"></path>
|
||||
<rect x="140.167" y="74.342" fill="#368FB1" width="25.821" height="25.821"></rect>
|
||||
<path fill="#368FB1" d="M137.108,98.083c-4.099-4.038-9.485-7.74-16.337-10.891c0,0-4.895-41.302-54.795-35.062
|
||||
c0,0,49.319-22.548,64.248,26.77c0,0,2.907,1.439,6.884,4.314V98.083z"></path>
|
||||
<path fill="#368FB1" d="M153.537,100.164h-13.37V85.509C144.543,89.027,149.59,93.922,153.537,100.164z"></path>
|
||||
<path fill="#368FB1" d="M159.747,129.932c-4.803,29.615-33.316,34.602-35.091,34.877c1.071-0.581,12.789-7.129,19.58-20.774
|
||||
c5.599-11.197,7.404-27.167-2.601-40.812h13.676C159.197,110.535,161.461,119.438,159.747,129.932z"></path>
|
||||
<rect x="160.828" y="51.603" fill="#368FB1" width="17.177" height="17.177"></rect>
|
||||
<rect x="173.065" y="75.186" fill="#368FB1" width="12.556" height="12.556"></rect>
|
||||
</g>
|
||||
<g transform="translate(10,90)">
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M2.52 0L2.52-24.68L12.99-24.68Q16.91-24.68 19.05-23.90L19.05-23.90Q23.01-22.46 24.35-18.61L24.35-18.61Q25.34-15.65 25.34-12.28L25.34-12.28Q25.34-8.95 24.38-5.99L24.38-5.99Q23.42-2.96 21.20-1.52L21.20-1.52Q19.79-0.63 18.11-0.31Q16.43 0 12.99 0L12.99 0L2.52 0ZM12.99-20.28L7.77-20.28L7.77-4.40L12.99-4.40Q16.43-4.40 17.76-5.92L17.76-5.92Q18.57-6.88 19.07-8.62Q19.57-10.36 19.57-12.32L19.57-12.32Q19.57-14.50 18.98-16.32Q18.39-18.13 17.43-19.02L17.43-19.02Q16.02-20.28 12.99-20.28L12.99-20.28Z"
|
||||
transform="translate(5 31.782999999999998)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M6.70-10.95L17.13-10.95L17.13-7.10L6.70-7.10Q6.84-5.33 7.84-4.59Q8.84-3.85 11.10-3.85L11.10-3.85L17.13-3.85L17.13 0L10.54 0Q8.40 0 7.05-0.39Q5.70-0.78 4.51-1.66L4.51-1.66Q1.22-4.22 1.22-9.25L1.22-9.25Q1.22-12.51 2.96-14.95L2.96-14.95Q4.14-16.61 5.81-17.32Q7.47-18.02 10.17-18.02L10.17-18.02L17.13-18.02L17.13-14.17L10.54-14.17Q8.51-14.17 7.70-13.49Q6.88-12.80 6.70-10.95L6.70-10.95Z"
|
||||
transform="translate(31.491999999999997 31.782999999999998)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M5.62-18.02L10.21-5.62L15.24-18.02L20.50-18.02L12.73 0L7.40 0L0.22-18.02L5.62-18.02Z"
|
||||
transform="translate(50.251 31.782999999999998)" ></path>
|
||||
<path fill="rgb(54, 143, 177)" d="M0-11.77L8.62-11.77L8.62-7.33L0-7.33L0-11.77Z"
|
||||
transform="translate(71.008 31.782999999999998)"></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M14.80 0L2.40 0L2.40-4.40L13.69-4.40Q16.39-4.40 17.28-4.92L17.28-4.92Q18.57-5.70 18.57-7.25L18.57-7.25Q18.57-9.06 17.06-9.88L17.06-9.88Q16.21-10.36 14.32-10.36L14.32-10.36L9.73-10.36Q5.55-10.36 3.70-11.88L3.70-11.88Q2.55-12.84 1.91-14.28Q1.26-15.72 1.26-17.39L1.26-17.39Q1.26-19.98 2.77-22.13L2.77-22.13Q4.25-24.20 7.51-24.57L7.51-24.57Q8.62-24.68 10.80-24.68L10.80-24.68L23.05-24.68L23.05-20.28L11.99-20.28Q9.51-20.24 8.70-20.05L8.70-20.05Q7.03-19.65 7.03-17.54L7.03-17.54Q7.03-15.76 8.44-15.17L8.44-15.17Q9.36-14.73 11.54-14.73L11.54-14.73L15.50-14.73Q18.54-14.73 20.02-14.21L20.02-14.21Q22.64-13.32 23.64-11.10L23.64-11.10Q24.42-9.32 24.42-7.29L24.42-7.29Q24.42-5.03 23.38-3.26L23.38-3.26Q21.94-0.74 19.05-0.22L19.05-0.22Q17.65 0 14.80 0L14.80 0Z"
|
||||
transform="translate(79.62899999999999 31.782999999999998)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M2.40 0L2.40-18.02L7.36-18.02L7.36 0L2.40 0ZM7.36-19.87L2.40-19.87L2.40-24.68L7.36-24.68L7.36-19.87Z"
|
||||
transform="translate(105.344 31.782999999999998)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M9.84-18.02L14.50-18.02L14.50-24.68L19.43-24.68L19.43 0L10.91 0Q8.32 0 6.99-0.41L6.99-0.41Q3.85-1.41 2.29-4.33L2.29-4.33Q1.22-6.29 1.22-9.14L1.22-9.14Q1.22-13.76 4.33-16.32L4.33-16.32Q6.36-18.02 9.84-18.02L9.84-18.02ZM10.91-3.85L14.50-3.85L14.50-14.17L10.91-14.17Q8.55-14.17 7.33-12.58L7.33-12.58Q6.25-11.25 6.25-9.14L6.25-9.14Q6.25-6.07 8.03-4.74L8.03-4.74Q9.25-3.85 10.91-3.85L10.91-3.85Z"
|
||||
transform="translate(115.18599999999999 31.782999999999998)"
|
||||
></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M6.70-10.95L17.13-10.95L17.13-7.10L6.70-7.10Q6.84-5.33 7.84-4.59Q8.84-3.85 11.10-3.85L11.10-3.85L17.13-3.85L17.13 0L10.54 0Q8.40 0 7.05-0.39Q5.70-0.78 4.51-1.66L4.51-1.66Q1.22-4.22 1.22-9.25L1.22-9.25Q1.22-12.51 2.96-14.95L2.96-14.95Q4.14-16.61 5.81-17.32Q7.47-18.02 10.17-18.02L10.17-18.02L17.13-18.02L17.13-14.17L10.54-14.17Q8.51-14.17 7.70-13.49Q6.88-12.80 6.70-10.95L6.70-10.95Z"
|
||||
transform="translate(137.053 31.782999999999998)"></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M10.69-18.02L17.46-18.02L17.46-14.17L11.54-14.17Q8.55-14.17 7.40-12.84L7.40-12.84Q6.29-11.58 6.29-8.99L6.29-8.99Q6.29-6.10 8.10-4.77L8.10-4.77Q8.84-4.25 9.71-4.05Q10.58-3.85 12.14-3.85L12.14-3.85L17.46-3.85L17.46 0L10.69 0Q7.95 0 6.36-0.55Q4.77-1.11 3.55-2.48L3.55-2.48Q1.22-5.07 1.22-9.06L1.22-9.06Q1.22-13.88 4.00-16.24L4.00-16.24Q5.11-17.20 6.66-17.61Q8.21-18.02 10.69-18.02L10.69-18.02Z"
|
||||
transform="translate(155.812 31.782999999999998)"></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M8.77-10.95L14.84-10.95Q14.84-12.84 14.08-13.50Q13.32-14.17 11.06-14.17L11.06-14.17L2.70-14.17L2.70-18.02L11.06-18.02Q13.32-18.02 14.17-17.93Q15.02-17.83 15.91-17.57L15.91-17.57Q20.02-16.02 19.79-10.14L19.79-10.14L19.79 0L9.36 0Q6.55 0 5.48-0.17Q4.40-0.33 3.59-0.85L3.59-0.85Q1.41-2.33 1.41-5.33L1.41-5.33Q1.41-7.14 2.28-8.57Q3.15-9.99 4.59-10.51L4.59-10.51Q5.85-10.95 8.77-10.95L8.77-10.95ZM14.84-3.85L14.84-7.10L9.14-7.10L8.29-7.10Q7.44-7.10 6.94-6.66Q6.44-6.22 6.44-5.44L6.44-5.44Q6.44-4.59 7.05-4.22Q7.66-3.85 9.14-3.85L9.14-3.85L14.84-3.85Z"
|
||||
transform="translate(174.645 31.782999999999998)"></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M2.40 0L2.40-18.02L9.73-18.02Q11.99-18.02 13.23-17.70Q14.47-17.39 15.28-16.65L15.28-16.65Q16.09-15.91 16.43-14.80Q16.76-13.69 16.76-11.62L16.76-11.62L16.76-9.88L11.99-9.88L11.99-10.84Q11.99-12.76 11.32-13.47Q10.66-14.17 8.77-14.17L8.77-14.17L7.36-14.17L7.36 0L2.40 0Z"
|
||||
transform="translate(196.66000000000003 31.782999999999998)"></path>
|
||||
</g>
|
||||
<g transform="translate(40,130)">
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M18.56-12.98L15.44-12.98L15.44-8.21L19.05-8.21L18.56-7.22L15.44-7.22L15.44-1.13L12.94-1.13L12.94-7.22L8.11-7.22L8.11-6.26L5.84-1.13L3.11-1.13L5.69-6.55L5.69-7.22L1.93-7.22L1.93-8.21L5.69-8.21L5.69-12.98L1.93-12.98L1.93-13.97L5.69-13.97L5.69-13.99L15.44-13.99L15.44-13.97L19.05-13.97L18.56-12.98ZM12.94-8.21L12.94-12.98L8.11-12.98L8.11-8.21L12.94-8.21Z"
|
||||
transform="translate(5 18.060000000000002)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M13.13-2.69L10.08-1.09L6.47-1.09L11.40-3.59L7.41-5.71L10.37-5.71L12.92-4.37L15.94-5.90L15.94-6.72L7.16-6.72L4.22-1.18L1.53-1.18L6.07-9.74L1.49-9.74L3.30-14.03L5.86-14.03L4.43-10.65L6.55-10.65L8.36-14.03L11.07-14.03L9.26-10.65L16.09-10.65L15.29-12.89L17.70-12.89L18.52-10.65L19.49-10.65L19.49-9.74L8.78-9.74L7.67-7.64L18.33-7.64L18.33-5.44L14.62-3.47L19.15-1.09L16.17-1.09L13.13-2.69Z"
|
||||
transform="translate(26 18.060000000000002)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M11.47-12.92L18.71-12.92L18.08-11.99L11.47-11.99L11.47-9.87L13.17-9.87L14.09-11.40L17.07-11.40L16.15-9.87L19.30-9.87L18.67-8.95L7.27-8.95L6.24-7.64L15.81-7.64L15.81-7.67L18.19-7.67L18.19-1.32L3.42-1.32L3.42-5.80L1.81-5.80L4.28-8.95L1.70-8.95L1.70-9.87L9.07-9.87L9.07-11.99L2.71-11.99L2.71-12.92L9.07-12.92L9.07-13.80L11.47-13.80L11.47-12.92ZM15.25-4.12L5.82-4.12L5.82-2.25L15.81-2.25L15.81-4.79L15.25-4.12ZM15.33-6.68L5.82-6.68L5.82-5.06L15.81-5.06L15.81-7.27L15.33-6.68Z"
|
||||
transform="translate(47 18.060000000000002)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M11.03-6.11L8.93-2.67L6.49-2.67L8.59-6.11L8.59-11.28L6.41-11.28L6.41-12.26L8.59-12.26L8.59-13.86L11.03-13.86L11.03-12.26L18.44-12.26L18.44-2.67L13.57-2.67L13.02-3.65L16.00-3.65L16.00-11.28L11.03-11.28L11.03-6.11ZM1.47-9.91L4.96-9.91L4.96-2.25L19.53-2.25L19.13-1.28L5.71-1.28L4.89-2.10L4.26-1.28L1.47-1.28L1.47-2.25L2.56-2.25L2.56-8.93L1.47-8.93L1.47-9.91ZM4.31-13.80L5.40-11.05L2.83-11.05L1.72-13.80L4.31-13.80Z"
|
||||
transform="translate(68 18.060000000000002)" ></path>
|
||||
<path fill="rgb(54, 143, 177)"
|
||||
d="M11.87-1.07L9.37-1.07L9.37-3.49L2.46-3.49L2.46-4.45L9.37-4.45L9.37-6.53L2.92-6.53L6.13-11.32L2.50-11.32L2.50-12.29L6.78-12.29L7.96-14.05L11.05-14.05L9.87-12.29L17.07-12.29L16.53-11.32L9.22-11.32L6.66-7.48L9.37-7.48L9.37-10.40L11.87-10.40L11.87-7.48L17.07-7.48L16.53-6.53L11.87-6.53L11.87-4.45L18.54-4.45L18.00-3.49L11.87-3.49L11.87-1.07Z"
|
||||
transform="translate(89 18.060000000000002)" ></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,143 @@
|
|||
'use strict'
|
||||
import path from 'path'
|
||||
import { app, protocol, BrowserWindow, Menu, Tray } from 'electron'
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
import bridge from './bridge/index'
|
||||
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let win
|
||||
|
||||
let forceClose = false
|
||||
|
||||
// Scheme must be registered before the app is ready
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'app', privileges: { secure: true, standard: true } }
|
||||
])
|
||||
|
||||
// 隐藏主窗口,并创建托盘,绑定关闭事件
|
||||
function setTray (app) {
|
||||
// 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区
|
||||
// 通常被添加到一个 context menu 上.
|
||||
// 系统托盘右键菜单
|
||||
const trayMenuTemplate = [
|
||||
{
|
||||
// 系统托盘图标目录
|
||||
label: '退出',
|
||||
click: () => {
|
||||
forceClose = true
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
]
|
||||
// 设置系统托盘图标
|
||||
const iconPath = path.join(__dirname, '../public/favicon.ico')
|
||||
|
||||
const appTray = new Tray(iconPath)
|
||||
|
||||
// 图标的上下文菜单
|
||||
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
|
||||
|
||||
// 设置托盘悬浮提示
|
||||
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
||||
|
||||
// 设置托盘菜单
|
||||
appTray.setContextMenu(contextMenu)
|
||||
|
||||
// 单击托盘小图标显示应用
|
||||
appTray.on('click', () => {
|
||||
// 显示主程序
|
||||
win.show()
|
||||
})
|
||||
return appTray
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
enableRemoteModule: true,
|
||||
// preload: path.join(__dirname, 'preload.js'),
|
||||
// Use pluginOptions.nodeIntegration, leave this alone
|
||||
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
|
||||
nodeIntegration: true// process.env.ELECTRON_NODE_INTEGRATION
|
||||
}
|
||||
})
|
||||
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
|
||||
if (!process.env.IS_TEST) win.webContents.openDevTools()
|
||||
} else {
|
||||
createProtocol('app')
|
||||
// Load the index.html when not in development
|
||||
win.loadURL('app://./index.html')
|
||||
}
|
||||
|
||||
win.on('closed', (e) => {
|
||||
win = null
|
||||
|
||||
bridge.devSidecar.expose.api.shutdown()
|
||||
})
|
||||
|
||||
win.on('close', (e) => {
|
||||
if (!forceClose) {
|
||||
e.preventDefault()
|
||||
win.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', async () => {
|
||||
if (isDevelopment && !process.env.IS_TEST) {
|
||||
// Install Vue Devtools
|
||||
// try {
|
||||
// await installExtension(VUEJS_DEVTOOLS)
|
||||
// } catch (e) {
|
||||
// console.error('Vue Devtools failed to install:', e.toString())
|
||||
// }
|
||||
}
|
||||
createWindow()
|
||||
// 最小化到托盘
|
||||
setTray(app)
|
||||
bridge.init(win)
|
||||
})
|
||||
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', (data) => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import lodash from 'lodash'
|
||||
import DevSidecar from 'dev-sidecar'
|
||||
import { ipcMain } from 'electron'
|
||||
import fs from 'fs'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
const localApi = {
|
||||
config: {
|
||||
/**
|
||||
* 保存自定义的 config
|
||||
* @param newConfig
|
||||
*/
|
||||
save (newConfig) {
|
||||
// 对比默认config的异同
|
||||
const defConfig = DevSidecar.api.config.get()
|
||||
|
||||
const saveConfig = {}
|
||||
|
||||
_merge(defConfig, newConfig, saveConfig, 'intercepts')
|
||||
_merge(defConfig, newConfig, saveConfig, 'dns.mapping')
|
||||
_merge(defConfig, newConfig, saveConfig, 'setting.startup.server', true)
|
||||
_merge(defConfig, newConfig, saveConfig, 'setting.startup.proxy')
|
||||
|
||||
// TODO 保存到文件
|
||||
console.log('save config ', saveConfig)
|
||||
fs.writeFileSync('./config.json5', JSON5.stringify(saveConfig, null, 2))
|
||||
return saveConfig
|
||||
},
|
||||
reload () {
|
||||
const file = fs.readFileSync('./config.json5')
|
||||
const userConfig = JSON5.parse(file.toString())
|
||||
DevSidecar.api.config.set(userConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _merge (defConfig, newConfig, saveConfig, target, self = false) {
|
||||
if (self) {
|
||||
const defValue = lodash.get(defConfig, target)
|
||||
const newValue = lodash.get(newConfig, target)
|
||||
if (newValue != null && newValue !== defValue) {
|
||||
lodash.set(saveConfig, newValue, target)
|
||||
}
|
||||
return
|
||||
}
|
||||
const saveObj = _mergeConfig(lodash.get(defConfig, target), lodash.get(newConfig, target))
|
||||
lodash.set(saveConfig, target, saveObj)
|
||||
}
|
||||
|
||||
function _mergeConfig (defObj, newObj) {
|
||||
for (const key in defObj) {
|
||||
// 从默认里面提取对比,是否有被删除掉的
|
||||
if (newObj[key] == null) {
|
||||
newObj[key] = false
|
||||
}
|
||||
}
|
||||
for (const key in newObj) {
|
||||
const newItem = newObj[key]
|
||||
const defItem = defObj[key]
|
||||
if (newItem && !defItem) {
|
||||
continue
|
||||
}
|
||||
// 深度对比 是否有修改
|
||||
if (lodash.isEqual(newItem, defItem)) {
|
||||
console.log('equle', key, newItem, defItem)
|
||||
// 没有修改则删除
|
||||
delete newObj[key]
|
||||
}
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
export default {
|
||||
init (win) {
|
||||
// 接收view的方法调用
|
||||
ipcMain.handle('apiInvoke', async (event, args) => {
|
||||
const api = args[0]
|
||||
let target = lodash.get(DevSidecar.api, api)
|
||||
if (target == null) {
|
||||
console.log('get local api')
|
||||
target = lodash.get(localApi, api)
|
||||
}
|
||||
if (target == null) {
|
||||
console.log('找不到此接口方法:', api)
|
||||
}
|
||||
let param
|
||||
if (args.length >= 2) {
|
||||
param = args[1]
|
||||
}
|
||||
const ret = target(param)
|
||||
console.log('api:', api, 'ret:', ret)
|
||||
return ret
|
||||
})
|
||||
// 注册从core里来的事件,并转发给view
|
||||
DevSidecar.api.event.register('status', (event) => {
|
||||
console.log('bridge on status', event)
|
||||
win.webContents.send('status', { ...event })
|
||||
})
|
||||
},
|
||||
devSidecar: DevSidecar
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import config from '../../../config/index.json5'
|
||||
export default config
|
|
@ -0,0 +1,11 @@
|
|||
import Vue from 'vue'
|
||||
import App from './view/components/App.vue'
|
||||
import antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
import './view'
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(antd)
|
||||
|
||||
new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
|
@ -0,0 +1 @@
|
|||
window.ipcRenderer = require('electron').ipcRenderer
|
|
@ -0,0 +1,36 @@
|
|||
import lodash from 'lodash'
|
||||
import { ipcRenderer } from 'electron'
|
||||
const doInvoke = (api, args) => {
|
||||
return ipcRenderer.invoke('apiInvoke', [api, args])
|
||||
}
|
||||
|
||||
const bindApi = (api, param1) => {
|
||||
lodash.set(apiObj, api, (param2) => {
|
||||
return doInvoke(api, param2 || param1)
|
||||
})
|
||||
}
|
||||
const apiObj = {
|
||||
on (channel, callback) {
|
||||
ipcRenderer.on(channel, callback)
|
||||
},
|
||||
doInvoke
|
||||
}
|
||||
|
||||
bindApi('startup')
|
||||
bindApi('shutdown')
|
||||
|
||||
bindApi('config.set')
|
||||
bindApi('config.get')
|
||||
bindApi('config.save')
|
||||
bindApi('config.reload')
|
||||
|
||||
bindApi('server.start')
|
||||
bindApi('server.close')
|
||||
|
||||
bindApi('proxy.system.open')
|
||||
bindApi('proxy.system.close')
|
||||
|
||||
bindApi('proxy.npm.open')
|
||||
bindApi('proxy.npm.close')
|
||||
|
||||
export default apiObj
|
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<template>
|
||||
<div style="margin:auto">
|
||||
<div style="text-align: center"><img height="80px" src="/logo.svg"></div>
|
||||
<a-card title="DevSidecar-开发者辅助工具 " style="width: 500px;margin:auto">
|
||||
<div style="display: flex; align-items:center;justify-content:space-around;flex-direction: row">
|
||||
<div style="text-align: center">
|
||||
<div class="big_button" >
|
||||
<a-button shape="circle" icon="poweroff" :type="startup.type()" :loading="startup.loading" @click="startup.doClick" ></a-button>
|
||||
<div style="margin-top: 10px">{{status.server?'已开启':'已关闭'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :span="12">
|
||||
<a-form style="margin-top:20px" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }" >
|
||||
<a-form-item label="代理服务">
|
||||
<a-switch :loading="server.loading" v-model="status.server" default-checked v-on:click="server.doClick">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-for=" (item, key) in proxy" :key="key" :label="_lang(key,langSetting.proxy) ">
|
||||
<a-switch :loading="item.loading" v-model="status.proxy[key]" default-checked v-on:click="item.doClick">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span slot="extra" >
|
||||
<a-button v-if="config" @click="openSettings" icon="setting" ></a-button>
|
||||
</span>
|
||||
</a-card>
|
||||
|
||||
<settings v-if="config" title="设置" :config="config" :visible.sync="settings.visible" @change="onConfigChanged"></settings>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '../api'
|
||||
import status from '../status'
|
||||
import lodash from 'lodash'
|
||||
import Settings from './settings'
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Settings
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
langSetting: {
|
||||
proxy: {
|
||||
system: '系统代理',
|
||||
npm: 'npm代理',
|
||||
yarn: 'yarn代理'
|
||||
}
|
||||
},
|
||||
status: status,
|
||||
startup: {
|
||||
loading: false,
|
||||
type: () => {
|
||||
return this.status.server ? 'primary' : 'default'
|
||||
},
|
||||
doClick: () => {
|
||||
if (this.status.server) {
|
||||
this.apiCall(this.startup, api.shutdown)
|
||||
} else {
|
||||
this.apiCall(this.startup, api.startup)
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
key: '代理服务',
|
||||
loading: false,
|
||||
doClick: (checked) => {
|
||||
this.onSwitchClick(this.server, api.server.start, api.server.close, checked)
|
||||
}
|
||||
},
|
||||
proxy: undefined,
|
||||
config: undefined,
|
||||
settings: {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_intercepts () {
|
||||
return this.config.intercepts
|
||||
}
|
||||
},
|
||||
created () {
|
||||
api.config.set().then(() => {
|
||||
return api.config.get().then(ret => {
|
||||
this.config = ret
|
||||
this.start(true)
|
||||
})
|
||||
}).then(() => {
|
||||
this.proxy = this.createProxyBtns()
|
||||
console.log('proxy', this.proxy)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
_lang (key, parent) {
|
||||
const label = parent ? lodash.get(parent, key) : lodash.get(this.langSetting, key)
|
||||
if (label) {
|
||||
return label
|
||||
}
|
||||
return key
|
||||
},
|
||||
createProxyBtns () {
|
||||
const btns = {}
|
||||
console.log('api.proxy', api.proxy, api)
|
||||
for (const type in api.proxy) {
|
||||
btns[type] = {
|
||||
loading: false,
|
||||
key: type,
|
||||
doClick: (checked) => {
|
||||
this.onSwitchClick(this.proxy[type], api.proxy[type].open, api.proxy[type].close, checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
return btns
|
||||
},
|
||||
async apiCall (btn, api, param) {
|
||||
btn.loading = true
|
||||
try {
|
||||
const ret = await api(param)
|
||||
return ret
|
||||
} catch (err) {
|
||||
console.log('api invoke error:', err)
|
||||
} finally {
|
||||
btn.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
onSwitchClick (btn, openApi, closeApi, checked) {
|
||||
if (checked) {
|
||||
this.apiCall(btn, openApi)
|
||||
} else {
|
||||
this.apiCall(btn, closeApi)
|
||||
}
|
||||
},
|
||||
start (checked) {
|
||||
this.apiCall(this.startup, api.startup)
|
||||
},
|
||||
openSettings () {
|
||||
this.settings.visible = true
|
||||
},
|
||||
onConfigChanged (newConfig) {
|
||||
console.log('config chagned', newConfig)
|
||||
api.config.save(newConfig).then(() => {
|
||||
api.config.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
padding-top:60px;
|
||||
}
|
||||
|
||||
.big_button >button{
|
||||
width:100px;
|
||||
height:100px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
.big_button >button i{
|
||||
size:40px
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
:title="title"
|
||||
placement="right"
|
||||
:closable="false"
|
||||
:visible="visible"
|
||||
:after-visible-change="afterVisibleChange"
|
||||
@close="onClose"
|
||||
width="650px"
|
||||
height="100%"
|
||||
wrapClassName="json-wrapper"
|
||||
>
|
||||
|
||||
<a-tabs
|
||||
default-active-key="1"
|
||||
tab-position="left"
|
||||
:style="{ height: '100%' }"
|
||||
>
|
||||
<a-tab-pane tab="拦截设置" key="1" >
|
||||
<vue-json-editor style="height:100%;" ref="editor" v-model="targetConfig.intercepts" :show-btns="false" :expandedOnStart="true" @json-change="onJsonChange" ></vue-json-editor>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="DNS设置" key="2">
|
||||
<div>
|
||||
<div>某些域名有时候需要通过其他DNS服务器获取到的IP才可以访问</div>
|
||||
<a-row :gutter="10" style="margin-top: 10px" v-for="item in dnsMappings" :key = 'item.key'>
|
||||
<a-col :span="18">
|
||||
<a-input v-model="item.key"></a-input>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select v-model="item.value">
|
||||
<a-select-option value="usa">USA</a-select-option>
|
||||
<a-select-option value="aliyun">Aliyun</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="启动设置" key="3" >
|
||||
<div>启动应用程序后自动启动</div>
|
||||
<a-form style="margin-top: 20px" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }" >
|
||||
<a-form-item label="代理服务" style="margin-bottom: 10px">
|
||||
<a-switch v-model="targetConfig.setting.startup.server" default-checked v-on:click="(checked)=>{targetConfig.setting.startup.server = checked}">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item style="margin-bottom: 10px" v-for="(item,key) in targetConfig.setting.startup.proxy" :key="key" :label="key">
|
||||
<a-switch v-model="targetConfig.setting.startup.proxy[key]" default-checked v-on:click="(checked)=>{targetConfig.setting.startup.proxy[key] = checked}">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vueJsonEditor from 'vue-json-editor'
|
||||
import lodash from 'lodash'
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
vueJsonEditor
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
type: Object
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '编辑'
|
||||
},
|
||||
visible: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
targetConfig: {},
|
||||
dnsMappings: [],
|
||||
changed: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.resetConfig()
|
||||
},
|
||||
methods: {
|
||||
resetConfig () {
|
||||
this.targetConfig = lodash.cloneDeep(this.config)
|
||||
console.log('targetConfig', this.targetConfig)
|
||||
this.dnsMappings = []
|
||||
for (const key in this.targetConfig.dns.mapping) {
|
||||
const value = this.targetConfig.dns.mapping[key]
|
||||
this.dnsMappings.push({
|
||||
key, value
|
||||
})
|
||||
}
|
||||
},
|
||||
onJsonChange (config) {
|
||||
this.changed = true
|
||||
},
|
||||
afterVisibleChange (val) {
|
||||
console.log('visible', val)
|
||||
if (val === true) {
|
||||
this.resetConfig()
|
||||
}
|
||||
},
|
||||
showDrawer () {
|
||||
this.$emit('update:visible', true)
|
||||
},
|
||||
onClose () {
|
||||
if (this.changed) {
|
||||
this.$confirm({
|
||||
title: '提示',
|
||||
content: '是否需要保存?',
|
||||
onOk: () => {
|
||||
this.$emit('change', this.targetConfig)
|
||||
},
|
||||
onCancel () {}
|
||||
})
|
||||
}
|
||||
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.json-wrapper .ant-drawer-wrapper-body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.json-wrapper .ant-drawer-wrapper-body .ant-drawer-body{
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
.json-wrapper .jsoneditor-vue{
|
||||
height:100%
|
||||
}
|
||||
.json-wrapper .ant-tabs{
|
||||
height: 100%;
|
||||
}
|
||||
.json-wrapper .ant-tabs-content{
|
||||
height: 100%;
|
||||
}
|
||||
.json-wrapper .ant-tabs-tabpane-active{
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1 @@
|
|||
import './status'
|
|
@ -0,0 +1,17 @@
|
|||
import api from './api'
|
||||
import lodash from 'lodash'
|
||||
const status = {
|
||||
server: false,
|
||||
proxy: {
|
||||
system: false,
|
||||
npm: false
|
||||
}
|
||||
}
|
||||
|
||||
api.on('status', (event, message) => {
|
||||
console.log('view on status', event, message)
|
||||
const value = message.value
|
||||
const key = message.key
|
||||
lodash.set(status, key, value)
|
||||
})
|
||||
export default status
|
|
@ -0,0 +1,39 @@
|
|||
module.exports = {
|
||||
configureWebpack: config => {
|
||||
const configNew = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.json5$/i,
|
||||
loader: 'json5-loader',
|
||||
options: {
|
||||
esModule: false
|
||||
},
|
||||
type: 'javascript/auto'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
return configNew
|
||||
},
|
||||
pluginOptions: {
|
||||
electronBuilder: {
|
||||
nodeIntegration: true,
|
||||
// Provide an array of files that, when changed, will recompile the main process and restart Electron
|
||||
// Your main process file will be added by default
|
||||
mainProcessWatch: ['src/bridge', 'src/*.js', 'node_modules/dev-sidecar/src'],
|
||||
builderOptions: {
|
||||
extraResources: [
|
||||
{
|
||||
from: 'src/config.json5',
|
||||
to: 'app/config.json5'
|
||||
},
|
||||
{
|
||||
from: 'public',
|
||||
to: 'public'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue