tfrere HF Staff commited on
Commit
82092f2
·
1 Parent(s): f797eff

fix: replace express-ws with native ws to fix WebSocket binary handling

Browse files

express-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 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 expressWebsockets from "express-ws";
 
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 { app } = expressWebsockets(express());
 
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
- app.ws("/collab/:doc", (ws, req) => {
149
- const token = extractToken(req.headers.cookie);
150
- hocuspocus.handleConnection(ws, req, { token });
 
 
 
 
 
 
 
 
151
  });
152
 
153
  // ---------- AI Chat ----------
@@ -384,7 +394,7 @@ if (existsSync(staticDir)) {
384
 
385
  // ---------- Start ----------
386
 
387
- const server = app.listen(PORT, () => {
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
  });