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

@bytesocket/uws

v0.4.0

Published

High-performance WebSocket server for Node.js using uWebSockets.js

Downloads

2,179

Readme

@bytesocket/uws

High-performance WebSocket server for ByteSocket built on uWebSockets.js.

npm version MIT node-current GitHub GitHub stars Socket Badge

✅ Compatible with Ultimate Express and any framework exposing a uWebSockets.js instance. ⚠️ Hyper Express supported only when accessing the underlying uWS instance.


Features

  • Room-based pub/sub -- join/leave rooms, scoped middleware, bulk operations
  • Authentication -- server-side auth function with callback and timeout
  • Global middleware -- inspect or block any incoming message before processing
  • Room middleware -- middleware chain per room+event, with next() / next(err) flow
  • Lifecycle hooks -- upgrade, open, message, close, error -- all typed and cancellable
  • Server-side broadcast -- emit to all sockets, a room, or multiple rooms
  • Origin validation -- allowlist origins at the framework level
  • Full TypeScript -- generic event maps shared with the client for end-to-end type safety
  • Dual serialization -- JSON or binary MessagePack (msgpackr) out of the box

Installation

# Server (Node.js backend)
npm install @bytesocket/uws
# or
pnpm add @bytesocket/uws
# or
yarn add @bytesocket/uws

# Client (browser / Node.js frontend)
npm install @bytesocket/client
# or
pnpm add @bytesocket/client
# or
yarn add @bytesocket/client

Peer dependency: uWebSockets.js must be installed separately.
See uWebSockets.js installation for platform-specific instructions.


Quick Start

import uWS from "uWebSockets.js";
import { ByteSocket } from "@bytesocket/uws";

const app = uWS.App();
const io = new ByteSocket();

io.lifecycle.onOpen((socket) => {
	console.log(`Socket ${socket.id} connected`);
	socket.rooms.join("lobby");
});

io.lifecycle.onClose((socket, code) => {
	console.log(`Socket ${socket.id} disconnected (${code})`);
});

io.on("hello", (socket, data) => {
	socket.emit("welcome", { message: `Hello, ${data.name}!` });
});

io.attach(app, "/socket");

app.listen(3000, (token) => {
	if (token) console.log("Listening on port 3000");
});

Ultimate Express Compatibility

import express from "ultimate-express";

const app = express();
const io = new ByteSocket();

io.lifecycle.onOpen((socket) => {
	console.log(`Socket ${socket.id} connected`);
	socket.rooms.join("lobby");
});

io.lifecycle.onClose((socket, code) => {
	console.log(`Socket ${socket.id} disconnected (${code})`);
});

io.on("hello", (socket, data) => {
	socket.emit("welcome", { message: `Hello, ${data.name}!` });
});

io.attach(app.uwsApp, "/socket");

app.listen(3000, (token) => {
	if (token) console.log("Listening on port 3000");
});

Type-Safe Events

Share a single event interface between server and client for end-to-end type safety. You can use symmetric events (emit and listen share the same map) or asymmetric events (full control via interface extension).

Symmetric usage (most common)

Use SocketEvents<T> directly with a single event map:

import { ByteSocket, SocketEvents } from "@bytesocket/uws";

type MyEvents = SocketEvents<{
	"chat:message": { text: string };
	"user:joined": { userId: string };
}>;

const io = new ByteSocket<MyEvents>();

// Emit and listen share the same typed events
io.emit("chat:message", { text: "Server announcement" });
io.on("user:joined", (socket, data) => {
	console.log(`User ${data.userId} joined`);
});

// Rooms also use the same map
io.rooms.emit("lobby", "chat:message", { text: "Welcome to the lobby" });
io.rooms.on("lobby", "chat:message", (socket, data, next) => {
	console.log(`${socket.id} said: ${data.text}`);
	next();
});

Asymmetric usage (full control)

Extend SocketEvents and override specific properties to differentiate emit/listen/room maps:

import { ByteSocket, SocketEvents } from "@bytesocket/uws";

