Implementing WiFi multiplayer on Cordova-based games - Part 2

Last time we have seen that a great solution for implementing local multiplayer on WiFi networks is to use Zeroconf and WebSockets. In this article we will see how to use these technologies, and some implications that are not obvious at first.

Content Security Policy

Let's start by enabling WebSockets on our Content Security Policy in the index.html file, by adding the string ws:, so that it now looks like this:

<meta http-equiv="Content-Security-Policy" content="default-src 'self' ws: data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">

Doing this will enable WebSocket connections to any host on any port, which can seem like a huge security hole, but in reality it's much less scary than it seems, because a WebSocket connection has to be initiated by JavaScript, so if the Content Security Policy for JavaScript is strict enough, there are no new security holes introduced (for the paranoid: in theory we could have just enabled WebSockets for all the hosts in the local network, with a string like ws://*.local:*, but as we will see later, this will not always work).

Server Setup

On the server side there is nothing special: we start the server, and advertise the service on Zeroconf once the server has been started.

var
    SERVICE_TYPE = '_my-service._tcp.',
    HOST_NAME = 'my host'
;

// Specify 0 as the port number, so that a random free port is used
wsserver.start(0, {
    onStart: function (addr, port) {
        // We can safely ignore the addr parameter, as it is always bound to any address (0.0.0.0)
        // The port parameter is the actual port that needs to be advertised on Zeroconf

        zeroconf.register(SERVICE_TYPE, 'local.', HOST_NAME, port, {
            // For the moment we leave the TXT record empty
        }, function (result) {
            // Here we have successfully advertised the service
        });
    },
    // Other server stuff...
});

Client Connection

The theory here is that the client watches for the service that the server has registered, and then it creates a WebSocket that connects to ws://hostname:port/ like this:

var connection;

zeroconf.watch(SERVICE_TYPE, 'local.', function (result) {
    var
        service = result.service,
        url
    ;

    if (result.action === 'added') {
        // Remove any trailing dots from the hostname
        url = ['ws://', service.hostname.replace(/[.]+$/g, ''), ':', service.port, '/'].join('');
        connection = new WebSocket(url);
        connection.onopen = function() {
            // Now the client is connected
        }
    }
});

And it works...

... on iOS, but when you try to connect from an Android device, you will see that the connection will always fail.

This is due to an Android bug dating back to 2010 that has not been fixed yet.

Long story short, Android cannot resolve local host names, so we need to find a different solution in order to connect from Android (and yes, you guessed it, the solution is to use directly the IP address of the server, but doing it right is easier said than done).

In the next blog post we will see what are the problems related to directly using IP addresses instead of host names, and a possible approach for doing it right, so stay tuned.

Comments !