refactor: 自动更新

pull/180/head
xiaojunnuo 2020-11-10 22:12:42 +08:00
parent 161411f9c6
commit f0552d5cce
25 changed files with 450 additions and 183 deletions

View File

@ -9,13 +9,22 @@
### 1、 github的release、source、zip下载加速
可解决npm install 时某些安装包下载不下来的问题
### 2、 解决git push某些情况下需要临时输入账号密码的问题
通过将api.github.com域名解析到美国服务器
### 2、 dns优选
根据网络状况智能解析域名ip地址获取最佳网络速度
比如:
1. 解决git push 偶尔失败需要输入账号密码的问题
2. 解决github头像加载不出来的问题
3. 解决gist.github.com访问不到的问题
### 3、 github的源代码查看raw/blame查看
通过跳转到国内加速链接上
### 4、 Stack Overflow 加速
将ajax.google.com代理到加速代理上 ,recaptcha 加速
将ajax.google.com代理到加速CDN上
recaptcha 图片验证码加速
### 5、 google cdn 加速
通过代理到加速链接上
@ -90,10 +99,16 @@ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keyc
1. yarn 设置淘宝镜像registry
2. npm设置官方registry。
3. 项目install使用yarn发布包publish用npm互不影响
4. 某些库用cnpm也下载不下来的话可以试试打开dev-sidecar的npm加速
### 其他加速
1. git clone 加速 [fgit-go](https://github.com/FastGitORG/fgit-go)
2. github.com代理网站(不能登录) [hub.fastgit.org](https://hub.fastgit.org/)
1. git clone 加速
> 使用方式用实际的名称替换{}的内容即可加速clone
> https://hub.fastgit.org/{username}/{reponame}.git
> clone 出来的 remote "origin" 为fastgit的地址需要手动改回来
> 你也可以直接使用他们的clone加速工具 [fgit-go](https://github.com/FastGitORG/fgit-go)
2. github.com的镜像网站(注意:不能登录)
>1. [hub.fastgit.org](https://hub.fastgit.org/)
>2. [github.com.cnpmjs.org](https://github.com.cnpmjs.org/) 这个很容易超限
## api
@ -128,21 +143,28 @@ const intercepts = {
}
```
### DNS配置
### DNS优选
某些域名比如api.github.com会被解析到新加坡的ip上新加坡的服务器在上午挺好到了晚上就卡死基本不可用。
所以将这些域名解析到美国服务器上就可以正常访问
另外配置了dns mapping的域名将会从dns获取到的ip列表中选择相对快一点的服务器进行访问
```js
dns: {
mapping: {
// "解决push的时候需要输入密码的问题",
'api.github.com': 'usa', //配置该域名使用USA的域名解析服务器
'gist.github.com': 'usa'
// "avatars*.githubusercontent.com": "usa"
//
'api.github.com': 'usa', // "解决push的时候需要输入密码的问题",
'gist.github.com': 'usa' // 解决gist无法访问的问题
"*.githubusercontent.com": "usa" // 解决github头像经常下载不到的问题
}
},
```
注意暂时只支持IPv4的解析
#### 开启前vs 开启后
![](./doc/avatar2.png)
![](./doc/avatar1.png)
## 感谢
本项目使用lerna包管理工具

BIN
doc/avatar1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
doc/avatar2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,31 +1,21 @@
{
"name": "@docmirror/dev-sidecar",
"version": "1.0.1",
"description": "",
"description": "给开发者的加速代理工具",
"main": "src/index.js",
"depedencies": {},
"keywords": [],
"keywords": ["dev-sidecar","github加速","google加速","代理"],
"author": "docmirror.cn",
"license": "MPL-2.0",
"private": false,
"scripts": {
"start": "node ./start",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"start": "node ./start"
},
"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",
@ -38,22 +28,17 @@
"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",
"@docmirror/mitmproxy": "1.0.1"
},
"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",
@ -61,8 +46,7 @@
"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"
"eslint-plugin-vue": "^6.2.2"
},
"eslintConfig": {
"root": true,

View File

@ -23,7 +23,7 @@ module.exports = {
'raw.githubusercontent.com': {
'.*': { proxy: 'raw.fastgit.org' }
},
'github.githubassets.com': {
'github11.githubassets.com': {
'.*': { proxy: 'assets.fastgit.org', test: 'https://github.githubassets.com/favicons/favicon.svg' }
},
'customer-stories-feed.github.com': {
@ -100,10 +100,11 @@ module.exports = {
}
},
mapping: {
'*.github.com': 'usa',
'*.githubusercontent.com': 'usa',
'*.githubassets.com': 'usa',
// "解决push的时候需要输入密码的问题",
'api.github.com': 'usa',
'gist.github.com': 'usa'
// "avatars*.githubusercontent.com": "usa"
'github.com': 'usa'
}
}
},

View File

@ -8,9 +8,6 @@
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<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" style="height:100%">
<div style="display: flex;align-items: center;justify-content: center;height:100%;width:100%"><img src="loading-spin.svg"></div>
</div>

View File

@ -75,6 +75,7 @@ function createWindow () {
win = new BrowserWindow({
width: 900,
height: 700,
title: 'Dev-Sidecar',
webPreferences: {
enableRemoteModule: true,
// preload: path.join(__dirname, 'preload.js'),
@ -167,7 +168,17 @@ if (!isFirstInstance) {
}
createWindow()
bridge.init(win)
updateHandle(win, 'http://localhost/dev-sidecar/')
let updateUrl = 'https://dev-sidecar.docmirror.cn/update/'
if (process.env.NODE_ENV === 'development') {
Object.defineProperty(app, 'isPackaged', {
get () {
return true
}
})
updateUrl = 'http://localhost/dev-sidecar/'
}
updateHandle(win, updateUrl)
try {
// 最小化到托盘
tray = setTray(app)

View File

@ -1,11 +1,15 @@
import lodash from 'lodash'
import DevSidecar from '@docmirror/dev-sidecar'
import { ipcMain } from 'electron'
import { ipcMain, Menu } from 'electron'
import fs from 'fs'
import JSON5 from 'json5'
import path from 'path'
const mitmproxyPath = path.join(__dirname, 'mitmproxy.js')
const localApi = {
/**
* 返回所有api列表供vue来ipc调用
* @returns {[]}
*/
getApiList () {
const core = lodash.cloneDeep(DevSidecar.api)
const local = lodash.cloneDeep(localApi)
@ -15,13 +19,40 @@ const localApi = {
// console.log('api list:', list)
return list
},
/**
* 软件设置
*/
setting: {
load () {
const settingPath = _getSettingPath()
const file = fs.readFileSync(settingPath)
const settings = JSON5.parse(file.toString())
return settings || {}
},
save (settings = {}) {
const settingPath = _getSettingPath()
fs.writeFileSync(settingPath, JSON5.stringify(settings, null, 2))
}
},
/**
* 启动所有
* @returns {Promise<void>}
*/
startup () {
return DevSidecar.api.startup({ mitmproxyPath })
},
server: {
/**
* 启动代理服务
* @returns {Promise<{port: *}>}
*/
start () {
return DevSidecar.api.server.start({ mitmproxyPath })
},
/**
* 重启代理服务
* @returns {Promise<void>}
*/
restart () {
return DevSidecar.api.server.restart({ mitmproxyPath })
}
@ -35,15 +66,13 @@ const localApi = {
// 对比默认config的异同
const defConfig = DevSidecar.api.config.getDefault()
const saveConfig = doMerge(defConfig, newConfig)
// _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
},
/**
* 读取后合并配置
* @returns {*}
*/
reload () {
const path = _getConfigPath()
if (!fs.existsSync(path)) {
@ -53,7 +82,7 @@ const localApi = {
const userConfig = JSON5.parse(file.toString())
DevSidecar.api.config.set(userConfig)
const config = DevSidecar.api.config.get()
return config
return config || {}
}
}
}
@ -69,6 +98,13 @@ function _deepFindFunction (list, parent, parentKey) {
}
}
function _getSettingPath () {
const dir = './config/'
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
return dir + 'setting.json5'
}
function _getConfigPath () {
const dir = './config/'
if (!fs.existsSync(dir)) {
@ -147,6 +183,7 @@ export default {
// 合并用户配置
localApi.config.reload()
// 启动所有
localApi.startup()
},
devSidecar: DevSidecar

View File

@ -1,6 +1,6 @@
import { ipcMain } from 'electron'
import { ipcMain, dialog } from 'electron'
import { autoUpdater } from 'electron-updater'
import path from 'path'
// win是所有窗口的引用
// const path = require('path') // 引入path模块
// const fs = require('fs-extra')
@ -14,61 +14,77 @@ function updateHandle (win, updateUrl) {
// fs.emptyDir(updatePendingPath)
// // 更新前,删除本地安装包 ↑
const message = {
error: 'update error',
checking: 'updating...',
updateAva: 'fetch new version and downloading...',
updateNotAva: 'do not to update'
error: '更新失败',
checking: '检查更新中',
updateAva: '发现新版本',
updateNotAva: '当前为最新版本,无需更新'
}
// 本地开发环境改变app-update.yml地址
// if (process.env.NODE_ENV === 'development' && !isMac) {
// autoUpdater.updateConfigPath = path.join(__dirname, 'win-unpacked/resources/app-update.yml')
// }
if (process.env.NODE_ENV === 'development' && !isMac) {
autoUpdater.updateConfigPath = path.join(__dirname, 'win-unpacked/resources/app-update.yml')
}
autoUpdater.autoDownload = false
// 设置服务器更新地址
autoUpdater.setFeedURL({
provider: 'generic',
url: updateUrl
})
autoUpdater.on('error', function (err) {
console.log('autoUpdater error', err)
sendUpdateMessage(message.error)
autoUpdater.on('error', function (error) {
console.log('autoUpdater error', error)
sendUpdateMessage({ key: 'error', value: error, error: error })
// dialog.showErrorBox('Error: ', error == null ? 'unknown' : (error.stack || error).toString())
})
autoUpdater.on('checking-for-update', function () {
console.log('autoUpdater checking-for-update')
sendUpdateMessage(message.checking)
sendUpdateMessage({ key: 'checking', value: message.checking })
})
// 准备更新,打开进度条读取页面,关闭其他页面
autoUpdater.on('update-available', function (info) {
console.log('autoUpdater update-available')
sendUpdateMessage(message.updateAva)
sendUpdateMessage({ key: 'available', value: info })
})
autoUpdater.on('update-not-available', function (info) {
console.log('autoUpdater update-not-available')
sendUpdateMessage(message.updateNotAva)
sendUpdateMessage({ key: 'notAvailable', value: message.updateNotAva })
})
// 更新下载进度
autoUpdater.on('download-progress', function (progressObj) {
console.log('autoUpdater download-progress')
win.webContents.send('download-progress', parseInt(progressObj.percent))
win.webContents.send('update', { key: 'progress', value: parseInt(progressObj.percent) })
})
// 更新完成,重启应用
autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
ipcMain.on('isUpdateNow', (e, arg) => {
// some code here to handle event
autoUpdater.quitAndInstall()
autoUpdater.on('update-downloaded', function (info) {
console.log('download complete', info.version)
win.webContents.send('update', {
key: 'downloaded',
value: {
version: info.version,
releaseData: info.releaseDate
}
})
win.webContents.send('isUpdateNow')
})
ipcMain.on('checkForUpdate', () => {
// 执行自动更新检查
console.log('autoUpdater checkForUpdates')
autoUpdater.checkForUpdates()
ipcMain.on('update', (e, arg) => {
if (arg.key === 'doUpdateNow') {
// some code here to handle event
autoUpdater.quitAndInstall(true, true)
} else if (arg.key === 'checkForUpdate') {
// 执行自动更新检查
console.log('autoUpdater checkForUpdates')
autoUpdater.checkForUpdates()
} else if (arg.key === 'downloadUpdate') {
// 下载新版本
console.log('autoUpdater downloadUpdate')
autoUpdater.downloadUpdate()
}
})
// 通过main进程发送事件给renderer进程提示更新信息
function sendUpdateMessage (text) {
function sendUpdateMessage (message) {
console.log('autoUpdater sendUpdateMessage')
win.webContents.send('message', text)
win.webContents.send('update', message)
}
console.log('auto update inited')
return autoUpdater
}
export default updateHandle

View File

@ -3,7 +3,6 @@ import App from './view/App.vue'
import antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
import view from './view'
import { apiInit } from './view/api'
import VueRouter from 'vue-router'
import routes from './view/router'
import DsContainer from './view/components/container'
@ -17,16 +16,17 @@ Vue.component(DsContainer)
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})
apiInit().then((api) => {
Vue.prototype.$global = {}
view.initApi().then(async (api) => {
Vue.prototype.$api = api
// 初始化status
await view.initPre(api)
const app = new Vue({
router,
render: h => h(App)
}).$mount('#app')
view.init(app)
view.initModules(app)
})
// fix vue-router NavigationDuplicated

View File

@ -1,27 +1,32 @@
import lodash from 'lodash'
import { ipcRenderer } from 'electron'
const doInvoke = (api, args) => {
const invoke = (api, args) => {
return ipcRenderer.invoke('apiInvoke', [api, args]).catch(err => {
console.error('api invoke error:', err)
})
}
const send = (channel, message) => {
console.log('do send,', channel, message)
return ipcRenderer.send(channel, message)
}
const bindApi = (api, param1) => {
lodash.set(apiObj, api, (param2) => {
return doInvoke(api, param2 || param1)
return invoke(api, param2 || param1)
})
}
const apiObj = {
on (channel, callback) {
ipcRenderer.on(channel, callback)
},
doInvoke
invoke,
send
}
let inited = false
export function apiInit () {
if (!inited) {
return doInvoke('getApiList').then(list => {
return invoke('getApiList').then(list => {
inited = true
for (const item of list) {
bindApi(item)
@ -30,7 +35,6 @@ export function apiInit () {
return apiObj
})
}
ipcRenderer.send('checkForUpdate')
return new Promise(resolve => {
resolve(apiObj)

View File

@ -1,7 +1,12 @@
import './status'
import register from './event'
import api, { apiInit } from './api'
import modules from './modules'
import status from './status'
export default {
init (app) {
register(app)
initApi: apiInit,
async initPre (api) {
await status.install(api)
},
initModules (app) {
modules.install(app, api)
}
}

View File

@ -1,6 +1,6 @@
import DsContainer from '../components/container'
import status from '../status'
import lodash from 'lodash'
export default {
components: {
DsContainer
@ -8,7 +8,7 @@ export default {
data () {
return {
config: undefined,
status: status,
status: {},
labelCol: { span: 4 },
wrapperCol: { span: 20 },
applyLoading: false
@ -20,8 +20,15 @@ export default {
mounted () {
},
methods: {
getKey () {
if (this.key) {
return this.key
}
throw new Error('请设置key')
},
init () {
this.$api.config.reload().then(ret => {
this.status = this.$status
return this.$api.config.reload().then(ret => {
this.config = ret
if (this.ready) {
return this.ready(this.config)
@ -40,11 +47,21 @@ export default {
async applyBefore () {
},
reloadDefault (key) {
this.$api.config.resetDefault(key).then(ret => {
this.config = ret
}).then(() => {
this.apply()
resetDefault () {
const key = this.getKey()
this.$confirm({
title: '提示',
content: '确定要恢复默认设置吗?',
cancelText: '取消',
okText: '确定',
onOk: async () => {
this.config = await this.$api.config.resetDefault(key)
if (this.ready) {
await this.ready(this.config)
}
await this.apply()
},
onCancel () {}
})
},
saveConfig () {

View File

@ -1,19 +1,9 @@
import api from './api'
import status from './status'
import lodash from 'lodash'
function register (app) {
api.on('status', (event, message) => {
console.log('view on status', event, message)
const value = message.value
const key = message.key
lodash.set(status, key, value)
})
function install (app, api) {
api.on('error.core', (event, message) => {
console.error('view on error', message)
const key = message.key
if (key === 'server') {
handleServerStartError(message, message.error, app)
handleServerStartError(message, message.error, app, api)
}
})
api.on('error', (event, message) => {
@ -21,7 +11,7 @@ function register (app) {
})
}
function handleServerStartError (message, err, app) {
function handleServerStartError (message, err, app, api) {
if (message.value === 'EADDRINUSE') {
app.$confirm({
title: '端口被占用,代理服务启动失败',
@ -42,4 +32,6 @@ function handleServerStartError (message, err, app) {
}
}
export default register
export default {
install
}

View File

@ -0,0 +1,9 @@
import update from './update'
import error from './error'
export default {
install (app, api) {
error.install(app, api)
update.install(app, api)
}
}

View File

@ -0,0 +1,91 @@
let updateParams = { }
function install (app, api) {
api.on('update', (event, message) => {
console.log('on message', event, message)
handleUpdateMessage(message, app)
})
api.update = {
checkForUpdate (params) {
updateParams = params || { fromUser: false, autoDownload: true, progress: 0 }
api.send('update', { key: 'checkForUpdate' })
},
downloadUpdate () {
api.send('update', { key: 'downloadUpdate' })
},
doUpdateNow () {
api.send('update', { key: 'doUpdateNow' })
}
}
function handleUpdateMessage (message) {
const type = message.key
if (type === 'available') {
foundNewVersion(message.value)
} else if (type === 'notAvailable') {
noNewVersion()
} else if (type === 'downloaded') {
// 更新包已下载完成,让用户确认是否更新
newUpdateIsReady(message.value)
} else if (type === 'progress') {
progressUpdate(message.value)
} else if (type === 'error') {
const error = message.error
app.$message.error('Error: ' + (error == null ? '未知错误' : (error.stack || error).toString()))
}
}
function noNewVersion (value) {
updateParams.newVersion = false
if (updateParams.fromUser) {
app.$message.info('当前已经是最新版本')
}
}
function progressUpdate (value) {
updateParams.progress = value
}
function foundNewVersion (value) {
updateParams.newVersion = true
if (updateParams.autoDownload !== false) {
api.update.downloadUpdate()
return
}
app.$confirm({
title: '发现新版本',
content: `是否要更新到v${value.version}?`,
cancelText: '暂不升级',
okText: '升级',
// content: h => <div><h4>{value.version}更新内容:</h4><div>{value.releaseNotes}</div></div>,
onOk () {
console.log('OK')
api.update.downloadUpdate()
},
onCancel () {
console.log('Cancel')
}
})
}
function newUpdateIsReady (value) {
app.$confirm({
title: '新版本已准备好',
content: `是否立即升级安装v${value.version}?`,
cancelText: '暂不升级',
okText: '立即升级',
// content: h => <div><h4>{value.version}更新内容:</h4><div>{value.releaseNotes}</div></div>,
onOk () {
console.log('OK')
api.update.doUpdateNow()
},
onCancel () {
console.log('Cancel')
}
})
}
}
export default {
install
}

View File

@ -4,10 +4,13 @@
给开发者的辅助工具
<span>
<a-button style="margin-right:10px" @click="openSetupCa"></a-button>
<a-badge :count="update.newVersion?1:0" dot>
<a-button style="margin-right:10px" @click="doCheckUpdate"></a-button>
</a-badge>
</span>
</template>
<div style="display: flex; align-items:center;justify-content:space-around;flex-direction: row">
<div v-if="status" style="display: flex; align-items:center;justify-content:space-around;flex-direction: row">
<div style="text-align: center">
<div class="big_button">
<a-button shape="circle" :type="startup.type()" :loading="startup.loading" @click="startup.doClick">
@ -38,8 +41,6 @@
</template>
<script>
import api from '../api'
import status from '../status'
import lodash from 'lodash'
import setupCa from '../components/setup-ca'
import DsContainer from '../components/container'
@ -52,7 +53,7 @@ export default {
},
data () {
return {
status: status,
status: undefined,
startup: {
loading: false,
type: () => {
@ -60,9 +61,9 @@ export default {
},
doClick: () => {
if (this.status.server.enabled) {
this.apiCall(this.startup, api.shutdown)
this.apiCall(this.startup, this.$api.shutdown)
} else {
this.apiCall(this.startup, api.startup)
this.apiCall(this.startup, this.$api.startup)
}
}
},
@ -77,39 +78,48 @@ export default {
config: undefined,
setupCa: {
visible: false
}
},
update: {}
}
},
created () {
console.log('index created')
this.reloadConfig().then(() => {
// this.start(true)
return api.status.get().then(ret => {
console.log('status', ret)
lodash.merge(status, ret)
this.$set(this, 'status', status)
})
}).then(() => {
this.switchBtns = this.createSwitchBtns()
})
async created () {
console.log('index created', this.status, this.$status)
await this.reloadConfig()
const status = await this.$api.status.get()
console.log('status', status)
this.$set(this, 'status', status)
this.switchBtns = this.createSwitchBtns()
console.log('switchBtns', this.switchBtns)
if (this.$global.update == null) {
this.$global.update = {
fromUser: false,
autoDownload: true,
progress: 0,
newVersion: false
}
this.update = this.$global.update
this.doCheckUpdate(false)
}
this.update = this.$global.update
},
mounted () {
console.log('index mounted')
},
methods: {
reloadConfig () {
return api.config.reload().then(ret => {
return this.$api.config.reload().then(ret => {
this.config = ret
return ret
})
},
createSwitchBtns () {
console.log('api,', api)
console.log('api,', this.$api)
const btns = {}
btns.server = this.createSwitchBtn('server', '代理服务', api.server, status)
btns.proxy = this.createSwitchBtn('proxy', '系统代理', api.proxy, status)
lodash.forEach(this.status.plugin, (item, key) => {
btns[key] = this.createSwitchBtn(key, this.config.plugin[key].name, api.plugin[key], status.plugin)
const status = this.status
btns.server = this.createSwitchBtn('server', '代理服务', this.$api.server, status)
btns.proxy = this.createSwitchBtn('proxy', '系统代理', this.$api.proxy, status)
lodash.forEach(status.plugin, (item, key) => {
btns[key] = this.createSwitchBtn(key, this.config.plugin[key].name, this.$api.plugin[key], status.plugin)
})
return btns
},
@ -145,10 +155,10 @@ export default {
}
},
onServerClick (checked) {
return this.onSwitchClick(this.server, api.server.start, api.server.close, checked)
return this.onSwitchClick(this.server, this.$api.server.start, this.$api.server.close, checked)
},
start (checked) {
this.apiCall(this.startup, api.startup)
this.apiCall(this.startup, this.$api.startup)
},
openSettings () {
this.settings.visible = true
@ -157,12 +167,16 @@ export default {
console.log('config changed', newConfig)
this.reloadConfig().then(() => {
if (this.status.server) {
return api.server.restart()
return this.$api.server.restart()
}
})
},
openSetupCa () {
this.setupCa.visible = true
},
doCheckUpdate (fromUser = true) {
this.update.fromUser = fromUser
this.$api.update.checkForUpdate(this.update)
}
}
}

View File

@ -59,7 +59,7 @@
</div>
<template slot="footer">
<div class="footer-bar">
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')"></a-button>
<a-button class="md-mr-10" icon="sync" @click="resetDefault()"></a-button>
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()"></a-button>
</div>
</template>
@ -75,6 +75,7 @@ export default {
mixins: [Plugin],
data () {
return {
key: 'plugin.node',
npmVariables: undefined,
registry: false
}

View File

@ -21,7 +21,7 @@
</div>
<template slot="footer">
<div class="footer-bar">
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')"></a-button>
<a-button class="md-mr-10" icon="sync" @click="resetDefault()"></a-button>
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()"></a-button>
</div>
</template>
@ -36,7 +36,7 @@ export default {
mixins: [Plugin],
data () {
return {
key: 'proxy'
}
},
created () {

View File

@ -69,7 +69,7 @@
</div>
<template slot="footer">
<div class="footer-bar">
<a-button class="md-mr-10" icon="sync" @click="reloadDefault('server')"></a-button>
<a-button class="md-mr-10" icon="sync" @click="resetDefault()"></a-button>
<a-button :loading="applyLoading" icon="check" type="primary" @click="apply()"></a-button>
</div>
</template>
@ -88,6 +88,7 @@ export default {
mixins: [Plugin],
data () {
return {
key: 'server',
labelCol: { span: 4 },
wrapperCol: { span: 20 },
dnsMappings: []

View File

@ -1,3 +1,5 @@
import lodash from 'lodash'
import Vue from 'vue'
const status = {
server: {
enabled: false
@ -9,4 +11,19 @@ const status = {
node: {}
}
}
export default status
async function install (api) {
api.on('status', (event, message) => {
console.log('view on status', event, message)
const value = message.value
const key = message.key
lodash.set(status, key, value)
})
const basicStatus = await api.status.get()
lodash.merge(status, basicStatus)
Vue.prototype.$status = status
return status
}
export default {
install,
status
}

View File

@ -3,56 +3,38 @@
"version": "1.0.1",
"description": "",
"main": "src/index.js",
"depedencies": {},
"keywords": [],
"keywords": [
"dev-sidecar"
],
"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",
"log4js": "^6.3.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"
"validator": "^13.1.17"
},
"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",
@ -60,8 +42,7 @@
"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"
"eslint-plugin-vue": "^6.2.2"
},
"eslintConfig": {
"root": true,

View File

@ -63,15 +63,12 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig) {
proxySocket.on('timeout', () => {
const end = new Date().getTime()
console.log('代理socket timeout', hostname, port, (end - start) + 'ms')
proxySocket.destroy()
cltSocket.destroy()
})
proxySocket.on('error', (e) => {
// 连接失败可能被GFW拦截或者服务端拥挤
const end = new Date().getTime()
console.error('代理连接失败:', e.message, hostname, port, (end - start) + 'ms')
cltSocket.destroy()
if (isDnsIntercept) {
const { dns, ip, hostname } = isDnsIntercept
dns.count(hostname, ip, true)

View File

@ -39,14 +39,14 @@ module.exports = function createRequestHandler (requestInterceptor, responseInte
const proxyRequestPromise = async () => {
rOptions.host = rOptions.hostname || rOptions.host || 'localhost'
if (dnsConfig) {
const dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.host)
if (dns) {
const ip = await dns.lookup(rOptions.host)
console.log('使用自定义dns:', rOptions.host, ip, dns.dnsServer)
rOptions.host = ip
}
}
// if (dnsConfig) {
// const dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.host)
// if (dns) {
// const ip = await dns.lookup(rOptions.host)
// console.log('使用自定义dns:', rOptions.host, ip, dns.dnsServer)
// rOptions.host = ip
// }
// }
return new Promise((resolve, reject) => {
// use the binded socket for NTLM
@ -64,6 +64,22 @@ module.exports = function createRequestHandler (requestInterceptor, responseInte
const url = `${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
const start = new Date().getTime()
console.log('代理请求:', url, rOptions.method)
let isDnsIntercept
if (dnsConfig) {
const dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname)
if (dns) {
rOptions.lookup = (hostname, options, callback) => {
dns.lookup(hostname).then(ip => {
isDnsIntercept = { dns, hostname, ip }
if (ip !== hostname) {
callback(null, ip, 4)
} else {
rOptions.lookup(hostname, options, callback)
}
})
}
}
}
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
const end = new Date().getTime()
@ -75,12 +91,22 @@ module.exports = function createRequestHandler (requestInterceptor, responseInte
proxyReq.on('timeout', () => {
const end = new Date().getTime()
if (isDnsIntercept) {
const { dns, ip, hostname } = isDnsIntercept
dns.count(hostname, ip, true)
console.error('记录ip失败次数,用于优选ip', hostname, ip)
}
console.error('代理请求超时', rOptions.protocol, rOptions.hostname, rOptions.path, (end - start) + 'ms')
reject(new Error(`${rOptions.host}:${rOptions.port}, 代理请求超时`))
})
proxyReq.on('error', (e, req, res) => {
const end = new Date().getTime()
if (isDnsIntercept) {
const { dns, ip, hostname } = isDnsIntercept
dns.count(hostname, ip, true)
console.error('记录ip失败次数,用于优选ip', hostname, ip)
}
console.error('代理请求错误', e.errno, rOptions.hostname, rOptions.path, (end - start) + 'ms')
reject(e)
})
@ -92,7 +118,7 @@ module.exports = function createRequestHandler (requestInterceptor, responseInte
req.on('aborted', function () {
console.error('请求被取消', rOptions.hostname, rOptions.path)
proxyReq.destroy()
proxyReq.abort()
reject(new Error('请求被取消'))
})
req.on('error', function (e, req, res) {

View File

@ -3541,6 +3541,16 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
date-format@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
date-format@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95"
integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
@ -4644,7 +4654,7 @@ flat-cache@^2.0.1:
rimraf "2.6.3"
write "1.0.3"
flatted@^2.0.0:
flatted@^2.0.0, flatted@^2.0.1:
version "2.0.2"
resolved "https://registry.npm.taobao.org/flatted/download/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha1-RXWyHivO50NKqb5mL0t7X5wrUTg=
@ -4720,6 +4730,15 @@ fs-extra@^7.0.1:
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.npm.taobao.org/fs-minipass/download/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@ -4901,7 +4920,7 @@ globby@^9.2.0:
pify "^4.0.1"
slash "^2.0.0"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2:
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
version "4.2.4"
resolved "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.4.tgz?cache=0&sync_timestamp=1589682809142&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgraceful-fs%2Fdownload%2Fgraceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha1-Ila94U02MpWMRl68ltxGfKB6Kfs=
@ -6055,6 +6074,17 @@ log-symbols@^2.2.0:
dependencies:
chalk "^2.0.1"
log4js@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb"
integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==
dependencies:
date-format "^3.0.0"
debug "^4.1.1"
flatted "^2.0.1"
rfdc "^1.1.4"
streamroller "^2.2.4"
loglevel@^1.6.8:
version "1.7.0"
resolved "https://registry.npm.taobao.org/loglevel/download/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
@ -8002,6 +8032,11 @@ retry@^0.12.0:
resolved "https://registry.npm.taobao.org/retry/download/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
rfdc@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
rgb-regex@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/rgb-regex/download/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
@ -8541,6 +8576,15 @@ stream-shift@^1.0.0:
resolved "https://registry.npm.taobao.org/stream-shift/download/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha1-1wiCgVWasneEJCebCHfaPDktWj0=
streamroller@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53"
integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==
dependencies:
date-format "^2.1.0"
debug "^4.1.1"
fs-extra "^8.1.0"
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.npm.taobao.org/strict-uri-encode/download/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"