6

I have a server-sent event (SSE) implementation that is working with almost no issues. The only issue that I am having is "one user can have many connections to the server". Basically, If a user opens more than one web browser's tab, each tab will create a brand new server-sent event request to the server which cause many requests to run from a single user.

To solve this problem, I would like to run the SSE inside a Javascript's SharedWorker.

This means that I have only one SSE communicating with a SharedWorker. Then, every page/web browser will communication with the SharedWorker. This gives me the advantage of only allowing one SSE per user.

This is how my SSE working currently without any type of worker.

$(function(){
    //connect to the server to read messages
    $(window).load(function(){
        startPolling( new EventSource("poll.php") );
    });

    //function to listen for new messages from the server
    function startPolling(evtSource){

        evtSource.addEventListener("getMessagingQueue", function(e) {
            var data = JSON.parse(e.data);
            //handle recieved messages
            processServerData(data);

        }, false);

        evtSource.onerror = function(e) {
            evtSource.close();
        };

    }
});

I would like to have the same setup running. However, I would like to run it inside a javascript's SharedWorker to eliminate having more than one SSE per user.

I am struggling to implement the SharedWorker. Here is what I tried so far

I created a file called worker.js and added this code to it

var ports = [] ;

onconnect = function(event) {

    var port = event.ports[0];
    ports.push(port);
    port.start();

    var serv = new EventSource(icwsPollingUrl) 
    serv.addEventListener("getMessagingQueue", function(e) {
        var data = JSON.parse(e.data);
        processServerData(data);

    }, false);

}

Then on the page where I want to listed to messages I have this code

$(function(){

    $(window).load(function(){

        var worker = new SharedWorker("worker.js");         


         worker.port.start();
         worker.port.onmessage = function(e) {
            console.log(e.data);
            console.log('Message received from worker');
         }

    });
});

What am I missing here?

What am I doing wrong?

How can I correct the implementation?

EDITED

Based on the comments below from @Bergi, here is an updated version of my implementation which is still not posting messages to the connectors. I added comments to my code explaining understanding of what is going on with the code.

On a landing page i.e. index.php I connect to my SharedWorker like this

$(function($){

    //establish connection to the shared worker
    var worker = new SharedWorker("/add-ons/icws/js/worker1.js");
        //listen for a message send from the worker
        worker.port.addEventListener("message",
                function(event) {
                    console.log(event.data);
                }
                , false
        );
        //start the connection to the shared worker
        worker.port.start();

 });

This is the code as my worker1.js file contains

var ports = [] ;

//runs only when a new connection starts
onconnect = function(event) {

    var port = event.ports[0];
    ports.push(port);
    port.start();

    //implement a channel for a communication between the connecter and the SharedWorker
    port.addEventListener("message",
        function(event) { 
            listenForMessage(event, port);
        }
    );
}

//reply to any message sent to the SharedWorker with the same message but add the phrase "SharedWorker Said: " to it
listenForMessage = function (event, port) {
    port.postMessage("SharedWorker Said: " + event.data);

}

//runs every time and post the message to all the connected ports
function readNewMessages(){
    var serv = new EventSource(icwsPollingUrl) 
        serv.addEventListener("getMessagingQueue", function(e) {
        var queue = JSON.parse(e.data);

        notifyAllPorts(queue);

    }, false);
}

//check all open ports and post a message to each
function notifyAllPorts(msg){
    for(i = 0; i < ports.length; i++) {
        ports[i].postMessage(msg);
    }
}

Here is one more version of my worker1.js

var ports = [] ;

//runs only when a new connection starts
onconnect = function(event) {

    var port = event.ports[0];
    ports.push(port);
    port.start();

    //implement a channel for a communication between the connecter and the SharedWorker
    port.addEventListener("message",
        function(event) { 
            listenForMessage(event, port);
        }
    );
}

//reply to any message sent to the SharedWorker with the same message but add the phrase "SharedWorker Said: " to it
listenForMessage = function (event, port) {
    port.postMessage("SharedWorker Said: " + event.data);

}

