npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jaxzhou/dsh-proxy-client

v0.4.1

Published

DeepSeek Harness plugin: connects this terminal's local dsh web to a dsh-proxy-server and lets the same account's other ends reach it through that server (reverse access). Signs in with the textwork.cn account (phone + SMS code, or username + password), a

Readme

@jaxzhou/dsh-proxy-client

dsh-proxy 的 DSH 客户端插件包(npm 包名 @jaxzhou/dsh-proxy-client,本地目录 packages/dsh-proxy-client):把本机终端里的 dsh web 通过 dsh-proxy-server 反向暴露给同账号的其它端,并提供 GUI 配置页面。

  • 宿主半(lib/index.js,cordis 插件):本地配置/令牌存储、出站 WebSocket 持久连接与自动重连、把服务器开出的虚拟流桥接到 ctx.webServer.port 上的本机 dsh web、注册面板 API。零运行时依赖(用 Node 内置 WebSocket)。
  • 浏览器半(lib/client.js,window.__ModuleLoader__ bundle):侧边栏底部入口 + 键控 main 面板,提供服务器地址、与 textwork.cn 相同的账号登录(手机号+短信验证码 / 账号密码)、连接开关、设备列表与「切换到此端」。

账号与 textwork.cn 一致:手机号 + 短信验证码(含京东云人机验证),或账号密码。登录由代理服务器 内部转调 textwork.cn 后端完成;本机只保存服务器签发的长效 token(默认 30 天),密码不落盘。

安装

# 在跑 dsh web 的机器上(包内声明了 dsh.bundle,安装后自动加入 profile bundles 并激活)
dsh plugin --profile web add @jaxzhou/dsh-proxy-client    # 从 npm
dsh plugin --profile web add <仓库路径>/packages/dsh-proxy-client   # 或从本地目录(开发时)
dsh --profile web          # 重启后生效

启动日志出现下面两行即装载成功:

[dsh-proxy-client] panel API ready; local dsh web on 127.0.0.1:3080
[dsh-proxy-client] not connected yet - configure the server address and log in from the DSH sidebar panel

若把包作为普通依赖安装(不带 bundle 声明),需在 profile 的 cordis.patch.yml 手工加入:

- insert:
    - id: jaxzhou-proxy-client
      name: '@jaxzhou/dsh-proxy-client'

⚠️ 不要同时使用两种方式:本包自带的 bundle 层会插入同一行;重复的 loader entry id 会让 dsh web 启动失败(duplicate loader entry id: jaxzhou-proxy-client)。如果你以前手工加过这一行,升级/安装本包后 请把手工行删掉(profile 的 cordis.patch.yml 恢复为 [])再重启。

故障隔离(远端不可用时不影响 DSH)

本插件是可选增强:代理服务器宕机、网络不可达、服务器地址写错、甚至本地配置文件不可写, 都不应影响 DSH 本体和其它插件。为此:

| 风险 | 处理 | |---|---| | 启动时阻塞 | apply() 不做任何网络 I/O;首次连接用 setTimeout(0) 推迟到启动路径之外,DSH 启动不等待本插件 | | 连接挂死 | 连接阶段有 20s 上限(connectTimeoutMs,含 TCP/TLS/WS 升级);握手另有 10s 上限;超时即放弃并按指数退避重试(1s → 30s) | | 关闭卡住 | stop() 最多等 3s(stopTimeoutMs),并用 generation 计数让被放弃的旧循环彻底失效,不会出现双循环 | | 未处理 rejection 杀掉进程 | 连接循环、重连、事件回调、节流定时器全部自带捕获;后台任务绝不以 rejection 收尾(unhandled rejection 在现代 Node 上是致命错误,会连带杀掉 DSH) | | 同步抛错破坏插件树 | apply() 整体不抛:面板路由注册失败只损失面板、隧道照常;配置存储不可用则整个插件安静退出 | | 配置文件不可写 | 读/写/解析失败一律降级为内存配置(store.lastError 记录原因,日志提示一次),插件继续可用 | | GUI 渲染异常 | 插件注入的两个 slot 都包在错误边界里:本插件面板崩溃只会显示一块「已隔离」小卡片,不会白屏 DSH 或影响其它插件 slot | | 面板请求挂住 | 浏览器半的每次调用有 30s 上限;宿主半的面板 API 对上游请求有 20s 上限,服务器不可达时快速返回类型化错误(含上游状态码) | | 日志刷屏 | 连接失败只在状态变化时记录,退避到 30s 后每分钟最多两条 | | 进程无法退出 | 插件创建的所有定时器都 unref(),不会阻止 DSH 正常关闭 |

