refactor: 子进程
							parent
							
								
									2d218c1463
								
							
						
					
					
						commit
						032465beae
					
				|  | @ -0,0 +1,4 @@ | ||||||
|  | // eslint-disable-next-line no-unused-vars
 | ||||||
|  | const server = require('@docmirror/mitmproxy') | ||||||
|  | const config = JSON.parse(process.argv[2]) | ||||||
|  | server.start(config) | ||||||
|  | @ -47,7 +47,8 @@ | ||||||
|     "util": "^0.12.3", |     "util": "^0.12.3", | ||||||
|     "validator": "^13.1.17", |     "validator": "^13.1.17", | ||||||
|     "vue": "^2.6.11", |     "vue": "^2.6.11", | ||||||
|     "winreg": "^1.2.4" |     "winreg": "^1.2.4", | ||||||
|  |     "@docmirror/mitmproxy": "1.0.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@vue/cli-plugin-babel": "~4.5.0", |     "@vue/cli-plugin-babel": "~4.5.0", | ||||||
|  |  | ||||||
|  | @ -1,15 +1,17 @@ | ||||||
| const Shell = require('./shell') | const Shell = require('./shell') | ||||||
| const lodash = require('lodash') | const lodash = require('lodash') | ||||||
| const defConfig = require('./config/index.js') | const defConfig = require('./config/index.js') | ||||||
| const proxyConfig = require('./lib/proxy/common/config') | const proxyServer = require('@docmirror/mitmproxy') | ||||||
| let configTarget = lodash.cloneDeep(defConfig) | let configTarget = lodash.cloneDeep(defConfig) | ||||||
| function _deleteDisabledItem (target, objKey) { | function _deleteDisabledItem (target) { | ||||||
|   const obj = lodash.get(target, objKey) |   lodash.forEach(target, (item, key) => { | ||||||
|   for (const key in obj) { |     if (item == null) { | ||||||
|     if (obj[key] === false) { |       delete target[key] | ||||||
|       delete obj[key] |  | ||||||
|     } |     } | ||||||
|   } |     if (lodash.isObject(item)) { | ||||||
|  |       _deleteDisabledItem(item) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
| } | } | ||||||
| const configApi = { | const configApi = { | ||||||
|   get () { |   get () { | ||||||
|  | @ -24,8 +26,7 @@ const configApi = { | ||||||
|     lodash.merge(merged, clone) |     lodash.merge(merged, clone) | ||||||
|     lodash.merge(merged, newConfig) |     lodash.merge(merged, newConfig) | ||||||
| 
 | 
 | ||||||
|     _deleteDisabledItem(merged, 'intercepts') |     _deleteDisabledItem(merged) | ||||||
|     _deleteDisabledItem(merged, 'dns.mapping') |  | ||||||
|     configTarget = merged |     configTarget = merged | ||||||
|     return configTarget |     return configTarget | ||||||
|   }, |   }, | ||||||
|  | @ -35,8 +36,15 @@ const configApi = { | ||||||
|   addDefault (key, defValue) { |   addDefault (key, defValue) { | ||||||
|     lodash.set(defConfig, key, defValue) |     lodash.set(defConfig, key, defValue) | ||||||
|   }, |   }, | ||||||
|   resetDefault () { |   resetDefault (key) { | ||||||
|     configTarget = lodash.cloneDeep(defConfig) |     if (key) { | ||||||
|  |       let value = lodash.get(defConfig, key) | ||||||
|  |       value = lodash.cloneDeep(value) | ||||||
|  |       lodash.set(configTarget, key, value) | ||||||
|  |     } else { | ||||||
|  |       configTarget = lodash.cloneDeep(defConfig) | ||||||
|  |     } | ||||||
|  |     return configTarget | ||||||
|   }, |   }, | ||||||
|   async getVariables (type) { |   async getVariables (type) { | ||||||
|     const method = type === 'npm' ? Shell.getNpmEnv : Shell.getSystemEnv |     const method = type === 'npm' ? Shell.getNpmEnv : Shell.getSystemEnv | ||||||
|  | @ -60,7 +68,7 @@ const configApi = { | ||||||
|     }) |     }) | ||||||
|     if (list.length > 0) { |     if (list.length > 0) { | ||||||
|       const context = { |       const context = { | ||||||
|         ca_cert_path: proxyConfig.getDefaultCACertPath() |         ca_cert_path: proxyServer.config.getDefaultCACertPath() | ||||||
|       } |       } | ||||||
|       for (const item of noSetList) { |       for (const item of noSetList) { | ||||||
|         if (item.value.indexOf('${') >= 0) { |         if (item.value.indexOf('${') >= 0) { | ||||||
|  |  | ||||||
|  | @ -2,95 +2,83 @@ module.exports = { | ||||||
|   server: { |   server: { | ||||||
|     enabled: true, |     enabled: true, | ||||||
|     port: 1181, |     port: 1181, | ||||||
|  |     setting: { | ||||||
|  |       NODE_TLS_REJECT_UNAUTHORIZED: true | ||||||
|  |     }, | ||||||
|     intercepts: { |     intercepts: { | ||||||
|       'github.com': [ |       'github.com': { | ||||||
|         { |         '/.*/.*/releases/download/': { | ||||||
|           // "release archive 下载链接替换",
 |  | ||||||
|           regexp: [ |  | ||||||
|             '/.*/.*/releases/download/', |  | ||||||
|             '/.*/.*/archive/' |  | ||||||
|           ], |  | ||||||
|           redirect: 'download.fastgit.org' |           redirect: 'download.fastgit.org' | ||||||
|         }, |         }, | ||||||
|         { |         '/.*/.*/archive/': { | ||||||
|           regexp: [ |           redirect: 'download.fastgit.org' | ||||||
|             '/.*/.*/raw/', |         }, | ||||||
|             '/.*/.*/blame/' |         '/.*/.*/raw/': { | ||||||
|           ], |           redirect: 'hub.fastgit.org' | ||||||
|  |         }, | ||||||
|  |         '/.*/.*/blame/': { | ||||||
|           redirect: 'hub.fastgit.org' |           redirect: 'hub.fastgit.org' | ||||||
|         } |         } | ||||||
|       ], |       }, | ||||||
|       // 'codeload.github.com': [
 |       'raw.githubusercontent.com': { | ||||||
|       //     {
 |         '.*': { proxy: 'raw.fastgit.org' } | ||||||
|       //         regexp: '.*',
 |       }, | ||||||
|       //         redirect:"download.fastgit.org"
 |       'github.githubassets.com': { | ||||||
|       //     }
 |         '.*': { proxy: 'assets.fastgit.org' } | ||||||
|       // ],
 |       }, | ||||||
|       'raw.githubusercontent.com': [{ proxy: 'raw.fastgit.org' }], |       'customer-stories-feed.github.com': { | ||||||
|       'github.githubassets.com': [ |         '.*': { proxy: 'customer-stories-feed.fastgit.org' } | ||||||
|         { |       }, | ||||||
|           proxy: 'assets.fastgit.org' |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
|       'customer-stories-feed.github.com': [ |  | ||||||
|         { |  | ||||||
|           proxy: 'customer-stories-feed.fastgit.org' |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
| 
 | 
 | ||||||
|       // google cdn
 |       // google cdn
 | ||||||
|       'ajax.googleapis.com': [ |       'ajax.googleapis.com': { | ||||||
|         { |         '.*': { | ||||||
|           proxy: 'ajax.loli.net', |           proxy: 'ajax.loli.net', | ||||||
|           backup: ['ajax.proxy.ustclug.org'], |           backup: ['ajax.proxy.ustclug.org'], | ||||||
|           case: 'ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js' |           test: 'ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js' | ||||||
|         } |         } | ||||||
|       ], |       }, | ||||||
|       'fonts.googleapis.com': [ |       'fonts.googleapis.com': { | ||||||
|         { |         '.*': { | ||||||
|           proxy: 'fonts.loli.net', |           proxy: 'fonts.loli.net', | ||||||
|           backup: ['fonts.proxy.ustclug.org'], |           backup: ['fonts.proxy.ustclug.org'], | ||||||
|           case: 'https://fonts.googleapis.com/css?family=Oswald' |           test: 'https://fonts.googleapis.com/css?family=Oswald' | ||||||
|         } |         } | ||||||
|       ], |       }, | ||||||
|       'themes.googleapis.com': [ |       'themes.googleapis.com': { | ||||||
|         { |         '.*': { | ||||||
|           proxy: 'themes.loli.net', |           proxy: 'themes.loli.net', | ||||||
|           backup: ['themes.proxy.ustclug.org'] |           backup: ['themes.proxy.ustclug.org'] | ||||||
|         } |         } | ||||||
|       ], |       }, | ||||||
|       'themes.googleusercontent.com': [ |       'themes.googleusercontent.com': { | ||||||
|         { proxy: 'google-themes.proxy.ustclug.org' } |         '.*': { proxy: 'google-themes.proxy.ustclug.org' } | ||||||
|       ], |       }, | ||||||
|       'www.google.com': [ |       'www.google.com': { | ||||||
|         { |         '/recaptcha/.*': { proxy: 'www.recaptcha.net' } | ||||||
|           regexp: '/recaptcha/.*', |       }, | ||||||
|           proxy: 'www.recaptcha.net' |       'fonts.gstatic.com': { | ||||||
|         } |         '.*': { | ||||||
|       ], |  | ||||||
|       'fonts.gstatic.com': [ |  | ||||||
|         { |  | ||||||
|           proxy: 'fonts-gstatic.proxy.ustclug.org', |           proxy: 'fonts-gstatic.proxy.ustclug.org', | ||||||
|           backup: ['gstatic.loli.net'] |           backup: ['gstatic.loli.net'] | ||||||
|         } |         } | ||||||
|       ], |       }, | ||||||
|       'clients*.google.com': [{ abort: true }], |       'clients*.google.com': { '.*': { abort: true } }, | ||||||
|       'www.googleapis.com': [{ abort: true }], |       'www.googleapis.com': { '.*': { abort: true } }, | ||||||
|       'lh*.googleusercontent.com': [{ abort: true }], |       'lh*.googleusercontent.com': { '.*': { abort: true } }, | ||||||
|       // mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.0/napi-v3-win32-x64.tar.gz
 |       // mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.0/napi-v3-win32-x64.tar.gz
 | ||||||
|       '*.s3.amazonaws.com': [ |       '*.s3.amazonaws.com': { | ||||||
|         { |         '/sqlite3/.*': { | ||||||
|           regexp: '/sqlite3/.*', |  | ||||||
|           redirect: 'npm.taobao.org/mirrors' |           redirect: 'npm.taobao.org/mirrors' | ||||||
|         } |         } | ||||||
|       ], |       }, | ||||||
|       'registry-1.docker.io': [{ proxy: 'docker.mirrors.ustc.edu.cn' }], |       'registry-1.docker.io': { '.*': { proxy: 'docker.mirrors.ustc.edu.cn' } }, | ||||||
|       'packages.elastic.co': [{ proxy: 'elastic.proxy.ustclug.org' }], |       'packages.elastic.co': { '.*': { proxy: 'elastic.proxy.ustclug.org' } }, | ||||||
|       'ppa.launchpad.net': [{ proxy: 'launchpad.proxy.ustclug.org' }], |       'ppa.launchpad.net': { '.*': { proxy: 'launchpad.proxy.ustclug.org' } }, | ||||||
|       'archive.cloudera.com': [{ regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' }], |       'archive.cloudera.com': { '.*': { regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' } }, | ||||||
|       'downloads.lede-project.org': [{ proxy: 'lede.proxy.ustclug.org' }], |       'downloads.lede-project.org': { '.*': { proxy: 'lede.proxy.ustclug.org' } }, | ||||||
|       'downloads.openwrt.org': [{ proxy: 'openwrt.proxy.ustclug.org' }], |       'downloads.openwrt.org': { '.*': { proxy: 'openwrt.proxy.ustclug.org' } }, | ||||||
|       'secure.gravatar.com': [{ proxy: 'gravatar.proxy.ustclug.org' }] |       'secure.gravatar.com': { '.*': { proxy: 'gravatar.proxy.ustclug.org' } } | ||||||
|     }, |     }, | ||||||
|     dns: { |     dns: { | ||||||
|       providers: { |       providers: { | ||||||
|  |  | ||||||
|  | @ -3,8 +3,9 @@ const config = require('./config') | ||||||
| const event = require('./event') | const event = require('./event') | ||||||
| const shell = require('./shell') | const shell = require('./shell') | ||||||
| const modules = require('./modules') | const modules = require('./modules') | ||||||
| const proxyConfig = require('./lib/proxy/common/config') |  | ||||||
| const lodash = require('lodash') | const lodash = require('lodash') | ||||||
|  | const proxyServer = require('@docmirror/mitmproxy') | ||||||
|  | const proxyConfig = proxyServer.config | ||||||
| const context = { | const context = { | ||||||
|   config, |   config, | ||||||
|   shell, |   shell, | ||||||
|  | @ -23,10 +24,6 @@ function setupPlugin (key, plugin, context, config) { | ||||||
|   return api |   return api | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function fireStatus (target) { |  | ||||||
|   event.fire('status', target) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const server = modules.server | const server = modules.server | ||||||
| const proxy = setupPlugin('proxy', modules.proxy, context, config) | const proxy = setupPlugin('proxy', modules.proxy, context, config) | ||||||
| const plugin = {} | const plugin = {} | ||||||
|  | @ -40,14 +37,11 @@ config.resetDefault() | ||||||
| module.exports = { | module.exports = { | ||||||
|   status, |   status, | ||||||
|   api: { |   api: { | ||||||
|     startup: async (newConfig) => { |     startup: async ({ mitmproxyPath }) => { | ||||||
|       if (newConfig) { |  | ||||||
|         config.set(newConfig) |  | ||||||
|       } |  | ||||||
|       const conf = config.get() |       const conf = config.get() | ||||||
|       if (conf.server.enabled) { |       if (conf.server.enabled) { | ||||||
|         try { |         try { | ||||||
|           await server.start() |           await server.start({ mitmproxyPath }) | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           console.error('代理服务启动失败:', err) |           console.error('代理服务启动失败:', err) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| class AbstractPlugin { |  | ||||||
|   constructor (context) { |  | ||||||
|     this._context = context |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _getConfig () { |  | ||||||
|     return this._context.config.get() |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _getShell () { |  | ||||||
|     return this._context.shell |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _fireStatus (event) { |  | ||||||
|     this._context.event.fire('status', event) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = AbstractPlugin |  | ||||||
|  | @ -1,15 +1,13 @@ | ||||||
| module.exports = { | module.exports = { | ||||||
|   name: 'NPM加速', |   name: 'NPM加速', | ||||||
|   enabled: true, |   enabled: false, | ||||||
|   startup: { |   startup: { | ||||||
|     npm: true, |  | ||||||
|     yarn: true, |  | ||||||
|     variables: true |     variables: true | ||||||
|   }, |   }, | ||||||
|   setting: { |   setting: { | ||||||
|     'strict-ssl': false, |     'strict-ssl': true, | ||||||
|     cafile: true, |     cafile: false, | ||||||
|     NODE_EXTRA_CA_CERTS: true, |     NODE_EXTRA_CA_CERTS: false, | ||||||
|     NODE_TLS_REJECT_UNAUTHORIZED: false, |     NODE_TLS_REJECT_UNAUTHORIZED: false, | ||||||
|     registry: 'https://registry.npmjs.org'// 可以选择切换官方或者淘宝镜像
 |     registry: 'https://registry.npmjs.org'// 可以选择切换官方或者淘宝镜像
 | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -83,7 +83,6 @@ const NodePlugin = function (context) { | ||||||
| 
 | 
 | ||||||
|     async setRegistry (registry) { |     async setRegistry (registry) { | ||||||
|       await nodeApi.setNpmEnv([{ key: 'registry', value: registry }]) |       await nodeApi.setNpmEnv([{ key: 'registry', value: registry }]) | ||||||
|       console.log(1111) |  | ||||||
|       return true |       return true | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|  | @ -103,7 +102,7 @@ const NodePlugin = function (context) { | ||||||
|        */ |        */ | ||||||
|       const nodeConfig = config.get().plugin.node |       const nodeConfig = config.get().plugin.node | ||||||
|       if (nodeConfig.setting['strict-ssl']) { |       if (nodeConfig.setting['strict-ssl']) { | ||||||
|         cmds.push('npm nodeConfig set strict-ssl false') |         cmds.push('npm config set strict-ssl false') | ||||||
|       } |       } | ||||||
|       if (nodeConfig.setting.cafile) { |       if (nodeConfig.setting.cafile) { | ||||||
|         cmds.push(`npm config set cafile "${rootCaFile}"`) |         cmds.push(`npm config set cafile "${rootCaFile}"`) | ||||||
|  | @ -115,13 +114,13 @@ const NodePlugin = function (context) { | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (nodeConfig.setting.NODE_TLS_REJECT_UNAUTHORIZED) { |       if (nodeConfig.setting.NODE_TLS_REJECT_UNAUTHORIZED) { | ||||||
|         cmds.push('npm nodeConfig set NODE_TLS_REJECT_UNAUTHORIZED 0') |         cmds.push('npm config set NODE_TLS_REJECT_UNAUTHORIZED 0') | ||||||
|         env.push({ key: 'NODE_TLS_REJECT_UNAUTHORIZED', value: '0' }) |         env.push({ key: 'NODE_TLS_REJECT_UNAUTHORIZED', value: '0' }) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const ret = await shell.exec(cmds, { type: 'cmd' }) |       const ret = await shell.exec(cmds, { type: 'cmd' }) | ||||||
|       if (env.length > 0) { |       if (env.length > 0) { | ||||||
|         //  await shell.setSystemEnv({ list: env })
 |         await shell.setSystemEnv({ list: env }) | ||||||
|       } |       } | ||||||
|       event.fire('status', { key: 'plugin.node.enabled', value: true }) |       event.fire('status', { key: 'plugin.node.enabled', value: true }) | ||||||
|       console.info('开启【NPM】代理成功') |       console.info('开启【NPM】代理成功') | ||||||
|  |  | ||||||
|  | @ -39,12 +39,7 @@ module.exports = { | ||||||
|     enabled: true, |     enabled: true, | ||||||
|     name: '系统代理', |     name: '系统代理', | ||||||
|     use: 'local', |     use: 'local', | ||||||
|     other: { |     other: [] | ||||||
|       host: undefined, |  | ||||||
|       port: undefined, |  | ||||||
|       username: undefined, |  | ||||||
|       password: undefined |  | ||||||
|     } |  | ||||||
|   }, |   }, | ||||||
|   status: { |   status: { | ||||||
|     enabled: false, |     enabled: false, | ||||||
|  |  | ||||||
|  | @ -1,10 +1,19 @@ | ||||||
| const ProxyOptions = require('./options') |  | ||||||
| const mitmproxy = require('../../lib/proxy') |  | ||||||
| const config = require('../../config') | const config = require('../../config') | ||||||
| const event = require('../../event') | const event = require('../../event') | ||||||
| const status = require('../../status') | const status = require('../../status') | ||||||
| const shell = require('../../shell') | const lodash = require('lodash') | ||||||
|  | const fork = require('child_process').fork | ||||||
| let server | let server | ||||||
|  | function fireStatus (status) { | ||||||
|  |   event.fire('status', { key: 'server.enabled', value: status }) | ||||||
|  | } | ||||||
|  | function sleep (time) { | ||||||
|  |   return new Promise(resolve => { | ||||||
|  |     setTimeout(() => { | ||||||
|  |       resolve() | ||||||
|  |     }, time) | ||||||
|  |   }) | ||||||
|  | } | ||||||
| const serverApi = { | const serverApi = { | ||||||
|   async startup () { |   async startup () { | ||||||
|     if (config.get().server.startup) { |     if (config.get().server.startup) { | ||||||
|  | @ -16,66 +25,73 @@ const serverApi = { | ||||||
|       return this.close() |       return this.close() | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   async start (newConfig) { |   async start ({ mitmproxyPath }) { | ||||||
|     if (server != null) { |     const allConfig = config.get() | ||||||
|       server.close() |     const serverConfig = lodash.cloneDeep(allConfig.server) | ||||||
|  | 
 | ||||||
|  |     const intercepts = serverConfig.intercepts | ||||||
|  |     const dnsMapping = serverConfig.dns.mapping | ||||||
|  | 
 | ||||||
|  |     if (allConfig.plugin) { | ||||||
|  |       lodash.each(allConfig.plugin, (value) => { | ||||||
|  |         const plugin = value | ||||||
|  |         if (plugin.intercepts) { | ||||||
|  |           lodash.merge(intercepts, plugin.intercepts) | ||||||
|  |         } | ||||||
|  |         if (plugin.dns) { | ||||||
|  |           lodash.merge(dnsMapping, plugin.dns) | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|     } |     } | ||||||
|     config.set(newConfig) |     // fireStatus('ing') // 启动中
 | ||||||
|     const proxyOptions = ProxyOptions(config.get()) |     const serverProcess = fork(mitmproxyPath, [JSON.stringify(serverConfig)]) | ||||||
|     const newServer = mitmproxy.createProxy(proxyOptions, () => { |     server = { | ||||||
|       event.fire('status', { key: 'server.enabled', value: true }) |       id: serverProcess.pid, | ||||||
|       console.log('代理服务已启动:127.0.0.1:' + proxyOptions.port) |       process: serverProcess, | ||||||
|     }) |       close () { | ||||||
|     newServer.on('close', () => { |         serverProcess.send({ type: 'action', event: { key: 'close' } }) | ||||||
|       if (server === newServer) { |       } | ||||||
|         server = null |     } | ||||||
|         event.fire('status', { key: 'server.enabled', value: false }) |     console.log('fork return pid: ' + serverProcess.pid) | ||||||
|  |     serverProcess.on('message', function (msg) { | ||||||
|  |       console.log('收到子进程消息', msg) | ||||||
|  |       if (msg.type === 'status') { | ||||||
|  |         fireStatus(msg.event) | ||||||
|  |       } else if (msg.type === 'error') { | ||||||
|  |         event.fire('error', { key: 'server', error: msg.event }) | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     newServer.on('error', (e) => { |     return { port: config.port } | ||||||
|       console.log('server error', e) |   }, | ||||||
|       // newServer = null
 |   async kill () { | ||||||
|       event.fire('error', { key: 'server', error: e }) |     if (server) { | ||||||
|     }) |       server.process.kill('SIGINT') | ||||||
|     newServer.config = proxyOptions |       await sleep(1000) | ||||||
|     server = newServer |     } | ||||||
|     return { port: proxyOptions.port } |     fireStatus(false) | ||||||
|   }, |   }, | ||||||
|   async close () { |   async close () { | ||||||
|  |     return await serverApi.kill() | ||||||
|  |   }, | ||||||
|  |   async close1 () { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       if (server) { |       if (server) { | ||||||
|         const currentServer = server |         // fireStatus('ing')// 关闭中
 | ||||||
|         let closed = false |  | ||||||
|         server.close((err) => { |         server.close((err) => { | ||||||
|           if (err) { |           if (err) { | ||||||
|             console.log('close error', err, ',', err.code, ',', err.message, ',', err.errno) |             console.log('close error', err, ',', err.code, ',', err.message, ',', err.errno) | ||||||
|             if (err.code === 'ERR_SERVER_NOT_RUNNING') { |             if (err.code === 'ERR_SERVER_NOT_RUNNING') { | ||||||
|               console.log('代理服务关闭成功') |               console.log('代理服务关闭成功') | ||||||
|               closed = true |  | ||||||
|               resolve() |               resolve() | ||||||
|               return |               return | ||||||
|             } |             } | ||||||
|  |             console.log('代理服务关闭失败', err) | ||||||
|             reject(err) |             reject(err) | ||||||
|           } else { |           } else { | ||||||
|             console.log('代理服务关闭成功') |             console.log('代理服务关闭成功') | ||||||
|             closed = true |  | ||||||
|             resolve() |             resolve() | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|         // 3秒后强制关闭
 |  | ||||||
|         setTimeout(async () => { |  | ||||||
|           if (closed) { |  | ||||||
|             return |  | ||||||
|           } |  | ||||||
|           console.log('强制关闭:', config.get().server.port) |  | ||||||
|           await shell.killByPort({ port: config.get().server.port }) |  | ||||||
|           if (currentServer === server) { |  | ||||||
|             server = null |  | ||||||
|             event.fire('status', { key: 'server.enabled', value: false }) |  | ||||||
|           } |  | ||||||
|           closed = true |  | ||||||
|           resolve() |  | ||||||
|         }, 3000) |  | ||||||
|       } else { |       } else { | ||||||
|         console.log('server is null') |         console.log('server is null') | ||||||
|         resolve() |         resolve() | ||||||
|  | @ -83,11 +99,7 @@ const serverApi = { | ||||||
|     }) |     }) | ||||||
|   }, |   }, | ||||||
|   async restart () { |   async restart () { | ||||||
|     try { |     await serverApi.kill() | ||||||
|       await serverApi.close() |  | ||||||
|     } catch (err) { |  | ||||||
|       console.log('stop error', err) |  | ||||||
|     } |  | ||||||
|     await serverApi.start() |     await serverApi.start() | ||||||
|   }, |   }, | ||||||
|   getServer () { |   getServer () { | ||||||
|  |  | ||||||
|  | @ -1,128 +0,0 @@ | ||||||
| const interceptors = require('../../lib/interceptor') |  | ||||||
| const dnsUtil = require('../../lib/dns') |  | ||||||
| const lodash = require('lodash') |  | ||||||
| function matchHostname (intercepts, hostname) { |  | ||||||
|   const interceptOpts = intercepts[hostname] |  | ||||||
|   if (interceptOpts) { |  | ||||||
|     return interceptOpts |  | ||||||
|   } |  | ||||||
|   if (!interceptOpts) { |  | ||||||
|     for (const target in intercepts) { |  | ||||||
|       if (target.indexOf('*') < 0) { |  | ||||||
|         continue |  | ||||||
|       } |  | ||||||
|       // 正则表达式匹配
 |  | ||||||
|       if (hostname.match(target)) { |  | ||||||
|         return intercepts[target] |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function isMatched (url, regexp) { |  | ||||||
|   return url.match(regexp) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function domainRegexply (target) { |  | ||||||
|   return target.replace(/\./g, '\\.').replace(/\*/g, '.*') |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // function test () {
 |  | ||||||
| //   const ret = domainRegexply('*.aaa.com')
 |  | ||||||
| //   console.log(ret)
 |  | ||||||
| //   const success = 'aa.aaa.com'.match(ret)
 |  | ||||||
| //   console.log(success)
 |  | ||||||
| //   const fail = 'a.aaaa.com'.match(ret)
 |  | ||||||
| //   console.log(fail)
 |  | ||||||
| // }
 |  | ||||||
| // test()
 |  | ||||||
| 
 |  | ||||||
| module.exports = (config) => { |  | ||||||
|   let intercepts = lodash.cloneDeep(config.server.intercepts) |  | ||||||
|   const dnsMapping = lodash.cloneDeep(config.server.dns.mapping) |  | ||||||
| 
 |  | ||||||
|   if (config.plugin) { |  | ||||||
|     lodash.each(config.plugin, (value) => { |  | ||||||
|       const plugin = value |  | ||||||
|       if (plugin.intercepts) { |  | ||||||
|         lodash.merge(intercepts, plugin.intercepts) |  | ||||||
|       } |  | ||||||
|       if (plugin.dns) { |  | ||||||
|         lodash.merge(dnsMapping, plugin.dns) |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const regexpIntercepts = {} |  | ||||||
|   lodash.each(intercepts, (value, domain) => { |  | ||||||
|     if (domain.indexOf('*') >= 0) { |  | ||||||
|       const regDomain = domainRegexply(domain) |  | ||||||
|       regexpIntercepts[regDomain] = value |  | ||||||
|     } else { |  | ||||||
|       regexpIntercepts[domain] = value |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
|   intercepts = regexpIntercepts |  | ||||||
| 
 |  | ||||||
|   const serverConfig = config.server |  | ||||||
| 
 |  | ||||||
|   return { |  | ||||||
|     port: serverConfig.port, |  | ||||||
|     dnsConfig: { |  | ||||||
|       providers: dnsUtil.initDNS(serverConfig.dns.providers), |  | ||||||
|       mapping: dnsMapping |  | ||||||
|     }, |  | ||||||
|     sslConnectInterceptor: (req, cltSocket, head) => { |  | ||||||
|       const hostname = req.url.split(':')[0] |  | ||||||
|       return !!matchHostname(intercepts, hostname) // 配置了拦截的域名,将会被代理
 |  | ||||||
|     }, |  | ||||||
|     requestInterceptor: (rOptions, req, res, ssl, next) => { |  | ||||||
|       const hostname = rOptions.hostname |  | ||||||
|       const interceptOpts = matchHostname(intercepts, hostname) |  | ||||||
|       if (!interceptOpts) { // 该域名没有配置拦截器,直接过
 |  | ||||||
|         next() |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       for (const interceptOpt of interceptOpts) { // 遍历拦截配置
 |  | ||||||
|         let regexpList |  | ||||||
|         if (interceptOpt.regexp != null) { |  | ||||||
|           if (interceptOpt.regexp instanceof Array) { |  | ||||||
|             regexpList = interceptOpt.regexp |  | ||||||
|           } else { |  | ||||||
|             regexpList = [interceptOpt.regexp] |  | ||||||
|           } |  | ||||||
|         } else { |  | ||||||
|           regexpList = [true] |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for (const regexp of regexpList) { // 遍历regexp配置
 |  | ||||||
|           if (regexp !== true) { |  | ||||||
|             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) { |  | ||||||
|               // 拦截失败
 |  | ||||||
|               console.error(err) |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       next() |  | ||||||
|     }, |  | ||||||
|     responseInterceptor: (req, res, proxyReq, proxyRes, ssl, next) => { |  | ||||||
|       next() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | const interceptors = require('../../../lib/interceptor') | ||||||
|  | const dnsUtil = require('../../../lib/dns') | ||||||
|  | const lodash = require('lodash') | ||||||
|  | function matchHostname (intercepts, hostname) { | ||||||
|  |   const interceptOpts = intercepts[hostname] | ||||||
|  |   if (interceptOpts) { | ||||||
|  |     return interceptOpts | ||||||
|  |   } | ||||||
|  |   if (!interceptOpts) { | ||||||
|  |     for (const target in intercepts) { | ||||||
|  |       if (target.indexOf('*') < 0) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       // 正则表达式匹配
 | ||||||
|  |       if (hostname.match(target)) { | ||||||
|  |         return intercepts[target] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isMatched (url, regexp) { | ||||||
|  |   return url.match(regexp) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function domainRegexply (target) { | ||||||
|  |   return target.replace(/\./g, '\\.').replace(/\*/g, '.*') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = (config) => { | ||||||
|  |   const regexpIntercepts = {} | ||||||
|  |   lodash.each(config.intercepts, (value, domain) => { | ||||||
|  |     if (domain.indexOf('*') >= 0) { | ||||||
|  |       const regDomain = domainRegexply(domain) | ||||||
|  |       regexpIntercepts[regDomain] = value | ||||||
|  |     } else { | ||||||
|  |       regexpIntercepts[domain] = value | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   const intercepts = regexpIntercepts | ||||||
|  |   const dnsMapping = config.dns.mapping | ||||||
|  |   const serverConfig = config | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     port: serverConfig.port, | ||||||
|  |     dnsConfig: { | ||||||
|  |       providers: dnsUtil.initDNS(serverConfig.dns.providers), | ||||||
|  |       mapping: dnsMapping | ||||||
|  |     }, | ||||||
|  |     sslConnectInterceptor: (req, cltSocket, head) => { | ||||||
|  |       const hostname = req.url.split(':')[0] | ||||||
|  |       return !!matchHostname(intercepts, hostname) // 配置了拦截的域名,将会被代理
 | ||||||
|  |     }, | ||||||
|  |     requestInterceptor: (rOptions, req, res, ssl, next) => { | ||||||
|  |       const hostname = rOptions.hostname | ||||||
|  |       const interceptOpts = matchHostname(intercepts, hostname) | ||||||
|  |       if (!interceptOpts) { // 该域名没有配置拦截器,直接过
 | ||||||
|  |         next() | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       for (const regexp in interceptOpts) { // 遍历拦截配置
 | ||||||
|  |         const interceptOpt = interceptOpts[regexp] | ||||||
|  |         if (regexp !== true) { | ||||||
|  |           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) { | ||||||
|  |             // 拦截失败
 | ||||||
|  |             console.error(err) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       next() | ||||||
|  |     }, | ||||||
|  |     responseInterceptor: (req, res, proxyReq, proxyRes, ssl, next) => { | ||||||
|  |       next() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,68 @@ | ||||||
|  | const mitmproxy = require('../../../lib/proxy') | ||||||
|  | const ProxyOptions = require('./options') | ||||||
|  | 
 | ||||||
|  | function fireError (e) { | ||||||
|  |   process.send({ type: 'error', event: e }) | ||||||
|  | } | ||||||
|  | function fireStatus (status) { | ||||||
|  |   process.send({ type: 'status', event: status }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | let server | ||||||
|  | function start () { | ||||||
|  |   const config = JSON.parse(process.argv[2]) | ||||||
|  |   const proxyOptions = ProxyOptions(config) | ||||||
|  |   console.log('proxy options:', proxyOptions) | ||||||
|  |   const newServer = mitmproxy.createProxy(proxyOptions, () => { | ||||||
|  |     fireStatus(true) | ||||||
|  |     console.log('代理服务已启动:127.0.0.1:' + proxyOptions.port) | ||||||
|  |   }) | ||||||
|  |   newServer.on('close', () => { | ||||||
|  |     if (server === newServer) { | ||||||
|  |       server = null | ||||||
|  |       fireStatus(false) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |   newServer.on('error', (e) => { | ||||||
|  |     console.log('server error', e) | ||||||
|  |     // newServer = null
 | ||||||
|  |     fireError(e) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const api = { | ||||||
|  |   async  close () { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       if (server) { | ||||||
|  |         server.close((err) => { | ||||||
|  |           if (err) { | ||||||
|  |             console.log('close error', err, ',', err.code, ',', err.message, ',', err.errno) | ||||||
|  |             if (err.code === 'ERR_SERVER_NOT_RUNNING') { | ||||||
|  |               console.log('代理服务关闭成功') | ||||||
|  |               resolve() | ||||||
|  |               return | ||||||
|  |             } | ||||||
|  |             reject(err) | ||||||
|  |           } else { | ||||||
|  |             console.log('代理服务关闭成功') | ||||||
|  |             resolve() | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       } else { | ||||||
|  |         console.log('server is null') | ||||||
|  |         fireStatus(false) | ||||||
|  |         resolve() | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | process.on('message', function (msg) { | ||||||
|  |   console.log('child get msg: ' + JSON.stringify(msg)) | ||||||
|  |   if (msg.type === 'action') { | ||||||
|  |     api[msg.event.key](msg.event.params) | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 启动服务
 | ||||||
|  | start() | ||||||
|  | @ -12,7 +12,7 @@ module.exports = { | ||||||
|   setSystemEnv, |   setSystemEnv, | ||||||
|   getNpmEnv, |   getNpmEnv, | ||||||
|   setNpmEnv, |   setNpmEnv, | ||||||
|   exec (cmds, args) { |   async exec (cmds, args) { | ||||||
|     shell.getSystemShell().exec(cmds, args) |     return shell.getSystemShell().exec(cmds, args) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| const Shell = require('../shell') | const Shell = require('../shell') | ||||||
| const execute = Shell.execute | const execute = Shell.execute | ||||||
| const proxyConfig = require('../../lib/proxy/common/config') | const proxyServer = require('@docmirror/mitmproxy') | ||||||
| const executor = { | const executor = { | ||||||
|   async windows (exec) { |   async windows (exec) { | ||||||
|     const cmds = ['start ' + proxyConfig.getDefaultCACertPath()] |     const cmds = ['start ' + proxyServer.config.getDefaultCACertPath()] | ||||||
|     // eslint-disable-next-line no-unused-vars
 |     // eslint-disable-next-line no-unused-vars
 | ||||||
|     const ret = await exec(cmds, { type: 'cmd' }) |     const ret = await exec(cmds, { type: 'cmd' }) | ||||||
|     return true |     return true | ||||||
|  |  | ||||||
|  | @ -34,8 +34,9 @@ class DarwinSystemShell extends SystemShell { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class WindowsSystemShell extends SystemShell { | class WindowsSystemShell extends SystemShell { | ||||||
|   static async exec (cmds, args = { type: 'ps' }) { |   static async exec (cmds, args = { }) { | ||||||
|     const { type } = args |     let { type } = args | ||||||
|  |     type = type || 'ps' | ||||||
|     if (cmds instanceof String) { |     if (cmds instanceof String) { | ||||||
|       cmds = [cmds] |       cmds = [cmds] | ||||||
|     } |     } | ||||||
|  | @ -50,28 +51,33 @@ class WindowsSystemShell extends SystemShell { | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const ret = await ps.invoke() |       const ret = await ps.invoke() | ||||||
|       console.log('ps complete:', cmds, ret) |       // console.log('ps complete:', cmds, ret)
 | ||||||
|       return ret |       return ret | ||||||
|     } else { |     } else { | ||||||
|       let compose = 'chcp 65001  ' |       let compose = 'chcp 65001  ' | ||||||
|       for (const cmd of cmds) { |       for (const cmd of cmds) { | ||||||
|         compose += ' && ' + cmd |         compose += ' && ' + cmd | ||||||
|       } |       } | ||||||
|       return new Promise((resolve, reject) => { |       const ret = await childExec(compose) | ||||||
|         childProcess.exec(compose, function (error, stdout, stderr) { |       // console.log('cmd complete:', cmds)
 | ||||||
|           if (error) { |       return ret | ||||||
|             console.error('cmd 命令执行错误:', compose, error, stderr) |  | ||||||
|             reject(error) |  | ||||||
|           } else { |  | ||||||
|             const data = stdout |  | ||||||
|             resolve(data) |  | ||||||
|           } |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function childExec (composeCmds) { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     childProcess.exec(composeCmds, function (error, stdout, stderr) { | ||||||
|  |       if (error) { | ||||||
|  |         console.error('cmd 命令执行错误:', composeCmds, error, stderr) | ||||||
|  |         reject(error) | ||||||
|  |       } else { | ||||||
|  |         resolve(stdout) | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function getSystemShell () { | function getSystemShell () { | ||||||
|   switch (getSystemPlatform()) { |   switch (getSystemPlatform()) { | ||||||
|     case 'mac': |     case 'mac': | ||||||
|  |  | ||||||
|  | @ -134,7 +134,7 @@ class WindowsSystemProxy extends SystemProxy { | ||||||
| 
 | 
 | ||||||
|       ps.invoke() |       ps.invoke() | ||||||
|         .then(output => { |         .then(output => { | ||||||
|           console.log(output) |           // console.log(output)
 | ||||||
|           resolve() |           resolve() | ||||||
|         }) |         }) | ||||||
|         .catch(err => { |         .catch(err => { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ const DevSidercar = require('.') | ||||||
| // require('json5/lib/register')
 | // require('json5/lib/register')
 | ||||||
| // const config = require('../../config/index.json5')
 | // const config = require('../../config/index.json5')
 | ||||||
| // 启动服务
 | // 启动服务
 | ||||||
| DevSidercar.api.startup() | const mitmproxyPath = './mitmproxy' | ||||||
|  | DevSidercar.api.startup({ mitmproxyPath }) | ||||||
| async function onClose () { | async function onClose () { | ||||||
|   console.log('on sigint ') |   console.log('on sigint ') | ||||||
|   await DevSidercar.api.shutdown() |   await DevSidercar.api.shutdown() | ||||||
|  |  | ||||||
|  | @ -16,6 +16,7 @@ | ||||||
|   "main": "background.js", |   "main": "background.js", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@docmirror/dev-sidecar": "1.0.0", |     "@docmirror/dev-sidecar": "1.0.0", | ||||||
|  |     "@docmirror/mitmproxy": "1.0.0", | ||||||
|     "ant-design-vue": "^1.6.5", |     "ant-design-vue": "^1.6.5", | ||||||
|     "core-js": "^3.6.5", |     "core-js": "^3.6.5", | ||||||
|     "electron-updater": "^4.3.5", |     "electron-updater": "^4.3.5", | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="en"> | <html lang="en"  style="height:100%"> | ||||||
|   <head> |   <head> | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|  | @ -7,11 +7,13 @@ | ||||||
|     <link rel="icon" href="<%= BASE_URL %>favicon.ico"> |     <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||||
|     <title><%= htmlWebpackPlugin.options.title %></title> |     <title><%= htmlWebpackPlugin.options.title %></title> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body  style="height:100%"> | ||||||
|     <noscript> |     <noscript> | ||||||
|       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | ||||||
|     </noscript> |     </noscript> | ||||||
|     <div id="app"></div> |     <div id="app" style="height:100%"> | ||||||
|  |         <div style="display: flex;align-items: center;justify-content: center;height:100%;width:100%"><img src="loading-spin.svg"></div> | ||||||
|  |     </div> | ||||||
|     <!-- built files will be auto injected --> |     <!-- built files will be auto injected --> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="#777"> | ||||||
|  |   <path opacity=".25" d="M16 0 A16 16 0 0 0 16 32 A16 16 0 0 0 16 0 M16 4 A12 12 0 0 1 16 28 A12 12 0 0 1 16 4"/> | ||||||
|  |   <path d="M16 0 A16 16 0 0 1 32 16 L28 16 A12 12 0 0 0 16 4z"> | ||||||
|  |     <animateTransform attributeName="transform" type="rotate" from="0 16 16" to="360 16 16" dur="0.8s" repeatCount="indefinite" /> | ||||||
|  |   </path> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 421 B | 
|  | @ -3,7 +3,8 @@ import DevSidecar from '@docmirror/dev-sidecar' | ||||||
| import { ipcMain } from 'electron' | import { ipcMain } from 'electron' | ||||||
| import fs from 'fs' | import fs from 'fs' | ||||||
| import JSON5 from 'json5' | import JSON5 from 'json5' | ||||||
| 
 | import path from 'path' | ||||||
|  | const mitmproxyPath = path.join(__dirname, 'mitmproxy.js') | ||||||
| const localApi = { | const localApi = { | ||||||
|   getApiList () { |   getApiList () { | ||||||
|     const core = lodash.cloneDeep(DevSidecar.api) |     const core = lodash.cloneDeep(DevSidecar.api) | ||||||
|  | @ -14,6 +15,14 @@ const localApi = { | ||||||
|     console.log('api list:', list) |     console.log('api list:', list) | ||||||
|     return list |     return list | ||||||
|   }, |   }, | ||||||
|  |   startup () { | ||||||
|  |     return DevSidecar.api.startup({ mitmproxyPath }) | ||||||
|  |   }, | ||||||
|  |   server: { | ||||||
|  |     start () { | ||||||
|  |       return DevSidecar.api.server.start({ mitmproxyPath }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|   config: { |   config: { | ||||||
|     /** |     /** | ||||||
|      * 保存自定义的 config |      * 保存自定义的 config | ||||||
|  | @ -22,13 +31,12 @@ const localApi = { | ||||||
|     save (newConfig) { |     save (newConfig) { | ||||||
|       // 对比默认config的异同
 |       // 对比默认config的异同
 | ||||||
|       const defConfig = DevSidecar.api.config.getDefault() |       const defConfig = DevSidecar.api.config.getDefault() | ||||||
|  |       const saveConfig = doMerge(defConfig, newConfig) | ||||||
| 
 | 
 | ||||||
|       const saveConfig = {} |       // _merge(defConfig, newConfig, saveConfig, 'intercepts')
 | ||||||
| 
 |       // _merge(defConfig, newConfig, saveConfig, 'dns.mapping')
 | ||||||
|       _merge(defConfig, newConfig, saveConfig, 'intercepts') |       // _merge(defConfig, newConfig, saveConfig, 'setting.startup.server', true)
 | ||||||
|       _merge(defConfig, newConfig, saveConfig, 'dns.mapping') |       // _merge(defConfig, newConfig, saveConfig, 'setting.startup.proxy')
 | ||||||
|       _merge(defConfig, newConfig, saveConfig, 'setting.startup.server', true) |  | ||||||
|       _merge(defConfig, newConfig, saveConfig, 'setting.startup.proxy') |  | ||||||
| 
 | 
 | ||||||
|       fs.writeFileSync(_getConfigPath(), JSON5.stringify(saveConfig, null, 2)) |       fs.writeFileSync(_getConfigPath(), JSON5.stringify(saveConfig, null, 2)) | ||||||
|       return saveConfig |       return saveConfig | ||||||
|  | @ -66,39 +74,42 @@ function _getConfigPath () { | ||||||
|   return dir + 'config.json5' |   return dir + 'config.json5' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function _merge (defConfig, newConfig, saveConfig, target, self = false) { | function doMerge (defObj, newObj) { | ||||||
|   if (self) { |   const defObj2 = { ...defObj } | ||||||
|     const defValue = lodash.get(defConfig, target) |   const newObj2 = {} | ||||||
|     const newValue = lodash.get(newConfig, target) |  | ||||||
|     if (newValue != null && newValue !== defValue) { |  | ||||||
|       lodash.set(saveConfig, newValue, target) |  | ||||||
|     } |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
|   const saveObj = _mergeConfig(lodash.get(defConfig, target), lodash.get(newConfig, target)) |  | ||||||
|   lodash.set(saveConfig, target, saveObj) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function _mergeConfig (defObj, newObj) { |  | ||||||
|   for (const key in defObj) { |  | ||||||
|     // 从默认里面提取对比,是否有被删除掉的
 |  | ||||||
|     if (newObj[key] == null) { |  | ||||||
|       newObj[key] = false |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   for (const key in newObj) { |   for (const key in newObj) { | ||||||
|     const newItem = newObj[key] |     const newValue = newObj[key] | ||||||
|     const defItem = defObj[key] |     const defValue = defObj[key] | ||||||
|     if (newItem && !defItem) { |     if (newValue != null && defValue == null) { | ||||||
|  |       newObj2[key] = newValue | ||||||
|       continue |       continue | ||||||
|     } |     } | ||||||
|     // 深度对比 是否有修改
 |     if (lodash.isEqual(newValue, defValue)) { | ||||||
|     if (lodash.isEqual(newItem, defItem)) { |       delete defObj2[key] | ||||||
|       // 没有修改则删除
 |       continue | ||||||
|       delete newObj[key] |     } | ||||||
|  | 
 | ||||||
|  |     if (lodash.isArray(newValue)) { | ||||||
|  |       delete defObj2[key] | ||||||
|  |       newObj2[key] = newValue | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     if (lodash.isObject(newValue)) { | ||||||
|  |       newObj2[key] = doMerge(defValue, newValue) | ||||||
|  |       delete defObj2[key] | ||||||
|  |       continue | ||||||
|  |     } else { | ||||||
|  |       // 基础类型,直接覆盖
 | ||||||
|  |       delete defObj2[key] | ||||||
|  |       newObj2[key] = newValue | ||||||
|  |       continue | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   return newObj |   // defObj 里面剩下的是被删掉的
 | ||||||
|  |   lodash.forEach(defObj2, (defValue, key) => { | ||||||
|  |     newObj2[key] = null | ||||||
|  |   }) | ||||||
|  |   return newObj2 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  | @ -133,7 +144,7 @@ export default { | ||||||
| 
 | 
 | ||||||
|     // 合并用户配置
 |     // 合并用户配置
 | ||||||
|     localApi.config.reload() |     localApi.config.reload() | ||||||
|     DevSidecar.api.startup() |     localApi.startup() | ||||||
|   }, |   }, | ||||||
|   devSidecar: DevSidecar |   devSidecar: DevSidecar | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | // eslint-disable-next-line no-unused-vars
 | ||||||
|  | const server = require('@docmirror/mitmproxy') | ||||||
|  | const config = JSON.parse(process.argv[2]) | ||||||
|  | server.start(config) | ||||||
|  | @ -19,6 +19,8 @@ const router = new VueRouter({ | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| apiInit().then((api) => { | apiInit().then((api) => { | ||||||
|  |   Vue.prototype.$api = api | ||||||
|  | 
 | ||||||
|   const app = new Vue({ |   const app = new Vue({ | ||||||
|     router, |     router, | ||||||
|     render: h => h(App) |     render: h => h(App) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | import DsContainer from '../components/container' | ||||||
|  | import status from '../status' | ||||||
|  | import lodash from 'lodash' | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     DsContainer | ||||||
|  |   }, | ||||||
|  |   data () { | ||||||
|  |     return { | ||||||
|  |       config: undefined, | ||||||
|  |       status: status, | ||||||
|  |       labelCol: { span: 4 }, | ||||||
|  |       wrapperCol: { span: 20 } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created () { | ||||||
|  |     this.init() | ||||||
|  |   }, | ||||||
|  |   mounted () { | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     init () { | ||||||
|  |       this.$api.config.reload().then(ret => { | ||||||
|  |         this.config = ret | ||||||
|  |         if (this.ready) { | ||||||
|  |           return this.ready(this.config) | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     apply () { | ||||||
|  |       return this.saveConfig().then(() => { | ||||||
|  |         if (this.applyAfter) { | ||||||
|  |           return this.applyAfter() | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     reloadDefault (key) { | ||||||
|  |       this.$api.config.resetDefault(key).then(ret => { | ||||||
|  |         this.config = ret | ||||||
|  |       }).then(() => { | ||||||
|  |         this.apply() | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     saveConfig () { | ||||||
|  |       return this.$api.config.save(this.config).then(() => { | ||||||
|  |         this.$message.info('设置已保存') | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     getConfig (key) { | ||||||
|  |       const value = lodash.get(this.config, key) | ||||||
|  |       if (value == null) { | ||||||
|  |         return {} | ||||||
|  |       } | ||||||
|  |       return value | ||||||
|  |     }, | ||||||
|  |     getStatus (key) { | ||||||
|  |       const value = lodash.get(this.status, key) | ||||||
|  |       if (value == null) { | ||||||
|  |         return {} | ||||||
|  |       } | ||||||
|  |       return value | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -11,17 +11,17 @@ | ||||||
|       <div style="text-align: center"> |       <div style="text-align: center"> | ||||||
|         <div class="big_button"> |         <div class="big_button"> | ||||||
|           <a-button shape="circle" :type="startup.type()" :loading="startup.loading" @click="startup.doClick"> |           <a-button shape="circle" :type="startup.type()" :loading="startup.loading" @click="startup.doClick"> | ||||||
|             <img v-if="!startup.loading && !status.server" width="50" src="/logo/logo-simple.svg"> |             <img v-if="!startup.loading && !status.server.enabled" width="50" src="/logo/logo-simple.svg"> | ||||||
|             <img v-if="!startup.loading && status.server" width="50" src="/logo/logo-fff.svg"> |             <img v-if="!startup.loading && status.server.enabled" width="50" src="/logo/logo-fff.svg"> | ||||||
|           </a-button> |           </a-button> | ||||||
|           <div style="margin-top: 10px">{{ status.server ? '已开启' : '已关闭' }}</div> |           <div style="margin-top: 10px">{{ status.server.enabled ? '已开启' : '已关闭' }}</div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div :span="12"> |       <div :span="12"> | ||||||
|         <a-form style="margin-top:20px" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }"> |         <a-form style="margin-top:20px" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }"> | ||||||
| 
 | 
 | ||||||
|           <a-form-item v-for=" (item, key) in switchBtns" :key="key" :label="item.label"> |           <a-form-item v-for=" (item, key) in switchBtns" :key="key" :label="item.label"> | ||||||
|             <a-switch :loading="item.loading" v-model="item.status[key].enabled" default-checked v-on:click="item.doClick"> |             <a-switch style="margin-left:10px" :loading="item.loading" v-model="item.status[key].enabled" default-checked v-on:click="item.doClick"> | ||||||
|               <a-icon slot="checkedChildren" type="check"/> |               <a-icon slot="checkedChildren" type="check"/> | ||||||
|               <a-icon slot="unCheckedChildren" type="close"/> |               <a-icon slot="unCheckedChildren" type="close"/> | ||||||
|             </a-switch> |             </a-switch> | ||||||
|  | @ -30,6 +30,7 @@ | ||||||
|         </a-form> |         </a-form> | ||||||
| 
 | 
 | ||||||
|       </div> |       </div> | ||||||
|  | 
 | ||||||
|     </div> |     </div> | ||||||
|     <setup-ca title="安装证书" :visible.sync="setupCa.visible"></setup-ca> |     <setup-ca title="安装证书" :visible.sync="setupCa.visible"></setup-ca> | ||||||
|   </ds-container> |   </ds-container> | ||||||
|  | @ -51,19 +52,14 @@ export default { | ||||||
|   }, |   }, | ||||||
|   data () { |   data () { | ||||||
|     return { |     return { | ||||||
|       status: { |       status: status, | ||||||
|         proxy: {}, |  | ||||||
|         plugin: { |  | ||||||
|           node: {} |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|       startup: { |       startup: { | ||||||
|         loading: false, |         loading: false, | ||||||
|         type: () => { |         type: () => { | ||||||
|           return this.status.server ? 'primary' : 'default' |           return (this.status.server && this.status.server.enabled) ? 'primary' : 'default' | ||||||
|         }, |         }, | ||||||
|         doClick: () => { |         doClick: () => { | ||||||
|           if (this.status.server) { |           if (this.status.server.enabled) { | ||||||
|             this.apiCall(this.startup, api.shutdown) |             this.apiCall(this.startup, api.shutdown) | ||||||
|           } else { |           } else { | ||||||
|             this.apiCall(this.startup, api.startup) |             this.apiCall(this.startup, api.startup) | ||||||
|  | @ -192,4 +188,5 @@ export default { | ||||||
| div.ant-form-item { | div.ant-form-item { | ||||||
|   margin-bottom: 10px; |   margin-bottom: 10px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -7,19 +7,27 @@ | ||||||
|     </template> |     </template> | ||||||
| 
 | 
 | ||||||
|     <div v-if="config"> |     <div v-if="config"> | ||||||
|       <div> |       <a-form layout="horizontal"> | ||||||
|         <a-form-item label="启用NPM加速插件" > |         <a-form-item label="启用NPM代理" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|           <a-checkbox v-model="config.plugin.node.enabled" > |           <a-checkbox v-model="config.plugin.node.enabled"> | ||||||
|             自动开启NPM加速 |             随应用启动 | ||||||
|           </a-checkbox> |           </a-checkbox> | ||||||
|           当前状态: |  | ||||||
|           <a-tag v-if="status.plugin.node.enabled" color="green"> |           <a-tag v-if="status.plugin.node.enabled" color="green"> | ||||||
|             已启动 |             当前已启动 | ||||||
|  |           </a-tag> | ||||||
|  |           <a-tag v-else color="red"> | ||||||
|  |             当前未启动 | ||||||
|           </a-tag> |           </a-tag> | ||||||
|         </a-form-item> |         </a-form-item> | ||||||
| 
 |         <a-form-item label="SSL相关" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|         <a-form-item label="切换registry" > |           <a-checkbox v-model="config.plugin.node.setting['strict-ssl']"> | ||||||
|           <a-radio-group v-model="config.plugin.node.setting.registry" @change="onSwitchRegistry" default-value="https://registry.npmjs.org" button-style="solid"> |             关闭strict-ssl | ||||||
|  |           </a-checkbox> | ||||||
|  |           npm代理启用后必须关闭 | ||||||
|  |         </a-form-item> | ||||||
|  |         <a-form-item label="切换registry" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|  |           <a-radio-group v-model="config.plugin.node.setting.registry" @change="onSwitchRegistry" | ||||||
|  |                          default-value="https://registry.npmjs.org" button-style="solid"> | ||||||
|             <a-radio-button value="https://registry.npmjs.org"> |             <a-radio-button value="https://registry.npmjs.org"> | ||||||
|               npmjs |               npmjs | ||||||
|             </a-radio-button> |             </a-radio-button> | ||||||
|  | @ -28,27 +36,31 @@ | ||||||
|             </a-radio-button> |             </a-radio-button> | ||||||
|           </a-radio-group> |           </a-radio-group> | ||||||
|         </a-form-item> |         </a-form-item> | ||||||
|       </div> |  | ||||||
| 
 | 
 | ||||||
|       <div> |         <a-form-item label="镜像变量设置" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|         <div>某些库需要自己设置镜像变量,才能下载,比如:electron</div> |           <a-checkbox v-model="config.plugin.node.startup.variables"> | ||||||
|         <a-row :gutter="10" style="margin-top: 10px" v-for="(item,index) of npmVariables" :key = 'index'> |             自动设置 | ||||||
|           <a-col :span="10"> |           </a-checkbox> | ||||||
|             <a-input :disabled="item.key ===false" v-model="item.key"></a-input> |           <div>某些库需要自己设置镜像变量,才能下载,比如:electron</div> | ||||||
|           </a-col> |           <a-row :gutter="10" style="margin-top: 10px" v-for="(item,index) of npmVariables" :key='index'> | ||||||
|           <a-col :span="10"> |             <a-col :span="10"> | ||||||
|             <a-input :disabled="item.value ===false" v-model="item.value"></a-input> |               <a-input :disabled="item.key ===false" v-model="item.key"></a-input> | ||||||
|           </a-col> |             </a-col> | ||||||
|           <a-col :span="4"> |             <a-col :span="10"> | ||||||
|             <a-icon v-if="item.exists" style="color:green" type="check" /> |               <a-input :disabled="item.value ===false" v-model="item.value"></a-input> | ||||||
|             <a-icon v-if="!item.exists || !item.set" title="还未设置" style="color:red" type="exclamation-circle" /> |             </a-col> | ||||||
|           </a-col> |             <a-col :span="4"> | ||||||
|         </a-row> |               <a-icon v-if="item.exists&& item.hadSet" title="已设置" style="color:green" type="check"/> | ||||||
|       </div> |               <a-icon v-else title="还未设置" style="color:red" type="exclamation-circle"/> | ||||||
|  |             </a-col> | ||||||
|  |           </a-row> | ||||||
|  |         </a-form-item> | ||||||
|  |       </a-form> | ||||||
|     </div> |     </div> | ||||||
|     <template slot="footer"> |     <template slot="footer"> | ||||||
|       <div class="footer-bar"> |       <div class="footer-bar"> | ||||||
|         <a-button  type="primary" @click="submit()">应用</a-button> |         <a-button class="md-mr-10"   @click="reloadDefault('plugin.node')">恢复默认</a-button> | ||||||
|  |         <a-button type="primary" @click="apply()">应用</a-button> | ||||||
|       </div> |       </div> | ||||||
|     </template> |     </template> | ||||||
|   </ds-container> |   </ds-container> | ||||||
|  | @ -56,45 +68,40 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import DsContainer from '../../components/container' | import Plugin from '../../mixins/plugin' | ||||||
| import api from '../../api' | 
 | ||||||
| import status from '../../status' |  | ||||||
| export default { | export default { | ||||||
|   name: 'Node', |   name: 'Node', | ||||||
|   components: { |   mixins: [Plugin], | ||||||
|     DsContainer |  | ||||||
|   }, |  | ||||||
|   data () { |   data () { | ||||||
|     return { |     return { | ||||||
|       config: undefined, |  | ||||||
|       status: status, |  | ||||||
|       npmVariables: undefined, |       npmVariables: undefined, | ||||||
|       registry: false |       registry: false | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   created () { |   created () { | ||||||
|     api.config.reload().then(ret => { |     console.log('status:', this.status) | ||||||
|       this.config = ret |  | ||||||
|     }) |  | ||||||
|     api.plugin.node.getVariables().then(ret => { |  | ||||||
|       this.npmVariables = ret |  | ||||||
|     }) |  | ||||||
|   }, |   }, | ||||||
|   mounted () { |   mounted () { | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| 
 |     ready () { | ||||||
|  |       return this.$api.plugin.node.getVariables().then(ret => { | ||||||
|  |         console.log('variables', ret) | ||||||
|  |         this.npmVariables = ret | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|     onSwitchRegistry (event) { |     onSwitchRegistry (event) { | ||||||
|       return this.setRegistry(event.target.value).then(() => { |       return this.setRegistry(event.target.value).then(() => { | ||||||
|         this.$message.success('切换成功') |         this.$message.success('切换成功') | ||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
|     setRegistry (registry) { |     setRegistry (registry) { | ||||||
|       return api.plugin.node.setRegistry(registry) |       return this.$api.plugin.node.setRegistry(registry) | ||||||
|     }, |     }, | ||||||
|     submit () { |     setNpmVariableAll () { | ||||||
|       return api.config.set(this.config).then(() => { |       this.saveConfig().then(() => { | ||||||
|         this.$message.success('设置已保存') |         this.$api.plugin.node.setVariables() | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -7,19 +7,22 @@ | ||||||
|     </template> |     </template> | ||||||
| 
 | 
 | ||||||
|     <div v-if="config"> |     <div v-if="config"> | ||||||
|       <a-form-item label="启用系统代理" > |       <a-form-item label="启用系统代理" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|         <a-checkbox v-model="config.proxy.enabled" > |         <a-checkbox v-model="config.proxy.enabled" > | ||||||
|           自动开启系统代理 |           随应用启动 | ||||||
|         </a-checkbox> |         </a-checkbox> | ||||||
|         当前状态: |         <a-tag v-if="status.proxy.enabled" color="green"> | ||||||
|         <a-tag v-if="status.plugin.node.enabled" color="green"> |           当前已启动 | ||||||
|           已启动 |         </a-tag> | ||||||
|  |         <a-tag v-else color="red"> | ||||||
|  |           当前未启动 | ||||||
|         </a-tag> |         </a-tag> | ||||||
|       </a-form-item> |       </a-form-item> | ||||||
|     </div> |     </div> | ||||||
|     <template slot="footer"> |     <template slot="footer"> | ||||||
|       <div class="footer-bar"> |       <div class="footer-bar"> | ||||||
|         <a-button  type="primary" @click="submit()">应用</a-button> |         <a-button class="md-mr-10"   @click="reloadDefault('proxy')">恢复默认</a-button> | ||||||
|  |         <a-button  type="primary" @click="apply()">应用</a-button> | ||||||
|       </div> |       </div> | ||||||
|     </template> |     </template> | ||||||
|   </ds-container> |   </ds-container> | ||||||
|  | @ -27,34 +30,22 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import DsContainer from '../components/container' | import Plugin from '../mixins/plugin' | ||||||
| import api from '../api' |  | ||||||
| import status from '../status' |  | ||||||
| export default { | export default { | ||||||
|   name: 'Proxy', |   name: 'Proxy', | ||||||
|   components: { |   mixins: [Plugin], | ||||||
|     DsContainer |  | ||||||
|   }, |  | ||||||
|   data () { |   data () { | ||||||
|     return { |     return { | ||||||
|       config: undefined, | 
 | ||||||
|       status: status |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   created () { |   created () { | ||||||
|     api.config.reload().then(ret => { | 
 | ||||||
|       this.config = ret |  | ||||||
|     }) |  | ||||||
|   }, |   }, | ||||||
|   mounted () { |   mounted () { | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| 
 | 
 | ||||||
|     submit () { |  | ||||||
|       api.config.set(this.config).then(() => { |  | ||||||
|         this.$message.info('设置已保存') |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
|       </span> |       </span> | ||||||
|     </template> |     </template> | ||||||
| 
 | 
 | ||||||
|     <div style="height: 100%" > |     <div style="height: 100%" class="json-wrapper" > | ||||||
| 
 | 
 | ||||||
|       <a-tabs |       <a-tabs | ||||||
|         default-active-key="1" |         default-active-key="1" | ||||||
|  | @ -15,21 +15,29 @@ | ||||||
|         v-if="config" |         v-if="config" | ||||||
|       > |       > | ||||||
|         <a-tab-pane tab="基本设置" key="1"  > |         <a-tab-pane tab="基本设置" key="1"  > | ||||||
|           <a-form-item label="启用代理服务" > |           <a-form-item label="代理服务:" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|             <a-checkbox :checked="config.server.enabled" @change="config.server.enabled = $event"> |             <a-checkbox :checked="config.server.enabled" @change="config.server.enabled = $event"> | ||||||
|               自动开启代理服务 |               随应用启动 | ||||||
|             </a-checkbox> |             </a-checkbox> | ||||||
|             当前状态: |             <a-tag v-if="status.proxy.enabled" color="green"> | ||||||
|             <a-tag v-if="status.plugin.node.enabled" color="green"> |               当前已启动 | ||||||
|               已启动 |             </a-tag> | ||||||
|  |             <a-tag v-else color="red"> | ||||||
|  |               当前未启动 | ||||||
|             </a-tag> |             </a-tag> | ||||||
|           </a-form-item> |           </a-form-item> | ||||||
|           <a-form-item label="代理端口" > |           <a-form-item label="代理端口" :label-col="labelCol" :wrapper-col="wrapperCol" > | ||||||
|             <a-input v-model="config.server.port"/> |             <a-input v-model="config.server.port"/> | ||||||
|           </a-form-item> |           </a-form-item> | ||||||
|  |           <a-form-item label="校验SSL" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||||
|  |             <a-checkbox :checked="config.server.setting.NODE_TLS_REJECT_UNAUTHORIZED" @change="config.server.setting.NODE_TLS_REJECT_UNAUTHORIZED = $event"> | ||||||
|  |               NODE_TLS_REJECT_UNAUTHORIZED | ||||||
|  |             </a-checkbox> | ||||||
|  |             <div>开启此项之后,被代理应用关闭SSL校验也问题不大了</div> | ||||||
|  |           </a-form-item> | ||||||
|         </a-tab-pane> |         </a-tab-pane> | ||||||
|         <a-tab-pane tab="拦截设置" key="2"  > |         <a-tab-pane tab="拦截设置" key="2"  > | ||||||
|           <vue-json-editor  style="height:100%;" ref="editor" v-model="config.server.intercepts" mode="code" :show-btns="false" :expandedOnStart="true" @json-change="onJsonChange" ></vue-json-editor> |             <vue-json-editor  style="height:100%;" ref="editor" v-model="config.server.intercepts" mode="code" :show-btns="false" :expandedOnStart="true" @json-change="onJsonChange" ></vue-json-editor> | ||||||
|         </a-tab-pane> |         </a-tab-pane> | ||||||
|         <a-tab-pane tab="DNS设置" key="3"> |         <a-tab-pane tab="DNS设置" key="3"> | ||||||
|           <div> |           <div> | ||||||
|  | @ -61,7 +69,8 @@ | ||||||
|     </div> |     </div> | ||||||
|     <template slot="footer"> |     <template slot="footer"> | ||||||
|       <div class="footer-bar"> |       <div class="footer-bar"> | ||||||
|         <a-button  type="primary" @click="submit()">应用</a-button> |         <a-button class="md-mr-10"   @click="reloadDefault('server')">恢复默认</a-button> | ||||||
|  |         <a-button  type="primary" @click="apply()">应用</a-button> | ||||||
|       </div> |       </div> | ||||||
|     </template> |     </template> | ||||||
|   </ds-container> |   </ds-container> | ||||||
|  | @ -70,24 +79,28 @@ | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
| import vueJsonEditor from 'vue-json-editor' | import vueJsonEditor from 'vue-json-editor' | ||||||
| import DsContainer from '../components/container' | import Plugin from '../mixins/plugin' | ||||||
| import api from '../api' |  | ||||||
| import status from '../status' |  | ||||||
| export default { | export default { | ||||||
|   name: 'Server', |   name: 'Server', | ||||||
|   components: { |   components: { | ||||||
|     DsContainer, vueJsonEditor |     vueJsonEditor | ||||||
|   }, |   }, | ||||||
|  |   mixins: [Plugin], | ||||||
|   data () { |   data () { | ||||||
|     return { |     return { | ||||||
|       config: undefined, |       labelCol: { span: 4 }, | ||||||
|       status: status, |       wrapperCol: { span: 20 }, | ||||||
|       dnsMappings: [] |       dnsMappings: [] | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   created () { |   created () { | ||||||
|     api.config.reload().then(ret => { |   }, | ||||||
|       this.config = ret |   mounted () { | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     onJsonChange (json) { | ||||||
|  |     }, | ||||||
|  |     ready () { | ||||||
|       this.dnsMappings = [] |       this.dnsMappings = [] | ||||||
|       for (const key in this.config.server.dns.mapping) { |       for (const key in this.config.server.dns.mapping) { | ||||||
|         const value = this.config.server.dns.mapping[key] |         const value = this.config.server.dns.mapping[key] | ||||||
|  | @ -95,17 +108,9 @@ export default { | ||||||
|           key, value |           key, value | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|     }) |  | ||||||
|   }, |  | ||||||
|   mounted () { |  | ||||||
|   }, |  | ||||||
|   methods: { |  | ||||||
|     onJsonChange (json) { |  | ||||||
|     }, |     }, | ||||||
|     submit () { |     applyAfter () { | ||||||
|       api.config.set(this.config).then(() => { | 
 | ||||||
|         this.$message.info('设置已保存') |  | ||||||
|       }) |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,3 +3,25 @@ | ||||||
|   text-align: right; |   text-align: right; | ||||||
|   border-top:#eee 1px solid; |   border-top:#eee 1px solid; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .md-mr-5{margin-right: 5px;} | ||||||
|  | .md-mr-10{margin-right: 10px;} | ||||||
|  | .md-mr-15{margin-right: 15px;} | ||||||
|  | .md-mr-20{margin-right: 20px;} | ||||||
|  | 
 | ||||||
|  | .md-mt-5{margin-top: 5px;} | ||||||
|  | .md-mt-10{margin-top: 10px;} | ||||||
|  | .md-mt-15{margin-top: 15px;} | ||||||
|  | .md-mt-20{margin-top: 20px;} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .md-ml-5{margin-left: 5px;} | ||||||
|  | .md-ml-10{margin-left: 10px;} | ||||||
|  | .md-ml-15{margin-left: 15px;} | ||||||
|  | .md-ml-20{margin-left: 20px;} | ||||||
|  | 
 | ||||||
|  | .md-mb-5{margin-bottom: 5px;} | ||||||
|  | .md-mb-10{margin-bottom: 10px;} | ||||||
|  | .md-mb-15{margin-bottom: 15px;} | ||||||
|  | .md-mb-20{margin-bottom: 20px;} | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | const path = require('path') | ||||||
| module.exports = { | module.exports = { | ||||||
|   configureWebpack: config => { |   configureWebpack: config => { | ||||||
|     const configNew = { |     const configNew = { | ||||||
|  | @ -29,6 +30,9 @@ module.exports = { | ||||||
|             to: 'extra' |             to: 'extra' | ||||||
|           } |           } | ||||||
|         ] |         ] | ||||||
|  |       }, | ||||||
|  |       chainWebpackMainProcess (config) { | ||||||
|  |         config.entry('mitmproxy').add(path.join(__dirname, 'src/bridge/mitmproxy.js')) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -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 @@ | ||||||
|  | module.exports = require('./src') | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | { | ||||||
|  |   "name": "@docmirror/mitmproxy", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "description": "", | ||||||
|  |   "main": "src/index.js", | ||||||
|  |   "depedencies": {}, | ||||||
|  |   "keywords": [], | ||||||
|  |   "author": "docmirror.cn", | ||||||
|  |   "license": "MPL-2.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", | ||||||
|  |     "ssl-root-cas": "^1.3.1", | ||||||
|  |     "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,100 @@ | ||||||
|  | const mitmproxy = require('./lib/proxy') | ||||||
|  | const ProxyOptions = require('./options') | ||||||
|  | const config = require('./lib/proxy/common/config') | ||||||
|  | 
 | ||||||
|  | function fireError (e) { | ||||||
|  |   process.send({ type: 'error', event: e }) | ||||||
|  | } | ||||||
|  | function fireStatus (status) { | ||||||
|  |   process.send({ type: 'status', event: status }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | let server | ||||||
|  | 
 | ||||||
|  | function registerProcessListener () { | ||||||
|  |   process.on('message', function (msg) { | ||||||
|  |     console.log('child get msg: ' + JSON.stringify(msg)) | ||||||
|  |     if (msg.type === 'action') { | ||||||
|  |       api[msg.event.key](msg.event.params) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   process.on('SIGINT', () => { | ||||||
|  |     console.log('on sigint : closed ') | ||||||
|  |     process.exit(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
 | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const api = { | ||||||
|  |   async start (config) { | ||||||
|  |     const proxyOptions = ProxyOptions(config) | ||||||
|  |     console.log('proxy options:', proxyOptions) | ||||||
|  |     if (proxyOptions.setting && proxyOptions.setting.NODE_TLS_REJECT_UNAUTHORIZED === false) { | ||||||
|  |       process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' | ||||||
|  |     } else { | ||||||
|  |       process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1' | ||||||
|  |     } | ||||||
|  |     const newServer = mitmproxy.createProxy(proxyOptions, () => { | ||||||
|  |       fireStatus(true) | ||||||
|  |       console.log('代理服务已启动:127.0.0.1:' + proxyOptions.port) | ||||||
|  |     }) | ||||||
|  |     newServer.on('close', () => { | ||||||
|  |       console.log('server will closed ') | ||||||
|  |       if (server === newServer) { | ||||||
|  |         server = null | ||||||
|  |         fireStatus(false) | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |     newServer.on('error', (e) => { | ||||||
|  |       console.log('server error', e) | ||||||
|  |       // newServer = null
 | ||||||
|  |       fireError(e) | ||||||
|  |     }) | ||||||
|  |     server = newServer | ||||||
|  | 
 | ||||||
|  |     registerProcessListener() | ||||||
|  |   }, | ||||||
|  |   async  close () { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       if (server) { | ||||||
|  |         server.close((err) => { | ||||||
|  |           if (err) { | ||||||
|  |             console.log('close error', err, ',', err.code, ',', err.message, ',', err.errno) | ||||||
|  |             if (err.code === 'ERR_SERVER_NOT_RUNNING') { | ||||||
|  |               console.log('代理服务关闭成功') | ||||||
|  |               resolve() | ||||||
|  |               return | ||||||
|  |             } | ||||||
|  |             reject(err) | ||||||
|  |           } else { | ||||||
|  |             console.log('代理服务关闭成功') | ||||||
|  |             resolve() | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       } else { | ||||||
|  |         console.log('server is null') | ||||||
|  |         fireStatus(false) | ||||||
|  |         resolve() | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   ...api, | ||||||
|  |   config | ||||||
|  | } | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | const interceptors = require('./lib/interceptor') | ||||||
|  | const dnsUtil = require('./lib/dns') | ||||||
|  | const lodash = require('lodash') | ||||||
|  | function matchHostname (intercepts, hostname) { | ||||||
|  |   const interceptOpts = intercepts[hostname] | ||||||
|  |   if (interceptOpts) { | ||||||
|  |     return interceptOpts | ||||||
|  |   } | ||||||
|  |   if (!interceptOpts) { | ||||||
|  |     for (const target in intercepts) { | ||||||
|  |       if (target.indexOf('*') < 0) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       // 正则表达式匹配
 | ||||||
|  |       if (hostname.match(target)) { | ||||||
|  |         return intercepts[target] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isMatched (url, regexp) { | ||||||
|  |   return url.match(regexp) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function domainRegexply (target) { | ||||||
|  |   return target.replace(/\./g, '\\.').replace(/\*/g, '.*') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = (config) => { | ||||||
|  |   const regexpIntercepts = {} | ||||||
|  |   lodash.each(config.intercepts, (value, domain) => { | ||||||
|  |     if (domain.indexOf('*') >= 0) { | ||||||
|  |       const regDomain = domainRegexply(domain) | ||||||
|  |       regexpIntercepts[regDomain] = value | ||||||
|  |     } else { | ||||||
|  |       regexpIntercepts[domain] = value | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   const intercepts = regexpIntercepts | ||||||
|  |   const dnsMapping = config.dns.mapping | ||||||
|  |   const serverConfig = config | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     port: serverConfig.port, | ||||||
|  |     dnsConfig: { | ||||||
|  |       providers: dnsUtil.initDNS(serverConfig.dns.providers), | ||||||
|  |       mapping: dnsMapping | ||||||
|  |     }, | ||||||
|  |     sslConnectInterceptor: (req, cltSocket, head) => { | ||||||
|  |       const hostname = req.url.split(':')[0] | ||||||
|  |       return !!matchHostname(intercepts, hostname) // 配置了拦截的域名,将会被代理
 | ||||||
|  |     }, | ||||||
|  |     requestInterceptor: (rOptions, req, res, ssl, next) => { | ||||||
|  |       const hostname = rOptions.hostname | ||||||
|  |       const interceptOpts = matchHostname(intercepts, hostname) | ||||||
|  |       if (!interceptOpts) { // 该域名没有配置拦截器,直接过
 | ||||||
|  |         next() | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       for (const regexp in interceptOpts) { // 遍历拦截配置
 | ||||||
|  |         const interceptOpt = interceptOpts[regexp] | ||||||
|  |         if (regexp !== true) { | ||||||
|  |           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) { | ||||||
|  |             // 拦截失败
 | ||||||
|  |             console.error(err) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       next() | ||||||
|  |     }, | ||||||
|  |     responseInterceptor: (req, res, proxyReq, proxyRes, ssl, next) => { | ||||||
|  |       next() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | const os = require('os') | ||||||
|  | module.exports = { | ||||||
|  |   isWindows7 () { | ||||||
|  |     const version = os.release() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | const os = require('os') | ||||||
|  | const util = { | ||||||
|  |   getNodeVersion () { | ||||||
|  |     const version = process.version | ||||||
|  |     console.log(version) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | util.getNodeVersion() | ||||||
|  | module.exports = util | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,57 @@ | ||||||
|  | import lodash from "lodash"; | ||||||
|  | 
 | ||||||
|  | const defConfig = {a:{aa:1,bb:2},b:{c:2},d:[1,2,3],e:{ee:1,aa:2}} | ||||||
|  | const newConfig = {a:{aa:1,bb:2},d:[5],e:{bb:2,ee:2,aa:2}} | ||||||
|  | 
 | ||||||
|  | const result = { d: [ 5 ],e:{ee:2} } | ||||||
|  | 
 | ||||||
|  | const load = {a:1,d:[5,1,2,3]} | ||||||
|  | const DELETE =  '____DELETE____' | ||||||
|  | // lodash.mergeWith(defConfig,newConfig, (objValue, srcValue, key, object, source, stack) => {
 | ||||||
|  | //     console.log('stack', stack,'key',key)
 | ||||||
|  | //
 | ||||||
|  | //     if (lodash.isArray(srcValue)) {
 | ||||||
|  | //         return srcValue
 | ||||||
|  | //     }
 | ||||||
|  | //     if(lodash.isEqual(objValue,srcValue)){
 | ||||||
|  | //        //如何删除
 | ||||||
|  | //         return DELETE
 | ||||||
|  | //     }
 | ||||||
|  | // })
 | ||||||
|  | 
 | ||||||
|  | function doMerge (defObj, newObj) { | ||||||
|  |     const defObj2 = { ...defObj } | ||||||
|  |     const newObj2 = {} | ||||||
|  |     lodash.forEach(newObj,(newValue,key)=>{ | ||||||
|  |        // const newValue = newObj[key]
 | ||||||
|  |         const defValue = defObj[key] | ||||||
|  |         if (lodash.isEqual(newValue, defValue)) { | ||||||
|  |             delete defObj2[key] | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (lodash.isArray(newValue)) { | ||||||
|  |             delete defObj2[key] | ||||||
|  |             newObj2[key] = newValue | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         if (lodash.isObject(newValue)) { | ||||||
|  |             newObj2[key] = doMerge(defValue, newValue) | ||||||
|  |             delete defObj2[key] | ||||||
|  |         } else { | ||||||
|  |             // 基础类型,直接覆盖
 | ||||||
|  |             delete defObj2[key] | ||||||
|  |             newObj2[key] = newValue | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     // defObj 里面剩下的是被删掉的
 | ||||||
|  |     lodash.forEach(defObj2, (defValue, key) => { | ||||||
|  |         newObj2[key] = null | ||||||
|  |     }) | ||||||
|  |     return newObj2 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | console.log(doMerge(defConfig,newConfig)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
		Loading…
	
		Reference in New Issue
	
	 xiaojunnuo
						xiaojunnuo