@showkat.dev/pusher-client
v1.1.0
Published
Pusher-style realtime client (channels, events, web push) for the super-advanced-realtime-service Socket.IO server
Maintainers
Readme
@showkat.dev/pusher-client
A small, typed, Pusher-style client for the super-advanced-realtime-service Socket.IO server. Channels, events, client-triggered events, connection state, auto-resubscribe, and browser push notifications.
- ~5 KB minified, one dependency (
socket.io-client), ESM + CommonJS + TypeScript types - Auto-reconnect with backoff; channels are re-joined after every reconnect
pusher:subscription_succeeded, connection state callbacks, readable auth errorschannel.trigger()with server acknowledgements- Web push ("Beams") with multiple interests per device
- Works in browsers, React/Next.js, Node, and React Native
Install
npm install @showkat.dev/pusher-clientYou need an app key: the token returned by the server's POST /api/user/login. For browser apps, add your site's origin to the account's allowedOrigins (PUT /api/user), otherwise the connection is refused.
Quick start
import Pusher from "@showkat.dev/pusher-client";
const pusher = new Pusher("YOUR_APP_KEY", {
base_url: "http://localhost:5000",
onError: (err) => console.error("realtime:", err),
});
const orders = pusher.subscribe("orders");
orders.bind("pusher:subscription_succeeded", () => console.log("ready"));
orders.bind("created", (msg) => {
console.log("New order:", msg);
});Publish from your backend:
curl -X POST http://localhost:5000/api/message/trigger \
-H "Authorization: Bearer $APP_KEY" -H "Content-Type: application/json" \
-d '{"channel":"orders","event":"created","message":{"id":1}}'…or from another client:
const res = await orders.trigger("created", { id: 1 });
if (!res.ok) console.error(res.error);Channels are private to your account:
ordersfor app key A andordersfor app key B are different channels.
React
import { useEffect, useState } from "react";
import Pusher from "@showkat.dev/pusher-client";
export function useChannelEvent<T>(appKey: string, channel: string, event: string) {
const [events, setEvents] = useState<T[]>([]);
useEffect(() => {
const pusher = new Pusher(appKey, { base_url: process.env.NEXT_PUBLIC_REALTIME_URL });
pusher.subscribe(channel).bind<T>(event, (data) => setEvents((prev) => [...prev, data]));
return () => pusher.disconnect(); // drops the socket, channels and callbacks
}, [appKey, channel, event]);
return events;
}Create one Pusher per app (for example in a context provider) rather than one per component; each instance opens its own connection.
In Next.js, only create it in client components ("use client") or inside useEffect.
Connection state
const pusher = new Pusher(key, {
onStateChange: (current, previous) => console.log(`${previous} → ${current}`),
});
pusher.state; // "initialized" | "connecting" | "connected" | "unavailable" | "disconnected"
pusher.error; // last connection error message, if any| State | Meaning |
|---|---|
| initialized | Created with autoConnect: false, not connected yet |
| connecting | Connecting or reconnecting after a drop |
| connected | Live. Channels are (re)joined automatically |
| unavailable | Last attempt failed (see onError); retrying with backoff |
| disconnected | You called disconnect() |
Typical onError messages: Invalid or expired token, You are not authorized, CORS error: Origin https://… is not allowed, Too many events, slow down (server rate limit).
API
new Pusher(appKey, options?)
| Option | Type | Default | Description |
|---|---|---|---|
| base_url | string | https://push.innomessage.com | Server URL |
| onError | (message: string) => void | — | Connection and rate-limit errors |
| onStateChange | (current, previous) => void | — | Connection state changes |
| autoConnect | boolean | true | Set false and call connect() yourself |
| platform | "web" \| "flutter" | "web" | "flutter" sends x-platform: flutter so the server skips the Origin check. Only for non-browser runtimes (Node, React Native). |
| socketOptions | socket.io-client options | — | Advanced passthrough (transports, path, timeouts…) |
Pusher methods
| Method | Returns | Description |
|---|---|---|
| subscribe(name) | Channel | Join a channel. Idempotent; works while offline (joins on connect). |
| unsubscribe(name) | void | Leave the channel and remove all its callbacks |
| channel(name) | Channel \| undefined | An already-subscribed channel |
| allChannels() | Channel[] | Every subscribed channel |
| bindGlobal(cb) | void | cb(eventName, data) for every server event |
| unbindGlobal(cb?) | void | Remove one or all global callbacks |
| connect() | this | Open the connection (with autoConnect: false) |
| disconnect() | void | Close and forget all channels and callbacks |
| beam(options) | BeamsClient | Web push, see below |
| socket | Socket | Underlying socket.io-client socket (escape hatch) |
Channel
| Member | Description |
|---|---|
| name | Channel name |
| subscribed | true once the server confirmed the subscription |
| bind<T>(event, cb) | Listen for event; chainable |
| unbind(event, cb?) | Remove one callback or all for event |
| unbind_all() | Remove every callback on the channel |
| trigger(event, data) | Publish to everyone on the channel (including you). Resolves { ok, data?: { id }, error? } |
Special event: pusher:subscription_succeeded fires when the server confirms the subscription, including after every reconnect. Binding it after it happened calls the callback immediately.
Types
import type { PusherOptions, ConnectionState, EventCallback, AckResponse, BeamsOptions } from "@showkat.dev/pusher-client";
import { Pusher, Channel, BeamsClient, CHANNEL_EVENTS } from "@showkat.dev/pusher-client";Web push notifications
Browser notifications that arrive even when your page is closed. The server needs VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY.
1. Add a service worker at public/sw.js:
self.addEventListener("push", (event) => {
const data = event.data?.json() ?? {};
event.waitUntil(
self.registration.showNotification(data.title ?? "Notification", {
body: data.body,
icon: data.icon,
data: { url: data.url },
})
);
});
self.addEventListener("notificationclick", (event) => {
event.notification.close();
if (event.notification.data?.url) event.waitUntil(clients.openWindow(event.notification.data.url));
});2. Register the device (call start() from a user gesture, e.g. a button click — browsers block permission prompts otherwise):
import { BeamsClient } from "@showkat.dev/pusher-client";
if (BeamsClient.isSupported()) {
const beams = pusher.beam({ vapidPublicKey: VAPID_PUBLIC_KEY, swPath: "/sw.js" });
if (await beams.start()) {
await beams.addDeviceInterest("orders");
await beams.addDeviceInterest("billing"); // interests accumulate
}
}3. Send from your backend:
curl -X POST http://localhost:5000/api/notification/publish \
-H "Authorization: Bearer $APP_KEY" -H "Content-Type: application/json" \
-d '{"interests":["orders"],"title":"New order","body":"#A1 was placed","url":"https://example.com/orders/A1"}'| BeamsClient method | Description |
|---|---|
| BeamsClient.isSupported() | Browser supports service workers + Push API |
| start() | Ask permission, register the SW, create or reuse the push subscription. Resolves false if unsupported/denied. |
| addDeviceInterest(name) / removeDeviceInterest(name) | Add/remove one interest |
| setDeviceInterests(names) / clearDeviceInterests() | Replace all. An empty list means "every channel". |
| getDeviceInterests() | Current interests |
| stop() | Remove the device from the server and unsubscribe the browser |
Interest methods reject if called before start().
Migrating from 1.0.x
1.1 is backward compatible. Worth knowing:
- Fixed: the CommonJS entry pointed at a file that was never built, so
require("@showkat.dev/pusher-client")failed. - Fixed:
addDeviceInterestreplaced the previous interest instead of adding one. - Fixed:
unsubscribe()left channel callbacks attached;disconnect()removed internal handlers. bindGlobalcallbacks are now tracked by the client, sounbindGlobal(cb)removes exactly that callback.beams.start()now resolvestrue/falseand reuses an existing browser subscription.
License
ISC © Showkat Ali
