@buildwithdarsh/tabjs 1.0.0
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/LICENSE +21 -0
- package/README.md +313 -0
- package/dist/tab.d.ts +286 -0
- package/dist/tab.esm.js +2 -0
- package/dist/tab.esm.js.map +1 -0
- package/dist/tab.umd.js +2 -0
- package/dist/tab.umd.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# TabJS
|
|
2
|
+
|
|
3
|
+
Cross-tab communication for the modern web — shared state, presence, leader election, locks, duplicate detection, and request/response between every open tab of your app.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@buildwithdarsh/tabjs)
|
|
6
|
+
[](#)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](#)
|
|
9
|
+
|
|
10
|
+
Built on `BroadcastChannel` where available, with a `localStorage`-event fallback so it works in every modern browser, including private mode and sandboxed iframes. Zero dependencies, ~3 KB gzipped, fully typed.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @buildwithdarsh/tabjs
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @buildwithdarsh/tabjs
|
|
18
|
+
# or
|
|
19
|
+
yarn add @buildwithdarsh/tabjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or via CDN:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script src="https://unpkg.com/@buildwithdarsh/tabjs"></script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { getTabs } from '@buildwithdarsh/tabjs';
|
|
32
|
+
|
|
33
|
+
type AppState = { theme: 'dark' | 'light'; counter: number };
|
|
34
|
+
|
|
35
|
+
const tabs = getTabs<AppState>({ initialState: { theme: 'dark', counter: 0 } });
|
|
36
|
+
|
|
37
|
+
// Listen to everything
|
|
38
|
+
tabs.subscribe((event) => {
|
|
39
|
+
console.log(event.type, event);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Shared state — synced to every tab
|
|
43
|
+
tabs.setState((s) => ({ ...s!, counter: s!.counter + 1 }));
|
|
44
|
+
|
|
45
|
+
// Broadcast
|
|
46
|
+
tabs.broadcast('logout');
|
|
47
|
+
|
|
48
|
+
// Direct send to one tab
|
|
49
|
+
tabs.send(targetId, 'focus-input', { selector: '#title' });
|
|
50
|
+
|
|
51
|
+
// RPC
|
|
52
|
+
tabs.handle('double', ({ x }) => x * 2);
|
|
53
|
+
const result = await tabs.request<{ x: number }, number>(targetId, 'double', { x: 21 });
|
|
54
|
+
// → 42
|
|
55
|
+
|
|
56
|
+
// Leader election
|
|
57
|
+
if (tabs.isLeader) startBackgroundPoller();
|
|
58
|
+
|
|
59
|
+
// Cross-tab mutex
|
|
60
|
+
await tabs.lock('save', async () => {
|
|
61
|
+
await persist();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Duplicate-tab detection
|
|
65
|
+
if (tabs.isDuplicate) showDuplicateWarning();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Core concepts
|
|
69
|
+
|
|
70
|
+
### Tabs & presence
|
|
71
|
+
|
|
72
|
+
Each tab announces itself on construction and posts a heartbeat (default every 1.5 s). Tabs that stop heartbeating are evicted after `tabTimeout` (default 5 s) and a `close` event fires.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
tabs.id // stable per-tab id, generated at construction
|
|
76
|
+
tabs.bornAt // wall-clock time the tab opened
|
|
77
|
+
tabs.tabs // snapshot of all known live tabs
|
|
78
|
+
tabs.info // this tab's own snapshot
|
|
79
|
+
|
|
80
|
+
tabs.on('open', (e) => console.log('+ tab', e.tab.id));
|
|
81
|
+
tabs.on('close', (e) => console.log('- tab', e.tab.id));
|
|
82
|
+
tabs.on('focus', (e) => console.log('focus', e.tab.id));
|
|
83
|
+
tabs.on('blur', (e) => console.log('blur', e.tab.id));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`TabInfo` shape:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
{
|
|
90
|
+
id: string; // stable per-tab
|
|
91
|
+
bornAt: number; // Date.now() at construction
|
|
92
|
+
lastSeen: number; // last heartbeat
|
|
93
|
+
focused: boolean; // visibility + focus
|
|
94
|
+
url: string; // window.location.href at last update
|
|
95
|
+
meta: Record<string, unknown>;
|
|
96
|
+
lineage: string; // sessionStorage-scoped — see "duplicate detection"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Leader election
|
|
101
|
+
|
|
102
|
+
The oldest live tab (lowest `bornAt`, ties broken by `id`) is the leader. When the leader leaves, a new one is elected automatically and a `leader` event fires in every tab.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
if (tabs.isLeader) startPolling();
|
|
106
|
+
|
|
107
|
+
tabs.on('leader', (e) => {
|
|
108
|
+
console.log('new leader', e.leader.id, 'was', e.previous?.id);
|
|
109
|
+
if (e.leader.id === tabs.id) startPolling();
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use it for "only one tab runs the background poller", "only one tab handles WebSocket reconnection", etc.
|
|
114
|
+
|
|
115
|
+
### Shared state
|
|
116
|
+
|
|
117
|
+
One typed state object, synchronized across every open tab. Stored in `localStorage` so new tabs hydrate instantly, and any peer can respond to a state-get request with the live in-memory value.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
tabs.setState({ theme: 'dark', counter: 0 });
|
|
121
|
+
tabs.setState((prev) => ({ ...prev!, counter: prev!.counter + 1 }));
|
|
122
|
+
|
|
123
|
+
tabs.getState(); // → current value, or null
|
|
124
|
+
tabs.on('state', (e) => render(e.state));
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Messaging
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// Fire-and-forget broadcast to every other tab
|
|
131
|
+
tabs.broadcast('chat', { text: 'hi' });
|
|
132
|
+
|
|
133
|
+
// Direct send to a specific tab
|
|
134
|
+
tabs.send(targetId, 'chat', { text: 'just you' });
|
|
135
|
+
|
|
136
|
+
// Subscribe to incoming messages
|
|
137
|
+
tabs.on('message', (e) => {
|
|
138
|
+
if (e.channel === 'chat') console.log(e.from, e.payload);
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Request / response
|
|
143
|
+
|
|
144
|
+
Promise-based RPC. Register a handler on one tab, `await` the result on another.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// Tab A
|
|
148
|
+
tabs.handle<{ x: number }, number>('double', ({ x }) => x * 2);
|
|
149
|
+
|
|
150
|
+
// Tab B
|
|
151
|
+
const answer = await tabs.request<{ x: number }, number>(tabAId, 'double', { x: 21 });
|
|
152
|
+
// → 42
|
|
153
|
+
|
|
154
|
+
// Pass `null` as the target to send to the current leader
|
|
155
|
+
const r = await tabs.request(null, 'work', payload, { timeout: 3000 });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Handlers can be async and may throw — exceptions surface as rejections on the caller.
|
|
159
|
+
|
|
160
|
+
### Cross-tab locks
|
|
161
|
+
|
|
162
|
+
A mutex that survives across tabs. The held lock heartbeats — if a holder tab crashes the lock auto-releases after `staleAfter` ms.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
await tabs.lock('save-doc', async () => {
|
|
166
|
+
// only one tab in the origin runs this block at a time
|
|
167
|
+
await persist();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// With options
|
|
171
|
+
await tabs.lock('queue', work, {
|
|
172
|
+
timeout: 30_000, // give up acquiring after 30s
|
|
173
|
+
pollInterval: 50,
|
|
174
|
+
staleAfter: 5_000, // assume the holder died if no heartbeat for 5s
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Duplicate detection
|
|
179
|
+
|
|
180
|
+
When a user picks "Duplicate tab" in Chrome/Firefox, the new tab inherits the original's `sessionStorage`. TabJS stamps each tab with a `lineage` id in `sessionStorage` on first run — if a heartbeat arrives from another live tab with the same lineage, this tab is a duplicate.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
if (tabs.isDuplicate) showWarning();
|
|
184
|
+
|
|
185
|
+
tabs.on('duplicate', (e) => {
|
|
186
|
+
console.log('shares lineage with', e.originals.map((t) => t.id));
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Singleton tab
|
|
191
|
+
|
|
192
|
+
Refuse a second tab — close it, redirect it, or show an overlay.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
tabs.singleton((others) => {
|
|
196
|
+
// Called when this tab boots and another is already alive
|
|
197
|
+
location.href = '/already-open';
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Metadata
|
|
202
|
+
|
|
203
|
+
Attach arbitrary metadata to this tab so other tabs can see it on the next heartbeat.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
tabs.setMeta({ role: 'admin', viewing: '/dashboard' });
|
|
207
|
+
tabs.tabs.find((t) => t.id !== tabs.id)?.meta;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Metrics
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
tabs.metrics;
|
|
214
|
+
// {
|
|
215
|
+
// messagesSent, messagesReceived,
|
|
216
|
+
// broadcasts, requests, responses,
|
|
217
|
+
// stateUpdates, locksAcquired,
|
|
218
|
+
// }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Options
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
new TabManager<AppState>({
|
|
225
|
+
namespace: 'myapp', // scopes storage keys + channel name (default 'tabjs')
|
|
226
|
+
initialState: { ... }, // used only if no peer has state yet
|
|
227
|
+
heartbeatInterval: 1500, // ms between heartbeats
|
|
228
|
+
tabTimeout: 5000, // ms after which a silent tab is evicted
|
|
229
|
+
broadcastChannelOnly: false, // skip the localStorage fallback
|
|
230
|
+
storageOnly: false, // skip BroadcastChannel
|
|
231
|
+
meta: { role: 'admin' }, // initial metadata for this tab
|
|
232
|
+
window: iframe.contentWindow, // override for tests / iframes
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Singleton vs. instance
|
|
237
|
+
|
|
238
|
+
`getTabs()` returns a lazy singleton — convenient for app code. For tests or iframes, instantiate directly:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { TabManager, getTabs, resetTabsSingleton } from '@buildwithdarsh/tabjs';
|
|
242
|
+
|
|
243
|
+
const a = getTabs<MyState>(); // app-wide
|
|
244
|
+
const b = new TabManager<MyState>(); // independent instance
|
|
245
|
+
resetTabsSingleton(); // tests
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## React example
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
252
|
+
import { getTabs } from '@buildwithdarsh/tabjs';
|
|
253
|
+
|
|
254
|
+
const tabs = getTabs<{ theme: 'dark' | 'light' }>();
|
|
255
|
+
|
|
256
|
+
export function useSharedState() {
|
|
257
|
+
return useSyncExternalStore(
|
|
258
|
+
(cb) => tabs.subscribe(cb),
|
|
259
|
+
() => tabs.getState(),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function useLiveTabs() {
|
|
264
|
+
const [list, setList] = useState(tabs.tabs);
|
|
265
|
+
useEffect(() => tabs.subscribe(() => setList(tabs.tabs)), []);
|
|
266
|
+
return list;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Transport details
|
|
271
|
+
|
|
272
|
+
Each envelope is sent over **both** transports (when available) and deduplicated on receive via a per-instance LRU of message ids:
|
|
273
|
+
|
|
274
|
+
- **BroadcastChannel** — fast, no storage churn, supported in every modern browser.
|
|
275
|
+
- **localStorage `storage` event** — fires in every other tab on the same origin. Used as a fallback for older browsers and for tabs where `BroadcastChannel` is blocked.
|
|
276
|
+
|
|
277
|
+
Force one or the other with `broadcastChannelOnly: true` or `storageOnly: true`.
|
|
278
|
+
|
|
279
|
+
## Demo
|
|
280
|
+
|
|
281
|
+
A full live playground is shipped in `example/index.html` — live tab list, leader badge, shared counter & theme, broadcast/direct/request controls, lock contention, duplicate-tab banner, and a real-time event log.
|
|
282
|
+
|
|
283
|
+
To run locally:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npm install
|
|
287
|
+
npm run build
|
|
288
|
+
npx serve example
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Then open the URL in two tabs.
|
|
292
|
+
|
|
293
|
+
## Development
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm install
|
|
297
|
+
npm run typecheck # tsc --noEmit
|
|
298
|
+
npm test # vitest run
|
|
299
|
+
npm run build # rollup → dist/
|
|
300
|
+
npm run dev # rollup --watch
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Publishing
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npm publish --access public
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The `prepublishOnly` script runs typecheck, tests, and the rollup build.
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
MIT
|
package/dist/tab.d.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
type TabId = string;
|
|
2
|
+
type LineageId = string;
|
|
3
|
+
interface TabInfo {
|
|
4
|
+
/** Stable per-tab id assigned at construction. */
|
|
5
|
+
id: TabId;
|
|
6
|
+
/** Wall-clock time the tab was opened (used as a tie-breaker for leader election). */
|
|
7
|
+
bornAt: number;
|
|
8
|
+
/** Last-seen heartbeat timestamp from this tab. */
|
|
9
|
+
lastSeen: number;
|
|
10
|
+
/** Is the tab visible/focused? */
|
|
11
|
+
focused: boolean;
|
|
12
|
+
/** Optional metadata the tab attached to itself via setMeta(). */
|
|
13
|
+
meta: Record<string, unknown>;
|
|
14
|
+
/** Where the tab is in the app (window.location.href at the time of last update). */
|
|
15
|
+
url: string;
|
|
16
|
+
/** Lineage id from sessionStorage — duplicated tabs inherit the same lineage. */
|
|
17
|
+
lineage: LineageId;
|
|
18
|
+
}
|
|
19
|
+
type TabEventType = 'open' | 'close' | 'focus' | 'blur' | 'leader' | 'message' | 'state' | 'duplicate';
|
|
20
|
+
interface TabOpenEvent {
|
|
21
|
+
type: 'open';
|
|
22
|
+
tab: TabInfo;
|
|
23
|
+
}
|
|
24
|
+
interface TabCloseEvent {
|
|
25
|
+
type: 'close';
|
|
26
|
+
tab: TabInfo;
|
|
27
|
+
}
|
|
28
|
+
interface TabFocusEvent {
|
|
29
|
+
type: 'focus' | 'blur';
|
|
30
|
+
tab: TabInfo;
|
|
31
|
+
}
|
|
32
|
+
interface TabLeaderEvent {
|
|
33
|
+
type: 'leader';
|
|
34
|
+
/** The new leader. May be this tab. */
|
|
35
|
+
leader: TabInfo;
|
|
36
|
+
/** The previous leader, if any. */
|
|
37
|
+
previous: TabInfo | null;
|
|
38
|
+
}
|
|
39
|
+
interface TabMessageEvent<TPayload = unknown> {
|
|
40
|
+
type: 'message';
|
|
41
|
+
channel: string;
|
|
42
|
+
from: TabId;
|
|
43
|
+
to: TabId | null;
|
|
44
|
+
payload: TPayload;
|
|
45
|
+
}
|
|
46
|
+
interface TabStateEvent<TState = unknown> {
|
|
47
|
+
type: 'state';
|
|
48
|
+
state: TState;
|
|
49
|
+
previous: TState | null;
|
|
50
|
+
from: TabId;
|
|
51
|
+
}
|
|
52
|
+
interface TabDuplicateEvent {
|
|
53
|
+
type: 'duplicate';
|
|
54
|
+
/** Other live tabs that share this lineage. */
|
|
55
|
+
originals: TabInfo[];
|
|
56
|
+
}
|
|
57
|
+
type TabEvent<TState = unknown, TPayload = unknown> = TabOpenEvent | TabCloseEvent | TabFocusEvent | TabLeaderEvent | TabMessageEvent<TPayload> | TabStateEvent<TState> | TabDuplicateEvent;
|
|
58
|
+
type Listener<TState = unknown, TPayload = unknown> = (event: TabEvent<TState, TPayload>) => void;
|
|
59
|
+
type Unsubscribe = () => void;
|
|
60
|
+
type MessageHandler<TPayload = unknown, TReply = unknown> = (payload: TPayload, context: {
|
|
61
|
+
from: TabId;
|
|
62
|
+
channel: string;
|
|
63
|
+
}) => TReply | Promise<TReply> | void | Promise<void>;
|
|
64
|
+
interface TabManagerOptions<TState = unknown> {
|
|
65
|
+
/** Override window — useful for testing or iframes. */
|
|
66
|
+
window?: Window;
|
|
67
|
+
/** Namespace used to scope storage keys and broadcast channels. Default: 'tabjs'. */
|
|
68
|
+
namespace?: string;
|
|
69
|
+
/** Initial shared state. Used when no other tab has written state yet. */
|
|
70
|
+
initialState?: TState;
|
|
71
|
+
/** Heartbeat interval in ms. Default: 1500. */
|
|
72
|
+
heartbeatInterval?: number;
|
|
73
|
+
/** Tabs are considered closed after this many ms with no heartbeat. Default: 5000. */
|
|
74
|
+
tabTimeout?: number;
|
|
75
|
+
/** Disable the localStorage fallback and use BroadcastChannel only. Default: false. */
|
|
76
|
+
broadcastChannelOnly?: boolean;
|
|
77
|
+
/** Disable BroadcastChannel and use localStorage only. Default: false. */
|
|
78
|
+
storageOnly?: boolean;
|
|
79
|
+
/** Optional metadata to attach to this tab. */
|
|
80
|
+
meta?: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
interface RequestOptions {
|
|
83
|
+
/** How long to wait before rejecting with a timeout error. Default: 5000ms. */
|
|
84
|
+
timeout?: number;
|
|
85
|
+
}
|
|
86
|
+
interface LockOptions {
|
|
87
|
+
/** Max time in ms to wait acquiring the lock before rejecting. Default: 30000. */
|
|
88
|
+
timeout?: number;
|
|
89
|
+
/** Polling interval in ms while waiting for the lock. Default: 50. */
|
|
90
|
+
pollInterval?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Time in ms after which a held lock is considered abandoned (if the holder
|
|
93
|
+
* stops sending heartbeats). Default: 5000.
|
|
94
|
+
*/
|
|
95
|
+
staleAfter?: number;
|
|
96
|
+
}
|
|
97
|
+
/** Snapshot returned by `tabs.metrics`. */
|
|
98
|
+
interface TabMetrics {
|
|
99
|
+
messagesSent: number;
|
|
100
|
+
messagesReceived: number;
|
|
101
|
+
broadcasts: number;
|
|
102
|
+
requests: number;
|
|
103
|
+
responses: number;
|
|
104
|
+
stateUpdates: number;
|
|
105
|
+
locksAcquired: number;
|
|
106
|
+
}
|
|
107
|
+
/** @internal — wire envelope used by the transport layer. */
|
|
108
|
+
interface Envelope<TPayload = unknown> {
|
|
109
|
+
/** Random id used to deduplicate echoes from the storage fallback. */
|
|
110
|
+
_id: string;
|
|
111
|
+
/** Sender tab id. */
|
|
112
|
+
from: TabId;
|
|
113
|
+
/** Recipient tab id, or null for broadcast. */
|
|
114
|
+
to: TabId | null;
|
|
115
|
+
/** Message kind — used by the manager to dispatch internally. */
|
|
116
|
+
kind: string;
|
|
117
|
+
/** User-visible "channel" / message type. */
|
|
118
|
+
channel: string;
|
|
119
|
+
/** User payload. */
|
|
120
|
+
payload: TPayload;
|
|
121
|
+
/** Sender wall-clock timestamp. */
|
|
122
|
+
ts: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
declare class TabManager<TState = unknown, TPayload = unknown> {
|
|
126
|
+
/** Stable id for this tab, generated at construction. */
|
|
127
|
+
readonly id: TabId;
|
|
128
|
+
/** Wall-clock time the tab was opened. */
|
|
129
|
+
readonly bornAt: number;
|
|
130
|
+
/** Lineage id from sessionStorage — shared with duplicated tabs. */
|
|
131
|
+
readonly lineage: LineageId;
|
|
132
|
+
readonly metrics: TabMetrics;
|
|
133
|
+
private readonly win;
|
|
134
|
+
private readonly namespace;
|
|
135
|
+
private readonly heartbeatInterval;
|
|
136
|
+
private readonly tabTimeout;
|
|
137
|
+
private readonly transport;
|
|
138
|
+
private readonly listeners;
|
|
139
|
+
private readonly handlers;
|
|
140
|
+
private readonly registry;
|
|
141
|
+
private readonly pending;
|
|
142
|
+
private readonly heldLocks;
|
|
143
|
+
private readonly stateKey;
|
|
144
|
+
private readonly lockKeyPrefix;
|
|
145
|
+
private heartbeatTimer;
|
|
146
|
+
private sweepTimer;
|
|
147
|
+
private leaderId;
|
|
148
|
+
private destroyed;
|
|
149
|
+
private meta;
|
|
150
|
+
private focused;
|
|
151
|
+
private state;
|
|
152
|
+
private readonly onVisibility;
|
|
153
|
+
private readonly onFocus;
|
|
154
|
+
private readonly onBlur;
|
|
155
|
+
private readonly onBeforeUnload;
|
|
156
|
+
constructor(options?: TabManagerOptions<TState>);
|
|
157
|
+
/** This tab's current info snapshot. */
|
|
158
|
+
get info(): TabInfo;
|
|
159
|
+
/** Snapshot list of all known live tabs (including this one). */
|
|
160
|
+
get tabs(): TabInfo[];
|
|
161
|
+
/** True if this tab is currently the elected leader. */
|
|
162
|
+
get isLeader(): boolean;
|
|
163
|
+
/** Current leader tab info, or null if none is known. */
|
|
164
|
+
get leader(): TabInfo | null;
|
|
165
|
+
/** Current shared state — synchronized across tabs. */
|
|
166
|
+
getState(): TState | null;
|
|
167
|
+
/**
|
|
168
|
+
* True if this tab shares a sessionStorage lineage with another live tab —
|
|
169
|
+
* i.e. it was likely duplicated via "Duplicate tab".
|
|
170
|
+
*/
|
|
171
|
+
get isDuplicate(): boolean;
|
|
172
|
+
/** Subscribe to all tab events. */
|
|
173
|
+
subscribe(listener: Listener<TState, TPayload>): Unsubscribe;
|
|
174
|
+
/** Subscribe to a single event type. */
|
|
175
|
+
on<E extends TabEvent<TState, TPayload>['type']>(type: E, listener: (event: Extract<TabEvent<TState, TPayload>, {
|
|
176
|
+
type: E;
|
|
177
|
+
}>) => void): Unsubscribe;
|
|
178
|
+
/** Broadcast a message to every other tab. */
|
|
179
|
+
broadcast<P = TPayload>(channel: string, payload?: P): void;
|
|
180
|
+
/** Send a direct message to a specific tab. */
|
|
181
|
+
send<P = TPayload>(targetId: TabId, channel: string, payload?: P): void;
|
|
182
|
+
/** Register a handler for an incoming message channel. Used by `request()`. */
|
|
183
|
+
handle<P = unknown, R = unknown>(channel: string, handler: MessageHandler<P, R>): Unsubscribe;
|
|
184
|
+
/**
|
|
185
|
+
* Send a request to a specific tab (or leader if `targetId` is null) and
|
|
186
|
+
* await a response. Resolves with whatever the handler returned, or rejects
|
|
187
|
+
* on timeout.
|
|
188
|
+
*/
|
|
189
|
+
request<P = unknown, R = unknown>(targetId: TabId | null, channel: string, payload?: P, options?: RequestOptions): Promise<R>;
|
|
190
|
+
/** Replace shared state and broadcast the update to other tabs. */
|
|
191
|
+
setState(updater: TState | ((prev: TState | null) => TState)): void;
|
|
192
|
+
/** Replace the metadata for this tab. Broadcast on the next heartbeat. */
|
|
193
|
+
setMeta(meta: Record<string, unknown>): void;
|
|
194
|
+
/**
|
|
195
|
+
* Enforce a single live tab per origin. If another tab is already open when
|
|
196
|
+
* this one starts, `onConflict` is called. Returns a teardown function.
|
|
197
|
+
*/
|
|
198
|
+
singleton(onConflict: (others: TabInfo[]) => void): Unsubscribe;
|
|
199
|
+
/**
|
|
200
|
+
* Acquire a cross-tab lock for the duration of `fn`. Other tabs calling
|
|
201
|
+
* `lock(key, ...)` for the same key will queue behind this one. The lock is
|
|
202
|
+
* released automatically when `fn` settles (or after `staleAfter` ms if the
|
|
203
|
+
* holder tab crashes).
|
|
204
|
+
*/
|
|
205
|
+
lock<T>(key: string, fn: () => T | Promise<T>, options?: LockOptions): Promise<T>;
|
|
206
|
+
destroy(): void;
|
|
207
|
+
private resolveLineage;
|
|
208
|
+
private snapshotSelf;
|
|
209
|
+
private beat;
|
|
210
|
+
private sweep;
|
|
211
|
+
private updatePresence;
|
|
212
|
+
private sayBye;
|
|
213
|
+
private electLeader;
|
|
214
|
+
private broadcastEnvelope;
|
|
215
|
+
private handleEnvelope;
|
|
216
|
+
private onHeartbeat;
|
|
217
|
+
private onBye;
|
|
218
|
+
private onMessage;
|
|
219
|
+
private onRequest;
|
|
220
|
+
private onResponse;
|
|
221
|
+
private onStateSet;
|
|
222
|
+
private onStateGet;
|
|
223
|
+
private onStateReply;
|
|
224
|
+
private emit;
|
|
225
|
+
private readStoredState;
|
|
226
|
+
private writeStoredState;
|
|
227
|
+
private lockKey;
|
|
228
|
+
private readLock;
|
|
229
|
+
private writeLock;
|
|
230
|
+
private acquireLock;
|
|
231
|
+
private releaseLock;
|
|
232
|
+
/** Refresh held locks so other tabs don't consider them stale. */
|
|
233
|
+
private refreshHeldLocks;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
type TransportListener = (env: Envelope) => void;
|
|
237
|
+
/**
|
|
238
|
+
* Wire-level transport that fans messages out via BroadcastChannel and/or
|
|
239
|
+
* localStorage events. Each envelope carries a random id so duplicates from
|
|
240
|
+
* the dual-write path can be deduplicated on receive.
|
|
241
|
+
*/
|
|
242
|
+
declare class Transport {
|
|
243
|
+
private readonly win;
|
|
244
|
+
private readonly storageKey;
|
|
245
|
+
private readonly channelName;
|
|
246
|
+
private readonly listeners;
|
|
247
|
+
private readonly seen;
|
|
248
|
+
private readonly seenOrder;
|
|
249
|
+
private readonly maxSeen;
|
|
250
|
+
private channel;
|
|
251
|
+
private readonly useChannel;
|
|
252
|
+
private readonly useStorage;
|
|
253
|
+
private readonly onStorage;
|
|
254
|
+
private readonly onChannelMessage;
|
|
255
|
+
private destroyed;
|
|
256
|
+
constructor(win: Window, namespace: string, options?: {
|
|
257
|
+
broadcastChannelOnly?: boolean;
|
|
258
|
+
storageOnly?: boolean;
|
|
259
|
+
});
|
|
260
|
+
/** Subscribe to incoming envelopes. Returns an unsubscribe function. */
|
|
261
|
+
subscribe(fn: TransportListener): () => void;
|
|
262
|
+
/** Send an envelope to all other tabs. */
|
|
263
|
+
send(env: Envelope): void;
|
|
264
|
+
/** Whether the transport is wired up to anything that can deliver messages. */
|
|
265
|
+
get isLive(): boolean;
|
|
266
|
+
destroy(): void;
|
|
267
|
+
private markSeen;
|
|
268
|
+
private receive;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Lazy singleton — convenient for app code that just wants "the" tab manager.
|
|
273
|
+
* Pass options the first time to configure. Subsequent calls ignore options.
|
|
274
|
+
* Use `new TabManager()` directly if you need multiple instances.
|
|
275
|
+
*/
|
|
276
|
+
declare function getTabs<TState = unknown, TPayload = unknown>(options?: TabManagerOptions<TState>): TabManager<TState, TPayload>;
|
|
277
|
+
/** Reset the singleton — primarily for tests. */
|
|
278
|
+
declare function resetTabsSingleton(): void;
|
|
279
|
+
declare const defaultExport: typeof getTabs & {
|
|
280
|
+
TabManager: typeof TabManager;
|
|
281
|
+
getTabs: typeof getTabs;
|
|
282
|
+
resetTabsSingleton: typeof resetTabsSingleton;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
export { TabManager, Transport, defaultExport as default, getTabs, resetTabsSingleton };
|
|
286
|
+
export type { Envelope, LineageId, Listener, LockOptions, MessageHandler, RequestOptions, TabCloseEvent, TabDuplicateEvent, TabEvent, TabEventType, TabFocusEvent, TabId, TabInfo, TabLeaderEvent, TabManagerOptions, TabMessageEvent, TabMetrics, TabOpenEvent, TabStateEvent, Unsubscribe };
|
package/dist/tab.esm.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let t=0;function s(s="tab"){t+=1;const i=Math.random().toString(36).slice(2,8);return`${s}_${Date.now().toString(36)}_${t.toString(36)}_${i}`}function i(t){if(null==t)return null;try{return JSON.parse(t)}catch{return null}}function h(t){return JSON.stringify(t)}function e(){return Date.now()}function r(t){try{return null!=t.localStorage}catch{return!1}}class n{constructor(t,s,h={}){this.listeners=new Set,this.seen=new Set,this.seenOrder=[],this.maxSeen=256,this.channel=null,this.destroyed=!1,this.win=t,this.storageKey=`__${s}__msg__`,this.channelName=`${s}_channel`;const e=function(t){return"function"==typeof t.BroadcastChannel}(t)&&!h.storageOnly,n=r(t)&&!h.broadcastChannelOnly;this.useChannel=e,this.useStorage=n,this.onChannelMessage=t=>{const s=t.data;s&&"object"==typeof s&&s.t&&this.receive(s)},this.onStorage=t=>{if(t.key!==this.storageKey||!t.newValue)return;const s=i(t.newValue);s&&s.t&&this.receive(s)},this.useChannel&&(this.channel=new t.BroadcastChannel(this.channelName),this.channel.addEventListener("message",this.onChannelMessage)),this.useStorage&&this.win.addEventListener("storage",this.onStorage)}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}send(t){if(!this.destroyed){if(this.markSeen(t.t),this.useChannel&&this.channel)try{this.channel.postMessage(t)}catch{}if(this.useStorage)try{const s=this.storageKey,i=h(t);this.win.localStorage.setItem(s,i),this.win.localStorage.removeItem(s)}catch{}}}get isLive(){return!this.destroyed&&(this.useChannel||this.useStorage)}destroy(){this.destroyed||(this.destroyed=!0,this.channel&&(this.channel.removeEventListener("message",this.onChannelMessage),this.channel.close(),this.channel=null),this.useStorage&&this.win.removeEventListener("storage",this.onStorage),this.listeners.clear())}markSeen(t){if(!this.seen.has(t)&&(this.seen.add(t),this.seenOrder.push(t),this.seenOrder.length>this.maxSeen)){const t=this.seenOrder.shift();t&&this.seen.delete(t)}}receive(t){if(!this.seen.has(t.t)){this.markSeen(t.t);for(const s of Array.from(this.listeners))try{s(t)}catch(t){console.error("[TabJS] transport listener threw:",t)}}}}const o="heartbeat",a="msg",l="res",c="state-set",u="state-get",f="state-reply";class b{constructor(t={}){this.metrics={messagesSent:0,messagesReceived:0,broadcasts:0,requests:0,responses:0,stateUpdates:0,locksAcquired:0},this.listeners=new Set,this.handlers=new Map,this.registry=new Map,this.pending=new Map,this.heldLocks=new Map,this.heartbeatTimer=null,this.sweepTimer=null,this.leaderId=null,this.destroyed=!1,this.win=function(t){if(t)return t;if("undefined"!=typeof window)return window;throw new Error("[TabJS] TabManager requires a window. Pass `options.window` when running outside the browser.")}(t.window),this.namespace=t.namespace??"tabjs",this.heartbeatInterval=t.heartbeatInterval??1500,this.tabTimeout=t.tabTimeout??5e3,this.meta=t.meta?{...t.meta}:{},this.id=s("tab"),this.bornAt=e(),this.lineage=this.resolveLineage(),this.stateKey=`__${this.namespace}__state__`,this.lockKeyPrefix=`__${this.namespace}__lock__`;const i=this.win.document?.visibilityState;this.focused=(null==i||"visible"===i)&&!1!==this.win.document?.hasFocus?.(),this.transport=new n(this.win,this.namespace,{broadcastChannelOnly:t.broadcastChannelOnly,storageOnly:t.storageOnly}),this.transport.subscribe(t=>this.handleEnvelope(t)),this.state=this.readStoredState()??t.initialState??null,this.registry.set(this.id,this.snapshotSelf()),this.leaderId=this.id,this.onVisibility=()=>this.updatePresence(),this.onFocus=()=>this.updatePresence(),this.onBlur=()=>this.updatePresence(),this.onBeforeUnload=()=>this.sayBye(),this.win.document?.addEventListener?.("visibilitychange",this.onVisibility),this.win.addEventListener?.("focus",this.onFocus),this.win.addEventListener?.("blur",this.onBlur),this.win.addEventListener?.("beforeunload",this.onBeforeUnload),this.win.addEventListener?.("pagehide",this.onBeforeUnload),this.beat(),this.heartbeatTimer=this.win.setInterval(()=>this.beat(),this.heartbeatInterval),this.sweepTimer=this.win.setInterval(()=>this.sweep(),this.heartbeatInterval),this.broadcastEnvelope(u,"",null,null)}get info(){return this.snapshotSelf()}get tabs(){return Array.from(this.registry.values())}get isLeader(){return this.leaderId===this.id}get leader(){return this.leaderId?this.registry.get(this.leaderId)??null:null}getState(){return this.state}get isDuplicate(){for(const t of this.registry.values())if(t.id!==this.id&&t.lineage===this.lineage)return!0;return!1}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}on(t,s){return this.subscribe(i=>{i.type===t&&s(i)})}broadcast(t,s){this.broadcastEnvelope(a,t,null,s??null),this.metrics.broadcasts+=1}send(t,s,i){this.broadcastEnvelope(a,s,t,i??null)}handle(t,s){return this.handlers.set(t,s),()=>{this.handlers.get(t)===s&&this.handlers.delete(t)}}request(t,i,h,e={}){const r=t??this.leaderId??null;if(!r)return Promise.reject(new Error("[TabJS] no target tab for request"));const n=e.timeout??5e3;return new Promise((t,e)=>{const o=s("req"),a=this.win.setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),e(new Error(`[TabJS] request "${i}" timed out after ${n}ms`)))},n);this.pending.set(o,{resolve:t,reject:e,timer:a}),this.broadcastEnvelope("req",i,r,{reqId:o,payload:h??null}),this.metrics.requests+=1})}setState(t){const s="function"==typeof t?t(this.state):t,i=this.state;this.state=s,this.writeStoredState(s),this.metrics.stateUpdates+=1,this.broadcastEnvelope(c,"",null,s),this.emit({type:"state",state:s,previous:i,from:this.id})}setMeta(t){this.meta={...t},this.beat()}singleton(t){const s=()=>{const s=this.tabs.filter(t=>t.id!==this.id);s.length>0&&t(s)},i=this.win.setTimeout(s,2*this.heartbeatInterval),h=this.on("open",s);return()=>{this.win.clearTimeout(i),h()}}async lock(t,s,i={}){const h=await this.acquireLock(t,i);try{return this.metrics.locksAcquired+=1,await s()}finally{this.releaseLock(t,h)}}destroy(){if(!this.destroyed){this.sayBye(),this.destroyed=!0,this.heartbeatTimer&&this.win.clearInterval(this.heartbeatTimer),this.sweepTimer&&this.win.clearInterval(this.sweepTimer),this.win.document?.removeEventListener?.("visibilitychange",this.onVisibility),this.win.removeEventListener?.("focus",this.onFocus),this.win.removeEventListener?.("blur",this.onBlur),this.win.removeEventListener?.("beforeunload",this.onBeforeUnload),this.win.removeEventListener?.("pagehide",this.onBeforeUnload);for(const[,t]of this.pending)t.timer&&this.win.clearTimeout(t.timer),t.reject(new Error("[TabJS] TabManager destroyed before request resolved"));this.pending.clear();for(const[t,s]of this.heldLocks)this.releaseLock(t,s.token);this.transport.destroy(),this.listeners.clear(),this.handlers.clear(),this.registry.clear()}}resolveLineage(){const t=`__${this.namespace??"tabjs"}__lineage__`;try{const i=this.win.sessionStorage?.getItem(t);if(i)return i;const h=s("lin");return this.win.sessionStorage?.setItem(t,h),h}catch{return s("lin")}}snapshotSelf(){return{id:this.id,bornAt:this.bornAt,lastSeen:e(),focused:this.focused,meta:this.meta,url:this.win.location?.href??"",lineage:this.lineage}}beat(){if(this.destroyed)return;const t=this.snapshotSelf();this.registry.set(this.id,t),this.broadcastEnvelope(o,"",null,t),this.electLeader(),this.refreshHeldLocks()}sweep(){if(this.destroyed)return;const t=e()-this.tabTimeout;let s=!1;const i=[];for(const[h,e]of Array.from(this.registry.entries()))h!==this.id&&(e.lastSeen<t?(this.registry.delete(h),s=!0,this.emit({type:"close",tab:e})):e.lineage===this.lineage&&i.push(e));s&&this.electLeader(),i.length>0&&this.emit({type:"duplicate",originals:i})}updatePresence(){const t=this.win.document?.visibilityState,s=(null==t||"visible"===t)&&!1!==this.win.document?.hasFocus?.();if(s===this.focused)return;this.focused=s;const i=this.snapshotSelf();this.registry.set(this.id,i),this.broadcastEnvelope(o,"",null,i),this.emit({type:s?"focus":"blur",tab:i})}sayBye(){try{this.broadcastEnvelope("bye","",null,{id:this.id})}catch{}}electLeader(){let t=null;for(const s of this.registry.values())t?(s.bornAt<t.bornAt||s.bornAt===t.bornAt&&s.id<t.id)&&(t=s):t=s;const s=t?.id??null;if(s!==this.leaderId){const i=this.leaderId?this.registry.get(this.leaderId)??null:null;this.leaderId=s,t&&this.emit({type:"leader",leader:t,previous:i})}}broadcastEnvelope(t,i,h,r){if(this.destroyed)return;const n={t:s("e"),from:this.id,to:h,kind:t,channel:i,payload:r,ts:e()};this.transport.send(n),this.metrics.messagesSent+=1}handleEnvelope(t){if(t.from!==this.id&&(null===t.to||t.to===this.id))switch(this.metrics.messagesReceived+=1,t.kind){case o:this.onHeartbeat(t.payload);break;case"bye":this.onBye(t.payload.id);break;case a:this.onMessage(t);break;case"req":this.onRequest(t);break;case l:this.onResponse(t);break;case c:this.onStateSet(t);break;case u:this.onStateGet(t);break;case f:this.onStateReply(t)}}onHeartbeat(t){if(!t||"string"!=typeof t.id)return;const s=this.registry.get(t.id);this.registry.set(t.id,t),s?s.focused!==t.focused&&this.emit({type:t.focused?"focus":"blur",tab:t}):(this.emit({type:"open",tab:t}),t.lineage===this.lineage&&this.emit({type:"duplicate",originals:[t]}),this.electLeader())}onBye(t){const s=this.registry.get(t);s&&(this.registry.delete(t),this.emit({type:"close",tab:s}),this.electLeader())}onMessage(t){this.emit({type:"message",channel:t.channel,from:t.from,to:t.to,payload:t.payload})}async onRequest(t){const s=this.handlers.get(t.channel),i=t.payload;if(s)try{const h=await s(i.payload,{from:t.from,channel:t.channel});this.broadcastEnvelope(l,t.channel,t.from,{reqId:i.reqId,result:h}),this.metrics.responses+=1}catch(s){this.broadcastEnvelope(l,t.channel,t.from,{reqId:i.reqId,error:s instanceof Error?s.message:String(s)})}else this.broadcastEnvelope(l,t.channel,t.from,{reqId:i.reqId,error:`no handler for "${t.channel}"`})}onResponse(t){const s=t.payload,i=this.pending.get(s.reqId);i&&(this.pending.delete(s.reqId),i.timer&&this.win.clearTimeout(i.timer),s.error?i.reject(new Error(s.error)):i.resolve(s.result))}onStateSet(t){const s=this.state;this.state=t.payload,this.writeStoredState(this.state),this.emit({type:"state",state:this.state,previous:s,from:t.from})}onStateGet(t){null!=this.state&&this.broadcastEnvelope(f,"",t.from,this.state)}onStateReply(t){if(null!=this.state)return;const s=this.state;this.state=t.payload,this.writeStoredState(this.state),this.emit({type:"state",state:this.state,previous:s,from:t.from})}emit(t){for(const s of Array.from(this.listeners))try{s(t)}catch(t){console.error("[TabJS] listener threw:",t)}}readStoredState(){if(!r(this.win))return null;try{return i(this.win.localStorage.getItem(this.stateKey))}catch{return null}}writeStoredState(t){if(r(this.win))try{null==t?this.win.localStorage.removeItem(this.stateKey):this.win.localStorage.setItem(this.stateKey,h(t))}catch{}}lockKey(t){return`${this.lockKeyPrefix}${t}`}readLock(t){return r(this.win)?i(this.win.localStorage.getItem(this.lockKey(t))):null}writeLock(t,s){if(!r(this.win))return;const i=this.lockKey(t);try{null==s?this.win.localStorage.removeItem(i):this.win.localStorage.setItem(i,h(s))}catch{}}async acquireLock(t,i){if(!r(this.win))throw new Error("[TabJS] lock() requires localStorage");const h=i.timeout??3e4,n=i.pollInterval??50,o=i.staleAfter??5e3,a=e()+h,l=s("lk");for(;;){const s=this.readLock(t);if(!(s&&e()-s.heartbeat<o)){const s={owner:this.id,token:l,acquiredAt:e(),heartbeat:e()};this.writeLock(t,s);const i=this.readLock(t);if(i&&i.token===l)return this.heldLocks.set(t,{token:l,release:()=>this.releaseLock(t,l)}),l}if(e()>=a)throw new Error(`[TabJS] lock "${t}" timed out after ${h}ms`);await new Promise(t=>this.win.setTimeout(t,n))}}releaseLock(t,s){const i=this.readLock(t);i&&i.token===s&&(this.writeLock(t,null),this.broadcastEnvelope("lock-release",t,null,null)),this.heldLocks.delete(t)}refreshHeldLocks(){if(0!==this.heldLocks.size)for(const[t,s]of this.heldLocks){const i=this.readLock(t);i&&i.token===s.token?(i.heartbeat=e(),this.writeLock(t,i)):this.heldLocks.delete(t)}}}let d=null;function y(t){return d||(d=new b(t)),d}function p(){d?.destroy(),d=null}const w=Object.assign(y,{TabManager:b,getTabs:y,resetTabsSingleton:p});export{b as TabManager,n as Transport,w as default,y as getTabs,p as resetTabsSingleton};
|
|
2
|
+
//# sourceMappingURL=tab.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab.esm.js","sources":["../src/utils.ts","../src/transport.ts","../src/manager.ts","../src/index.ts"],"sourcesContent":[null,null,null,null],"names":["idCounter","makeId","prefix","rand","Math","random","toString","slice","Date","now","safeJsonParse","raw","JSON","parse","safeJsonStringify","value","stringify","hasStorage","win","localStorage","Transport","constructor","namespace","options","this","listeners","Set","seen","seenOrder","maxSeen","channel","destroyed","storageKey","channelName","canChannel","BroadcastChannel","hasBroadcastChannel","storageOnly","canStorage","broadcastChannelOnly","useChannel","useStorage","onChannelMessage","e","env","data","_id","receive","onStorage","key","newValue","addEventListener","subscribe","fn","add","delete","send","markSeen","postMessage","setItem","removeItem","isLive","destroy","removeEventListener","close","clear","id","has","push","length","drop","shift","Array","from","err","console","error","KIND_HEARTBEAT","KIND_MSG","KIND_RES","KIND_STATE_SET","KIND_STATE_GET","KIND_STATE_REPLY","TabManager","metrics","messagesSent","messagesReceived","broadcasts","requests","responses","stateUpdates","locksAcquired","handlers","Map","registry","pending","heldLocks","heartbeatTimer","sweepTimer","leaderId","maybeWin","window","Error","resolveWindow","heartbeatInterval","tabTimeout","meta","bornAt","lineage","resolveLineage","stateKey","lockKeyPrefix","visible","document","visibilityState","focused","hasFocus","transport","handleEnvelope","state","readStoredState","initialState","set","snapshotSelf","onVisibility","updatePresence","onFocus","onBlur","onBeforeUnload","sayBye","beat","setInterval","sweep","broadcastEnvelope","info","tabs","values","isLeader","leader","get","getState","isDuplicate","tab","listener","on","type","event","broadcast","payload","targetId","handle","handler","request","target","Promise","reject","timeoutMs","timeout","resolve","reqId","timer","setTimeout","setState","updater","next","previous","writeStoredState","emit","setMeta","singleton","onConflict","check","others","filter","t","off","clearTimeout","lock","token","acquireLock","releaseLock","clearInterval","held","existing","sessionStorage","getItem","fresh","lastSeen","url","location","href","electLeader","refreshHeldLocks","cutoff","changed","duplicates","entries","originals","nextFocused","snap","candidate","nextLeaderId","kind","to","ts","onHeartbeat","onBye","onMessage","onRequest","onResponse","onStateSet","onStateGet","onStateReply","prev","body","result","message","String","lockKey","name","readLock","writeLock","record","pollMs","pollInterval","staleAfter","deadline","heartbeat","owner","acquiredAt","release","size","getTabs","resetTabsSingleton","defaultExport","Object","assign"],"mappings":"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"}
|
package/dist/tab.umd.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports):"function"==typeof define&&define.amd?define(["exports"],s):s((t="undefined"!=typeof globalThis?globalThis:t||self).Tab={})}(this,function(t){"use strict";let s=0;function i(t="tab"){s+=1;const i=Math.random().toString(36).slice(2,8);return`${t}_${Date.now().toString(36)}_${s.toString(36)}_${i}`}function e(t){if(null==t)return null;try{return JSON.parse(t)}catch{return null}}function h(t){return JSON.stringify(t)}function n(){return Date.now()}function r(t){try{return null!=t.localStorage}catch{return!1}}class o{constructor(t,s,i={}){this.listeners=new Set,this.seen=new Set,this.seenOrder=[],this.maxSeen=256,this.channel=null,this.destroyed=!1,this.win=t,this.storageKey=`__${s}__msg__`,this.channelName=`${s}_channel`;const h=function(t){return"function"==typeof t.BroadcastChannel}(t)&&!i.storageOnly,n=r(t)&&!i.broadcastChannelOnly;this.useChannel=h,this.useStorage=n,this.onChannelMessage=t=>{const s=t.data;s&&"object"==typeof s&&s.t&&this.receive(s)},this.onStorage=t=>{if(t.key!==this.storageKey||!t.newValue)return;const s=e(t.newValue);s&&s.t&&this.receive(s)},this.useChannel&&(this.channel=new t.BroadcastChannel(this.channelName),this.channel.addEventListener("message",this.onChannelMessage)),this.useStorage&&this.win.addEventListener("storage",this.onStorage)}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}send(t){if(!this.destroyed){if(this.markSeen(t.t),this.useChannel&&this.channel)try{this.channel.postMessage(t)}catch{}if(this.useStorage)try{const s=this.storageKey,i=h(t);this.win.localStorage.setItem(s,i),this.win.localStorage.removeItem(s)}catch{}}}get isLive(){return!this.destroyed&&(this.useChannel||this.useStorage)}destroy(){this.destroyed||(this.destroyed=!0,this.channel&&(this.channel.removeEventListener("message",this.onChannelMessage),this.channel.close(),this.channel=null),this.useStorage&&this.win.removeEventListener("storage",this.onStorage),this.listeners.clear())}markSeen(t){if(!this.seen.has(t)&&(this.seen.add(t),this.seenOrder.push(t),this.seenOrder.length>this.maxSeen)){const t=this.seenOrder.shift();t&&this.seen.delete(t)}}receive(t){if(!this.seen.has(t.t)){this.markSeen(t.t);for(const s of Array.from(this.listeners))try{s(t)}catch(t){console.error("[TabJS] transport listener threw:",t)}}}}const a="heartbeat",l="msg",c="res",u="state-set",f="state-get",d="state-reply";class b{constructor(t={}){this.metrics={messagesSent:0,messagesReceived:0,broadcasts:0,requests:0,responses:0,stateUpdates:0,locksAcquired:0},this.listeners=new Set,this.handlers=new Map,this.registry=new Map,this.pending=new Map,this.heldLocks=new Map,this.heartbeatTimer=null,this.sweepTimer=null,this.leaderId=null,this.destroyed=!1,this.win=function(t){if(t)return t;if("undefined"!=typeof window)return window;throw new Error("[TabJS] TabManager requires a window. Pass `options.window` when running outside the browser.")}(t.window),this.namespace=t.namespace??"tabjs",this.heartbeatInterval=t.heartbeatInterval??1500,this.tabTimeout=t.tabTimeout??5e3,this.meta=t.meta?{...t.meta}:{},this.id=i("tab"),this.bornAt=n(),this.lineage=this.resolveLineage(),this.stateKey=`__${this.namespace}__state__`,this.lockKeyPrefix=`__${this.namespace}__lock__`;const s=this.win.document?.visibilityState;this.focused=(null==s||"visible"===s)&&!1!==this.win.document?.hasFocus?.(),this.transport=new o(this.win,this.namespace,{broadcastChannelOnly:t.broadcastChannelOnly,storageOnly:t.storageOnly}),this.transport.subscribe(t=>this.handleEnvelope(t)),this.state=this.readStoredState()??t.initialState??null,this.registry.set(this.id,this.snapshotSelf()),this.leaderId=this.id,this.onVisibility=()=>this.updatePresence(),this.onFocus=()=>this.updatePresence(),this.onBlur=()=>this.updatePresence(),this.onBeforeUnload=()=>this.sayBye(),this.win.document?.addEventListener?.("visibilitychange",this.onVisibility),this.win.addEventListener?.("focus",this.onFocus),this.win.addEventListener?.("blur",this.onBlur),this.win.addEventListener?.("beforeunload",this.onBeforeUnload),this.win.addEventListener?.("pagehide",this.onBeforeUnload),this.beat(),this.heartbeatTimer=this.win.setInterval(()=>this.beat(),this.heartbeatInterval),this.sweepTimer=this.win.setInterval(()=>this.sweep(),this.heartbeatInterval),this.broadcastEnvelope(f,"",null,null)}get info(){return this.snapshotSelf()}get tabs(){return Array.from(this.registry.values())}get isLeader(){return this.leaderId===this.id}get leader(){return this.leaderId?this.registry.get(this.leaderId)??null:null}getState(){return this.state}get isDuplicate(){for(const t of this.registry.values())if(t.id!==this.id&&t.lineage===this.lineage)return!0;return!1}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}on(t,s){return this.subscribe(i=>{i.type===t&&s(i)})}broadcast(t,s){this.broadcastEnvelope(l,t,null,s??null),this.metrics.broadcasts+=1}send(t,s,i){this.broadcastEnvelope(l,s,t,i??null)}handle(t,s){return this.handlers.set(t,s),()=>{this.handlers.get(t)===s&&this.handlers.delete(t)}}request(t,s,e,h={}){const n=t??this.leaderId??null;if(!n)return Promise.reject(new Error("[TabJS] no target tab for request"));const r=h.timeout??5e3;return new Promise((t,h)=>{const o=i("req"),a=this.win.setTimeout(()=>{this.pending.has(o)&&(this.pending.delete(o),h(new Error(`[TabJS] request "${s}" timed out after ${r}ms`)))},r);this.pending.set(o,{resolve:t,reject:h,timer:a}),this.broadcastEnvelope("req",s,n,{reqId:o,payload:e??null}),this.metrics.requests+=1})}setState(t){const s="function"==typeof t?t(this.state):t,i=this.state;this.state=s,this.writeStoredState(s),this.metrics.stateUpdates+=1,this.broadcastEnvelope(u,"",null,s),this.emit({type:"state",state:s,previous:i,from:this.id})}setMeta(t){this.meta={...t},this.beat()}singleton(t){const s=()=>{const s=this.tabs.filter(t=>t.id!==this.id);s.length>0&&t(s)},i=this.win.setTimeout(s,2*this.heartbeatInterval),e=this.on("open",s);return()=>{this.win.clearTimeout(i),e()}}async lock(t,s,i={}){const e=await this.acquireLock(t,i);try{return this.metrics.locksAcquired+=1,await s()}finally{this.releaseLock(t,e)}}destroy(){if(!this.destroyed){this.sayBye(),this.destroyed=!0,this.heartbeatTimer&&this.win.clearInterval(this.heartbeatTimer),this.sweepTimer&&this.win.clearInterval(this.sweepTimer),this.win.document?.removeEventListener?.("visibilitychange",this.onVisibility),this.win.removeEventListener?.("focus",this.onFocus),this.win.removeEventListener?.("blur",this.onBlur),this.win.removeEventListener?.("beforeunload",this.onBeforeUnload),this.win.removeEventListener?.("pagehide",this.onBeforeUnload);for(const[,t]of this.pending)t.timer&&this.win.clearTimeout(t.timer),t.reject(new Error("[TabJS] TabManager destroyed before request resolved"));this.pending.clear();for(const[t,s]of this.heldLocks)this.releaseLock(t,s.token);this.transport.destroy(),this.listeners.clear(),this.handlers.clear(),this.registry.clear()}}resolveLineage(){const t=`__${this.namespace??"tabjs"}__lineage__`;try{const s=this.win.sessionStorage?.getItem(t);if(s)return s;const e=i("lin");return this.win.sessionStorage?.setItem(t,e),e}catch{return i("lin")}}snapshotSelf(){return{id:this.id,bornAt:this.bornAt,lastSeen:n(),focused:this.focused,meta:this.meta,url:this.win.location?.href??"",lineage:this.lineage}}beat(){if(this.destroyed)return;const t=this.snapshotSelf();this.registry.set(this.id,t),this.broadcastEnvelope(a,"",null,t),this.electLeader(),this.refreshHeldLocks()}sweep(){if(this.destroyed)return;const t=n()-this.tabTimeout;let s=!1;const i=[];for(const[e,h]of Array.from(this.registry.entries()))e!==this.id&&(h.lastSeen<t?(this.registry.delete(e),s=!0,this.emit({type:"close",tab:h})):h.lineage===this.lineage&&i.push(h));s&&this.electLeader(),i.length>0&&this.emit({type:"duplicate",originals:i})}updatePresence(){const t=this.win.document?.visibilityState,s=(null==t||"visible"===t)&&!1!==this.win.document?.hasFocus?.();if(s===this.focused)return;this.focused=s;const i=this.snapshotSelf();this.registry.set(this.id,i),this.broadcastEnvelope(a,"",null,i),this.emit({type:s?"focus":"blur",tab:i})}sayBye(){try{this.broadcastEnvelope("bye","",null,{id:this.id})}catch{}}electLeader(){let t=null;for(const s of this.registry.values())t?(s.bornAt<t.bornAt||s.bornAt===t.bornAt&&s.id<t.id)&&(t=s):t=s;const s=t?.id??null;if(s!==this.leaderId){const i=this.leaderId?this.registry.get(this.leaderId)??null:null;this.leaderId=s,t&&this.emit({type:"leader",leader:t,previous:i})}}broadcastEnvelope(t,s,e,h){if(this.destroyed)return;const r={t:i("e"),from:this.id,to:e,kind:t,channel:s,payload:h,ts:n()};this.transport.send(r),this.metrics.messagesSent+=1}handleEnvelope(t){if(t.from!==this.id&&(null===t.to||t.to===this.id))switch(this.metrics.messagesReceived+=1,t.kind){case a:this.onHeartbeat(t.payload);break;case"bye":this.onBye(t.payload.id);break;case l:this.onMessage(t);break;case"req":this.onRequest(t);break;case c:this.onResponse(t);break;case u:this.onStateSet(t);break;case f:this.onStateGet(t);break;case d:this.onStateReply(t)}}onHeartbeat(t){if(!t||"string"!=typeof t.id)return;const s=this.registry.get(t.id);this.registry.set(t.id,t),s?s.focused!==t.focused&&this.emit({type:t.focused?"focus":"blur",tab:t}):(this.emit({type:"open",tab:t}),t.lineage===this.lineage&&this.emit({type:"duplicate",originals:[t]}),this.electLeader())}onBye(t){const s=this.registry.get(t);s&&(this.registry.delete(t),this.emit({type:"close",tab:s}),this.electLeader())}onMessage(t){this.emit({type:"message",channel:t.channel,from:t.from,to:t.to,payload:t.payload})}async onRequest(t){const s=this.handlers.get(t.channel),i=t.payload;if(s)try{const e=await s(i.payload,{from:t.from,channel:t.channel});this.broadcastEnvelope(c,t.channel,t.from,{reqId:i.reqId,result:e}),this.metrics.responses+=1}catch(s){this.broadcastEnvelope(c,t.channel,t.from,{reqId:i.reqId,error:s instanceof Error?s.message:String(s)})}else this.broadcastEnvelope(c,t.channel,t.from,{reqId:i.reqId,error:`no handler for "${t.channel}"`})}onResponse(t){const s=t.payload,i=this.pending.get(s.reqId);i&&(this.pending.delete(s.reqId),i.timer&&this.win.clearTimeout(i.timer),s.error?i.reject(new Error(s.error)):i.resolve(s.result))}onStateSet(t){const s=this.state;this.state=t.payload,this.writeStoredState(this.state),this.emit({type:"state",state:this.state,previous:s,from:t.from})}onStateGet(t){null!=this.state&&this.broadcastEnvelope(d,"",t.from,this.state)}onStateReply(t){if(null!=this.state)return;const s=this.state;this.state=t.payload,this.writeStoredState(this.state),this.emit({type:"state",state:this.state,previous:s,from:t.from})}emit(t){for(const s of Array.from(this.listeners))try{s(t)}catch(t){console.error("[TabJS] listener threw:",t)}}readStoredState(){if(!r(this.win))return null;try{return e(this.win.localStorage.getItem(this.stateKey))}catch{return null}}writeStoredState(t){if(r(this.win))try{null==t?this.win.localStorage.removeItem(this.stateKey):this.win.localStorage.setItem(this.stateKey,h(t))}catch{}}lockKey(t){return`${this.lockKeyPrefix}${t}`}readLock(t){return r(this.win)?e(this.win.localStorage.getItem(this.lockKey(t))):null}writeLock(t,s){if(!r(this.win))return;const i=this.lockKey(t);try{null==s?this.win.localStorage.removeItem(i):this.win.localStorage.setItem(i,h(s))}catch{}}async acquireLock(t,s){if(!r(this.win))throw new Error("[TabJS] lock() requires localStorage");const e=s.timeout??3e4,h=s.pollInterval??50,o=s.staleAfter??5e3,a=n()+e,l=i("lk");for(;;){const s=this.readLock(t);if(!(s&&n()-s.heartbeat<o)){const s={owner:this.id,token:l,acquiredAt:n(),heartbeat:n()};this.writeLock(t,s);const i=this.readLock(t);if(i&&i.token===l)return this.heldLocks.set(t,{token:l,release:()=>this.releaseLock(t,l)}),l}if(n()>=a)throw new Error(`[TabJS] lock "${t}" timed out after ${e}ms`);await new Promise(t=>this.win.setTimeout(t,h))}}releaseLock(t,s){const i=this.readLock(t);i&&i.token===s&&(this.writeLock(t,null),this.broadcastEnvelope("lock-release",t,null,null)),this.heldLocks.delete(t)}refreshHeldLocks(){if(0!==this.heldLocks.size)for(const[t,s]of this.heldLocks){const i=this.readLock(t);i&&i.token===s.token?(i.heartbeat=n(),this.writeLock(t,i)):this.heldLocks.delete(t)}}}let y=null;function p(t){return y||(y=new b(t)),y}function w(){y?.destroy(),y=null}const g=Object.assign(p,{TabManager:b,getTabs:p,resetTabsSingleton:w});t.TabManager=b,t.Transport=o,t.default=g,t.getTabs=p,t.resetTabsSingleton=w,Object.defineProperty(t,"i",{value:!0})});
|
|
2
|
+
//# sourceMappingURL=tab.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab.umd.js","sources":["../src/utils.ts","../src/transport.ts","../src/manager.ts","../src/index.ts"],"sourcesContent":[null,null,null,null],"names":["idCounter","makeId","prefix","rand","Math","random","toString","slice","Date","now","safeJsonParse","raw","JSON","parse","safeJsonStringify","value","stringify","hasStorage","win","localStorage","Transport","constructor","namespace","options","this","listeners","Set","seen","seenOrder","maxSeen","channel","destroyed","storageKey","channelName","canChannel","BroadcastChannel","hasBroadcastChannel","storageOnly","canStorage","broadcastChannelOnly","useChannel","useStorage","onChannelMessage","e","env","data","_id","receive","onStorage","key","newValue","addEventListener","subscribe","fn","add","delete","send","markSeen","postMessage","setItem","removeItem","isLive","destroy","removeEventListener","close","clear","id","has","push","length","drop","shift","Array","from","err","console","error","KIND_HEARTBEAT","KIND_MSG","KIND_RES","KIND_STATE_SET","KIND_STATE_GET","KIND_STATE_REPLY","TabManager","metrics","messagesSent","messagesReceived","broadcasts","requests","responses","stateUpdates","locksAcquired","handlers","Map","registry","pending","heldLocks","heartbeatTimer","sweepTimer","leaderId","maybeWin","window","Error","resolveWindow","heartbeatInterval","tabTimeout","meta","bornAt","lineage","resolveLineage","stateKey","lockKeyPrefix","visible","document","visibilityState","focused","hasFocus","transport","handleEnvelope","state","readStoredState","initialState","set","snapshotSelf","onVisibility","updatePresence","onFocus","onBlur","onBeforeUnload","sayBye","beat","setInterval","sweep","broadcastEnvelope","info","tabs","values","isLeader","leader","get","getState","isDuplicate","tab","listener","on","type","event","broadcast","payload","targetId","handle","handler","request","target","Promise","reject","timeoutMs","timeout","resolve","reqId","timer","setTimeout","setState","updater","next","previous","writeStoredState","emit","setMeta","singleton","onConflict","check","others","filter","t","off","clearTimeout","lock","token","acquireLock","releaseLock","clearInterval","held","existing","sessionStorage","getItem","fresh","lastSeen","url","location","href","electLeader","refreshHeldLocks","cutoff","changed","duplicates","entries","originals","nextFocused","snap","candidate","nextLeaderId","kind","to","ts","onHeartbeat","onBye","onMessage","onRequest","onResponse","onStateSet","onStateGet","onStateReply","prev","body","result","message","String","lockKey","name","readLock","writeLock","record","pollMs","pollInterval","staleAfter","deadline","heartbeat","owner","acquiredAt","release","size","getTabs","resetTabsSingleton","defaultExport","Object","assign"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@buildwithdarsh/tabjs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cross-tab communication, shared state, presence, leader election, locks, duplicate detection, and request/response — over BroadcastChannel with a localStorage fallback. Typed, framework-agnostic, zero dependencies.",
|
|
5
|
+
"main": "dist/tab.umd.js",
|
|
6
|
+
"module": "dist/tab.esm.js",
|
|
7
|
+
"types": "dist/tab.d.ts",
|
|
8
|
+
"unpkg": "dist/tab.umd.js",
|
|
9
|
+
"jsdelivr": "dist/tab.umd.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/tab.d.ts",
|
|
13
|
+
"import": "./dist/tab.esm.js",
|
|
14
|
+
"require": "./dist/tab.umd.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/tab.umd.js",
|
|
19
|
+
"dist/tab.umd.js.map",
|
|
20
|
+
"dist/tab.esm.js",
|
|
21
|
+
"dist/tab.esm.js.map",
|
|
22
|
+
"dist/tab.d.ts",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "rollup -c && rm -rf example/dist && cp -r dist example/dist",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"dev": "rollup -c -w",
|
|
35
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"tabs",
|
|
39
|
+
"cross-tab",
|
|
40
|
+
"broadcastchannel",
|
|
41
|
+
"localstorage",
|
|
42
|
+
"shared-state",
|
|
43
|
+
"leader-election",
|
|
44
|
+
"presence",
|
|
45
|
+
"lock",
|
|
46
|
+
"mutex",
|
|
47
|
+
"duplicate-tab",
|
|
48
|
+
"singleton-tab",
|
|
49
|
+
"tab-sync",
|
|
50
|
+
"typescript"
|
|
51
|
+
],
|
|
52
|
+
"author": "Darsh Gupta",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"type": "module",
|
|
55
|
+
"sideEffects": false,
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
58
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
59
|
+
"happy-dom": "^15.0.0",
|
|
60
|
+
"rollup": "^4.34.8",
|
|
61
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
62
|
+
"tslib": "^2.8.1",
|
|
63
|
+
"typescript": "^5.7.3",
|
|
64
|
+
"vitest": "^2.1.0"
|
|
65
|
+
}
|
|
66
|
+
}
|