diff --git a/plugins/draw/README.md b/plugins/draw/README.md index 01f4ef6..31b45a7 100644 --- a/plugins/draw/README.md +++ b/plugins/draw/README.md @@ -12,11 +12,14 @@ Build the UI from its own lockfile: ```sh cd ui npm ci +npx tsc --noEmit npm run build cd .. npm run test:integration ``` -The integration tests connect the UI Socket.IO client to the Draw server over loopback WebSocket and polling transports. They verify room joins, scene updates, cursor updates, state requests, and disconnects. +The integration tests connect the UI Socket.IO client to the Draw server over loopback WebSocket and polling transports. They verify room joins, scene updates, cursor updates, state requests, and disconnects. They also check that the built UI serves its Excalidraw stylesheet. The unit tests exercise parser compatibility, rejection of malformed binary headers, Nano ID's six-character session suffixes and Draw storage in temporary directories. They do not open a browser or sharing tunnel. CI runs these checks on Node.js 20, 22, and 24 alongside the repository's Bats and Shellcheck jobs. + +The UI overrides vulnerable versions pinned by Excalidraw and its Mermaid converter: Nano ID, lodash-es, and Sass. The Sass update also removes the old Chokidar/Braces dependency chain. Keep these overrides until the upstream dependency ranges include patched releases, and preserve Node.js 20 compatibility when updating them. Browser checks should cover drawing and Mermaid-to-Excalidraw conversion as well as the build. diff --git a/plugins/draw/test/integration/ui.test.mjs b/plugins/draw/test/integration/ui.test.mjs new file mode 100644 index 0000000..1a47348 --- /dev/null +++ b/plugins/draw/test/integration/ui.test.mjs @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import { test } from 'node:test'; +import { createHttpServer } from '../../dist/server/http.js'; + +test('built UI serves the Excalidraw stylesheet', async (t) => { + const server = createHttpServer({ getElements: () => [], saveElements: () => {} }); + t.after(() => new Promise((resolve) => server.close(resolve))); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + const origin = `http://127.0.0.1:${server.address().port}`; + const response = await fetch(origin); + assert.equal(response.status, 200); + const html = await response.text(); + const stylesheets = [...html.matchAll(/]*rel="stylesheet"[^>]*>/g)] + .map(([tag]) => tag.match(/href="([^"]+)"/)?.[1]); + assert.ok(stylesheets.length > 0, 'the UI must load its canvas stylesheet'); + + let css = ''; + for (const href of stylesheets) { + assert.ok(href, 'stylesheet link must have an href'); + const stylesheet = await fetch(new URL(href, origin)); + assert.equal(stylesheet.status, 200); + assert.match(stylesheet.headers.get('content-type'), /^text\/css/); + css += await stylesheet.text(); + } + assert.match(css, /\.excalidraw\b/, 'the served CSS must include Excalidraw styles'); +}); diff --git a/plugins/draw/ui/package-lock.json b/plugins/draw/ui/package-lock.json index 2d0e3ae..3e836f8 100644 --- a/plugins/draw/ui/package-lock.json +++ b/plugins/draw/ui/package-lock.json @@ -392,77 +392,6 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, - "node_modules/@excalidraw/excalidraw/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/immutable": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz", - "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==", - "license": "MIT" - }, - "node_modules/@excalidraw/excalidraw/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@excalidraw/excalidraw/node_modules/sass": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.51.0.tgz", - "integrity": "sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==", - "license": "MIT", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@excalidraw/laser-pointer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@excalidraw/laser-pointer/-/laser-pointer-1.3.1.tgz", @@ -488,9 +417,9 @@ } }, "node_modules/@excalidraw/mermaid-to-excalidraw/node_modules/nanoid": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", - "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", "funding": [ { "type": "github", @@ -502,7 +431,7 @@ "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^14 || ^16 || >=18" + "node": "^18 || >=20" } }, "node_modules/@excalidraw/random-username": { @@ -592,11 +521,9 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", @@ -632,13 +559,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -654,13 +579,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -676,13 +599,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -698,13 +619,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -720,13 +639,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -742,13 +659,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -764,13 +679,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -786,13 +699,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -808,13 +719,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -830,13 +739,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -852,13 +759,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -874,13 +779,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -2254,31 +2157,6 @@ } } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -2291,30 +2169,6 @@ "node": ">=10" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browser-fs-access": { "version": "0.29.1", "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.29.1.tgz", @@ -2357,10 +2211,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "readdirp": "^5.0.0" }, @@ -2981,7 +2832,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3054,16 +2905,22 @@ "strictdom": "^1.0.1" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/fractional-indexing": { @@ -3079,6 +2936,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3106,18 +2964,6 @@ "node": ">=6" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/glur": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", @@ -3155,10 +3001,7 @@ "version": "5.1.9", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/import-meta-resolve": { "version": "4.2.0", @@ -3185,23 +3028,12 @@ "node": ">=12" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -3211,6 +3043,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", + "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3218,15 +3051,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3578,9 +3402,9 @@ } }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -3693,9 +3517,15 @@ } }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" @@ -3708,19 +3538,8 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "optional": true }, "node_modules/object-assign": { "version": "4.1.1", @@ -3794,7 +3613,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12" @@ -3879,25 +3698,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/pwacompat": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/pwacompat/-/pwacompat-2.0.17.tgz", @@ -3998,10 +3798,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">= 20.19.0" }, @@ -4084,10 +3881,7 @@ "version": "1.105.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.105.0.tgz", "integrity": "sha512-4EjSuRTyG8M4yRXXY1PT9OUV5zz23xBxJkbIAprohKHqf6HOU4MCpE3U3vAC+iO1gxobBjESNbcj8rYAQZWnyA==", - "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", @@ -4212,36 +4006,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/ts-dedent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", diff --git a/plugins/draw/ui/package.json b/plugins/draw/ui/package.json index 02c7a3c..03ea173 100644 --- a/plugins/draw/ui/package.json +++ b/plugins/draw/ui/package.json @@ -23,5 +23,15 @@ }, "engines": { "node": "^20.19.0 || >=22.12.0" + }, + "overrides": { + "@excalidraw/excalidraw": { + "nanoid": "3.3.19" + }, + "@excalidraw/mermaid-to-excalidraw": { + "nanoid": "5.1.16" + }, + "lodash-es": "4.18.1", + "sass": "1.105.0" } } diff --git a/plugins/draw/ui/src/App.tsx b/plugins/draw/ui/src/App.tsx index cf7491e..6649e42 100644 --- a/plugins/draw/ui/src/App.tsx +++ b/plugins/draw/ui/src/App.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Excalidraw } from '@excalidraw/excalidraw'; -import type { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types'; +import type { Collaborator, ExcalidrawImperativeAPI, SocketId } from '@excalidraw/excalidraw/types'; import { CollabClient } from './collab/client'; function getUrlParams(): { room: string; name: string } { @@ -96,9 +96,9 @@ export default function App() { const [username, setUsername] = useState(urlName || ''); const [initialData, setInitialData] = useState<{ elements: any[] } | null>(null); const [loading, setLoading] = useState(true); - const excalidrawApiRef = useRef(null); + const [excalidrawApi, setExcalidrawApi] = useState(null); const collabRef = useRef(null); - const [collaborators, setCollaborators] = useState>(new Map()); + const [collaborators, setCollaborators] = useState>(new Map()); const handleNameSubmit = useCallback((name: string) => { setUsername(name); @@ -121,17 +121,23 @@ export default function App() { // Initialize collab once we have a username and excalidraw API useEffect(() => { - if (!username || loading || !excalidrawApiRef.current) return; + if (!username || loading || !excalidrawApi) return; - const api = excalidrawApiRef.current; const collab = new CollabClient({ roomId: room, username, onRemoteSceneUpdate: (elements) => { - api.updateScene({ elements }); + excalidrawApi.updateScene({ elements }); }, onCollaboratorsChange: (collabs) => { - setCollaborators(new Map(collabs)); + setCollaborators(new Map(Array.from(collabs, ([id, collaborator]) => [ + id as SocketId, + { + username: collaborator.username, + color: { background: collaborator.color, stroke: collaborator.color }, + pointer: { ...collaborator.pointer, tool: 'pointer' }, + }, + ]))); }, }); @@ -140,8 +146,13 @@ export default function App() { return () => { collab.disconnect(); + collabRef.current = null; }; - }, [username, loading, room]); + }, [username, loading, room, excalidrawApi]); + + useEffect(() => { + excalidrawApi?.updateScene({ collaborators }); + }, [excalidrawApi, collaborators]); // Handle scene changes const handleChange = useCallback( @@ -181,9 +192,7 @@ export default function App() { return (
{ - excalidrawApiRef.current = api; - }} + excalidrawAPI={setExcalidrawApi} initialData={initialData || undefined} onChange={handleChange} onPointerUpdate={handlePointerUpdate} diff --git a/plugins/draw/ui/src/main.tsx b/plugins/draw/ui/src/main.tsx index c018515..5ec08fc 100644 --- a/plugins/draw/ui/src/main.tsx +++ b/plugins/draw/ui/src/main.tsx @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import '@excalidraw/excalidraw/index.css'; import App from './App'; ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/plugins/draw/ui/tsconfig.json b/plugins/draw/ui/tsconfig.json index 1ada14b..f559cdf 100644 --- a/plugins/draw/ui/tsconfig.json +++ b/plugins/draw/ui/tsconfig.json @@ -4,6 +4,7 @@ "module": "ESNext", "moduleResolution": "bundler", "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], "jsx": "react-jsx", "strict": true, "esModuleInterop": true,