Debugging WebSockets using JS Proxy Object

Debugging WebSocket connections with JS Proxy object is easy and requires surprisingly little code. Proxies allow you to set "traps" which can listen for new operator calls ("construct"), function calls ("apply") and more.

Examples

Here is a basic example of proxying the native window.WebSocket object then replacing it with the proxied version.

Calling new WebSocket(...) will still work exactly as before.

// proxy the window.WebSocket object
var WebSocketProxy = new Proxy(window.WebSocket, {  
  construct: function(target, args) {
    // create WebSocket instance
    const instance = new target(...args);
    // return the WebSocket instance
    return instance;
  }
});

// replace the native WebSocket with the proxy
window.WebSocket = WebSocketProxy;  

Here we are adding a trap for the new operator, so that when new WebSocket(...) is called we can manually create a new WebSocket instance.

Now we can now start to add listeners to monitor open, message and close events and proxy the WebSocket.send() function before finally returning the new WebSocket instance.

// proxy the window.WebSocket object
var WebSocketProxy = new Proxy(window.WebSocket, {  
  construct: function(target, args) {
    // create WebSocket instance
    const instance = new target(...args);

    // WebSocket "onopen" handler
    const openHandler = (event) => {
      console.log('Open', event);
    };

    // WebSocket "onmessage" handler
    const messageHandler = (event) => {
      console.log('Message', event);
    };

    // WebSocket "onclose" handler
    const closeHandler = (event) => {
      console.log('Close', event);
      // remove event listeners
      instance.removeEventListener('open', openHandler);
      instance.removeEventListener('message', messageHandler);
      instance.removeEventListener('close', closeHandler);
    };  

    // add event listeners
    instance.addEventListener('open', openHandler);
    instance.addEventListener('message', messageHandler);
    instance.addEventListener('close', closeHandler);

    // proxy the WebSocket.send() function
    const sendProxy = new Proxy(instance.send, {
      apply: function(target, thisArg, args) {
        console.log('Send', args);
        target.apply(thisArg, args);
      }
    });

    // replace the native send function with the proxy
    instance.send = sendProxy;

    // return the WebSocket instance
    return instance;
  }
});

// replace the native WebSocket with the proxy
window.WebSocket = WebSocketProxy;  

You can test this snippet in the console at https://www.websocket.org/echo.html.

Example output:

Now when a new WebSocket connection is made we will be able to 'passively' listen-in on the connection events and any messages sent or received.

Alternatives

Here are some other alternatives to monitoring WebSocket connections, after some investigation I still find the proxy method much cleaner and more flexible.

Wrapper

Wrapping the window.WebSocket in a custom class/function is possible but then you need to proxy each individual WebSocket function like in this example.

Chrome Extensions

Chrome since v58 has limited support for debugging WebSockets in extensions. They only go as far as to capture the initial handshake as that is a HTTP upgrade request.

Any messages sent or received, redirections or closing of the connection are not supported yet.

Checkout webRequest API for documentation on what's possible. Also see this Chrome bug for more discussion on the implementation.

Chrome Dev Tools

OK... the Chrome dev tools are pretty awesome for debugging WebSocket connections. Its been around since 2012 and works great.

Check out this Stack Overflow page to find out how to do it.

Conclusion

Proxy is very powerful when it comes to intercepting, debugging and monitoring JS Objects!

One thing to note here - in the examples I used above I was simply a passive listener to events and messages for WebSocket. However this pattern also allows arbitrary function calls to be made on the objects and functions that are proxied.

For example I could send my own messages through my proxied WebSocket instance:

...
instance.addEventListener('open', (event) => {  
      console.log('open', event);
      instance.send('Hello from the Proxy!');
});
...

Now there are a million other use cases with that kind of power!

Get Proxying :)

References