tabus-js 0.1.1 → 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 +187 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
# tabus-js
|
|
2
|
+
|
|
1
3
|
<div align="center">
|
|
2
|
-
<img src="./public/tabus-logo.png" alt="tabus" width="
|
|
4
|
+
<img src="./public/tabus-logo.png" alt="tabus-js" width="250" />
|
|
3
5
|
</div>
|
|
4
6
|
|
|
5
|
-
# tabus
|
|
6
|
-
|
|
7
7
|
> Type-safe cross-tab message bus for the browser, built on the native `BroadcastChannel` API.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/tabus-js)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
## Why
|
|
14
14
|
|
|
15
15
|
When a user signs out in one tab, other open tabs keep showing sensitive data.
|
|
16
|
-
`tabus` solves this by letting tabs broadcast events to each other instantly — no server, no WebSockets, no polling.
|
|
16
|
+
`tabus-js` solves this by letting tabs broadcast events to each other instantly — no server, no WebSockets, no polling.
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
@@ -23,6 +23,23 @@ npm install tabus-js
|
|
|
23
23
|
pnpm add tabus-js
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
## Framework compatibility
|
|
27
|
+
|
|
28
|
+
`tabus-js` is framework-agnostic. It works in any environment that runs JavaScript in the browser.
|
|
29
|
+
|
|
30
|
+
| Environment | Supported |
|
|
31
|
+
| --------------------- | ----------- |
|
|
32
|
+
| Vanilla JS | ✅ |
|
|
33
|
+
| React | ✅ |
|
|
34
|
+
| Vue | ✅ |
|
|
35
|
+
| Angular | ✅ |
|
|
36
|
+
| Svelte | ✅ |
|
|
37
|
+
| Next.js (client only) | ✅ |
|
|
38
|
+
| Nuxt (client only) | ✅ |
|
|
39
|
+
| Node.js / SSR | ⚠️ fallback |
|
|
40
|
+
|
|
41
|
+
> **SSR note:** `BroadcastChannel` is a browser API — it does not exist on the server. In SSR environments (Next.js, Nuxt, SvelteKit), create the `Tabus` instance only on the client side. If `BroadcastChannel` is unavailable, `tabus-js` falls back to an in-memory bus automatically and emits a `console.warn`.
|
|
42
|
+
|
|
26
43
|
## Quick start
|
|
27
44
|
|
|
28
45
|
```ts
|
|
@@ -48,6 +65,171 @@ bus.emit("logout", { userId: 42 });
|
|
|
48
65
|
bus.destroy();
|
|
49
66
|
```
|
|
50
67
|
|
|
68
|
+
## Examples
|
|
69
|
+
|
|
70
|
+
### Vanilla JS
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { Tabus } from "tabus-js";
|
|
74
|
+
|
|
75
|
+
const bus = new Tabus("my-app");
|
|
76
|
+
|
|
77
|
+
bus.on("notification", ({ message }) => {
|
|
78
|
+
alert(message);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
document.querySelector("#btn").addEventListener("click", () => {
|
|
82
|
+
bus.emit("notification", { message: "Hello from this tab!" });
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### React
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useEffect } from "react";
|
|
90
|
+
import { Tabus } from "tabus-js";
|
|
91
|
+
|
|
92
|
+
type AppEvents = {
|
|
93
|
+
logout: { userId: number };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const bus = new Tabus<AppEvents>("my-app");
|
|
97
|
+
|
|
98
|
+
function App() {
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const handler = ({ userId }: { userId: number }) => {
|
|
101
|
+
console.log("Logged out:", userId);
|
|
102
|
+
redirectToLogin();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
bus.on("logout", handler);
|
|
106
|
+
return () => bus.off("logout", handler);
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
const handleLogout = () => {
|
|
110
|
+
bus.emit("logout", { userId: 42 });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return <button onClick={handleLogout}>Logout</button>;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Vue
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<script setup lang="ts">
|
|
121
|
+
import { onMounted, onUnmounted } from "vue";
|
|
122
|
+
import { Tabus } from "tabus-js";
|
|
123
|
+
|
|
124
|
+
type AppEvents = {
|
|
125
|
+
logout: { userId: number };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const bus = new Tabus<AppEvents>("my-app");
|
|
129
|
+
|
|
130
|
+
const handler = ({ userId }: { userId: number }) => {
|
|
131
|
+
console.log("Logged out:", userId);
|
|
132
|
+
redirectToLogin();
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
onMounted(() => bus.on("logout", handler));
|
|
136
|
+
onUnmounted(() => bus.off("logout", handler));
|
|
137
|
+
|
|
138
|
+
const handleLogout = () => bus.emit("logout", { userId: 42 });
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<template>
|
|
142
|
+
<button @click="handleLogout">Logout</button>
|
|
143
|
+
</template>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Next.js (client only)
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
"use client";
|
|
150
|
+
|
|
151
|
+
import { useEffect } from "react";
|
|
152
|
+
import { Tabus } from "tabus-js";
|
|
153
|
+
|
|
154
|
+
type AppEvents = {
|
|
155
|
+
logout: { userId: number };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Create outside the component to share across renders
|
|
159
|
+
const bus = new Tabus<AppEvents>("my-app");
|
|
160
|
+
|
|
161
|
+
export function LogoutButton() {
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const handler = ({ userId }: { userId: number }) => {
|
|
164
|
+
redirectToLogin();
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
bus.on("logout", handler);
|
|
168
|
+
return () => bus.off("logout", handler);
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<button onClick={() => bus.emit("logout", { userId: 42 })}>Logout</button>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Real-world: sync logout across tabs
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { Tabus } from "tabus-js";
|
|
181
|
+
|
|
182
|
+
type AuthEvents = {
|
|
183
|
+
logout: { userId: number };
|
|
184
|
+
sessionExpired: { reason: string };
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const auth = new Tabus<AuthEvents>("auth");
|
|
188
|
+
|
|
189
|
+
// In your auth service
|
|
190
|
+
auth.on("logout", () => {
|
|
191
|
+
clearLocalStorage();
|
|
192
|
+
redirectToLogin();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
auth.on("sessionExpired", ({ reason }) => {
|
|
196
|
+
showToast(`Session expired: ${reason}`);
|
|
197
|
+
redirectToLogin();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// When the user clicks logout
|
|
201
|
+
function logout(userId: number) {
|
|
202
|
+
auth.emit("logout", { userId });
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Real-world: sync cart across tabs
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { Tabus } from "tabus-js";
|
|
210
|
+
|
|
211
|
+
type CartEvents = {
|
|
212
|
+
itemAdded: { productId: string; qty: number };
|
|
213
|
+
itemRemoved: { productId: string };
|
|
214
|
+
cleared: Record<string, never>;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const cart = new Tabus<CartEvents>("cart");
|
|
218
|
+
|
|
219
|
+
cart.on("itemAdded", ({ productId, qty }) => {
|
|
220
|
+
updateCartUI(productId, qty);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
cart.on("cleared", () => {
|
|
224
|
+
resetCartUI();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// When user adds an item
|
|
228
|
+
function addToCart(productId: string, qty: number) {
|
|
229
|
+
cart.emit("itemAdded", { productId, qty });
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
51
233
|
## API
|
|
52
234
|
|
|
53
235
|
### `new Tabus<Events>(channelName?)`
|
|
@@ -88,7 +270,7 @@ These are emitted automatically — you cannot emit them manually.
|
|
|
88
270
|
|
|
89
271
|
## Fallback
|
|
90
272
|
|
|
91
|
-
If `BroadcastChannel` is not available (old browsers, some WebViews), `tabus` falls back to an in-memory bus automatically. Events will still work within the same tab. A `console.warn` is emitted once per channel to notify you.
|
|
273
|
+
If `BroadcastChannel` is not available (old browsers, some WebViews, SSR), `tabus-js` falls back to an in-memory bus automatically. Events will still work within the same tab. A `console.warn` is emitted once per channel to notify you.
|
|
92
274
|
|
|
93
275
|
## Browser support
|
|
94
276
|
|
package/package.json
CHANGED