- Startseite
- Plugins
- Tools & Funktionen
- dsh-better-sidebar-onlyoffice
dsh-better-sidebar-onlyoffice
chendefine/dsh-better-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).
Installation
dsh plugin --profile web add github:chendefine/dsh-better-sidebar-onlyofficeREADME
dsh-better-sidebar-onlyoffice
A DeepSeek Harness (DSH) web plugin that opens and edits .docx / .xlsx / .pptx in the dsh-better-sidebar file sidebar through a self-hosted ONLYOFFICE Document Server — 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
internalDocumentServerUrlrewrites 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'sdocx/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.watchon 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/fileroute). While mounted it subscribes to the watch stream and reacts to outside (AI) edits by re-fetching the config and callingdocEditor.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 —
refreshFileunconditionally drops unsaved edits (noisDocumentModifiedguard 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 (
noteSelfSaverecords the just-written signature), so a user save doesn't bounce a refresh back and reset the cursor. - File deletion pushes a
removednotice; re-creation pushes achange.
Requirements
- A DSH web profile (
dsh web), Node.js ≥ 20, with the 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
jwtSecretmust equal the DSJWT_SECRET. - The DS needs
ALLOW_PRIVATE_IP_ADDRESS=truewhendocument.url/callbackUrlare private addresses (the DS refuses them by default).
Installation
From the npm registry (prebuilt — no build permission needed):
dsh plugin --profile web add dsh-better-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):
dsh plugin --profile web add github:chendefine/dsh-better-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-better-sidebar-onlyoffice and restart again.
Configuration
All keys are optional; the profile's cordis.patch.yml layer carries them:
- id: dsh-better-sidebar-onlyoffice
config:
jwtSecret: "<the DS JWT_SECRET>" # 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 →
internalBaseUrlwas 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'scordis.patch.ymlhot-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: localhostto getdocument.url, thendocker exec onlyoffice-documentserver curl -v <that URL>— 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
noteSelfSavestat can be wrongly absorbed (one missed refresh event; the next change recovers). Negligible in practice. onRequestRefreshFilerequires ONLYOFFICE Docs ≥ 8.3;refreshFileitself 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
documentServerUrlat 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 for the full stance and threat model.
Development
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
Ähnliche Plugins
WeKnora (dsh-weknora)
tencent/weknora
weknora
tencent/weknora
archify (deepseek-harness)
tt-a1i/archify
BrowserSkill (dsh-plugin-browserskill)
tencent/browserskill