chendefine/dsh-sidebar-onlyoffice ↗★ 0

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; the DS browser UI is always served same-origin through the built-in reverse proxy). 适合需要自托管ONLYOFFICE并在侧边栏直接编辑文档的用户。

Package
dsh-sidebar-onlyoffice
Compatibility
Unverified
Cordis peer range
>=4.0.1-rc.1 <5
Version
0.2.0
License
MIT
Last updated
Sep 2, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:chendefine/dsh-sidebar-onlyoffice

Configuration

All keys are optional; the profile's cordis.patch.yml layer carries them:

- id: dsh-sidebar-onlyoffice
  config:
    jwtSecret: ""          # empty = unsigned (only for JWT_ENABLED=false servers)
    # documentServerUrl: http://onlyoffice-documentserver  # the DS entry dsh web reaches directly (its own nginx)
    # internalBaseUrl: http://172.31.255.4:3080    # how the DS reaches dsh web; empty = auto-detect
    # publicBaseUrl: https://work.example.com      # browser-visible origin when a gateway rewrites Host; empty = request Origin
    # defaultMode: edit                             # edit | view
    # username: User                                 # editor display username (top-right corner)
    # fileLimitMb: 100
    # tokenTtlSec: 600
FieldDefaultDescription
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''The one Document Server address: the entry dsh web itself reaches directly — the DS container's own nginx on the docker network (http://onlyoffice-documentserver) or a published host port. Doubles as the reverse-proxy upstream and the base saved-document bytes are fetched through.
internalBaseUrl(auto-detect)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.
publicBaseUrl(request Origin)Browser-visible origin (scheme://host[:port]), used when minting X-Forwarded-Host/Proto and matching DS-reported save URLs. Set it when a fronting gateway rewrites the Host header away from the browser-visible authority; empty = derive from each request's Origin.
defaultModeeditDefault mode for newly opened files (edit/view).
usernameUserEditor display username (editorConfig.user.name, the co-author name in the editor's top-right corner); empty reverts to User. Reopen files after a change.
fileLimitMb100Max file size (MB) for serving and saving back.
tokenTtlSec600Signed URL token lifetime in seconds.

Legacy pre-0.2.0 keys (proxy, proxyUpstream, internalDocumentServerUrl, proxyPublicBase, documentServerPort) are mapped once at startup — proxyUpstream/internalDocumentServerUrl onto documentServerUrl, proxyPublicBase onto publicBaseUrl — with a warning; move to the new names.

Graphical configuration (settings card)

When the composition mounts the DSH settings service (the standard dsh web bundle does), the plugin registers a settings section under the dsh-sidebar-onlyoffice namespace and the web client's Settings → Plugins tab renders a configuration card for it — all eight fields, zh/en copy, staged edits with a save/discard footer. A committed change is written to the deployment's user settings (/settings.yaml), layered over the composition entry, and applies without a restart: both the route closures AND the reverse proxy read the live section (jwt/mode/username/limit/token/URLs take effect on the next editor-config fetch; a changed documentServerUrl swaps the proxy upstream at once — the discovered versioned prefix resets and re-probes, and an address that first resolves through the card claims the mount immediately). A deployment may therefore boot with an empty composition entry and be configured entirely through the card.

Precedence per field: card/user layer → the composition entry (cordis.patch.yml config: block) → schema defaults. Removing a card edit (the "Reset to default" control) reverts to the composition layer; hand-editing settings.yaml works too (the section re-resolves live).

Save-URL rewrite (in-network fetch)

Two directions are configured independently: DS → this server via internalBaseUrl (document download + callbacks), and this server → DS for the saved bytes its callbacks report. The DS mints those save URLs against the browser-visible base the proxy forwards (publicBaseUrl, else the request Origin) — behind a reverse proxy they would hairpin out through the public entry (DNS + TLS + terminator + gateway). The plugin rewrites the minted prefix onto documentServerUrl (e.g. http://onlyoffice-documentserver, the docker-network container name) — 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 public base is fetched as-is with a warn log.

Same-origin proxy

The plugin is proxy-only: it always reverse-proxies the Document Server's whole browser UI through the dsh web port at /sidebar/onlyoffice/ds. The config route hands out that mount's api.js URL, and everything the editor then loads — the iframe, sdkjs/web-apps statics, fonts, the co-authoring socket, exports and downloads — flows back through dsh web. The browser never needs to reach the DS at all; point documentServerUrl at the DS container entry and the DS can stay on a private network. This also removes the mixed-content pitfall: an https GUI proxies an http-only DS transparently.

How it cooperates with the DS (9.x; the same generation the plugin targets):

  • api.js derives the DS base from its own script URL, and the editor derives the socket path from the iframe URL — both stay under the mount automatically.
  • The proxy sends X-Forwarded-Host: /sidebar/onlyoffice/ds (the official ONLYOFFICE "virtual path" shape), so the URLs the DS itself mints (the versioned script-caching 302s, save/download links) point back into the mount; absolute Location headers are additionally rewritten to mount-relative form. The same headers ride the WebSocket upgrade leg, where the DS derives the /cache/files document-data base from the connection.
  • The editor's socket.io path is /~/doc//c, where `` is the plugin's own content-addressed document key. The proxy registers that exact WebSocket upgrade route just-in-time per opened document (disposed on the DS's status-4 close, LRU-capped, re-discovered when a DS upgrade moves the versioned prefix). A missed registration degrades softly: the editor falls back to socket.io polling over plain HTTP — editing keeps working.
  • Both proxy legs sit behind the same browser-trust fence as every other plugin route (same-origin editor passes; cross-site pages are refused).

Notes and limits:

  • Requires a DS of the 9.x generation (sub-path URL minting).
  • Every document save changes the key (content-addressed), so upgrade routes churn per save — disposed on close, capped, invisible in practice.
  • If a gateway in front of dsh web rewrites Host (e.g. to 127.0.0.1:3080), URLs minted inside DS responses would carry that unreachable host: set publicBaseUrl to the browser-visible origin.
  • Security trade-off: the DS UI runs same-origin with the DSH page, so a compromised DS could script the dsh origin. The fence still stops cross-site callers; keep the DS trusted.
  • Static assets hop twice (browser → dsh web → DS); the versioned immutable caching survives the hop, but an outer gateway may need its cache map extended for /sidebar/onlyoffice/ds/** for best performance.

Deployment shape

The canonical deployment: the Document Server container and dsh web share a docker network (DS container name resolvable, e.g. onlyoffice-documentserver — that is documentServerUrl), JWT enabled with a fixed secret, and ALLOW_PRIVATE_IP_ADDRESS=true on the DS container. The DS needs no published browser-facing port at all.

Graphical configuration (settings card)

When the composition mounts the DSH settings service (the standard dsh web bundle does), the plugin registers a settings section under the dsh-sidebar-onlyoffice namespace and the web client's Settings → Plugins tab renders a configuration card for it — all eight fields, zh/en copy, staged edits with a save/discard footer. A committed change is written to the deployment's user settings (/settings.yaml), layered over the composition entry, and applies without a restart: both the route closures AND the reverse proxy read the live section (jwt/mode/username/limit/token/URLs take effect on the next editor-config fetch; a changed documentServerUrl swaps the proxy upstream at once — the discovered versioned prefix resets and re-probes, and an address that first resolves through the card claims the mount immediately). A deployment may therefore boot with an empty composition entry and be configured entirely through the card.

Precedence per field: card/user layer → the composition entry (cordis.patch.yml config: block) → schema defaults. Removing a card edit (the "Reset to default" control) reverts to the composition layer; hand-editing settings.yaml works too (the section re-resolves live).