Back to home@chendefine

dsh-cdp-live-view

deepseek harness live view chromium via cdp in sidebar tab

Stars
0
Language
TypeScript
Created
Aug 19, 2026
Updated
Aug 19, 2026
GitHub repo

Introduction

dsh-cdp-live-view

DSH 双端插件:在 DSH Web 侧边栏中实时查看并操控一个外部 Chromium 浏览器。

目标网页不会作为 iframe 运行在 DSH 页面里——Host 进程独占 CDP 连接,把页面抓成像素帧推给浏览器,Client 只在 Canvas 上渲染,并回传一组严格受限的交互命令。没有任意 CDP 透传,没有跨源 iframe 的麻烦。

依赖:本插件基于侧边栏框架 dsh-better-sidebar(>=0.13.0)注册 Tab。它是必选 peer 依赖——不安装它,本插件的 Tab 不会出现。请用下面的一句组合命令一起安装。

用途

  • 在 DSH Web UI 内提供一个「CDP 实时视图」侧边栏 Tab:看到 Chromium 里正在发生什么,并在允许的范围内直接操作它;
  • Host 是唯一接触 raw CDP WebSocket 的一端:Client 浏览器既拿不到 CDP 地址,也发不出任意 CDP 命令;
  • 像素流 + 受控输入的模型,天然适合把一个浏览器会话「投影」给多个观察者,或交给 AI 会话的操纵者监督。

使用场景

  • 远程 / 无头 Chromium 可视化:浏览器跑在服务器或容器里,你在 DSH 界面里直接看、直接点;
  • 在 DSH 会话中观察浏览器自动化:让 agent 操作浏览器的同时,人可以实时盯着画面,随时接管;
  • 轻量 VNC 替代:只针对浏览器这一种「远程桌面」,不需要装任何客户端;
  • 受控演示 / 排查:把一个测试浏览器投影给同事看,导航、点击都在你的权限模型之内。

安装方法

前提

  • Node.js >= 20;
  • DSH Web profile(web);
  • dsh-better-sidebar >= 0.13.0(见顶部依赖说明);
  • 一个已启动、且能从 DSH Host 进程访问的 Chromium CDP endpoint。示例:
chromium \
  --remote-debugging-address=127.0.0.1 \
  --remote-debugging-port=9222

从 npm 安装(推荐)

一条命令同时安装两个插件(DSH 按 profile 直接依赖激活插件层,缺了 better-sidebar 本插件不会生效):

dsh plugin add dsh-better-sidebar dsh-cdp-live-view --profile <profile-name>

安装后重启 dsh web 使新插件层生效。

pnpm 可能拦截 better-sidebar 的原生依赖构建(如 node-pty),按提示把具体包名加入 profile 目录下 pnpm-workspace.yamlallowBuilds 后重跑即可。

从 GitHub 源安装

dsh plugin add dsh-better-sidebar github:chendefine/dsh-cdp-live-view --profile <profile-name>

git 依赖在安装时通过 prepare 脚本构建,同样需要在 allowBuilds 放行 dsh-cdp-live-view(pnpm 会打印确切的 key)。

使用方法

配置 CDP 地址

