[HN Gopher] Show HN: Offline JavaScript PubSub between browser tabs
       ___________________________________________________________________
        
       Show HN: Offline JavaScript PubSub between browser tabs
        
       Author : l1am0
       Score  : 72 points
       Date   : 2025-04-03 14:09 UTC (8 hours ago)
        
 (HTM) web link (simon-frey.com)
 (TXT) w3m dump (simon-frey.com)
        
       | edweis wrote:
       | I love it, the code is so simple:
       | https://github.com/simonfrey/tabsub/blob/master/tabsub.v1.js
        
         | l1am0 wrote:
         | Thank you :) Simple because I don't know any better :'D
        
         | stanac wrote:
         | I did something similar (local storage events) in time of
         | jQuery to sync shopping cart between tabs. Very simple
         | solution, but, IIRC, it didn't work with IE.
        
       | sisk wrote:
       | If I understand this correctly, the `BroadcastChannel` API solves
       | a similar purpose.
       | 
       | https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_C...
        
         | l1am0 wrote:
         | Oh, did not know about it :D Thank you! Added a link to this to
         | the TabSub landingpage
        
           | sisk wrote:
           | My pleasure--happy to share an alternative. Thanks for making
           | and sharing a thing!
        
       | EGreg wrote:
       | I remember implementing the same thing in our framework like a
       | decade ago, but eventually taking it out. We wanted to use it for
       | caching data between tabs, so as not to hit the server again per
       | tab:
       | 
       | https://github.com/Qbix/Platform/blob/main/platform/plugins/...
       | 
       | It had the concept of a "main frame" that they would route all
       | requests to the server through.
       | 
       | I remember now -- I did it not so much for tabs as for our
       | solution of having tons of little iframes on a page:
       | https://qbix.com/ecosystem
        
         | remram wrote:
         | You can use a SharedWorker now instead of picking a tab to do
         | that work (and failing over to another tab if it gets closed).
         | Support is still spotty though.
        
           | EGreg wrote:
           | Well, you can just use a service worker, instead of broadcast
           | channels or shared workers. They have all the functionality
           | you need, now.
           | 
           | Any iframe that loads in a secure context and itself creates
           | a secure context, can load a service worker for its domain or
           | subdomain or path.
        
       | kvemkon wrote:
       | Related recently discussed:
       | 
       | Show HN: JavaScript PubSub in 163 Bytes (31.03.2025)
       | 
       | https://news.ycombinator.com/item?id=43529774
        
       | Lerc wrote:
       | I built a ComfyUi node that let you run a little paint program in
       | another tab. (Sorry about the lack of updates if you use it. I
       | intend to get back to it)
       | 
       | Negotiating the communication between tabs was by far the hardest
       | part. In the end I ended up using local storage for signaling to
       | establish a dedicated messsageport channel.
       | 
       | It was such a fight to make something that re-established the
       | connection when either page reloaded.
       | 
       | There are still some connection issues that I haven't been able
       | to resolve. It seems some browsers on some systems reject
       | messages between tabs if they were loaded hours apart.
       | 
       | It might be worth benchmarking a pure local storage fallback, but
       | I'm guessing it would suffer with high traffic.
       | 
       | A generalised implementation that allowed switching multiple
       | paint programs and multiple ComfyUi pages would be ideal. A
       | PubSub might be the way to go.
       | 
       | There's also the issue of other parts of the app also using local
       | storage. Need to not step on toes.
        
       | hombre_fatal wrote:
       | It's nice that the 'storage' event also gives you event.newValue
       | to spare you the race condition of reading from localStorage.
        
       | qudat wrote:
       | Very cool, I love seeing pubsubs in the wild. We built an ssh
       | based pubsub that we have been using in production for awhile
       | now: https://pipe.pico.sh/
        
       | Minor49er wrote:
       | The audio demo makes me think of Bandcamp which will pause music
       | that you're playing if another Bandcamp tab starts playing a song
       | separately. Must be a similar mechanism under the hood
        
         | realPubkey wrote:
         | This is likely done with the WebLocks API
        
           | sltkr wrote:
           | That doesn't allow signaling another tab, does it?
        
             | 0x457 wrote:
             | > The Web Locks API allows scripts running in one tab or
             | worker to asynchronously acquire a lock, hold it while work
             | is performed, then release it. While held, no other script
             | executing in the same origin can acquire the same lock,
             | *which allows a web app running in multiple tabs or workers
             | to coordinate work and the use of resources.*
             | 
             | source: https://developer.mozilla.org/en-
             | US/docs/Web/API/Web_Locks_A...
        
               | sltkr wrote:
               | I read that, but how can that API be used to notify the
               | original tab to stop playing?
        
       | nottorp wrote:
       | Isn't this a security hole waiting to be exploited?
       | 
       | How does the browser handle access control to the local storage,
       | especially offline when they aren't loaded from the same site?
       | 
       | [Yes, I really don't know. Yes, I'm asking. Not everyone is a web
       | dev.]
        
         | sltkr wrote:
         | From the post:
         | 
         | > As TabSub uses local store this only works on the same
         | domain, as the browser separates the local storage by domains
         | as security measure.
         | 
         | (More precisely, the separation is based on _origin_ , which is
         | roughly the combination of protocol, hostname, and port.)
         | 
         | The conclusion is this only works between tabs that have the
         | same website open.
        
           | nottorp wrote:
           | But it's offline, what's the website? Or offline doesn't mean
           | offline?
        
             | godot wrote:
             | I think in their case, offline is as in you don't need to
             | set up a pubsub server and the client doesn't have to talk
             | to a server for the specific pubsub functionality, not as
             | in "use this for offline web pages/html files locally" (it
             | may or may not work for that, I have no idea, didn't look).
        
               | l1am0 wrote:
               | Works for offline apps as well :) (you would need to
               | download the tabsub JS ofc and not use it from my server)
        
             | l1am0 wrote:
             | It means, that you don't need an internet connection for
             | this to work :) (so it is no rabbitmq or so which runs on a
             | server and the browser is just the client)
             | 
             | You can try on the demopage when you
             | 
             | 1. play the songs each (for them to buffer a little audio
             | snippet)
             | 
             | 2. open the page in a second tab
             | 
             | 3. Disconnect from the internt
             | 
             | Still works :D
        
               | nottorp wrote:
               | I'm just asking random questions now and then to get an
               | idea of how things work in the web world. Especially
               | those that aren't mentioned in tutorials.
        
       | sltkr wrote:
       | The subscribe() implementation seems suboptimal in the case where
       | there are many different topics:
       | subscribe(topic, callback) {             if
       | (this.listeners[topic] == undefined) {                 // Not yet
       | any listener for this topic                 this.listeners[topic]
       | = [];                      window.addEventListener('storage', (e)
       | => {                     const dataKey = topic + "_data";
       | if (e.key === dataKey) {                         const data =
       | JSON.parse(e.newValue)[0];
       | this.listeners[topic].forEach((v, k) => {
       | v(data);                         });                     }
       | }, false);             }
       | this.listeners[topic].push(callback)         }
       | 
       | This installs a handler for every single topic, and every time a
       | message is published, the handlers for all topics are called,
       | even though at most one is interested in the change. A more
       | efficient implementation would install a single handler, e.g.
       | (untested):                   window.addEventListener('storage',
       | (e) => {             if (e.key.endsWith('_data')) {
       | const topic = e.key.substring(0, e.key.length - 5);
       | const data = JSON.parse(e.newValue)[0];
       | this.listeners[topic]?.forEach(v => v(data));            }
       | }, false);
        
         | l1am0 wrote:
         | Will check this tomorrow. If you come to it in the meantime,
         | feel free to open a PR :) Thx
        
       | amazingamazing wrote:
       | I wonder how one does this on nodejs across instances gracefully.
        
         | kolme wrote:
         | Well, there's RabbitMQ and the rest of the message queues out
         | there. But I don't know if those fall in your definition of
         | "graceful".
        
           | amazingamazing wrote:
           | No, I mean with just NodeJS only.
        
       ___________________________________________________________________
       (page generated 2025-04-03 23:01 UTC)