dsh-plugin-tether 0.1.8 → 0.1.10

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 CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  本文件记录面向用户的变化。每个版本的这一节会原样作为该版本 Release 的说明。
4
4
 
5
+ ## 0.1.10
6
+
7
+ ### 修复
8
+
9
+ - **macOS / Linux:sidecar 起不来,配对时报「sidecar 没有在 5 秒内给出配对码」,终端见 Permission denied**。npm 平台子包里的 `tether-host` 二进制自首个版本起就缺可执行位——CI 的 artifact 传输不保留 Unix 权限位,打包时原样带进了 tgz。Windows 不看执行位,故只在 Mac/Linux 上暴露。由 Mac 用户反馈发现。
10
+
11
+ 现打包前补回执行位,并对每个发布产物断言执行位存在,缺位则发布直接失败。**Mac/Linux 用户升级本版即修复**:`dsh plugin --profile web add dsh-plugin-tether@0.1.10`。如暂不升级,也可手动 `chmod 0755` 装好的 `dsh-tether-host-*/bin/tether-host` 应急。
12
+
13
+ - sidecar 启动失败不再只留一句干瘪的错误:插件现在会接住并打印可读原因(EACCES 附排查提示),「连接手机」弹窗里也会立即显示原因,而不是干等 5 秒换一个 504。
14
+
15
+ ## 0.1.9
16
+
17
+ ### 新增
18
+
19
+ - **适配 dsh 0.1.2-alpha 的浏览器认证**([#4](https://github.com/zexadev/dsh-tether/issues/4))。dsh 0.1.2-alpha 起,网页界面与 Host API 要求持有经启动 token 兑换的签名 cookie,无凭据一律 401——手机端表现为连上后一片 401 提示。
20
+
21
+ 适配完全在电脑侧完成:插件启动时自动完成 token→cookie 兑换,sidecar 在代理出口把认证注入每个转发请求。**手机 App 无需更新**;dsh 的跨站请求防护经隧道原样保留(伪造来源仍被 403 拒绝)。
22
+
23
+ cookie 是 `SameSite=Strict` 且绑定地址端口,手机 WebView 的内嵌页面属跨站上下文、cookie 根本不会随请求发出,所以「把登录链接发给手机打开」这条路在浏览器规则下走不通,注入只能发生在电脑侧。
24
+
25
+ - 继续兼容 dsh 0.1.0-rc.7 / rc.8:旧版没有浏览器认证,代理保持原样透传,行为不变。
26
+
5
27
  ## 0.1.8
6
28
 
7
29
  ### 修复
package/README.md CHANGED
@@ -17,9 +17,11 @@
17
17
  <a href="../../releases"><img src="https://img.shields.io/github/downloads/zexadev/dsh-tether/total?style=flat&label=downloads&color=4D6BFE" alt="Downloads"></a>
18
18
  <a href="../../stargazers"><img src="https://img.shields.io/github/stars/zexadev/dsh-tether?style=flat&label=%E2%98%85&color=08C" alt="Stars"></a>
19
19
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat" alt="MIT"></a>
20
- <img src="https://img.shields.io/badge/dsh-0.1.0--rc.7%20%7C%20rc.8-4D6BFE?style=flat" alt="dsh 0.1.0-rc.7 | rc.8">
20
+ <img src="https://img.shields.io/badge/dsh-0.1.0--rc.7%20%7C%20rc.8%20%7C%200.1.2--alpha-4D6BFE?style=flat" alt="dsh 0.1.0-rc.7 | rc.8 | 0.1.2-alpha">
21
21
  <img src="https://img.shields.io/badge/Android-4493F8?style=flat" alt="Android">
22
22
  <img src="https://img.shields.io/badge/iOS-beta-8E8E93?style=flat" alt="iOS beta">
23
+ <a href="https://www.dsh.so/artifact/dsh-tether"><img src="https://www.dsh.so/badge/dsh-tether.svg" alt="dsh.so security scan"></a>
24
+ <a href="https://www.dsh.so/artifact/dsh-tether"><img src="https://www.dsh.so/badge/install/dsh-tether.svg" alt="dsh.so install check"></a>
23
25
  </p>
24
26
 
25
27
  <p align="center">
@@ -106,7 +108,7 @@ From then on the app connects by itself when you open it.
106
108
  - The iOS build is beta: built only in CI, never run on a real device, and you sign it yourself. Android is the one verified on real hardware.
107
109
  - The app connects when you open it and holds no background connection — Android's doze would not let it anyway.
108
110
  - The interface on the phone is DSH's own; the narrow-screen fit comes from minimal injected styles, so a dsh layout change may need a follow-up here.
109
- - Verified against dsh **`0.1.0-rc.7`** and **`0.1.0-rc.8`**. dsh is in developer preview — check this line before assuming a newer dsh works.
111
+ - Verified against dsh **`0.1.0-rc.7`**, **`0.1.0-rc.8`** and **`0.1.2-alpha`**. dsh is in developer preview — check this line before assuming a newer dsh works. The browser authentication dsh introduced in 0.1.2-alpha is handled entirely on the machine side; the phone app needs no update for it.
110
112
 
111
113
  ## What has been verified
112
114
 
package/README.zh.md CHANGED
@@ -17,9 +17,11 @@
17
17
  <a href="../../releases"><img src="https://img.shields.io/github/downloads/zexadev/dsh-tether/total?style=flat&label=downloads&color=4D6BFE" alt="下载量"></a>
18
18
  <a href="../../stargazers"><img src="https://img.shields.io/github/stars/zexadev/dsh-tether?style=flat&label=%E2%98%85&color=08C" alt="Stars"></a>
19
19
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat" alt="MIT"></a>
20
- <img src="https://img.shields.io/badge/dsh-0.1.0--rc.7%20%7C%20rc.8-4D6BFE?style=flat" alt="dsh 0.1.0-rc.7 | rc.8">
20
+ <img src="https://img.shields.io/badge/dsh-0.1.0--rc.7%20%7C%20rc.8%20%7C%200.1.2--alpha-4D6BFE?style=flat" alt="dsh 0.1.0-rc.7 | rc.8 | 0.1.2-alpha">
21
21
  <img src="https://img.shields.io/badge/Android-4493F8?style=flat" alt="Android">
22
22
  <img src="https://img.shields.io/badge/iOS-%E6%B5%8B%E8%AF%95%E7%89%88-8E8E93?style=flat" alt="iOS 测试版">
23
+ <a href="https://www.dsh.so/artifact/dsh-tether"><img src="https://www.dsh.so/badge/dsh-tether.svg" alt="dsh.so 安全扫描"></a>
24
+ <a href="https://www.dsh.so/artifact/dsh-tether"><img src="https://www.dsh.so/badge/install/dsh-tether.svg" alt="dsh.so 安装实测"></a>
23
25
  </p>
24
26
 
25
27
  <p align="center">
@@ -106,7 +108,7 @@ dsh plugin --profile web add .
106
108
  - iOS 版是测试版:只在 CI 上构建,从未在真机运行过,且需要自行签名。安卓版是经过真机验证的那一个。
107
109
  - App 打开时才连接,不在后台常驻——Android 的 doze 也留不住它。
108
110
  - 手机上的界面是 DSH 自己的,窄屏适配靠注入的最小样式完成;dsh 改版式时可能需要跟进。
109
- - 已针对 dsh **`0.1.0-rc.7`** 与 **`0.1.0-rc.8`** 验证。dsh 处于 developer preview,换更新的 dsh 之前先看这一行。
111
+ - 已针对 dsh **`0.1.0-rc.7`**、**`0.1.0-rc.8`** 与 **`0.1.2-alpha`** 验证。dsh 处于 developer preview,换更新的 dsh 之前先看这一行。dsh 0.1.2-alpha 引入的浏览器认证完全在电脑侧处理,手机 App 无需更新。
110
112
 
111
113
  ## 验证过什么
112
114
 
package/index.js CHANGED
@@ -107,10 +107,22 @@ function apply(ctx, config = {}) {
107
107
  if (config.pair) args.push('--pair')
108
108
  const child = spawn(binary, args, { stdio: ['pipe', 'pipe', 'inherit'] })
109
109
  ctx.effect(() => () => { child.kill() })
110
+ /** sidecar 不在了的原因;undefined = 活着。等它回话的路由用来立即报错 */
111
+ let sidecarGone
110
112
  child.on('exit', (code) => {
111
113
  // sidecar 死了必须可见:手机端会静默失联,而电脑侧一切照常
114
+ sidecarGone = `sidecar 已退出(code=${code})`
112
115
  console.error(`[tether] sidecar 退出 code=${code};手机端已失联`)
113
116
  })
117
+ // spawn 失败只走 error 不走 exit;不接住就是 uncaught。EACCES 曾实发过:
118
+ // 打包丢了二进制执行位,现场只有一句干瘪的 Permission denied,毫无指向
119
+ child.on('error', (error) => {
120
+ const hint = error.code === 'EACCES'
121
+ ? ';EACCES 通常是二进制缺可执行位,试 chmod 0755 该文件并升级插件'
122
+ : ''
123
+ sidecarGone = `sidecar 启动失败: ${error.message}${hint}`
124
+ console.error(`[tether] ${sidecarGone}(${binary})`)
125
+ })
114
126
 
115
127
  const send = (msg) => { child.stdin.write(JSON.stringify(msg) + '\n') }
116
128
  let endpointId = ''
@@ -175,6 +187,44 @@ function apply(ctx, config = {}) {
175
187
 
176
188
  ctx.effect(() => ctx.webServer.tapIndex(injectNarrowScreenCss))
177
189
 
190
+ // dsh 0.1.2-alpha 起浏览器界面要求认证:进程启动 token 经 GET /?token= 换成
191
+ // HMAC 签名 cookie,index 与 /api 无 cookie 一律 401。cookie 是 SameSite=Strict
192
+ // 且绑定 Host authority——手机 WebView 的 iframe 属跨站上下文,cookie 根本不会
193
+ // 随请求发出,把 token 交给手机没有用;唯一可行位置在电脑侧:这里自己完成兑换,
194
+ // 把 cookie 交给 sidecar 在代理流上逐请求注入。
195
+ ctx.inject(['connection'], (connCtx) => {
196
+ // rc.7/rc.8 也有 connection 服务,只是没有浏览器认证;判据必须是能力
197
+ // 本身而不是服务存在性(实测 rc.8 走到这里,靠服务存在性判会误报错)
198
+ if (typeof connCtx.connection.authenticatedUrl !== 'function') return
199
+ const authority = new URL(`http://${proxyTarget}`).host
200
+ const exchange = async () => {
201
+ const url = connCtx.connection.authenticatedUrl(`http://${proxyTarget}/`)
202
+ const res = await fetch(url, { redirect: 'manual' })
203
+ const setCookie = res.headers.getSetCookie()
204
+ if (res.status !== 303 || setCookie.length !== 1) {
205
+ throw new Error(`token 兑换应答异常: HTTP ${res.status}, set-cookie ×${setCookie.length}`)
206
+ }
207
+ send({ type: 'proxy-auth', cookie: setCookie[0].split(';', 1)[0], authority })
208
+ }
209
+ const exchangeWithRetry = async () => {
210
+ for (let attempt = 1; ; attempt++) {
211
+ try {
212
+ return await exchange()
213
+ } catch (error) {
214
+ if (attempt >= 3) {
215
+ console.error('[tether] 浏览器认证 cookie 获取失败,手机端将收到 401:', error)
216
+ return
217
+ }
218
+ await new Promise((resolve) => setTimeout(resolve, 1000 * attempt))
219
+ }
220
+ }
221
+ }
222
+ void exchangeWithRetry()
223
+ // cookie 绝对有效期默认 30 天,dsh 进程可能活得更久;定期重兑换续上
224
+ const timer = setInterval(() => { void exchangeWithRetry() }, 12 * 60 * 60 * 1000)
225
+ connCtx.effect(() => () => clearInterval(timer))
226
+ })
227
+
178
228
  // 「打开配置文件」在宿主机桌面开编辑器,手机上按了毫无反应。这条路由把同一份
179
229
  // 文件的内容原样交给手机自己渲染。路径由 Host 侧的 settings.documentPath 决定,
180
230
  // 不接受客户端传路径——否则就成了任意文件读取。
@@ -212,6 +262,12 @@ function apply(ctx, config = {}) {
212
262
  path: PAIRING_PATH,
213
263
  handler: async (req, res) => {
214
264
  if (!isTrustedRequest(req)) return refuse(res)
265
+ // sidecar 不在了就别让人干等 5 秒换一个不明所以的 504
266
+ if (sidecarGone !== undefined) {
267
+ res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' })
268
+ res.end(sidecarGone)
269
+ return
270
+ }
215
271
  const result = await new Promise((resolve) => {
216
272
  pendingPairing = resolve
217
273
  send({ type: 'pairing-begin' })
@@ -238,6 +294,11 @@ function apply(ctx, config = {}) {
238
294
  path: DEVICES_PATH,
239
295
  handler: async (req, res) => {
240
296
  if (!isTrustedRequest(req)) return refuse(res)
297
+ if (sidecarGone !== undefined) {
298
+ res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' })
299
+ res.end(sidecarGone)
300
+ return
301
+ }
241
302
  if (req.method === 'POST') {
242
303
  const id = await readForgetId(req)
243
304
  if (id === undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-tether",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Mobile client for the DeepSeek Harness — use the dsh on your dev machine from your Android or iOS phone, remotely across networks, peer-to-peer with no server in between (iroh)",
5
5
  "keywords": [
6
6
  "dsh",
@@ -42,18 +42,18 @@
42
42
  }
43
43
  },
44
44
  "peerDependencies": {
45
- "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.7",
46
- "@deepseek-ai/dsh-user-approval": ">=0.1.0-rc.7"
45
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.7 <0.1.1-0 || >=0.1.2-alpha.0 <0.1.3-0",
46
+ "@deepseek-ai/dsh-user-approval": ">=0.1.0-rc.7 <0.1.1-0 || >=0.1.2-alpha.0 <0.1.3-0"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
49
  "@deepseek-ai/dsh-host-webserver": { "optional": true },
50
50
  "@deepseek-ai/dsh-user-approval": { "optional": true }
51
51
  },
52
52
  "optionalDependencies": {
53
- "dsh-tether-host-darwin-arm64": "0.1.8",
54
- "dsh-tether-host-darwin-x64": "0.1.8",
55
- "dsh-tether-host-linux-arm64": "0.1.8",
56
- "dsh-tether-host-linux-x64": "0.1.8",
57
- "dsh-tether-host-win32-x64": "0.1.8"
53
+ "dsh-tether-host-darwin-arm64": "0.1.10",
54
+ "dsh-tether-host-darwin-x64": "0.1.10",
55
+ "dsh-tether-host-linux-arm64": "0.1.10",
56
+ "dsh-tether-host-linux-x64": "0.1.10",
57
+ "dsh-tether-host-win32-x64": "0.1.10"
58
58
  }
59
59
  }