对应的自动化断言见 npm run e2e 的「resilience: remote server unavailable」一节(93 项断言内)。

配置页面

入口在 侧边栏底部、设置(Settings)行正上方:一个「远程访问」行(折叠成窄栏时是圆形图标), 行尾带一个连接状态小圆点(绿=已连接 / 黄=连接中 / 红=认证失败 / 灰=未连接)。点击即可打开配置面板:

| 区块 | 内容 | |---|---| | 状态 | 已连接 / 连接中 / 认证失败 / 未配置,以及当前连接的细节 | | 本机 DSH Web | 代理将要暴露的本机地址(自动取自 DSH 实际监听端口)与设备名/ID | | 服务器 | 服务器地址、设备名、自动连接、反向访问(是否共享本机 DSH 启动令牌)、连接 / 断开、保存并应用;外加一行只读的「登录账号」状态(已登录显示显示名 + 切换账号 / 退出登录) | | 同账号在线端 | 设备名、ID、连接时间、「本机」标记与「在服务器中打开 →」(新标签页打开服务器同源的设备视图 https://<server>/d/<设备ID>/,由服务器经本插件的隧道代理到对方本机的 dsh web) |

生产服务器地址:https://dsh.textwork.cn。

连接与登录流程

面板本身只有「地址 + 连接」,凭据只在弹窗里输入:

填服务器地址 ──► 点「连接」
                   │
                   ├─ 本机已有可用 token ──► 直接连接(不打扰)
                   │
                   └─ 没有 token / token 已失效 ──► 弹出账号输入框
                                                     ├─ 手机号验证码登录(可注册)
                                                     └─ 账号密码登录
                                                     ↓
                                提交给代理服务器换取登录令牌(密码不落盘)
                                                     ↓
                                              自动连接并上线

要点:

  • 客户端不保存密码:弹窗里的凭据只提交给代理服务器,服务器校验后返回一个长效 token, 本机只保存这个 token($DSH_HOME/dsh-proxy-client.json,0600)。
  • 弹窗内容随服务器能力变化:/api/auth/config 报告该服务器是否支持短信登录 / 账号密码 / 注册 (textwork.cn 三种都支持;--auth local 的独立部署只显示账号密码)。
  • 切换服务器会失效旧 token:/config 发现地址变化时会清掉上一个服务器签发的 token, 下次「连接」重新弹窗——避免拿 A 服务器的 token 去连 B 服务器。
  • 登录态失效(服务器重启、token 过期、被吊销)时面板显示「认证失败」,点「连接」会重新弹窗。
  • 已登录时也可以点「切换账号」主动换一个账号,或点「退出登录」清除本机 token。

本地配置

$DSH_HOME/dsh-proxy-client.json(默认 ~/.dsh/dsh-proxy-client.json,权限 0600):

{
  "serverUrl": "https://dsh.textwork.cn",
  "account": "13810385787",
  "displayName": "138****5787",
  "token": "<代理服务器下发的 bearer token>",
  "deviceId": "<本机稳定设备 ID,首次自动生成>",
  "deviceName": "desk-pc",
  "autoConnect": true,
  "shareLoginToken": true
}

密码与短信验证码从不写入该文件。

面板 API(DSH 同源,供面板使用)

| 方法与路径 | 说明 | |---|---| | GET /dsh-proxy-client/api/status | 连接状态、脱敏配置、本机 dsh web 地址、同账号设备(带 self 标记) | | GET /dsh-proxy-client/api/auth-config?serverUrl= | 目标服务器支持的登录方式(authMode/phoneLogin/passwordLogin/register) | | GET /dsh-proxy-client/api/captcha-session?serverUrl= | 人机验证风险预判(转发给代理服务器 → textwork 上游) | | POST /dsh-proxy-client/api/send-code | {serverUrl?, phone, captchaSid, captchaToken} → 发送短信验证码 | | POST /dsh-proxy-client/api/login | /register | {serverUrl?, phone, smsCode, displayName?} 或 {serverUrl?, username, password} → 登录并保存 token、连接 | | POST /dsh-proxy-client/api/config | {serverUrl?, deviceName?, autoConnect?, shareLoginToken?} → 保存并重连;地址变化时清除上一个服务器签发的 token,返回体里的 config.hasToken 决定面板是直接连接还是弹窗 | | POST /dsh-proxy-client/api/logout | 注销并断开 | | POST /dsh-proxy-client/api/connect | /disconnect | 手动连接控制 | | POST /dsh-proxy-client/api/attach | {deviceId} → 服务器返回同源代理地址(/d/<设备ID>/?ticket&token),浏览器无需能访问目标机器 |

该路由与 DSH GUI 同源、默认只在回环可达;令牌始终留在宿主半,浏览器半拿不到。 上游状态码会一并回传({ok:false,error,status}),因此验证码服务不可用时前端能与服务器一样「放行」。

反向访问的登录链路

目标端 DSH 的首页需要它自己的进程启动令牌。宿主半通过 DSH Connection 服务的 ctx.connection.authenticatedUrl() 读到该令牌(受「反向访问」开关控制),随 hello 上报给服务器;服务器在 attach URL 上拼接:

https://dsh.textwork.cn/d/<deviceId>/?__dshproxy_ticket=<ticket>&token=<目标端启动令牌>

浏览器停在代理服务器的源上,服务器经隧道转发;目标 DSH 完成 token→cookie 交换,标签页里获得完整可用的 GUI。

开发与测试

# 无需 DSH 的宿主模拟器(跑本包真实的连接管理器)
node scripts/fake-client.ts --server http://127.0.0.1:4000 --username demo --password demo1234 \
     --name desk-pc --target 127.0.0.1:3080 --state .tmp/client-desk.json

npm run smoke:client     # 用 react-dom/server 真渲染浏览器半(28 项:面板无内联凭据表单、弹窗两种登录方式、错误边界)
npm run e2e              # 进程内 97 项断言(含 textwork 认证中转、面板 API 契约与故障隔离)
npm run probe:deployed   # 对已部署服务器做真实 TLS 端到端探测(两阶段:服务端隧道 + 面板 API 登录流程)
npm run check            # typecheck + smoke:client + e2e
npm run demo             # 五进程本地演示

代码地图

| 文件 | 作用 | |---|---| | lib/index.js | cordis 插件:注册面板路由、读取 DSH 启动令牌、启动/停止连接;apply() 不抛错、不做内联网络 I/O | | lib/connection.js | WebSocket 会话循环、重连退避、流桥接、背压;连接/握手/停止均有超时上界,回调全部隔离 | | lib/settings.js | 配置存储(原子写、0600、deviceId 生成);I/O 失败降级为内存配置并记录 lastError | | lib/api.js | 面板 API、textwork 登录链路中转、服务器 REST 调用(20s 上游超时,响应写入自带保护;切换服务器时清 token) | | lib/protocol.js | 线协议帧(与 proxy-server 的 protocol.ts 逐字节一致,e2e 校验) | | lib/client.js | 浏览器半 bundle:侧边栏图标 + 「地址 + 连接」主面板 + 账号弹窗(两种登录方式)+ 人机验证浮层;两个 slot 均包错误边界,调用有 30s 上限 | | cordis.patch.yml | bundle 层:插入插件行(id: jaxzhou-proxy-client) |