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

@foony/realtime

v0.7.0

Published

TypeScript SDK for the Foony Realtime service.

Readme

@foony/realtime

TypeScript SDK for the Foony Realtime service. A small client for the wire protocol implemented by services/realtime-saas — connect, sub / unsub, publish, and presence.

Install

npm install @foony/realtime

The package ships compiled ESM output and TypeScript declarations.

Quick start

Browser / Foony client

import { Realtime } from '@foony/realtime';

const realtime = new Realtime({
  authCallback: async () => {
    const response = await fetch('/api/realtime/token');
    return await response.text();
  },
});

const channel = realtime.channels.get('chat:room-1');

channel.on((message) => {
  console.log('chat message:', message.data);
});

await channel.publish('chat', { text: 'hello world' });

channel.presence.on((event) => {
  console.log(event.action, event.clientId, event.data);
});
await channel.presence.enter({ name: 'Alice' });

Node / server (browser auth)

Browser clients should fetch a short-lived JWT from your backend via the SDK's authCallback option. Your backend obtains that JWT by exchanging its Realtime API key at the service's POST /auth/token endpoint — the signing key never leaves Foony's infrastructure.

Local development against the realtime backend

Start the backend following services/realtime-saas/README.md. Then mint a dev token:

cd services/realtime-saas
JWT_SIGNING_KEY=local-dev-key go run ./cmd/devtoken -app foony -client alice

Use the printed token in the SDK:

const realtime = new Realtime({
  endpoint: 'ws://localhost:3000',
  token: process.env.FOONY_REALTIME_DEV_TOKEN!,
});

Omit endpoint in production to use wss://realtime.foony.com.

Channel names

Channel names must match [A-Za-z0-9._-]{1,255} and cannot start or end with a .. Use dots to express hierarchy (chat.rooms.42). The server rejects invalid names with error code 40001 (BadFrame).

API surface

  • Realtime — top-level client. Owns the WebSocket; channels attach lazily.
  • client.channels.get(name) — returns a stable Channel for that name.
  • channel.on(fn) — message listener; returns an unsubscribe fn.
  • channel.on(name, fn) — message listener for one message name.
  • channel.publish(name, data) — publish one message; resolves on ack.
  • channel.presence.on(fn) — presence listener.
  • channel.presence.enter|update|leave(data?) — mutate this connection's membership.
  • client.connection.on(fn) — observe all connection events.
  • client.connection.on('connected', fn) — observe one connection event.
  • client.connection.off() — remove all connection listeners.
  • client.connection.once('connected') — await the next matching connection event.

Reconnect

When the connection drops unexpectedly the client retries with exponential backoff (1s, 2s, 4s, ..., capped at 30s). All subscriptions that were established before the disconnect are re-issued automatically; presence membership is NOT automatically restored — call enter() again on the disconnected -> connected transition if you need it.

Pass autoReconnect: false to disable retries entirely (useful in tests).

Publishes made while the connection is establishing or temporarily down are queued locally and flushed on the next successful (re)connect — so a publish during a brief blip resolves rather than rejects. A publish that was already in flight when the connection dropped is resent on reconnect too. Every publish carries a stable client-assigned id, so the server collapses any duplicate that a resend would otherwise create (exactly-once). Pass queueMessages: false to disable buffering/resend and reject such publishes immediately.

Tests

npm test

Runs wire unit tests plus an in-process end-to-end test that drives the SDK against a fake edge built on ws. No external services required.

License

Apache-2.0 © Foony Limited