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", | ||||
|     "validator": "^13.1.17", | ||||
|     "vue": "^2.6.11", | ||||
|     "winreg": "^1.2.4" | ||||
|     "winreg": "^1.2.4", | ||||
|     "@docmirror/mitmproxy": "1.0.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@vue/cli-plugin-babel": "~4.5.0", | ||||
|  |  | |||
|  | @ -1,15 +1,17 @@ | |||
| const Shell = require('./shell') | ||||
| const lodash = require('lodash') | ||||
| const defConfig = require('./config/index.js') | ||||
| const proxyConfig = require('./lib/proxy/common/config') | ||||
| const proxyServer = require('@docmirror/mitmproxy') | ||||
| let configTarget = lodash.cloneDeep(defConfig) | ||||
| function _deleteDisabledItem (target, objKey) { | ||||
|   const obj = lodash.get(target, objKey) | ||||
|   for (const key in obj) { | ||||
|     if (obj[key] === false) { | ||||
|       delete obj[key] | ||||
| function _deleteDisabledItem (target) { | ||||
|   lodash.forEach(target, (item, key) => { | ||||
|     if (item == null) { | ||||
|       delete target[key] | ||||
|     } | ||||
|   } | ||||
|     if (lodash.isObject(item)) { | ||||
|       _deleteDisabledItem(item) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| const configApi = { | ||||
|   get () { | ||||
|  | @ -24,8 +26,7 @@ const configApi = { | |||
|     lodash.merge(merged, clone) | ||||
|     lodash.merge(merged, newConfig) | ||||
| 
 | ||||
|     _deleteDisabledItem(merged, 'intercepts') | ||||
|     _deleteDisabledItem(merged, 'dns.mapping') | ||||
|     _deleteDisabledItem(merged) | ||||
|     configTarget = merged | ||||
|     return configTarget | ||||
|   }, | ||||
|  | @ -35,8 +36,15 @@ const configApi = { | |||
|   addDefault (key, defValue) { | ||||
|     lodash.set(defConfig, key, defValue) | ||||
|   }, | ||||
|   resetDefault () { | ||||
|     configTarget = lodash.cloneDeep(defConfig) | ||||
|   resetDefault (key) { | ||||
|     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) { | ||||
|     const method = type === 'npm' ? Shell.getNpmEnv : Shell.getSystemEnv | ||||
|  | @ -60,7 +68,7 @@ const configApi = { | |||
|     }) | ||||
|     if (list.length > 0) { | ||||
|       const context = { | ||||
|         ca_cert_path: proxyConfig.getDefaultCACertPath() | ||||
|         ca_cert_path: proxyServer.config.getDefaultCACertPath() | ||||
|       } | ||||
|       for (const item of noSetList) { | ||||
|         if (item.value.indexOf('${') >= 0) { | ||||
|  |  | |||
|  | @ -2,95 +2,83 @@ module.exports = { | |||
|   server: { | ||||
|     enabled: true, | ||||
|     port: 1181, | ||||
|     setting: { | ||||
|       NODE_TLS_REJECT_UNAUTHORIZED: true | ||||
|     }, | ||||
|     intercepts: { | ||||
|       'github.com': [ | ||||
|         { | ||||
|           // "release archive 下载链接替换",
 | ||||
|           regexp: [ | ||||
|             '/.*/.*/releases/download/', | ||||
|             '/.*/.*/archive/' | ||||
|           ], | ||||
|       'github.com': { | ||||
|         '/.*/.*/releases/download/': { | ||||
|           redirect: 'download.fastgit.org' | ||||
|         }, | ||||
|         { | ||||
|           regexp: [ | ||||
|             '/.*/.*/raw/', | ||||
|             '/.*/.*/blame/' | ||||
|           ], | ||||
|         '/.*/.*/archive/': { | ||||
|           redirect: 'download.fastgit.org' | ||||
|         }, | ||||
|         '/.*/.*/raw/': { | ||||
|           redirect: 'hub.fastgit.org' | ||||
|         }, | ||||
|         '/.*/.*/blame/': { | ||||
|           redirect: 'hub.fastgit.org' | ||||
|         } | ||||
|       ], | ||||
|       // 'codeload.github.com': [
 | ||||
|       //     {
 | ||||
|       //         regexp: '.*',
 | ||||
|       //         redirect:"download.fastgit.org"
 | ||||
|       //     }
 | ||||
|       // ],
 | ||||
|       'raw.githubusercontent.com': [{ proxy: 'raw.fastgit.org' }], | ||||
|       'github.githubassets.com': [ | ||||
|         { | ||||
|           proxy: 'assets.fastgit.org' | ||||
|         } | ||||
|       ], | ||||
|       'customer-stories-feed.github.com': [ | ||||
|         { | ||||
|           proxy: 'customer-stories-feed.fastgit.org' | ||||
|         } | ||||
|       ], | ||||
|       }, | ||||
|       'raw.githubusercontent.com': { | ||||
|         '.*': { proxy: 'raw.fastgit.org' } | ||||
|       }, | ||||
|       'github.githubassets.com': { | ||||
|         '.*': { proxy: 'assets.fastgit.org' } | ||||
|       }, | ||||
|       'customer-stories-feed.github.com': { | ||||
|         '.*': { proxy: 'customer-stories-feed.fastgit.org' } | ||||
|       }, | ||||
| 
 | ||||
|       // google cdn
 | ||||
|       'ajax.googleapis.com': [ | ||||
|         { | ||||
|       'ajax.googleapis.com': { | ||||
|         '.*': { | ||||
|           proxy: 'ajax.loli.net', | ||||
|           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', | ||||
|           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', | ||||
|           backup: ['themes.proxy.ustclug.org'] | ||||
|         } | ||||
|       ], | ||||
|       'themes.googleusercontent.com': [ | ||||
|         { proxy: 'google-themes.proxy.ustclug.org' } | ||||
|       ], | ||||
|       'www.google.com': [ | ||||
|         { | ||||
|           regexp: '/recaptcha/.*', | ||||
|           proxy: 'www.recaptcha.net' | ||||
|         } | ||||
|       ], | ||||
|       'fonts.gstatic.com': [ | ||||
|         { | ||||
|       }, | ||||
|       'themes.googleusercontent.com': { | ||||
|         '.*': { proxy: 'google-themes.proxy.ustclug.org' } | ||||
|       }, | ||||
|       'www.google.com': { | ||||
|         '/recaptcha/.*': { proxy: 'www.recaptcha.net' } | ||||
|       }, | ||||
|       'fonts.gstatic.com': { | ||||
|         '.*': { | ||||
|           proxy: 'fonts-gstatic.proxy.ustclug.org', | ||||
|           backup: ['gstatic.loli.net'] | ||||
|         } | ||||
|       ], | ||||
|       'clients*.google.com': [{ abort: true }], | ||||
|       'www.googleapis.com': [{ abort: true }], | ||||
|       'lh*.googleusercontent.com': [{ abort: true }], | ||||
|       }, | ||||
|       'clients*.google.com': { '.*': { abort: true } }, | ||||
|       'www.googleapis.com': { '.*': { abort: true } }, | ||||
|       'lh*.googleusercontent.com': { '.*': { abort: true } }, | ||||
|       // mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.0/napi-v3-win32-x64.tar.gz
 | ||||
|       '*.s3.amazonaws.com': [ | ||||
|         { | ||||
|           regexp: '/sqlite3/.*', | ||||
|       '*.s3.amazonaws.com': { | ||||
|         '/sqlite3/.*': { | ||||
|           redirect: 'npm.taobao.org/mirrors' | ||||
|         } | ||||
|       ], | ||||
|       'registry-1.docker.io': [{ proxy: 'docker.mirrors.ustc.edu.cn' }], | ||||
|       'packages.elastic.co': [{ proxy: 'elastic.proxy.ustclug.org' }], | ||||
|       'ppa.launchpad.net': [{ proxy: 'launchpad.proxy.ustclug.org' }], | ||||
|       'archive.cloudera.com': [{ regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' }], | ||||
|       'downloads.lede-project.org': [{ proxy: 'lede.proxy.ustclug.org' }], | ||||
|       'downloads.openwrt.org': [{ proxy: 'openwrt.proxy.ustclug.org' }], | ||||
|       'secure.gravatar.com': [{ proxy: 'gravatar.proxy.ustclug.org' }] | ||||
|       }, | ||||
|       'registry-1.docker.io': { '.*': { proxy: 'docker.mirrors.ustc.edu.cn' } }, | ||||
|       'packages.elastic.co': { '.*': { proxy: 'elastic.proxy.ustclug.org' } }, | ||||
|       'ppa.launchpad.net': { '.*': { proxy: 'launchpad.proxy.ustclug.org' } }, | ||||
|       'archive.cloudera.com': { '.*': { regexp: '/cdh5/.*', proxy: 'cloudera.proxy.ustclug.org' } }, | ||||
|       'downloads.lede-project.org': { '.*': { proxy: 'lede.proxy.ustclug.org' } }, | ||||
|       'downloads.openwrt.org': { '.*': { proxy: 'openwrt.proxy.ustclug.org' } }, | ||||
|       'secure.gravatar.com': { '.*': { proxy: 'gravatar.proxy.ustclug.org' } } | ||||
|     }, | ||||
|     dns: { | ||||
|       providers: { | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ const config = require('./config') | |||
| const event = require('./event') | ||||
| const shell = require('./shell') | ||||
| const modules = require('./modules') | ||||
| const proxyConfig = require('./lib/proxy/common/config') | ||||
| const lodash = require('lodash') | ||||
| const proxyServer = require('@docmirror/mitmproxy') | ||||
| const proxyConfig = proxyServer.config | ||||
| const context = { | ||||
|   config, | ||||
|   shell, | ||||
|  | @ -23,10 +24,6 @@ function setupPlugin (key, plugin, context, config) { | |||
|   return api | ||||
| } | ||||
| 
 | ||||
| function fireStatus (target) { | ||||
|   event.fire('status', target) | ||||
| } | ||||
| 
 | ||||
| const server = modules.server | ||||
| const proxy = setupPlugin('proxy', modules.proxy, context, config) | ||||
| const plugin = {} | ||||
|  | @ -40,14 +37,11 @@ config.resetDefault() | |||
| module.exports = { | ||||
|   status, | ||||
|   api: { | ||||
|     startup: async (newConfig) => { | ||||
|       if (newConfig) { | ||||
|         config.set(newConfig) | ||||
|       } | ||||
|     startup: async ({ mitmproxyPath }) => { | ||||
|       const conf = config.get() | ||||
|       if (conf.server.enabled) { | ||||
|         try { | ||||
|           await server.start() | ||||
|           await server.start({ mitmproxyPath }) | ||||
|         } catch (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 = { | ||||
|   name: 'NPM加速', | ||||
|   enabled: true, | ||||
|   enabled: false, | ||||
|   startup: { | ||||
|     npm: true, | ||||
|     yarn: true, | ||||
|     variables: true | ||||
|   }, | ||||
|   setting: { | ||||
|     'strict-ssl': false, | ||||
|     cafile: true, | ||||
|     NODE_EXTRA_CA_CERTS: true, | ||||
|     'strict-ssl': true, | ||||
|     cafile: false, | ||||
|     NODE_EXTRA_CA_CERTS: false, | ||||
|     NODE_TLS_REJECT_UNAUTHORIZED: false, | ||||
|     registry: 'https://registry.npmjs.org'// 可以选择切换官方或者淘宝镜像
 | ||||
|   }, | ||||
|  |  | |||
|  | @ -83,7 +83,6 @@ const NodePlugin = function (context) { | |||
| 
 | ||||
|     async setRegistry (registry) { | ||||
|       await nodeApi.setNpmEnv([{ key: 'registry', value: registry }]) | ||||
|       console.log(1111) | ||||
|       return true | ||||
|     }, | ||||
| 
 | ||||
|  | @ -103,7 +102,7 @@ const NodePlugin = function (context) { | |||
|        */ | ||||
|       const nodeConfig = config.get().plugin.node | ||||
|       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) { | ||||
|         cmds.push(`npm config set cafile "${rootCaFile}"`) | ||||
|  | @ -115,13 +114,13 @@ const NodePlugin = function (context) { | |||
|       } | ||||
| 
 | ||||
|       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' }) | ||||
|       } | ||||
| 
 | ||||
|       const ret = await shell.exec(cmds, { type: 'cmd' }) | ||||
|       if (env.length > 0) { | ||||
|         //  await shell.setSystemEnv({ list: env })
 | ||||
|         await shell.setSystemEnv({ list: env }) | ||||
|       } | ||||
|       event.fire('status', { key: 'plugin.node.enabled', value: true }) | ||||
|       console.info('开启【NPM】代理成功') | ||||
|  |  | |||
|  | @ -39,12 +39,7 @@ module.exports = { | |||
|     enabled: true, | ||||
|     name: '系统代理', | ||||
|     use: 'local', | ||||
|     other: { | ||||
|       host: undefined, | ||||
|       port: undefined, | ||||
|       username: undefined, | ||||
|       password: undefined | ||||
|     } | ||||
|     other: [] | ||||
|   }, | ||||
|   status: { | ||||
|     enabled: false, | ||||
|  |  | |||
|  | @ -1,10 +1,19 @@ | |||
| const ProxyOptions = require('./options') | ||||
| const mitmproxy = require('../../lib/proxy') | ||||
| const config = require('../../config') | ||||
| const event = require('../../event') | ||||
| const status = require('../../status') | ||||
| const shell = require('../../shell') | ||||
| const lodash = require('lodash') | ||||
| const fork = require('child_process').fork | ||||
| 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 = { | ||||
|   async startup () { | ||||
|     if (config.get().server.startup) { | ||||
|  | @ -16,66 +25,73 @@ const serverApi = { | |||
|       return this.close() | ||||
|     } | ||||
|   }, | ||||
|   async start (newConfig) { | ||||
|     if (server != null) { | ||||
|       server.close() | ||||
|   async start ({ mitmproxyPath }) { | ||||
|     const allConfig = config.get() | ||||
|     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) | ||||
|     const proxyOptions = ProxyOptions(config.get()) | ||||
|     const newServer = mitmproxy.createProxy(proxyOptions, () => { | ||||
|       event.fire('status', { key: 'server.enabled', value: true }) | ||||
|       console.log('代理服务已启动:127.0.0.1:' + proxyOptions.port) | ||||
|     }) | ||||
|     newServer.on('close', () => { | ||||
|       if (server === newServer) { | ||||
|         server = null | ||||
|         event.fire('status', { key: 'server.enabled', value: false }) | ||||
|     // fireStatus('ing') // 启动中
 | ||||
|     const serverProcess = fork(mitmproxyPath, [JSON.stringify(serverConfig)]) | ||||
|     server = { | ||||
|       id: serverProcess.pid, | ||||
|       process: serverProcess, | ||||
|       close () { | ||||
|         serverProcess.send({ type: 'action', event: { key: 'close' } }) | ||||
|       } | ||||
|     } | ||||
|     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) => { | ||||
|       console.log('server error', e) | ||||
|       // newServer = null
 | ||||
|       event.fire('error', { key: 'server', error: e }) | ||||
|     }) | ||||
|     newServer.config = proxyOptions | ||||
|     server = newServer | ||||
|     return { port: proxyOptions.port } | ||||
|     return { port: config.port } | ||||
|   }, | ||||
|   async kill () { | ||||
|     if (server) { | ||||
|       server.process.kill('SIGINT') | ||||
|       await sleep(1000) | ||||
|     } | ||||
|     fireStatus(false) | ||||
|   }, | ||||
|   async close () { | ||||
|     return await serverApi.kill() | ||||
|   }, | ||||
|   async close1 () { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       if (server) { | ||||
|         const currentServer = server | ||||
|         let closed = false | ||||
|         // fireStatus('ing')// 关闭中
 | ||||
|         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('代理服务关闭成功') | ||||
|               closed = true | ||||
|               resolve() | ||||
|               return | ||||
|             } | ||||
|             console.log('代理服务关闭失败', err) | ||||
|             reject(err) | ||||
|           } else { | ||||
|             console.log('代理服务关闭成功') | ||||
|             closed = true | ||||
|             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 { | ||||
|         console.log('server is null') | ||||
|         resolve() | ||||
|  | @ -83,11 +99,7 @@ const serverApi = { | |||
|     }) | ||||
|   }, | ||||
|   async restart () { | ||||
|     try { | ||||
|       await serverApi.close() | ||||
|     } catch (err) { | ||||
|       console.log('stop error', err) | ||||
|     } | ||||
|     await serverApi.kill() | ||||
|     await serverApi.start() | ||||
|   }, | ||||
|   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, | ||||
|   getNpmEnv, | ||||
|   setNpmEnv, | ||||
|   exec (cmds, args) { | ||||
|     shell.getSystemShell().exec(cmds, args) | ||||
|   async exec (cmds, args) { | ||||
|     return shell.getSystemShell().exec(cmds, args) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| const Shell = require('../shell') | ||||
| const execute = Shell.execute | ||||
| const proxyConfig = require('../../lib/proxy/common/config') | ||||
| const proxyServer = require('@docmirror/mitmproxy') | ||||
| const executor = { | ||||
|   async windows (exec) { | ||||
|     const cmds = ['start ' + proxyConfig.getDefaultCACertPath()] | ||||
|     const cmds = ['start ' + proxyServer.config.getDefaultCACertPath()] | ||||
|     // eslint-disable-next-line no-unused-vars
 | ||||
|     const ret = await exec(cmds, { type: 'cmd' }) | ||||
|     return true | ||||
|  |  | |||
|  | @ -34,8 +34,9 @@ class DarwinSystemShell extends SystemShell { | |||
| } | ||||
| 
 | ||||
| class WindowsSystemShell extends SystemShell { | ||||
|   static async exec (cmds, args = { type: 'ps' }) { | ||||
|     const { type } = args | ||||
|   static async exec (cmds, args = { }) { | ||||
|     let { type } = args | ||||
|     type = type || 'ps' | ||||
|     if (cmds instanceof String) { | ||||
|       cmds = [cmds] | ||||
|     } | ||||
|  | @ -50,28 +51,33 @@ class WindowsSystemShell extends SystemShell { | |||
|       } | ||||
| 
 | ||||
|       const ret = await ps.invoke() | ||||
|       console.log('ps complete:', cmds, ret) | ||||
|       // console.log('ps complete:', cmds, ret)
 | ||||
|       return ret | ||||
|     } else { | ||||
|       let compose = 'chcp 65001  ' | ||||
|       for (const cmd of cmds) { | ||||
|         compose += ' && ' + cmd | ||||
|       } | ||||
|       return new Promise((resolve, reject) => { | ||||
|         childProcess.exec(compose, function (error, stdout, stderr) { | ||||
|           if (error) { | ||||
|             console.error('cmd 命令执行错误:', compose, error, stderr) | ||||
|             reject(error) | ||||
|           } else { | ||||
|             const data = stdout | ||||
|             resolve(data) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|       const ret = await childExec(compose) | ||||
|       // console.log('cmd complete:', cmds)
 | ||||
|       return ret | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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 () { | ||||
|   switch (getSystemPlatform()) { | ||||
|     case 'mac': | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ class WindowsSystemProxy extends SystemProxy { | |||
| 
 | ||||
|       ps.invoke() | ||||
|         .then(output => { | ||||
|           console.log(output) | ||||
|           // console.log(output)
 | ||||
|           resolve() | ||||
|         }) | ||||
|         .catch(err => { | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ const DevSidercar = require('.') | |||
| // require('json5/lib/register')
 | ||||
| // const config = require('../../config/index.json5')
 | ||||
| // 启动服务
 | ||||
| DevSidercar.api.startup() | ||||
| const mitmproxyPath = './mitmproxy' | ||||
| DevSidercar.api.startup({ mitmproxyPath }) | ||||
| async function onClose () { | ||||
|   console.log('on sigint ') | ||||
|   await DevSidercar.api.shutdown() | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ | |||
|   "main": "background.js", | ||||
|   "dependencies": { | ||||
|     "@docmirror/dev-sidecar": "1.0.0", | ||||
|     "@docmirror/mitmproxy": "1.0.0", | ||||
|     "ant-design-vue": "^1.6.5", | ||||
|     "core-js": "^3.6.5", | ||||
|     "electron-updater": "^4.3.5", | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <html lang="en"  style="height:100%"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|  | @ -7,11 +7,13 @@ | |||
|     <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||
|     <title><%= htmlWebpackPlugin.options.title %></title> | ||||
|   </head> | ||||
|   <body> | ||||
|   <body  style="height:100%"> | ||||
|     <noscript> | ||||
|       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | ||||
|     </noscript> | ||||
|     <div id="app"></div> | ||||
|     <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 --> | ||||
|   </body> | ||||
| </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 fs from 'fs' | ||||
| import JSON5 from 'json5' | ||||
| 
 | ||||
| import path from 'path' | ||||
| const mitmproxyPath = path.join(__dirname, 'mitmproxy.js') | ||||
| const localApi = { | ||||
|   getApiList () { | ||||
|     const core = lodash.cloneDeep(DevSidecar.api) | ||||
|  | @ -14,6 +15,14 @@ const localApi = { | |||
|     console.log('api list:', list) | ||||
|     return list | ||||
|   }, | ||||
|   startup () { | ||||
|     return DevSidecar.api.startup({ mitmproxyPath }) | ||||
|   }, | ||||
|   server: { | ||||
|     start () { | ||||
|       return DevSidecar.api.server.start({ mitmproxyPath }) | ||||
|     } | ||||
|   }, | ||||
|   config: { | ||||
|     /** | ||||
|      * 保存自定义的 config | ||||
|  | @ -22,13 +31,12 @@ const localApi = { | |||
|     save (newConfig) { | ||||
|       // 对比默认config的异同
 | ||||
|       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, 'setting.startup.server', true) | ||||
|       _merge(defConfig, newConfig, saveConfig, 'setting.startup.proxy') | ||||
|       // _merge(defConfig, newConfig, saveConfig, 'intercepts')
 | ||||
|       // _merge(defConfig, newConfig, saveConfig, 'dns.mapping')
 | ||||
|       // _merge(defConfig, newConfig, saveConfig, 'setting.startup.server', true)
 | ||||
|       // _merge(defConfig, newConfig, saveConfig, 'setting.startup.proxy')
 | ||||
| 
 | ||||
|       fs.writeFileSync(_getConfigPath(), JSON5.stringify(saveConfig, null, 2)) | ||||
|       return saveConfig | ||||
|  | @ -66,39 +74,42 @@ function _getConfigPath () { | |||
|   return dir + 'config.json5' | ||||
| } | ||||
| 
 | ||||
| function _merge (defConfig, newConfig, saveConfig, target, self = false) { | ||||
|   if (self) { | ||||
|     const defValue = lodash.get(defConfig, target) | ||||
|     const newValue = lodash.get(newConfig, target) | ||||
|     if (newValue != null && newValue !== defValue) { | ||||
|       lodash.set(saveConfig, newValue, target) | ||||
|     } | ||||
|     return | ||||
|   } | ||||
|   const saveObj = _mergeConfig(lodash.get(defConfig, target), lodash.get(newConfig, target)) | ||||
|   lodash.set(saveConfig, target, saveObj) | ||||
| } | ||||
| 
 | ||||
| function _mergeConfig (defObj, newObj) { | ||||
|   for (const key in defObj) { | ||||
|     // 从默认里面提取对比,是否有被删除掉的
 | ||||
|     if (newObj[key] == null) { | ||||
|       newObj[key] = false | ||||
|     } | ||||
|   } | ||||
| function doMerge (defObj, newObj) { | ||||
|   const defObj2 = { ...defObj } | ||||
|   const newObj2 = {} | ||||
|   for (const key in newObj) { | ||||
|     const newItem = newObj[key] | ||||
|     const defItem = defObj[key] | ||||
|     if (newItem && !defItem) { | ||||
|     const newValue = newObj[key] | ||||
|     const defValue = defObj[key] | ||||
|     if (newValue != null && defValue == null) { | ||||
|       newObj2[key] = newValue | ||||
|       continue | ||||
|     } | ||||
|     // 深度对比 是否有修改
 | ||||
|     if (lodash.isEqual(newItem, defItem)) { | ||||
|       // 没有修改则删除
 | ||||
|       delete newObj[key] | ||||
|     if (lodash.isEqual(newValue, defValue)) { | ||||
|       delete defObj2[key] | ||||
|       continue | ||||
|     } | ||||
| 
 | ||||
|     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 { | ||||
|  | @ -133,7 +144,7 @@ export default { | |||
| 
 | ||||
|     // 合并用户配置
 | ||||
|     localApi.config.reload() | ||||
|     DevSidecar.api.startup() | ||||
|     localApi.startup() | ||||
|   }, | ||||
|   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) => { | ||||
|   Vue.prototype.$api = api | ||||
| 
 | ||||
|   const app = new Vue({ | ||||
|     router, | ||||
|     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 class="big_button"> | ||||
|           <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" width="50" src="/logo/logo-fff.svg"> | ||||
|             <img v-if="!startup.loading && !status.server.enabled" width="50" src="/logo/logo-simple.svg"> | ||||
|             <img v-if="!startup.loading && status.server.enabled" width="50" src="/logo/logo-fff.svg"> | ||||
|           </a-button> | ||||
|           <div style="margin-top: 10px">{{ status.server ? '已开启' : '已关闭' }}</div> | ||||
|           <div style="margin-top: 10px">{{ status.server.enabled ? '已开启' : '已关闭' }}</div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div :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-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="unCheckedChildren" type="close"/> | ||||
|             </a-switch> | ||||
|  | @ -30,6 +30,7 @@ | |||
|         </a-form> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
|     <setup-ca title="安装证书" :visible.sync="setupCa.visible"></setup-ca> | ||||
|   </ds-container> | ||||
|  | @ -51,19 +52,14 @@ export default { | |||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       status: { | ||||
|         proxy: {}, | ||||
|         plugin: { | ||||
|           node: {} | ||||
|         } | ||||
|       }, | ||||
|       status: status, | ||||
|       startup: { | ||||
|         loading: false, | ||||
|         type: () => { | ||||
|           return this.status.server ? 'primary' : 'default' | ||||
|           return (this.status.server && this.status.server.enabled) ? 'primary' : 'default' | ||||
|         }, | ||||
|         doClick: () => { | ||||
|           if (this.status.server) { | ||||
|           if (this.status.server.enabled) { | ||||
|             this.apiCall(this.startup, api.shutdown) | ||||
|           } else { | ||||
|             this.apiCall(this.startup, api.startup) | ||||
|  | @ -192,4 +188,5 @@ export default { | |||
| div.ant-form-item { | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -7,19 +7,27 @@ | |||
|     </template> | ||||
| 
 | ||||
|     <div v-if="config"> | ||||
|       <div> | ||||
|         <a-form-item label="启用NPM加速插件" > | ||||
|           <a-checkbox v-model="config.plugin.node.enabled" > | ||||
|             自动开启NPM加速 | ||||
|       <a-form layout="horizontal"> | ||||
|         <a-form-item label="启用NPM代理" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||
|           <a-checkbox v-model="config.plugin.node.enabled"> | ||||
|             随应用启动 | ||||
|           </a-checkbox> | ||||
|           当前状态: | ||||
|           <a-tag v-if="status.plugin.node.enabled" color="green"> | ||||
|             已启动 | ||||
|             当前已启动 | ||||
|           </a-tag> | ||||
|           <a-tag v-else color="red"> | ||||
|             当前未启动 | ||||
|           </a-tag> | ||||
|         </a-form-item> | ||||
| 
 | ||||
|         <a-form-item label="切换registry" > | ||||
|           <a-radio-group v-model="config.plugin.node.setting.registry" @change="onSwitchRegistry" default-value="https://registry.npmjs.org" button-style="solid"> | ||||
|         <a-form-item label="SSL相关" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||
|           <a-checkbox v-model="config.plugin.node.setting['strict-ssl']"> | ||||
|             关闭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"> | ||||
|               npmjs | ||||
|             </a-radio-button> | ||||
|  | @ -28,27 +36,31 @@ | |||
|             </a-radio-button> | ||||
|           </a-radio-group> | ||||
|         </a-form-item> | ||||
|       </div> | ||||
| 
 | ||||
|       <div> | ||||
|         <div>某些库需要自己设置镜像变量,才能下载,比如:electron</div> | ||||
|         <a-row :gutter="10" style="margin-top: 10px" v-for="(item,index) of npmVariables" :key = 'index'> | ||||
|           <a-col :span="10"> | ||||
|             <a-input :disabled="item.key ===false" v-model="item.key"></a-input> | ||||
|           </a-col> | ||||
|           <a-col :span="10"> | ||||
|             <a-input :disabled="item.value ===false" v-model="item.value"></a-input> | ||||
|           </a-col> | ||||
|           <a-col :span="4"> | ||||
|             <a-icon v-if="item.exists" style="color:green" type="check" /> | ||||
|             <a-icon v-if="!item.exists || !item.set" title="还未设置" style="color:red" type="exclamation-circle" /> | ||||
|           </a-col> | ||||
|         </a-row> | ||||
|       </div> | ||||
|         <a-form-item label="镜像变量设置" :label-col="labelCol" :wrapper-col="wrapperCol"> | ||||
|           <a-checkbox v-model="config.plugin.node.startup.variables"> | ||||
|             自动设置 | ||||
|           </a-checkbox> | ||||
|           <div>某些库需要自己设置镜像变量,才能下载,比如:electron</div> | ||||
|           <a-row :gutter="10" style="margin-top: 10px" v-for="(item,index) of npmVariables" :key='index'> | ||||
|             <a-col :span="10"> | ||||
|               <a-input :disabled="item.key ===false" v-model="item.key"></a-input> | ||||
|             </a-col> | ||||
|             <a-col :span="10"> | ||||
|               <a-input :disabled="item.value ===false" v-model="item.value"></a-input> | ||||
|             </a-col> | ||||
|             <a-col :span="4"> | ||||
|               <a-icon v-if="item.exists&& item.hadSet" title="已设置" style="color:green" type="check"/> | ||||
|               <a-icon v-else title="还未设置" style="color:red" type="exclamation-circle"/> | ||||
|             </a-col> | ||||
|           </a-row> | ||||
|         </a-form-item> | ||||
|       </a-form> | ||||
|     </div> | ||||
|     <template slot="footer"> | ||||
|       <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> | ||||
|     </template> | ||||
|   </ds-container> | ||||
|  | @ -56,45 +68,40 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import DsContainer from '../../components/container' | ||||
| import api from '../../api' | ||||
| import status from '../../status' | ||||
| import Plugin from '../../mixins/plugin' | ||||
| 
 | ||||
| export default { | ||||
|   name: 'Node', | ||||
|   components: { | ||||
|     DsContainer | ||||
|   }, | ||||
|   mixins: [Plugin], | ||||
|   data () { | ||||
|     return { | ||||
|       config: undefined, | ||||
|       status: status, | ||||
|       npmVariables: undefined, | ||||
|       registry: false | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     api.config.reload().then(ret => { | ||||
|       this.config = ret | ||||
|     }) | ||||
|     api.plugin.node.getVariables().then(ret => { | ||||
|       this.npmVariables = ret | ||||
|     }) | ||||
|     console.log('status:', this.status) | ||||
|   }, | ||||
|   mounted () { | ||||
|   }, | ||||
|   methods: { | ||||
| 
 | ||||
|     ready () { | ||||
|       return this.$api.plugin.node.getVariables().then(ret => { | ||||
|         console.log('variables', ret) | ||||
|         this.npmVariables = ret | ||||
|       }) | ||||
|     }, | ||||
|     onSwitchRegistry (event) { | ||||
|       return this.setRegistry(event.target.value).then(() => { | ||||
|         this.$message.success('切换成功') | ||||
|       }) | ||||
|     }, | ||||
|     setRegistry (registry) { | ||||
|       return api.plugin.node.setRegistry(registry) | ||||
|       return this.$api.plugin.node.setRegistry(registry) | ||||
|     }, | ||||
|     submit () { | ||||
|       return api.config.set(this.config).then(() => { | ||||
|         this.$message.success('设置已保存') | ||||
|     setNpmVariableAll () { | ||||
|       this.saveConfig().then(() => { | ||||
|         this.$api.plugin.node.setVariables() | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -7,19 +7,22 @@ | |||
|     </template> | ||||
| 
 | ||||
|     <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> | ||||
|         当前状态: | ||||
|         <a-tag v-if="status.plugin.node.enabled" color="green"> | ||||
|           已启动 | ||||
|         <a-tag v-if="status.proxy.enabled" color="green"> | ||||
|           当前已启动 | ||||
|         </a-tag> | ||||
|         <a-tag v-else color="red"> | ||||
|           当前未启动 | ||||
|         </a-tag> | ||||
|       </a-form-item> | ||||
|     </div> | ||||
|     <template slot="footer"> | ||||
|       <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> | ||||
|     </template> | ||||
|   </ds-container> | ||||
|  | @ -27,34 +30,22 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import DsContainer from '../components/container' | ||||
| import api from '../api' | ||||
| import status from '../status' | ||||
| import Plugin from '../mixins/plugin' | ||||
| export default { | ||||
|   name: 'Proxy', | ||||
|   components: { | ||||
|     DsContainer | ||||
|   }, | ||||
|   mixins: [Plugin], | ||||
|   data () { | ||||
|     return { | ||||
|       config: undefined, | ||||
|       status: status | ||||
| 
 | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     api.config.reload().then(ret => { | ||||
|       this.config = ret | ||||
|     }) | ||||
| 
 | ||||
|   }, | ||||
|   mounted () { | ||||
|   }, | ||||
|   methods: { | ||||
| 
 | ||||
|     submit () { | ||||
|       api.config.set(this.config).then(() => { | ||||
|         this.$message.info('设置已保存') | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
|       </span> | ||||
|     </template> | ||||
| 
 | ||||
|     <div style="height: 100%" > | ||||
|     <div style="height: 100%" class="json-wrapper" > | ||||
| 
 | ||||
|       <a-tabs | ||||
|         default-active-key="1" | ||||
|  | @ -15,21 +15,29 @@ | |||
|         v-if="config" | ||||
|       > | ||||
|         <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> | ||||
|             当前状态: | ||||
|             <a-tag v-if="status.plugin.node.enabled" color="green"> | ||||
|               已启动 | ||||
|             <a-tag v-if="status.proxy.enabled" color="green"> | ||||
|               当前已启动 | ||||
|             </a-tag> | ||||
|             <a-tag v-else color="red"> | ||||
|               当前未启动 | ||||
|             </a-tag> | ||||
|           </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-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 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 tab="DNS设置" key="3"> | ||||
|           <div> | ||||
|  | @ -61,7 +69,8 @@ | |||
|     </div> | ||||
|     <template slot="footer"> | ||||
|       <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> | ||||
|     </template> | ||||
|   </ds-container> | ||||
|  | @ -70,24 +79,28 @@ | |||
| 
 | ||||
| <script> | ||||
| import vueJsonEditor from 'vue-json-editor' | ||||
| import DsContainer from '../components/container' | ||||
| import api from '../api' | ||||
| import status from '../status' | ||||
| import Plugin from '../mixins/plugin' | ||||
| export default { | ||||
|   name: 'Server', | ||||
|   components: { | ||||
|     DsContainer, vueJsonEditor | ||||
|     vueJsonEditor | ||||
|   }, | ||||
|   mixins: [Plugin], | ||||
|   data () { | ||||
|     return { | ||||
|       config: undefined, | ||||
|       status: status, | ||||
|       labelCol: { span: 4 }, | ||||
|       wrapperCol: { span: 20 }, | ||||
|       dnsMappings: [] | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     api.config.reload().then(ret => { | ||||
|       this.config = ret | ||||
|   }, | ||||
|   mounted () { | ||||
|   }, | ||||
|   methods: { | ||||
|     onJsonChange (json) { | ||||
|     }, | ||||
|     ready () { | ||||
|       this.dnsMappings = [] | ||||
|       for (const key in this.config.server.dns.mapping) { | ||||
|         const value = this.config.server.dns.mapping[key] | ||||
|  | @ -95,17 +108,9 @@ export default { | |||
|           key, value | ||||
|         }) | ||||
|       } | ||||
|     }) | ||||
|   }, | ||||
|   mounted () { | ||||
|   }, | ||||
|   methods: { | ||||
|     onJsonChange (json) { | ||||
|     }, | ||||
|     submit () { | ||||
|       api.config.set(this.config).then(() => { | ||||
|         this.$message.info('设置已保存') | ||||
|       }) | ||||
|     applyAfter () { | ||||
| 
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -3,3 +3,25 @@ | |||
|   text-align: right; | ||||
|   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 = { | ||||
|   configureWebpack: config => { | ||||
|     const configNew = { | ||||
|  | @ -29,6 +30,9 @@ module.exports = { | |||
|             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