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

websocket-event

v0.0.5

Published

Typed event-based WebSocket client for browsers

Readme

websocket-event

Typed event-based WebSocket client for browsers

The core client has zero runtime dependencies

import { WebSocketClient } from "websocket-event";

// React
import { useWebSocket, createWebSocketContext } from "websocket-event/react";

Installation

npm install websocket-event

For React integrations, install React 19 or newer:

npm install react@19

Message format

By default, incoming and outgoing messages use this structure:

type Message = {
  key: string;
  data: unknown;
};

Example payload:

{
  "key": "message.created",
  "data": {
    "id": "42",
    "text": "Hello"
  }
}

For full type safety, define a union containing every supported message:

type AppMessage =
  | {
      key: "message.created";
      data: {
        id: string;
        text: string;
      };
    }
  | {
      key: "user.online";
      data: {
        userId: string;
      };
    }
  | {
      key: "typing.changed";
      data: {
        userId: string;
        typing: boolean;
      };
    };

The message key and its corresponding data type are then inferred by invoke() and event listeners.

Core client

Creating and connecting a client

import { WebSocketClient } from "websocket-event";

const client = new WebSocketClient<AppMessage>("wss://example.com/ws", {
  autoReconnect: true,
  reconnectDelay: 2_000,
  onOpen: () => {
    console.log("Connected");
  },
  onClose: (event) => {
    console.log("Disconnected", event.code, event.reason);
  },
  onError: (error) => {
    console.error("WebSocket error", error);
  },
});

client.connect();

The constructor does not connect automatically. Call connect() when the client should open the connection.

Listening for events

const unsubscribe = client.on("message.created", (message, event) => {
  console.log(message.data.id);
  console.log(message.data.text);
  console.log(event.origin);
});

// Remove this listener later.
unsubscribe();

You can also remove listeners explicitly:

const handleUserOnline = (message: Extract<AppMessage, { key: "user.online" }>) => {
  console.log(message.data.userId);
};

client.on("user.online", handleUserOnline);
client.off("user.online", handleUserOnline);

// Remove every listener for this key.
client.off("user.online");

Sending typed events

Use invoke() to serialize an event as JSON:

client.invoke("typing.changed", {
  userId: "user-1",
  typing: true,
});

Both the key and data are checked by TypeScript:

client.invoke("typing.changed", {
  userId: "user-1",
  typing: "yes", // TypeScript error
});

Use send() when you need to send raw WebSocket-compatible data:

client.send("plain text");
client.send(new Uint8Array([1, 2, 3]));

Both send() and invoke() return false when the socket is not open and true after the data has been passed to WebSocket.send().

Connection state

const unsubscribe = client.subscribeState((state) => {
  console.log(state);
});

Possible states:

type ConnectionState =
  | "initializing"
  | "connecting"
  | "open"
  | "closed"
  | "reconnecting";

The current state and native socket are also available directly:

console.log(client.connectionState);
console.log(client.rawSocket);

Reconnecting and closing

client.reconnect();
client.close(1000, "Finished");

Calling close() disables automatic reconnect for that closure.

When the client is no longer needed, destroy it to close the socket and clear all listeners:

client.destroy();

Updating options

client.updateOptions({
  autoReconnect: true,
  reconnectDelay: 5_000,
});

Updating options does not recreate the current socket. Protocol changes take effect when a new connection is created.

Custom message parser

The default parser accepts stringified JSON or an already parsed value and validates that it contains string key and data fields.

Use parseMessage for another server format:

const client = new WebSocketClient<AppMessage>("wss://example.com/ws", {
  parseMessage(event) {
    const payload = JSON.parse(String(event.data)) as {
      event: AppMessage["key"];
      payload: unknown;
    };

    return {
      key: payload.event,
      data: payload.payload,
    } as AppMessage;
  },
});

Parser errors are passed to onError.

React hook

Import React-specific APIs from the /react entry point:

import { useWebSocket } from "websocket-event/react";

const options = {
  autoReconnect: true,
  reconnectDelay: 2_000,
}

