dsh-sidebar-onlyoffice
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).
安装
dsh plugin --profile web add github:chendefine/dsh-sidebar-onlyoffice
需要可复现安装时,可在仓库后追加 #commit 固定提交。
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).
该插件未提供要点说明,请参考仓库 README。
- 安装并启动 DeepSeek Harness:
npx @deepseek-ai/dsh web - 在终端执行上面的安装命令(CLI 会解析插件并核验来源)
- 用 dsh plugins list 确认已安装,必要时重启 Harness 生效
插件以当前 dsh 进程的权限运行,安装时可能执行代码。请先通读仓库源码与许可证,确认无破坏性命令与越权访问;本站只做索引,不对第三方插件安全性作担保。
| 代码仓库 | github.com/chendefine/dsh-sidebar-onlyoffice |
| 许可证 | MIT |
| 主要语言 | main |
| 下载量 | 1 |
| GitHub 星标 | 0 |
| 最近推送 | 2026-08-27 |
| 收录日期 | 2026-09-19 |
| 分类 | 界面增强 |
事实信息来自公开插件目录快照(2026-10-01),介绍文案由本站再加工。
以下为插件仓库 README 全文(原始内容,由公开目录抓取整理)。
# dsh-sidebar-onlyoffice
[中文](./README.zh-CN.md) · [npm](https://www.npmjs.com/package/dsh-sidebar-onlyoffice) · [GitHub](https://github.com/chendefine/dsh-sidebar-onlyoffice)
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) web plugin that opens and edits `.docx` / `.xlsx` / `.pptx` in the [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) file sidebar through a self-hosted [ONLYOFFICE Document Server](https://github.com/ONLYOFFICE/Docker-DocumentServer) — with JWT-signed configs, atomic save-back to disk, and **live refresh when the AI edits an open file**.
    
## Features
- **Real office editing** — the full ONLYOFFICE editor (words / sheets / slides) embedded in the better-sidebar viewer, not a static preview. Saves write back to the file on disk.
- **Signed and fenced by default** — editor configs are HS256-JWT-signed when a secret is configured; every file download is gated by a short-lived HMAC token; browser-facing routes sit behind the dsh web trust fence; files outside the session working directory are refused.
- **Live refresh on AI edits** — when the agent modifies a file that is open in the editor, the viewer swaps to the new version within ~a second via `refreshFile`, with no page reload and no api.js reload. A dirty editor is never auto-refreshed (a banner offers a manual reload instead).
- **Atomic saves, self-save suppression** — Document Server callbacks download the saved bytes and replace the file through temp-file + rename, serialized per document key; the plugin's own writes are absorbed by the watch hub so saving never echoes a refresh back.
- **In-network save fetch** — optional `internalDocumentServerUrl` rewrites Document Server save URLs onto a docker-network base (container-to-container) instead of hairpinning through the public reverse proxy.
- **Coexists with the office preview plugin** — viewer ids `onlyoffice:docx|xlsx|pptx` (priority 10) never clash with `@huanlin/dsh-plugin-better-sidebar-plugin-office`'s `docx/xlsx/pptx` (priority 0); either side can be disabled per-viewer in the side card settings.
## How it works
| Half | Location | Responsibility |
| --- | --- | --- |
| Host (server) | `src/` | Registers four routes on the dsh web webServer implementing the "document storage service" role from the official ONLYOFFICE integration docs. |
| Browser (client) | `src/client/` | Registers the three better-sidebar file viewers; loads the Document Server api.js on demand, mounts `DocsAPI.DocEditor`, subscribes to the SSE watch stream. |
```
better-sidebar viewer (onlyoffice:docx|xlsx|pptx)
├─ GET /sidebar/onlyoffice/config (browser, trust-fenced)
│ → editor config + JWT + api.js URL + HMAC file token
├─ GET /sidebar/onlyoffice/file (Document Server; HMAC token = auth)
├─ POST /sidebar/onlyoffice/callback (Document Server; status 2/6 → atomic save-back)
└─ GET /sidebar/onlyoffice/watch (browser SSE; inotify-fed disk changes)
→ change → re-fetch config → docEditor.refreshFile(config) [no reload]
```
- **Node half** — the config route builds the editor config for an absolute path inside the session cwd (documentType mapping, content-addressed `key = sha256(host+path+size+mtime)`), signs the whole config as an HS256 JWT, and registers the key→file mapping. The file route serves the raw bytes to the Document Server. The callback route (JWT-verified) downloads saved bytes on status 2/6 and **atomically replaces the file**, forgets the key on status 4, and always acknowledges with `{"error":0}`. The watch route streams disk changes as SSE, fed by an inotify watch hub (`fs.watch` on the file **and** its directory, debounced, signature-filtered through the same content-addressed key).
- **Browser half** — loads the Document Server api.js once per URL, mounts the editor, calls `destroyEditor()` on teardown; failures show an error panel plus a download fallback link (reusing better-sidebar's `/sidebar/file` route). While mounted it subscribes to the watch stream and reacts to outside (AI) edits by re-fetching the config and calling `docEditor.refreshFile(config)`.
### Live refresh on AI edits
When an agent modifies a file that is open in the ONLYOFFICE editor, the viewer finds out within ~a second: inotify → SSE `change` event → config re-fetch (whose content-addressed key changed with the file) → `refreshFile`. The editor iframe itself is reused — no page reload, no api.js reload, and the DS opens the new version as a fresh session (old key closed with status 4). Guard rails:
- A **dirty editor is never auto-refreshed** — `refreshFile` unconditionally drops unsaved edits (no `isDocumentModified` guard on the integrator path, verified in the DS source). Instead a banner appears ("file changed on disk — reloading discards unsaved edits") with a manual **Reload** button; clicking it is the user's confirmation.
- The DS's own `onRequestRefreshFile` (fired on reconnect / same-key saves, only while NOT modified) rides the same refresh path.
- The plugin's own callback saves are suppressed server-side (`noteSelfSave` records the just-written signature), so a user save doesn't bounce a refresh back and reset the cursor.
- File deletion pushes a `removed` notice; re-creation pushes a `change`.
## Requirements
- A DSH **web** profile (`dsh web`), Node.js ≥ 20, with the [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) plugin installed.
- A self-hosted ONLYOFFICE Document Server (verified against 9.4 community edition) that can reach the dsh web server over the network, and that the browser can reach directly.
- With JWT enabled (the DS default), the plugin's `jwtSecret` must equal the DS `JWT_SECRET`.
- The DS needs `ALLOW_PRIVATE_IP_ADDRESS=true` when `document.url` / `callbackUrl` are private addresses (the DS refuses them by default).
## Installation
From the npm registry (prebuilt — no build permission needed):
```sh
dsh plugin --profile web add dsh-sidebar-onlyoffice
```
From a GitHub repository (source — pnpm runs the `prepare` build; allowlist the package in `profiles/web/pnpm-workspace.yaml` if pnpm blocks the build script):
```sh
dsh plugin --profile web add github:chendefine/dsh-sidebar-onlyoffice
```
Or through the DSH plugin marketplace (设置 → DSH插件市场) — the repo carries the `dsh-plugin` topic and is indexed automatically.
After a bundle plugin is added to the profile layer stack, write your config into the profile's `cordis.patch.yml` layer (see below), **restart `dsh web`**, and hard-refresh the browser (Ctrl+Shift+R). Uninstall with `dsh plugin --profile web remove dsh-sidebar-onlyoffice` and restart again.
## Configuration
All keys are optional; the profile's `cordis.patch.yml` layer carries them:
```yaml
- id: dsh-sidebar-onlyoffice
config:
jwtSecret: "" # empty = unsigned (only for JWT_ENABLED=false servers)
# documentServerUrl: http://192.168.1.10:3082 # browser-side api.js base; empty = derive from the page Origin
# internalDocumentServerUrl: http://onlyoffice-documentserver # server-side fetch base for DS save URLs (see below)
# internalBaseUrl: http://172.31.255.4:3080 # how the DS reaches dsh web; empty = auto-detect
# documentServerPort: 3082
# defaultMode: edit # edit | view
# fileLimitMb: 100
# tokenTtlSec: 600
```
| Field | Default | Description |
| --- | --- | --- |
| `jwtSecret` | `''` | Shared secret with the Document Server (its `JWT_SECRET`). Editor configs and callbacks are JWT-signed/verified when set; empty only fits a DS running `JWT_ENABLED=false`. |
| `documentServerUrl` | *(derived)* | Browser-side DS base URL (e.g. `http://192.168.1.10:3082`). Empty = derive from each request's Origin host + `documentServerPort`. |
| `documentServerPort` | `3082` | Port appended when deriving the DS URL from the page Origin. |
| `internalDocumentServerUrl` | `''` | Base URL this server fetches DS-reported save URLs through (docker-network direct, e.g. `http://onlyoffice-documentserver`). Empty = fetch exactly as reported (through the public entry). |
| `internalBaseUrl` | `''` | Base URL the DS uses to reach this dsh web server (document download + callbacks). Empty = auto-detect the first non-loopback IPv4 + the webserver port. |
| `defaultMode` | `edit` | Default open mode; the viewer's gear setting overrides it per user. |
| `fileLimitMb` | `100` | Max file size (MB) for serving and saving back. |
| `tokenTtlSec` | `600` | Signed URL token lifetime in seconds. |
Each viewer's gear settings add two shared toggles: **open mode** (edit/view) and **document server URL** (browser-side override; a value set on any card applies to all three viewers).
### In-network save fetch (`internalDocumentServerUrl`)
The three directions are configured independently: browser → DS via `documentServerUrl`, DS → this server via `internalBaseUrl`, and this server → DS (downloading saved bytes) defaults to the URL the DS **reports** in its callback — behind a reverse proxy that points back through the public entry, so save traffic hairpins out (DNS + TLS + terminator + gateway). Setting `internalDocumentServerUrl` (e.g. `http://onlyoffice-documentserver`, the docker-network container name) rewrites the browser-side prefix (scheme/host/sub-path) of reported save URLs onto that base — path suffix and query stay intact — for a direct container-to-container fetch.
Safety verified against Document Server 9.4: `/cache/files` auth is an nginx `secure_link` md5 over `expires + request-path + server secret`, where the request path is the one AFTER the proxy strips the sub-path prefix — host and stripped prefix are not signed material, so a rewritten URL still returns 200. A reported URL outside the browser base is fetched as-is with a warn log.
### Deployment shape
The canonical deployment: the Document Server container and dsh web share a docker network (DS container name resolvable, e.g. `onlyoffice-documentserver`), DS published on host port 3082, JWT enabled with a fixed secret, and `ALLOW_PRIVATE_IP_ADDRESS=true` on the DS container.
## Troubleshooting: editor reports errorCode:-4 "download failed"
`-4` means the DS could not download `document.url`. Start from the target URL in the DS container log (`error downloadFile:url=...`):
- URL points at the DS itself → `internalBaseUrl` was misconfigured to the DS address. It must be the address **the DS uses to call back into dsh web** (use the container name when both share a docker network); editing the profile's `cordis.patch.yml` hot-applies via Cordis HMR, no restart needed.
- 404 → wrong host in the URL; 403 → token expired/bad signature (reopen the file after a dsh web restart); connection refused/timeout → network unreachable (both containers must share a network; the DS needs `ALLOW_PRIVATE_IP_ADDRESS=true`).
- Quick check without a browser: fetch the config route with `Host: localhost` to get `document.url`, then `docker exec onlyoffice-documentserver curl -v ` — expect 200.
## Known limits
- Saves are whole-file overwrites: concurrent writes to the same file by the agent race (last writer wins). Live refresh narrows this window — a clean editor is pushed to the newest version within ~a second — but a dirty editor that saves after an AI edit still overwrites it; the stale banner is cleared on save (the overwrite resolved the divergence).
- Self-save suppression has a millisecond race: an external write landing between the plugin's save and its `noteSelfSave` stat can be wrongly absorbed (one missed refresh event; the next change recovers). Negligible in practice.
- `onRequestRefreshFile` requires ONLYOFFICE Docs ≥ 8.3; `refreshFile` itself was verified against Document Server 9.4 community edition.
- The browser must reach the Document Server directly (mixed-content rule: an https GUI cannot load http api.js — put a proxy in front or point `documentServerUrl` at an https entry).
- A dsh web restart loses the in-memory key→file mapping; editors already open in the DS will see error 1 on their next save callback (the DS retries, then gives up) — reopening the file is the fix. The watch stream reconnects on its own (EventSource `retry: 3000`).
## Security
The host half only ever serves and writes files **inside a session working directory**, browser-facing routes sit behind the dsh web trust fence, and every Document-Server download is gated by a short-lived HMAC token minted per config request. The Document Server itself is user-deployed and user-configured — deploy it on a trusted network. See [SECURITY.md](./SECURITY.md) for the full stance and threat model.
## Development
```sh
pnpm install
pnpm run typecheck # type gate
pnpm test # vitest (69 cases: JWT/key/tokens, route fences and callback saves, in-network save-URL rewrite, viewer descriptors, inotify watch hub + SSE route)
pnpm run build # lib/index.js (node half) + lib/client.js (ModuleLoader-wrapped browser half)
```
Repository layout:
```
src/
├── index.ts # host entry: the four webServer routes
├── config.ts # schemastery schema, base-URL derivation/detection
├── onlyoffice.ts # editor config, JWT payload, file tokens, save-URL rewrite
├── jwt.ts # minimal HS256 sign/verify (no dependencies)
├── registry.ts # document key → file mapping, atomic save-back
├── watch.ts # inotify watch hub (debounce, signature filter, self-save absorption)
├── trust-fence.ts # browser-request trust check (host/origin)
├── paths.ts # absolute-path + containment helpers
├── wire.ts # JSON body/error helpers
└── client/ # browser half: viewers, editor mount, i18n, settings
```
## License
[MIT](./LICENSE)
数据来源:公开的 DeepSeek Harness 插件目录与各插件 GitHub 仓库。本站为独立第三方目录,与 DeepSeek、幻方(High-Flyer)及插件作者均无隶属或背书关系。