fix: replace express-ws with native ws to fix WebSocket binary handling
Browse filesexpress-ws bundles ws@7.5.10 while @hocuspocus /server requires ws@8.x.
This version mismatch causes ERR_ENCODING_INVALID_ENCODED_DATA errors
as the binary Yjs protocol messages get corrupted passing through
express-ws's incompatible ws instance.
Replace express-ws with native http.createServer + ws.WebSocketServer
handling the upgrade event directly. This ensures a single ws@8.20.0
is used throughout, matching what Hocuspocus expects.
Made-with: Cursor
- backend/package-lock.json +2 -51
- backend/package.json +2 -2
- backend/src/server.ts +17 -7
backend/package-lock.json
CHANGED
|
@@ -34,17 +34,17 @@
|
|
| 34 |
"ai": "^6.0.158",
|
| 35 |
"dotenv": "^17.4.1",
|
| 36 |
"express": "^4.21.0",
|
| 37 |
-
"express-ws": "^5.0.2",
|
| 38 |
"lowlight": "^3.3.0",
|
| 39 |
"multer": "^2.1.1",
|
|
|
|
| 40 |
"yjs": "^13.6.0",
|
| 41 |
"zod": "^4.3.6"
|
| 42 |
},
|
| 43 |
"devDependencies": {
|
| 44 |
"@types/express": "^5.0.0",
|
| 45 |
-
"@types/express-ws": "^3.0.5",
|
| 46 |
"@types/multer": "^2.1.0",
|
| 47 |
"@types/node": "^22.0.0",
|
|
|
|
| 48 |
"tsx": "^4.19.0",
|
| 49 |
"typescript": "^5.6.0"
|
| 50 |
}
|
|
@@ -1301,18 +1301,6 @@
|
|
| 1301 |
"@types/send": "*"
|
| 1302 |
}
|
| 1303 |
},
|
| 1304 |
-
"node_modules/@types/express-ws": {
|
| 1305 |
-
"version": "3.0.6",
|
| 1306 |
-
"resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.6.tgz",
|
| 1307 |
-
"integrity": "sha512-6ZDt+tMEQgM4RC1sMX1fIO7kHQkfUDlWfxoPddXUeeDjmc+Yt/fCzqXfp8rFahNr5eIxdomrWphLEWDkB2q3UQ==",
|
| 1308 |
-
"dev": true,
|
| 1309 |
-
"license": "MIT",
|
| 1310 |
-
"dependencies": {
|
| 1311 |
-
"@types/express": "*",
|
| 1312 |
-
"@types/express-serve-static-core": "*",
|
| 1313 |
-
"@types/ws": "*"
|
| 1314 |
-
}
|
| 1315 |
-
},
|
| 1316 |
"node_modules/@types/hast": {
|
| 1317 |
"version": "3.0.4",
|
| 1318 |
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
|
@@ -1941,7 +1929,6 @@
|
|
| 1941 |
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
| 1942 |
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
| 1943 |
"license": "MIT",
|
| 1944 |
-
"peer": true,
|
| 1945 |
"dependencies": {
|
| 1946 |
"accepts": "~1.3.8",
|
| 1947 |
"array-flatten": "1.1.1",
|
|
@@ -1983,42 +1970,6 @@
|
|
| 1983 |
"url": "https://opencollective.com/express"
|
| 1984 |
}
|
| 1985 |
},
|
| 1986 |
-
"node_modules/express-ws": {
|
| 1987 |
-
"version": "5.0.2",
|
| 1988 |
-
"resolved": "https://registry.npmjs.org/express-ws/-/express-ws-5.0.2.tgz",
|
| 1989 |
-
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
|
| 1990 |
-
"license": "BSD-2-Clause",
|
| 1991 |
-
"dependencies": {
|
| 1992 |
-
"ws": "^7.4.6"
|
| 1993 |
-
},
|
| 1994 |
-
"engines": {
|
| 1995 |
-
"node": ">=4.5.0"
|
| 1996 |
-
},
|
| 1997 |
-
"peerDependencies": {
|
| 1998 |
-
"express": "^4.0.0 || ^5.0.0-alpha.1"
|
| 1999 |
-
}
|
| 2000 |
-
},
|
| 2001 |
-
"node_modules/express-ws/node_modules/ws": {
|
| 2002 |
-
"version": "7.5.10",
|
| 2003 |
-
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
| 2004 |
-
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
| 2005 |
-
"license": "MIT",
|
| 2006 |
-
"engines": {
|
| 2007 |
-
"node": ">=8.3.0"
|
| 2008 |
-
},
|
| 2009 |
-
"peerDependencies": {
|
| 2010 |
-
"bufferutil": "^4.0.1",
|
| 2011 |
-
"utf-8-validate": "^5.0.2"
|
| 2012 |
-
},
|
| 2013 |
-
"peerDependenciesMeta": {
|
| 2014 |
-
"bufferutil": {
|
| 2015 |
-
"optional": true
|
| 2016 |
-
},
|
| 2017 |
-
"utf-8-validate": {
|
| 2018 |
-
"optional": true
|
| 2019 |
-
}
|
| 2020 |
-
}
|
| 2021 |
-
},
|
| 2022 |
"node_modules/fetch-ponyfill": {
|
| 2023 |
"version": "7.1.0",
|
| 2024 |
"resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz",
|
|
|
|
| 34 |
"ai": "^6.0.158",
|
| 35 |
"dotenv": "^17.4.1",
|
| 36 |
"express": "^4.21.0",
|
|
|
|
| 37 |
"lowlight": "^3.3.0",
|
| 38 |
"multer": "^2.1.1",
|
| 39 |
+
"ws": "^8.20.0",
|
| 40 |
"yjs": "^13.6.0",
|
| 41 |
"zod": "^4.3.6"
|
| 42 |
},
|
| 43 |
"devDependencies": {
|
| 44 |
"@types/express": "^5.0.0",
|
|
|
|
| 45 |
"@types/multer": "^2.1.0",
|
| 46 |
"@types/node": "^22.0.0",
|
| 47 |
+
"@types/ws": "^8.18.1",
|
| 48 |
"tsx": "^4.19.0",
|
| 49 |
"typescript": "^5.6.0"
|
| 50 |
}
|
|
|
|
| 1301 |
"@types/send": "*"
|
| 1302 |
}
|
| 1303 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1304 |
"node_modules/@types/hast": {
|
| 1305 |
"version": "3.0.4",
|
| 1306 |
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
|
|
|
| 1929 |
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
| 1930 |
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
| 1931 |
"license": "MIT",
|
|
|
|
| 1932 |
"dependencies": {
|
| 1933 |
"accepts": "~1.3.8",
|
| 1934 |
"array-flatten": "1.1.1",
|
|
|
|
| 1970 |
"url": "https://opencollective.com/express"
|
| 1971 |
}
|
| 1972 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1973 |
"node_modules/fetch-ponyfill": {
|
| 1974 |
"version": "7.1.0",
|
| 1975 |
"resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz",
|
backend/package.json
CHANGED
|
@@ -35,17 +35,17 @@
|
|
| 35 |
"ai": "^6.0.158",
|
| 36 |
"dotenv": "^17.4.1",
|
| 37 |
"express": "^4.21.0",
|
| 38 |
-
"express-ws": "^5.0.2",
|
| 39 |
"lowlight": "^3.3.0",
|
| 40 |
"multer": "^2.1.1",
|
|
|
|
| 41 |
"yjs": "^13.6.0",
|
| 42 |
"zod": "^4.3.6"
|
| 43 |
},
|
| 44 |
"devDependencies": {
|
| 45 |
"@types/express": "^5.0.0",
|
| 46 |
-
"@types/express-ws": "^3.0.5",
|
| 47 |
"@types/multer": "^2.1.0",
|
| 48 |
"@types/node": "^22.0.0",
|
|
|
|
| 49 |
"tsx": "^4.19.0",
|
| 50 |
"typescript": "^5.6.0"
|
| 51 |
}
|
|
|
|
| 35 |
"ai": "^6.0.158",
|
| 36 |
"dotenv": "^17.4.1",
|
| 37 |
"express": "^4.21.0",
|
|
|
|
| 38 |
"lowlight": "^3.3.0",
|
| 39 |
"multer": "^2.1.1",
|
| 40 |
+
"ws": "^8.20.0",
|
| 41 |
"yjs": "^13.6.0",
|
| 42 |
"zod": "^4.3.6"
|
| 43 |
},
|
| 44 |
"devDependencies": {
|
| 45 |
"@types/express": "^5.0.0",
|
|
|
|
| 46 |
"@types/multer": "^2.1.0",
|
| 47 |
"@types/node": "^22.0.0",
|
| 48 |
+
"@types/ws": "^8.18.1",
|
| 49 |
"tsx": "^4.19.0",
|
| 50 |
"typescript": "^5.6.0"
|
| 51 |
}
|
backend/src/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import "dotenv/config";
|
| 2 |
import express from "express";
|
| 3 |
-
import
|
|
|
|
| 4 |
import { Hocuspocus } from "@hocuspocus/server";
|
| 5 |
import { Database } from "@hocuspocus/extension-database";
|
| 6 |
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
@@ -100,7 +101,8 @@ const hocuspocus = new Hocuspocus({
|
|
| 100 |
],
|
| 101 |
});
|
| 102 |
|
| 103 |
-
const
|
|
|
|
| 104 |
|
| 105 |
app.use(express.json({ limit: "1mb" }));
|
| 106 |
|
|
@@ -143,11 +145,19 @@ app.get("/api/auth/status", async (req, res) => {
|
|
| 143 |
});
|
| 144 |
});
|
| 145 |
|
| 146 |
-
// ---------- Collab WebSocket ----------
|
| 147 |
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
});
|
| 152 |
|
| 153 |
// ---------- AI Chat ----------
|
|
@@ -384,7 +394,7 @@ if (existsSync(staticDir)) {
|
|
| 384 |
|
| 385 |
// ---------- Start ----------
|
| 386 |
|
| 387 |
-
const server =
|
| 388 |
console.log(`[server] running on http://localhost:${PORT}`);
|
| 389 |
console.log(`[server] collab websocket at ws://localhost:${PORT}/collab/:doc`);
|
| 390 |
});
|
|
|
|
| 1 |
import "dotenv/config";
|
| 2 |
import express from "express";
|
| 3 |
+
import { createServer } from "http";
|
| 4 |
+
import { WebSocketServer } from "ws";
|
| 5 |
import { Hocuspocus } from "@hocuspocus/server";
|
| 6 |
import { Database } from "@hocuspocus/extension-database";
|
| 7 |
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
|
|
| 101 |
],
|
| 102 |
});
|
| 103 |
|
| 104 |
+
const app = express();
|
| 105 |
+
const httpServer = createServer(app);
|
| 106 |
|
| 107 |
app.use(express.json({ limit: "1mb" }));
|
| 108 |
|
|
|
|
| 145 |
});
|
| 146 |
});
|
| 147 |
|
| 148 |
+
// ---------- Collab WebSocket (native ws upgrade, no express-ws) ----------
|
| 149 |
|
| 150 |
+
const wss = new WebSocketServer({ noServer: true });
|
| 151 |
+
|
| 152 |
+
httpServer.on("upgrade", (req, socket, head) => {
|
| 153 |
+
if (req.url?.startsWith("/collab/")) {
|
| 154 |
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
| 155 |
+
const token = extractToken(req.headers.cookie);
|
| 156 |
+
hocuspocus.handleConnection(ws, req, { token });
|
| 157 |
+
});
|
| 158 |
+
} else {
|
| 159 |
+
socket.destroy();
|
| 160 |
+
}
|
| 161 |
});
|
| 162 |
|
| 163 |
// ---------- AI Chat ----------
|
|
|
|
| 394 |
|
| 395 |
// ---------- Start ----------
|
| 396 |
|
| 397 |
+
const server = httpServer.listen(PORT, () => {
|
| 398 |
console.log(`[server] running on http://localhost:${PORT}`);
|
| 399 |
console.log(`[server] collab websocket at ws://localhost:${PORT}/collab/:doc`);
|
| 400 |
});
|