Over the last couple of weeks I've been working on an application in iFlexRTSStudio aimed at providing live analytics for Wordpress sites, essentially by creating a Websocket connection to a Crossbar server and maintaining it over the lifetime of the user's browser session. From that I can grab stats, but also a live listing of who's on the site, which has all sorts of future potential.

The problem is and has always been, maintaining a websocket connection to Wordpress while the user switches between pages on the site. Here are the options I've looked at;

  • Shared Web Service workers
  • Run the site in an iFrame with the websocket in the parent
  • Convert Wordpress into an SPA (!)

The first is a bit of a non-starter as shared web-workers are relatively new and only covered by three browsers. Can I use reckons this currently covers less than half of potential UK users.

The second, well, I've been trying but there are just too many potential gotcha's with plugins and core functionality just not getting on with having to run inside an iFrame. Good effort, but it just wasn't going to fly.

So, what else? Well, just the obvious, turn Wordpress into a Single Page Application and kick off a single websocket connection when they bring up the first page.

Now, if you look at Wordpress structurally, this is basically a complete rewrite and hence a non-starter. However, on reflection, to convert any standard website into an SPA, technically all we need to do is trap anything that might lead to a page switch and cause it to load some text into the pre-existing document instead. If the websocket exists in the global scope but outside of the DOM, or indeed in a standard web worker, which does have good browser support, we're in business.

Turns out it's not that hard (!) , there are ways to break this from the Wordpress perspective, and it doesn't yet catch a forced reload, however the following JS script can be added via plugin code with;

function inject_fix_links() {
    wp_enqueue_script(
        'fix_links',
        plugins_url('/js/fix_links.js', __FILE__),
        array('jquery')
    );
};
add_action('wp_enqueue_scripts', inject_fix_links);
add_action('admin_enqueue_scripts', inject_fix_links);

Updated: this version of fix_links does handler external links properly ...

//
//  fix_links.js
//  (c) Gareth Bult, iFlexRTS Ltd
//  Simple code mangler, replace links with code to load new page into
//  current document, assuming the link is another page on same site.
//
jQuery( document ).ready(function($) {
    var filter = new RegExp('/' + window.location.host + '/');
    var func = 'function(data){'+
                'handle = document.open("text/html","replace");'+
                'handle.write(data); handle.close();'+
               '}';                
    var mangle = function(self, tag, handler) {
        var href = $(self).attr(tag), fn;
        if(!href||!filter.test(href)) return;
        switch(tag) {
            case 'action':
                fn='post("'+href+'",jQuery(this).serialize(),'+func+')';
                break;
            case 'href':
                fn='get("'+href+'",'+func+')';
                break;
        }
        $(self).attr(tag, 'javascript:void(0);');
        $(self).attr(handler, 'javascript:jQuery.'+fn);
    }
    $('a').each(function() { mangle(this, 'href', 'onclick') });
    $('form').each(function() { mangle(this, 'action', 'onsubmit') });
});

Doesn't look like much, but that just turned pretty much any standard wordpress site into a Single Page Application, so when you click from page to page, you never leave the first document, hence anything global (like an Autobahn Connection) will be persistent.

It sounds like it shouldn't work .. but thus far it seems to, this is what the console looks like when I switch between pages, so jQuery is correctly re-initialising on page switches, but we're still retaining global scope.

I need to do a little work to make the browser URL mirror the actual page, and I need to test for external links (Ooops!) and probably things like action modifiers on button submits, but it's all relatively trivial stuff. Moving forward, it should be able to mark things like jQuery as persistent which should speed up page rendering, just as an added bonus. The real value here is for online services that need fixed connections like chat applications, external news feeds, real-time feedback, well, pretty much anything that's common to the site rather than page specific. And (!) unlike the iFrame solution, it doesn't require any special handling to keep search engines happy ... if the Javascript isn't loaded in a browser, it just yields normal Wordpress .. :)