interface MyEvents extends SocketEvents {
	emit: {
		"server:broadcast": { text: string; from: string };
		"room:created": { roomId: string };
	};
	listen: {
		"user:message": { text: string };
		"user:typing": { userId: string };
	};
	emitRoom: {
		chat: { message: { text: string; sender: string } };
	};
	listenRoom: {
		chat: { message: { text: string; sender: string } };
	};
	emitRooms: { rooms: ["lobby", "announcements"]; event: { alert: string } } | { rooms: ["roomA", "roomB"]; event: { message: { text: string } } };
}

const io = new ByteSocket<MyEvents>();

// Global emits/listens
io.emit("server:broadcast", { text: "Hello all", from: "system" });
io.on("user:message", (socket, data) => {
	console.log(data.text); // string ✓
});

// Room-specific emits/listens (different maps per room)
io.rooms.emit("chat", "message", { text: "Hello!", sender: "server" });
io.rooms.on("chat", "message", (socket, data, next) => {
	console.log(`${data.sender}: ${data.text}`);
	next();
});

All server methods (emit, on, off, once, rooms.emit, rooms.on, etc.) are fully typed -- wrong event names or payload shapes become compile-time errors.


Authentication

Validate credentials when a client first connects. Until auth succeeds, no user messages are processed.

import { ByteSocket } from "@bytesocket/uws";

interface MySocketData extends SocketData {
	userId: number;
}

const io = new ByteSocket<MyEvents, MySocketData>({
	auth: (socket, data, callback) => {
		// data is whatever the client sent in its auth payload
		if (data.token === "valid-token") {
			callback({ userId: 42 }); // payload is attached to socket.payload
		} else {
			callback(null, new Error("Invalid token"));
		}
	},
	authTimeout: 8000, // ms before closing unauthenticated connections
});

io.lifecycle.onOpen((socket) => {
	// Only fires after successful auth
	console.log("Authenticated user:", socket.payload);
});

Rooms

Joining and leaving (server-side)

io.lifecycle.onOpen((socket) => {
	socket.rooms.join("lobby");
	socket.rooms.leave("lobby");
	console.log(socket.rooms.list()); // ["__bytesocket_broadcast__"]
});

Emitting to rooms

// From any socket instance
socket.rooms.emit("chat", "message", { text: "Hello room!", sender: "server" });

// From the server globally
io.rooms.emit("chat", "message", { text: "Announcement!", sender: "server" });

// Broadcast to all connected sockets
socket.broadcast("user:joined", { userId: socket.id, name: "Ahmed" });
io.emit("user:joined", { userId: "abc", name: "Ahmed" });

Bulk operations

socket.rooms.bulk.join(["lobby", "notifications", "chat"]);
socket.rooms.bulk.leave(["lobby", "notifications"]);
socket.rooms.bulk.emit(["room1", "room2"], "alert", { msg: "Hello both!" });

Room lifecycle hooks (join/leave guards)

// Single-room guard
io.rooms.lifecycle.onJoin((socket, room, next) => {
	if (room === "admin" && !socket.payload?.isAdmin) {
		// The error is sent to the client as a join_room_error with a proper ErrorContext
		next(new Error("Not authorized"));
	} else {
		next();
	}
});

// Bulk join guard
io.rooms.bulk.lifecycle.onJoin((socket, rooms, next) => {
	console.log(`${socket.id} joining: ${rooms.join(", ")}`);
	next();
});

Room event middleware

// Middleware chain per room + event -- call next() to forward, next(err) to block
io.rooms.on("chat", "message", (socket, data, next) => {
	if (data.text.includes("badword")) {
		next(new Error("Profanity not allowed")); // message is not forwarded
	} else {
		next();
	}
});

// One-time middleware
io.rooms.once("chat", "message", (socket, data, next) => {
	console.log("First chat message ever:", data.text);
	next();
});

// Remove middleware
io.rooms.off("chat", "message", myMiddleware);
io.rooms.off("chat", "message"); // remove all for this event
io.rooms.off("chat"); // remove all for this room

Global Middleware

Runs before any user message is dispatched to listeners.

