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

robosocket

v0.1.1

Published

Reliable, backend-agnostic WebSocket client for React and React Native.

Readme

RoboSocket

A reliable, backend-agnostic WebSocket client for React and React Native. Handles reconnects, heartbeats, guaranteed delivery, and offline queuing so you don't have to hand-roll it — works against any RFC6455 WebSocket server, no server-side library required.

See PROGRESS.md for the full v1 scope and what's intentionally not built yet.

Install

npm install robosocket

Optional peer dependencies, only needed if you use the corresponding feature:

  • react — for the robosocket/react and robosocket/react-native hooks
  • @msgpack/msgpack — for the built-in msgpack serializer

Quick start

import { RoboSocket } from "robosocket";

type Events = {
  message: { text: string };
  typing: { userId: string };
};

const socket = new RoboSocket<Events>({
  url: "wss://api.example.com/ws",
  auth: async () => getAccessToken(),
});

await socket.connect();

socket.on("message", (payload) => console.log(payload.text));
await socket.emit("message", { text: "hello" });

const user = await socket.request("get-user", { id: 1 });

React

import {
  useConnectionStatus,
  useSocket,
  useSubscription,
} from "robosocket/react";

function Chat() {
  const socket = useSocket<Events>({ url: "wss://api.example.com/ws" });
  const status = useConnectionStatus(socket);
  useSubscription(socket, "chat:123");

  return <div>{status}</div>;
}

useSocket connects on mount and calls socket.destroy() on unmount. Other hooks: useNetworkQuality, useStats, useSocketRequest.

React Native

Same hooks, plus app-lifecycle integration. RN's own modules aren't imported directly by RoboSocket (they only resolve inside a Metro/RN project) — you inject them yourself:

import { AppState } from "react-native";
import NetInfo from "@react-native-community/netinfo";
import { createAsyncStorageStore, useSocket } from "robosocket/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";

const socket = useSocket(
  {
    url: "wss://api.example.com/ws",
    queueStore: createAsyncStorageStore(AsyncStorage, "robosocket:queue"),
  },
  { appState: AppState, netInfo: NetInfo },
);

With both injected: backgrounding the app disconnects the socket (most OSes kill it anyway), foregrounding reconnects immediately, and a NetInfo connectivity-restored event triggers an immediate reconnect instead of waiting on the backoff timer. Set pauseOnBackground: false to keep the socket alive in the background.

DevTools

Web only (RN needs a native-styled equivalent, not built yet):

import { RoboSocketDevTools } from "robosocket/devtools";

<RoboSocketDevTools socket={socket} />;

A small floating overlay showing connection state, latency, reconnects, queue size, subscribed channels, and a live event log.

Presence

socket.on("presence", ({ userId, status }) => console.log(userId, status));
await socket.updatePresence("away");

socket.getPresence("user-123"); // => "online" | "offline" | "away" | "busy" | undefined
socket.getAllPresence(); // => { [userId]: status }

No special wire protocol — updatePresence just emits a presence:update event, and any incoming event named presence with { userId, status } data both updates the local presence map and fires normally to .on("presence", ...) listeners. Your backend defines what triggers a presence broadcast.

Serializers

JSON by default. Swap in msgpack for smaller/faster binary payloads:

import { createMsgpackSerializer } from "robosocket";

const socket = new RoboSocket({
  url: "wss://api.example.com/ws",
  serializer: createMsgpackSerializer(), // requires @msgpack/msgpack installed
});

Need protobuf? Implement the Serializer interface yourself — protobuf requires per-message-type schemas ahead of time, which doesn't fit a library where emit(anyEventName, anyShapeData) is the whole point, so it's not built in:

import type { Serializer } from "robosocket";

const protobufSerializer: Serializer = {
  binary: true,
  encode: (packet) => myProtoEncode(packet),
  decode: (raw) => myProtoDecode(raw),
};

Offline queue storage

import { createLocalStorageStore, createMemoryStore } from "robosocket";

new RoboSocket({
  url: "...",
  queueStore: createLocalStorageStore("robosocket:queue"), // web
  // or createMemoryStore() (default), or createAsyncStorageStore(...) on RN
});

Implement QueueStore (load()/save()) for any other backing store.

Plugins & middleware

The extension point for anything not built in — encryption included. RoboSocket never touches crypto code; bring your own and drop it in as middleware:

socket.useOutgoing(async (packet, next) => next(await myEncrypt(packet)));
socket.useIncoming(async (packet, next) => next(await myDecrypt(packet)));

socket.usePlugin({
  name: "analytics",
  onConnect: (client) => track("socket_connected"),
  onDisconnect: (client) => track("socket_disconnected"),
});

Reserved event names

connect, disconnect, reconnect, reconnecting, reconnect_failed, error, message, raw, binary, latency, heartbeat_timeout, queue_updated, queue_overflow, queue_flushed, auth_error, auth_refreshed, and presence are used internally. In particular, message is also emitted as a catch-all for every incoming packet — if your backend sends an application event literally named message, your handler fires twice (once as the named event, once as the catch-all). Pick a different event name for your own "generic message" concept (e.g. chat).

Backend-friendly client behavior

RoboSocket is client-only and has no control over your backend's scaling or ingress — but a few client-side defaults exist specifically to avoid making a backend's job harder during an outage:

  • Reconnect backoff is jittered (reconnectJitter, default true) — a server restart or shared network blip doesn't cause every client to reconnect in the same instant.
  • Heartbeats are jittered (heartbeatJitter, default true) — clients that connect around the same moment don't end up pinging in lockstep, which would otherwise create periodic load spikes.
  • Auth-refresh is jittered (authRefreshJitter, default true) — a fleet whose tokens share a fixed-TTL expiry won't all hit your refresh endpoint at the same instant.
  • Offline queue flush can be paced (queueFlushPacing, default 0 / off) — after a shared outage, many clients reconnecting at once would otherwise each dump their whole queue simultaneously. Set this above 0 (milliseconds between packets) if you expect large simultaneous reconnect events; left at 0 for low-latency single-client catch-up.

If you're tuning these for your own infrastructure: turning jitter off is fine for a handful of clients, but at fleet scale it re-introduces exactly the thundering-herd risk these defaults exist to avoid.

Development

npm install
npm run typecheck
npm test
npm run build
node examples/node-e2e-smoke.mjs  # real end-to-end check against an actual ws server

The unit suite mocks the WebSocket transport; examples/node-e2e-smoke.mjs runs the actual built package against a real ws server and is what caught a real bug (a hasOwnProperty check that silently dropped every message from real transports whose message-event data is inherited via the prototype chain, e.g. the ws package) that the mocked suite couldn't see.