Search code examples
javascriptfirefoxwebsocketfirefox-addonkodi

Firefox add-on drops WebSocket connection to Kodi after a few seconds


I have decided to develop an add-on for Firefox to control Kodi because I couldn't find any that already did everything I wanted. I have chosen to use WebSocket to communicate with Kodi so that I can receive events, but am having problems implementing the WebSocket portion of my add-on. Temporarily, I have created an add-on with a page-worker solely for testing the WebSocket functionality. Both my development add-on and the temporary testing add-on exhibit the following behavior:

If I load test.html (see bottom of post for contents) directly in Firefox outside of the add-on, WebSocket works as expected. The connection stays open and receives events as I play or pause Kodi:

Console output from running test.html outside of the add-on

Connection opened test.html:12:9
Object { jsonrpc: "2.0", method: "Player.OnPlay", params: Object } test.html:28:9
Object { jsonrpc: "2.0", method: "Player.OnPause", params: Object } test.html:28:9
Object { jsonrpc: "2.0", method: "Player.OnPlay", params: Object } test.html:28:9
Object { jsonrpc: "2.0", method: "Player.OnPause", params: Object } test.html:28:9
Object { jsonrpc: "2.0", method: "Player.OnPlay", params: Object } test.html:28:9
Object { jsonrpc: "2.0", method: "Player.OnPause", params: Object } test.html:28:9
Object { jsonrpc: "2.0", method: "GUI.OnScreensaverActivated", params: Object } test.html:28:9

However, this is not the case when running the add-on from the command line using jpm run. The connection closes after a few seconds (four seconds max). (Note that I can receive events just fine during the time the connection is open):

Console output from running the add-on using jpm

C:\Users\User\Desktop\example>jpm run
JPM [info] Starting jpm run on test
JPM [info] Creating a new profile
console.log: test: Connection opened
console.log: test: {"jsonrpc":"2.0","method":"Player.OnPlay","params":{"data":{"item":{"id":23,"type":"song"},"player":{"playerid":0,"speed":1}},"sender":"xbmc"}}
console.log: test: {"jsonrpc":"2.0","method":"Player.OnPause","params":{"data":{"item":{"id":23,"type":"song"},"player":{"playerid":0,"speed":0}},"sender":"xbmc"}}
console.log: test: Websocket error: undefined
console.log: test: Connection closed: 1006

How can I find out why the connection drops inside the add-on, but not outside of the add-on? How can I remedy this strange behavior?


index.js:

var data = require("sdk/self").data;
var pageWorker = require("sdk/page-worker");

var pw = pageWorker.Page({
  contentURL: data.url('test.html')
});

pw.port.on('message', function(message) {
  console.log(message);
});

test.html:

<!DOCTYPE html>
<html lang="en-US">
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <script type=text/javascript>
    var ws = new WebSocket('ws://192.168.2.34:9090/jsonrpc');

    ws.onopen = function (event) {
      try {
        addon.port.emit('message', "Connection opened");
      } catch (e) {
        console.log("Connection opened");
      }
    };

    ws.onerror = function (event) {
      try {
        addon.port.emit('message', "Websocket error: "+event.data);
      } catch (e) {
        console.log("Websocket error: "+event.data);
      }
    };

    ws.onmessage = function (event) {
      try {
        addon.port.emit('message', JSON.parse(event.data));
      } catch (e) {
        console.log(JSON.parse(event.data));
      }
    };

    ws.onclose = function (event) {
      try {
        addon.port.emit('message', "Connection closed: "+event.code);
      } catch (e) {
        console.log("Connection closed: "+event.code);
      }
    };
    </script>
  </head>
  <body>
  </body>
</html>

Solution

  • I installed Wireshark and discovered that a Websocket ping request was being sent when running inside the add-on, but not outside of the add-on. Kodi fails to respond to the ping with a pong and therefore Firefox closes the connection.

    Interestingly enough, delayed and subsequent connections from within the add-on do not appear to have a ping request generated and therefore do not get closed. So, the solution is to simply delay or re-open the connection upon it's closing (this is good to do anyway to handle loss of network connectivity).

    test.html

    <!DOCTYPE html>
    <html lang="en-US">
      <head>
      <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <script type=text/javascript>
    
        //connect(); //generates a ping request
        setTimeout(connect, 10000); //doesn't generate a ping request
        
        function connect() {
          this.ws = new WebSocket('ws://192.168.2.34:9090/jsonrpc');
    
          this.ws.onopen = function (event) {
            try {
              addon.port.emit('message', "Connection opened");
            } catch (e) {
              console.log("Connection opened");
            }
          };
          
          this.ws.onerror = function (event) {
            try {
              addon.port.emit('message', "Websocket error: "+event.data);
            } catch (e) {
              console.log("Websocket error: "+event.data);
            }
          };
          
          this.ws.onmessage = function (event) {
            try {
              addon.port.emit('message', JSON.parse(event.data));
            } catch (e) {
              console.log(JSON.parse(event.data));
            }
          };
          
          this.ws.onclose = function (event) {
            try {
              addon.port.emit('message', "Connection closed: "+event.code);
            } catch (e) {
              console.log("Connection closed: "+event.code);
            }
            setTimeout(connect, 5000); //re-opens connection, doesn't generate a ping request
          };
        }
        </script>
      </head>
      <body>
      </body>
    </html>