function Chat() {
  const {
    connectionState,
    invoke,
    on,
    reconnect,
    close,
  } = useWebSocket<AppMessage>("wss://example.com/ws", options);

  function sendTyping() {
    invoke("typing.changed", {
      userId: "user-1",
      typing: true,
    });
  }

  return (
    <section>
      <p>State: {connectionState}</p>
      <button onClick={sendTyping}>Send typing event</button>
      <button onClick={reconnect}>Reconnect</button>
      <button onClick={() => close()}>Close</button>
    </section>
  );
}

Subscribe to events inside an effect:

import { useEffect } from "react";
import { useWebSocket } from "websocket-event/react";

function Notifications() {
  const { on } = useWebSocket<AppMessage>("wss://example.com/ws", {
    autoReconnect: true,
  });

  useEffect(() => {
    return on("message.created", (message) => {
      console.log(message.data.text);
    });
  }, [on]);

  return null;
}

The hook creates a new client when url changes and destroys the previous client during cleanup.

React context

Use createWebSocketContext() when multiple components should share one connection.

Create a typed context once, outside React components:

import { createWebSocketContext } from "websocket-event/react";

export const WebSocket = createWebSocketContext<AppMessage>();

Add the provider near the application root:

import { WebSocket } from "./WebSocket";
import type { PropsWithChildren } from "react";

const options = {
  autoReconnect: true,
  reconnectDelay: 2_000,
}

export function WebSocketProvider({ children }: PropsWithChildren) {
  return (
    <WebSocket.Provider
      url="wss://example.com/ws"
      options={options}
    >
      {children}
    </WebSocket.Provider>
  );
}

Reading the shared client

import { WebSocket } from "./WebSocket";

function SendButton() {
  const { invoke, connectionState } = WebSocket.useWebSocket();

  return (
    <button
      disabled={connectionState !== "open"}
      onClick={() => {
        invoke("typing.changed", {
          userId: "user-1",
          typing: true,
        });
      }}
    >
      Send
    </button>
  );
}

Listening for an event

import { WebSocket } from "./WebSocket";

function MessageListener() {
  WebSocket.useWebSocketEvent("message.created", (message) => {
    console.log(message.data.id, message.data.text);
  });

  return null;
}

useWebSocketEvent() keeps the subscription stable while using the latest callback implementation.

Reading connection state

import { WebSocket } from "./WebSocket";

function ConnectionIndicator() {
  const state = WebSocket.useConnectionState();

  return <span>{state}</span>;
}

Controlling the connection

The provider accepts connectEnabled:

<WebSocket.Provider
  url="wss://example.com/ws"
  connectEnabled={isAuthenticated}
>
  {children}
</WebSocket.Provider>

When connectEnabled becomes false, the client closes the current connection. When it becomes true, the client connects again.

Options

type WebSocketClientOptions<M> = {
  autoReconnect?: boolean;
  reconnectDelay?: number;
  protocols?: string | string[];
  parseMessage?: (event: MessageEvent<unknown>) => M;
  onOpen?: (event: Event) => void;
  onMessage?: (message: M, event: MessageEvent) => void;
  onClose?: (event: CloseEvent) => void;
  onError?: (error: unknown) => void;
};

| Option | Default | Description | | --- | --- | --- | | autoReconnect | false | Reconnect after an unexpected closure. | | reconnectDelay | 1000 | Delay before reconnecting, in milliseconds. | | protocols | undefined | WebSocket subprotocol or list of subprotocols. | | parseMessage | Built-in parser | Converts an incoming MessageEvent into a typed message. | | onOpen | — | Called after the connection opens. | | onMessage | — | Called for every successfully parsed message. | | onClose | — | Called after the connection closes. | | onError | — | Called for socket errors and parsing errors. |

Type utilities

The root entry point also exports utility types:

import type {
  Message,
  MessageKey,
  MessageByKey,
  MessageData,
  ConnectionState,
  WebSocketClientOptions,
  WebSocketEventCallback,
} from "websocket-event";

Example:

type CreatedMessage = MessageByKey<AppMessage, "message.created">;
type CreatedData = MessageData<AppMessage, "message.created">;
type AppMessageKey = MessageKey<AppMessage>;

Browser support

The package uses the native browser WebSocket API. A compatible global WebSocket implementation must exist in the runtime.