{"_id":"@diugemi/kabarcast-client","name":"@diugemi/kabarcast-client","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@diugemi/kabarcast-client","version":"0.1.0","description":"TypeScript client for kabarcast - realtime message broadcasting with channel-scoped auth and automatic reconnect.","license":"MIT","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"sideEffects":false,"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --clean --sourcemap","typecheck":"tsc --noEmit","test":"node --test test/*.test.js","prepublishOnly":"npm run build"},"keywords":["websocket","realtime","pubsub","broadcast","kabarcast","notifications","sdk"],"repository":{"type":"git","url":"git+https://github.com/BerieGithub/kabarcast.git","directory":"clients/typescript"},"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","ws":"^8.18.0"},"engines":{"node":">=18"},"gitHead":"a03f9b034e326a2a7856f796d530951c63499c8a","_id":"@diugemi/kabarcast-client@0.1.0","bugs":{"url":"https://github.com/BerieGithub/kabarcast/issues"},"homepage":"https://github.com/BerieGithub/kabarcast#readme","_nodeVersion":"24.11.0","_npmVersion":"11.6.1","dist":{"integrity":"sha512-9nLazBeMj37MNwPsNeuYUqu71g35U7ACZt5CqyVjxP6gAUqaMCsZxP4NQziFBWQajspsRzEdFOQZbBuwAQlJcg==","shasum":"89a63806451a397311835ce851ad315be02b92b1","tarball":"https://registry.npmjs.org/@diugemi/kabarcast-client/-/kabarcast-client-0.1.0.tgz","fileCount":8,"unpackedSize":65446,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHup/NMpNCUlh/Kf7jdKzzvZiMNIWYx90dO6ENLbCsd3AiAssMai98wZUdnPtzWz8yoznUlWtigDGlc/bh2493BdpQ=="}]},"_npmUser":{"name":"berie.h","email":"berie.handika@gmail.com"},"directories":{},"maintainers":[{"name":"berie.h","email":"berie.handika@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/kabarcast-client_0.1.0_1788562193842_0.18308962218002867"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-04T22:49:53.686Z","0.1.0":"2026-09-04T22:49:53.994Z","modified":"2026-09-04T22:49:54.165Z"},"maintainers":[{"name":"berie.h","email":"berie.handika@gmail.com"}],"description":"TypeScript client for kabarcast - realtime message broadcasting with channel-scoped auth and automatic reconnect.","homepage":"https://github.com/BerieGithub/kabarcast#readme","keywords":["websocket","realtime","pubsub","broadcast","kabarcast","notifications","sdk"],"repository":{"type":"git","url":"git+https://github.com/BerieGithub/kabarcast.git","directory":"clients/typescript"},"bugs":{"url":"https://github.com/BerieGithub/kabarcast/issues"},"license":"MIT","readme":"# @diugemi/kabarcast-client\n\nTypeScript client for [kabarcast](https://github.com/BerieGithub/kabarcast) -\nrealtime message broadcasting with channel-scoped auth and automatic reconnect.\n\nWorks in the browser, and in Node 22+ (which ships a global `WebSocket`).\n\n```bash\nnpm install @diugemi/kabarcast-client\n```\n\n## Usage\n\n```ts\nimport { KabarcastClient } from '@diugemi/kabarcast-client';\n\nconst kabar = new KabarcastClient({\n  url: import.meta.env.VITE_KABARCAST_URL,      // wss://kabarcast.example.com\n  getToken: async () => {\n    // Your own backend mints a short-lived, channel-scoped token.\n    const { data } = await api.get('/realtime/token');\n    return data.token;\n  },\n});\n\nawait kabar.connect();\nawait kabar.subscribe(`ssap:user:${userId}`);\n\nkabar.on('notification.created', (n) => {\n  showToast(n.title);\n});\n```\n\n## What it handles for you\n\n- **Token refresh.** `getToken()` is called on *every* connect attempt, so an\n  expired short-lived token can never wedge a reconnect.\n- **Reconnect with jittered backoff.** Full jitter, so a fleet of clients\n  disconnected by a deploy does not stampede the hub when it returns.\n- **Automatic re-subscription.** Channels you subscribed to are restored after\n  a reconnect. Your application code does nothing.\n- **Ack correlation.** `subscribe()` resolves when the hub acknowledges, and\n  rejects if the channel is refused, so authorisation failures surface as\n  errors instead of silence.\n- **Refused channels are not retried.** A channel your token does not grant is\n  dropped from the restore set rather than retried on every reconnect.\n\n## API\n\n### `new KabarcastClient(options)`\n\n| Option | Default | Description |\n|---|---|---|\n| `url` | required | Hub base URL, e.g. `wss://kabarcast.example.com` |\n| `getToken` | required | Returns a channel token (sync or async) |\n| `minReconnectDelayMs` | `500` | Backoff floor |\n| `maxReconnectDelayMs` | `30000` | Backoff ceiling |\n| `maxReconnectAttempts` | `Infinity` | Give up after N consecutive failures |\n| `ackTimeoutMs` | `10000` | How long to wait for a subscribe ack |\n| `webSocketFactory` | - | Supply a WebSocket implementation (Node < 22) |\n| `debug` | `false` | Log lifecycle to `console.debug` |\n\n### Methods\n\n```ts\nawait kabar.connect();                          // open the connection\nconst sub = await kabar.subscribe('channel');   // resolves on ack\nawait sub.unsubscribe();                        // or kabar.unsubscribe('channel')\n\nconst off = kabar.on('event.name', (data, meta) => {});\nconst offAll = kabar.on('*', (data, meta) => {});   // every event\noff();                                              // remove handler\n\nkabar.onStateChange((s) => console.log(s));\n// 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closed'\n\nkabar.connectionState;   // current state\nkabar.close();           // close and stop reconnecting\n```\n\nHandlers receive `(data, meta)` where `meta` is\n`{ channel, event, ts }`. Type the payload with a generic:\n\n```ts\ntype Notification = { id: string; title: string };\nkabar.on<Notification>('notification.created', (n) => n.title);\n```\n\n## React\n\nOne client per app, shared through context or a module singleton. Do not\ncreate one per component.\n\n```tsx\n// realtime.ts\nexport const kabar = new KabarcastClient({\n  url: import.meta.env.VITE_KABARCAST_URL,\n  getToken: () => api.get('/realtime/token').then((r) => r.data.token),\n});\n\n// useChannel.ts\nexport function useChannel<T>(channel: string, event: string, onEvent: (d: T) => void) {\n  const handler = useRef(onEvent);\n  handler.current = onEvent;              // avoid resubscribing on every render\n\n  useEffect(() => {\n    let sub: Subscription | undefined;\n    let cancelled = false;\n\n    kabar.connect()\n      .then(() => kabar.subscribe(channel))\n      .then((s) => { if (cancelled) s.unsubscribe(); else sub = s; })\n      .catch(console.error);\n\n    const off = kabar.on<T>(event, (d) => handler.current(d));\n\n    return () => { cancelled = true; off(); sub?.unsubscribe(); };\n  }, [channel, event]);\n}\n```\n\n```tsx\nuseChannel<Notification>(`ssap:user:${userId}`, 'notification.created', (n) => {\n  queryClient.invalidateQueries({ queryKey: ['notifications'] });\n});\n```\n\n## Node\n\nNode 22+ works out of the box. On older Node, pass a factory:\n\n```ts\nimport WebSocket from 'ws';\n\nconst kabar = new KabarcastClient({\n  url: process.env.KABARCAST_URL!,\n  getToken: () => mintToken(),\n  webSocketFactory: (url) => new WebSocket(url) as any,\n});\n```\n\n## Publishing is a server concern\n\nThis package only **receives**. Broadcasting requires the service secret,\nwhich must never reach a browser. Publish from your backend with a plain HTTP\ncall to `POST /v1/publish` (see the\n[main README](https://github.com/BerieGithub/kabarcast#integrating-from-your-backend)).\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm run build\nnpm test        # runs against a stand-in hub over real WebSockets\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-85d525dec615c89b28ba4e341d9cfab1"}