Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ source .venv/bin/activate # for linux/mac

#### 2)安装 `pnpm`

运行如下命令即可安装所需依赖
运行如下命令即可安装

```shell
npm install -g pnpm --registry=https://registry.npmmirror.com
Expand All @@ -422,6 +422,7 @@ npm run electron
```

> 如果electron依赖包下载不动,可以开启ds的npm加速
> 如果pnpm install只是单纯卡住,大概是因为你忘记进python环境了

### 8.3、打包成可执行文件

Expand Down
110 changes: 110 additions & 0 deletions _script/electron-dev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { spawn } from 'node:child_process'
import { createRequire } from 'node:module'
import process from 'node:process'
import { setTimeout as delay } from 'node:timers/promises'

const guiDir = process.cwd()
const require = createRequire(import.meta.url)

const devServerUrl = 'http://localhost:8080'
const state = {
closing: false,
devServer: null,
electron: null,
}

function spawnCommand (entry, args = [], extraEnv = {}) {
return spawn(entry, args, {
cwd: guiDir,
env: { ...process.env, ...extraEnv },
shell: false,
stdio: 'inherit',
windowsHide: false,
})
}

function resolveVueCliServiceBin () {
return require.resolve('@vue/cli-service/bin/vue-cli-service.js', {
paths: [guiDir],
})
}

function resolveElectronBin () {
return require('electron')
}

async function waitForServer (url, child) {
const timeoutAt = Date.now() + 120000

while (Date.now() < timeoutAt) {
if (child.exitCode != null || child.signalCode != null) {
throw new Error('Dev server exited before it became ready')
}

try {
const response = await fetch(url, { method: 'GET' })
if (response.ok || response.status >= 200) {
return
}
} catch {
// Keep polling until the dev server is reachable.
}

await delay(500)
}

throw new Error(`Timed out waiting for ${url}`)
}

function stopChild (child) {
if (!child || child.exitCode != null || child.signalCode != null) {
return
}

child.kill('SIGTERM')
}

async function shutdown (code = 0) {
if (state.closing) {
return
}

state.closing = true
stopChild(state.electron)
stopChild(state.devServer)
process.exitCode = code
}

process.on('SIGINT', () => {
void shutdown(0)
})
process.on('SIGTERM', () => {
void shutdown(0)
})

async function main () {
const vueCliServiceBin = resolveVueCliServiceBin()
const electronBin = resolveElectronBin()

state.devServer = spawnCommand(process.execPath, [vueCliServiceBin, 'serve', '--port', '8080'])
state.devServer.on('exit', (code, signal) => {
if (!state.closing) {
void shutdown(code ?? (signal ? 1 : 0))
}
})

try {
await waitForServer(devServerUrl, state.devServer)
state.electron = spawnCommand(electronBin, ['.'], {
WEBPACK_DEV_SERVER_URL: devServerUrl,
})
state.electron.on('exit', (code, signal) => {
void shutdown(code ?? (signal ? 1 : 0))
})
} catch (error) {
console.error(error)
await shutdown(1)
}
}

void main()
2 changes: 1 addition & 1 deletion packages/core/src/shell/scripts/kill-by-port.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const execute = Shell.execute

const executor = {
async windows (exec, { port }) {
const cmds = [`for /f "tokens=5" %a in ('netstat -aon ^| find ":${port}" ^| find "LISTENING"') do (taskkill /f /pid %a & exit /B) `]
const cmds = [`for /f "tokens=5" %a in ('netstat -aon ^| find ":${port}" ^| find "LISTENING"') do (taskkill /f /pid %a /t & exit /B) `]
await exec(cmds, { type: 'cmd' })
return true
},
Expand Down
73 changes: 63 additions & 10 deletions packages/core/src/shell/scripts/set-system-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,73 @@ const execute = Shell.execute

const executor = {
async windows (exec, { list }) {
const cmds = []
const psCmdsMachine = []
const psCmdsUser = []
for (const item of list) {
// [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
cmds.push(`[Environment]::SetEnvironmentVariable('${item.key}', '${item.value}', 'Machine')`)
const v = item.value == null ? '' : String(item.value)
// escape single quotes for PowerShell single-quoted string
const escaped = v.replace(/'/g, "''")
psCmdsMachine.push(`[Environment]::SetEnvironmentVariable('${item.key}', '${escaped}', 'Machine')`)
psCmdsUser.push(`[Environment]::SetEnvironmentVariable('${item.key}', '${escaped}', 'User')`)
}
const ret = await exec(cmds, { type: 'ps' })

const cmds2 = []
for (const item of list) {
// [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
cmds2.push(`set ${item.key}=""`)
// Wrap whole flow and return structured result so caller can show detailed errors
try {
let ret
let appliedScope = null
try {
ret = await exec(psCmdsMachine, { type: 'ps' })
appliedScope = 'Machine:PowerShell'
} catch (eMachine) {
// try User scope via PowerShell
try {
ret = await exec(psCmdsUser, { type: 'ps' })
appliedScope = 'User:PowerShell'
} catch (eUser) {
// PowerShell attempts failed; fallback to setx via cmd
const cmdsSetxMachine = []
const cmdsSetxUser = []
for (const item of list) {
const v = item.value == null ? '' : String(item.value)
// basic escape for double quotes in cmd
const escaped = v.replace(/"/g, '\\"')
cmdsSetxMachine.push(`setx ${item.key} "${escaped}" /M`)
cmdsSetxUser.push(`setx ${item.key} "${escaped}"`)
}
try {
ret = await exec(cmdsSetxMachine, { type: 'cmd' })
appliedScope = 'Machine:setx'
} catch (eSetxMachine) {
try {
ret = await exec(cmdsSetxUser, { type: 'cmd' })
appliedScope = 'User:setx'
} catch (eSetxUser) {
// all attempts failed — include all error messages
const combined = [eMachine, eUser, eSetxMachine, eSetxUser].map(e => (e && e.message) || String(e)).join(' | ')
return { success: false, error: 'Failed to set environment variables', details: combined }
}
}
}
}

// inject into current process so subsequent exec/child processes can inherit immediately
let envUpdateError = null
try {
for (const item of list) {
if (item.value == null) {
delete process.env[item.key]
} else {
process.env[item.key] = String(item.value)
}
}
} catch (e) {
envUpdateError = e.message || String(e)
}

return { success: true, scope: appliedScope, output: ret, envUpdateError }
} catch (finalErr) {
return { success: false, error: finalErr.message || String(finalErr), details: finalErr.stack }
}
await exec(cmds2, { type: 'cmd' })
return ret
},
async linux (exec, { port }) {
throw new Error('暂未实现此功能')
Expand Down
20 changes: 10 additions & 10 deletions packages/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
"license": "MPL-2.0",
"homepage": "https://github.com/docmirror/dev-sidecar",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"electron": "concurrently -k \"vue-cli-service serve --port 8080\" \"wait-on http-get://localhost:8080 && cross-env WEBPACK_DEV_SERVER_URL=http://localhost:8080 electron .\"",
"electron:build": "npm run build && electron-builder --config electron-builder.config.cjs",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"electron:icons": "electron-icon-builder --input=./public/logo/win.png --output=build --flatten",
"electron:icons-mac": "electron-icon-builder --input=./public/logo/mac.png --output=build --flatten",
"electron:icons-black": "electron-icon-builder --input=./public/logo/win-black.png --output=build/black --flatten"
"serve": "pnpm exec vue-cli-service serve",
"build": "pnpm exec vue-cli-service build",
"lint": "pnpm exec vue-cli-service lint",
"electron": "node ../../_script/electron-dev.mjs",
"electron:build": "pnpm run build && pnpm exec electron-builder --config electron-builder.config.cjs",
"postinstall": "pnpm exec electron-builder install-app-deps",
"postuninstall": "pnpm exec electron-builder install-app-deps",
"electron:icons": "pnpm exec electron-icon-builder --input=./public/logo/win.png --output=build --flatten",
"electron:icons-mac": "pnpm exec electron-icon-builder --input=./public/logo/mac.png --output=build --flatten",
"electron:icons-black": "pnpm exec electron-icon-builder --input=./public/logo/win-black.png --output=build/black --flatten"
},
"dependencies": {
"@docmirror/dev-sidecar": "workspace:*",
Expand Down
49 changes: 36 additions & 13 deletions packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
const defaultDns = require('node:dns')
const net = require('node:net')
const log = require('../../../utils/util.log.server')
const speedTest = require('../../speed')

function isValidIpAddress (ip) {
return typeof ip === 'string' && net.isIP(ip) !== 0
}

function getAddressFamily (ip) {
return net.isIP(ip) === 6 ? 6 : 4
}

function respondLookup (callback, ip, family, all) {
if (all) {
callback(null, [{ address: ip, family }])
return
}

callback(null, ip, family)
}

function createIpChecker (tester) {
if (!tester || tester.backupList == null || tester.backupList.length === 0) {
return null
Expand Down Expand Up @@ -33,38 +51,43 @@ module.exports = {
const family = Number.parseInt(dnsAndFamily.family) === 6 ? 6 : 4

return (hostname, options, callback) => {
const all = options && options.all === true
const tester = speedTest.getSpeedTester(hostname, port)
if (tester) {
const aliveIpObj = tester.pickFastAliveIpObj()
if (aliveIpObj) {
if (aliveIpObj && isValidIpAddress(aliveIpObj.host)) {
const addressFamily = getAddressFamily(aliveIpObj.host)
log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `IpTester: ${aliveIpObj.host} ${aliveIpObj.dns === '预设IP' ? 'PreSet' : aliveIpObj.dns}`)
}
callback(null, aliveIpObj.host, family)
respondLookup(callback, aliveIpObj.host, addressFamily, all)
return
} else {
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
log.info(`----- ${action}: ${hostname}, no valid alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
}
}

const ipChecker = createIpChecker(tester)

dns.lookup(hostname, { ipChecker, family }).then((ip) => {
if (isDnsIntercept) {
isDnsIntercept.dns = dns
isDnsIntercept.hostname = hostname
isDnsIntercept.ip = ip
}

if (ip !== hostname) {
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}(family: ${family})${target} -----`)
if (ip !== hostname && isValidIpAddress(ip)) {
const addressFamily = getAddressFamily(ip)
if (isDnsIntercept) {
isDnsIntercept.dns = dns
isDnsIntercept.hostname = hostname
isDnsIntercept.ip = ip
}
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}(family: ${addressFamily})${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `DNS: ${ip}(IPv${family}) ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
res.setHeader('DS-DNS-Lookup', `DNS: ${ip}(IPv${addressFamily}) ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
}
callback(null, ip, family)
respondLookup(callback, ip, addressFamily, all)
} else {
// 使用默认dns
if (ip != null && ip !== hostname && !isValidIpAddress(ip)) {
log.warn(`----- ${action}: ${hostname}, dns returned invalid ip '${ip}'${target}, fallback to default DNS`)
}
log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
defaultDns.lookup(hostname, options, callback)
}
Expand Down
Loading
Loading