purrtabby 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <br>
6
6
  <a href="https://www.npmjs.org/package/purrtabby"><img src="https://img.shields.io/npm/v/purrtabby.svg" alt="npm"></a>
7
7
  <img src="https://github.com/let-sunny/purrtabby/workflows/CI/badge.svg" alt="build status">
8
- <a href="https://unpkg.com/purrtabby/dist/index.global.js"><img src="https://img.badgesize.io/https://unpkg.com/purrtabby/dist/index.global.js?compression=gzip" alt="gzip size"></a>
8
+ <a href="https://bundlephobia.com/package/purrtabby"><img src="https://img.shields.io/badge/gzip-2.1%20KB-blue?style=flat" alt="bundle size (gzipped)"></a>
9
9
  </p>
10
10
 
11
11
  > Lightweight browser tab communication and leader election library using BroadcastChannel and localStorage
@@ -14,7 +14,7 @@ A lightweight library for cross-tab communication and leader election in browser
14
14
 
15
15
  ## Highlights
16
16
 
17
- **Microscopic**: weighs less than 7KB minified (~3KB gzipped)
17
+ **Microscopic**: weighs 6.3KB minified (2.1KB gzipped)
18
18
 
19
19
  **Reliable**: leader election with lease-based heartbeat mechanism
20
20
 
@@ -34,7 +34,7 @@ Try purrtabby in your browser with our interactive demo. Test cross-tab communic
34
34
 
35
35
  ## 📚 Documentation
36
36
 
37
- **[Architecture Documentation →](./docs/ARCHITECTURE.md)**
37
+ **[Architecture Documentation →](./docs/ARCHITECTURE.md)**
38
38
 
39
39
  Learn about purrtabby's internal architecture and design decisions.
40
40
 
@@ -67,11 +67,13 @@ Learn about purrtabby's internal architecture and design decisions.
67
67
  ## Installation
68
68
 
69
69
  **npm:**
70
+
70
71
  ```bash
71
72
  npm install purrtabby
72
73
  ```
73
74
 
74
75
  **UMD build (via unpkg):**
76
+
75
77
  ```html
76
78
  <script src="https://unpkg.com/purrtabby/dist/index.global.js"></script>
77
79
  ```
@@ -79,10 +81,12 @@ npm install purrtabby
79
81
  ### Requirements
80
82
 
81
83
  **Runtime:**
84
+
82
85
  - **Browser**: Modern browsers with BroadcastChannel and localStorage support
83
86
  - **Node.js**: Not supported (requires browser APIs)
84
87
 
85
88
  **Development:**
89
+
86
90
  - **Node.js**: 24.13.0 (tested with this version)
87
91
 
88
92
  ## Usage
@@ -123,9 +127,9 @@ import { createLeaderElector } from 'purrtabby';
123
127
  const leader = createLeaderElector({
124
128
  key: 'my-app-leader',
125
129
  tabId: 'tab-1', // Optional: auto-generated if not provided
126
- leaseMs: 5000, // Lease duration in milliseconds
130
+ leaseMs: 5000, // Lease duration in milliseconds
127
131
  heartbeatMs: 2000, // Heartbeat interval
128
- jitterMs: 500, // Jitter to avoid thundering herd
132
+ jitterMs: 500, // Jitter to avoid thundering herd
129
133
  });
130
134
 
131
135
  // Start leader election
@@ -256,10 +260,10 @@ Creates a new TabBus instance for cross-tab communication.
256
260
 
257
261
  #### Options
258
262
 
259
- | Option | Type | Default | Description |
260
- |--------|------|---------|-------------|
261
- | `channel` | `string` | **required** | BroadcastChannel name |
262
- | `tabId` | `string` | auto-generated | Unique tab identifier |
263
+ | Option | Type | Default | Description |
264
+ | --------- | -------- | -------------- | --------------------- |
265
+ | `channel` | `string` | **required** | BroadcastChannel name |
266
+ | `tabId` | `string` | auto-generated | Unique tab identifier |
263
267
 
