36

On one of my scripts I have this code:

var webSocket = window.WebSocket || window.MozWebSocket;
window.ws = new webSocket('ws://64.121.210.140:2585/consoleappsample', 'my-protocol');

Which works fine. However, when the user changes pages, I have to re-establish the connection. I believe this is causing problems in my code because if the client sends data to the server and then changes pages, the data may not be received and race conditions are occurring.

I tried to put the window.ws in global scope but it didn't seem to fix the problem. Is there any way for the WebSockets connection to persist between pages so the connection does not need to be constantly reestablished?

Ryan Peschel
  • 11,087
  • 19
  • 74
  • 136

4 Answers4

31

The global scope you mentioned is always related to the JavaScript Context, and a Context is created for each windows (and destroyed when the document is unloaded from the memory). Therefore, your effort are useless: you can't keep a connection opened if the user change page. Of course you can have your webapp as "single page" application, where all the data are loaded using XMLHttpRequest / ajax / WebSocket. So, leaving the page means leaving / shutdown the application, and makes sense close the socket.

Another old approach could be put your pages in a frame, where the user navigate only in the frame (even if it takes the whole size of the window). In that way, you can create your WebSocket in the top most window, that is never changed (that also means the URL showed in the location bar will be always the same).

Said that, I agreed with @dystroy: your application should be always able to handle this scenario - the user could have some network problem and lost the connection for a moment, even if it doesn't leave the page.

ZER0
  • 24,846
  • 5
  • 51
  • 54
  • 1
    With the frame trick, is there any way of having the location in the address bar reflect the navigated location within the frame containing the navigatable content? Automatically? With the links in the content staying plain hrefs? – oberstet Jun 04 '12 at 19:53
  • 1
    You can try to do something using the [History APIs](https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history). – ZER0 Jun 04 '12 at 19:59
  • 4
    I know this is an old reply, but if an application has users always changing page, won't this hammer the web socket server with constant disconnects and reconnects? – Alias May 28 '14 at 10:08
23

You could try creating your WebSocket connection in a Shared WebWorker which allows multiple pages from the same domain to share an execution context. However, it's unclear whether Shared Workers persist across a page reload or replace: Do Shared Web Workers persist across a single page reload, link navigation

Also, Shared WebWorkers have limited browser support (webkit and Opera) currently.

Update:

Since a single shared web worker can serve multiple pages, the implementation is slightly more complicated that normal web workers.

Here is a shared web worker example that uses WebSockets and can share between

First the HTML:

<!DOCTYPE html>
<html>
<body>
<script>
    var worker = new SharedWorker("shared.js");
    worker.port.addEventListener("message", function(e) {
        console.log("Got message: " + e.data);
    }, false);
    worker.port.start();
    worker.port.postMessage("start");
</script>
</body>
</html>

The Javascript that implements the shared worker in shared.js:

var ws = null
var url = "ws://" + location.hostname + ":6080"
self.addEventListener("connect", function(e) {
    var port = e.ports[0]
    port.addEventListener("message", function(e) {
        if (e.data === "start") {
            if (ws === null) {
                ws = new WebSocket(url);
                port.postMessage("started connection to " + url);
            } else {
                port.postMessage("reusing connection to " + url);
            }
        }
    }, false);
    port.start();
}, false);

I have verified that this works in Chrome 52.

Community
  • 1
  • 1
kanaka
  • 70,845
  • 23
  • 144
  • 140
  • I have a doubt. There is no websocket support in webworkers in that case how can we maintain context of app. – kongaraju Aug 06 '12 at 10:26
  • @kongaraju, you can use WebSockets via Web Workers in Chrome and there is a Mozilla bug to implement support for this in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=504553 – kanaka Aug 07 '12 at 21:28
  • When i try to access websockets via webworkers in chrome it returns DOM exception. can you specify an example. – kongaraju Aug 08 '12 at 05:26
  • 1
    @kongaraju, I just added an example and I have verified that it works in Chrome 20 and does in fact make a WebSocket connection to the server from the the Shared WebWorker. – kanaka Aug 10 '12 at 15:27
  • 2
    Good suggestion, unfortunately not useful for me. It's lacking support across browsers http://caniuse.com/#feat=sharedworkers. – Anthony De Smet Mar 18 '16 at 12:23
  • As I understand, after page refresh - SharedWorker recreate anew one, and called new WebSocket() so old connection will be closed and opened anew one? – SiJey Sep 07 '16 at 15:09
  • @ShkarbatovDmitriy if you only have one page open to the domain, then yes, a refresh will probably kill your SharedWorker. If you have multiple pages open to the same domain, refreshing one shouldn't kill the SharedWorker. ServiceWorkers might be a viable option for persistent connections even where there are no pages open for that domain (i.e. during momentary refresh of a single page). – kanaka Sep 07 '16 at 17:11
  • Unfortunately no. I add console.log('webSocket start') after starting WebSocket in SharedWorker - and it always show 'webSocket start' in every open page and in each page refresh. Maby any other ideas? – SiJey Sep 08 '16 at 10:33
  • 1
    @ShkarbatovDmitriy I just tried it with Chrome 52 and it's working as expected (refresh of single page recreates, refresh one of two pages results in re-use). Note that you will need to modify my example to store the websocket connection state outside the connect handler. Also, your page and websocket target will probably need to be on something other than localhost. – kanaka Sep 08 '16 at 20:40
  • Can you please show me your example, how you stored websocket connection state outside the connect handler? Thanks! – SiJey Sep 09 '16 at 04:48
  • @ShkarbatovDmitriy I updated the shared.js to the version that I tested. Note that the way it is written you need a websocket server running at the same origin/host but on port 6080. – kanaka Sep 09 '16 at 14:48
  • Thanks, will check. – SiJey Sep 09 '16 at 15:35
  • It helps, but I don't understand, isn't variable ws will not assign again with null when SheredWorker rerunning? – SiJey Sep 12 '16 at 14:14
  • @ShkarbatovDmitriy ws will only be set to new when the shared worker is started fresh which only occurs if there is no window/tab that has the page actively loaded. Otherwise, the existing shared worker will simple receive a connect event and thus re-use the existing websocket connection (rather than create a new one). – kanaka Sep 13 '16 at 05:21
  • Yes, I understend, Thanks a lot for Your help! Will check WebSocket in ServiceWorker. As I understand ServiceWorker not destroy themself when I reload the page. – SiJey Sep 13 '16 at 15:08
  • Can you give me a sample example to work single WebSocket with multiple pages? – Pooja K Bhatt Oct 13 '20 at 09:06
  • It doesn't persist between page loads – Steve Moretz Oct 20 '21 at 15:02
1

Unfortunately, the cleanest solution without changing your site in a SPA application is obtained using just one ServiceWorker because it does the job in the background (also with tab closed) and it solve also problems of multiple tabs. I said "unfortunately" because they are still not compatible with most of browsers.

The only solution I've found by myself consists of grouping socket channels on the server side, and create a queue in order to hold the messages lost by changing page. In this case, you have kind of virtual channels.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91
Marcello Kad
  • 186
  • 1
  • 7
-5

You could log the conversation with the websocket server in localstorage, which persists across page loads.