// Synchronous
io.use((socket, ctx, next) => {
	console.log("Incoming message:", ctx.event);
	next();
});

// Async
io.use(async (socket, ctx, next) => {
	await logToDatabase(socket.id, ctx);
	next();
});

// Block a message
io.use((socket, ctx, next) => {
	if (socket.locals.rateLimited) {
		next(new Error("Rate limited"));
	} else {
		next();
	}
});

Middleware error handling

const io = new ByteSocket({
	middlewareTimeout: 5000, // ms before timeout error
	onMiddlewareError: "close", // "ignore" | "close" | (error, socket) => void
	onMiddlewareTimeout: "ignore",
});

Lifecycle Events

// HTTP upgrade phase
io.lifecycle.onUpgrade((res, req, userData, context) => {
	// Inspect headers, validate origin, etc.
	// Throw or call res.end() to reject
});

// Socket open (fires after auth if configured)
io.lifecycle.onOpen((socket) => {
	console.log(`${socket.id} connected`);
});

// Authentication success (fires after server confirms auth)
io.lifecycle.onAuthSuccess((socket) => {
	console.log(`Socket ${socket.id} authenticated`);
});

// Authentication failure (fires when auth fails or times out)
io.lifecycle.onAuthError((socket, ctx) => {
	console.error(`Auth failed for ${socket.id}:`, ctx.error);
});

// Raw incoming message
io.lifecycle.onMessage((socket, rawBuffer, isBinary) => {
	console.log("Raw message received", rawBuffer);
});

// Socket closed
io.lifecycle.onClose((socket, code, message) => {
	console.log(`${socket.id} closed with code ${code}`);
});

// Errors (decode, auth, middleware, etc.)
io.lifecycle.onError((socket, ctx) => {
	// socket may be null if the error occurred before the socket was fully created (e.g., upgrade phase)
	const socketId = socket?.id ?? "unknown";
	console.error(`[${socketId}] Error in phase "${ctx.phase}":`, ctx.error);
});

All lifecycle methods have on, once, and off variants:

io.lifecycle.onceOpen((socket) => console.log("First ever connection"));
io.lifecycle.offClose(myCloseHandler);
io.lifecycle.offClose(); // remove all close listeners

Socket API

Every event handler and middleware receives a Socket instance:

// Unique identifier
socket.id; // UUID string

// Auth payload (set by your auth function)
socket.payload; // any (cast to your type)

// Arbitrary data store -- survives across middleware
socket.locals.requestId = randomUUID();

// HTTP metadata from upgrade request (convenience getters)
socket.query; // query string
socket.cookie; // Cookie header
socket.authorization; // Authorization header
socket.userAgent; // User-Agent header
socket.host; // Host header
socket.xForwardedFor; // X-Forwarded-For header

// The raw userData object (including any custom fields) is still available:
socket.userData; // full SocketData object

// Auth state
socket.isAuthenticated; // boolean
socket.isClosed; // boolean

// Send directly to this socket
socket.emit("welcome", { message: "Hello!" });
socket.sendRaw(buffer); // bypass serialization

// Room operations
socket.rooms.join("chat");
socket.rooms.leave("chat");
socket.rooms.list(); // string[]
socket.rooms.emit("chat", "message", { text: "Hi" });

// Broadcast to everyone (including this socket)
socket.broadcast("user:joined", { userId: socket.id });

// Close this connection
socket.close();
socket.close(1008, "Policy violation");

Custom Socket Data

Extend SocketData to add your own typed fields, populated during the upgrade:

import { ByteSocket, SocketData } from "@bytesocket/uws";

interface AppSocketData extends SocketData {
	tenantId: string;
}

const io = new ByteSocket<MyEvents, AppSocketData>({
	auth: (socket, data, callback) => {
		const tenant = lookupTenant(data.token);
		if (!tenant) return callback(null, new Error("Unauthorized"));
		// Attach to userData directly during upgrade via onUpgrade,
		// or use socket.locals / socket.payload for runtime data
		callback({ tenantId: tenant.id });
	},
});

Origin Validation

