From fb7b1a7c5abd7a05926848583ff0709f8a87bc17 Mon Sep 17 00:00:00 2001 From: Francesco Virga Date: Mon, 26 Jan 2026 15:26:18 -0800 Subject: [PATCH 1/9] Check for hibernated websocket connections --- package-lock.json | 51 ++++------------ package.json | 1 + packages/partyserver/package.json | 2 +- packages/partyserver/src/connection.ts | 59 +++++++++++++++++-- packages/partyserver/src/index.ts | 18 +++++- packages/partyserver/src/tests/index.test.ts | 41 +++++++++++++ packages/partyserver/src/tests/worker.ts | 32 ++++++++++ packages/partyserver/src/tests/wrangler.jsonc | 6 +- 8 files changed, 163 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f1e4539..5a0b26bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@cloudflare/vite-plugin": "^1.19.0", "@cloudflare/vitest-pool-workers": "^0.10.4", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", + "@rolldown/binding-darwin-arm64": "^1.0.0-beta.55", "@types/node": "25.0.3", "@vitejs/plugin-react": "^5.1.2", "@vitest/runner": "3.2.4", @@ -352,7 +353,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1549,7 +1549,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1593,7 +1592,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2088,7 +2086,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -3097,7 +3094,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -4986,7 +4982,6 @@ ], "dev": true, "license": "MIT", - "optional": true, "os": [ "darwin" ], @@ -5842,7 +5837,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.14.0.tgz", "integrity": "sha512-nm0VWVA1Vq/jaKY3wyRXViL/kf78yMdH7qETpv4qZXDQLU+pdWV3IGoRTQTKESc7d8L1wL/2uCeByLNUJfrSIw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6094,7 +6088,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.14.0.tgz", "integrity": "sha512-rsjFH0Vd/4UbDsjwMLay7oz72VVu1r35t8ofAzy5587jn5JAjflaZs05XbRRMD2imUTK41dyajVSh8CqSnDEJw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6200,7 +6193,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.14.0.tgz", "integrity": "sha512-qQBVKqzU4ZVjRn8W0UbdfE4LaaIgcIWHOMrNnJ+PutrRzQ6ZzhmD/kRONvRWBfG9z3DU7pSKGwVYSR2hztsGuQ==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6215,7 +6207,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.14.0.tgz", "integrity": "sha512-xrZmqI5jl4yMeAsu8p8gVP9S3An5h2MBi8BQHNnZmpyzkUrlpd40vlT6u13SWIqVi5ZWhBZ6U3rL7mkVLZuRKg==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6358,7 +6349,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6372,7 +6362,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6521,7 +6510,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6653,7 +6643,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6663,7 +6652,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6793,7 +6781,6 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -6809,7 +6796,6 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -6906,6 +6892,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -6947,6 +6934,7 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -7078,7 +7066,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7458,6 +7445,7 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -7513,7 +7501,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dompurify": { "version": "3.2.7", @@ -9109,7 +9098,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -9597,7 +9585,6 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9614,6 +9601,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9744,7 +9732,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.2.tgz", "integrity": "sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww==", "license": "MIT", - "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -9774,7 +9761,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -9823,7 +9809,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.4.tgz", "integrity": "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -10029,7 +10014,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10039,7 +10023,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10064,7 +10047,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -10265,7 +10249,6 @@ "integrity": "sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "=0.103.0", "@rolldown/pluginutils": "1.0.0-beta.55" @@ -11003,7 +10986,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11072,7 +11054,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11113,7 +11094,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -11364,7 +11344,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -11405,7 +11384,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11481,7 +11459,6 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -11667,7 +11644,6 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11797,7 +11773,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11811,7 +11786,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -12018,7 +11992,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -12140,7 +12113,6 @@ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.85" }, @@ -12168,7 +12140,6 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.28.tgz", "integrity": "sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.99" }, diff --git a/package.json b/package.json index 71eb0215..0f55afdb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@cloudflare/vite-plugin": "^1.19.0", "@cloudflare/vitest-pool-workers": "^0.10.4", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", + "@rolldown/binding-darwin-arm64": "^1.0.0-beta.55", "@types/node": "25.0.3", "@vitejs/plugin-react": "^5.1.2", "@vitest/runner": "3.2.4", diff --git a/packages/partyserver/package.json b/packages/partyserver/package.json index ef1a71f4..e23adabd 100644 --- a/packages/partyserver/package.json +++ b/packages/partyserver/package.json @@ -1,6 +1,6 @@ { "name": "partyserver", - "version": "0.1.0", + "version": "0.1.1", "repository": { "type": "git", "url": "git://github.com/cloudflare/partykit.git" diff --git a/packages/partyserver/src/connection.ts b/packages/partyserver/src/connection.ts index 8cadaa6b..24d8c0c3 100644 --- a/packages/partyserver/src/connection.ts +++ b/packages/partyserver/src/connection.ts @@ -38,6 +38,39 @@ type ConnectionAttachments = { __user?: unknown; }; +function tryGetPartyServerMeta( + ws: WebSocket +): ConnectionAttachments["__pk"] | null { + try { + // Avoid AttachmentCache.get() here: hibernated sockets accepted outside + // PartyServer can have an attachment without a __pk namespace. + const attachment = WebSocket.prototype.deserializeAttachment.call( + ws + ) as unknown; + if (!attachment || typeof attachment !== "object") { + return null; + } + if (!("__pk" in attachment)) { + return null; + } + const pk = (attachment as ConnectionAttachments).__pk as unknown; + if (!pk || typeof pk !== "object") { + return null; + } + const { id, server } = pk as { id?: unknown; server?: unknown }; + if (typeof id !== "string" || typeof server !== "string") { + return null; + } + return pk as ConnectionAttachments["__pk"]; + } catch { + return null; + } +} + +export function isPartyServerWebSocket(ws: WebSocket): boolean { + return tryGetPartyServerMeta(ws) !== null; +} + /** * Cache websocket attachments to avoid having to rehydrate them on every property access. */ @@ -180,6 +213,12 @@ class HibernatingConnectionIterator implements IterableIterator< while ((socket = sockets[this.index++])) { // only yield open sockets to match non-hibernating behaviour if (socket.readyState === WebSocket.READY_STATE_OPEN) { + // Durable Objects hibernation APIs allow storing arbitrary sockets via + // `state.acceptWebSocket()`. Those sockets won't have PartyServer's + // `__pk` attachment namespace and must be ignored. + if (!isPartyServerWebSocket(socket)) { + continue; + } const value = createLazyConnection(socket) as Connection; return { done: false, value }; } @@ -263,15 +302,27 @@ export class HibernatingConnectionManager implements ConnectionManager { constructor(private controller: DurableObjectState) {} getCount() { - return Number(this.controller.getWebSockets().length); + // Only count sockets managed by PartyServer. Other hibernated sockets may + // exist on the same Durable Object via `state.acceptWebSocket()`. + let count = 0; + for (const ws of this.controller.getWebSockets()) { + // if (ws.readyState !== WebSocket.READY_STATE_OPEN) continue; + if (isPartyServerWebSocket(ws)) count++; + } + return count; } getConnection(id: string) { // TODO: Should we cache the connections? const sockets = this.controller.getWebSockets(id); - if (sockets.length === 0) return undefined; - if (sockets.length === 1) - return createLazyConnection(sockets[0]) as Connection; + const matching = sockets.filter((ws) => { + // if (ws.readyState !== WebSocket.READY_STATE_OPEN) return false; + return tryGetPartyServerMeta(ws)?.id === id; + }); + + if (matching.length === 0) return undefined; + if (matching.length === 1) + return createLazyConnection(matching[0]) as Connection; throw new Error( `More than one connection found for id ${id}. Did you mean to use getConnections(tag) instead?` diff --git a/packages/partyserver/src/index.ts b/packages/partyserver/src/index.ts index a009a62e..bb5f1a81 100644 --- a/packages/partyserver/src/index.ts +++ b/packages/partyserver/src/index.ts @@ -8,7 +8,8 @@ import { nanoid } from "nanoid"; import { createLazyConnection, HibernatingConnectionManager, - InMemoryConnectionManager + InMemoryConnectionManager, + isPartyServerWebSocket } from "./connection"; import type { ConnectionManager } from "./connection"; @@ -422,6 +423,13 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam return; } + // Ignore websockets accepted outside PartyServer (e.g. via + // `state.acceptWebSocket()` in user code). These sockets won't have the + // `__pk` attachment namespace required to rehydrate a Connection. + if (!isPartyServerWebSocket(ws)) { + return; + } + const connection = createLazyConnection(ws); // rehydrate the server name if it's woken up @@ -449,6 +457,10 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam return; } + if (!isPartyServerWebSocket(ws)) { + return; + } + const connection = createLazyConnection(ws); // rehydrate the server name if it's woken up @@ -470,6 +482,10 @@ Did you try connecting directly to this Durable Object? Try using getServerByNam return; } + if (!isPartyServerWebSocket(ws)) { + return; + } + const connection = createLazyConnection(ws); // rehydrate the server name if it's woken up diff --git a/packages/partyserver/src/tests/index.test.ts b/packages/partyserver/src/tests/index.test.ts index 44991d97..4311be80 100644 --- a/packages/partyserver/src/tests/index.test.ts +++ b/packages/partyserver/src/tests/index.test.ts @@ -149,6 +149,47 @@ describe("Server", () => { expect(response.headers.get("Location")).toBe("https://example3.com"); }); + it("ignores foreign hibernated websockets when broadcasting", async () => { + const ctx = createExecutionContext(); + + // Create a websocket that is accepted via the DO hibernation API directly + // (no PartyServer `__pk` attachment). + const foreignReq = new Request( + "http://example.com/parties/mixed/room/foreign", + { + headers: { Upgrade: "websocket" } + } + ); + const foreignRes = await worker.fetch(foreignReq, env, ctx); + const foreignWs = foreignRes.webSocket!; + foreignWs.accept(); + + // Now connect via PartyServer. onConnect() will call broadcast(), which must + // not crash due to the foreign socket. + const req = new Request("http://example.com/parties/mixed/room", { + headers: { Upgrade: "websocket" } + }); + const res = await worker.fetch(req, env, ctx); + const ws = res.webSocket!; + ws.accept(); + + const { promise, resolve, reject } = Promise.withResolvers(); + ws.addEventListener("message", (message) => { + try { + // We should receive at least one message from the server. + expect(["hello", "connected"]).toContain(message.data); + resolve(); + } catch (e) { + reject(e); + } finally { + ws.close(); + foreignWs.close(); + } + }); + + return promise; + }); + // it("can be connected with a query parameter"); // it("can be connected with a header"); diff --git a/packages/partyserver/src/tests/worker.ts b/packages/partyserver/src/tests/worker.ts index 46468f93..d0c12b4c 100644 --- a/packages/partyserver/src/tests/worker.ts +++ b/packages/partyserver/src/tests/worker.ts @@ -11,6 +11,7 @@ function assert(condition: unknown, message: string): asserts condition { export type Env = { Stateful: DurableObjectNamespace; OnStartServer: DurableObjectNamespace; + Mixed: DurableObjectNamespace; }; export class Stateful extends Server { @@ -61,6 +62,37 @@ export class OnStartServer extends Server { } } +export class Mixed extends Server { + static options = { + hibernate: true + }; + + async fetch(request: Request): Promise { + const url = new URL(request.url); + if (url.pathname.endsWith("/foreign")) { + const room = request.headers.get("x-partykit-room"); + if (room) { + await this.setName(room); + } + + const pair = new WebSocketPair(); + const [client, server] = Object.values(pair); + // Accept a hibernated websocket that PartyServer does not manage. This is + // equivalent to user code calling `this.ctx.acceptWebSocket()` directly. + this.ctx.acceptWebSocket(server, ["foreign"]); + return new Response(null, { status: 101, webSocket: client }); + } + + return super.fetch(request); + } + + onConnect(connection: Connection): void { + // Trigger a broadcast while a foreign hibernated socket exists. + this.broadcast("hello"); + connection.send("connected"); + } +} + export default { async fetch(request: Request, env: Env, _ctx: ExecutionContext) { return ( diff --git a/packages/partyserver/src/tests/wrangler.jsonc b/packages/partyserver/src/tests/wrangler.jsonc index 02ca8231..3ed93153 100644 --- a/packages/partyserver/src/tests/wrangler.jsonc +++ b/packages/partyserver/src/tests/wrangler.jsonc @@ -18,13 +18,17 @@ { "name": "OnStartServer", "class_name": "OnStartServer" + }, + { + "name": "Mixed", + "class_name": "Mixed" } ] }, "migrations": [ { "tag": "v1", // Should be unique for each entry - "new_classes": ["Stateful", "OnStartServer"] + "new_classes": ["Stateful", "OnStartServer", "Mixed"] } ] } From 20142455c47889511a19f26debfaeb7009fd323b Mon Sep 17 00:00:00 2001 From: Francesco Virga Date: Mon, 26 Jan 2026 15:36:32 -0800 Subject: [PATCH 2/9] Cleanup comments --- packages/partyserver/src/connection.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/partyserver/src/connection.ts b/packages/partyserver/src/connection.ts index 24d8c0c3..b834f541 100644 --- a/packages/partyserver/src/connection.ts +++ b/packages/partyserver/src/connection.ts @@ -306,7 +306,6 @@ export class HibernatingConnectionManager implements ConnectionManager { // exist on the same Durable Object via `state.acceptWebSocket()`. let count = 0; for (const ws of this.controller.getWebSockets()) { - // if (ws.readyState !== WebSocket.READY_STATE_OPEN) continue; if (isPartyServerWebSocket(ws)) count++; } return count; @@ -316,7 +315,6 @@ export class HibernatingConnectionManager implements ConnectionManager { // TODO: Should we cache the connections? const sockets = this.controller.getWebSockets(id); const matching = sockets.filter((ws) => { - // if (ws.readyState !== WebSocket.READY_STATE_OPEN) return false; return tryGetPartyServerMeta(ws)?.id === id; }); From 6b14765430753f1a0e87cd0e2ce486a730d084e5 Mon Sep 17 00:00:00 2001 From: Francesco Virga Date: Mon, 26 Jan 2026 15:46:11 -0800 Subject: [PATCH 3/9] Revert version --- packages/partyserver/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/partyserver/package.json b/packages/partyserver/package.json index e23adabd..ef1a71f4 100644 --- a/packages/partyserver/package.json +++ b/packages/partyserver/package.json @@ -1,6 +1,6 @@ { "name": "partyserver", - "version": "0.1.1", + "version": "0.1.0", "repository": { "type": "git", "url": "git://github.com/cloudflare/partykit.git" From 8577b36389f14ab554ad70bc27a0ac2b063d1c3f Mon Sep 17 00:00:00 2001 From: Francesco Virga Date: Mon, 26 Jan 2026 16:14:06 -0800 Subject: [PATCH 4/9] Remove macos build bindings --- package-lock.json | 13 ++++++++++++- package.json | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a0b26bd..00dad38a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@cloudflare/vite-plugin": "^1.19.0", "@cloudflare/vitest-pool-workers": "^0.10.4", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", - "@rolldown/binding-darwin-arm64": "^1.0.0-beta.55", "@types/node": "25.0.3", "@vitejs/plugin-react": "^5.1.2", "@vitest/runner": "3.2.4", @@ -4982,6 +4981,7 @@ ], "dev": true, "license": "MIT", + "optional": true, "os": [ "darwin" ], @@ -8600,6 +8600,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8621,6 +8622,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8642,6 +8644,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8663,6 +8666,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8684,6 +8688,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8705,6 +8710,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8726,6 +8732,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8747,6 +8754,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8768,6 +8776,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8789,6 +8798,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8810,6 +8820,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, diff --git a/package.json b/package.json index 0f55afdb..71eb0215 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@cloudflare/vite-plugin": "^1.19.0", "@cloudflare/vitest-pool-workers": "^0.10.4", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", - "@rolldown/binding-darwin-arm64": "^1.0.0-beta.55", "@types/node": "25.0.3", "@vitejs/plugin-react": "^5.1.2", "@vitest/runner": "3.2.4", From 806bca46ab8cc41f1475674e9161e7a5f50a911f Mon Sep 17 00:00:00 2001 From: Francesco Virga Date: Mon, 26 Jan 2026 17:35:32 -0800 Subject: [PATCH 5/9] Revert package lock changesgp --- package-lock.json | 60 ++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00dad38a..3f1e4539 100644 --- a/package-lock.json +++ b/package-lock.json @@ -352,6 +352,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1548,6 +1549,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1591,6 +1593,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2085,6 +2088,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -3093,6 +3097,7 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5837,6 +5842,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.14.0.tgz", "integrity": "sha512-nm0VWVA1Vq/jaKY3wyRXViL/kf78yMdH7qETpv4qZXDQLU+pdWV3IGoRTQTKESc7d8L1wL/2uCeByLNUJfrSIw==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6088,6 +6094,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.14.0.tgz", "integrity": "sha512-rsjFH0Vd/4UbDsjwMLay7oz72VVu1r35t8ofAzy5587jn5JAjflaZs05XbRRMD2imUTK41dyajVSh8CqSnDEJw==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6193,6 +6200,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.14.0.tgz", "integrity": "sha512-qQBVKqzU4ZVjRn8W0UbdfE4LaaIgcIWHOMrNnJ+PutrRzQ6ZzhmD/kRONvRWBfG9z3DU7pSKGwVYSR2hztsGuQ==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6207,6 +6215,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.14.0.tgz", "integrity": "sha512-xrZmqI5jl4yMeAsu8p8gVP9S3An5h2MBi8BQHNnZmpyzkUrlpd40vlT6u13SWIqVi5ZWhBZ6U3rL7mkVLZuRKg==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6349,6 +6358,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6362,6 +6372,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6510,8 +6521,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6643,6 +6653,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6652,6 +6663,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6781,6 +6793,7 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -6796,6 +6809,7 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -6892,7 +6906,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6934,7 +6947,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -7066,6 +7078,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7445,7 +7458,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7501,8 +7513,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dompurify": { "version": "3.2.7", @@ -8600,7 +8611,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8622,7 +8632,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8644,7 +8653,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8666,7 +8674,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8688,7 +8695,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8710,7 +8716,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8732,7 +8737,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8754,7 +8758,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8776,7 +8779,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8798,7 +8800,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8820,7 +8821,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -9109,6 +9109,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -9596,6 +9597,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9612,7 +9614,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9743,6 +9744,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.2.tgz", "integrity": "sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww==", "license": "MIT", + "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -9772,6 +9774,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -9820,6 +9823,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.4.tgz", "integrity": "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -10025,6 +10029,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10034,6 +10039,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10058,8 +10064,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -10260,6 +10265,7 @@ "integrity": "sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "=0.103.0", "@rolldown/pluginutils": "1.0.0-beta.55" @@ -10997,6 +11003,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11065,6 +11072,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11105,6 +11113,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -11355,6 +11364,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -11395,6 +11405,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11470,6 +11481,7 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -11655,6 +11667,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11784,6 +11797,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11797,6 +11811,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -12003,6 +12018,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -12124,6 +12140,7 @@ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", "license": "MIT", + "peer": true, "dependencies": { "lib0": "^0.2.85" }, @@ -12151,6 +12168,7 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.28.tgz", "integrity": "sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA==", "license": "MIT", + "peer": true, "dependencies": { "lib0": "^0.2.99" }, From 92b92bcd335c0b7fc507bbcda6cae99ca05d0a89 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 28 Jan 2026 22:44:01 +0000 Subject: [PATCH 6/9] Fixes foreign WebSocket handling; LGTM Co-authored-by: threepointone --- package-lock.json | 60 +++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f1e4539..00dad38a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -352,7 +352,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1549,7 +1548,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1593,7 +1591,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2088,7 +2085,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -3097,7 +3093,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5842,7 +5837,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.14.0.tgz", "integrity": "sha512-nm0VWVA1Vq/jaKY3wyRXViL/kf78yMdH7qETpv4qZXDQLU+pdWV3IGoRTQTKESc7d8L1wL/2uCeByLNUJfrSIw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6094,7 +6088,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.14.0.tgz", "integrity": "sha512-rsjFH0Vd/4UbDsjwMLay7oz72VVu1r35t8ofAzy5587jn5JAjflaZs05XbRRMD2imUTK41dyajVSh8CqSnDEJw==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6200,7 +6193,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.14.0.tgz", "integrity": "sha512-qQBVKqzU4ZVjRn8W0UbdfE4LaaIgcIWHOMrNnJ+PutrRzQ6ZzhmD/kRONvRWBfG9z3DU7pSKGwVYSR2hztsGuQ==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6215,7 +6207,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.14.0.tgz", "integrity": "sha512-xrZmqI5jl4yMeAsu8p8gVP9S3An5h2MBi8BQHNnZmpyzkUrlpd40vlT6u13SWIqVi5ZWhBZ6U3rL7mkVLZuRKg==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6358,7 +6349,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6372,7 +6362,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6521,7 +6510,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6653,7 +6643,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6663,7 +6652,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6793,7 +6781,6 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -6809,7 +6796,6 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -6906,6 +6892,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -6947,6 +6934,7 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -7078,7 +7066,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7458,6 +7445,7 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -7513,7 +7501,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dompurify": { "version": "3.2.7", @@ -8611,6 +8600,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8632,6 +8622,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8653,6 +8644,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8674,6 +8666,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8695,6 +8688,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8716,6 +8710,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8737,6 +8732,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8758,6 +8754,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8779,6 +8776,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8800,6 +8798,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8821,6 +8820,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -9109,7 +9109,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -9597,7 +9596,6 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9614,6 +9612,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9744,7 +9743,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.2.tgz", "integrity": "sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww==", "license": "MIT", - "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -9774,7 +9772,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -9823,7 +9820,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.4.tgz", "integrity": "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -10029,7 +10025,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10039,7 +10034,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10064,7 +10058,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -10265,7 +10260,6 @@ "integrity": "sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "=0.103.0", "@rolldown/pluginutils": "1.0.0-beta.55" @@ -11003,7 +10997,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11072,7 +11065,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11113,7 +11105,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -11364,7 +11355,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -11405,7 +11395,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11481,7 +11470,6 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -11667,7 +11655,6 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11797,7 +11784,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11811,7 +11797,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -12018,7 +12003,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -12140,7 +12124,6 @@ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.85" }, @@ -12168,7 +12151,6 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.28.tgz", "integrity": "sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.99" }, From b8338de383a2b7aba2d1736d7349bc256f7798f9 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 28 Jan 2026 22:44:59 +0000 Subject: [PATCH 7/9] Create selfish-bananas-clean.md --- .changeset/selfish-bananas-clean.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/selfish-bananas-clean.md diff --git a/.changeset/selfish-bananas-clean.md b/.changeset/selfish-bananas-clean.md new file mode 100644 index 00000000..3508149b --- /dev/null +++ b/.changeset/selfish-bananas-clean.md @@ -0,0 +1,5 @@ +--- +"partyserver": patch +--- + +Check for hibernated websocket connections From f9dfa074eed026de5f2e18e9298e41a0bae763b5 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 28 Jan 2026 22:54:25 +0000 Subject: [PATCH 8/9] Improve test reliability and type safety in test suites Refactors test files to ensure event handlers are attached before connections, adds explicit type annotations for WebSocket instances, and improves cleanup logic for server and client resources. These changes enhance test reliability, prevent race conditions, and improve type safety in both browser and Node.js environments. --- package-lock.json | 60 +++++++++++------ .../partysocket/src/tests/integration.test.ts | 2 +- .../src/tests/react-hooks.test.tsx | 66 +++++++++++++++---- .../partysocket/src/tests/react-ssr.test.tsx | 38 +++++++---- .../src/tests/reconnecting-node.test.ts | 2 +- .../src/tests/reconnecting.test.ts | 18 ++--- 6 files changed, 131 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00dad38a..3f1e4539 100644 --- a/package-lock.json +++ b/package-lock.json @@ -352,6 +352,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1548,6 +1549,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1591,6 +1593,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2085,6 +2088,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -3093,6 +3097,7 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -5837,6 +5842,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.14.0.tgz", "integrity": "sha512-nm0VWVA1Vq/jaKY3wyRXViL/kf78yMdH7qETpv4qZXDQLU+pdWV3IGoRTQTKESc7d8L1wL/2uCeByLNUJfrSIw==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6088,6 +6094,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.14.0.tgz", "integrity": "sha512-rsjFH0Vd/4UbDsjwMLay7oz72VVu1r35t8ofAzy5587jn5JAjflaZs05XbRRMD2imUTK41dyajVSh8CqSnDEJw==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6193,6 +6200,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.14.0.tgz", "integrity": "sha512-qQBVKqzU4ZVjRn8W0UbdfE4LaaIgcIWHOMrNnJ+PutrRzQ6ZzhmD/kRONvRWBfG9z3DU7pSKGwVYSR2hztsGuQ==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6207,6 +6215,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.14.0.tgz", "integrity": "sha512-xrZmqI5jl4yMeAsu8p8gVP9S3An5h2MBi8BQHNnZmpyzkUrlpd40vlT6u13SWIqVi5ZWhBZ6U3rL7mkVLZuRKg==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6349,6 +6358,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -6362,6 +6372,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -6510,8 +6521,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6643,6 +6653,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6652,6 +6663,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6781,6 +6793,7 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -6796,6 +6809,7 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -6892,7 +6906,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6934,7 +6947,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -7066,6 +7078,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7445,7 +7458,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7501,8 +7513,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dompurify": { "version": "3.2.7", @@ -8600,7 +8611,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8622,7 +8632,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8644,7 +8653,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8666,7 +8674,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8688,7 +8695,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8710,7 +8716,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8732,7 +8737,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8754,7 +8758,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8776,7 +8779,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8798,7 +8800,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8820,7 +8821,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -9109,6 +9109,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -9596,6 +9597,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9612,7 +9614,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9743,6 +9744,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.2.tgz", "integrity": "sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww==", "license": "MIT", + "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -9772,6 +9774,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -9820,6 +9823,7 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.4.tgz", "integrity": "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -10025,6 +10029,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10034,6 +10039,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10058,8 +10064,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -10260,6 +10265,7 @@ "integrity": "sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "=0.103.0", "@rolldown/pluginutils": "1.0.0-beta.55" @@ -10997,6 +11003,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11065,6 +11072,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.6.2.tgz", "integrity": "sha512-XKZYrCVFsyQGF6dXQR73YR222l/76wkKfZ+2/4LCrem5qtcOarmv5pYxjUBG8mRuBPskTTBImSFTeQltJIUNCg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -11105,6 +11113,7 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.6.2.tgz", "integrity": "sha512-g+NXjqjbj6NfHOMl22uNWVYIu8oCq7RFfbnpohPMsSKJLaHYE8mJR++7T6P5R9FoqhIFdwizg1jTpwRU5CHqXQ==", "license": "MIT", + "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -11355,6 +11364,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -11395,6 +11405,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11470,6 +11481,7 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -11655,6 +11667,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11784,6 +11797,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11797,6 +11811,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -12003,6 +12018,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -12124,6 +12140,7 @@ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", "license": "MIT", + "peer": true, "dependencies": { "lib0": "^0.2.85" }, @@ -12151,6 +12168,7 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.28.tgz", "integrity": "sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA==", "license": "MIT", + "peer": true, "dependencies": { "lib0": "^0.2.99" }, diff --git a/packages/partysocket/src/tests/integration.test.ts b/packages/partysocket/src/tests/integration.test.ts index 7fba7ba4..682508ca 100644 --- a/packages/partysocket/src/tests/integration.test.ts +++ b/packages/partysocket/src/tests/integration.test.ts @@ -163,7 +163,7 @@ describe("Integration - Full Lifecycle", () => { for (let i = 0; i < messageCount; i++) { expect(receivedMessages[i]).toBe(`message-${i}`); } - } catch (e) { + } catch (_e) { // If we still have Blobs, messages aren't fully processed yet return; } diff --git a/packages/partysocket/src/tests/react-hooks.test.tsx b/packages/partysocket/src/tests/react-hooks.test.tsx index b1598a57..9f4870d4 100644 --- a/packages/partysocket/src/tests/react-hooks.test.tsx +++ b/packages/partysocket/src/tests/react-hooks.test.tsx @@ -9,7 +9,7 @@ import { WebSocketServer } from "ws"; import usePartySocket, { useWebSocket } from "../react"; const PORT = 50128; -const URL = `ws://localhost:${PORT}`; +// const URL = `ws://localhost:${PORT}`; describe("usePartySocket", () => { let wss: WebSocketServer; @@ -313,25 +313,40 @@ describe("usePartySocket", () => { test("attaches onOpen event handler", async () => { const onOpen = vitest.fn(); - wss.once("connection", (ws) => { + wss.once("connection", (_ws) => { // Connection established }); + // Start with socket closed to ensure handler is attached before connection const { result } = renderHook(() => usePartySocket({ host: `localhost:${PORT}`, room: "test-room", - onOpen + onOpen, + startClosed: true }) ); + // Verify socket starts closed + expect(result.current.readyState).toBe(WebSocket.CLOSED); + + // Give useEffect time to attach event handlers + await new Promise((resolve) => setTimeout(resolve, 50)); + + // Manually trigger reconnection - handler should be attached by now + result.current.reconnect(); + + // Wait for connection to be established await waitFor( () => { - expect(onOpen).toHaveBeenCalled(); + expect(result.current.readyState).toBe(WebSocket.OPEN); }, { timeout: 3000 } ); + // Verify onOpen was called + expect(onOpen).toHaveBeenCalled(); + result.current.close(); }); @@ -340,7 +355,10 @@ describe("usePartySocket", () => { const testMessage = "hello from server"; wss.once("connection", (ws) => { - ws.send(testMessage); + // Wait for connection to be fully established before sending + setTimeout(() => { + ws.send(testMessage); + }, 100); }); const { result } = renderHook(() => @@ -351,6 +369,15 @@ describe("usePartySocket", () => { }) ); + // Wait for connection to be established first + await waitFor( + () => { + expect(result.current.readyState).toBe(WebSocket.OPEN); + }, + { timeout: 3000 } + ); + + // Then wait for message await waitFor( () => { expect(onMessage).toHaveBeenCalled(); @@ -367,7 +394,8 @@ describe("usePartySocket", () => { const onClose = vitest.fn(); wss.once("connection", (ws) => { - setTimeout(() => ws.close(), 50); + // Wait for connection to be fully established before closing + setTimeout(() => ws.close(), 100); }); const { result } = renderHook(() => @@ -378,14 +406,21 @@ describe("usePartySocket", () => { }) ); + // Wait for connection to be established first await waitFor( () => { - expect(onClose).toHaveBeenCalled(); + expect(result.current.readyState).toBe(WebSocket.OPEN); }, { timeout: 3000 } ); - result.current.close(); + // Then wait for close event + await waitFor( + () => { + expect(onClose).toHaveBeenCalled(); + }, + { timeout: 3000 } + ); }); test("attaches onError event handler", async () => { @@ -415,8 +450,9 @@ describe("usePartySocket", () => { const onMessage2 = vitest.fn(); wss.once("connection", (ws) => { - setTimeout(() => ws.send("message1"), 50); - setTimeout(() => ws.send("message2"), 100); + // Wait for connection to be fully established before sending messages + setTimeout(() => ws.send("message1"), 100); + setTimeout(() => ws.send("message2"), 200); }); const { result, rerender } = renderHook( @@ -431,6 +467,14 @@ describe("usePartySocket", () => { const firstSocket = result.current; + // Wait for connection to be established first + await waitFor( + () => { + expect(result.current.readyState).toBe(WebSocket.OPEN); + }, + { timeout: 3000 } + ); + // Wait for first message await waitFor( () => { @@ -485,7 +529,7 @@ describe("usePartySocket", () => { }); test("connects automatically when startClosed is false", async () => { - wss.once("connection", (ws) => { + wss.once("connection", (_ws) => { // Connection established }); diff --git a/packages/partysocket/src/tests/react-ssr.test.tsx b/packages/partysocket/src/tests/react-ssr.test.tsx index 15786cf5..c79ab665 100644 --- a/packages/partysocket/src/tests/react-ssr.test.tsx +++ b/packages/partysocket/src/tests/react-ssr.test.tsx @@ -3,7 +3,7 @@ */ import { renderToString } from "react-dom/server"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { WebSocketServer } from "ws"; +import { type WebSocket as NodeWebSocket, WebSocketServer } from "ws"; import { usePartySocket, useWebSocket } from "../react"; const PORT = 50135; @@ -25,9 +25,16 @@ describe("SSR/Node.js Environment - usePartySocket", () => { }); afterEach(() => { - wss.close(); - global.window = originalWindow; - global.document = originalDocument; + return new Promise((resolve) => { + wss.clients.forEach((client: NodeWebSocket) => { + client.terminate(); + }); + wss.close(() => { + global.window = originalWindow; + global.document = originalDocument; + resolve(); + }); + }); }); it("should use default host when window is not available", () => { @@ -151,7 +158,7 @@ describe("SSR/Node.js Environment - usePartySocket", () => { it("should handle query params in SSR", () => { function TestComponent() { - const socket = usePartySocket({ + const _socket = usePartySocket({ host: "example.com", room: "test-room", query: { token: "abc123" }, @@ -167,7 +174,7 @@ describe("SSR/Node.js Environment - usePartySocket", () => { it("should handle async query params in SSR", () => { function TestComponent() { - const socket = usePartySocket({ + const _socket = usePartySocket({ host: "example.com", room: "test-room", query: async () => ({ token: "abc123" }), @@ -219,9 +226,16 @@ describe("SSR/Node.js Environment - useWebSocket", () => { }); afterEach(() => { - wss.close(); - global.window = originalWindow; - global.document = originalDocument; + return new Promise((resolve) => { + wss.clients.forEach((client: NodeWebSocket) => { + client.terminate(); + }); + wss.close(() => { + global.window = originalWindow; + global.document = originalDocument; + resolve(); + }); + }); }); it("should render with string URL in SSR", () => { @@ -273,7 +287,7 @@ describe("SSR/Node.js Environment - useWebSocket", () => { it("should handle protocols array in SSR", () => { function TestComponent() { - const socket = useWebSocket( + const _socket = useWebSocket( `ws://localhost:${PORT + 1}`, ["protocol1", "protocol2"], { @@ -290,7 +304,7 @@ describe("SSR/Node.js Environment - useWebSocket", () => { it("should handle protocol function in SSR", () => { function TestComponent() { - const socket = useWebSocket( + const _socket = useWebSocket( `ws://localhost:${PORT + 1}`, () => "protocol1", { @@ -307,7 +321,7 @@ describe("SSR/Node.js Environment - useWebSocket", () => { it("should handle async protocol in SSR", () => { function TestComponent() { - const socket = useWebSocket( + const _socket = useWebSocket( `ws://localhost:${PORT + 1}`, async () => "protocol1", { diff --git a/packages/partysocket/src/tests/reconnecting-node.test.ts b/packages/partysocket/src/tests/reconnecting-node.test.ts index b27c3eb0..9272654a 100644 --- a/packages/partysocket/src/tests/reconnecting-node.test.ts +++ b/packages/partysocket/src/tests/reconnecting-node.test.ts @@ -56,7 +56,7 @@ afterEach(() => { afterAll(() => { return new Promise((resolve) => { - wss.clients.forEach((client) => { + wss.clients.forEach((client: NodeWebSocket) => { client.terminate(); }); wss.close(() => { diff --git a/packages/partysocket/src/tests/reconnecting.test.ts b/packages/partysocket/src/tests/reconnecting.test.ts index cfa0c8ed..723ce7ee 100644 --- a/packages/partysocket/src/tests/reconnecting.test.ts +++ b/packages/partysocket/src/tests/reconnecting.test.ts @@ -41,7 +41,7 @@ afterEach(() => { afterAll(() => { return new Promise((resolve) => { - wss.clients.forEach((client) => { + wss.clients.forEach((client: NodeWebSocket) => { client.terminate(); }); wss.close(() => { @@ -475,13 +475,13 @@ testDone("start closed", (done, fail) => { const anyMessageText = "hello"; const anyProtocol = "foobar"; - wss.once("connection", (ws) => { - void ws.once("message", (msg) => { + wss.once("connection", (ws: NodeWebSocket) => { + void ws.once("message", (msg: Buffer) => { ws.send(msg); }); }); - wss.once("error", (e) => { + wss.once("error", (e: Error) => { fail(e); }); @@ -526,13 +526,13 @@ testDone("connect, send, receive, close", (done, fail) => { const anyMessageText = "hello"; const anyProtocol = "foobar"; - wss.once("connection", (ws) => { - void ws.once("message", (msg) => { + wss.once("connection", (ws: NodeWebSocket) => { + void ws.once("message", (msg: Buffer) => { ws.send(msg); }); }); - wss.on("error", (e) => { + wss.on("error", (e: Error) => { fail(e); }); @@ -739,8 +739,8 @@ testDone( expect(ws.bufferedAmount).toBe(messages.reduce((a, m) => a + m.length, 0)); let i = 0; - wss.once("connection", (client) => { - client.on("message", (data) => { + wss.once("connection", (client: NodeWebSocket) => { + client.on("message", (data: Buffer) => { // eslint-disable-next-line @typescript-eslint/no-base-to-string if (data.toString() === "ok") { expect(i).toBe(messages.length); From 7eccf4f5cba65bdb8f36c3f57b3f4f0354a3e255 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 28 Jan 2026 23:04:44 +0000 Subject: [PATCH 9/9] Refactor usePartySocket tests for connection handling Improves reliability of usePartySocket tests by setting up server connection handlers before rendering hooks and removing unnecessary waits for connection establishment. Also ensures proper cleanup of event listeners after each test. --- .../src/tests/react-hooks.test.tsx | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/packages/partysocket/src/tests/react-hooks.test.tsx b/packages/partysocket/src/tests/react-hooks.test.tsx index 9f4870d4..ecf17987 100644 --- a/packages/partysocket/src/tests/react-hooks.test.tsx +++ b/packages/partysocket/src/tests/react-hooks.test.tsx @@ -313,30 +313,26 @@ describe("usePartySocket", () => { test("attaches onOpen event handler", async () => { const onOpen = vitest.fn(); - wss.once("connection", (_ws) => { - // Connection established + // Set up connection handler before rendering + const connectionPromise = new Promise((resolve) => { + wss.once("connection", (_ws: any) => { + // Connection established + resolve(); + }); }); - // Start with socket closed to ensure handler is attached before connection const { result } = renderHook(() => usePartySocket({ host: `localhost:${PORT}`, room: "test-room", - onOpen, - startClosed: true + onOpen }) ); - // Verify socket starts closed - expect(result.current.readyState).toBe(WebSocket.CLOSED); - - // Give useEffect time to attach event handlers - await new Promise((resolve) => setTimeout(resolve, 50)); + // Wait for connection to be established on server side + await connectionPromise; - // Manually trigger reconnection - handler should be attached by now - result.current.reconnect(); - - // Wait for connection to be established + // Wait for connection to be established on client side await waitFor( () => { expect(result.current.readyState).toBe(WebSocket.OPEN); @@ -354,12 +350,13 @@ describe("usePartySocket", () => { const onMessage = vitest.fn(); const testMessage = "hello from server"; - wss.once("connection", (ws) => { - // Wait for connection to be fully established before sending + const connectionHandler = (ws: any) => { + // Send message after a small delay to ensure connection is fully established setTimeout(() => { ws.send(testMessage); - }, 100); - }); + }, 50); + }; + wss.on("connection", connectionHandler); const { result } = renderHook(() => usePartySocket({ @@ -369,15 +366,7 @@ describe("usePartySocket", () => { }) ); - // Wait for connection to be established first - await waitFor( - () => { - expect(result.current.readyState).toBe(WebSocket.OPEN); - }, - { timeout: 3000 } - ); - - // Then wait for message + // Wait for message to be received await waitFor( () => { expect(onMessage).toHaveBeenCalled(); @@ -387,6 +376,7 @@ describe("usePartySocket", () => { { timeout: 3000 } ); + wss.off("connection", connectionHandler); result.current.close(); }); @@ -449,11 +439,12 @@ describe("usePartySocket", () => { const onMessage1 = vitest.fn(); const onMessage2 = vitest.fn(); - wss.once("connection", (ws) => { - // Wait for connection to be fully established before sending messages + const connectionHandler = (ws: any) => { + // Send messages with delays to ensure connection is established setTimeout(() => ws.send("message1"), 100); setTimeout(() => ws.send("message2"), 200); - }); + }; + wss.on("connection", connectionHandler); const { result, rerender } = renderHook( ({ onMessage }) => @@ -467,14 +458,6 @@ describe("usePartySocket", () => { const firstSocket = result.current; - // Wait for connection to be established first - await waitFor( - () => { - expect(result.current.readyState).toBe(WebSocket.OPEN); - }, - { timeout: 3000 } - ); - // Wait for first message await waitFor( () => { @@ -497,6 +480,7 @@ describe("usePartySocket", () => { { timeout: 3000 } ); + wss.off("connection", connectionHandler); result.current.close(); });