Files
tfaour dadd9ec164
Build and push server image / build-and-push (push) Successful in 1m57s
Fix whiteboard render sidecar crash on startup
@excalidraw/utils is a browser bundle -- it references devicePixelRatio,
location, matchMedia etc. as bare globals the way inline <script> code
would, not as window.foo. Only copying window/document/navigator onto
Node's global left everything else undefined, so the very first bare
reference threw a ReferenceError as soon as the module loaded. Copy
jsdom's entire window onto global instead, enable pretendToBeVisual so
jsdom actually populates devicePixelRatio/requestAnimationFrame, and
stub matchMedia since jsdom doesn't implement it at all.
2026-07-23 17:50:27 -04:00

45 lines
2.3 KiB
Markdown

# whiteboard-render
Local sidecar for whiteboard frame mode -- see `server.js`'s own header
comment for the full "why Node, why not a headless browser" reasoning.
Not an independently deployed service: it runs as a second process
inside the main server's container (`../Dockerfile` installs Node,
`../start.sh` launches this in the background before `exec`-ing
uvicorn), reachable only at `127.0.0.1:3001` from the Python process in
that same container.
**Not runtime-tested against a real `npm install` during development**
-- this environment had no Node.js/npm available, only the npm registry
API (used to verify the dependency versions/licenses in `package.json`
actually exist and resolve). The code is written carefully against each
library's documented API (`@excalidraw/utils`'s `exportToSvg`,
`@resvg/resvg-js`'s `Resvg` class), but `docker compose build`+actually
running it is the first time this has executed end to end -- and the
first real run did in fact crash: `@excalidraw/utils`'s bundle touches
bare browser globals (`devicePixelRatio`, `location`, `matchMedia`, ...)
the same way inline `<script>` code in a real page would, not as
`window.foo` -- jsdom only puts those *on* `dom.window`, so only
copying `window`/`document`/`navigator` onto Node's `global` (the
original version of this file) left everything else undefined. Fixed
by copying jsdom's entire `window` onto `global` plus a `matchMedia`
stub (jsdom doesn't implement it at all). If another crash like this
shows up, it's almost certainly the same shape -- another bare global
the bundle expects that this file hasn't stubbed yet. One other gap
worth knowing about going in: jsdom's `<canvas>` has no real 2D
rendering context (no `node-canvas` installed), so if Excalidraw's text
measurement path depends on `canvas.getContext('2d').measureText(...)`
rather than pure SVG/font-metrics math, that could be a next thing to
watch for -- not something confirmed broken, just not yet exercised.
## Local development (if you have Node 20.19+/22.13+ installed)
```sh
cd render-service
npm install
npm start # listens on 127.0.0.1:3001
curl -X POST http://127.0.0.1:3001/render \
-H "Content-Type: application/json" \
-d '{"elements": [], "width": 800}' \
-o /tmp/test.png # an empty scene -- just checks the service comes up and returns a valid PNG
```