const io = new ByteSocket({
	origins: ["https://example.com", "https://app.example.com"],
	// Empty array (default) = allow all origins
});

Serialization

// Binary (default) -- msgpackr, smallest payloads
const io = new ByteSocket({ serialization: "binary" });

// JSON -- plain text, easier to inspect/debug
const io = new ByteSocket({ serialization: "json" });

// Advanced msgpackr options
const io = new ByteSocket({
	serialization: "binary",
	msgpackrOptions: {
		useFloat32: true,
		bundleStrings: false,
	},
});

The serialization mode must match the client's serialization option.


Advanced: Manual Serialization

If you need to inspect, pre-encode, or bypass the automatic serialization, you can use the encode() and decode() methods.

⚠️ These are advanced APIs. Prefer emit() and on() for type-safe, automatic encoding/decoding.

// Encode a structured payload (returns a string or Uint8Array)
const encoded = io.encode({ event: "chat", data: { text: "Hello" } });

// Broadcast the raw encoded payload to a room
io.rooms.publishRaw("lobby", encoded);

// Or send it to a specific socket
socket.sendRaw(encoded);

// Decode a raw incoming message (useful in lifecycle.onMessage)
io.lifecycle.onMessage((socket, rawBuffer, isBinary) => {
	const decoded = io.decode(rawBuffer, isBinary);
	console.log("Decoded message:", decoded);
});
  • encode(payload) - uses the configured serialization ("json" or "binary").
  • decode(message, isBinary?) - parses a raw WebSocket message back into an object. If isBinary is omitted, the format is auto-detected.

Caution: encode() throws if the payload cannot be serialised (e.g., circular references or functions). Wrap it in a try-catch when dealing with untrusted data structures.

These methods give you full control when integrating with external systems or debugging the wire format.


Server Sockets Map

// Iterate all connected sockets
for (const [id, socket] of io.sockets) {
	socket.emit("ping", undefined);
}

// Look up a specific socket
const socket = io.sockets.get(socketId);

Destroy

io.destroy();

Closes all connections and cleans up all resources. The instance cannot be reused.

After destroy(), the WebSocket route remains registered on the uWS app, but it is backed by the destroyed instance and becomes inactive. To use the same path again, simply create a new ByteSocket and call attach() — the new handler overwrites the old one automatically.


Full Configuration Reference

const io = new ByteSocket({
	// Authentication
	auth: (socket, data, callback) => {
		callback({ userId: 1 });
	},
	authTimeout: 5000,

	// Middleware
	middlewareTimeout: 5000,
	roomMiddlewareTimeout: 5000,
	onMiddlewareError: "ignore", // "ignore" | "close" | (err, socket) => void
	onMiddlewareTimeout: "ignore",

	// Serialization
	serialization: "binary", // "binary" | "json"
	msgpackrOptions: {},

	// CORS
	origins: ["https://example.com"],

	// Broadcast
	broadcastRoom: "__bytesocket_broadcast__",

	// Debug
	debug: false,

	// uWebSockets.js pass-through options
	idleTimeout: 120000, // milliseconds, 0 = disabled
	sendPingsAutomatically: true,
	serverOptions: {
		maxPayloadLength: 16 * 1024 * 1024,
		compression: uWS.SHARED_COMPRESSOR,
		maxLifetime: 120,
		// any other uWS WebSocketBehavior option except
		// upgrade, open, message, close, idleTimeout, sendPingsAutomatically
	},
});

Transport‑specific options (serverOptions)

All native uWebSockets.js settings (except idleTimeout and sendPingsAutomatically, which are managed by ByteSocket) must be placed inside the serverOptions object:

const io = new ByteSocket({
	serverOptions: {
		maxPayloadLength: 64 * 1024,
		compression: uWS.SHARED_COMPRESSOR,
		closeOnBackpressureLimit: true,
	},
});

Transport‑specific uWebSockets.js options are provided via the serverOptions property. idleTimeout and sendPingsAutomatically are passed to uWebSockets.js as well. See {@link WebSocketServerOptions} for the full list of available settings.


License

MIT © 2026 Ahmed Ouda