mswjs / interceptors

Low-level network interception library.
https://npm.im/@mswjs/interceptors
MIT License
523 stars 118 forks source link

feat(WebSocket): add connection "info" to the "connection" event payload #577

Closed DanielleHuisman closed 1 month ago

DanielleHuisman commented 1 month ago

Currently, there is no way to access the full list of protocols supported by the WebSocket client. WebSocketOverride only exposes the first value (relevant line).

This PR exposes the client protocols in the connection event, so tests can verify the client provided the right protocols.

kettanaito commented 1 month ago

Hi, @DanielleHuisman. Thanks for opening this.

WebSocketOverride only exposes the first value

That is correct. WebSocketOverride complies with WebSocket, which complies with WHATWG WebSocket, where the protocol property on the WebSocket interface is a single string representing the protocol that was eventually chosen by the instance (source).

In other words, this is also true:

let ws = new WebSocket(url, ['ws:', 'wss:'])
// You cannot access the full list of protocols anymore.
ws.protocol // "ws:"

Can you tell me more about your use case to access the list of protocols?

kettanaito commented 1 month ago

Also a bit of information about how the string[] input of protocols is handled:

Each string in the array is a subprotocol name. The connection will only be established if the server reports that it has selected one of these subprotocols.

As I understand it, the handshake request's Sec-WebSocket-Protocol header will combine all the protocols you've specified on the client so then the server can report which protocol it ended up using.

Does this mean that you want to access the list of protocols to choose between them within the event handler? I'm still a bit confused how that's beneficial. Would love to learn more.

DanielleHuisman commented 1 month ago

I think you're mixing up URL protocols and WebSocket subprotocols. The URL protocol (ws: or wss:) is specified in the url parameter of the WebSocket constructor. The WebSocket subprotocols are specified in the protocols parameter of WebSocket. The naming of the second parameter is a bit unfortunate.

WebSocket subprotocols are user defined strings that could represent the capabilities of the client and server. As you mentioned, the client and server need to agree on the subprotocol they are using and this single value is stored in WebSocket.protocol. An example of these subprotocols can be found in section 1.2 of the RFC. More information can be found in the RFC when searching for Sec-WebSocket-Protocol or subprotocol.

I have some WebSocket client code that conditionally chooses a subprotocol, so I would like to verify this behaviour in unit tests using MSW. Currently, I can only verify the first value sent by the client in WebSocket.protocol. My PR attempts to expose the full list of subprotocols provided by the client.

Now that I've looked at it in more detail, I think the default value (ws) in WebSocketOverride (line) does not match the specification. I believe if no subprotocols are provided by the client, WebSocket.protocol is an empty string, because the client and server both agree not to use a subprotocol. Ofcourse assuming the server doesn't require the use of a subprotocol.

I hope this clarifies the intentions of my PR.

kettanaito commented 1 month ago

I think you're mixing up URL protocols and WebSocket subprotocols.

🤦 I totally am mixing those up. Thanks for clarifying!

My PR attempts to expose the full list of subprotocols provided by the client.

I think this is reasonable then. Let me think of the exact way we can do that.

Now that I've looked at it in more detail, I think the default value (ws) in WebSocketOverride (line) does not match the specification.

Yes, you are right. Please, could you open a new pull request to fix this? I would be grateful!

kettanaito commented 1 month ago

I've nested the protocols under info key of the event payload. This is how you can access it now:

interceptor.on('connection', ({ info, client }) => {
  info.protocols // ['one', 'two']
  client.socket.protocol // 'one'
})
kettanaito commented 1 month ago

Released: v0.30.0 🎉

This has been released in v0.30.0!

Make sure to always update to the latest version (npm i @mswjs/interceptors@latest) to get the newest features and bug fixes.


Predictable release automation by @ossjs/release.