264
268
  #### TabBus Methods
265
269
 
@@ -315,16 +319,17 @@ Creates a new LeaderElector instance for leader election.
315
319
 
316
320
  #### Options
317
321
 
318
- | Option | Type | Default | Description |
319
- |--------|------|---------|-------------|
320
- | `key` | `string` | **required** | localStorage key for leader lease |
321
- | `tabId` | `string` | **required** | Unique tab identifier |
322
- | `leaseMs` | `number` | `5000` | Lease duration in milliseconds |
323
- | `heartbeatMs` | `number` | `2000` | Heartbeat interval in milliseconds |
324
- | `jitterMs` | `number` | `500` | Jitter range to avoid synchronization |
325
- | `buffer` | `BufferConfig` | `{ size: 100, overflow: 'oldest' }` | Buffer configuration for stream generators |
322
+ | Option | Type | Default | Description |
323
+ | ------------- | -------------- | ----------------------------------- | ------------------------------------------ |
324
+ | `key` | `string` | **required** | localStorage key for leader lease |
325
+ | `tabId` | `string` | **required** | Unique tab identifier |
326
+ | `leaseMs` | `number` | `5000` | Lease duration in milliseconds |
327
+ | `heartbeatMs` | `number` | `2000` | Heartbeat interval in milliseconds |
328
+ | `jitterMs` | `number` | `500` | Jitter range to avoid synchronization |
329
+ | `buffer` | `BufferConfig` | `{ size: 100, overflow: 'oldest' }` | Buffer configuration for stream generators |
326
330
 
327
331
  **BufferConfig:**
332
+
328
333
  - `size`: Maximum queue size (default: 100)
329
334
  - `overflow`: Overflow policy - `'oldest'` (drop oldest), `'newest'` (drop newest), or `'error'` (throw error)
330
335
 
@@ -347,6 +352,7 @@ Returns `true` if this tab is currently the leader.
347
352
  Subscribes to a specific leader event. Returns an unsubscribe function.
348
353
 
349
354
  Events:
355
+
350
356
  - `'acquire'` - This tab became the leader
351
357
  - `'lose'` - This tab lost leadership
352
358
  - `'change'` - Leadership changed to another tab
@@ -398,7 +404,7 @@ leader.on('lose', () => {
398
404
  // Listen for messages from other tabs
399
405
  bus.subscribe('user-action', (message) => {
400
406
  console.log('User action from tab:', message.tabId, message.payload);
401
-
407
+
402
408
  // If we're the leader, process the action
403
409
  if (leader.isLeader()) {
404
410
  console.log('Processing action as leader');
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var k=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var P=(e,r)=>{for(var a in r)k(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of D(r))!G.call(e,s)&&s!==a&&k(e,s,{get:()=>r[s],enumerable:!(n=_(r,s))||n.enumerable});return e};var F=e=>J(k({},"__esModule",{value:!0}),e);var H={};P(H,{createBus:()=>E,createLeaderElector:()=>y,default:()=>q});module.exports=F(H);function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function x(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function O(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function B(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function S(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function M(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function C(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await S(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*A(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await S(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function j(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function N(e,r,a,n){try{let s=e.data;j(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function E(e){let{channel:r,tabId:a,buffer:n}=e,s=a||x(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{N(o,s,i,t)},c.onmessageerror=()=>{let o=O("err",{error:"Failed to receive message"})},{publish(o,f){let p={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(p),queueMicrotask(()=>{j(p,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let p=i.messageCallbacks.get(o);p&&(p.delete(f),p.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return R(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function V(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(M(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function $(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};M(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function z(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function y(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},L=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||z(t,L)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&C(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),V(t,L),t.isLeader){let I=B(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{$(t,L)},I)}let d=B(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{z(t,L)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&C(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,L)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let h=t.eventCallbacks.get(d);h&&(h.delete(I),h.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return A(t,{events:L},d?.signal)},getTabId(){return t.tabId}}}var q={createBus:E,createLeaderElector:y};0&&(module.exports={createBus,createLeaderElector});
1
+ "use strict";var y=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var P=(e,r)=>{for(var a in r)y(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of D(r))!G.call(e,s)&&s!==a&&y(e,s,{get:()=>r[s],enumerable:!(n=z(r,s))||n.enumerable});return e};var F=e=>J(y({},"__esModule",{value:!0}),e);var H={};P(H,{createBus:()=>E,createLeaderElector:()=>h,default:()=>q});module.exports=F(H);function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function x(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function R(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function B(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function S(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function M(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function C(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*O(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await S(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*A(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await S(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function j(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function N(e,r,a,n){try{let s=e.data;j(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function E(e){let{channel:r,tabId:a,buffer:n}=e,s=a||x(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{N(o,s,i,t)},c.onmessageerror=()=>{let o=R("err",{error:"Failed to receive message"})},{publish(o,f){let L={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(L),queueMicrotask(()=>{j(L,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let L=i.messageCallbacks.get(o);L&&(L.delete(f),L.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return O(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function V(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(M(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function $(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};M(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function _(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function h(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},p=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||_(t,p)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&C(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),V(t,p),t.isLeader){let I=B(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{$(t,p)},I)}let d=B(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{_(t,p)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&C(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,p)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let k=t.eventCallbacks.get(d);k&&(k.delete(I),k.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return A(t,{events:p},d?.signal)},getTabId(){return t.tabId}}}var q={createBus:E,createLeaderElector:h};0&&(module.exports={createBus,createLeaderElector});
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** TabBus message structure */
2
- interface TabBusMessage<T = any> {
2
+ interface TabBusMessage<T = unknown> {
3
3
  type: string;
4
4
  payload?: T;
5
5
  tabId: string;
@@ -11,7 +11,7 @@ type TabBusEventType = 'msg' | 'err';
11
11
  interface TabBusEvent {
12
12
  type: TabBusEventType;
13
13
  ts: number;
14
- meta?: Record<string, any>;
14
+ meta?: Record<string, unknown>;
15
15
  }
16
16
  /** Buffer overflow policy */
17
17
  type BufferOverflowPolicy = 'oldest' | 'newest' | 'error';
@@ -27,7 +27,7 @@ interface BusOptions {
27
27
  buffer?: BufferConfig;
28
28
  }
29
29
  /** Return type of createBus(), provides async iterables and callbacks */
30
- interface TabBus<T = any> {
30
+ interface TabBus<T = unknown> {
31
31
  /** Publish a message to all tabs */
32
32
  publish(type: string, payload?: T): void;
33
33
  /** Subscribe to messages of a specific type */
@@ -49,7 +49,7 @@ type LeaderEventType = 'acquire' | 'lose' | 'change';
49
49
  interface LeaderEvent {
50
50
  type: LeaderEventType;
51
51
  ts: number;
52
- meta?: Record<string, any>;
52
+ meta?: Record<string, unknown>;
53
53
  }
54
54
  /** Options for createLeaderElector() */
55
55
  interface LeaderElectorOptions {
@@ -85,7 +85,7 @@ interface LeaderElector {
85
85
  * @param options - Bus configuration options
86
86
  * @returns TabBus instance
87
87
  */
88
- declare function createBus<T = any>(options: BusOptions): TabBus<T>;
88
+ declare function createBus<T = unknown>(options: BusOptions): TabBus<T>;
89
89
 
90
90
  /**
91
91
  * Create a LeaderElector instance for leader election
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** TabBus message structure */
2
- interface TabBusMessage<T = any> {
2
+ interface TabBusMessage<T = unknown> {
3
3
  type: string;
4
4
  payload?: T;
5
5
  tabId: string;
@@ -11,7 +11,7 @@ type TabBusEventType = 'msg' | 'err';
11
11
  interface TabBusEvent {
12
12
  type: TabBusEventType;
13
13
  ts: number;
14
- meta?: Record<string, any>;
14
+ meta?: Record<string, unknown>;
15
15
  }
16
16
  /** Buffer overflow policy */
17
17
  type BufferOverflowPolicy = 'oldest' | 'newest' | 'error';
@@ -27,7 +27,7 @@ interface BusOptions {
27
27
  buffer?: BufferConfig;
28
28
  }
29
29
  /** Return type of createBus(), provides async iterables and callbacks */
30
- interface TabBus<T = any> {
30
+ interface TabBus<T = unknown> {
31
31
  /** Publish a message to all tabs */
32
32
  publish(type: string, payload?: T): void;
33
33
  /** Subscribe to messages of a specific type */
@@ -49,7 +49,7 @@ type LeaderEventType = 'acquire' | 'lose' | 'change';
49
49
  interface LeaderEvent {
50
50
  type: LeaderEventType;
51
51
  ts: number;
52
- meta?: Record<string, any>;
52
+ meta?: Record<string, unknown>;
53
53
  }
54
54
  /** Options for createLeaderElector() */
55
55
  interface LeaderElectorOptions {
@@ -85,7 +85,7 @@ interface LeaderElector {
85
85
  * @param options - Bus configuration options
86
86
  * @returns TabBus instance
87
87
  */
88
- declare function createBus<T = any>(options: BusOptions): TabBus<T>;
88
+ declare function createBus<T = unknown>(options: BusOptions): TabBus<T>;
89
89
 
90
90
  /**
91
91
  * Create a LeaderElector instance for leader election
@@ -1 +1 @@
1
- "use strict";var purrtabby=(()=>{var k=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var P=(e,r)=>{for(var a in r)k(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of D(r))!G.call(e,s)&&s!==a&&k(e,s,{get:()=>r[s],enumerable:!(n=_(r,s))||n.enumerable});return e};var F=e=>J(k({},"__esModule",{value:!0}),e);var H={};P(H,{createBus:()=>E,createLeaderElector:()=>y,default:()=>q});function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function x(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function O(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function B(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function S(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function M(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function C(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await S(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*A(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await S(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function j(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function N(e,r,a,n){try{let s=e.data;j(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function E(e){let{channel:r,tabId:a,buffer:n}=e,s=a||x(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{N(o,s,i,t)},c.onmessageerror=()=>{let o=O("err",{error:"Failed to receive message"})},{publish(o,f){let p={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(p),queueMicrotask(()=>{j(p,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let p=i.messageCallbacks.get(o);p&&(p.delete(f),p.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return R(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function V(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(M(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function $(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};M(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function z(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function y(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},L=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||z(t,L)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&C(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),V(t,L),t.isLeader){let I=B(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{$(t,L)},I)}let d=B(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{z(t,L)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&C(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,L)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let h=t.eventCallbacks.get(d);h&&(h.delete(I),h.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return A(t,{events:L},d?.signal)},getTabId(){return t.tabId}}}var q={createBus:E,createLeaderElector:y};return F(H);})();
1
+ "use strict";var purrtabby=(()=>{var y=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var P=(e,r)=>{for(var a in r)y(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of D(r))!G.call(e,s)&&s!==a&&y(e,s,{get:()=>r[s],enumerable:!(n=z(r,s))||n.enumerable});return e};var F=e=>J(y({},"__esModule",{value:!0}),e);var H={};P(H,{createBus:()=>E,createLeaderElector:()=>h,default:()=>q});function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function x(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function R(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function B(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function S(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function M(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function C(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*O(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await S(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*A(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await S(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function j(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function N(e,r,a,n){try{let s=e.data;j(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function E(e){let{channel:r,tabId:a,buffer:n}=e,s=a||x(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{N(o,s,i,t)},c.onmessageerror=()=>{let o=R("err",{error:"Failed to receive message"})},{publish(o,f){let L={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(L),queueMicrotask(()=>{j(L,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let L=i.messageCallbacks.get(o);L&&(L.delete(f),L.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return O(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function V(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(M(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function $(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};M(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function _(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function h(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},p=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||_(t,p)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&C(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),V(t,p),t.isLeader){let I=B(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{$(t,p)},I)}let d=B(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{_(t,p)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&C(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,p)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let k=t.eventCallbacks.get(d);k&&(k.delete(I),k.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return A(t,{events:p},d?.signal)},getTabId(){return t.tabId}}}var q={createBus:E,createLeaderElector:h};return F(H);})();
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function C(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function x(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function y(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function h(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function k(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function B(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*O(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await h(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await h(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function A(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function z(e,r,a,n){try{let s=e.data;A(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function S(e){let{channel:r,tabId:a,buffer:n}=e,s=a||C(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{z(o,s,i,t)},c.onmessageerror=()=>{let o=x("err",{error:"Failed to receive message"})},{publish(o,f){let p={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(p),queueMicrotask(()=>{A(p,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let p=i.messageCallbacks.get(o);p&&(p.delete(f),p.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return O(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function _(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(k(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function D(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};k(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function j(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function M(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},L=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||j(t,L)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&B(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),_(t,L),t.isLeader){let I=y(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{D(t,L)},I)}let d=y(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{j(t,L)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&B(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,L)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let E=t.eventCallbacks.get(d);E&&(E.delete(I),E.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return R(t,{events:L},d?.signal)},getTabId(){return t.tabId}}}var Q={createBus:S,createLeaderElector:M};export{S as createBus,M as createLeaderElector,Q as default};
1
+ function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function C(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function x(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function h(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function k(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function y(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function B(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await k(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*O(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await k(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function A(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function _(e,r,a,n){try{let s=e.data;A(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function S(e){let{channel:r,tabId:a,buffer:n}=e,s=a||C(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{_(o,s,i,t)},c.onmessageerror=()=>{let o=x("err",{error:"Failed to receive message"})},{publish(o,f){let L={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(L),queueMicrotask(()=>{A(L,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let L=i.messageCallbacks.get(o);L&&(L.delete(f),L.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return R(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function z(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(y(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function D(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};y(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function j(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function M(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},p=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||j(t,p)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&B(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),z(t,p),t.isLeader){let I=h(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{D(t,p)},I)}let d=h(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{j(t,p)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&B(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,p)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let E=t.eventCallbacks.get(d);E&&(E.delete(I),E.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return O(t,{events:p},d?.signal)},getTabId(){return t.tabId}}}var Q={createBus:S,createLeaderElector:M};export{S as createBus,M as createLeaderElector,Q as default};
package/package.json CHANGED
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "purrtabby",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Lightweight browser tab communication and leader election library using BroadcastChannel and localStorage",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "unpkg": "dist/index.global.js",
9
9
  "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
10
18
  "files": [
11
19
  "dist",
12
20
  "README.md",
@@ -18,6 +26,10 @@
18
26
  "test": "vitest run",
19
27
  "test:watch": "vitest",
20
28
  "test:coverage": "vitest run --coverage",
29
+ "lint": "eslint .",
30
+ "lint:fix": "eslint . --fix",
31
+ "format": "prettier --write \"**/*.{ts,js,json,md}\"",
32
+ "format:check": "prettier --check \"**/*.{ts,js,json,md}\"",
21
33
  "prepublishOnly": "npm run build && npm test",
22
34
  "version": "npm run build"
23
35
  },
@@ -42,7 +54,14 @@
42
54
  "vitest": "^2.0.0",
43
55
  "@vitest/coverage-v8": "^2.0.0",
44
56
  "@types/node": "^20.0.0",
45
- "jsdom": "^24.0.0"
57
+ "jsdom": "^24.0.0",
58
+ "prettier": "^3.8.0",
59
+ "eslint": "^9.0.0",
60
+ "@eslint/js": "^9.0.0",
61
+ "typescript-eslint": "^8.0.0",
62
+ "eslint-plugin-prettier": "^5.0.0",
63
+ "eslint-config-prettier": "^10.0.0",
64
+ "globals": "^15.0.0"
46
65
  },
47
66
  "engines": {
48
67
  "node": ">=18.0.0"