trexinf14s
trexinf14s

Reputation: 291

How can an extension listen to a WebSocket? (and what if the WebSocket is within an iframe?)

I am writing a Firefox extension that needs to listen to communication between browser and server. For HTTP communication, I use the webRequest library in a background script. But according to this Mozilla bug report, I cannot use webRequest to intercept WebSocket messages. How can my extension listen in on WebSocket communication?

Update:

I've injected my WebSocket wrapper, but it's never called! The WebSocket I am trying to listen to is inside an iframe. Does that matter?

Upvotes: 3

Views: 4493

Answers (2)

Thi Hoang
Thi Hoang

Reputation: 87

I don't know if you still need this, but I got it to work by editing something from trexinf14s's answer.

1, Add syringe.js to your manifest.json, "run_at": "document_end" if you want this extension run after webpage is fully loaded, change "web_accessible_resources" to new manifest format.

"content_scripts": [{
"css": ["styles.css"],
"run_at": "document_end",
"all_frames": true,  
"js": ["lib/jquery-3.6.0.min.js","content/jquerycontent.js","content/syringe.js"],  
"matches": ["https://example.com/*"]
}],



"web_accessible_resources": [{ 
"resources": ["lib/socket-sniffer.js"],
"matches": ["<all_urls>"]
}]

2, inject "s" variable at syringe.js (not at socket-sniffer.js), the result is:

var s = document.createElement('script');
s.src = chrome.runtime.getURL('lib/socket-sniffer.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
  1. Remove appendChilds(s) from socket-sniffer.js, the result is:

   (function () {
      var OrigWebSocket = window.WebSocket;
    
      var callWebSocket = OrigWebSocket.apply.bind(OrigWebSocket);
      var wsAddListener = OrigWebSocket.prototype.addEventListener;
      wsAddListener = wsAddListener.call.bind(wsAddListener);
      window.WebSocket = function WebSocket(url, protocols) {
        var ws;
        if (!(this instanceof WebSocket)) {
          // Called without 'new' (browsers will throw an error).
          ws = callWebSocket(this, arguments);
        } else if (arguments.length === 1) {
          ws = new OrigWebSocket(url);
        } else if (arguments.length >= 2) {
          ws = new OrigWebSocket(url, protocols);
        } else { // No arguments (browsers will throw an error)
          ws = new OrigWebSocket();
        }
    
        wsAddListener(ws, 'message', function (event) {
          console.log("Received:", event);
        });
        return ws;
      }.bind();
      window.WebSocket.prototype = OrigWebSocket.prototype;
      window.WebSocket.prototype.constructor = window.WebSocket;
    
      var wsSend = OrigWebSocket.prototype.send;
      wsSend = wsSend.apply.bind(wsSend);
      OrigWebSocket.prototype.send = function (data) {
        //console.log("Sent:", data);
        return wsSend(this, arguments);
      };
    })();
console.log("nothing to show");

Upvotes: 0

trexinf14s
trexinf14s

Reputation: 291

The only way to listen to WebSocket communication is to inject a WebSocket wrapper into the site's code and have it relay the messages to you. You're code should look something like this:

manifest.json

{
  ...
  "content_scripts": [
    {
      "matches": [
        "<website-url>",    
      ],
      "js": ["content/syringe.js"]
    }
  ],
  "web_accessible_resources": ["lib/socket-sniffer.js"]
}

content/syringe.js

var s = document.createElement('script');
s.src = browser.runtime.getURL('lib/socket-sniffer.js');
s.onload = function() {
    this.remove();
};

lib/socket-sniffer.js

(function() {
  var OrigWebSocket = window.WebSocket;
  var callWebSocket = OrigWebSocket.apply.bind(OrigWebSocket);
  var wsAddListener = OrigWebSocket.prototype.addEventListener;
  wsAddListener = wsAddListener.call.bind(wsAddListener);
  window.WebSocket = function WebSocket(url, protocols) {
    var ws;
    if (!(this instanceof WebSocket)) {
      // Called without 'new' (browsers will throw an error).
      ws = callWebSocket(this, arguments);
    } else if (arguments.length === 1) {
      ws = new OrigWebSocket(url);
    } else if (arguments.length >= 2) {
      ws = new OrigWebSocket(url, protocols);
    } else { // No arguments (browsers will throw an error)
      ws = new OrigWebSocket();
    }

    wsAddListener(ws, 'message', function(event) {
      console.log("Received:", event);
    });
    return ws;
  }.bind();
  window.WebSocket.prototype = OrigWebSocket.prototype;
  window.WebSocket.prototype.constructor = window.WebSocket;

  var wsSend = OrigWebSocket.prototype.send;
  wsSend = wsSend.apply.bind(wsSend);
  OrigWebSocket.prototype.send = function(data) {
    console.log("Sent:", data);
    return wsSend(this, arguments);
  };
})();

(document.head || document.documentElement).appendChild(s);

All credit for the above code obviously goes to the posters linked above. I have only copied tt here for convenience' sake.

Update:

Yes, it does matter that the WebSocket is within an iframe! By default, extensions are only loaded in the top-most frame. For it to load into the iframe, you must add "all_frames": true to your manifest.json:

manifest.json

{
  ...
  "content_scripts": [
    {
      "matches": [
        "<website-url>",    
      ],
      "all_frames": true,      
      "js": ["content/syringe.js"]
    }
  ],
  "web_accessible_resources": ["lib/socket-sniffer.js"]
}

If you'd like to read more, here's the documentation for all_frames.

Upvotes: 7

Related Questions