7 ms·
Show HN: Offline JavaScript PubSub between browser tabs
- edweis 2y agoI love it, the code is so simple: https://github.com/simonfrey/tabsub/blob/master/tabsub.v1.js https://github.com/simonfrey/tabsub/blob/master/tabsub.v1.js
- deleted 2y ago[deleted]
- sisk 2y agoIf I understand this correctly, the `BroadcastChannel` API solves a similar purpose. https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_C...
- l1am0 2y agoOh, did not know about it :D Thank you! Added a link to this to the TabSub landingpage
- deleted 2y ago[deleted]
- sisk 2y agoMy pleasure—happy to share an alternative. Thanks for making and sharing a thing!
- deleted 2y ago[deleted]
- nchmy 2y agoI came to say this as well. Surely it is the proper way to do this. Could be combined with weblocks if you want some master/leader event bus tab or web worker
- EGreg 2y agoI 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/Q/web/js/Q/Frames.js 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 https://qbix.com/ecosystem
- remram 2y agoYou 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 2y agoWell, 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 2y agoRelated recently discussed: Show HN: JavaScript PubSub in 163 Bytes (31.03.2025) https://news.ycombinator.com/item?id=43529774 https://news.ycombinator.com/item?id=43529774
- ilmasalsabil 2y ago[dead]
- Lerc 2y agoI 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 2y agoIt's nice that the 'storage' event also gives you event.newValue to spare you the race condition of reading from localStorage.
- qudat 2y agoVery 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/ https://pipe.pico.sh/
- Minor49er 2y agoThe 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 2y agoThis is likely done with the WebLocks API
- sltkr 2y agoThat doesn't allow signaling another tab, does it?
- 0x457 2y ago> 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_API https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_A...
- nottorp 2y agoIsn'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 2y agoFrom 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 2y agoBut it's offline, what's the website? Or offline doesn't mean offline?
- godot 2y agoI 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 2y agoWorks for offline apps as well :) (you would need to download the tabsub JS ofc and not use it from my server)
- l1am0 2y agoIt 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
- sltkr 2y agoThe 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 2y agoWill check this tomorrow. If you come to it in the meantime, feel free to open a PR :) Thx
- amazingamazing 2y agoI wonder how one does this on nodejs across instances gracefully.
- kolme 2y agoWell, 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 2y agoNo, I mean with just NodeJS only.
- l1am0 2y ago"easiest/low tech" would be some sort of unix file-locking and inode. Only works if you are on the same host machine though...AAAND sounds like it would be quite hacky.