提交 /github/monkey.js
parent
b7654d6ab0
commit
418871f215
|
@ -4,3 +4,8 @@
|
|||
out
|
||||
gen
|
||||
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
|
||||
开发者边车,命名取自service-mesh的service-sidecar,意为为开发者打辅助的边车工具
|
||||
通过本地代理的方式将请求代理到一些国内的加速通道上
|
||||
解决一些网站和库无法访问或访问速度慢的问题
|
||||
|
||||
## 特性
|
||||
### 1、 解决git push某些情况下需要临时输入账号密码的问题
|
||||
当前分支用于提供最新版本的一些实用脚本,供大家使用。
|
||||
|
||||
### 2、 github的release、source、zip下载加速
|
||||
可解决npm install 时某些安装包下载不下来的问题
|
||||
|
||||
### 3、 github的源代码查看(raw/blame查看)
|
||||
|
||||
### 4、 Stack Overflow 加速
|
||||
|
||||
### 5、 google cdn 加速
|
||||
|
||||
### 6、 gist.github.com 加速
|
||||
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1、安装与启动
|
||||
```shell
|
||||
git clone https://gitee.com/docmirror/dev-sidecar.git
|
||||
cd ./dev-sidecar/packages/core
|
||||
npm install
|
||||
npm run start
|
||||
|
||||
#或使用cnpm
|
||||
cnpm install
|
||||
npm run start
|
||||
|
||||
#或使用yarn
|
||||
yarn install
|
||||
npm run start
|
||||
|
||||
```
|
||||
|
||||
输出
|
||||
```
|
||||
CA Cert saved in: C:\Users\Administrator\.dev-sidecar\dev-sidecar.ca.crt
|
||||
CA private key saved in: C:\Users\Administrator\.dev-sidecar\dev-sidecar.ca.key.pem
|
||||
|
||||
dev-sidecar启动端口: 1181
|
||||
代理已开启, 127.0.0.1 1181
|
||||
|
||||
```
|
||||
启动后会自动设置系统代理、npm代理
|
||||
|
||||
### 2、设置信任根证书
|
||||
|
||||
第一次启动时会本地随机生成一个根证书放在此目录下(由于此证书是本地随机生成的,所以信任它是安全的)
|
||||
```
|
||||
# 你的Home路径如果有修改,输出会不一样,请按照实际日志输出路径查看
|
||||
CA Cert saved in: C:\Users\Administrator\.dev-sidecar\dev-sidecar.ca.crt
|
||||
```
|
||||
|
||||
windows用户安装根证书
|
||||
```
|
||||
start %HOMEPATH%/.dev-sidecar/dev-sidecar.ca.crt
|
||||
或者
|
||||
打开`C:\Users\Administrator\.dev-sidecar\`文件夹,双击`dev-sidecar.ca.crt`
|
||||
```
|
||||
依次点击安装证书->所有用户->将所有证书都放入下列存储->受信任的根证书颁发机构->确定,下一步,确定即可
|
||||
|
||||

|
||||
|
||||
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/)
|
||||
请使用 [DevSidecar-1.8.1](https://github.com/docmirror/dev-sidecar/releases/tag/v1.8.1) 的新特性 [#294](https://github.com/docmirror/dev-sidecar/pull/294),来引用最新版本的脚本。
|
|
@ -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