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

dsh-sidebar-onlyoffice

v0.2.0

Published

DSH web plugin: open .docx/.xlsx/.pptx in the better-sidebar editor through a self-hosted ONLYOFFICE Document Server (JWT-signed config, in-network document/callback routes, save-back to disk; the DS browser UI is always served same-origin through the bui

Readme

dsh-sidebar-onlyoffice

English · npm · GitHub

DeepSeek Harness(DSH)web 插件:在 dsh-better-sidebar 的文件侧边栏中,用自部署的 ONLYOFFICE Document Server 打开并编辑 .docx / .xlsx / .pptx —— 配置经 JWT 签名、保存原子写回磁盘,并且 AI 修改已打开的文件时编辑器实时刷新

npm license node CI stars

功能

  • 真正的办公编辑 —— better-sidebar viewer 内嵌完整的 ONLYOFFICE 编辑器(文字/表格/演示),不是静态预览;保存自动写回磁盘文件。
  • 同源反代(常开) —— DS 的整套浏览器界面(api.js、编辑器 iframe、静态资源、协同 socket)由 dsh web 自身在 /sidebar/onlyoffice/ds 反代,浏览器无需访问到 DS 的服务地址,DS 可完全留在内网。
  • 默认签名与围栏 —— 配置密钥后编辑器配置整体 HS256 JWT 签名;文件下载一律凭短时效 HMAC 令牌;浏览器侧路由在 dsh web 信任围栏之内;会话工作目录之外的文件一律拒绝。
  • AI 修改实时可见 —— agent 修改编辑器中打开的文件时,viewer 约 1 秒内经 refreshFile 换到新版本,页面不重载、api.js 不重载。脏编辑器绝不自动刷新(改为横幅提示手动重载)。
  • 原子保存 + 自保存抑制 —— 回调下载保存字节后以临时文件 + rename 原子替换,同 key 串行化;插件自身写入被监听枢纽吸收,用户保存不会回环触发刷新。
  • 内网直连拉取保存文档 —— DS 上报的保存 URL 会改写到 documentServerUrl(容器间直连),避免绕公网反代回环。
  • 与 office 预览插件共存 —— viewer id onlyoffice:docx|xlsx|pptx(priority 10)与 @huanlin/dsh-plugin-better-sidebar-plugin-officedocx/xlsx/pptx(priority 0)不冲突;侧边卡片设置页可分别禁用。

工作原理

| 半 | 位置 | 职责 | | --- | --- | --- | | Node(服务端)| src/ | 在 dsh web 的 webServer 上注册四个路由,实现 ONLYOFFICE 官方集成文档中的 "document storage service" 角色。 | | 浏览器(客户端)| src/client/ | 注册三个 better-sidebar 文件 viewer;按需加载 DS api.js、挂载 DocsAPI.DocEditor、订阅 SSE watch 流。 |

better-sidebar viewer (onlyoffice:docx|xlsx|pptx)
   ├─ GET /sidebar/onlyoffice/config    (浏览器端,信任围栏内)
   │     → 编辑器配置 + JWT + api.js 地址 + HMAC 文件令牌
   ├─ GET /sidebar/onlyoffice/file      (Document Server;HMAC 令牌即鉴权)
   ├─ POST /sidebar/onlyoffice/callback (Document Server;status 2/6 → 原子写回)
   ├─ GET /sidebar/onlyoffice/watch     (浏览器端 SSE;inotify 磁盘变更)
   │     → change → 重取 config → docEditor.refreshFile(config)  [不重载]
   └─ /sidebar/onlyoffice/ds/**         (浏览器 ↔ Document Server,同源)
         → api.js + 编辑器 iframe + 静态资源 + socket.io(WS 按文档即时路由,失败降级轮询)
  • Node 半 —— config 路由对会话 cwd 内的绝对路径生成编辑器配置(documentType 映射、内容寻址 key = sha256(host+path+size+mtime)),整体 HS256 JWT 签名,登记 key→文件 映射并签发短时效 HMAC 文件令牌。file 路由向 DS 提供文件字节。callback 路由(JWT 校验)在 status 2/6 时下载保存字节并临时文件+rename 原子写回,status 4 清除映射,应答 {"error":0}。watch 路由以 SSE 推送磁盘变更,由 inotify 监听枢纽供给(fs.watch 同时盯文件与其所在目录,防抖后按同一内容寻址 key 做签名过滤)。
  • 浏览器半 —— 按 URL 仅一次加载 DS api.js,挂载编辑器,卸载时 destroyEditor();失败时展示错误面板 +「下载文件」回退链接(复用 better-sidebar 的 /sidebar/file 路由)。挂载期间订阅 watch 流,发现外部(通常是 AI)修改后重取 config 并调用 docEditor.refreshFile(config)

AI 修改的实时可见

agent 修改了编辑器中打开的文件时,viewer 约 1 秒内感知:inotify → SSE change 事件 → 重取 config(内容寻址 key 随文件而变)→ refreshFile。编辑器 iframe 本体被复用——页面不重载、api.js 不重载,DS 以全新会话打开新版本(旧 key 以 status 4 关闭)。护栏:

  • 脏编辑器绝不自动刷新 —— refreshFile 无条件丢弃未保存修改(集成方调用路径上没有 isDocumentModified 守卫,已在 DS 源码中核实)。此时改为展示横幅(「文件已被外部修改——重新加载将丢弃未保存的修改」)+ 手动「重新加载」按钮,点击即视为用户确认。
  • DS 自发的 onRequestRefreshFile(断线重连 / 同 key 保存时触发,仅在非脏状态)走同一条刷新路径。
  • 插件自身的回调保存在服务端被抑制(noteSelfSave 记录刚写入的签名),用户保存不会回环刷新、重置光标。
  • 文件被删除推 removed 提示;重新出现推 change

环境要求

  • DSH web profile(dsh web),Node.js ≥ 20,并已安装 dsh-better-sidebar 插件。
  • 自部署的 ONLYOFFICE Document Server(已在 9.4 社区版实测),网络可达 dsh web 服务器;浏览器无需能访问它(反代挂载点是唯一的浏览器侧 DS 入口)。
  • JWT 启用时(DS 默认),插件的 jwtSecret 必须与 DS 的 JWT_SECRET 一致。
  • document.url / callbackUrl 为私网地址时,DS 需要 ALLOW_PRIVATE_IP_ADDRESS=true(DS 默认拒绝)。

安装

从 npm 源安装(预构建 —— 无需构建权限):

dsh plugin --profile web add dsh-sidebar-onlyoffice

从 GitHub 仓库安装(源码 —— pnpm 会执行 prepare 构建;若 pnpm 拦截构建脚本,请在 profiles/web/pnpm-workspace.yaml 里放行):

dsh plugin --profile web add github:chendefine/dsh-sidebar-onlyoffice

也可以通过 DSH 插件市场(设置 → DSH插件市场)—— 仓库带 dsh-plugin topic,会被自动索引。

bundle 插件加入 profile 层栈后,把配置写进 profile 的 cordis.patch.yml 手编层(见下),重启 dsh web,浏览器硬刷新(Ctrl+Shift+R)。卸载:dsh plugin --profile web remove dsh-sidebar-onlyoffice 后再重启。

配置

所有键均可选;由 profile 的 cordis.patch.yml 层承载:

- id: dsh-sidebar-onlyoffice
  config:
    jwtSecret: "<DS 的 JWT_SECRET>"                # 空 = 不签名(仅 JWT_ENABLED=false 的 DS)
    # documentServerUrl: http://onlyoffice-documentserver  # dsh web 直连的 DS 入口(其自带 nginx)
    # internalBaseUrl: http://172.31.255.4:3080    # DS 回连 dsh web 的基址;空 = 自动探测
    # publicBaseUrl: https://work.example.com      # 网关改写 Host 时的浏览器可见源;空 = 按请求 Origin
    # defaultMode: edit                             # edit | view
    # username: User                                 # 编辑器右上角显示的协作用户名
    # fileLimitMb: 100
    # tokenTtlSec: 600

| 字段 | 默认值 | 说明 | | --- | --- | --- | | jwtSecret | '' | 与 Document Server 共享的密钥(其 JWT_SECRET)。设置后编辑器配置与回调均 JWT 签名/校验;为空仅适用于 JWT_ENABLED=false 的 DS。 | | documentServerUrl | '' | 唯一的 Document Server 地址:dsh web 自身直连的 DS 入口——docker 网络上 DS 容器自带的 nginx(http://onlyoffice-documentserver)或发布到宿主的端口。同时充当反代上游与保存字节拉取基址。 | | internalBaseUrl | (自动探测) | DS 回连本 dsh web 服务器的基址(文档下载 + 回调)。空 = 自动探测本机非环回 IPv4 + web 端口。 | | publicBaseUrl | (请求 Origin) | 浏览器可见源(协议://主机[:端口]),用于铸造 X-Forwarded-Host/Proto 和匹配 DS 上报的保存 URL。dsh web 前面的网关会改写 Host 时必须设置;空 = 按每个请求的 Origin 推导。 | | defaultMode | edit | 新标签页的默认打开模式(edit/view)。 | | username | User | 编辑器右上角显示的协作用户名(editorConfig.user.name);留空恢复为 User。修改后重新打开文件生效。 | | fileLimitMb | 100 | 提供与写回的最大文件体积(MB)。 | | tokenTtlSec | 600 | 签名 URL 令牌的有效期(秒)。 |

0.2.0 之前的旧键(proxyproxyUpstreaminternalDocumentServerUrlproxyPublicBasedocumentServerPort)启动时映射一次——proxyUpstream/internalDocumentServerUrl 映射到 documentServerUrl,proxyPublicBase 映射到 publicBaseUrl——并打一条警告;请尽快迁移到新名称。

图形化配置(设置卡片)

组合装载了 DSH settings 服务时(标准 dsh web bundle 均装载),插件会注册 dsh-sidebar-onlyoffice 设置命名空间,Web 客户端的 设置 → 插件 标签页会为它渲染一张配置卡片——八个字段全部可视化,中英文案,暂存式编辑 + 保存/放弃。提交的修改写入部署的用户配置(<DSH_HOME>/settings.yaml),叠加在组合层之上,无需重启即生效:路由闭包与反向代理都按实时配置读取(jwt/mode/username/limit/token/URL 在下一次编辑器配置请求生效;documentServerUrl 变更则立即切换代理上游——已发现的版本化前缀会重置并重新探测,通过卡片首次配置出的地址会即刻认领挂载点)。因此部署可以带空的组合层条目启动,完全通过卡片完成配置。

字段优先级:卡片/用户层 → 组合层(cordis.patch.ymlconfig: 块)→ schema 默认值。在卡片上点"恢复默认"即回落组合层;直接手编 settings.yaml 同样实时生效。

保存 URL 改写(内网直连拉取)

两个方向各自独立配置:DS → 本插件用 internalBaseUrl(文档下载 + 回调);本插件 → DS 拉取回调上报的保存字节。DS 是按反代转发的浏览器可见基址(publicBaseUrl,否则请求 Origin)铸造这些保存 URL 的——反代部署下会绕公网回环(DNS + TLS + 终结器 + 网关)。插件把铸造前缀改写到 documentServerUrl(如 http://onlyoffice-documentserver,docker 网络容器名直连)——路径后缀与查询串原样保留,实现容器间直连。

安全性已实测验证(DS 9.4):/cache/files 的鉴权是 nginx secure_linkmd5(expires + 请求路径 + 服务端secret),其中"请求路径"是剥掉子路径前缀后的路径,主机名与前缀都不在签名材料内,因此改写主机并剥前缀后仍返回 200。URL 不在公网基址之下时不改写、按原样请求并记一条 warn。

同源反代

本插件是纯反代架构:始终把 DS 的整套浏览器界面通过 dsh web 端口反代到 /sidebar/onlyoffice/ds。config 路由直接下发挂载点的 api.js 地址,编辑器其后加载的一切——iframe、sdkjs/web-apps 静态资源、字体、协同 socket、导出与下载——都经 dsh web 回流。浏览器完全不需要访问到 DS 的服务地址:把 documentServerUrl 指向 DS 容器入口,DS 可以留在内网。顺带消除混合内容坑:https 的 GUI 可透明反代只支持 http 的 DS。

与 DS(9.x,即本插件目标同代)的协作机制:

  • api.js 从自身脚本地址推导 DS 基址,编辑器从 iframe 地址推导 socket 路径——两者都天然落在挂载点之内。
  • 代理按官方「virtual path」形态发送 X-Forwarded-Host: <浏览器主机>/sidebar/onlyoffice/ds,DS 自行铸造的 URL(版本化脚本缓存 302、保存/下载链接)因此指回挂载点;绝对 Location 响应头还会被额外改写为挂载点相对形式。同样的转发头也随 WebSocket 升级腿转发——DS 在该连接上推导 /cache/files 文档数据基址。
  • 编辑器的 socket.io 路径为 <挂载点>/<版本>~<hash>/doc/<key>/c,其中 <key> 正是本插件的内容寻址文档 key。代理按打开的文档即时注册该精确 WebSocket 升级路由(DS status-4 关闭时注销、LRU 上限、DS 升级换版本前缀时自动重发现)。偶发漏注册则平滑降级:编辑器回落到 socket.io 轮询(普通 HTTP,走挂载点前缀路由),编辑不中断。
  • 两条代理腿(HTTP 与升级)都在与其他插件路由相同的浏览器信任围栏之内(同源编辑器放行;跨站页面拒绝)。

注意与边界:

  • 需要 9.x 一代的 DS(子路径 URL 铸造能力)。
  • 每次保存都会更换 key(内容寻址),升级路由随保存而更替——关闭即注销、有上限,实践中无感。
  • 若 dsh web 前面的网关会改写 Host(例如改成 127.0.0.1:3080),DS 响应体内铸造的 URL 会携带该浏览器不可达的主机:此时设置 publicBaseUrl 为浏览器可见源。
  • 安全取舍:DS 界面与 DSH 页面同源运行,DS 被攻破时可脚本化 dsh 源。围栏仍拦截跨站调用;请保持 DS 可信。
  • 静态资源走两跳(浏览器 → dsh web → DS);版本化不可变缓存可穿越该跳,但外层网关若要最佳性能,需把 /sidebar/onlyoffice/ds/** 纳入其缓存策略映射。

部署形态

典型部署:Document Server 容器与 dsh web 同一 docker 网络(容器名可解析,如 onlyoffice-documentserver——即 documentServerUrl),JWT 启用固定密钥,DS 容器加 ALLOW_PRIVATE_IP_ADDRESS=true。DS 完全不需要对外发布浏览器可达的端口。

排错:编辑器报 errorCode:-4「下载失败」

-4 是 DS 下载 document.url 失败。诊断入口是 DS 容器日志里 error downloadFile:url=...目标 URL:

  • URL 指向 DS 自己 → internalBaseUrl 配错成了 DS 地址。它必须是 DS 回连 dsh web 的地址(两者同网络时用容器名);改 profile 的 cordis.patch.yml 后 Cordis HMR 自动热生效,无需重启。
  • 404 → 路由不在(配错 host);403 → token 过期/签名不符(重启 dsh web 后重开文件);连接被拒/超时 → 网络不通(确认两个容器同网络,DS 需 ALLOW_PRIVATE_IP_ADDRESS=true)。
  • 快速自测(不开浏览器):带 Host: localhost 请求 config 路由拿到 document.url,再 docker exec onlyoffice-documentserver curl -v <该URL>,应当 200。

已知边界

  • 保存是整文件覆盖:与 agent 并发写同一文件时最后写入者胜。实时刷新收窄了该窗口——干净的编辑器约 1 秒内被推到最新版本——但脏编辑器在 AI 修改后再保存仍会覆盖之;保存完成时横幅随之清除(覆盖已解决分歧)。
  • 自保存抑制存在毫秒级竞态:恰落在插件保存与其 noteSelfSave stat 之间的外部写入可能被误吸收(漏推一次刷新事件;下一次变更即恢复)。实践中可忽略。
  • onRequestRefreshFile 事件需要 ONLYOFFICE Docs ≥ 8.3;refreshFile 本身已在 Document Server 9.4 社区版实测可用。
  • DS 需为 9.x 一代(子路径 URL 铸造);WebSocket 腿依赖按文档即时注册路由,漏注册时静默降级为 socket.io 轮询(老版本 DS 或路由冲突场景)。
  • key→文件 注册表持久化在 <DSH_HOME>/plugins/dsh-sidebar-onlyoffice/registry.json(最新 128 条),dsh web 重启后 DS 重投的保存回调仍能落盘。watch 流会自动重连(EventSource retry: 3000);跨重启保持打开的 viewer 在下次交互时重取配置。

安全

服务端半只会提供与写入会话工作目录之内的文件,浏览器侧路由位于 dsh web 信任围栏之内,DS 的每次下载都凭 config 请求时签发的短时效 HMAC 令牌。Document Server 由用户自行部署与配置——请将其部署在可信网络。完整立场与威胁模型见 SECURITY.md

开发

pnpm install
pnpm run typecheck   # 类型门禁
pnpm test            # vitest(JWT/key/令牌、路由围栏与回调落盘、保存 URL 内网改写、同源 ds 反代、settings 接线、viewer 描述符、inotify 监听枢纽 + SSE 路由)
pnpm run build       # lib/index.js(Node 半)+ lib/client.js(ModuleLoader 包裹的浏览器半)

仓库结构:

src/
├── index.ts        # 宿主入口:webServer 四路由(含 dsProxy 接线)
├── dsProxy.ts      # 同源 DS 反代:挂载、转发头、即时 WS 路由
├── config.ts       # schemastery schema、基址推导/探测
├── settings.ts     # 设置区接线(图形化配置卡片的宿主半)
├── onlyoffice.ts   # 编辑器配置、JWT 载荷、文件令牌、保存 URL 改写
├── jwt.ts          # 极简 HS256 签名/校验(零依赖)
├── registry.ts     # key→文件 映射、原子写回、持久化
├── watch.ts        # inotify 监听枢纽(防抖、签名过滤、自保存吸收)
├── trust-fence.ts  # 浏览器请求信任校验(host/origin)
├── paths.ts        # 绝对路径 + 包含关系工具
├── wire.ts         # JSON body/错误工具
└── client/         # 浏览器半:viewer、编辑器挂载、i18n、设置卡片

许可

MIT