CDP 地址在 DSH Web 设置页配置,不需要改 profile 数组:

  1. 打开 设置 → 侧边卡片 → 侧边栏内容 → CDP实时视图,点击卡片右下角的齿轮;
  2. 在「CDP 地址」输入框填写地址,失焦或按 Enter 保存;
  3. 留空 = 默认地址 127.0.0.1:9222;
  4. 支持的格式:
    • host:port(自动补 http://)或 http://host:port:Host 从 /json/version 发现 webSocketDebuggerUrl;
    • https:// / wss://:同上,TLS 由部署层保证;
    • ws://.../devtools/browser/...:直接连接 browser WebSocket;
  5. 保存后 Host 断开旧连接,已打开的实时视图自动重连到新地址。

地址持久化在 better-sidebar 的 prefs 文档(pluginSettings['dsh-cdp-live-view:live'].endpoint),Host 通过 DSH settings 服务读取并订阅变更。没有 loopback 限制——远程地址直接可用,请自行确保网络可达与访问控制。

同一设置面板里还有「允许交互」开关:

  • 关闭(默认):observe 模式,只能看;
  • 打开:interactive 模式,可以点击、输入、导航、新建/关闭标签页。

日常操作

  • 标签页(target)切换 / 新建 / 关闭:顶部标签条,需要 interactive 模式;
  • 鼠标:点击、拖动、滚轮,坐标按帧画面映射回页面;
  • 键盘:先点击画面获得焦点,之后直接输入。中文等 IME 走 composition 合成后一次性提交;粘贴经本机剪贴板转成文本插入;复制(Ctrl+C)暂不支持;
  • 导航:工具栏输入 HTTP(S) 地址回车,或使用 back / forward / reload;
  • 连接:工具栏可手动重连;断线后客户端自动退避重连。

Loader 运行时调优(可选)

profile 配置里的可选项(均可省略,括号内为默认值与范围):

- insert:
    - id: cdp-live-view
      name: dsh-cdp-live-view
      config:
        frameQuality: 60        # JPEG 质量 (20–90)
        frameMaxWidth: 1280     # 帧最大宽 (320–3840)
        frameMaxHeight: 900     # 帧最大高 (240–2160)
字段默认值范围
ticketTtlMs300005000–120000,一次性 ticket 有效期
connectTimeoutMs150001000–120000,CDP 连接超时
frameQuality6020–90,JPEG 质量
frameMaxWidth1280320–3840
frameMaxHeight900240–2160
frameEveryNth11–30,每 N 帧取 1 帧
bufferedAmountSoftLimit52428864KB–16MB,超过开始丢帧
bufferedAmountHardLimit4194304256KB–64MB,超过断开连接

故障排查

被拒绝的 WebSocket 升级返回明确的 HTTP 状态,浏览器控制台显示 Unexpected response code: <status>:

  • 401:ticket 无效或已过期(先调 /open 拿新 ticket);
  • 403:trust fence 拒绝(Host 头不在信任列表,或带 cross-site 标记);
  • 404:路径错误;
  • 1006(无状态码):请求在反向代理层就被掐断了。反代必须转发 WebSocket 升级头:
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
location / {
    proxy_pass http://127.0.0.1:3080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

CDP 连接失败会在 DSH Host 日志里输出 [dsh-cdp-live-view] CDP session attach failed: ...,浏览器端只看到断连,排障先看 Host 日志。

技术原理

双端架构

┌──────────────── DSH Host 进程 ────────────────┐      ┌── Chromium ──┐
│  Host half (src/index.ts)                     │      │              │
│  ├─ POST /dsh-cdp-live/api/open  ── 签发 ticket│      │  CDP :9222   │
│  ├─ WS   /sidebar/ws/cdp-live    ── 业务协议   │      │  (screencast │
│  └─ EndpointManager → puppeteer-core ────────────────┤   + Input)   │
└───────────────────────────────────────────────┘      └──────────────┘
         ▲ ticket + 二进制帧(JSON 元信息 + JPEG)          ┌── 浏览器 ────┐
         │                                                │ Client half  │
         └────────────────────────────────────────────────┤ Canvas 渲染  │
              鼠标/键盘/导航等受限命令(zod 白名单)          └──────────────┘
  • Host half(src/):注入 webServer / sessions / webRuntime 服务,注册 HTTP 与 WebSocket 路由,通过 puppeteer-core 维持唯一一条 CDP 连接;
  • Client half(src/client/,经 dsh-better-sidebar 注册 Tab):React 组件,Canvas 渲染帧,输入事件翻译成受限命令。

连接握手

POST /dsh-cdp-live/api/open        → trust fence + DSH session 校验
                                   → 签发一次性 256-bit ticket(TTL 默认 30s)
WS   /sidebar/ws/cdp-live?ticket=… → trust fence + ticket 一次性消费
                                   → 101 后进入版本化协议(v: 1)
  • Client 从头到尾不知道 CDP 地址,也拿不到任何 CDP 凭据——连接和 target ID 都是 Host 进程内的临时状态;
  • ticket 绑定 session 与模式(observe / interactive),用一次即废;
  • 升级请求不要求 Origin:DSH 常见反代模式会把 Host 重写为 loopback 并丢弃 Origin,因此准入门槛是 trust fence(Host + cross-site 标记)加一次性 ticket。

帧流水线

Page.startScreencast (JPEG, 质量上限受配置约束)
  → Host 收到 Page.screencastFrame 后立即 ACK(与下游消费解耦)
  → LatestFrameQueue        单槽最新帧队列:生产者永不阻塞,新帧顶掉旧帧
  → ws.send(frameMeta JSON) 元信息(sequence / mimeType / byteLength …)
  → ws.send(JPEG bytes)     二进制帧紧随其后
  → Client Canvas 绘制

两级背压基于 ws.bufferedAmount:

  • 超过软限(默认 512KB):丢帧不发送,画面稍旧但连接健康;
  • 超过硬限(默认 4MB):直接以 1013 断开,防止慢客户端无限积压内存。

Tab 隐藏或面板折叠时(better-sidebar 的 visible gating)自动停止帧流,重新可见再恢复。

命令协议

Client 能发的命令是一个 zod strict() 判别联合(src/cdp/protocol.ts),枚举如下,没有任何 {method, params} 形式的任意 CDP 透传:

  • targets.list / targets.create / target.select / target.detach / target.close
  • visibility
  • screencast.start / screencast.stop(选项:format / quality / maxWidth / maxHeight / everyNthFrame)
  • input.mouse / input.key / input.text
  • navigate(仅 http(s) URL)/ history(back / forward / reload)
  • ping

连接与租约管理

  • EndpointManager:单条隐式 CDP 连接,generation 标识;设置页变更地址 → Host 监听 settings/document-updated → 关旧连接、以 1012 弹开客户端 → 客户端退避重连落到新地址;
  • LeaseManager:同一 target 的 screencast 帧队列同一时刻只有一个消费者,切换 target 先释放旧租约;
  • TargetRegistry:target 发现、切换与销毁通知(title 以 Target.getTargets 快照为准,1s 轮询)。

安全边界

默认开放的 CDP 能力仅包括:target 生命周期、screencast start/stop/ack、Input.dispatch*Page.navigate / Page.reload / navigation history。

默认开放:

  • Runtime.evaluate;
  • Cookie / Storage / Network;
  • 下载、上传、权限授予;
  • 任意 CDP method passthrough;
  • DevTools frontend。

注意两点:

  1. CDP 地址由 Web UI 配置,Host 不施加 loopback 限制——能打开该设置页的会话就能让 DSH Host 向任意地址发起连接,请仅在可信环境暴露设置页;
  2. trust fence 防的是 cross-site 与 DNS rebinding,不替代用户认证;Keyless DSH 应仅作为本机单用户工具使用。

目录结构

src/
├── index.ts              Host half 入口(路由注册、settings 订阅)
├── config.ts             配置 schema 与 endpoint 归一化
├── trust-fence.ts        Host/cross-site 信任判定
├── routes/
│   ├── http.ts           /open 路由 + ticket registry
│   └── websocket.ts      WS 升级路由(trust fence + ticket 准入)
├── cdp/
│   ├── live-session.ts   会话编排:attach、命令分发、帧泵
│   ├── endpoint-manager.ts / browser-connection-manager.ts / puppeteer-adapter.ts
│   ├── screencast-controller.ts / frame-queue.ts
│   ├── input-controller.ts / target-controller.ts / target-registry.ts
│   ├── lease-manager.ts  target 独占租约
│   └── protocol.ts       版本化协议 zod schema
└── client/               better-sidebar Tab(React + Canvas + 输入桥接)
tests/                    vitest(协议、安全、队列、键盘桥接、wiring 等)

开发

pnpm install
pnpm typecheck
pnpm test      # 构建后跑 vitest
pnpm build

构建产物:

lib/index.js             Host half
lib/client.js            官方 profile client bundle
lib/client-registry.js   registry client bundle
lib/types/               类型声明

已知限制

  • 一个 target 同一时刻只支持一个消费中的 screencast 帧队列;
  • Chromium 的启动与进程生命周期不由插件管理;
  • target title 以 1s 轮询的命令快照为准,导航后有短暂延迟;
  • 键盘:复制(Ctrl+C)暂不支持(需要读取远端选区,后续版本提供);Touch、文件上传/下载、完整 DevTools 未实现;
  • 未实现 Page.captureScreenshot 兼容回退;
  • 远程、多用户、多 Host 部署需要外部认证、共享 ticket store 和更严格的网络策略。

许可证

MIT