-
-
Notifications
You must be signed in to change notification settings - Fork 34
Open
Description
Problem
The node adapter hardcodes handleProtocols: () => false:
// src/adapters/node.ts
const wss = options.wss || new _WebSocketServer({
noServer: true,
handleProtocols: () => false,
...options.serverOptions
});This breaks WebSocket subprotocol negotiation for browsers. When using protocols like graphql-transport-ws (for GraphQL subscriptions), browsers reject the connection:
WebSocket connection failed: Error during WebSocket handshake:
Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
Why This Happens
- Browser sends
Sec-WebSocket-Protocol: graphql-transport-ws wslibrary callshandleProtocols()→ returnsfalsewsinternally marks "no protocol selected"- Even if we add
Sec-WebSocket-Protocolheader in theheadersevent, browsers still reject - Node.js
wsclient is lenient and accepts, but browsers are strict
Current Workaround Doesn't Work for Browsers
defineWebSocketHandler({
upgrade(request) {
const protocol = request.headers.get('sec-websocket-protocol')
if (protocol?.includes('graphql-transport-ws')) {
return {
headers: { 'Sec-WebSocket-Protocol': 'graphql-transport-ws' }
}
}
}
})This works for Node.js clients but not for browsers.
Proposed Solutions
Option 1: Smart default based on upgrade hook response
If the upgrade hook returns a Sec-WebSocket-Protocol header, use that as the selected protocol:
handleProtocols: (protocols, request) => {
// Check if upgrade hook set a protocol header
const upgradeProtocol = request._upgradeHeaders?.get?.('sec-websocket-protocol')
if (upgradeProtocol && protocols.has(upgradeProtocol)) {
return upgradeProtocol
}
return false
}Option 2: Allow configuration via options
wsAdapter({
serverOptions: {
handleProtocols: (protocols) => {
if (protocols.has('graphql-transport-ws')) return 'graphql-transport-ws'
return false
}
}
})This already works via ...options.serverOptions spread, but requires upstream (Nitro) to expose it.
Option 3: Echo back first requested protocol by default
Instead of () => false, default to echoing the first protocol:
handleProtocols: (protocols) => protocols.values().next().value || falseUse Case
GraphQL subscriptions using the graphql-ws protocol, which is standard for:
- GraphQL Yoga
- Apollo Server
- Any server implementing graphql-ws spec
Environment
- crossws: 0.4.1
- ws: 8.x
- Tested with Chrome, Firefox, Safari - all fail
- Node.js ws client works (more lenient)
Related
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels