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

2.3 KiB

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)

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