提交 /github/monkey.js
parent
b7654d6ab0
commit
418871f215
|
@ -4,3 +4,8 @@
|
||||||
out
|
out
|
||||||
gen
|
gen
|
||||||
node_modules/
|
node_modules/
|
||||||
|
*.lock
|
||||||
|
*.log
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
*.lnk
|
373
LICENSE
373
LICENSE
|
@ -1,373 +0,0 @@
|
||||||
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.
|
|
104
README.md
104
README.md
|
@ -1,105 +1,5 @@
|
||||||
# dev-sidecar
|
# dev-sidecar
|
||||||
开发者边车,命名取自service-mesh的service-sidecar,意为为开发者打辅助的边车工具
|
|
||||||
通过本地代理的方式将请求代理到一些国内的加速通道上
|
|
||||||
解决一些网站和库无法访问或访问速度慢的问题
|
|
||||||
|
|
||||||
## 特性
|
当前分支用于提供最新版本的一些实用脚本,供大家使用。
|
||||||
### 1、 解决git push某些情况下需要临时输入账号密码的问题
|
|
||||||
|
|
||||||
### 2、 github的release、source、zip下载加速
|
请使用 [DevSidecar-1.8.1](https://github.com/docmirror/dev-sidecar/releases/tag/v1.8.1) 的新特性 [#294](https://github.com/docmirror/dev-sidecar/pull/294),来引用最新版本的脚本。
|
||||||
可解决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`
|
|
||||||
```
|
|
||||||
依次点击安装证书->所有用户->将所有证书都放入下列存储->受信任的根证书颁发机构->确定,下一步,确定即可
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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/)
|
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
BIN
doc/setup.png
BIN
doc/setup.png
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
|
@ -0,0 +1,466 @@
|
||||||
|
// ==UserScript==
|
||||||
|
// @name Github 增强 - 高速下载
|
||||||
|
// @name:en Github Enhancement - High Speed Download
|
||||||
|
// @version 2.5.19
|
||||||
|
// @author X.I.U
|
||||||
|
// @description 高速下载 Git Clone/SSH、Release、Raw、Code(ZIP) 等文件 (公益加速)、项目列表单文件快捷下载 (☁)、添加 git clone 命令
|
||||||
|
// @description:en High-speed download of Git Clone/SSH, Release, Raw, Code(ZIP) and other files (Based on public welfare), project list file quick download (☁)
|
||||||
|
// @license GPL-3.0 License
|
||||||
|
// @namespace https://greasyfork.org/scripts/412245
|
||||||
|
// @supportURL https://github.com/XIU2/UserScript
|
||||||
|
// @homepageURL https://github.com/XIU2/UserScript
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
window.addEventListener("load", ()=> {
|
||||||
|
const GM_registerMenuCommand = window.__ds_global__['GM_registerMenuCommand'] || (() => {})
|
||||||
|
const GM_unregisterMenuCommand = window.__ds_global__['GM_unregisterMenuCommand']
|
||||||
|
const GM_openInTab = window.__ds_global__['GM_openInTab']
|
||||||
|
const GM_getValue = window.__ds_global__['GM_getValue']
|
||||||
|
const GM_setValue = window.__ds_global__['GM_setValue']
|
||||||
|
const GM_notification = window.__ds_global__['GM_notification']
|
||||||
|
window.onurlchange = window.__ds_global__['window.onurlchange'];
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
var backColor = '#ffffff', fontColor = '#888888', menu_rawFast = GM_getValue('xiu2_menu_raw_fast'), menu_rawFast_ID, menu_rawDownLink_ID, menu_gitClone_ID, menu_feedBack_ID;
|
||||||
|
const download_url_us = [
|
||||||
|
['https://gh.h233.eu.org/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@X.I.U/XIU2] 提供'],
|
||||||
|
//['https://gh.api.99988866.xyz/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [hunshcn/gh-proxy] 提供'], // 官方演示站用的人太多了
|
||||||
|
['https://gh.ddlc.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@mtr-static-official] 提供'],
|
||||||
|
//['https://gh2.yanqishui.work/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@HongjieCN] 提供'], // 解析错误
|
||||||
|
['https://dl.ghpig.top/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [feizhuqwq.com] 提供'],
|
||||||
|
//['https://gh.flyinbug.top/gh/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [Mintimate] 提供'], // 错误
|
||||||
|
['https://slink.ltd/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [知了小站] 提供'],
|
||||||
|
['https://git.xfj0.cn/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'],
|
||||||
|
['https://gh.con.sh/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'],
|
||||||
|
//['https://ghps.cc/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'], // 提示 blocked
|
||||||
|
//['https://gh-proxy.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [佚名] 提供'], // 502
|
||||||
|
['https://cors.isteed.cc/github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@Lufs\'s] 提供'],
|
||||||
|
['https://hub.gitmirror.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [GitMirror] 提供'],
|
||||||
|
['https://sciproxy.com/github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [sciproxy.com] 提供'],
|
||||||
|
['https://ghproxy.cc/https://github.com', '美国', '[美国 洛杉矶] - 该公益加速源由 [@yionchiii lau] 提供'],
|
||||||
|
['https://cf.ghproxy.cc/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@yionchiii lau] 提供'],
|
||||||
|
['https://gh.jiasu.in/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@0-RTT] 提供'],
|
||||||
|
//['https://download.fgit.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 被投诉挂了
|
||||||
|
['https://download.nuaa.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'],
|
||||||
|
['https://download.scholar.rr.nu', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'],
|
||||||
|
//['https://download.njuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 域名挂了
|
||||||
|
['https://download.yzuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供']
|
||||||
|
], download_url = [
|
||||||
|
//['https://download.fastgit.org', '德国', '[德国] - 该公益加速源由 [FastGit] 提供 提示:希望大家尽量多使用前面的美国节点(每次随机 4 个来负载均衡), 避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~', 'https://archive.fastgit.org'], // 证书过期
|
||||||
|
['https://mirror.ghproxy.com/https://github.com', '韩国', '[日本、韩国、德国等](CDN 不固定) - 该公益加速源由 [ghproxy] 提供 提示:希望大家尽量多使用前面的美国节点(每次随机 负载均衡), 避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'],
|
||||||
|
['https://ghproxy.net/https://github.com', '日本', '[日本 大阪] - 该公益加速源由 [ghproxy] 提供 提示:希望大家尽量多使用前面的美国节点(每次随机 负载均衡), 避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'],
|
||||||
|
['https://kkgithub.com', '香港', '[中国香港、日本、新加坡等] - 该公益加速源由 [help.kkgithub.com] 提供 提示:希望大家尽量多使用前面的美国节点(每次随机 4 个来负载均衡), 避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'],
|
||||||
|
//['https://download.incept.pw', '香港', '[中国香港] - 该公益加速源由 [FastGit 群组成员] 提供 提示:希望大家尽量多使用前面的美国节点(每次随机 4 个来负载均衡), 避免流量都集中到亚洲公益节点,减少成本压力,公益才能更持久~'] // ERR_SSL_PROTOCOL_ERROR
|
||||||
|
], clone_url = [
|
||||||
|
['https://gitclone.com', '国内', '[中国 国内] - 该公益加速源由 [GitClone] 提供 - 缓存:有 - 首次比较慢,缓存后较快'],
|
||||||
|
['https://kkgithub.com', '香港', '[中国香港、日本、新加坡等] - 该公益加速源由 [help.kkgithub.com] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://hub.incept.pw', '香港', '[中国香港、美国] - 该公益加速源由 [FastGit 群组成员] 提供'],
|
||||||
|
['https://mirror.ghproxy.com/https://github.com', '韩国', '[日本、韩国、德国等](CDN 不固定) - 该公益加速源由 [ghproxy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
//['https://gh-proxy.com/https://github.com', '韩国', '[韩国] - 该公益加速源由 [ghproxy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://githubfast.com', '韩国', '[韩国] - 该公益加速源由 [Github Fast] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://ghproxy.net/https://github.com', '日本', '[日本 大阪] - 该公益加速源由 [ghproxy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://github.moeyy.xyz/https://github.com', '新加坡', '[新加坡、中国香港、日本等](CDN 不固定) - 该公益加速源由 [Moeyy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
//['https://slink.ltd/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [知了小站] 提供'] // 暂无必要
|
||||||
|
//['https://hub.gitmirror.com/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [GitMirror] 提供'], // 暂无必要
|
||||||
|
//['https://sciproxy.com/github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [sciproxy.com] 提供'], // 暂无必要
|
||||||
|
//['https://ghproxy.cc/https://github.com', '美国', '[美国 洛杉矶] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
|
||||||
|
//['https://cf.ghproxy.cc/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
|
||||||
|
//['https://gh.jiasu.in/https://github.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@0-RTT] 提供'], // 暂无必要
|
||||||
|
//['https://hub.fgit.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 被投诉挂了
|
||||||
|
//['https://hub.nuaa.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
|
||||||
|
//['https://hub.scholar.rr.nu', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
|
||||||
|
//['https://hub.njuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 域名挂了
|
||||||
|
//['https://hub.yzuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
|
||||||
|
//['https://hub.0z.gs', '美国', '[美国 Cloudflare CDN]'], // 域名无解析
|
||||||
|
//['https://hub.shutcm.cf', '美国', '[美国 Cloudflare CDN]'] // 连接超时
|
||||||
|
], clone_ssh_url = [
|
||||||
|
['ssh://git@ssh.github.com:443/', 'Github 原生', '[日本、新加坡等] - Github 官方提供的 443 端口的 SSH(依然是 SSH 协议),适用于限制访问 22 端口的网络环境'],
|
||||||
|
['git@ssh.fastgit.org:', '香港', '[中国 香港] - 该公益加速源由 [FastGit] 提供']
|
||||||
|
//['git@git.zhlh6.cn:', '美国', '[美国 洛杉矶]'] // 挂了
|
||||||
|
], raw_url = [
|
||||||
|
['https://raw.githubusercontent.com', 'Github 原生', '[日本 东京]'],
|
||||||
|
['https://raw.kkgithub.com', '香港', '[中国香港、日本、新加坡等] - 该公益加速源由 [help.kkgithub.com] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://mirror.ghproxy.com/https://raw.githubusercontent.com', '韩国', '[日本、韩国、德国等](CDN 不固定) - 该公益加速源由 [ghproxy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
//['https://gh-proxy.com/https://raw.githubusercontent.com', '韩国 2', '[韩国] - 该公益加速源由 [ghproxy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://ghproxy.net/https://raw.githubusercontent.com', '日本 1', '[日本 大阪] - 该公益加速源由 [ghproxy] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
['https://fastly.jsdelivr.net/gh', '日本 2', '[日本 东京] - 该公益加速源由 [JSDelivr CDN] 提供 - 缓存:有 - 不支持大小超过 50 MB 的文件 - 不支持版本号格式的分支名(如 v1.2.3)'],
|
||||||
|
['https://fastraw.ixnic.net', '日本 3', '[日本 大阪] - 该公益加速源由 [FastGit 群组成员] 提供 - 缓存:无(或时间很短)'],
|
||||||
|
//['https://gcore.jsdelivr.net/gh', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [JSDelivr CDN] 提供 - 缓存:有 - 不支持大小超过 50 MB 的文件 - 不支持版本号格式的分支名(如 v1.2.3)'], // 变成 美国 Cloudflare CDN 了
|
||||||
|
['https://cdn.jsdelivr.us/gh', '其他 1', '[韩国、美国、马来西亚、罗马尼亚等](CDN 不固定) - 该公益加速源由 [@ayao] 提供 - 缓存:有'],
|
||||||
|
['https://jsdelivr.b-cdn.net/gh', '其他 2', '[中国香港、台湾、日本、新加坡等](CDN 不固定) - 该公益加速源由 [@rttwyjz] 提供 - 缓存:有'],
|
||||||
|
['https://github.moeyy.xyz/https://raw.githubusercontent.com', '其他 3', '[新加坡、中国香港、日本等](CDN 不固定) - 缓存:无(或时间很短)'],
|
||||||
|
['https://raw.cachefly.998111.xyz', '其他 4', '[新加坡、日本、印度等](Anycast CDN 不固定) - 该公益加速源由 [@XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxX0] 提供 - 缓存:有(约 12 小时)'],
|
||||||
|
//['https://raw.incept.pw', '香港', '[中国香港、美国] - 该公益加速源由 [FastGit 群组成员] 提供 - 缓存:无(或时间很短)'], // ERR_SSL_PROTOCOL_ERROR
|
||||||
|
//['https://ghproxy.cc/https://raw.githubusercontent.com', '美国', '[美国 洛杉矶] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
|
||||||
|
//['https://cf.ghproxy.cc/https://raw.githubusercontent.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@yionchiii lau] 提供'], // 暂无必要
|
||||||
|
//['https://gh.jiasu.in/https://raw.githubusercontent.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [@0-RTT] 提供'], // 暂无必要
|
||||||
|
//['https://raw.fgit.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供 - 缓存:无(或时间很短)'], // 被投诉挂了
|
||||||
|
//['https://raw.nuaa.cf', '美国', '[美国 洛杉矶] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
|
||||||
|
//['https://raw.scholar.rr.nu', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供'], // 暂无必要
|
||||||
|
//['https://raw.njuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供 - 缓存:无(或时间很短)'], // 域名挂了
|
||||||
|
//['https://raw.yzuu.cf', '美国', '[美国 纽约] - 该公益加速源由 [FastGit 群组成员] 提供 - 缓存:无(或时间很短)'], // 暂无必要
|
||||||
|
//['https://raw.gitmirror.com', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [GitMirror] 提供 - 缓存:有'], // 暂无必要
|
||||||
|
//['https://cdn.54188.cf/gh', '美国', '[美国 Cloudflare CDN] - 该公益加速源由 [PencilNavigator] 提供 - 缓存:有'], // 暂无必要
|
||||||
|
//['https://raw.fastgit.org', '德国', '[德国] - 该公益加速源由 [FastGit] 提供 - 缓存:无(或时间很短)'], // 挂了
|
||||||
|
//['https://git.yumenaka.net/https://raw.githubusercontent.com', '美国', '[美国 圣何塞] - 缓存:无(或时间很短)'], // 连接超时
|
||||||
|
], svg = [
|
||||||
|
'<svg class="octicon octicon-cloud-download" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M9 12h2l-3 3-3-3h2V7h2v5zm3-8c0-.44-.91-3-4.5-3C5.08 1 3 2.92 3 5 1.02 5 0 6.52 0 8c0 1.53 1 3 3 3h3V9.7H3C1.38 9.7 1.3 8.28 1.3 8c0-.17.05-1.7 1.7-1.7h1.3V5c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2H12c.81 0 2.7.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2h-2V11h2c2.08 0 4-1.16 4-3.5C16 5.06 14.08 4 12 4z"></path></svg>'
|
||||||
|
], style = ['padding:0 6px; margin-right: -1px; border-radius: 2px; background-color: var(--XIU2-back-Color); border-color: rgba(27, 31, 35, 0.1); font-size: 11px; color: var(--XIU2-font-Color);'];
|
||||||
|
|
||||||
|
if (menu_rawFast == null){menu_rawFast = 1; GM_setValue('xiu2_menu_raw_fast', 1)}
|
||||||
|
if (GM_getValue('menu_rawDownLink') == null){GM_setValue('menu_rawDownLink', true)}
|
||||||
|
if (GM_getValue('menu_gitClone') == null){GM_setValue('menu_gitClone', true)}
|
||||||
|
registerMenuCommand();
|
||||||
|
// 注册脚本菜单
|
||||||
|
function registerMenuCommand() {
|
||||||
|
// 如果反馈菜单ID不是 null,则删除所有脚本菜单
|
||||||
|
if (menu_feedBack_ID) {GM_unregisterMenuCommand(menu_rawFast_ID); GM_unregisterMenuCommand(menu_rawDownLink_ID); GM_unregisterMenuCommand(menu_gitClone_ID); GM_unregisterMenuCommand(menu_feedBack_ID); menu_rawFast = GM_getValue('xiu2_menu_raw_fast');}
|
||||||
|
// 避免在减少 raw 数组后,用户储存的数据大于数组而报错
|
||||||
|
if (menu_rawFast > raw_url.length - 1) menu_rawFast = 0
|
||||||
|
if (GM_getValue('menu_rawDownLink')) menu_rawFast_ID = GM_registerMenuCommand(`${['0️⃣','1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟'][menu_rawFast]} [ ${raw_url[menu_rawFast][1]} ] 加速源 (☁) - 点击切换`, menu_toggle_raw_fast);
|
||||||
|
menu_rawDownLink_ID = GM_registerMenuCommand(`${GM_getValue('menu_rawDownLink')?'✅':'❌'} 项目列表单文件快捷下载 (☁)`, function(){if (GM_getValue('menu_rawDownLink') === true) {GM_setValue('menu_rawDownLink', false); GM_notification({text: `已关闭 [项目列表单文件快捷下载 (☁)] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});} else {GM_setValue('menu_rawDownLink', true); GM_notification({text: `已开启 [项目列表单文件快捷下载 (☁)] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});}registerMenuCommand();});
|
||||||
|
menu_gitClone_ID = GM_registerMenuCommand(`${GM_getValue('menu_gitClone')?'✅':'❌'} 添加 git clone 命令`, function(){if (GM_getValue('menu_gitClone') === true) {GM_setValue('menu_gitClone', false); GM_notification({text: `已关闭 [添加 git clone 命令] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});} else {GM_setValue('menu_gitClone', true); GM_notification({text: `已开启 [添加 git clone 命令] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});}registerMenuCommand();});
|
||||||
|
menu_feedBack_ID = GM_registerMenuCommand('💬 反馈 & 建议 [Github]', function () {GM_openInTab('https://github.com/XIU2/UserScript', {active: true,insert: true,setParent: true});GM_openInTab('https://greasyfork.org/zh-CN/scripts/412245/feedback', {active: true,insert: true,setParent: true});});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换加速源
|
||||||
|
function menu_toggle_raw_fast() {
|
||||||
|
// 如果当前加速源位置大于等于加速源总数,则改为第一个加速源,反之递增下一个加速源
|
||||||
|
if (menu_rawFast >= raw_url.length - 1) {menu_rawFast = 0;} else {menu_rawFast += 1;}
|
||||||
|
GM_setValue('xiu2_menu_raw_fast', menu_rawFast);
|
||||||
|
delRawDownLink(); // 删除旧加速源
|
||||||
|
addRawDownLink(); // 添加新加速源
|
||||||
|
GM_notification({text: "已切换加速源为:" + raw_url[menu_rawFast][1], timeout: 3000}); // 提示消息
|
||||||
|
registerMenuCommand(); // 重新注册脚本菜单
|
||||||
|
}
|
||||||
|
|
||||||
|
colorMode(); // 适配白天/夜间主题模式
|
||||||
|
setTimeout(addRawFile, 1000); // Raw 加速
|
||||||
|
setTimeout(addRawDownLink, 2000); // Raw 单文件快捷下载(☁),延迟 2 秒执行,避免被 pjax 刷掉
|
||||||
|
|
||||||
|
// Tampermonkey v4.11 版本添加的 onurlchange 事件 grant,可以监控 pjax 等网页的 URL 变化
|
||||||
|
if (window.onurlchange === undefined) addUrlChangeEvent();
|
||||||
|
window.addEventListener('urlchange', function() {
|
||||||
|
colorMode(); // 适配白天/夜间主题模式
|
||||||
|
if (location.pathname.indexOf('/releases')) addRelease(); // Release 加速
|
||||||
|
setTimeout(addRawFile, 1000); // Raw 加速
|
||||||
|
setTimeout(addRawDownLink, 2000); // Raw 单文件快捷下载(☁),延迟 2 秒执行,避免被 pjax 刷掉
|
||||||
|
setTimeout(addRawDownLink_, 1000); // 在浏览器返回/前进时重新添加 Raw 下载链接(☁)鼠标事件
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Github Git Clone/SSH、Release、Download ZIP 改版为动态加载文件列表,因此需要监控网页元素变化
|
||||||
|
const callback = (mutationsList) => {
|
||||||
|
if (location.pathname.indexOf('/releases') > -1) { // Release
|
||||||
|
for (const mutation of mutationsList) {
|
||||||
|
for (const target of mutation.addedNodes) {
|
||||||
|
if (target.nodeType !== 1) return
|
||||||
|
if (target.tagName === 'DIV' && target.dataset.viewComponent === 'true' && target.classList[0] === 'Box') addRelease();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (document.querySelector('#repository-container-header:not([hidden])')) { // 项目首页
|
||||||
|
for (const mutation of mutationsList) {
|
||||||
|
for (const target of mutation.addedNodes) {
|
||||||
|
if (target.nodeType !== 1) return
|
||||||
|
if (target.tagName === 'DIV' && target.parentElement.id === '__primerPortalRoot__') {
|
||||||
|
addDownloadZIP(target);
|
||||||
|
addGitClone(target);
|
||||||
|
if (addGitCloneSSH(target)) return;
|
||||||
|
} else if (target.tagName === 'DIV' && target.className.indexOf('Box-sc-') !== -1) {
|
||||||
|
if (target.querySelector('input[value^="https:"]')) {
|
||||||
|
addGitCloneClear('.XIU2-GCS');
|
||||||
|
if (addGitClone(target)) return;
|
||||||
|
} else if (target.querySelector('input[value^="git@"]')) {
|
||||||
|
addGitCloneClear('.XIU2-GC');
|
||||||
|
if (addGitCloneSSH(target)) return;
|
||||||
|
} else if (target.querySelector('input[value^="gh "]')) {
|
||||||
|
addGitCloneClear('.XIU2-GC, .XIU2-GCS');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const observer = new MutationObserver(callback);
|
||||||
|
observer.observe(document, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
|
||||||
|
// download_url 随机 4 个美国加速源
|
||||||
|
function get_New_download_url() {
|
||||||
|
//return download_url_us.concat(download_url) // 全输出调试用
|
||||||
|
let shuffled = download_url_us.slice(0), i = download_url_us.length, min = i - 4, temp, index;
|
||||||
|
while (i-- > min) {index = Math.floor((i + 1) * Math.random()); temp = shuffled[index]; shuffled[index] = shuffled[i]; shuffled[i] = temp;}
|
||||||
|
return shuffled.slice(min).concat(download_url); // 随机洗牌 download_url_us 数组并取前 4 个,然后将其合并至 download_url 数组
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release
|
||||||
|
function addRelease() {
|
||||||
|
let html = document.querySelectorAll('.Box-footer'); if (html.length === 0 || location.pathname.indexOf('/releases') === -1) return
|
||||||
|
let divDisplay = 'margin-left: -90px;', new_download_url = get_New_download_url();
|
||||||
|
if (document.documentElement.clientWidth > 755) {divDisplay = 'margin-top: -3px;margin-left: 8px;display: inherit;';} // 调整小屏幕时的样式
|
||||||
|
for (const current of html) {
|
||||||
|
if (current.querySelector('.XIU2-RS')) continue
|
||||||
|
current.querySelectorAll('li.Box-row a').forEach(function (_this) {
|
||||||
|
let href = _this.href.split(location.host),
|
||||||
|
url = '', _html = `<div class="XIU2-RS" style="${divDisplay}">`;
|
||||||
|
|
||||||
|
for (let i=0;i<new_download_url.length;i++) {
|
||||||
|
if (new_download_url[i][3] !== undefined && url.indexOf('/archive/') !== -1) {
|
||||||
|
url = new_download_url[i][3] + href[1]
|
||||||
|
} else {
|
||||||
|
url = new_download_url[i][0] + href[1]
|
||||||
|
}
|
||||||
|
if (location.host !== 'github.com') url = url.replace(location.host,'github.com')
|
||||||
|
_html += `<a style="${style[0]}" class="btn" href="${url}" target="_blank" title="${new_download_url[i][2]}" rel="noreferrer noopener nofollow">${new_download_url[i][1]}</a>`
|
||||||
|
}
|
||||||
|
_this.parentElement.nextElementSibling.insertAdjacentHTML('beforeend', _html + '</div>');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Download ZIP
|
||||||
|
function addDownloadZIP(target) {
|
||||||
|
let html = target.querySelector('ul[class^=List__ListBox-sc-] ul[class^=List__ListBox-sc-]>li:last-child');
|
||||||
|
if (!html) return;
|
||||||
|
let href_script = document.querySelector('react-partial[partial-name=repos-overview]>script[data-target="react-partial.embeddedData"]'),
|
||||||
|
href_slice = href_script.textContent.slice(href_script.textContent.indexOf('"zipballUrl":"')+14),
|
||||||
|
href = href_slice.slice(0, href_slice.indexOf('"')),
|
||||||
|
url = '', _html = '', new_download_url = get_New_download_url();
|
||||||
|
|
||||||
|
// 克隆原 Download ZIP 元素,并定位 <a> <span> 标签
|
||||||
|
let html_clone = html.cloneNode(true),
|
||||||
|
html_clone_a = html_clone.querySelector('a[href$=".zip"]'),
|
||||||
|
html_clone_span = html_clone.querySelector('span[id]');
|
||||||
|
|
||||||
|
for (let i=0;i<new_download_url.length;i++) {
|
||||||
|
if (new_download_url[i][3] === '') continue
|
||||||
|
|
||||||
|
if (new_download_url[i][3] !== undefined) {
|
||||||
|
url = new_download_url[i][3] + href
|
||||||
|
} else {
|
||||||
|
url = new_download_url[i][0] + href
|
||||||
|
}
|
||||||
|
if (location.host !== 'github.com') url = url.replace(location.host,'github.com')
|
||||||
|
|
||||||
|
html_clone_a.href = url
|
||||||
|
html_clone_a.setAttribute('title', new_download_url[i][2].replaceAll(' ','\n'))
|
||||||
|
html_clone_span.textContent = 'Download ZIP ' + new_download_url[i][1]
|
||||||
|
_html += html_clone.outerHTML
|
||||||
|
}
|
||||||
|
html.insertAdjacentHTML('afterend', _html);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Git Clone 切换清理
|
||||||
|
function addGitCloneClear(css) {
|
||||||
|
document.querySelectorAll(css).forEach((e)=>{e.remove()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Git Clone
|
||||||
|
function addGitClone(target) {
|
||||||
|
let html = target.querySelector('input[value^="https:"]');
|
||||||
|
if (!html) return;
|
||||||
|
if (!html.nextElementSibling) return true;
|
||||||
|
let href_split = html.value.split(location.host)[1],
|
||||||
|
html_parent = '<div style="margin-top: 4px;" class="XIU2-GC ' + html.parentElement.className + '">',
|
||||||
|
url = '', _html = '', _gitClone = '';
|
||||||
|
html.nextElementSibling.hidden = true; // 隐藏右侧复制按钮
|
||||||
|
if (GM_getValue('menu_gitClone')) {_gitClone='git clone '; html.value = _gitClone + html.value; html.setAttribute('value', html.value);}
|
||||||
|
// 克隆原 Git Clone 元素
|
||||||
|
let html_clone = html.cloneNode(true);
|
||||||
|
for (let i=0;i<clone_url.length;i++) {
|
||||||
|
if (clone_url[i][0] === 'https://gitclone.com') {
|
||||||
|
url = _gitClone + clone_url[i][0] + '/github.com' + href_split
|
||||||
|
} else {
|
||||||
|
url = _gitClone + clone_url[i][0] + href_split
|
||||||
|
}
|
||||||
|
html_clone.title = `加速源:${clone_url[i][1]} (点击可直接复制)\n${clone_url[i][2].replaceAll(' ','\n')}`
|
||||||
|
html_clone.setAttribute('value', url)
|
||||||
|
_html += html_parent + html_clone.outerHTML + '</div>'
|
||||||
|
}
|
||||||
|
html.parentElement.insertAdjacentHTML('afterend', _html);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Git Clone SSH
|
||||||
|
function addGitCloneSSH(target) {
|
||||||
|
let html = target.querySelector('input[value^="git@"]');
|
||||||
|
if (!html) return;
|
||||||
|
if (!html.nextElementSibling) return true;
|
||||||
|
let href_split = html.value.split(':')[1],
|
||||||
|
html_parent = '<div style="margin-top: 4px;" class="XIU2-GCS ' + html.parentElement.className + '">',
|
||||||
|
url = '', _html = '', _gitClone = '';
|
||||||
|
html.nextElementSibling.hidden = true; // 隐藏右侧复制按钮
|
||||||
|
if (GM_getValue('menu_gitClone')) {_gitClone='git clone '; html.value = _gitClone + html.value; html.setAttribute('value', html.value);}
|
||||||
|
// 克隆原 Git Clone SSH 元素
|
||||||
|
let html_clone = html.cloneNode(true);
|
||||||
|
for (let i=0;i<clone_ssh_url.length;i++) {
|
||||||
|
url = _gitClone + clone_ssh_url[i][0] + href_split
|
||||||
|
html_clone.title = `加速源:${clone_ssh_url[i][1]} (点击可直接复制)\n${clone_ssh_url[i][2].replaceAll(' ','\n')}`
|
||||||
|
html_clone.setAttribute('value', url)
|
||||||
|
_html += html_parent + html_clone.outerHTML + '</div>'
|
||||||
|
}
|
||||||
|
html.parentElement.insertAdjacentHTML('afterend', _html);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Raw
|
||||||
|
function addRawFile() {
|
||||||
|
let html = document.querySelector('a[data-testid="raw-button"]');
|
||||||
|
if (!html) return;
|
||||||
|
let href = location.href.replace(`https://${location.host}`,''),
|
||||||
|
href2 = href.replace('/blob/','/'),
|
||||||
|
url = '', _html = '';
|
||||||
|
|
||||||
|
for (let i=1;i<raw_url.length;i++) {
|
||||||
|
if ((raw_url[i][0].indexOf('/gh') + 3 === raw_url[i][0].length) && raw_url[i][0].indexOf('cdn.staticaly.com') === -1) {
|
||||||
|
url = raw_url[i][0] + href.replace('/blob/','@');
|
||||||
|
} else {
|
||||||
|
url = raw_url[i][0] + href2;
|
||||||
|
}
|
||||||
|
_html += `<a href="${url}" title="${raw_url[i][2]}" target="_blank" role="button" rel="noreferrer noopener nofollow" data-size="small" class="${html.className} XIU2-RF">${raw_url[i][1].replace(/ \d/,'')}</a>`
|
||||||
|
}
|
||||||
|
if (document.querySelector('.XIU2-RF')) document.querySelectorAll('.XIU2-RF').forEach((e)=>{e.remove()})
|
||||||
|
html.insertAdjacentHTML('afterend', _html);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Raw 单文件快捷下载(☁)
|
||||||
|
function addRawDownLink() {
|
||||||
|
if (!GM_getValue('menu_rawDownLink')) return
|
||||||
|
// 如果不是项目文件页面,就返回,如果网页有 Raw 下载链接(☁)就返回
|
||||||
|
let files = document.querySelectorAll('div.Box-row svg.octicon.octicon-file, .react-directory-filename-column>svg.color-fg-muted');if(files.length === 0) return;if (location.pathname.indexOf('/tags') > -1) return
|
||||||
|
let files1 = document.querySelectorAll('a.fileDownLink');if(files1.length > 0) return;
|
||||||
|
|
||||||
|
// 鼠标指向则显示
|
||||||
|
var mouseOverHandler = function(evt) {
|
||||||
|
let elem = evt.currentTarget,
|
||||||
|
aElm_new = elem.querySelectorAll('.fileDownLink'),
|
||||||
|
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
|
||||||
|
aElm_new.forEach(el=>{el.style.cssText = 'display: inline'});
|
||||||
|
aElm_now.forEach(el=>{el.style.cssText = 'display: none'});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 鼠标离开则隐藏
|
||||||
|
var mouseOutHandler = function(evt) {
|
||||||
|
let elem = evt.currentTarget,
|
||||||
|
aElm_new = elem.querySelectorAll('.fileDownLink'),
|
||||||
|
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
|
||||||
|
aElm_new.forEach(el=>{el.style.cssText = 'display: none'});
|
||||||
|
aElm_now.forEach(el=>{el.style.cssText = 'display: inline'});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 循环添加
|
||||||
|
files.forEach(function(fileElm) {
|
||||||
|
let trElm = fileElm.parentNode.parentNode,
|
||||||
|
cntElm_a = trElm.querySelector('[role="rowheader"] > .css-truncate.css-truncate-target.d-block.width-fit > a, .react-directory-truncate>a'),
|
||||||
|
Name = cntElm_a.innerText,
|
||||||
|
href = cntElm_a.getAttribute('href'),
|
||||||
|
href2 = href.replace('/blob/','/'), url, url_name, url_tip;
|
||||||
|
if ((raw_url[menu_rawFast][0].indexOf('/gh') + 3 === raw_url[menu_rawFast][0].length) && raw_url[menu_rawFast][0].indexOf('cdn.staticaly.com') === -1) {
|
||||||
|
url = raw_url[menu_rawFast][0] + href.replace('/blob/','@');
|
||||||
|
} else {
|
||||||
|
url = raw_url[menu_rawFast][0] + href2;
|
||||||
|
}
|
||||||
|
|
||||||
|
url_name = raw_url[menu_rawFast][1]; url_tip = raw_url[menu_rawFast][2];
|
||||||
|
fileElm.insertAdjacentHTML('afterend', `<a href="${url}" download="${Name}" target="_blank" rel="noreferrer noopener nofollow" class="fileDownLink" style="display: none;" title="「${url_name}」 [Alt + 左键] 或 [右键 - 另存为...] 下载文件。 注意:鼠标点击 [☁] 图标,而不是左侧的文件名! ${url_tip}提示:点击浏览器右上角 Tampermonkey 扩展图标 - [ ${raw_url[menu_rawFast][1]} ] 加速源 (☁) 即可切换。">${svg[0]}</a>`);
|
||||||
|
// 绑定鼠标事件
|
||||||
|
trElm.onmouseover = mouseOverHandler;
|
||||||
|
trElm.onmouseout = mouseOutHandler;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 移除 Raw 单文件快捷下载(☁)
|
||||||
|
function delRawDownLink() {
|
||||||
|
if (!GM_getValue('menu_rawDownLink')) return
|
||||||
|
let aElm = document.querySelectorAll('.fileDownLink');if(aElm.length === 0) return;
|
||||||
|
aElm.forEach(function(fileElm) {fileElm.remove();})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 在浏览器返回/前进时重新添加 Raw 单文件快捷下载(☁)鼠标事件
|
||||||
|
function addRawDownLink_() {
|
||||||
|
if (!GM_getValue('menu_rawDownLink')) return
|
||||||
|
// 如果不是项目文件页面,就返回,如果网页没有 Raw 下载链接(☁)就返回
|
||||||
|
let files = document.querySelectorAll('div.Box-row svg.octicon.octicon-file, .react-directory-filename-column>svg.color-fg-muted');if(files.length === 0) return;
|
||||||
|
let files1 = document.querySelectorAll('a.fileDownLink');if(files1.length === 0) return;
|
||||||
|
|
||||||
|
// 鼠标指向则显示
|
||||||
|
var mouseOverHandler = function(evt) {
|
||||||
|
let elem = evt.currentTarget,
|
||||||
|
aElm_new = elem.querySelectorAll('.fileDownLink'),
|
||||||
|
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
|
||||||
|
aElm_new.forEach(el=>{el.style.cssText = 'display: inline'});
|
||||||
|
aElm_now.forEach(el=>{el.style.cssText = 'display: none'});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 鼠标离开则隐藏
|
||||||
|
var mouseOutHandler = function(evt) {
|
||||||
|
let elem = evt.currentTarget,
|
||||||
|
aElm_new = elem.querySelectorAll('.fileDownLink'),
|
||||||
|
aElm_now = elem.querySelectorAll('svg.octicon.octicon-file, svg.color-fg-muted');
|
||||||
|
aElm_new.forEach(el=>{el.style.cssText = 'display: none'});
|
||||||
|
aElm_now.forEach(el=>{el.style.cssText = 'display: inline'});
|
||||||
|
};
|
||||||
|
// 循环添加
|
||||||
|
files.forEach(function(fileElm) {
|
||||||
|
let trElm = fileElm.parentNode.parentNode;
|
||||||
|
// 绑定鼠标事件
|
||||||
|
trElm.onmouseover = mouseOverHandler;
|
||||||
|
trElm.onmouseout = mouseOutHandler;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 适配白天/夜间主题模式
|
||||||
|
function colorMode() {
|
||||||
|
let style_Add;
|
||||||
|
if (document.getElementById('XIU2-Github')) {style_Add = document.getElementById('XIU2-Github')} else {style_Add = document.createElement('style'); style_Add.id = 'XIU2-Github'; style_Add.type = 'text/css';}
|
||||||
|
backColor = '#ffffff'; fontColor = '#888888';
|
||||||
|
|
||||||
|
if (document.lastElementChild.dataset.colorMode === 'dark') { // 如果是夜间模式
|
||||||
|
if (document.lastElementChild.dataset.darkTheme === 'dark_dimmed') {
|
||||||
|
backColor = '#272e37'; fontColor = '#768390';
|
||||||
|
} else {
|
||||||
|
backColor = '#161a21'; fontColor = '#97a0aa';
|
||||||
|
}
|
||||||
|
} else if (document.lastElementChild.dataset.colorMode === 'auto') { // 如果是自动模式
|
||||||
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches || document.lastElementChild.dataset.lightTheme.indexOf('dark') > -1) { // 如果浏览器是夜间模式 或 白天模式是 dark 的情况
|
||||||
|
if (document.lastElementChild.dataset.darkTheme === 'dark_dimmed') {
|
||||||
|
backColor = '#272e37'; fontColor = '#768390';
|
||||||
|
} else if (document.lastElementChild.dataset.darkTheme.indexOf('light') === -1) { // 排除夜间模式是 light 的情况
|
||||||
|
backColor = '#161a21'; fontColor = '#97a0aa';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.lastElementChild.appendChild(style_Add).textContent = `.XIU2-RS a {--XIU2-back-Color: ${backColor}; --XIU2-font-Color: ${fontColor};}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 自定义 urlchange 事件(用来监听 URL 变化),针对非 Tampermonkey 油猴管理器
|
||||||
|
function addUrlChangeEvent() {
|
||||||
|
history.pushState = ( f => function pushState(){
|
||||||
|
var ret = f.apply(this, arguments);
|
||||||
|
window.dispatchEvent(new Event('pushstate'));
|
||||||
|
window.dispatchEvent(new Event('urlchange'));
|
||||||
|
return ret;
|
||||||
|
})(history.pushState);
|
||||||
|
|
||||||
|
history.replaceState = ( f => function replaceState(){
|
||||||
|
var ret = f.apply(this, arguments);
|
||||||
|
window.dispatchEvent(new Event('replacestate'));
|
||||||
|
window.dispatchEvent(new Event('urlchange'));
|
||||||
|
return ret;
|
||||||
|
})(history.replaceState);
|
||||||
|
|
||||||
|
window.addEventListener('popstate',()=>{ // 点击浏览器的前进/后退按钮时触发 urlchange 事件
|
||||||
|
window.dispatchEvent(new Event('urlchange'))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
console.log("ds_github_monkey_2.5.19 completed")
|
||||||
|
})
|
||||||
|
console.log("ds_github_monkey_2.5.19 loaded")
|
19
lerna.json
19
lerna.json
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"packages": [
|
|
||||||
"packages/*"
|
|
||||||
],
|
|
||||||
"command": {
|
|
||||||
"publish": {
|
|
||||||
"ignoreChanges": [
|
|
||||||
"*.md",
|
|
||||||
"config",
|
|
||||||
"doc"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bootstrap": {
|
|
||||||
"ignore": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"name": "dev-sidecar-parent",
|
|
||||||
"private": true,
|
|
||||||
"devDependencies": {
|
|
||||||
"lerna": "^3.22.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
module.exports = require('./src')
|
|
|
@ -1,84 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
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
|
|
|
@ -1,53 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
|
@ -1,45 +0,0 @@
|
||||||
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})`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
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拦截的
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
const proxy = require('./impl/proxy')
|
|
||||||
const redirect = require('./impl/redirect')
|
|
||||||
|
|
||||||
const modules = [proxy, redirect]
|
|
||||||
|
|
||||||
module.exports = modules
|
|
|
@ -1,32 +0,0 @@
|
||||||
#!/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}`))
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
require('babel-polyfill')
|
|
||||||
module.exports = require('./mitmproxy')
|
|
|
@ -1,98 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,180 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,264 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
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:*')
|
|
|
@ -1,33 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
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
|
|
|
@ -1,87 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
|
@ -1,61 +0,0 @@
|
||||||
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
|
|
|
@ -1,196 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
<#
|
|
||||||
.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"
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
$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
|
|
|
@ -1,12 +0,0 @@
|
||||||
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
|
@ -1,5 +0,0 @@
|
||||||
[*.{js,jsx,ts,tsx,vue}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
|
@ -1,26 +0,0 @@
|
||||||
.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
|
|
|
@ -1,24 +0,0 @@
|
||||||
# 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/).
|
|
|
@ -1,5 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,61 +0,0 @@
|
||||||
{
|
|
||||||
"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.
Before Width: | Height: | Size: 4.2 KiB |
|
@ -1,17 +0,0 @@
|
||||||
<!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>
|
|
|
@ -1,71 +0,0 @@
|
||||||
<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>
|
|
Before Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
|
@ -1,143 +0,0 @@
|
||||||
'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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
import config from '../../../config/index.json5'
|
|
||||||
export default config
|
|
|
@ -1,11 +0,0 @@
|
||||||
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')
|
|
|
@ -1 +0,0 @@
|
||||||
window.ipcRenderer = require('electron').ipcRenderer
|
|
|
@ -1,36 +0,0 @@
|
||||||
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
|
|
|
@ -1,183 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,155 +0,0 @@
|
||||||
<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>
|
|
|
@ -1 +0,0 @@
|
||||||
import './status'
|
|
|
@ -1,17 +0,0 @@
|
||||||
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
|
|
|
@ -1,39 +0,0 @@
|
||||||
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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
10213
packages/gui/yarn.lock
10213
packages/gui/yarn.lock
File diff suppressed because it is too large
Load Diff
5114
yarn-error.log
5114
yarn-error.log
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue