@twinsearth/dsh-2xray 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +12 -0
- package/lib/binary.js +81 -2
- package/lib/client.js +6 -2
- package/package.json +1 -1
- package/scripts/release-check.mjs +2 -1
- package/test/binary.test.js +28 -0
- package/tools/launch-with-browser-proxy.cmd +30 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.6 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
### 内置浏览器支持
|
|
6
|
+
|
|
7
|
+
- 新增 `tools/launch-with-browser-proxy.cmd`:以 `--proxy-server=http://127.0.0.1:20808` 启动 DSH,让内置 Chromium 浏览器也走插件的 xray。
|
|
8
|
+
- 设置页与插件页卡片增加说明:Node 插件无法在运行时修改 Chromium 的 session 代理,必须带启动参数重启;系统代理/TUN 是另一条全机路径。
|
|
9
|
+
- 保留边界:插件仍只接管 DSH 进程内的 undici fetch。
|
|
10
|
+
|
|
11
|
+
## 0.2.5 — 2026-10-29
|
|
12
|
+
|
|
13
|
+
### 修复
|
|
14
|
+
|
|
15
|
+
- **binary-missing 自动恢复**:未配置 `binary.path` 时,会按 `V2RAYN_HOME/V2RAYN_PATH`、`%LOCALAPPDATA%`、桌面/下载、Program Files、盘符根目录做有界搜索(只进入名字含 v2ray 的目录,3s 预算),识别 v2rayN 7.x 的 `bin/xray/xray.exe` 并直接借用。
|
|
16
|
+
- 找不到时错误信息仍列出显式候选与 PATH,便于诊断。
|
|
17
|
+
|
|
3
18
|
## 0.2.4 — 2026-10-29
|
|
4
19
|
|
|
5
20
|
### 设置页与导入
|
package/README.md
CHANGED
|
@@ -328,6 +328,18 @@ v0.2.0 提供两个入口,互不替代:
|
|
|
328
328
|
| 状态一直 `degraded` + `route-install-failed` | 查日志里的模块解析诊断;必要时确认 DSH 安装目录结构 |
|
|
329
329
|
| `xray_switch_node` 报 `unknown service ... RoutingService` | xray 版本早于 2024-09;升级二进制,或关闭 `control.enabled` 只用 `activeNode` |
|
|
330
330
|
|
|
331
|
+
## 内置浏览器(Chromium)
|
|
332
|
+
|
|
333
|
+
DSH 内置浏览器走 Chromium 网络栈,不读 undici dispatcher,插件无法在运行时改它的代理。支持方式只有启动参数:
|
|
334
|
+
|
|
335
|
+
```powershell
|
|
336
|
+
& "E:\DS\DeepSeek Harness.exe" --proxy-server=http://127.0.0.1:20808
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
或运行 `tools/launch-with-browser-proxy.cmd`(自动查找 DSH 路径并透传 `--proxy-server`)。
|
|
340
|
+
|
|
341
|
+
**必须先完全退出 DSH**:单实例再次启动只会聚焦旧窗口,不会应用参数。系统代理/TUN(v2rayN)也能让 Chromium 生效,但那是全机代理,与本插件无关。
|
|
342
|
+
|
|
331
343
|
## 开发
|
|
332
344
|
|
|
333
345
|
```powershell
|
package/lib/binary.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync } from 'node:fs'
|
|
1
|
+
import { accessSync, constants, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { delimiter, join } from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
@@ -54,6 +54,8 @@ export async function resolveBinary({
|
|
|
54
54
|
launcher,
|
|
55
55
|
exists = isExecutable,
|
|
56
56
|
packageRoot = PACKAGE_ROOT,
|
|
57
|
+
discoveryRoots,
|
|
58
|
+
discoveryOptions = {},
|
|
57
59
|
}) {
|
|
58
60
|
const candidates = []
|
|
59
61
|
const explicit = config.binary.path?.trim()
|
|
@@ -90,14 +92,91 @@ export async function resolveBinary({
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
// v2rayN / v2rayN-core ship xray under their own directory (7.x uses
|
|
96
|
+
// bin/xray/xray.exe). Borrowing it avoids asking the user to copy a binary.
|
|
97
|
+
const discovered = discoverXray({ roots: discoveryRoots, ...discoveryOptions })
|
|
98
|
+
if (discovered.length > 0) {
|
|
99
|
+
return { path: discovered[0], source: 'discovered' }
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
throw new BinaryError(
|
|
94
103
|
`找不到 xray-core 可执行文件。已尝试:${candidates.map((candidate) => candidate.path).join(', ') || '(无显式路径)'},以及 PATH 中的 ${pathEntries.join(delimiter)}。` +
|
|
95
104
|
'请把 xray 放进插件自己的 bin/、数据目录的 bin/,或设置 config.binary.path。',
|
|
96
105
|
'binary-missing',
|
|
97
|
-
candidates.map((candidate) => candidate.path),
|
|
106
|
+
[...candidates.map((candidate) => candidate.path), ...discovered],
|
|
98
107
|
)
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
/** Common install roots for a borrowed xray binary (Windows-first). */
|
|
111
|
+
export function defaultDiscoveryRoots() {
|
|
112
|
+
const home = process.env.USERPROFILE ?? process.env.HOME ?? ''
|
|
113
|
+
const local = process.env.LOCALAPPDATA ?? ''
|
|
114
|
+
const roots = [
|
|
115
|
+
process.env.V2RAYN_HOME,
|
|
116
|
+
process.env.V2RAYN_PATH,
|
|
117
|
+
local,
|
|
118
|
+
home ? join(home, 'Desktop') : '',
|
|
119
|
+
home ? join(home, 'Downloads') : '',
|
|
120
|
+
'C:\\Program Files',
|
|
121
|
+
'C:\\Program Files (x86)',
|
|
122
|
+
'D:\\',
|
|
123
|
+
'E:\\',
|
|
124
|
+
'C:\\',
|
|
125
|
+
]
|
|
126
|
+
return [...new Set(roots.filter((root) => typeof root === 'string' && root.length > 0))]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Bounded search for a v2rayN-style xray layout. Directory names must look
|
|
131
|
+
* like v2rayN, so the walk does not crawl unrelated trees; the time budget
|
|
132
|
+
* keeps a failed start from stalling activation.
|
|
133
|
+
*/
|
|
134
|
+
export function discoverXray({ roots = defaultDiscoveryRoots(), timeBudgetMs = 3000, max = 5 } = {}) {
|
|
135
|
+
const found = []
|
|
136
|
+
const deadline = Date.now() + timeBudgetMs
|
|
137
|
+
const skipped = /^(windows|programdata|recovery|\$recycle\.bin|system volume information|node_modules|\.git|appdata|temp)$/i
|
|
138
|
+
const layouts = [
|
|
139
|
+
['bin', 'xray', 'xray.exe'],
|
|
140
|
+
['bin', 'xray', 'xray'],
|
|
141
|
+
['bin', 'xray.exe'],
|
|
142
|
+
['bin', 'xray'],
|
|
143
|
+
['xray.exe'],
|
|
144
|
+
['xray'],
|
|
145
|
+
]
|
|
146
|
+
const check = (dir) => {
|
|
147
|
+
for (const parts of layouts) {
|
|
148
|
+
const candidate = join(dir, ...parts)
|
|
149
|
+
if (existsSync(candidate)) {
|
|
150
|
+
if (!found.includes(candidate)) found.push(candidate)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const visit = (dir, depth) => {
|
|
156
|
+
if (found.length >= max || Date.now() > deadline) return
|
|
157
|
+
let entries
|
|
158
|
+
try {
|
|
159
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
160
|
+
} catch {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (found.length >= max || Date.now() > deadline) return
|
|
165
|
+
if (!entry.isDirectory()) continue
|
|
166
|
+
const name = entry.name
|
|
167
|
+
if (skipped.test(name)) continue
|
|
168
|
+
const full = join(dir, name)
|
|
169
|
+
if (/v2ray/i.test(name)) check(full)
|
|
170
|
+
if (depth < 3) visit(full, depth + 1)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const root of roots) {
|
|
174
|
+
if (found.length >= max || Date.now() > deadline) break
|
|
175
|
+
if (existsSync(root)) visit(root, 0)
|
|
176
|
+
}
|
|
177
|
+
return found
|
|
178
|
+
}
|
|
179
|
+
|
|
101
180
|
/**
|
|
102
181
|
* Run `xray version` and parse the version line. A failed probe (missing
|
|
103
182
|
* dynamic library, wrong architecture, antivirus block) is a startup failure,
|
package/lib/client.js
CHANGED
|
@@ -7,6 +7,8 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
const PANEL_ID = 'xray'
|
|
8
8
|
const NS = 'xray-proxy'
|
|
9
9
|
const inject = ['slots']
|
|
10
|
+
const BROWSER_HINT =
|
|
11
|
+
'内置浏览器走 Chromium 网络栈,不受插件 dispatcher 影响:请用插件目录 tools/launch-with-browser-proxy.cmd 启动 DSH,或在快捷方式目标后追加 --proxy-server=http://127.0.0.1:20808(需完全退出 DSH 后重启才生效)。'
|
|
10
12
|
|
|
11
13
|
const C = {
|
|
12
14
|
page: { maxWidth: 980, margin: '0 auto', padding: '20px 24px 16px', color: 'var(--dsw-alias-label-primary, #0f1115)' },
|
|
@@ -463,7 +465,8 @@ window.__ModuleLoader__.load({
|
|
|
463
465
|
}
|
|
464
466
|
return h('div', { style: { ...C.card, marginTop: 8 } },
|
|
465
467
|
h('div', { style: C.cardHead }, h(GlobeIcon), '2Xray 连接'),
|
|
466
|
-
h(XrayConfigForm, { pageForm: form })
|
|
468
|
+
h(XrayConfigForm, { pageForm: form }),
|
|
469
|
+
h('p', { style: { ...C.muted, fontSize: 12, marginTop: 10 } }, BROWSER_HINT))
|
|
467
470
|
}
|
|
468
471
|
|
|
469
472
|
function SettingsSection({ controller }) {
|
|
@@ -473,7 +476,8 @@ window.__ModuleLoader__.load({
|
|
|
473
476
|
h('div', null,
|
|
474
477
|
h('h1', { style: C.title }, '2Xray'),
|
|
475
478
|
h('p', { style: C.sub }, '导入 VLESS 链接或本地二维码,保存后重启 DSH 生效。'))),
|
|
476
|
-
h('section', { style: C.card }, h(XrayConfigForm, { controller }))
|
|
479
|
+
h('section', { style: C.card }, h(XrayConfigForm, { controller })),
|
|
480
|
+
h('p', { style: { ...C.muted, fontSize: 12, marginTop: 4 } }, BROWSER_HINT))
|
|
477
481
|
}
|
|
478
482
|
|
|
479
483
|
function apply(ctx) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/dsh-2xray",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "DSH-2Xray: supervises xray-core and routes DSH process-wide HTTP(S) traffic through its local inbound, with optional multi-node gRPC control plane",
|
|
6
6
|
"type": "module",
|
|
@@ -73,7 +73,8 @@ const files = walk(ROOT)
|
|
|
73
73
|
const outside = files.filter((file) => !isAllowed(file))
|
|
74
74
|
check('every packaged file is allow-listed', outside.length === 0, outside.join(', ') || `${files.length} files`)
|
|
75
75
|
|
|
76
|
-
for (const required of ['index.js', 'package.json', 'cordis.patch.yml', 'README.md', 'CHANGELOG.md', 'LICENSE', 'lib/supervisor.js', 'lib/control/grpc.js',
|
|
76
|
+
for (const required of ['index.js', 'package.json', 'cordis.patch.yml', 'README.md', 'CHANGELOG.md', 'LICENSE', 'lib/supervisor.js', 'lib/control/grpc.js',
|
|
77
|
+
'tools/launch-with-browser-proxy.cmd', 'locale/en.json', 'locale/zh.json', 'icon.svg']) {
|
|
77
78
|
check(`required file ${required}`, files.includes(required))
|
|
78
79
|
}
|
|
79
80
|
|
package/test/binary.test.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import test from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
3
5
|
import { join } from 'node:path'
|
|
4
6
|
import { BinaryError, resolveBinary } from '../lib/binary.js'
|
|
5
7
|
import { normalizeConfig } from '../lib/config.js'
|
|
@@ -89,6 +91,7 @@ test('a missing binary fails with every candidate listed', async () => {
|
|
|
89
91
|
packageRoot,
|
|
90
92
|
exists: () => false,
|
|
91
93
|
launcher: {},
|
|
94
|
+
discoveryRoots: [],
|
|
92
95
|
}),
|
|
93
96
|
(error) => {
|
|
94
97
|
assert.ok(error instanceof BinaryError)
|
|
@@ -101,3 +104,28 @@ test('a missing binary fails with every candidate listed', async () => {
|
|
|
101
104
|
process.env.PATH = previousPath
|
|
102
105
|
}
|
|
103
106
|
})
|
|
107
|
+
|
|
108
|
+
test('discovers a v2rayN layout when nothing else resolves', async () => {
|
|
109
|
+
const dir = mkdtempSync(join(tmpdir(), 'dsh-xray-discover-'))
|
|
110
|
+
const previousPath = process.env.PATH
|
|
111
|
+
try {
|
|
112
|
+
const layout = join(dir, 'v2rayN-windows-64', 'bin', 'xray')
|
|
113
|
+
mkdirSync(layout, { recursive: true })
|
|
114
|
+
writeFileSync(join(layout, 'xray.exe'), '')
|
|
115
|
+
process.env.PATH = ''
|
|
116
|
+
const found = await resolveBinary({
|
|
117
|
+
config: config(),
|
|
118
|
+
dataDir: join(dir, 'data'),
|
|
119
|
+
packageRoot: join(dir, 'plugin'),
|
|
120
|
+
exists: () => false,
|
|
121
|
+
launcher: {},
|
|
122
|
+
discoveryRoots: [dir],
|
|
123
|
+
discoveryOptions: { timeBudgetMs: 2000 },
|
|
124
|
+
})
|
|
125
|
+
assert.equal(found.source, 'discovered')
|
|
126
|
+
assert.ok(found.path.endsWith('xray.exe'))
|
|
127
|
+
} finally {
|
|
128
|
+
process.env.PATH = previousPath
|
|
129
|
+
rmSync(dir, { recursive: true, force: true })
|
|
130
|
+
}
|
|
131
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal
|
|
3
|
+
rem Launch DeepSeek Harness with the embedded Chromium browser pointed at the
|
|
4
|
+
rem plugin's local HTTP inbound. A Node plugin cannot change Chromium's proxy at
|
|
5
|
+
rem runtime, so the switch has to be present when the app starts.
|
|
6
|
+
rem
|
|
7
|
+
rem Usage:
|
|
8
|
+
rem launch-with-browser-proxy.cmd ["C:\path\to\DeepSeek Harness.exe"]
|
|
9
|
+
rem
|
|
10
|
+
rem Close DSH completely before running this: a second launch only focuses the
|
|
11
|
+
rem existing single instance and never applies the flag.
|
|
12
|
+
|
|
13
|
+
set "PORT=20808"
|
|
14
|
+
set "EXE=%~1"
|
|
15
|
+
if not defined EXE set "EXE=%DSH_EXE%"
|
|
16
|
+
|
|
17
|
+
if not defined EXE if exist "%LOCALAPPDATA%\Programs\DeepSeek Harness\DeepSeek Harness.exe" set "EXE=%LOCALAPPDATA%\Programs\DeepSeek Harness\DeepSeek Harness.exe"
|
|
18
|
+
if not defined EXE if exist "%ProgramFiles%\DeepSeek Harness\DeepSeek Harness.exe" set "EXE=%ProgramFiles%\DeepSeek Harness\DeepSeek Harness.exe"
|
|
19
|
+
if not defined EXE if exist "E:\DS\DeepSeek Harness.exe" set "EXE=E:\DS\DeepSeek Harness.exe"
|
|
20
|
+
|
|
21
|
+
if not defined EXE (
|
|
22
|
+
echo DeepSeek Harness executable not found.
|
|
23
|
+
echo Pass the path explicitly:
|
|
24
|
+
echo %~nx0 "C:\path\to\DeepSeek Harness.exe"
|
|
25
|
+
exit /b 1
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
echo Launching with embedded browser proxy: http://127.0.0.1:%PORT%
|
|
29
|
+
start "" "%EXE%" --proxy-server=http://127.0.0.1:%PORT%
|
|
30
|
+
endlocal
|