commit
b7654d6ab0
@ -0,0 +1,6 @@
|
||||
# IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
node_modules/
|
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
server: {
|
||||
port: 1181
|
||||
},
|
||||
"intercepts": {
|
||||
'notify3.note.youdao.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://localhost:99999'
|
||||
}
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"mapping": {
|
||||
//"avatars*.githubusercontent.com": "usa"
|
||||
}
|
||||
},
|
||||
// setting: {
|
||||
// startup: {
|
||||
// // 开机启动
|
||||
// server: true,
|
||||
// proxy: {
|
||||
// system: true,
|
||||
// npm: true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
After Width: | Height: | Size: 87 KiB |
@ -0,0 +1,19 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"command": {
|
||||
"publish": {
|
||||
"ignoreChanges": [
|
||||
"*.md",
|
||||
"config",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
"bootstrap": {
|
||||
"ignore": [
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "dev-sidecar-parent",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"lerna": "^3.22.1"
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
module.exports = require('./src')
|
@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "@docmirror/dev-sidecar",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"depedencies": {},
|
||||
"keywords": [],
|
||||
"author": "docmirror.cn",
|
||||
"license": "MPL2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"start": "node start.js",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"agentkeepalive": "^2.1.1",
|
||||
"babel-core": "^6.8.0",
|
||||
"babel-plugin-transform-async-to-generator": "^6.7.4",
|
||||
"babel-polyfill": "^6.8.0",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-register": "^6.8.0",
|
||||
"charset": "^1.0.0",
|
||||
"child_process": "^1.0.2",
|
||||
"colors": "^1.1.2",
|
||||
"commander": "^2.9.0",
|
||||
"core-js": "^3.6.5",
|
||||
"debug": "^4.1.1",
|
||||
"dns-over-http": "^0.2.0",
|
||||
"dns-over-tls": "^0.0.8",
|
||||
"iconv-lite": "^0.4.13",
|
||||
"is-browser": "^2.1.0",
|
||||
"jschardet": "^1.4.1",
|
||||
"json5": "^2.1.3",
|
||||
"lodash": "^4.7.0",
|
||||
"lru-cache": "^6.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-cmd": "^3.0.0",
|
||||
"node-forge": "^0.8.2",
|
||||
"node-mitmproxy": "^3.1.1",
|
||||
"node-powershell": "^4.0.0",
|
||||
"require-context": "^1.1.0",
|
||||
"through2": "^2.0.1",
|
||||
"tunnel-agent": "^0.4.3",
|
||||
"util": "^0.12.3",
|
||||
"validator": "^13.1.17",
|
||||
"vue": "^2.6.11",
|
||||
"winreg": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
const lodash = require('lodash')
|
||||
const defConfig = require('./config/index.js')
|
||||
let configTarget = defConfig
|
||||
module.exports = {
|
||||
get () {
|
||||
return configTarget
|
||||
},
|
||||
set (newConfig) {
|
||||
const clone = lodash.cloneDeep(defConfig)
|
||||
lodash.merge(clone, newConfig)
|
||||
configTarget = clone
|
||||
return configTarget
|
||||
},
|
||||
getDefault () {
|
||||
return defConfig
|
||||
},
|
||||
resetDefault () {
|
||||
configTarget = defConfig
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
module.exports = {
|
||||
server: {
|
||||
port: 1181
|
||||
},
|
||||
intercepts: {
|
||||
'github.com': [
|
||||
{
|
||||
// "release archive 下载链接替换",
|
||||
regexp: [
|
||||
'/.*/.*/releases/download/',
|
||||
'/.*/.*/archive/'
|
||||
],
|
||||
redirect: 'https://download.fastgit.org'
|
||||
},
|
||||
{
|
||||
regexp: [
|
||||
'/.*/.*/raw/',
|
||||
'/.*/.*/blame/'
|
||||
],
|
||||
redirect: 'https://hub.fastgit.org'
|
||||
}
|
||||
],
|
||||
// 'codeload.github.com': [
|
||||
// {
|
||||
// regexp: '.*',
|
||||
// redirect:"https://download.fastgit.org"
|
||||
// }
|
||||
// ],
|
||||
'raw.githubusercontent.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://raw.fastgit.org'
|
||||
}
|
||||
],
|
||||
'github.githubassets.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://assets.fastgit.org'
|
||||
}
|
||||
],
|
||||
'customer-stories-feed.github.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://customer-stories-feed.fastgit.org'
|
||||
}
|
||||
],
|
||||
// google cdn
|
||||
'ajax.googleapis.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://ajax.loli.net'
|
||||
}
|
||||
],
|
||||
'fonts.googleapis.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://fonts.loli.net'
|
||||
}
|
||||
],
|
||||
'themes.googleapis.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://themes.loli.net'
|
||||
}
|
||||
],
|
||||
'fonts.gstatic.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
proxy: 'https://gstatic.loli.net'
|
||||
}
|
||||
],
|
||||
'www.google.com': [
|
||||
{
|
||||
regexp: '/recaptcha/.*',
|
||||
proxy: 'https://www.recaptcha.net'
|
||||
}
|
||||
],
|
||||
'secure.gravatar.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://gravatar.loli.net'
|
||||
}
|
||||
],
|
||||
'clients*.google.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://localhost:99999'
|
||||
}
|
||||
],
|
||||
'lh*.googleusercontent.com': [
|
||||
{
|
||||
regexp: '.*',
|
||||
redirect: 'https://localhost:99999'
|
||||
}
|
||||
]
|
||||
},
|
||||
dns: {
|
||||
providers: {
|
||||
aliyun: {
|
||||
type: 'https',
|
||||
server: 'https://dns.alidns.com/dns-query',
|
||||
cacheSize: 1000
|
||||
},
|
||||
usa: {
|
||||
type: 'https',
|
||||
server: 'https://cloudflare-dns.com/dns-query',
|
||||
cacheSize: 1000
|
||||
}
|
||||
},
|
||||
mapping: {
|
||||
// "解决push的时候需要输入密码的问题",
|
||||
'api.github.com': 'usa',
|
||||
'gist.github.com': 'usa'
|
||||
// "avatars*.githubusercontent.com": "usa"
|
||||
}
|
||||
},
|
||||
setting: {
|
||||
startup: { // 开机启动
|
||||
server: true,
|
||||
proxy: {
|
||||
system: true,
|
||||
npm: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
const listener = {}
|
||||
let index = 1
|
||||
function register (channel, handle, order = 10) {
|
||||
let handles = listener[channel]
|
||||
if (handles == null) {
|
||||
handles = listener[channel] = []
|
||||
}
|
||||
handles.push({ id: index, handle, order })
|
||||
handles.sort((a, b) => { return a.order - b.order })
|
||||
return index++
|
||||
}
|
||||
function fire (channel, event) {
|
||||
const handles = listener[channel]
|
||||
if (handles == null) {
|
||||
return
|
||||
}
|
||||
for (const item of handles) {
|
||||
item.handle(event)
|
||||
}
|
||||
}
|
||||
|
||||
function unregister (id) {
|
||||
for (const key in listener) {
|
||||
const handlers = listener[key]
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
const handle = handlers[i]
|
||||
if (handle.id === id) {
|
||||
handlers.splice(i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const EventHub = {
|
||||
register,
|
||||
fire,
|
||||
unregister
|
||||
}
|
||||
module.exports = EventHub
|
@ -0,0 +1,53 @@
|
||||
const server = require('./server/index.js')
|
||||
const proxy = require('./switch/proxy/index.js')
|
||||
const status = require('./status')
|
||||
const config = require('./config')
|
||||
const event = require('./event')
|
||||
|
||||
async function proxyStartup ({ ip, port }) {
|
||||
for (const key in proxy) {
|
||||
if (config.get().setting.startup.proxy[key]) {
|
||||
await proxy[key].open({ ip, port })
|
||||
}
|
||||
}
|
||||
}
|
||||
async function proxyShutdown () {
|
||||
for (const key in proxy) {
|
||||
console.log('status', status)
|
||||
if (status.proxy[key] === false) {
|
||||
continue
|
||||
}
|
||||
await proxy[key].close()
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
status,
|
||||
api: {
|
||||
server,
|
||||
proxy,
|
||||
config,
|
||||
startup: async (newConfig) => {
|
||||
config.set(newConfig)
|
||||
try {
|
||||
if (config.get().setting.startup.server) {
|
||||
server.start(newConfig)
|
||||
}
|
||||
await proxyStartup({ ip: '127.0.0.1', port: config.get().server.port })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
shutdown: async () => {
|
||||
try {
|
||||
await proxyShutdown()
|
||||
return new Promise(resolve => {
|
||||
server.close()
|
||||
resolve()
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
event
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
const expose = require('./expose.js')
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
// 避免异常崩溃
|
||||
process.on('uncaughtException', function (err) {
|
||||
if (err.code === 'ECONNABORTED') {
|
||||
// console.error(err.errno)
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason)
|
||||
// application specific logging, throwing an error, or other logic here
|
||||
})
|
||||
|
||||
module.exports = expose
|
@ -0,0 +1,45 @@
|
||||
const LRU = require('lru-cache')
|
||||
const { isIP } = require('validator')
|
||||
const getLogger = require('../utils/logger')
|
||||
|
||||
const logger = getLogger('dns')
|
||||
const cacheSize = 1024
|
||||
function _isIP (v) {
|
||||
return v && isIP(v)
|
||||
}
|
||||
|
||||
module.exports = class BaseDNS {
|
||||
constructor () {
|
||||
this.cache = new LRU(cacheSize)
|
||||
}
|
||||
|
||||
async lookup (hostname) {
|
||||
try {
|
||||
let ip = this.cache.get(hostname)
|
||||
if (ip) {
|
||||
return ip
|
||||
}
|
||||
|
||||
const t = new Date()
|
||||
|
||||
ip = hostname
|
||||
for (let depth = 0; !_isIP(ip) && depth < 5; depth++) {
|
||||
ip = await this._lookup(ip).catch(error => {
|
||||
logger.debug(error)
|
||||
return ip
|
||||
})
|
||||
}
|
||||
|
||||
if (!_isIP(ip)) {
|
||||
throw new Error(`BAD IP FORMAT (${ip})`)
|
||||
}
|
||||
|
||||
logger.debug(`[DNS] ${hostname} -> ${ip} (${new Date() - t} ms)`)
|
||||
this.cache.set(hostname, ip)
|
||||
return ip
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
logger.debug(`[DNS] cannot resolve hostname ${hostname} (${error})`)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
const { promisify } = require('util')
|
||||
const doh = require('dns-over-http')
|
||||
const BaseDNS = require('./base')
|
||||
|
||||
const dohQueryAsync = promisify(doh.query)
|
||||
|
||||
module.exports = class DNSOverHTTPS extends BaseDNS {
|
||||
constructor (dnsServer) {
|
||||
super()
|
||||
this.dnsServer = dnsServer
|
||||
}
|
||||
|
||||
async _lookup (hostname) {
|
||||
const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
||||
return result.answers[0].data
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
const DNSOverTLS = require('./tls.js')
|
||||
const DNSOverHTTPS = require('./https.js')
|
||||
module.exports = {
|
||||
initDNS (dnsProviders) {
|
||||
const dnsMap = {}
|
||||
for (const provider in dnsProviders) {
|
||||
const conf = dnsProviders[provider]
|
||||
dnsMap[provider] = conf.type === 'https' ? new DNSOverHTTPS(conf.server) : new DNSOverTLS(conf.server)
|
||||
}
|
||||
return dnsMap
|
||||
},
|
||||
hasDnsLookup (dnsConfig, hostname) {
|
||||
let providerName = dnsConfig.mapping[hostname]
|
||||
if (!providerName) {
|
||||
for (const target in dnsConfig.mapping) {
|
||||
if (target.indexOf('*') < 0) {
|
||||
continue
|
||||
}
|
||||
const regexp = target.replace('.', '\\.')
|
||||
.replace('*', '.*')
|
||||
if (hostname.match(regexp)) {
|
||||
providerName = dnsConfig.mapping[target]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (providerName) {
|
||||
console.log('匹配到dns:', providerName, hostname)
|
||||
return dnsConfig.providers[providerName]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
const dnstls = require('dns-over-tls')
|
||||
const BaseDNS = require('./base')
|
||||
|
||||
module.exports = class DNSOverTLS extends BaseDNS {
|
||||
async _lookup (hostname) {
|
||||
const { answers } = await dnstls.query(hostname)
|
||||
|
||||
const answer = answers.find(answer => answer.type === 'A' && answer.class === 'IN')
|
||||
|
||||
if (answer) {
|
||||
return answer.data
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
const url = require('url')
|
||||
module.exports = {
|
||||
requestInterceptor (interceptOpt, rOptions, req, res, ssl) {
|
||||
req.abort()
|
||||
console.log('abort:', rOptions.hostname, req.url)
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return !!interceptOpt.abort
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
const url = require('url')
|
||||
module.exports = {
|
||||
requestInterceptor (interceptOpt, rOptions, req, res, ssl) {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const URL = url.parse(interceptOpt.proxy)
|
||||
rOptions.protocol = URL.protocol
|
||||
rOptions.hostname = URL.host
|
||||
rOptions.host = URL.host
|
||||
rOptions.headers.host = URL.host
|
||||
if (URL.port == null) {
|
||||
rOptions.port = rOptions.protocol === 'https:' ? 443 : 80
|
||||
}
|
||||
|
||||
console.log('proxy:', rOptions.hostname, req.url, interceptOpt.proxy)
|
||||
},
|
||||
is (interceptOpt) {
|
||||
return !!interceptOpt.proxy
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
const proxy = require('./impl/proxy')
|
||||
const redirect = require('./impl/redirect')
|
||||
|
||||
const modules = [proxy, redirect]
|
||||
|
||||
module.exports = modules
|
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
require('babel-polyfill')
|
||||
const mitmproxy = require('../mitmproxy')
|
||||
const program = require('commander')
|
||||
const packageJson = require('../../package.json')
|
||||
// const tlsUtils = require('../tls/tlsUtils')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const colors = require('colors')
|
||||
|
||||
fs.existsSync = fs.existsSync || path.existsSync
|
||||
|
||||
program
|
||||
.version(packageJson.version)
|
||||
.option('-c, --config [value]', 'config file path')
|
||||
.parse(process.argv)
|
||||
|
||||
console.log(program.config)
|
||||
|
||||
const configPath = path.resolve(program.config)
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
const configObject = require(configPath)
|
||||
|
||||
if (typeof configObject !== 'object') {
|
||||
console.error(colors.red(`Config Error in ${configPath}`))
|
||||
} else {
|
||||
mitmproxy.createProxy(configObject)
|
||||
}
|
||||
} else {
|
||||
console.error(colors.red(`Can not find \`config file\` file: ${configPath}`))
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
const AgentOrigin = require('agentkeepalive')
|
||||
|
||||
module.exports = class Agent extends AgentOrigin {
|
||||
// Hacky
|
||||
getName (option) {
|
||||
let name = AgentOrigin.prototype.getName.call(this, option)
|
||||
name += ':'
|
||||
if (option.customSocketId) {
|
||||
name += option.customSocketId
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
const HttpsAgentOrigin = require('agentkeepalive').HttpsAgent
|
||||
|
||||
module.exports = class HttpsAgent extends HttpsAgentOrigin {
|
||||
// Hacky
|
||||
getName (option) {
|
||||
let name = HttpsAgentOrigin.prototype.getName.call(this, option)
|
||||
name += ':'
|
||||
if (option.customSocketId) {
|
||||
name += option.customSocketId
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
const path = require('path')
|
||||
|
||||
const config = exports
|
||||
|
||||
config.caCertFileName = 'dev-sidecar.ca.crt'
|
||||
|
||||
config.caKeyFileName = 'dev-sidecar.ca.key.pem'
|
||||
|
||||
config.defaultPort = 1181
|
||||
|
||||
config.caName = 'Dev-Sidecar CA'
|
||||
|
||||
config.getDefaultCABasePath = function () {
|
||||
const userHome = process.env.HOME || process.env.USERPROFILE
|
||||
return path.resolve(userHome, './.dev-sidecar')
|
||||
}
|
||||
|
||||
config.getDefaultCACertPath = function () {
|
||||
return path.resolve(config.getDefaultCABasePath(), config.caCertFileName)
|
||||
}
|
||||
|
||||
config.getDefaultCAKeyPath = function () {
|
||||
return path.resolve(config.getDefaultCABasePath(), config.caKeyFileName)
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
const url = require('url')
|
||||
const Agent = require('./ProxyHttpAgent')
|
||||
const HttpsAgent = require('./ProxyHttpsAgent')
|
||||
const tunnelAgent = require('tunnel-agent')
|
||||
|
||||
const util = exports
|
||||
const httpsAgent = new HttpsAgent({
|
||||
keepAlive: true,
|
||||
timeout: 60000,
|
||||
keepAliveTimeout: 30000, // free socket keepalive for 30 seconds
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
const httpAgent = new Agent({
|
||||
keepAlive: true,
|
||||
timeout: 60000,
|
||||
keepAliveTimeout: 30000 // free socket keepalive for 30 seconds
|
||||
})
|
||||
let socketId = 0
|
||||
|
||||
let httpsOverHttpAgent, httpOverHttpsAgent, httpsOverHttpsAgent
|
||||
|
||||
util.getOptionsFormRequest = (req, ssl, externalProxy = null) => {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const urlObject = url.parse(req.url)
|
||||
const defaultPort = ssl ? 443 : 80
|
||||
const protocol = ssl ? 'https:' : 'http:'
|
||||
const headers = Object.assign({}, req.headers)
|
||||
let externalProxyUrl = null
|
||||
|
||||
if (externalProxy) {
|
||||
if (typeof externalProxy === 'string') {
|
||||
externalProxyUrl = externalProxy
|
||||
} else if (typeof externalProxy === 'function') {
|
||||
try {
|
||||
externalProxyUrl = externalProxy(req, ssl)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete headers['proxy-connection']
|
||||
let agent = false
|
||||
if (!externalProxyUrl) {
|
||||
// keepAlive
|
||||
if (headers.connection !== 'close') {
|
||||
if (protocol === 'https:') {
|
||||
agent = httpsAgent
|
||||
} else {
|
||||
agent = httpAgent
|
||||
}
|
||||
headers.connection = 'keep-alive'
|
||||
}
|
||||
} else {
|
||||
agent = util.getTunnelAgent(protocol === 'https:', externalProxyUrl)
|
||||
}
|
||||
|
||||
const options = {
|
||||
protocol: protocol,
|
||||
hostname: req.headers.host.split(':')[0],
|
||||
method: req.method,
|
||||
port: req.headers.host.split(':')[1] || defaultPort,
|
||||
path: urlObject.path,
|
||||
headers: req.headers,
|
||||
agent: agent
|
||||
}
|
||||
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
if (protocol === 'http:' && externalProxyUrl && (url.parse(externalProxyUrl)).protocol === 'http:') {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const externalURL = url.parse(externalProxyUrl)
|
||||
options.hostname = externalURL.hostname
|
||||
options.port = externalURL.port
|
||||
// support non-transparent proxy
|
||||
options.path = `http://${urlObject.host}${urlObject.path}`
|
||||
}
|
||||
|
||||
// mark a socketId for Agent to bind socket for NTLM
|
||||
if (req.socket.customSocketId) {
|
||||
options.customSocketId = req.socket.customSocketId
|
||||
} else if (headers.authorization) {
|
||||
options.customSocketId = req.socket.customSocketId = socketId++
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
util.getTunnelAgent = (requestIsSSL, externalProxyUrl) => {
|
||||
// eslint-disable-next-line node/no-deprecated-api
|
||||
const urlObject = url.parse(externalProxyUrl)
|
||||
const protocol = urlObject.protocol || 'http:'
|
||||
let port = urlObject.port
|
||||
if (!port) {
|
||||
port = protocol === 'http:' ? 80 : 443
|
||||
}
|
||||
const hostname = urlObject.hostname || 'localhost'
|
||||
|
||||
if (requestIsSSL) {
|
||||
if (protocol === 'http:') {
|
||||
if (!httpsOverHttpAgent) {
|
||||
httpsOverHttpAgent = tunnelAgent.httpsOverHttp({
|
||||
proxy: {
|
||||
host: hostname,
|
||||
port: port
|
||||
}
|
||||
})
|
||||
}
|
||||
return httpsOverHttpAgent
|
||||
} else {
|
||||
if (!httpsOverHttpsAgent) {
|
||||
httpsOverHttpsAgent = tunnelAgent.httpsOverHttps({
|
||||
proxy: {
|
||||
host: hostname,
|
||||
port: port
|
||||
}
|
||||
})
|
||||
}
|
||||
return httpsOverHttpsAgent
|
||||
}
|
||||
} else {
|
||||
if (protocol === 'http:') {
|
||||
// if (!httpOverHttpAgent) {
|
||||
// httpOverHttpAgent = tunnelAgent.httpOverHttp({
|
||||
// proxy: {
|
||||
// host: hostname,
|
||||
// port: port
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
return false
|
||||
} else {
|
||||
if (!httpOverHttpsAgent) {
|
||||
httpOverHttpsAgent = tunnelAgent.httpOverHttps({
|
||||
proxy: {
|
||||
host: hostname,
|
||||
port: port
|
||||
}
|
||||
})
|
||||
}
|
||||
return httpOverHttpsAgent
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
require('babel-polyfill')
|
||||
module.exports = require('./mitmproxy')
|
@ -0,0 +1,98 @@
|
||||
const through = require('through2')
|
||||
const zlib = require('zlib')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const url = require('url')
|
||||
|
||||
const httpUtil = {}
|
||||
httpUtil.isGzip = function (res) {
|
||||
const contentEncoding = res.headers['content-encoding']
|
||||
return !!(contentEncoding && contentEncoding.toLowerCase() === 'gzip')
|
||||
}
|
||||
httpUtil.isHtml = function (res) {
|
||||
const contentType = res.headers['content-type']
|
||||
return (typeof contentType !== 'undefined') && /text\/html|application\/xhtml\+xml/.test(contentType)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function injectContentIntoHtmlHead (html, content) {
|
||||
html = html.replace(/(<\/head>)/i, function (match) {
|
||||
return content + match
|
||||
})
|
||||
return html
|
||||
}
|
||||
function injectScriptIntoHtmlHead (html, content) {
|
||||
return html
|
||||
}
|
||||
function injectContentIntoHtmlBody (html, content) {
|
||||
html = html.replace(/(<\/body>)/i, function (match) {
|
||||
return content + match
|
||||
})
|
||||
return html
|
||||
}
|
||||
|
||||
function chunkReplace (_this, chunk, enc, callback, headContent, bodyContent) {
|
||||
let chunkString = chunk.toString()
|
||||
if (headContent) {
|
||||
chunkString = injectScriptIntoHtmlHead(chunkString, headContent)
|
||||
}
|
||||
if (bodyContent) {
|
||||
chunkString = injectContentIntoHtmlBody(chunkString, bodyContent)
|
||||
}
|
||||
_this.push(Buffer.alloc(chunkString))
|
||||
callback()
|
||||
}
|
||||
|
||||
module.exports = class InjectHtmlPlugin {
|
||||
constructor ({
|
||||
head,
|
||||
body
|
||||
}) {
|
||||
this.head = head
|
||||
this.body = body
|
||||
}
|
||||
|
||||
responseInterceptor (req, res, proxyReq, proxyRes, ssl, next) {
|
||||
if (!this.head && !this.body) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const isHtml = httpUtil.isHtml(proxyRes)
|
||||
const contentLengthIsZero = (() => {
|
||||
return proxyRes.headers['content-length'] === 0
|
||||
})()
|
||||
if (!isHtml || contentLengthIsZero) {
|
||||
next()
|
||||
} else {
|
||||
Object.keys(proxyRes.headers).forEach(function (key) {
|
||||
if (proxyRes.headers[key] !== undefined) {
|
||||
let newkey = key.replace(/^[a-z]|-[a-z]/g, (match) => {
|
||||
return match.toUpperCase()
|
||||
})
|
||||
newkey = key
|
||||
if (isHtml && key === 'content-length') {
|
||||
// do nothing
|
||||
} else {
|
||||
res.setHeader(newkey, proxyRes.headers[key])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
res.writeHead(proxyRes.statusCode)
|
||||
|
||||
const isGzip = httpUtil.isGzip(proxyRes)
|
||||
|
||||
if (isGzip) {
|
||||
proxyRes.pipe(new zlib.Gunzip())
|
||||
.pipe(through(function (chunk, enc, callback) {
|
||||
chunkReplace(this, chunk, enc, callback, this.head, this.body)
|
||||
})).pipe(new zlib.Gzip()).pipe(res)
|
||||
} else {
|
||||
proxyRes.pipe(through(function (chunk, enc, callback) {
|
||||
chunkReplace(this, chunk, enc, callback, this.head, this.body)
|
||||
})).pipe(res)
|
||||
}
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
const fs = require('fs')
|
||||
const forge = require('node-forge')
|
||||
const FakeServersCenter = require('../tls/FakeServersCenter')
|
||||
const colors = require('colors')
|
||||
|
||||
module.exports = function createFakeServerCenter ({
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
requestHandler,
|
||||
upgradeHandler,
|
||||
getCertSocketTimeout
|
||||
}) {
|
||||
let caCert
|
||||
let caKey
|
||||
try {
|
||||
fs.accessSync(caCertPath, fs.F_OK)
|
||||
fs.accessSync(caKeyPath, fs.F_OK)
|
||||
const caCertPem = fs.readFileSync(caCertPath)
|
||||
const caKeyPem = fs.readFileSync(caKeyPath)
|
||||
caCert = forge.pki.certificateFromPem(caCertPem)
|
||||
caKey = forge.pki.privateKeyFromPem(caKeyPem)
|
||||
} catch (e) {
|
||||
console.log(colors.red('Can not find `CA certificate` or `CA key`.'), e)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return new FakeServersCenter({
|
||||
caCert,
|
||||
caKey,
|
||||
maxLength: 100,
|
||||
requestHandler,
|
||||
upgradeHandler,
|
||||
getCertSocketTimeout
|
||||
})
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const commonUtil = require('../common/util')
|
||||
// const upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i
|
||||
const DnsUtil = require('../../dns/index')
|
||||
|
||||
// create requestHandler function
|
||||
module.exports = function createRequestHandler (requestInterceptor, responseInterceptor, middlewares, externalProxy, dnsConfig) {
|
||||
// return
|
||||
return function requestHandler (req, res, ssl) {
|
||||
let proxyReq
|
||||
|
||||
const rOptions = commonUtil.getOptionsFormRequest(req, ssl, externalProxy)
|
||||
|
||||
if (rOptions.headers.connection === 'close') {
|
||||
req.socket.setKeepAlive(false)
|
||||
} else if (rOptions.customSocketId != null) { // for NTLM
|
||||
req.socket.setKeepAlive(true, 60 * 60 * 1000)
|
||||
} else {
|
||||
req.socket.setKeepAlive(true, 30000)
|
||||
}
|
||||
|
||||
const requestInterceptorPromise = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const next = () => {
|
||||
resolve()
|
||||
}
|
||||
try {
|
||||
if (typeof requestInterceptor === 'function') {
|
||||
requestInterceptor(rOptions, req, res, ssl, next)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const proxyRequestPromise = async () => {
|
||||
rOptions.host = rOptions.hostname || rOptions.host || 'localhost'
|
||||
if (dnsConfig) {
|
||||
const dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.host)
|
||||
if (dns) {
|
||||
const ip = await dns.lookup(rOptions.host)
|
||||
console.log('使用自定义dns:', rOptions.host, ip, dns.dnsServer)
|
||||
rOptions.host = ip
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// use the binded socket for NTLM
|
||||
if (rOptions.agent && rOptions.customSocketId != null && rOptions.agent.getName) {
|
||||
const socketName = rOptions.agent.getName(rOptions)
|
||||
const bindingSocket = rOptions.agent.sockets[socketName]
|
||||
if (bindingSocket && bindingSocket.length > 0) {
|
||||
bindingSocket[0].once('free', onFree)
|
||||
return
|
||||
}
|
||||
}
|
||||
onFree()
|
||||
|
||||
function onFree () {
|
||||
const url = `${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
|
||||
const start = new Date().getTime()
|
||||
if (rOptions.protocol === 'https:') {
|
||||
console.log('代理请求:', url)
|
||||
}
|
||||
|
||||
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
|
||||
const end = new Date().getTime()
|
||||
if (rOptions.protocol === 'https:') {
|
||||
console.log('代理请求返回:', url, (end - start) + 'ms')
|
||||
}
|
||||
resolve(proxyRes)
|
||||
})
|
||||
|
||||
proxyReq.on('timeout', () => {
|
||||
console.error('代理请求超时', rOptions.host, rOptions.path)
|
||||
reject(new Error(`${rOptions.host}:${rOptions.port}, 代理请求超时`))
|
||||
})
|
||||
|
||||
proxyReq.on('error', (e, req, res) => {
|
||||
console.error('代理请求错误', e.errno, rOptions.host, rOptions.path)
|
||||
reject(e)
|
||||
if (res) {
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
|
||||
proxyReq.on('aborted', () => {
|
||||
console.error('代理请求被取消', rOptions.host, rOptions.path)
|
||||
reject(new Error('代理请求被取消'))
|
||||
req.abort()
|
||||
})
|
||||
|
||||
req.on('aborted', function () {
|
||||
console.error('请求被取消', rOptions.host, rOptions.path)
|
||||
proxyReq.abort()
|
||||
reject(new Error('请求被取消'))
|
||||
})
|
||||
req.on('error', function (e, req, res) {
|
||||
console.error('请求错误:', e.errno, rOptions.host, rOptions.path)
|
||||
reject(e)
|
||||
if (res) {
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
req.on('timeout', () => {
|
||||
console.error('请求超时', rOptions.host, rOptions.path)
|
||||
reject(new Error(`${rOptions.host}:${rOptions.port}, 请求超时`))
|
||||
})
|
||||
req.pipe(proxyReq)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// workflow control
|
||||
(async () => {
|
||||
await requestInterceptorPromise()
|
||||
|
||||
if (res.finished) {
|
||||
return false
|
||||
}
|
||||
|
||||
const proxyRes = await proxyRequestPromise()
|
||||
|
||||
const responseInterceptorPromise = new Promise((resolve, reject) => {
|
||||
const next = () => {
|
||||
resolve()
|
||||
}
|
||||
try {
|
||||
if (typeof responseInterceptor === 'function') {
|
||||
responseInterceptor(req, res, proxyReq, proxyRes, ssl, next)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
|
||||
await responseInterceptorPromise
|
||||
|
||||
if (res.finished) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!res.headersSent) { // prevent duplicate set headers
|
||||
Object.keys(proxyRes.headers).forEach(function (key) {
|
||||
if (proxyRes.headers[key] !== undefined) {
|
||||
// https://github.com/nodejitsu/node-http-proxy/issues/362
|
||||
if (/^www-authenticate$/i.test(key)) {
|
||||
if (proxyRes.headers[key]) {
|
||||
proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(',')
|
||||
}
|
||||
key = 'www-authenticate'
|
||||
}
|
||||
res.setHeader(key, proxyRes.headers[key])
|
||||
}
|
||||
})
|
||||
|
||||
res.writeHead(proxyRes.statusCode)
|
||||
proxyRes.pipe(res)
|
||||
}
|
||||
})().then(
|
||||
(flag) => {
|
||||
// do nothing
|
||||
},
|
||||
(e) => {
|
||||
if (!res.finished) {
|
||||
res.writeHead(500)
|
||||
res.write(`Dev-Sidecar Warning:\n\n ${e.toString()}`)
|
||||
res.end()
|
||||
}
|
||||
console.error(e)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const util = require('../common/util')
|
||||
|
||||
// copy from node-http-proxy. ^_^
|
||||
|
||||
// create connectHandler function
|
||||
module.exports = function createUpgradeHandler () {
|
||||
// return
|
||||
return function upgradeHandler (req, cltSocket, head, ssl) {
|
||||
const clientOptions = util.getOptionsFormRequest(req, ssl)
|
||||
const proxyReq = (ssl ? https : http).request(clientOptions)
|
||||
proxyReq.on('error', (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
proxyReq.on('response', function (res) {
|
||||
// if upgrade event isn't going to happen, close the socket
|
||||
if (!res.upgrade) cltSocket.end()
|
||||
})
|
||||
|
||||
proxyReq.on('upgrade', function (proxyRes, proxySocket, proxyHead) {
|
||||
proxySocket.on('error', (e) => {
|
||||
console.log('error-----1111')
|
||||
console.error(e)
|
||||
})
|
||||
|
||||
cltSocket.on('error', function () {
|
||||
console.log('error-----2222')
|
||||
proxySocket.end()
|
||||
})
|
||||
|
||||
proxySocket.setTimeout(0)
|
||||
proxySocket.setNoDelay(true)
|
||||
|
||||
proxySocket.setKeepAlive(true, 0)
|
||||
|
||||
if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead)
|
||||
|
||||
cltSocket.write(
|
||||
Object.keys(proxyRes.headers).reduce(function (head, key) {
|
||||
const value = proxyRes.headers[key]
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
head.push(key + ': ' + value)
|
||||
return head
|
||||
}
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
head.push(key + ': ' + value[i])
|
||||
}
|
||||
return head
|
||||
}, ['HTTP/1.1 101 Switching Protocols'])
|
||||
.join('\r\n') + '\r\n\r\n'
|
||||
)
|
||||
|
||||
proxySocket.pipe(cltSocket).pipe(proxySocket)
|
||||
})
|
||||
proxyReq.end()
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
const tlsUtils = require('../tls/tlsUtils')
|
||||
const http = require('http')
|
||||
const config = require('../common/config')
|
||||
const colors = require('colors')
|
||||
const createRequestHandler = require('./createRequestHandler')
|
||||
const createConnectHandler = require('./createConnectHandler')
|
||||
const createFakeServerCenter = require('./createFakeServerCenter')
|
||||
const createUpgradeHandler = require('./createUpgradeHandler')
|
||||
|
||||
module.exports = {
|
||||
createProxy ({
|
||||
port = config.defaultPort,
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
sslConnectInterceptor,
|
||||
requestInterceptor,
|
||||
responseInterceptor,
|
||||
getCertSocketTimeout = 1 * 1000,
|
||||
middlewares = [],
|
||||
externalProxy,
|
||||
dnsConfig
|
||||
}, callback) {
|
||||
// Don't reject unauthorized
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
if (!caCertPath && !caKeyPath) {
|
||||
const rs = this.createCA()
|
||||
caCertPath = rs.caCertPath
|
||||
caKeyPath = rs.caKeyPath
|
||||
if (rs.create) {
|
||||
console.log(colors.cyan(`CA Cert saved in: ${caCertPath}`))
|
||||
console.log(colors.cyan(`CA private key saved in: ${caKeyPath}`))
|
||||
}
|
||||
}
|
||||
|
||||
port = ~~port
|
||||
const requestHandler = createRequestHandler(
|
||||
requestInterceptor,
|
||||
responseInterceptor,
|
||||
middlewares,
|
||||
externalProxy,
|
||||
dnsConfig
|
||||
)
|
||||
|
||||
const upgradeHandler = createUpgradeHandler()
|
||||
|
||||
const fakeServersCenter = createFakeServerCenter({
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
requestHandler,
|
||||
upgradeHandler,
|
||||
getCertSocketTimeout
|
||||
})
|
||||
|
||||
const connectHandler = createConnectHandler(
|
||||
sslConnectInterceptor,
|
||||
fakeServersCenter,
|
||||
dnsConfig
|
||||
)
|
||||
|
||||
const server = new http.Server()
|
||||
server.listen(port, () => {
|
||||
console.log(colors.green(`dev-sidecar启动端口: ${port}`))
|
||||
server.on('error', (e) => {
|
||||
console.error(colors.red(e))
|
||||
})
|
||||
server.on('request', (req, res) => {
|
||||
const ssl = false
|
||||
requestHandler(req, res, ssl)
|
||||
})
|
||||
// tunneling for https
|
||||
server.on('connect', (req, cltSocket, head) => {
|
||||
connectHandler(req, cltSocket, head)
|
||||
})
|
||||
// TODO: handler WebSocket
|
||||
server.on('upgrade', function (req, socket, head) {
|
||||
const ssl = false
|
||||
upgradeHandler(req, socket, head, ssl)
|
||||
})
|
||||
|
||||
if (callback) {
|
||||
callback(server)
|
||||
}
|
||||
})
|
||||
return server
|
||||
},
|
||||
createCA (caBasePath = config.getDefaultCABasePath()) {
|
||||
return tlsUtils.initCA(caBasePath)
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
const _ = require('lodash')
|
||||
module.exports = (middlewares) => {
|
||||
if (middlewares) {
|
||||
if (Object.prototype.toString.call(middlewares) !== '[object Array]') {
|
||||
throw new TypeError('middlewares must be a array')
|
||||
}
|
||||
}
|
||||
//
|
||||
// const sslConnectInterceptors = []
|
||||
// const requestInterceptors = []
|
||||
// const responseInterceptors = []
|
||||
|
||||
_.each(middlewares, (m) => {
|
||||
if (m.buildIn === false || m.buildIn === 'false') {
|
||||
|
||||
} else {
|
||||
// m.name
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
const tlsUtils = require('./tlsUtils')
|
||||
const https = require('https')
|
||||
|
||||
module.exports = class CertAndKeyContainer {
|
||||
constructor ({
|
||||
maxLength = 1000,
|
||||
getCertSocketTimeout = 2 * 1000,
|
||||
caCert,
|
||||
caKey
|
||||
}) {
|
||||
this.queue = []
|
||||
this.maxLength = maxLength
|
||||
this.getCertSocketTimeout = getCertSocketTimeout
|
||||
this.caCert = caCert
|
||||
this.caKey = caKey
|
||||
}
|
||||
|
||||
addCertPromise (certPromiseObj) {
|
||||
if (this.queue.length >= this.maxLength) {
|
||||
this.queue.shift()
|
||||
}
|
||||
this.queue.push(certPromiseObj)
|
||||
return certPromiseObj
|
||||
}
|
||||
|
||||
getCertPromise (hostname, port) {
|
||||
for (let i = 0; i < this.queue.length; i++) {
|
||||
const _certPromiseObj = this.queue[i]
|
||||
const mappingHostNames = _certPromiseObj.mappingHostNames
|
||||
for (let j = 0; j < mappingHostNames.length; j++) {
|
||||
const DNSName = mappingHostNames[j]
|
||||
if (tlsUtils.isMappingHostName(DNSName, hostname)) {
|
||||
this.reRankCert(i)
|
||||
return _certPromiseObj.promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const certPromiseObj = {
|
||||
mappingHostNames: [hostname] // temporary hostname
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
let once = true
|
||||
const _resolve = (_certObj) => {
|
||||
if (once) {
|
||||
once = false
|
||||
const mappingHostNames = tlsUtils.getMappingHostNamesFormCert(_certObj.cert)
|
||||
certPromiseObj.mappingHostNames = mappingHostNames // change
|
||||
resolve(_certObj)
|
||||
}
|
||||
}
|
||||
let certObj
|
||||
const fast = true
|
||||
if (fast) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
_resolve(certObj)
|
||||
} else {
|
||||
// 这个太慢了
|
||||
const preReq = https.request({
|
||||
port: port,
|
||||
hostname: hostname,
|
||||
path: '/',
|
||||
method: 'HEAD'
|
||||
}, (preRes) => {
|
||||
try {
|
||||
const realCert = preRes.socket.getPeerCertificate()
|
||||
if (realCert) {
|
||||
try {
|
||||
certObj = tlsUtils.createFakeCertificateByCA(this.caKey, this.caCert, realCert)
|
||||
} catch (error) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
}
|
||||
} else {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
}
|
||||
_resolve(certObj)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
preReq.setTimeout(~~this.getCertSocketTimeout, () => {
|
||||
if (!certObj) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
_resolve(certObj)
|
||||
}
|
||||
})
|
||||
preReq.on('error', (e) => {
|
||||
if (!certObj) {
|
||||
certObj = tlsUtils.createFakeCertificateByDomain(this.caKey, this.caCert, hostname)
|
||||
_resolve(certObj)
|
||||
}
|
||||
})
|
||||
preReq.end()
|
||||
}
|
||||
})
|
||||
|
||||
certPromiseObj.promise = promise
|
||||
|
||||
return (this.addCertPromise(certPromiseObj)).promise
|
||||
}
|
||||
|
||||
reRankCert (index) {
|
||||
// index ==> queue foot
|
||||
this.queue.push((this.queue.splice(index, 1))[0])
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
const https = require('https')
|
||||
const tlsUtils = require('./tlsUtils')
|
||||
const CertAndKeyContainer = require('./CertAndKeyContainer')
|
||||
const forge = require('node-forge')
|
||||
const pki = forge.pki
|
||||
// const colors = require('colors')
|
||||
const tls = require('tls')
|
||||
|
||||
module.exports = class FakeServersCenter {
|
||||
constructor ({ maxLength = 256, requestHandler, upgradeHandler, caCert, caKey, getCertSocketTimeout }) {
|
||||
this.queue = []
|
||||
this.maxLength = maxLength
|
||||
this.requestHandler = requestHandler
|
||||
this.upgradeHandler = upgradeHandler
|
||||
this.certAndKeyContainer = new CertAndKeyContainer({
|
||||
getCertSocketTimeout,
|
||||
caCert,
|
||||
caKey
|
||||
})
|
||||
}
|
||||
|
||||
addServerPromise (serverPromiseObj) {
|
||||
if (this.queue.length >= this.maxLength) {
|
||||
const delServerObj = this.queue.shift()
|
||||
try {
|
||||
console.log('超过最大服务数量,删除旧服务', delServerObj)
|
||||
delServerObj.serverObj.server.close()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
console.log('add server promise:', serverPromiseObj)
|
||||
this.queue.push(serverPromiseObj)
|
||||
return serverPromiseObj
|
||||
}
|
||||
|
||||
getServerPromise (hostname, port) {
|
||||
for (let i = 0; i < this.queue.length; i++) {
|
||||
const serverPromiseObj = this.queue[i]
|
||||
const mappingHostNames = serverPromiseObj.mappingHostNames
|
||||
for (let j = 0; j < mappingHostNames.length; j++) {
|
||||
const DNSName = mappingHostNames[j]
|
||||
if (tlsUtils.isMappingHostName(DNSName, hostname)) {
|
||||
this.reRankServer(i)
|
||||
return serverPromiseObj.promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverPromiseObj = {
|
||||
mappingHostNames: [hostname] // temporary hostname
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const certObj = await this.certAndKeyContainer.getCertPromise(hostname, port)
|
||||
const cert = certObj.cert
|
||||
const key = certObj.key
|
||||
const certPem = pki.certificateToPem(cert)
|
||||
const keyPem = pki.privateKeyToPem(key)
|
||||
const fakeServer = new https.Server({
|
||||
key: keyPem,
|
||||
cert: certPem,
|
||||
SNICallback: (hostname, done) => {
|
||||
(async () => {
|
||||
const certObj = await this.certAndKeyContainer.getCertPromise(hostname, port)
|
||||
done(null, tls.createSecureContext({
|
||||
key: pki.privateKeyToPem(certObj.key),
|
||||
cert: pki.certificateToPem(certObj.cert)
|
||||
}))
|
||||
})()
|
||||
}
|
||||
})
|
||||
const serverObj = {
|
||||
cert,
|
||||
key,
|
||||
server: fakeServer,
|
||||
port: 0 // if prot === 0 ,should listen server's `listening` event.
|
||||
}
|
||||
serverPromiseObj.serverObj = serverObj
|
||||
fakeServer.listen(0, () => {
|
||||
const address = fakeServer.address()
|
||||
serverObj.port = address.port
|
||||
})
|
||||
fakeServer.on('request', (req, res) => {
|
||||
const ssl = true
|
||||
this.requestHandler(req, res, ssl)
|
||||
})
|
||||
fakeServer.on('error', (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
fakeServer.on('listening', () => {
|
||||
const mappingHostNames = tlsUtils.getMappingHostNamesFormCert(certObj.cert)
|
||||
serverPromiseObj.mappingHostNames = mappingHostNames
|
||||
resolve(serverObj)
|
||||
})
|
||||
fakeServer.on('upgrade', (req, socket, head) => {
|
||||
const ssl = true
|
||||
this.upgradeHandler(req, socket, head, ssl)
|
||||
})
|
||||
})()
|
||||
})
|
||||
|
||||
serverPromiseObj.promise = promise
|
||||
|
||||
return (this.addServerPromise(serverPromiseObj)).promise
|
||||
}
|
||||
|
||||
reRankServer (index) {
|
||||
// index ==> queue foot
|
||||
this.queue.push((this.queue.splice(index, 1))[0])
|
||||
}
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
const forge = require('node-forge')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const config = require('../common/config')
|
||||
const _ = require('lodash')
|
||||
const mkdirp = require('mkdirp')
|
||||
// const colors = require('colors')
|
||||
|
||||
const utils = exports
|
||||
const pki = forge.pki
|
||||
|
||||
utils.createCA = function (CN) {
|
||||
const keys = pki.rsa.generateKeyPair(2046)
|
||||
const cert = pki.createCertificate()
|
||||
cert.publicKey = keys.publicKey
|
||||
cert.serialNumber = (new Date()).getTime() + ''
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 5)
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20)
|
||||
const attrs = [{
|
||||
name: 'commonName',
|
||||
value: CN
|
||||
}, {
|
||||
name: 'countryName',
|
||||
value: 'CN'
|
||||
}, {
|
||||
shortName: 'ST',
|
||||
value: 'GuangDong'
|
||||
}, {
|
||||
name: 'localityName',
|
||||
value: 'ShenZhen'
|
||||
}, {
|
||||
name: 'organizationName',
|
||||
value: 'dev-sidecar'
|
||||
}, {
|
||||
shortName: 'OU',
|
||||
value: 'https://github.com/docmirror/dev-sidecar'
|
||||
}]
|
||||
cert.setSubject(attrs)
|
||||
cert.setIssuer(attrs)
|
||||
cert.setExtensions([{
|
||||
name: 'basicConstraints',
|
||||
critical: true,
|
||||
cA: true
|
||||
}, {
|
||||
name: 'keyUsage',
|
||||
critical: true,
|
||||
keyCertSign: true
|
||||
}, {
|
||||
name: 'subjectKeyIdentifier'
|
||||
}])
|
||||
|
||||
// self-sign certificate
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create())
|
||||
|
||||
return {
|
||||
key: keys.privateKey,
|
||||
cert: cert
|
||||
}
|
||||
}
|
||||
|
||||
utils.covertNodeCertToForgeCert = function (originCertificate) {
|
||||
const obj = forge.asn1.fromDer(originCertificate.raw.toString('binary'))
|
||||
return forge.pki.certificateFromAsn1(obj)
|
||||
}
|
||||
|
||||
utils.createFakeCertificateByDomain = function (caKey, caCert, domain) {
|
||||
const keys = pki.rsa.generateKeyPair(2046)
|
||||
const cert = pki.createCertificate()
|
||||
cert.publicKey = keys.publicKey
|
||||
|
||||
cert.serialNumber = (new Date()).getTime() + ''
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1)
|
||||
const attrs = [{
|
||||
name: 'commonName',
|
||||
value: domain
|
||||
}, {
|
||||
name: 'countryName',
|
||||
value: 'CN'
|
||||
}, {
|
||||
shortName: 'ST',
|
||||
value: 'GuangDong'
|
||||
}, {
|
||||
name: 'localityName',
|
||||
value: 'ShengZhen'
|
||||
}, {
|
||||
name: 'organizationName',
|
||||
value: 'dev-sidecar'
|
||||
}, {
|
||||
shortName: 'OU',
|
||||
value: 'https://github.com/docmirror/dev-sidecar'
|
||||
}]
|
||||
|
||||
cert.setIssuer(caCert.subject.attributes)
|
||||
cert.setSubject(attrs)
|
||||
|
||||
cert.setExtensions([{
|
||||
name: 'basicConstraints',
|
||||
critical: true,
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
critical: true,
|
||||
digitalSignature: true,
|
||||
contentCommitment: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true,
|
||||
keyAgreement: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true,
|
||||
encipherOnly: true,
|
||||
decipherOnly: true
|
||||
},
|
||||
{
|
||||
name: 'subjectAltName',
|
||||
altNames: [{
|
||||
type: 2,
|
||||
value: domain
|
||||
}]
|
||||
},
|
||||
{
|
||||
name: 'subjectKeyIdentifier'
|
||||
},
|
||||
{
|
||||
name: 'extKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true,
|
||||
codeSigning: true,
|
||||
emailProtection: true,
|
||||
timeStamping: true
|
||||
},
|
||||
{
|
||||
name: 'authorityKeyIdentifier'
|
||||
}])
|
||||
cert.sign(caKey, forge.md.sha256.create())
|
||||
|
||||
return {
|
||||
key: keys.privateKey,
|
||||
cert: cert
|
||||
}
|
||||
}
|
||||
|
||||
utils.createFakeCertificateByCA = function (caKey, caCert, originCertificate) {
|
||||
const certificate = utils.covertNodeCertToForgeCert(originCertificate)
|
||||
|
||||
const keys = pki.rsa.generateKeyPair(2046)
|
||||
const cert = pki.createCertificate()
|
||||
cert.publicKey = keys.publicKey
|
||||
|
||||
cert.serialNumber = certificate.serialNumber
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1)
|
||||
|
||||
cert.setSubject(certificate.subject.attributes)
|
||||
cert.setIssuer(caCert.subject.attributes)
|
||||
|
||||
certificate.subjectaltname && (cert.subjectaltname = certificate.subjectaltname)
|
||||
|
||||
const subjectAltName = _.find(certificate.extensions, { name: 'subjectAltName' })
|
||||
cert.setExtensions([{
|
||||
name: 'basicConstraints',
|
||||
critical: true,
|
||||
cA: false
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
critical: true,
|
||||
digitalSignature: true,
|
||||
contentCommitment: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true,
|
||||
keyAgreement: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true,
|
||||
encipherOnly: true,
|
||||
decipherOnly: true
|
||||
},
|
||||
{
|
||||
name: 'subjectAltName',
|
||||
altNames: subjectAltName.altNames
|
||||
},
|
||||
{
|
||||
name: 'subjectKeyIdentifier'
|
||||
},
|
||||
{
|
||||
name: 'extKeyUsage',
|
||||
serverAuth: true,
|
||||
clientAuth: true,
|
||||
codeSigning: true,
|
||||
emailProtection: true,
|
||||
timeStamping: true
|
||||
},
|
||||
{
|
||||
name: 'authorityKeyIdentifier'
|
||||
}])
|
||||
cert.sign(caKey, forge.md.sha256.create())
|
||||
|
||||
return {
|
||||
key: keys.privateKey,
|
||||
cert: cert
|
||||
}
|
||||
}
|
||||
|
||||
utils.isBrowserRequest = function (userAgent) {
|
||||
return /Mozilla/i.test(userAgent)
|
||||
}
|
||||
//
|
||||
// /^[^.]+\.a\.com$/.test('c.a.com')
|
||||
//
|
||||
utils.isMappingHostName = function (DNSName, hostname) {
|
||||
let reg = DNSName.replace(/\./g, '\\.').replace(/\*/g, '[^.]+')
|
||||
reg = '^' + reg + '$'
|
||||
return (new RegExp(reg)).test(hostname)
|
||||
}
|
||||
|
||||
utils.getMappingHostNamesFormCert = function (cert) {
|
||||
let mappingHostNames = []
|
||||
mappingHostNames.push(cert.subject.getField('CN') ? cert.subject.getField('CN').value : '')
|
||||
const altNames = cert.getExtension('subjectAltName') ? cert.getExtension('subjectAltName').altNames : []
|
||||
mappingHostNames = mappingHostNames.concat(_.map(altNames, 'value'))
|
||||
return mappingHostNames
|
||||
}
|
||||
|
||||
// sync
|
||||
utils.initCA = function (basePath = config.getDefaultCABasePath()) {
|
||||
const caCertPath = path.resolve(basePath, config.caCertFileName)
|
||||
const caKeyPath = path.resolve(basePath, config.caKeyFileName)
|
||||
|
||||
try {
|
||||
fs.accessSync(caCertPath, fs.F_OK)
|
||||
fs.accessSync(caKeyPath, fs.F_OK)
|
||||
|
||||
// has exist
|
||||
return {
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
create: false
|
||||
}
|
||||
} catch (e) {
|
||||
const caObj = utils.createCA(config.caName)
|
||||
|
||||
const caCert = caObj.cert
|
||||
const cakey = caObj.key
|
||||
|
||||
const certPem = pki.certificateToPem(caCert)
|
||||
const keyPem = pki.privateKeyToPem(cakey)
|
||||
|
||||
mkdirp.sync(path.dirname(caCertPath))
|
||||
fs.writeFileSync(caCertPath, certPem)
|
||||
fs.writeFileSync(caKeyPath, keyPem)
|
||||
}
|
||||
return {
|
||||
caCertPath,
|
||||
caKeyPath,
|
||||
create: true
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
const cmd = require('node-cmd')
|
||||
const util = require('util')
|
||||
const winExec = util.promisify(cmd.get, { multiArgs: true, context: cmd })
|
||||
const os = require('os')
|
||||
|
||||
class SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
throw new Error('You have to implement the method setProxy!')
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
throw new Error('You have to implement the method unsetProxy!')
|
||||
}
|
||||
}
|
||||
|
||||
class DarwinSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
class LinuxSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
|
||||
class WindowsSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
let ret = await winExec(`yarn config set proxy=http://${ip}:${port}`)
|
||||
console.log('yarn http proxy set success', ret)
|
||||
|
||||
ret = await winExec(`yarn config set https-proxy=http://${ip}:${port}`)
|
||||
console.log('yarn https proxy set success', ret)
|
||||
|
||||
// ret = await winExec(`yarn config set cafile ${config.getDefaultCAKeyPath()}`)
|
||||
// console.log('yarn cafile set success', ret)
|
||||
|
||||
ret = await winExec('yarn config set strict-ssl false')
|
||||
console.log('yarn strict-ssl false success', ret)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
await winExec('yarn config delete proxy')
|
||||
console.log('yarn https proxy unset success')
|
||||
await winExec('yarn config delete https-proxy')
|
||||
console.log('yarn https proxy unset success')
|
||||
|
||||
// await winExec(`yarn config delete cafile`)
|
||||
// console.log('yarn cafile unset success')
|
||||
await winExec(' yarn config delete strict-ssl')
|
||||
console.log('yarn strict-ssl true success')
|
||||
}
|
||||
|
||||
static _asyncRegSet (regKey, name, type, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
regKey.set(name, type, value, e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemProxy () {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return DarwinSystemProxy
|
||||
case 'linux':
|
||||
return LinuxSystemProxy
|
||||
case 'win32':
|
||||
case 'win64':
|
||||
return WindowsSystemProxy
|
||||
case 'unknown os':
|
||||
default:
|
||||
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async setProxy (ip, port) {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.setProxy(ip, port)
|
||||
},
|
||||
async unsetProxy () {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.unsetProxy()
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
const debug = require('debug')
|
||||
|
||||
module.exports = function getLogger (name) {
|
||||
return {
|
||||
debug: debug(`dev-sidecar:${name}:debug`),
|
||||
info: debug(`dev-sidecar:${name}:info`),
|
||||
error: debug(`dev-sidecar:${name}:error`)
|
||||
}
|
||||
}
|
||||
|
||||
debug.enable('dev-sidecar:*')
|
@ -0,0 +1,33 @@
|
||||
const ProxyOptions = require('./options')
|
||||
const mitmproxy = require('../lib/proxy')
|
||||
const getLogger = require('../lib/utils/logger')
|
||||
const logger = getLogger('proxy')
|
||||
const config = require('../config')
|
||||
const event = require('../event')
|
||||
let server
|
||||
module.exports = {
|
||||
async start (newConfig) {
|
||||
config.set(newConfig)
|
||||
const proxyOptions = ProxyOptions(config.get())
|
||||
server = mitmproxy.createProxy(proxyOptions, () => {
|
||||
event.fire('status', { key: 'server', value: true })
|
||||
server.on('close', () => {
|
||||
event.fire('status', { key: 'server', value: false })
|
||||
})
|
||||
})
|
||||
server.config = config.get()
|
||||
return server.config
|
||||
},
|
||||
close () {
|
||||
try {
|
||||
if (server) {
|
||||
server.close()
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
}
|
||||
},
|
||||
getServer () {
|
||||
return server
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
const getLogger = require('../lib/utils/logger')
|
||||
const logger = getLogger('proxy')
|
||||
const interceptors = require('../lib/interceptor')
|
||||
const dnsUtil = require('../lib/dns')
|
||||
function matchHostname (intercepts, hostname) {
|
||||
const interceptOpts = intercepts[hostname]
|
||||
if (interceptOpts) {
|
||||
return interceptOpts
|
||||
}
|
||||
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
|
||||
for (const target in intercepts) {
|
||||
if (target.indexOf('*') < 0) {
|
||||
continue
|
||||
}
|
||||
// 正则表达式匹配
|
||||
const regexp = target.replace('.', '\\.').replace('*', '.*')
|
||||
if (hostname.match(regexp)) {
|
||||
return intercepts[target]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isMatched (url, regexp) {
|
||||
return url.match(regexp)
|
||||
}
|
||||
|
||||
module.exports = (config) => {
|
||||
console.log('config', config)
|
||||
return {
|
||||
port: config.server.port,
|
||||
dnsConfig: {
|
||||
providers: dnsUtil.initDNS(config.dns.providers), mapping: config.dns.mapping
|
||||
},
|
||||
sslConnectInterceptor: (req, cltSocket, head) => {
|
||||
const hostname = req.url.split(':')[0]
|
||||
return !!matchHostname(config.intercepts, hostname) // 配置了拦截的域名,将会被代理
|
||||
},
|
||||
requestInterceptor: (rOptions, req, res, ssl, next) => {
|
||||
const hostname = rOptions.hostname
|
||||
const interceptOpts = matchHostname(config.intercepts, hostname)
|
||||
if (!interceptOpts) { // 该域名没有配置拦截器,直接过
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
for (const interceptOpt of interceptOpts) { // 遍历拦截配置
|
||||
let regexpList
|
||||
if (interceptOpt.regexp instanceof Array) {
|
||||
regexpList = interceptOpt.regexp
|
||||
} else {
|
||||
regexpList = [interceptOpt.regexp]
|
||||
}
|
||||
|
||||
for (const regexp of regexpList) { // 遍历regexp配置
|
||||
if (!isMatched(req.url, regexp)) {
|
||||
continue
|
||||
}
|
||||
for (const interceptImpl of interceptors) {
|
||||
// 根据拦截配置挑选合适的拦截器来处理
|
||||
if (!interceptImpl.is(interceptOpt) && interceptImpl.requestInterceptor) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const result = interceptImpl.requestInterceptor(interceptOpt, rOptions, req, res, ssl)
|
||||
if (result) { // 拦截成功,其他拦截器就不处理了
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
// 拦截失败
|
||||
logger.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next()
|
||||
},
|
||||
responseInterceptor: (req, res, proxyReq, proxyRes, ssl, next) => {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
const event = require('./event')
|
||||
const lodash = require('lodash')
|
||||
const status = {
|
||||
server: false,
|
||||
proxy: {
|
||||
system: false,
|
||||
npm: false,
|
||||
git: false
|
||||
}
|
||||
}
|
||||
|
||||
event.register('status', (event) => {
|
||||
lodash.set(status, event.key, event.value)
|
||||
console.log('status changed:', event)
|
||||
}, -999)
|
||||
module.exports = status
|
@ -0,0 +1,87 @@
|
||||
const cmd = require('node-cmd')
|
||||
const util = require('util')
|
||||
const winExec = util.promisify(cmd.get, { multiArgs: true, context: cmd })
|
||||
const os = require('os')
|
||||
|
||||
class SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
throw new Error('You have to implement the method setProxy!')
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
throw new Error('You have to implement the method unsetProxy!')
|
||||
}
|
||||
}
|
||||
|
||||
class DarwinSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
class LinuxSystemProxy extends SystemProxy {
|
||||
|
||||
}
|
||||
|
||||
class WindowsSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
let ret = await winExec(`npm config set proxy=http://${ip}:${port}`)
|
||||
console.log('npm http proxy set success', ret)
|
||||
|
||||
ret = await winExec(`npm config set https-proxy=http://${ip}:${port}`)
|
||||
console.log('npm https proxy set success', ret)
|
||||
|
||||
// ret = await winExec(`npm config set cafile ${config.getDefaultCAKeyPath()}`)
|
||||
// console.log('npm cafile set success', ret)
|
||||
|
||||
ret = await winExec('npm config set strict-ssl false')
|
||||
console.log('npm strict-ssl false success', ret)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
await winExec('npm config delete proxy')
|
||||
console.log('npm https proxy unset success')
|
||||
await winExec('npm config delete https-proxy')
|
||||
console.log('npm https proxy unset success')
|
||||
|
||||
// await winExec(`npm config delete cafile`)
|
||||
// console.log('npm cafile unset success')
|
||||
await winExec(' npm config delete strict-ssl')
|
||||
console.log('npm strict-ssl true success')
|
||||
}
|
||||
|
||||
static _asyncRegSet (regKey, name, type, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
regKey.set(name, type, value, e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemProxy () {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return DarwinSystemProxy
|
||||
case 'linux':
|
||||
return LinuxSystemProxy
|
||||
case 'win32':
|
||||
case 'win64':
|
||||
return WindowsSystemProxy
|
||||
case 'unknown os':
|
||||
default:
|
||||
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async setProxy (ip, port) {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.setProxy(ip, port)
|
||||
},
|
||||
async unsetProxy () {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.unsetProxy()
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
const script = `
|
||||
$signature = @'
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
|
||||
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
|
||||
'@
|
||||
|
||||
$INTERNET_OPTION_SETTINGS_CHANGED = 39
|
||||
$INTERNET_OPTION_REFRESH = 37
|
||||
$type = Add-Type -MemberDefinition $signature -Name wininet -Namespace pinvoke -PassThru
|
||||
$a = $type::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
|
||||
$b = $type::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
|
||||
$a -and $b
|
||||
`
|
||||
module.exports = script
|
@ -0,0 +1,61 @@
|
||||
const script = `
|
||||
Function Set-InternetProxy
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
|
||||
[String[]]$Proxy,
|
||||
|
||||
[Parameter(Mandatory=$False,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
|
||||
[AllowEmptyString()]
|
||||
[String[]]$acs
|
||||
|
||||
)
|
||||
|
||||
Begin
|
||||
{
|
||||
|
||||
$regKey="HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
|
||||
|
||||
}
|
||||
|
||||
Process
|
||||
{
|
||||
|
||||
Set-ItemProperty -path $regKey ProxyEnable -value 1
|
||||
|
||||
Set-ItemProperty -path $regKey ProxyServer -value $proxy
|
||||
|
||||
if($acs)
|
||||
{
|
||||
|
||||
Set-ItemProperty -path $regKey AutoConfigURL -Value $acs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
End
|
||||
{
|
||||
|
||||
Write-Output "Proxy is now enabled"
|
||||
|
||||
Write-Output "Proxy Server : $proxy"
|
||||
|
||||
if ($acs)
|
||||
{
|
||||
|
||||
Write-Output "Automatic Configuration Script : $acs"
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Write-Output "Automatic Configuration Script : Not Defined"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
module.exports = script
|
@ -0,0 +1,196 @@
|
||||
const util = require('util')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const childProcess = require('child_process')
|
||||
const _exec = childProcess.exec
|
||||
const spawn = childProcess.spawn
|
||||
const Registry = require('winreg')
|
||||
// const cmd = require('node-cmd')
|
||||
console.log('childProcess', childProcess)
|
||||
const exec = util.promisify(_exec)
|
||||
const setproxyPs = require('./set-internet-proxy')
|
||||
const refreshInternetPs = require('./refresh-internet')
|
||||
const Shell = require('node-powershell')
|
||||
|
||||
const _lanIP = [
|
||||
'localhost',
|
||||
'127.*',
|
||||
'10.*',
|
||||
'172.16.*',
|
||||
'172.17.*',
|
||||
'172.18.*',
|
||||
'172.19.*',
|
||||
'172.20.*',
|
||||
'172.21.*',
|
||||
'172.22.*',
|
||||
'172.23.*',
|
||||
'172.24.*',
|
||||
'172.25.*',
|
||||
'172.26.*',
|
||||
'172.27.*',
|
||||
'172.28.*',
|
||||
'172.29.*',
|
||||
'172.30.*',
|
||||
'172.31.*',
|
||||
'192.168.*',
|
||||
'<local>'
|
||||
]
|
||||
|
||||
class SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
throw new Error('You have to implement the method setProxy!')
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
throw new Error('You have to implement the method unsetProxy!')
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add path http_proxy and https_proxy
|
||||
// TODO: Support for non-gnome
|
||||
class LinuxSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
await exec('gsettings set org.gnome.system.proxy mode manual')
|
||||
await exec(`gsettings set org.gnome.system.proxy.http host ${ip}`)
|
||||
await exec(`gsettings set org.gnome.system.proxy.http port ${port}`)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
await exec('gsettings set org.gnome.system.proxy mode none')
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Support for lan connections too
|
||||
// TODO: move scripts to ../scripts/darwin
|
||||
class DarwinSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
const wifiAdaptor = (await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 | cut -d \' \' -f2"')).stdout.trim()
|
||||
|
||||
await exec(`networksetup -setwebproxy '${wifiAdaptor}' ${ip} ${port}`)
|
||||
await exec(`networksetup -setsecurewebproxy '${wifiAdaptor}' ${ip} ${port}`)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
const wifiAdaptor = (await exec('sh -c "networksetup -listnetworkserviceorder | grep `route -n get 0.0.0.0 | grep \'interface\' | cut -d \':\' -f2` -B 1 | head -n 1 | cut -d \' \' -f2"')).stdout.trim()
|
||||
|
||||
await exec(`networksetup -setwebproxystate '${wifiAdaptor}' off`)
|
||||
await exec(`networksetup -setsecurewebproxystate '${wifiAdaptor}' off`)
|
||||
}
|
||||
}
|
||||
|
||||
class WindowsSystemProxy extends SystemProxy {
|
||||
static async setProxy (ip, port) {
|
||||
const regKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
|
||||
})
|
||||
|
||||
let lanIpStr = ''
|
||||
for (const string of _lanIP) {
|
||||
lanIpStr += string + ';'
|
||||
}
|
||||
console.log('lanIps:', lanIpStr, ip, port)
|
||||
await Promise.all([
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'MigrateProxy', Registry.REG_DWORD, 1),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyEnable', Registry.REG_DWORD, 1),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyHttp1.1', Registry.REG_DWORD, 0),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyServer', Registry.REG_SZ, `${ip}:${port}`),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyOverride', Registry.REG_SZ, lanIpStr)
|
||||
])
|
||||
await WindowsSystemProxy._resetWininetProxySettings('echo refreshing') // 要执行以下这个才能生效
|
||||
await WindowsSystemProxy._resetWininetProxySettings(refreshInternetPs)
|
||||
}
|
||||
|
||||
static async unsetProxy () {
|
||||
const regKey = new Registry({
|
||||
hive: Registry.HKCU,
|
||||
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyEnable', Registry.REG_DWORD, 0),
|
||||
WindowsSystemProxy._asyncRegSet(regKey, 'ProxyServer', Registry.REG_SZ, '')
|
||||
])
|
||||
await WindowsSystemProxy._resetWininetProxySettings(refreshInternetPs)
|
||||
}
|
||||
|
||||
static _asyncRegSet (regKey, name, type, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
regKey.set(name, type, value, e => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static _resetWininetProxySettings (script) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ps = new Shell({
|
||||
executionPolicy: 'Bypass',
|
||||
noProfile: true
|
||||
})
|
||||
// ps.addCommand(setproxyPs)
|
||||
// ps.addCommand(`Set-InternetProxy -Proxy "${ip}:${port}"`)
|
||||
|
||||
ps.addCommand(script)
|
||||
|
||||
ps.invoke()
|
||||
.then(output => {
|
||||
console.log(output)
|
||||
resolve()
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
reject(err)
|
||||
})
|
||||
|
||||
// const scriptPath = path.join(__dirname, '..', 'scripts', 'windows', 'wininet-reset-settings.ps1')
|
||||
// const child = spawn('powershell.exe', [scriptPath])
|
||||
// child.stdout.setEncoding('utf8')
|
||||
// child.stdout.on('data', (data) => {
|
||||
// console.log('data', data)
|
||||
// if (data.includes('True')) {
|
||||
// resolve()
|
||||
// } else {
|
||||
// reject(data)
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// child.stderr.on('data', (err) => {
|
||||
// console.log('data', err)
|
||||
// reject(err)
|
||||
// })
|
||||
//
|
||||
// child.stdin.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemProxy () {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return DarwinSystemProxy
|
||||
case 'linux':
|
||||
return LinuxSystemProxy
|
||||
case 'win32':
|
||||
case 'win64':
|
||||
return WindowsSystemProxy
|
||||
case 'unknown os':
|
||||
default:
|
||||
throw new Error(`UNKNOWN OS TYPE ${os.platform()}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
async setProxy (ip, port) {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.setProxy(ip, port)
|
||||
},
|
||||
async unsetProxy () {
|
||||
const systemProxy = getSystemProxy()
|
||||
await systemProxy.unsetProxy()
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
const systemProxy = require('./impl/system-proxy')
|
||||
const npmProxy = require('./impl/npm-proxy')
|
||||
const event = require('../../event')
|
||||
const config = require('../../config')
|
||||
function createProxyApi (type, impl) {
|
||||
return {
|
||||
async open (conf = { ip: '127.0.0.1', port: config.get().server.port }) {
|
||||
try {
|
||||
const { ip, port } = conf
|
||||
await impl.setProxy(ip, port)
|
||||
event.fire('status', { key: 'proxy.' + type, value: true })
|
||||
console.info(`开启【${type}】代理成功`)
|
||||
} catch (e) {
|
||||
console.error(`开启【${type}】代理失败`, e)
|
||||
}
|
||||
},
|
||||
async close () {
|
||||
try {
|
||||
await impl.unsetProxy()
|
||||
event.fire('status', { key: 'proxy.' + type, value: false })
|
||||
console.info(`关闭【${type}】代理成功`)
|
||||
} catch (e) {
|
||||
console.error(`关闭【${type}】代理失败`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
system: createProxyApi('system', systemProxy),
|
||||
npm: createProxyApi('npm', npmProxy)
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
$signature = @'
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]
|
||||
public static extern bool InternetSetOption(IntPtr hInternet, int
|
||||
dwOption, IntPtr lpBuffer, int dwBufferLength);
|
||||
'@
|
||||
|
||||
$interopHelper = Add-Type -MemberDefinition $signature -Name MyInteropHelper -PassThru
|
||||
$INTERNET_OPTION_SETTINGS_CHANGED = 39
|
||||
$INTERNET_OPTION_REFRESH = 37
|
||||
|
||||
$result1 = $interopHelper::InternetSetOption(0, $INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
|
||||
$result2 = $interopHelper::InternetSetOption(0, $INTERNET_OPTION_REFRESH, 0, 0)
|
||||
|
||||
$result1 -and $result2
|
@ -0,0 +1,12 @@
|
||||
const DevSidercar = require('.')
|
||||
require('json5/lib/register')
|
||||
const config = require('../../config/index.json5')
|
||||
// 启动服务
|
||||
DevSidercar.api.startup(config)
|
||||
async function onClose () {
|
||||
console.log('on sigint ')
|
||||
await DevSidercar.api.shutdown()
|
||||
console.log('on closed ')
|
||||
process.exit(0)
|
||||
}
|
||||
process.on('SIGINT', onClose)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
@ -0,0 +1,26 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
#Electron-builder output
|
||||
/dist_electron
|
@ -0,0 +1,24 @@
|
||||
# dev-sidecar-gui
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "dev-sidecar-gui",
|
||||
"version": "1.0.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron": "vue-cli-service electron:serve",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^1.6.5",
|
||||
"core-js": "^3.6.5",
|
||||
"lodash": "^4.17.20",
|
||||
"vue": "^2.6.11",
|
||||
"vue-json-editor": "^1.4.2",
|
||||
"dev-sidecar": "1.0.0",
|
||||
"json5": "^2.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"electron": "^10.1.3",
|
||||
"electron-devtools-installer": "^3.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"json5-loader": "^4.0.1",
|
||||
"vue-cli-plugin-electron-builder": "^2.0.0-rc.4",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 6.7 KiB |
@ -0,0 +1,143 @@
|
||||
'use strict'
|
||||
import path from 'path'
|
||||
import { app, protocol, BrowserWindow, Menu, Tray } from 'electron'
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
import bridge from './bridge/index'
|
||||
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let win
|
||||
|
||||
let forceClose = false
|
||||
|
||||
// Scheme must be registered before the app is ready
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'app', privileges: { secure: true, standard: true } }
|
||||
])
|
||||
|
||||
// 隐藏主窗口,并创建托盘,绑定关闭事件
|
||||
function setTray (app) {
|
||||
// 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区
|
||||
// 通常被添加到一个 context menu 上.
|
||||
// 系统托盘右键菜单
|
||||
const trayMenuTemplate = [
|
||||
{
|
||||
// 系统托盘图标目录
|
||||
label: '退出',
|
||||
click: () => {
|
||||
forceClose = true
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
]
|
||||
// 设置系统托盘图标
|
||||
const iconPath = path.join(__dirname, '../public/favicon.ico')
|
||||
|
||||
const appTray = new Tray(iconPath)
|
||||
|
||||
// 图标的上下文菜单
|
||||
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
|
||||
|
||||
// 设置托盘悬浮提示
|
||||
appTray.setToolTip('DevSidecar-开发者边车辅助工具')
|
||||
|
||||
// 设置托盘菜单
|
||||
appTray.setContextMenu(contextMenu)
|
||||
|
||||
// 单击托盘小图标显示应用
|
||||
appTray.on('click', () => {
|
||||
// 显示主程序
|
||||
win.show()
|
||||
})
|
||||
return appTray
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
enableRemoteModule: true,
|
||||
// preload: path.join(__dirname, 'preload.js'),
|
||||
// Use pluginOptions.nodeIntegration, leave this alone
|
||||
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
|
||||
nodeIntegration: true// process.env.ELECTRON_NODE_INTEGRATION
|
||||
}
|
||||
})
|
||||
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
|
||||
if (!process.env.IS_TEST) win.webContents.openDevTools()
|
||||
} else {
|
||||
createProtocol('app')
|
||||
// Load the index.html when not in development
|
||||
win.loadURL('app://./index.html')
|
||||
}
|
||||
|
||||
win.on('closed', (e) => {
|
||||
win = null
|
||||
|
||||
bridge.devSidecar.expose.api.shutdown()
|
||||
})
|
||||
|
||||
win.on('close', (e) => {
|
||||
if (!forceClose) {
|
||||
e.preventDefault()
|
||||
win.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', async () => {
|
||||
if (isDevelopment && !process.env.IS_TEST) {
|
||||
// Install Vue Devtools
|
||||
// try {
|
||||
// await installExtension(VUEJS_DEVTOOLS)
|
||||
// } catch (e) {
|
||||
// console.error('Vue Devtools failed to install:', e.toString())
|
||||
// }
|
||||
}
|
||||
createWindow()
|
||||
// 最小化到托盘
|
||||
setTray(app)
|
||||
bridge.init(win)
|
||||
})
|
||||
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', (data) => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
import config from '../../../config/index.json5'
|
||||
export default config
|
@ -0,0 +1,11 @@
|
||||
import Vue from 'vue'
|
||||
import App from './view/components/App.vue'
|
||||
import antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
import './view'
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(antd)
|
||||
|
||||
new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
@ -0,0 +1 @@
|
||||
window.ipcRenderer = require('electron').ipcRenderer
|
@ -0,0 +1,36 @@
|
||||
import lodash from 'lodash'
|
||||
import { ipcRenderer } from 'electron'
|
||||
const doInvoke = (api, args) => {
|
||||
return ipcRenderer.invoke('apiInvoke', [api, args])
|
||||
}
|
||||
|
||||
const bindApi = (api, param1) => {
|
||||
lodash.set(apiObj, api, (param2) => {
|
||||
return doInvoke(api, param2 || param1)
|
||||
})
|
||||
}
|
||||
const apiObj = {
|
||||
on (channel, callback) {
|
||||
ipcRenderer.on(channel, callback)
|
||||
},
|
||||
doInvoke
|
||||
}
|
||||
|
||||
bindApi('startup')
|
||||
bindApi('shutdown')
|
||||
|
||||
bindApi('config.set')
|
||||
bindApi('config.get')
|
||||
bindApi('config.save')
|
||||
bindApi('config.reload')
|
||||
|
||||
bindApi('server.start')
|
||||
bindApi('server.close')
|
||||
|
||||
bindApi('proxy.system.open')
|
||||
bindApi('proxy.system.close')
|
||||
|
||||
bindApi('proxy.npm.open')
|
||||
bindApi('proxy.npm.close')
|
||||
|
||||
export default apiObj
|
@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<template>
|
||||
<div style="margin:auto">
|
||||
<div style="text-align: center"><img height="80px" src="/logo.svg"></div>
|
||||
<a-card title="DevSidecar-开发者辅助工具 " style="width: 500px;margin:auto">
|
||||
<div style="display: flex; align-items:center;justify-content:space-around;flex-direction: row">
|
||||
<div style="text-align: center">
|
||||
<div class="big_button" >
|
||||
<a-button shape="circle" icon="poweroff" :type="startup.type()" :loading="startup.loading" @click="startup.doClick" ></a-button>
|
||||
<div style="margin-top: 10px">{{status.server?'已开启':'已关闭'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :span="12">
|
||||
<a-form style="margin-top:20px" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }" >
|
||||
<a-form-item label="代理服务">
|
||||
<a-switch :loading="server.loading" v-model="status.server" default-checked v-on:click="server.doClick">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-for=" (item, key) in proxy" :key="key" :label="_lang(key,langSetting.proxy) ">
|
||||
<a-switch :loading="item.loading" v-model="status.proxy[key]" default-checked v-on:click="item.doClick">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span slot="extra" >
|
||||
<a-button v-if="config" @click="openSettings" icon="setting" ></a-button>
|
||||
</span>
|
||||
</a-card>
|
||||
|
||||
<settings v-if="config" title="设置" :config="config" :visible.sync="settings.visible" @change="onConfigChanged"></settings>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '../api'
|
||||
import status from '../status'
|
||||
import lodash from 'lodash'
|
||||
import Settings from './settings'
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Settings
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
langSetting: {
|
||||
proxy: {
|
||||
system: '系统代理',
|
||||
npm: 'npm代理',
|
||||
yarn: 'yarn代理'
|
||||
}
|
||||
},
|
||||
status: status,
|
||||
startup: {
|
||||
loading: false,
|
||||
type: () => {
|
||||
return this.status.server ? 'primary' : 'default'
|
||||
},
|
||||
doClick: () => {
|
||||
if (this.status.server) {
|
||||
this.apiCall(this.startup, api.shutdown)
|
||||
} else {
|
||||
this.apiCall(this.startup, api.startup)
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
key: '代理服务',
|
||||
loading: false,
|
||||
doClick: (checked) => {
|
||||
this.onSwitchClick(this.server, api.server.start, api.server.close, checked)
|
||||
}
|
||||
},
|
||||
proxy: undefined,
|
||||
config: undefined,
|
||||
settings: {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_intercepts () {
|
||||
return this.config.intercepts
|
||||
}
|
||||
},
|
||||
created () {
|
||||
api.config.set().then(() => {
|
||||
return api.config.get().then(ret => {
|
||||
this.config = ret
|
||||
this.start(true)
|
||||
})
|
||||
}).then(() => {
|
||||
this.proxy = this.createProxyBtns()
|
||||
console.log('proxy', this.proxy)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
_lang (key, parent) {
|
||||
const label = parent ? lodash.get(parent, key) : lodash.get(this.langSetting, key)
|
||||
if (label) {
|
||||
return label
|
||||
}
|
||||
return key
|
||||
},
|
||||
createProxyBtns () {
|
||||
const btns = {}
|
||||
console.log('api.proxy', api.proxy, api)
|
||||
for (const type in api.proxy) {
|
||||
btns[type] = {
|
||||
loading: false,
|
||||
key: type,
|
||||
doClick: (checked) => {
|
||||
this.onSwitchClick(this.proxy[type], api.proxy[type].open, api.proxy[type].close, checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
return btns
|
||||
},
|
||||
async apiCall (btn, api, param) {
|
||||
btn.loading = true
|
||||
try {
|
||||
const ret = await api(param)
|
||||
return ret
|
||||
} catch (err) {
|
||||
console.log('api invoke error:', err)
|
||||
} finally {
|
||||
btn.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
onSwitchClick (btn, openApi, closeApi, checked) {
|
||||
if (checked) {
|
||||
this.apiCall(btn, openApi)
|
||||
} else {
|
||||
this.apiCall(btn, closeApi)
|
||||
}
|
||||
},
|
||||
start (checked) {
|
||||
this.apiCall(this.startup, api.startup)
|
||||
},
|
||||
openSettings () {
|
||||
this.settings.visible = true
|
||||
},
|
||||
onConfigChanged (newConfig) {
|
||||
console.log('config chagned', newConfig)
|
||||
api.config.save(newConfig).then(() => {
|
||||
api.config.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
padding-top:60px;
|
||||
}
|
||||
|
||||
.big_button >button{
|
||||
width:100px;
|
||||
height:100px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
.big_button >button i{
|
||||
size:40px
|
||||
}
|
||||
</style>
|
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:title="title"
|
||||
placement="right"
|
||||
:closable="false"
|
||||
:visible="visible"
|
||||
:after-visible-change="afterVisibleChange"
|
||||
@close="onClose"
|
||||
width="650px"
|
||||
height="100%"
|
||||
wrapClassName="json-wrapper"
|
||||
>
|
||||
|
||||
<a-tabs
|
||||
default-active-key="1"
|
||||
tab-position="left"
|
||||
:style="{ height: '100%' }"
|
||||
>
|
||||
<a-tab-pane tab="拦截设置" key="1" >
|
||||
<vue-json-editor style="height:100%;" ref="editor" v-model="targetConfig.intercepts" :show-btns="false" :expandedOnStart="true" @json-change="onJsonChange" ></vue-json-editor>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="DNS设置" key="2">
|
||||
<div>
|
||||
<div>某些域名有时候需要通过其他DNS服务器获取到的IP才可以访问</div>
|
||||
<a-row :gutter="10" style="margin-top: 10px" v-for="item in dnsMappings" :key = 'item.key'>
|
||||
<a-col :span="18">
|
||||
<a-input v-model="item.key"></a-input>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select v-model="item.value">
|
||||
<a-select-option value="usa">USA</a-select-option>
|
||||
<a-select-option value="aliyun">Aliyun</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="启动设置" key="3" >
|
||||
<div>启动应用程序后自动启动</div>
|
||||
<a-form style="margin-top: 20px" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }" >
|
||||
<a-form-item label="代理服务" style="margin-bottom: 10px">
|
||||
<a-switch v-model="targetConfig.setting.startup.server" default-checked v-on:click="(checked)=>{targetConfig.setting.startup.server = checked}">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item style="margin-bottom: 10px" v-for="(item,key) in targetConfig.setting.startup.proxy" :key="key" :label="key">
|
||||
<a-switch v-model="targetConfig.setting.startup.proxy[key]" default-checked v-on:click="(checked)=>{targetConfig.setting.startup.proxy[key] = checked}">
|
||||
<a-icon slot="checkedChildren" type="check" />
|
||||
<a-icon slot="unCheckedChildren" type="close" />
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vueJsonEditor from 'vue-json-editor'
|
||||
import lodash from 'lodash'
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
vueJsonEditor
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
type: Object
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '编辑'
|
||||
},
|
||||
visible: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
targetConfig: {},
|
||||
dnsMappings: [],
|
||||
changed: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.resetConfig()
|
||||
},
|
||||
methods: {
|
||||
resetConfig () {
|
||||
this.targetConfig = lodash.cloneDeep(this.config)
|
||||
console.log('targetConfig', this.targetConfig)
|
||||
this.dnsMappings = []
|
||||
for (const key in this.targetConfig.dns.mapping) {
|
||||
const value = this.targetConfig.dns.mapping[key]
|
||||
this.dnsMappings.push({
|
||||
key, value
|
||||
})
|
||||
}
|
||||
},
|
||||
onJsonChange (config) {
|
||||
this.changed = true
|
||||
},
|
||||
afterVisibleChange (val) {
|
||||
console.log('visible', val)
|
||||
if (val === true) {
|
||||
this.resetConfig()
|
||||
}
|
||||
},
|
||||
showDrawer () {
|
||||
this.$emit('update:visible', true)
|
||||
},
|
||||
onClose () {
|
||||
if (this.changed) {
|
||||
this.$confirm({
|
||||
title: '提示',
|
||||
content: '是否需要保存?',
|
||||
onOk: () => {
|
||||
this.$emit('change', this.targetConfig)
|
||||
},
|
||||
onCancel () {}
|
||||
})
|
||||
}
|
||||
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.json-wrapper .ant-drawer-wrapper-body{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.json-wrapper .ant-drawer-wrapper-body .ant-drawer-body{
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
.json-wrapper .jsoneditor-vue{
|
||||
height:100%
|
||||
}
|
||||
.json-wrapper .ant-tabs{
|
||||
height: 100%;
|
||||
}
|
||||
.json-wrapper .ant-tabs-content{
|
||||
height: 100%;
|
||||
}
|
||||
.json-wrapper .ant-tabs-tabpane-active{
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1 @@
|
||||
import './status'
|
@ -0,0 +1,17 @@
|
||||
import api from './api'
|
||||
import lodash from 'lodash'
|
||||
const status = {
|
||||
server: false,
|
||||
proxy: {
|
||||
system: false,
|
||||
npm: false
|
||||
}
|
||||
}
|
||||
|
||||
api.on('status', (event, message) => {
|
||||
console.log('view on status', event, message)
|
||||
const value = message.value
|
||||
const key = message.key
|
||||
lodash.set(status, key, value)
|
||||
})
|
||||
export default status
|
@ -0,0 +1,39 @@
|
||||
module.exports = {
|
||||
configureWebpack: config => {
|
||||
const configNew = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.json5$/i,
|
||||
loader: 'json5-loader',
|
||||
options: {
|
||||
esModule: false
|
||||
},
|
||||
type: 'javascript/auto'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
return configNew
|
||||
},
|
||||
pluginOptions: {
|
||||
electronBuilder: {
|
||||
nodeIntegration: true,
|
||||
// Provide an array of files that, when changed, will recompile the main process and restart Electron
|
||||
// Your main process file will be added by default
|
||||
mainProcessWatch: ['src/bridge', 'src/*.js', 'node_modules/dev-sidecar/src'],
|
||||
builderOptions: {
|
||||
extraResources: [
|
||||
{
|
||||
from: 'src/config.json5',
|
||||
to: 'app/config.json5'
|
||||
},
|
||||
{
|
||||
from: 'public',
|
||||
to: 'public'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue