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

@brashkie/ws

v0.3.0

Published

Cliente WebSocket (RFC 6455) en TypeScript puro, de propósito general. Cero dependencias. Node 18+.

Readme

@brashkie/ws

A general-purpose WebSocket client (RFC 6455) in pure TypeScript. No dependencies. No native addons. No surprises.

npm license node dependencies types module

English | Español


Table of contents


Why @brashkie/ws?

Most Node WebSocket clients drag in native C++ addons (bufferutil, utf-8-validate) or dependency chains that end up in npm audit warnings. @brashkie/ws takes the opposite path: a complete RFC 6455 implementation built on Node's standard library (node:net, node:tls, node:crypto), with zero runtime dependencies.

  • Zero dependencies. Your node_modules tree doesn't grow and your security surface doesn't expand.
  • No native build step. No node-gyp, no binaries that break in CI or on Alpine.
  • Portable. The same code runs anywhere Node 18+ runs.
  • A clean primitive. It does one thing —speak WebSocket— and does it well. Reconnection, session, and application-level heartbeat logic is left up to you.

Need maximum throughput, or the browser? @brashkie/ws is part of a planned family of WebSocket transports that share one contract. See Package family and the Roadmap.


Package family

@brashkie/ws is the foundation of a family of WebSocket transports that share one isomorphic contract (WebSocketLike / WebSocketConstructor). They are interchangeable: you program against the contract and pick the implementation that fits your environment and performance needs.

| Package | Environment | Implementation | Status | Best for | | --- | --- | --- | --- | --- | | @brashkie/ws-core | isomorphic | Contract + types only (0 runtime) | 🚧 Planned | Shared contract for the whole family | | @brashkie/ws | Node.js | Pure TypeScript (RFC 6455 from scratch) | ✅ Available | Portability, simplicity, zero deps | | @brashkie/ws-native | Node.js | Rust + TypeScript (napi-rs) | 🚧 Planned | Maximum throughput, SIMD masking | | @brashkie/ws-web | Browser | Thin wrapper over the native WebSocket | 🚧 Planned | Isomorphic apps that also run in the browser |

Because they honor the same contract, switching implementation is a single import change:

// Node, pure TS (today)
import { WebSocket } from '@brashkie/ws';

// Node, native Rust core (planned) — same API
import { WebSocket } from '@brashkie/ws-native';

// Browser (planned) — same API, over the platform WebSocket
import { WebSocket } from '@brashkie/ws-web';

Why a family? This package speaks the protocol over raw TCP/TLS, so it only runs on Node.js, not in the browser (the browser already implements WebSocket itself). @brashkie/ws-web will adapt the platform WebSocket to the same contract, and @brashkie/ws-native will offer a Rust-powered core for heavy workloads. The shared contract will live in @brashkie/ws-core, isomorphic and runtime-free.


Features

  • ✅ Full RFC 6455 client (ws:// and wss:// over TLS).
  • ✅ Handshake with Sec-WebSocket-Key / Accept (SHA-1), validated against the official RFC vector.
  • ✅ Complete framing: 7 / 16 / 64-bit payload lengths, mandatory client masking.
  • Automatic ping/pong and reassembly of fragmented messages.
  • ✅ Close with application code (3000–4999) and reason.
  • ESM + CommonJS + types in a single package.
  • Incremental parser (correctly handles partial TCP chunks).
  • Zero runtime dependencies. Node 18+.

Installation

npm install @brashkie/ws
# or
pnpm add @brashkie/ws
# or
yarn add @brashkie/ws

Quick start

import { WebSocket } from '@brashkie/ws';

const ws = new WebSocket('wss://example.com/socket');

ws.on('open', () => {
  console.log('connected');
  ws.send('hello');
});

ws.on('message', (data, isBinary) => {
  console.log('received:', isBinary ? data : data.toString());
});

ws.on('close', (code, reason) => console.log('closed:', code, reason));
ws.on('error', (err) => console.error('error:', err));

Examples

Every example is copy-paste ready and uses only the package's real API.

Send and receive text

import { WebSocket } from '@brashkie/ws';

const ws = new WebSocket('wss://example.com');
ws.on('open', () => ws.send(JSON.stringify({ type: 'greet', data: 'hi' })));
ws.on('message', (data) => {
  const msg = JSON.parse(data.toString());
  console.log(msg);
});

Send binary data

import { WebSocket } from '@brashkie/ws';

const ws = new WebSocket('wss://example.com');
ws.on('open', () => {
  const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04]);
  ws.send(buffer);
});
ws.on('message', (data, isBinary) => {
  if (isBinary) console.log('bytes:', data);
});

Heartbeat (periodic ping with timeout)

import { WebSocket } from '@brashkie/ws';

function withHeartbeat(url: string, intervalMs = 30_000) {
  const ws = new WebSocket(url);
  let alive = true;
  let timer: NodeJS.Timeout;

  ws.on('open', () => {
    timer = setInterval(() => {
      if (!alive) return ws.close(4000, 'no response');
      alive = false;
      ws.ping();
    }, intervalMs);
  });

  ws.on('pong', () => { alive = true; });
  ws.on('close', () => clearInterval(timer));
  return ws;
}

withHeartbeat('wss://example.com');

Reconnection with exponential backoff

import { ReconnectingWebSocket } from '@brashkie/ws';

const ws = new ReconnectingWebSocket('wss://example.com', {
  minDelay: 1000, // first retry after 1s
  maxDelay: 30_000, // cap at 30s
  factor: 2, // exponential
});

ws.on('open', () => ws.send('hi'));
ws.on('message', (data) => console.log(data.toString()));
ws.on('reconnect', (attempt, delay) => console.log(`retry #${attempt} in ${delay}ms`));
ws.close(); // stops reconnection

Subprotocols, headers and compression

import { WebSocket } from '@brashkie/ws';

const ws = new WebSocket('wss://example.com', {
  protocols: ['chat', 'superchat'], // Sec-WebSocket-Protocol
  headers: { Authorization: 'Bearer <token>', Cookie: 'sid=abc' },
  perMessageDeflate: true, // negotiate RFC 7692 compression
});

ws.on('open', () => console.log('negotiated subprotocol:', ws.protocol));

CommonJS

const { WebSocket } = require('@brashkie/ws');

const ws = new WebSocket('wss://example.com');
ws.on('open', () => ws.send('hello from CJS'));
ws.on('message', (data) => console.log(data.toString()));

API reference

new WebSocket(url: string, options?: WebSocketOptions)

Creates the connection. Accepts ws:// (TCP) and wss:// (TLS). Starts the handshake immediately.

WebSocketOptions:

| Option | Type | Description | | --- | --- | --- | | protocols | string \| string[] | Subprotocol(s) offered in Sec-WebSocket-Protocol. | | headers | Record<string, string> | Extra handshake headers (auth, cookies, …). | | perMessageDeflate | boolean | Negotiate permessage-deflate compression (RFC 7692). | | maxPayload | number | Max message size in bytes (default 100 MiB). Exceeding it closes with 1009. |

For auto-reconnect, see ReconnectingWebSocket(url, options?), which accepts the same options plus maxRetries, minDelay, maxDelay and factor.

Methods

| Method | Description | | --- | --- | | send(data: string \| Buffer): void | Sends a text frame (string) or binary frame (Buffer). | | ping(data?: Buffer): void | Sends a ping frame. | | close(code = 1000, reason = ''): void | Initiates the closing handshake. Supports application codes 3000–4999. | | terminate(): void | Closes immediately by destroying the socket (no closing handshake). |

Properties

| Property | Type | Description | | --- | --- | --- | | readyState | number | Current connection state. | | url | string | The URL the connection was created with. | | protocol | string | Negotiated subprotocol (empty if none). | | bufferedAmount | number | Bytes queued in the socket but not yet sent (write backpressure). |

Statics

WebSocket.CONNECTING (0) · WebSocket.OPEN (1) · WebSocket.CLOSING (2) · WebSocket.CLOSED (3)

Events

| Event | Payload | When | | --- | --- | --- | | open | — | Handshake complete; ready to send. | | message | (data: Buffer, isBinary: boolean) | A full message arrived (fragments already reassembled). | | ping | (data: Buffer) | The server sent a ping (a pong is sent automatically). | | pong | (data: Buffer) | The server sent a pong. | | close | (code: number, reason: string) | The connection closed. 1006 means abnormal close. | | error | (err: Error) | Network, handshake, or protocol error. |

Exported types

import type { WebSocketLike, WebSocketConstructor } from '@brashkie/ws';

WebSocketLike describes an instance's shape; WebSocketConstructor describes the class (including statics). Use them to accept any implementation in the family via injection:

import type { WebSocketConstructor } from '@brashkie/ws';

function createClient(WS: WebSocketConstructor, url: string) {
  return new WS(url); // works with @brashkie/ws, @brashkie/ws-native or @brashkie/ws-web
}

Comparison

| | @brashkie/ws | @brashkie/ws-native (planned) | @brashkie/ws-web (planned) | ws (npm) | | --- | --- | --- | --- | --- | | Environment | Node.js | Node.js | Browser | Node.js | | Language | Pure TypeScript | Rust + TypeScript | TypeScript (wrapper) | JS (+ optional C++ addons) | | Runtime dependencies | 0 | 0 (own binary) | 0 (platform WebSocket) | 0 (optional addons) | | Masking / unmasking | JavaScript | Rust (SIMD, goal) | handled by the browser | JS, or bufferutil (C++) | | Installation | no build | prebuilt binaries | no build | no build | | Client | ✅ | ✅ | ✅ | ✅ | | Server | — (future) | — | — | ✅ | | permessage-deflate | planned | planned | handled by the browser | ✅ | | Minimum runtime | Node 18+ | Node 18+ | modern browsers | Node 10+ | | Ideal for | portability & simplicity | high throughput | browser / isomorphic apps | de-facto standard, client+server |

