Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: How to know if a connection with a shared worker is still alive?

I'm trying to use a shared worker to maintain a list of all the windows/tabs of a web application. Therefore following code is used:

//lives in shared-worker.js var connections=[];//this represents the list of all windows/tabs onconnect=function(e){   connections.push(e.ports[0]); }; 

Everytime a window is created a connection is established with the shared-worker.js worker and the worker adds the connection with the window to the connections list.

When a user closes a window its connection with the shared worker expires and should be removed from the connections variable. But I don't find any reliable way to do that.

Looking at the specification the objects of the connections variable doesn't seem to hold a property/function to check if the connection is still alive.

Is it possible?
Again, the overall goal is to have the list of all windows/tabs.

EDIT: An approach would be to make the shared worker message the windows and expect a reply. If the shared worker doesn't receive a reply then it would assume that the window is closed. In my experiments this approach has not shown to be reliable; the problem being that there is no way to tell if a window is closed or is just taking a long time to reply.

like image 451
brillout Avatar asked Dec 01 '12 17:12

brillout


People also ask

How do I know if Webworker is running?

Show activity on this post. You should have the web worker post messages about events, like when it is finished work, this way the parent can listen to these messages/events and know when work has completed. The web worker can even post progress events, this is all up to you to build though, it does not come included.

How do I debug a shared worker?

For shared worker, you would need to go to chrome://inspect/#workers. Select "Shared workers" on the left panel. You would be able to see a list of shared workers if you have any running. You can click "inspect", which will open a new console for you to debug.

What is a shared worker JavaScript?

The SharedWorker interface represents a specific kind of worker that can be accessed from several browsing contexts, such as several windows, iframes or even workers. They implement an interface different than dedicated workers and have a different global scope, SharedWorkerGlobalScope .

What is a SharedWorker?

Shared workers are special web workers that can be accessed by multiple browser contexts like browser tabs, windows, iframes, or other workers, etc. They're different from dedicated workers in that they are instances of SharedWorkers and have a different global scope.


2 Answers

This is only as reliable as beforeunload, but seems to work (tested in Firefox and Chrome). I definitely favour it over a polling solution.

// Tell the SharedWorker we're closing addEventListener( 'beforeunload', function() {     port.postMessage( {command:'closing'} ); }); 

Then handle the cleanup of the port object in the SharedWorker.

e.ports[0].onmessage = function( e ) {     const port = this,     data = e.data;      switch( data.command )     {         // Tab closed, remove port         case 'closing': myConnections.splice( myConnections.indexOf( port ), 1 );             break;     } } 
like image 142
Adria Avatar answered Sep 28 '22 02:09

Adria


I have been neck deep in the documentation all week working around the same problem.

The problem is the MessagePort specification. The bad news being that it has no error handling, and no flag, method or event to determine whether it has been closed.

The good news is I have created a viable solution, but it's a lot of code.

Keep in mind even among the supporting browsers the activity is handled differently. For example Opera will throw an error if you attempt to message or close a closed port. The bad news is you have to use a try-catch to handle the error, the good news is you can use that feedback to close a port on at least one-side.

Chrome and Safari fail silently leaving you no feedback and no way to end invalid objects.


My solution involves delivery confirmation or a custom "callback" approach. You use a setTimeout and pass the ID for it to the SharedWorker with your command, and before processing the command it sends back a confirmation to cancel the timeout. That timeout is generally hooked to a closeConnection() method.

This takes a reactive approach instead of a pre-emptive, originally I toyed with using the TCP/IP protocol model but that involved creating more functions to handle each process.


Some Psuedo-Code as an example:

Client/Tab Code:

function customClose() {     try {         worker.port.close();     } catch (err) { /* For Opera */ } } function send() {     try {         worker.port.postMessage({command: "doSomething", content: "some Data", id: setTimeout(function() { customClose(); ); }, 1000);     } catch (err) { /* For Opera */ } } 

Thread/Worker Code:

function respond(p, d) {     p.postMessage({ command: "confirmation", id: d.id }); } function message(e) {// Attached to all ports onmessage     if (e.data.id) respond(this, e.data);     if (e.data.command) e.data.command(p, e.data);// Execute command if it exists passing context and content } 

I have placed a complete demonstration here: http://www.cdelorme.com/SharedWorker/

I am new to stack overflow, so I am not familiar with how they handle large code posts, but my full solution is two 150 line files.


Just using delivery confirmation alone is not perfect, so I have worked at improving it by adding additional components.

In particular I was investigating this for a ChatBox system, so I wanted to use EventSource (SSE), XHR, and WebSockets, only XHR is supported inside SharedWorker objects supposedly, which creates a limitation if I wanted to have the SharedWorker do all the server communication.

Plus since it needs to work for browsers without SharedWorker support I would be creating long-hand duplicate processing inside the SharedWorker which doesn't make a lot of sense.

So in the end if I implement SharedWorker it would be as a communication channel for the open tabs only, and one tab will be the Control Tab.

If the control tab is closed, the SharedWorker won't know, so I added a setInterval to the SharedWorker to send an empty response request every few seconds to all open ports. This allows Chrome and Safari to eliminate closed connections when no messages are being processed, and allows the control tab to change.

However, this also means if the SharedWorker process dies the tabs must have an interval to check in with the SharedWorker using the same approach every so often, allowing them to use the fallback approach of every-tab-for-themeselves that is inherent to all other browsers using the same code.


So, as you can see a combination of callbacks for delivery confirmation, setTimeout and setInterval must be used from both ends to maintain knowledge of connectivity. It can be done but it's a giant pain in the rear.

like image 36
CDeLorme Avatar answered Sep 28 '22 01:09

CDeLorme