npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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 errors
  • channel.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-client

You 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: orders for app key A and orders for 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: addDeviceInterest replaced the previous interest instead of adding one.
  • Fixed: unsubscribe() left channel callbacks attached; disconnect() removed internal handlers.
  • bindGlobal callbacks are now tracked by the client, so unbindGlobal(cb) removes exactly that callback.
  • beams.start() now resolves true/false and reuses an existing browser subscription.

License

ISC © Showkat Ali