ws is excellent and the community standard; if you need a WebSocket server today, use it. The @brashkie family aims at something else: a minimal, first-party, dependency-free client with one contract across Node (pure TS or native Rust) and the browser.


Performance

@brashkie/ws prioritizes portability and simplicity. The parser is incremental and avoids unnecessary copies, but masking/unmasking runs in JavaScript, which is plenty for the vast majority of applications (chats, bots, dashboards, telemetry).

For very high-volume workloads (hundreds of thousands of messages/second, large payloads), the family will offer @brashkie/ws-native, with Rust-accelerated masking and native parsing. The design goal is to expose the same API, so the switch is a single import.

Comparative benchmarks (@brashkie/ws vs @brashkie/ws-native vs ws) will ship alongside @brashkie/ws-native. We don't publish numbers we can't reproduce.


Compatibility

  • Node.js 18+ (uses node:net, node:tls, node:crypto, and Buffer).
  • ESM (import) and CommonJS (require) from the same package.
  • TypeScript with bundled types (.d.ts and .d.cts).
  • Browser: not this package — see @brashkie/ws-web (planned).

Roadmap

Development proceeds in phases: first @brashkie/ws (the pure-TS Node client) is completed and hardened; then the shared contract is extracted into @brashkie/ws-core, enabling the browser (@brashkie/ws-web) and native (@brashkie/ws-native) implementations.

Phase 1 — @brashkie/ws (Node, pure TypeScript) · in progress

A complete, portable WebSocket client.

  • [x] RFC 6455 client: handshake, 7/16/64-bit framing, masking
  • [x] Automatic ping/pong, fragment reassembly, close with code
  • [x] RSV-bit validation (rejects frames using non-negotiated extensions)
  • [x] ws:// and wss://
  • [x] url, bufferedAmount, terminate()
  • [x] ESM + CJS + types, zero dependencies, vitest tests (87 tests)
  • [x] Subprotocols (Sec-WebSocket-Protocol)
  • [x] Custom handshake headers (auth, cookies)
  • [x] permessage-deflate (compression) via node:zlib
  • [x] Optional reconnection helper with backoff (ReconnectingWebSocket)
  • [x] Strict incremental UTF-8 validation on text frames (closes 1007)
  • [x] Close-code validation, control-frame & fragmentation rules, maxPayload (1002/1007/1009)
  • [x] Memory-leak & backpressure verification (npm run test:leak / test:backpressure)
  • [ ] Autobahn test suite conformance (harness in autobahn/; all required validations implemented — run wstest with Docker to confirm the score)

Phase 2 — @brashkie/ws-core (isomorphic contract)

The shared, runtime-free contract that the whole family implements.

  • [ ] Extract WebSocketLike / WebSocketConstructor into a standalone package
  • [ ] Isomorphic types (data as Uint8Array; ping() optional, as the browser doesn't expose it)
  • [ ] Shared close codes and constants
  • [ ] No runtime code — types and tiny helpers only

Phase 3 — @brashkie/ws-web (Browser)

A thin wrapper over the platform WebSocket, exposing the ws-core contract.

  • [ ] Adapt addEventListener/onmessage to the .on(...) event style
  • [ ] Normalize incoming data (ArrayBuffer/BlobUint8Array)
  • [ ] Graceful handling of browser limitations (no app-level ping, no custom headers)
  • [ ] Bundle for ESM + types

Phase 4 — @brashkie/ws-native (Node, Rust + TypeScript)

A Rust-powered core for heavy workloads, same ws-core contract.

  • [ ] Native core with napi-rs implementing the contract
  • [ ] SIMD-accelerated masking/unmasking and native parsing
  • [ ] Prebuilt binaries per platform (linux/macos/windows · x64/arm64)
  • [ ] Automatic fallback to @brashkie/ws when no native binary is available
  • [ ] Reproducible benchmarks vs @brashkie/ws and vs ws

The roadmap is indicative and may change. Checked items reflect what's already available in the current version.


Security

  • Zero runtime dependencies. The published package ships only dist/; it adds no attack surface and no npm audit alerts to your project.
  • Any alerts you may see when cloning this repo come exclusively from devDependencies (the build and test chain) and are not published nor delivered to anyone installing the package.
  • Found a security issue? Open an issue in the repository.

Contributing

git clone https://github.com/Brashkie/ws.git
cd ws
npm install
npm run build      # ESM + CJS + types
npm test           # vitest
npm run typecheck
npm run lint

PRs are welcome. For large changes, open an issue first to discuss the approach.


License

Apache-2.0 © Brashkie (Hepein Oficial)