readNewMessages();


//runs every time and post the message to all the connected ports
function readNewMessages(){
    console.log('Start Reading...');
    var serv = new EventSource(icwsPollingUrl);
        serv.addEventListener("getMessagingQueue", function(e) {
        var queue = JSON.parse(e.data);
        console.log('Message Received');
        console.log(queue);

        notifyAllPorts(queue);

    }, false);
}

//check all open ports and post a message to each
function notifyAllPorts(msg){
    for(i = 0; i < ports.length; i++) {
        ports[i].postMessage(msg);
    }
}
Junior
  • 11,602
  • 27
  • 106
  • 212
  • 2
    I really wouldn't call this `…Polling` - SSE are a *push* technology :-) – Bergi Aug 30 '15 at 21:44
  • So what exactly is your problem? What does not work, are there any errors? What is `processServerData`? Do you ever post a message from the worker to the conncted page(s)? – Bergi Aug 30 '15 at 21:47
  • Well, you really shouldn't create a `new EventSource(icwsPollingUrl)` in the `onconnect` handler of your worker, this just does create one eventsource per connecting web page again. – Bergi Aug 30 '15 at 21:48
  • @Bergi where would I create the connection then? The `processServerData` functions reads the message send by SSE and it process it "display different messages to the screen" I am trying to implement a push technology where one message is read and pushed to any open browser – Junior Aug 30 '15 at 22:11
  • `onconnect` is not called when your worker does connect to your push service, but when a web page does connect to your worker. You will want to create the `new EventSource` only once right in (the startup of) the worker itself. – Bergi Aug 31 '15 at 10:38
  • @Bergi I updated the code based on your comments. However, no messages are being sent to the ports aka scripts. I updated my question with the new implementation to make sure you have the latest code. Please advise. – Junior Aug 31 '15 at 15:54
  • Which message do you expect to be sent from where? Neither do you call `readNewMessages()` in the initialisation of your worker, nor do your pages send any events that the worker would then echo. – Bergi Aug 31 '15 at 15:59
  • @Bergi the function `readNewMessages()` should start connection to the php script to get net messages. Then it should take each message, convert it from json to object and finally posts that object to the scrips. I thought I am suppose to call this function outside the `onconnect` method so it is not executed every-time a script connects to the SharedWorker. – Junior Aug 31 '15 at 16:05
  • Yes, you are supposed to do exactly that. But I don't see that call to the function in the code you posted? – Bergi Aug 31 '15 at 16:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88391/discussion-between-mike-a-and-bergi). – Junior Aug 31 '15 at 16:09

1 Answers1

3

maybe is late but you can create a EventSource singleton in the worker like this:

let ports = [];
var EventSourceSingleton = (function () {
    var instance;

    function createInstance() {
        var object = new EventSource('your path');
        return object;
    }

    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

onconnect = function(e) {
    var port = e.ports[0];
    ports.push(port);
    var notifyAll = function(message){
        ports.forEach(port => port.postMessage(message));
    }

    var makeConnection = function (){
        var source = EventSourceSingleton.getInstance();

        source.onopen = function (e){
            var message = "Connection open"
            port.postMessage(message);
        }
        source.onerror = function(e){
            var message ="Ups you have an error";
            port.postMessage(message);
        }
        source.onmessage = function(e){
            // var message = JSON.parse(event.data);
            notifyAll(e.data);
        }
    }

    port.onmessage = function(e) {
        makeConnection();
    }
    port.start();
  }

And you can call it from outside like this.

            var shWorker = new SharedWorker('woker.js');

            shWorker.port.onmessage = function (e) {
                console.log('Message received from worker');
                setmsj(e.data);
            }
            //Dummy message - For initialize
            shWorker.port.postMessage(true);

Have fun debuggin this on // chrome://inspect/#workers.

  • So IIUC the reason you need a singleton is because else each call to `new SharedWorker("worker.js")` would create new instance... but I don't understand why they'd choose to have it behave like that instead of running it once and then just return a handle to the worker object or something. – cassepipe May 16 '23 at 14:04