Related
I'm playing with the service worker API in my computer so I can grasp how can I benefit from it in my real world apps.
I came across a weird situation where I registered a service worker which intercepts fetch event so it can check its cache for requested content before sending a request to the origin.
The problem is that this code has an error which prevented the function from making the request, so my page is left blank; nothing happens.
As the service worker has been registered, the second time I load the page it intercepts the very first request (the one which loads the HTML). Because I have this bug, that fetch event fails, it never requests the HTML and all I see its a blank page.
In this situation, the only way I know to remove the bad service worker script is through chrome://serviceworker-internals/ console.
If this error gets to a live website, which is the best way to solve it?
Thanks!
I wanted to expand on some of the other answers here, and approach this from the point of view of "what strategies can I use when rolling out a service worker to production to ensure that I can make any needed changes"? Those changes might include fixing any minor bugs that you discover in production, or it might (but hopefully doesn't) include neutralizing the service worker due to an insurmountable bug—a so called "kill switch".
For the purposes of this answer, let's assume you call
navigator.serviceWorker.register('service-worker.js');
on your pages, meaning your service worker JavaScript resource is service-worker.js. (See below if you're not sure the exact service worker URL that was used—perhaps because you added a hash or versioning info to the service worker script.)
The question boils down to how you go about resolving the initial issue in your service-worker.js code. If it's a small bug fix, then you can obviously just make the change and redeploy your service-worker.js to your hosting environment. If there's no obvious bug fix, and you don't want to leave your users running the buggy service worker code while you take the time to work out a solution, it's a good idea to keep a simple, no-op service-worker.js handy, like the following:
// A simple, no-op service worker that takes immediate control.
self.addEventListener('install', () => {
// Skip over the "waiting" lifecycle state, to ensure that our
// new service worker is activated immediately, even if there's
// another tab open controlled by our older service worker code.
self.skipWaiting();
});
/*
self.addEventListener('activate', () => {
// Optional: Get a list of all the current open windows/tabs under
// our service worker's control, and force them to reload.
// This can "unbreak" any open windows/tabs as soon as the new
// service worker activates, rather than users having to manually reload.
self.clients.matchAll({type: 'window'}).then(windowClients => {
windowClients.forEach(windowClient => {
windowClient.navigate(windowClient.url);
});
});
});
*/
That should be all your no-op service-worker.js needs to contain. Because there's no fetch handler registered, all navigation and resource requests from controlled pages will end up going directly against the network, effectively giving you the same behavior you'd get without if there were no service worker at all.
Additional steps
It's possible to go further, and forcibly delete everything stored using the Cache Storage API, or to explicitly unregister the service worker entirely. For most common cases, that's probably going to be overkill, and following the above recommendations should be sufficient to get you in a state where your current users get the expected behavior, and you're ready to redeploy updates once you've fixed your bugs. There is some degree of overhead involved with starting up even a no-op service worker, so you can go the route of unregistering the service worker if you have no plans to redeploy meaningful service worker code.
If you're already in a situation in which you're serving service-worker.js with HTTP caching directives giving it a lifetime that's longer than your users can wait for, keep in mind that a Shift + Reload on desktop browsers will force the page to reload outside of service worker control. Not every user will know how to do this, and it's not possible on mobile devices, though. So don't rely on Shift + Reload as a viable rollback plan.
What if you don't know the service worker URL?
The information above assumes that you know what the service worker URL is—service-worker.js, sw.js, or something else that's effectively constant. But what if you included some sort of versioning or hash information in your service worker script, like service-worker.abcd1234.js?
First of all, try to avoid this in the future—it's against best practices. But if you've already deployed a number of versioned service worker URLs already and you need to disable things for all users, regardless of which URL they might have registered, there is a way out.
Every time a browser makes a request for a service worker script, regardless of whether it's an initial registration or an update check, it will set an HTTP request header called Service-Worker:.
Assuming you have full control over your backend HTTP server, you can check incoming requests for the presence of this Service-Worker: header, and always respond with your no-op service worker script response, regardless of what the request URL is.
The specifics of configuring your web server to do this will vary from server to server.
The Clear-Site-Data: response header
A final note: some browsers will automatically clear out specific data and potentially unregister service workers when a special HTTP response header is returned as part of any response: Clear-Site-Data:.
Setting this header can be helpful when trying to recover from a bad service worker deployment, and kill-switch scenarios are included in the feature's specification as an example use case.
It's important to check the browser support story for Clear-Site-Data: before your rely solely on it as a kill-switch. As of July 2019, it's not supported in 100% of the browsers that support service workers, so at the moment, it's safest to use Clear-Site-Data: along with the techniques mentioned above if you're concerned about recovering from a faulty service worker in all browsers.
You can 'unregister' the service worker using javascript.
Here is an example:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
//returns installed service workers
if (registrations.length) {
for(let registration of registrations) {
registration.unregister();
}
}
});
}
That's a really nasty situation, that hopefully won't happen to you in production.
In that case, if you don't want to go through the developer tools of the different browsers, chrome://serviceworker-internals/ for blink based browsers, or about:serviceworkers (about:debugging#workers in the future) in Firefox, there are two things that come to my mind:
Use the serviceworker update mechanism. Your user agent will check if there is any change on the worker registered, will fetch it and will go through the activate phase again. So potentially you can change the serviceworker script, fix (purge caches, etc) any weird situation and continue working. The only downside is you will need to wait until the browser updates the worker that could be 1 day.
Add some kind of kill switch to your worker. Having a special url where you can point users to visit that can restore the status of your caches, etc.
I'm not sure if clearing your browser data will remove the worker, so that could be another option.
I haven't tested this, but there is an unregister() and an update() method on the ServiceWorkerRegistration object. you can get this from the navigator.serviceWorker.
navigator.serviceWorker.getRegistration('/').then(function(registration) {
registration.update();
});
update should then immediately check if there is a new serviceworker and if so install it. This bypasses the 24 hour waiting period and will download the serviceworker.js every time this javascript is encountered.
For live situations you need to alter the service worker at byte-level (put a comment on the first line, for instance) and it will be updated in the next 24 hours. You can emulate this with the chrome://serviceworker-internals/ in Chrome by clicking on Update button.
This should work even for situations when the service worker itself got cached as the step 9 of the update algorithm set a flag to bypass the service worker.
We had moved a site from godaddy.com to a regular WordPress install. Client (not us) had a serviceworker file (sw.js) cached into all their browsers which completely messed things up. Our site, a normal WordPress site, has no service workers.
It's like a virus, in that it's on every page, it does not come from our server and there is no way to get rid of it easily.
We made a new empty file called sw.js on the root of the server, then added the following to every page on the site.
<script>
if (navigator && navigator.serviceWorker && navigator.serviceWorker.getRegistration) {
navigator.serviceWorker.getRegistration('/').then(function(registration) {
if (registration) {
registration.update();
registration.unregister();
}
});
}
</script>
In case it helps someone else, I was trying to kill off service workers that were running in browsers that had hit a production site that used to register them.
I solved it by publishing a service-worker.js that contained just this:
self.globalThis.registration.unregister();
I have a web application where a persistent connection from the server to it's clients (browser) is needed in order push news / updates to the clients in (near) real-time. This would not be so tricky if the navigation through some elements of the website would not cause complete page refreshs.
Polling (standard way or long polling) the server for news is not an option, since it results in often unnecessary request calls (because no news are available). Moreover news can rise up randomly. Therefore with the polling strategy the server would go down...
For the websocket (bidirectional communication channel) the client and server have to accept the upgrade to websocket.
A similar problem was discussed here, but no satisfying solution was found.
Data can survive a full page refresh by storing it in cookies or other ways:
cookies
window.name (www.thomasfrank.se/sessionvars.html)
localstorage: stores the data with no expiration date. The data will not be deleted
when the browser is closed. Example: Perseverance (github.com/viseztrance/perseverance)
PersistJS: Cross Browser Client-Side Persistent Storage without cookies Storing the
Javascript object is done, by serialize / deserialize the object.
Is there something that works similar for „running“ objects like websockets?
Some possibilities I thought of, are:
An old style „solution“ would be to put the whole web application in an iFrame and add the connection to the outermost window (of the frame). This is not an option since it causes a lot of different other problems.
Since HTML5 Share Web Workers exits, but because of the limited browser support this can also not be used.
So my question is: Is there a possibility / hack how I can keep my websocket connection open also if the page is refreshed? So that I don't have to reinitialize the connection to the server?
Simple answer - best solution is to change your server part, so it can handle connection lost and recovery (And use cookies to keep "session id" or something else).
As I cannot see any requirement to achive this literally. And even more - you can loose connection not because of referesh but because of connection problems (But you can figure out which of them happened)
I found an intereseting solution on https://crossbario.com/blog/Websocket-Persistent-Connections/. It can be achieved via SharedWorker. In your page you start it via:
var worker = new SharedWorker("worker.js");
worker.port.addEventListener("message", function(e) {
// process messages
}, false);
worker.port.start();
worker.port.postMessage("myMessageContent");
and your worker.js part looks like this:
self.addEventListener("connect", function (e) {
var port = e.ports[0];
port.start();
port.addEventListener("message", function (e) {
port.postMessage("response");
}, false);
}, false);
The full solution can be found on https://github.com/goeddea/scratchbox/tree/master/test_cases/shared_webworkers
Unfortunately according to https://caniuse.com/sharedworkers - SharedWorker works only in desktop versions of Chrome, Edge, Firefox and Opera.
Is these an API symmetric to Server-Sent Event to generate fire-and-forget events from browser to server? I know how to not reply to a request on the server side, but how to tell the browser that it does not need to wait for a reply?
The goal here is to save resources on the client side, say you want to send 10k events to the server as fast as possible, not caring about what the sever replies.
Edit: While mostly irrelevant to the question, here is some background about the project I'm working on which would make use of an "AJAX fire-and-forget". I want to build a JavaScript networking library for Scala.js that will have as one of its applications to be the transport layer between Akka actors on the JVM and on a browser (compiled with Scala.js). When WebSockets are not available I want to have some sort of fallback, and having a pending connection for the duration of a round trip on each JS->JVM message is not acceptable.
As you have asked for "how to tell the browser that it does not need to wait for a reply?"
I assume that you do not want to process the server reply.
in such case, it is better to utilize one pixel image response trick which is implemented by Google for analytics and tracking, and many other such services.
More details here
The trick is to create new image using javascript and set src property, the browser will immediately fire the request for image and browser can parallelly request form multiple such requests.
var image = new Image();
image.src = "your-script.php?id=123&other_params=also";
PROs:
easy to implement
less load on server/client, then ajax request
CONs:
you can send only GET requests using this appproach.
Edit
For more references:
http://help.yahoo.com/l/us/yahoo/ywa/faqs/tracking/advtrack/3520294.html
https://support.google.com/dfp_premium/answer/1347585?hl=en
How to create and implement a pixel tracking code
Again they are using same technique of pixel image.
So, just to be clear, you're trying to use the XMLHttpRequest as a proxy for your network communication, which means you are 100% at the mercy of whatever XMLHttpRequest offers you, right?
My take is that if you're going to stick with XMLHttpRequest for this, you're going to have to just make peace with getting a server response. Just make the call asynchronously and have the response handled by a no-op function. Consider what somebody else suggested, using a queue on the server (or an asynchronous method on the server) so you return immediately to the client. Otherwise, I really think JavaScript is just the wrong tool for the job you're describing.
XMLHttpRequest is going to be a different implementation (presenting a more or less common interface contract) in every browser. I mean, Microsoft invented the thing, then the other browser makers emulated it, then voila, everybody started calling it Web 2.0. Point being, if you push too hard at the doughy center of XMLHttpRequest, you may get different behavior in different browsers.
XMLHttpRequest, as far as I know, strictly uses TCP (no UDP option), so at the very least your client is going to receive a TCP ACK from the server. There is no way to tell the server not to respond at that level. It's baked into the TCP/IP network stack.
Additionally, the communication uses the HTTP protocol, so the server will respond with HTTP headers... right? I mean, that is simply the way the protocol is defined. Telling HTTP to be something different is kind of like telling a cat to bark like a chicken.
Even if you could cancel the request on the client side by calling abort() on XMLHttpRequest, you're not cancelling it on the server side. To do so, even if it were possible with XMLHttpRequest, would require an additional request sent all the way to the server to tell it to cancel the response to the preceding request. How does it know which response to cancel? You'd have to manage request id's of some kind. You would have to be resilient to out-of-order cancellation requests. Complicated.
So here's a thought (I'm just thinking out loud): Microsoft's XMLHttpRequest was based at least in spirit on an even earlier Microsoft technology from the Visual Interdev days, which used a Java applet on the client to asynchronously fire off a request to the server, then it would pass control to your preferred JavaScript callback function when the response showed up, etc. Pretty familiar.
That Java async request thing got skewered during the whole Sun vs. Microsoft lawsuit fiasco. I heard rumors that a certain original Microsoft CEO would blow a gasket any time he learned about Microsoft tech being implemented using Java, and kill the tech. Who knows? I was unhappy when that capability disappeared for a couple of years, then happy again when XMLHttpRequest eventually showed up.
Maybe you see where I'm going, here... :-)
I think perhaps you're trying to squeeze behavior out of XMLHttpRequest that it just isn't built for.
The answer might be to just write your own Java applet, do some socket programming and have it do the kind communications you want to see from it. But then, of course, you'll have issues with people not having Java enabled in their browsers, exacerbated by all the recent Java security problems. So you're looking at code-signing certificates and so on. And you're also looking at issues that you'll need to resolve on the server side. If you still use HTTP and work through your web server, the web server will still want to send HTTP responses, which will still tie up resources on the server. You could make those actions on the server asynchronous so that TCP sockets don't stay tied up longer than necessary, but you're still tying up resources on the server side.
I managed to get the expected behavior using a very small timeout of 2ms. The following call is visible by the server but the connection is closed on the client side before any reply from the server:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 2) {
alert("Response header recived, it was not a fire-and-forget...");
}
};
xhr.open("POST", "http://www.service.org/myService.svc/Method", true);
xhr.timeout = 2;
xhr.send(null);
This is not fully satisfactory because the timeout may change between browser/computers (for instance, 1ms does not work on my setup). Using a large timeout in the order of 50ms means that the client might hit the limit of maximum concurrent opened connections (6 on my setup).
Using XMLHttpRequest to send an async request (i.e. where you don't care if it succeeds or what the response is:
var req = new XMLHttpRequest();
req.open('GET', 'http://my.url.goes.here.com');
req.send();
You can do much the same thing with an Image object, too, btw:
new Image().src = 'http://my.url.goes.here.com';
The Image approach works particularly well if you're making cross-domain requests, since Images aren't subject to same-origin security restrictions the way XHR requests are. (BTW, it's good practice but not essential to have your endpoint return a 1x1 pixel PNG or GIF response with the appropriate Content-Type, to avoid browser console warnings like 'Resource interpreted as Image but transferred with MIME type text/html'.)
It sounds like you're trying to solve the wrong problem. Instead of dealing with this on the client, why not handle this on the server side.
Take the message from the client and put a message on a service bus or store the data in a database and return to the client. Depending on your stack and architecture, this should be fairly simple and very fast. You can process the message out of band, either a second service listens to the message bus and processes the request, or some sort of batch processor can come along later and process the records in the database.
You won't have the same level of fine-grained control of the connection with XHR as with WebSockets. Ultimately, it's the browser that manages the HTTP connection lifecycle.
Instead of falling back from WebSockets to discrete XHR connections, maybe you can store and batch your events. For instance:
Client JS
function sendMessage(message) {
WebSocketsAvailable ? sendWithWebSockets(message) : sendWithXHR(message);
}
var xhrQueue = [];
function sendWithXHR(message) {
xhrQueue.push({
timestamp: Date.now(), // if this matters
message: message
});
}
function flushXhrQueue() {
if (xhrQueue.length) {
var req = new XMLHttpRequest();
req.open('POST', 'http://...');
req.onload = function() { setTimeout(flushXhrQueue, 5000); };
// todo: needs to handle errors, too
req.send(JSON.stringify(xhrQueue));
xhrQueue = [];
}
else {
setTimeout(flushXhrQueue, 5000);
}
}
setTimeout(flushXhrQueue, 5000);
On the server, maybe you can have two endpoints: one for WebSockets and one for XHR. The XHR handler deserialises the JSON queue object and calls (once per message) the same handler used by the WebSockets handler.
Server pseudo-code
function WSHandler(message) {
handleMessage(message, Date.now());
}
function XHRHandler(jsonString) {
var messages = JSON.parse(jsonString);
for (var messageObj in messages) {
handleMessage(messageObj.message, messageObj.timestamp);
}
}
function handleMessage(message, timestamp) {
...
}
On my website, I have built a chatroom with support for multiple rooms. When a user joins the room, a session is placed into the database so that if they try to join the room again in another browser window, they are locked out.
It works like this
1. Join the chatroom page
2. Connect to chatroom #main
If the user has a session in the database for #main
--- Block user from joining
else
--- Load chatroom
When the chatroom is closed client side or the user terminates there connection with the /quit command, all of their sessions are deleted, and this works fine.
However
There is a possibility that users will just close the browser window rather than terminating their connection. The problem with this is that their session will stay in the database, meaning when they try to connect to the room, they are blocked.
I'm using this code onbeforeunload to try and prevent that
function disconnect() {
$.ajax({
url: "/remove-chat-sessions.php?global",
async: false
});
};
This is also the function called when the user types the /quit command
The problem
The problem with this is that when I reload the page, 5 times out of 10 the sessions have not been taken out of the database, as if the ajax request failed or the page reloaded before it could finish. This means that when I go back into the chatroom, the database still thinks that I am connected, and blocks me from entering the chatroom
Is there a better way to make sure that this AJAX call will load and if not, is there a better alternative than storing user sessions in an online database?
Edit:
The reason users are blocked from joining rooms more than once is because messages you post do not appear to you when the chatroom updates for new messages. They are appended to the chatroom box when you post them. This means that if users could be in the same chatroom over multiple windows, they would not be able to see the comments that they posted across all of the windows.
In this situation you could add some sort of polling. Basically, you request with javascript a page every X time. That page adds the user session to the database. Then there's a script executing every Y time, where Y > X, that cleans old sessions.
The script that is called every X time
...
// DB call (do as you like)
$All = fetch_all_recent();
foreach ($All as $Session)
{
if ($Session['time'] < time() - $y)
{
delete_session($Session['id']);
}
}
The script that javascript is calling every X time
...
delete_old_session($User->id);
add_user_session($User->id, $Chat->id, time());
The main disadvantage of this method is the increment in requests, something Apache is not so used to (for large request number). There are two non-exclusive alternatives for this, which involve access to the server, are:
Use nginx server. I have no experience in this but I've read it supports many more connections than Apache.
Use some modern form of persistent connection, like socket.io. However, it uses node.js, which can be good or bad, depending on your business.
I was searching for a way how to communicate between multiple tabs or windows in a browser (on the same domain, not CORS) without leaving traces. There were several solutions:
using the window object
postMessage
cookies
localStorage
The first is probably the worst solution - you need to open a window from your current window and then you can communicate only as long as you keep the windows open. If you reload the page in any of the windows, you most likely lost the communication.
The second approach, using postMessage, probably enables cross-origin communication, but it suffers the same problem as the first approach. You need to maintain a window object.
The third way, using cookies, store some data in the browser, which can effectively look like sending a message to all windows on the same domain, but the problem is that you can never know if all tabs read the "message" already or not before cleaning up. You have to implement some sort of timeout to read the cookie periodically. Furthermore you are limited by maximum cookie length, which is 4 KB.
The fourth solution, using localStorage, seemed to overcome the limitations of cookies, and it can be even listen-to using events. How to use it is described in the accepted answer.
You may better use BroadcastChannel for this purpose. See other answers below. Yet if you still prefer to use localstorage for communication between tabs, do it this way:
In order to get notified when a tab sends a message to other tabs, you simply need to bind on 'storage' event. In all tabs, do this:
$(window).on('storage', message_receive);
The function message_receive will be called every time you set any value of localStorage in any other tab. The event listener contains also the data newly set to localStorage, so you don't even need to parse localStorage object itself. This is very handy because you can reset the value just right after it was set, to effectively clean up any traces. Here are functions for messaging:
// use local storage for messaging. Set message in local storage and clear it right away
// This is a safe way how to communicate with other tabs while not leaving any traces
//
function message_broadcast(message)
{
localStorage.setItem('message',JSON.stringify(message));
localStorage.removeItem('message');
}
// receive message
//
function message_receive(ev)
{
if (ev.originalEvent.key!='message') return; // ignore other keys
var message=JSON.parse(ev.originalEvent.newValue);
if (!message) return; // ignore empty msg or msg reset
// here you act on messages.
// you can send objects like { 'command': 'doit', 'data': 'abcd' }
if (message.command == 'doit') alert(message.data);
// etc.
}
So now once your tabs bind on the onstorage event, and you have these two functions implemented, you can simply broadcast a message to other tabs calling, for example:
message_broadcast({'command':'reset'})
Remember that sending the exact same message twice will be propagated only once, so if you need to repeat messages, add some unique identifier to them, like
message_broadcast({'command':'reset', 'uid': (new Date).getTime()+Math.random()})
Also remember that the current tab which broadcasts the message doesn't actually receive it, only other tabs or windows on the same domain.
You may ask what happens if the user loads a different webpage or closes his tab just after the setItem() call before the removeItem(). Well, from my own testing the browser puts unloading on hold until the entire function message_broadcast() is finished. I tested to put some very long for() cycle in there and it still waited for the cycle to finish before closing. If the user kills the tab just in-between, then the browser won't have enough time to save the message to disk, thus this approach seems to me like safe way how to send messages without any traces.
There is a modern API dedicated for this purpose - Broadcast Channel
It is as easy as:
var bc = new BroadcastChannel('test_channel');
bc.postMessage('This is a test message.'); /* send */
bc.onmessage = function (ev) { console.log(ev); } /* receive */
There is no need for the message to be just a DOMString. Any kind of object can be sent.
Probably, apart from API cleanness, it is the main benefit of this API - no object stringification.
It is currently supported only in Chrome and Firefox, but you can find a polyfill that uses localStorage.
For those searching for a solution not based on jQuery, this is a plain JavaScript version of the solution provided by Thomas M:
window.addEventListener("storage", message_receive);
function message_broadcast(message) {
localStorage.setItem('message',JSON.stringify(message));
}
function message_receive(ev) {
if (ev.key == 'message') {
var message=JSON.parse(ev.newValue);
}
}
Checkout AcrossTabs - Easy communication between cross-origin browser tabs. It uses a combination of the postMessage and sessionStorage APIs to make communication much easier and reliable.
There are different approaches and each one has its own advantages and disadvantages. Let’s discuss each:
LocalStorage
Pros:
Web storage can be viewed simplistically as an improvement on cookies, providing much greater storage capacity. If you look at the Mozilla source code we can see that 5120 KB (5 MB which equals 2.5 million characters on Chrome) is the default storage size for an entire domain. This gives you considerably more space to work with than a typical 4 KB cookie.
The data is not sent back to the server for every HTTP request (HTML, images, JavaScript, CSS, etc.) - reducing the amount of traffic between client and server.
The data stored in localStorage persists until explicitly deleted. Changes made are saved and available for all current and future visits to the site.
Cons:
It works on same-origin policy. So, data stored will only be able available on the same origin.
Cookies
Pros:
Compared to others, there's nothing AFAIK.
Cons:
The 4 KB limit is for the entire cookie, including name, value, expiry date, etc. To support most browsers, keep the name under 4000 bytes, and the overall cookie size under 4093 bytes.
The data is sent back to the server for every HTTP request (HTML, images, JavaScript, CSS, etc.) - increasing the amount of traffic between client and server.
Typically, the following are allowed:
300 cookies in total
4096 bytes per cookie
20 cookies per domain
81920 bytes per domain (given 20 cookies of the maximum size 4096 = 81920 bytes.)
sessionStorage
Pros:
It is similar to localStorage.
Changes are only available per window (or tab in browsers like Chrome and Firefox). Changes made are saved and available for the current page, as well as future visits to the site on the same window. Once the window is closed, the storage is deleted
Cons:
The data is available only inside the window/tab in which it was set.
The data is not persistent, i.e., it will be lost once the window/tab is closed.
Like localStorage, tt works on same-origin policy. So, data stored will only be able available on the same origin.
PostMessage
Pros:
Safely enables cross-origin communication.
As a data point, the WebKit implementation (used by Safari and Chrome) doesn't currently enforce any limits (other than those imposed by running out of memory).
Cons:
Need to open a window from the current window and then can communicate only as long as you keep the windows open.
Security concerns - Sending strings via postMessage is that you will pick up other postMessage events published by other JavaScript plugins, so be sure to implement a targetOrigin and a sanity check for the data being passed on to the messages listener.
A combination of PostMessage + SessionStorage
Using postMessage to communicate between multiple tabs and at the same time using sessionStorage in all the newly opened tabs/windows to persist data being passed. Data will be persisted as long as the tabs/windows remain opened. So, even if the opener tab/window gets closed, the opened tabs/windows will have the entire data even after getting refreshed.
I have written a JavaScript library for this, named AcrossTabs which uses postMessage API to communicate between cross-origin tabs/windows and sessionStorage to persist the opened tabs/windows identity as long as they live.
I've created a library sysend.js for sending messages between browser tabs and windows. The library doesn't have any external dependencies.
You can use it for communication between tabs/windows in the same browser and domain. The library uses BroadcastChannel, if supported, or storage event from localStorage.
The API is very simple:
sysend.on('foo', function(data) {
console.log(data);
});
sysend.broadcast('foo', {message: 'Hello'});
sysend.broadcast('foo', "hello");
sysend.broadcast('foo', ["hello", "world"]);
sysend.broadcast('foo'); // empty notification
When your browser supports BroadcastChannel it sends a literal object (but it's in fact auto-serialized by the browser) and if not, it's serialized to JSON first and deserialized on another end.
The recent version also has a helper API to create a proxy for cross-domain communication (it requires a single HTML file on the target domain).
Here is a demo.
The new version also supports cross-domain communication, if you include a special proxy.html file on the target domain and call proxy function from the source domain:
sysend.proxy('https://target.com');
(proxy.html is a very simple HTML file, that only have one script tag with the library).
If you want two-way communication you need to do the same on other domains.
NOTE: If you will implement the same functionality using localStorage, there is an issue in Internet Explorer. The storage event is sent to the same window, which triggers the event and for other browsers, it's only invoked for other tabs/windows.
Another method that people should consider using is shared workers. I know it's a cutting-edge concept, but you can create a relay on a shared worker that is much faster than localstorage, and doesn't require a relationship between the parent/child window, as long as you're on the same origin.
See my answer here for some discussion I made about this.
There's a tiny open-source component to synchronise and communicate between tabs/windows of the same origin (disclaimer - I'm one of the contributors!) based around localStorage.
TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);
TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
DoSomething();
});
TabUtils.CallOnce("lockname", function () {
alert("I run only once across multiple tabs");
});
P.S.: I took the liberty to recommend it here since most of the "lock/mutex/sync" components fail on websocket connections when events happen almost simultaneously.
I wrote an article on this on my blog: Sharing sessionStorage data across browser tabs.
Using a library, I created storageManager. You can achieve this as follows:
storageManager.savePermanentData('data', 'key'): //saves permanent data
storageManager.saveSyncedSessionData('data', 'key'); //saves session data to all opened tabs
storageManager.saveSessionData('data', 'key'); //saves session data to current tab only
storageManager.getData('key'); //retrieves data
There are other convenient methods as well to handle other scenarios as well.
This is a development storage part of Tomas M's answer for Chrome. We must add a listener:
window.addEventListener("storage", (e)=> { console.log(e) } );
Load/save the item in storage will not fire this event - we must trigger it manually by
window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME
And now, all open tabs will receive the event.