{"_id":"@brashkie/ws","_rev":"4-98f0cdcc102f569f7df414bb369b81c3","name":"@brashkie/ws","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@brashkie/ws","version":"0.1.0","keywords":["websocket","ws","rfc6455","client","zero-dependencies","brashkie"],"author":{"url":"Hepein Oficial","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/ws@0.1.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/ws#readme","bugs":{"url":"https://github.com/Brashkie/ws/issues"},"dist":{"shasum":"9e26a160f24c7f924cadb232930a07c896360547","tarball":"https://registry.npmjs.org/@brashkie/ws/-/ws-0.1.0.tgz","fileCount":10,"integrity":"sha512-i/z8OomyPUNYDrtTvEFmgVJ+ZuZ0Ln/qPvji6eEJ4zeEQ3mcsm8ojGfGSu/0pqKsQDOMae0TiS9APlWEGSyNPw==","signatures":[{"sig":"MEQCIBSu/Bm3x56TqI9uJtTzJU307gP5L7VYcE2ovY9+VpVTAiA85nV9L9EoevLgFDm+7jTo/lccFhQrL+Zt3PLEjkySDg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":111521},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"2392c9d14c5fcd685bd2b36a3606b99f033f1504","scripts":{"lint":"biome check src","test":"vitest run","build":"tsup","format":"biome format --write src","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/ws.git","type":"git"},"_npmVersion":"10.8.2","description":"Cliente WebSocket (RFC 6455) en TypeScript puro, de propósito general. Cero dependencias. Node 18+.","directories":{},"_nodeVersion":"20.20.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.6.0","@types/node":"^20.14.0","@biomejs/biome":"^1.9.4","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/ws_0.1.0_1781130090623_0.23233211085977734","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@brashkie/ws","version":"0.1.1","keywords":["websocket","ws","rfc6455","client","zero-dependencies","brashkie"],"author":{"url":"Hepein Oficial","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/ws@0.1.1","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/ws#readme","bugs":{"url":"https://github.com/Brashkie/ws/issues"},"dist":{"shasum":"fe63b37cc46cb7ae03eecd7168a5c6dc09aa2fc7","tarball":"https://registry.npmjs.org/@brashkie/ws/-/ws-0.1.1.tgz","fileCount":10,"integrity":"sha512-XpIEu189HiRPlhEeNoGziI5YSJf9CRKqfnvel1Qeo3QRGBoTbn2tXVcYpzvf7a4NOuU+1FwrFZntrU2HBWL7xQ==","signatures":[{"sig":"MEQCIA7bO+INPQLfbOC0rs2WyeWrb4cLHwnvXztxfBUfQV7AAiAcN80ZZFvKSKgBuakctCXod2iexbVxyjga6qYArMhraw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":121140},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"587c5d55247b6d8149ac8b7c656a6129fe6bf836","scripts":{"lint":"biome check src","test":"vitest run","build":"tsup","format":"biome format --write src","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/ws.git","type":"git"},"_npmVersion":"10.8.2","description":"Cliente WebSocket (RFC 6455) en TypeScript puro, de propósito general. Cero dependencias. Node 18+.","directories":{},"_nodeVersion":"20.20.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.6.0","@types/node":"^20.14.0","@biomejs/biome":"^1.9.4","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/ws_0.1.1_1781363368111_0.7566747221775547","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@brashkie/ws","version":"0.2.0","keywords":["websocket","ws","rfc6455","client","zero-dependencies","brashkie"],"author":{"url":"Hepein Oficial","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/ws@0.2.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/ws#readme","bugs":{"url":"https://github.com/Brashkie/ws/issues"},"dist":{"shasum":"b32b00d0e7efa9d78ee2c8606c626e5f061d18d9","tarball":"https://registry.npmjs.org/@brashkie/ws/-/ws-0.2.0.tgz","fileCount":10,"integrity":"sha512-ccq31g7TaOSofKbMY2cMnUMeAj+COWdj1FVJHGxf3cezwU3NwfilukDiwBvSFQ1ActQqOJnJHL1vG0s2dGc9Hg==","signatures":[{"sig":"MEQCICiFRIibuu9HD78j3SOF2KCrn2NKnAwB26/jdFIe0usJAiBIy9+dEiS4WZpptnkCFWw0l3K/2uyP2vmhFxK6z7XoRw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":181882},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"4b5029e4b0136ea896893c54f401c1afe944a1e6","scripts":{"lint":"biome check src","test":"vitest run","build":"tsup","format":"biome format --write src","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/ws.git","type":"git"},"_npmVersion":"10.8.2","description":"Cliente WebSocket (RFC 6455) en TypeScript puro, de propósito general. Cero dependencias. Node 18+.","directories":{},"_nodeVersion":"20.20.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.6.0","@types/node":"^20.14.0","@biomejs/biome":"^1.9.4","@vitest/coverage-v8":"^2.1.0"},"_npmOperationalInternal":{"tmp":"tmp/ws_0.2.0_1781640544396_0.10342686193335338","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@brashkie/ws","version":"0.3.0","description":"Cliente WebSocket (RFC 6455) en TypeScript puro, de propósito general. Cero dependencias. Node 18+.","author":{"name":"Brashkie","url":"Hepein Oficial"},"license":"Apache-2.0","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"engines":{"node":">=18.0.0"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit","lint":"biome check src","format":"biome format --write src","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run build","test:coverage":"vitest run --coverage","test:leak":"node --expose-gc scripts/memcheck.mjs","test:backpressure":"node scripts/backpressure.mjs"},"keywords":["websocket","ws","rfc6455","client","zero-dependencies","brashkie"],"repository":{"type":"git","url":"git+https://github.com/Brashkie/ws.git"},"bugs":{"url":"https://github.com/Brashkie/ws/issues"},"homepage":"https://github.com/Brashkie/ws#readme","publishConfig":{"access":"public"},"devDependencies":{"@biomejs/biome":"^1.9.4","@types/node":"^20.14.0","@vitest/coverage-v8":"^2.1.0","tsup":"^8.3.0","typescript":"^5.6.0","vitest":"^2.1.0"},"_id":"@brashkie/ws@0.3.0","gitHead":"258879fa6db207d4bf08760a12eb8939c5d93017","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-OoOpaOnMO+Vzzr/XVen5pNAiq7f/A34Gw70N9hnEZ4VevORgQt/ExIf7RbK2u2kkqFmffzBG8Lm2x1PJi8rMYw==","shasum":"8bce5dc32419d05f10e09dfd2bfa1b987c629938","tarball":"https://registry.npmjs.org/@brashkie/ws/-/ws-0.3.0.tgz","fileCount":10,"unpackedSize":213488,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF6qTELBRvabrXEydB1EHTEirO/BylrS0lWhDr5kpTicAiEArH2Wz3wJFR/vLwu6Gji+n67XLGrxjUdc2U74W4hdln4="}]},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"directories":{},"maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ws_0.3.0_1783140327441_0.963167345839629"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-10T22:21:30.418Z","modified":"2026-07-04T04:45:27.703Z","0.1.0":"2026-06-10T22:21:30.782Z","0.1.1":"2026-06-13T15:09:28.256Z","0.2.0":"2026-06-16T20:09:04.545Z","0.3.0":"2026-07-04T04:45:27.565Z"},"bugs":{"url":"https://github.com/Brashkie/ws/issues"},"author":{"name":"Brashkie","url":"Hepein Oficial"},"license":"Apache-2.0","homepage":"https://github.com/Brashkie/ws#readme","keywords":["websocket","ws","rfc6455","client","zero-dependencies","brashkie"],"repository":{"type":"git","url":"git+https://github.com/Brashkie/ws.git"},"description":"Cliente WebSocket (RFC 6455) en TypeScript puro, de propósito general. Cero dependencias. Node 18+.","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"readme":"<div align=\"center\">\n\n# @brashkie/ws\n\n**A general-purpose WebSocket client (RFC 6455) in pure TypeScript.**\nNo dependencies. No native addons. No surprises.\n\n[![npm](https://img.shields.io/npm/v/@brashkie/ws.svg)](https://www.npmjs.com/package/@brashkie/ws)\n[![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)\n[![node](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](https://nodejs.org)\n[![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#security)\n[![types](https://img.shields.io/badge/types-included-3178c6.svg)](#)\n[![module](https://img.shields.io/badge/module-ESM%20%2B%20CJS-f7df1e.svg)](#)\n\n</div>\n\n**English** | [Español](./README.es.md)\n\n---\n\n## Table of contents\n\n- [Why @brashkie/ws?](#why-brashkiews)\n- [Package family](#package-family)\n- [Features](#features)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Examples](#examples)\n- [API reference](#api-reference)\n- [Comparison](#comparison)\n- [Performance](#performance)\n- [Compatibility](#compatibility)\n- [Roadmap](#roadmap)\n- [Security](#security)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Why @brashkie/ws?\n\nMost 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.\n\n- **Zero dependencies.** Your `node_modules` tree doesn't grow and your security surface doesn't expand.\n- **No native build step.** No `node-gyp`, no binaries that break in CI or on Alpine.\n- **Portable.** The same code runs anywhere Node 18+ runs.\n- **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.\n\n> Need maximum throughput, or the browser? `@brashkie/ws` is part of a planned family of WebSocket transports that share one contract. See [Package family](#package-family) and the [Roadmap](#roadmap).\n\n---\n\n## Package family\n\n`@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.\n\n| Package | Environment | Implementation | Status | Best for |\n| --- | --- | --- | --- | --- |\n| **`@brashkie/ws-core`** | isomorphic | Contract + types only (0 runtime) | 🚧 Planned | Shared contract for the whole family |\n| **`@brashkie/ws`** | Node.js | Pure TypeScript (RFC 6455 from scratch) | ✅ Available | Portability, simplicity, zero deps |\n| **`@brashkie/ws-native`** | Node.js | Rust + TypeScript (napi-rs) | 🚧 Planned | Maximum throughput, SIMD masking |\n| **`@brashkie/ws-web`** | Browser | Thin wrapper over the native `WebSocket` | 🚧 Planned | Isomorphic apps that also run in the browser |\n\nBecause they honor the same contract, switching implementation is a single import change:\n\n```ts\n// Node, pure TS (today)\nimport { WebSocket } from '@brashkie/ws';\n\n// Node, native Rust core (planned) — same API\nimport { WebSocket } from '@brashkie/ws-native';\n\n// Browser (planned) — same API, over the platform WebSocket\nimport { WebSocket } from '@brashkie/ws-web';\n```\n\n> **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.\n\n---\n\n## Features\n\n- ✅ Full **RFC 6455** client (`ws://` and `wss://` over TLS).\n- ✅ Handshake with `Sec-WebSocket-Key` / `Accept` (SHA-1), validated against the official RFC vector.\n- ✅ Complete framing: **7 / 16 / 64-bit** payload lengths, mandatory client masking.\n- ✅ **Automatic ping/pong** and reassembly of fragmented messages.\n- ✅ Close with **application code** (3000–4999) and reason.\n- ✅ **ESM + CommonJS + types** in a single package.\n- ✅ **Incremental** parser (correctly handles partial TCP chunks).\n- ✅ **Zero** runtime dependencies. Node 18+.\n\n---\n\n## Installation\n\n```bash\nnpm install @brashkie/ws\n# or\npnpm add @brashkie/ws\n# or\nyarn add @brashkie/ws\n```\n\n---\n\n## Quick start\n\n```ts\nimport { WebSocket } from '@brashkie/ws';\n\nconst ws = new WebSocket('wss://example.com/socket');\n\nws.on('open', () => {\n  console.log('connected');\n  ws.send('hello');\n});\n\nws.on('message', (data, isBinary) => {\n  console.log('received:', isBinary ? data : data.toString());\n});\n\nws.on('close', (code, reason) => console.log('closed:', code, reason));\nws.on('error', (err) => console.error('error:', err));\n```\n\n---\n\n## Examples\n\nEvery example is **copy-paste** ready and uses only the package's real API.\n\n### Send and receive text\n\n```ts\nimport { WebSocket } from '@brashkie/ws';\n\nconst ws = new WebSocket('wss://example.com');\nws.on('open', () => ws.send(JSON.stringify({ type: 'greet', data: 'hi' })));\nws.on('message', (data) => {\n  const msg = JSON.parse(data.toString());\n  console.log(msg);\n});\n```\n\n### Send binary data\n\n```ts\nimport { WebSocket } from '@brashkie/ws';\n\nconst ws = new WebSocket('wss://example.com');\nws.on('open', () => {\n  const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04]);\n  ws.send(buffer);\n});\nws.on('message', (data, isBinary) => {\n  if (isBinary) console.log('bytes:', data);\n});\n```\n\n### Heartbeat (periodic ping with timeout)\n\n```ts\nimport { WebSocket } from '@brashkie/ws';\n\nfunction withHeartbeat(url: string, intervalMs = 30_000) {\n  const ws = new WebSocket(url);\n  let alive = true;\n  let timer: NodeJS.Timeout;\n\n  ws.on('open', () => {\n    timer = setInterval(() => {\n      if (!alive) return ws.close(4000, 'no response');\n      alive = false;\n      ws.ping();\n    }, intervalMs);\n  });\n\n  ws.on('pong', () => { alive = true; });\n  ws.on('close', () => clearInterval(timer));\n  return ws;\n}\n\nwithHeartbeat('wss://example.com');\n```\n\n### Reconnection with exponential backoff\n\n```ts\nimport { ReconnectingWebSocket } from '@brashkie/ws';\n\nconst ws = new ReconnectingWebSocket('wss://example.com', {\n  minDelay: 1000, // first retry after 1s\n  maxDelay: 30_000, // cap at 30s\n  factor: 2, // exponential\n});\n\nws.on('open', () => ws.send('hi'));\nws.on('message', (data) => console.log(data.toString()));\nws.on('reconnect', (attempt, delay) => console.log(`retry #${attempt} in ${delay}ms`));\nws.close(); // stops reconnection\n```\n\n### Subprotocols, headers and compression\n\n```ts\nimport { WebSocket } from '@brashkie/ws';\n\nconst ws = new WebSocket('wss://example.com', {\n  protocols: ['chat', 'superchat'], // Sec-WebSocket-Protocol\n  headers: { Authorization: 'Bearer <token>', Cookie: 'sid=abc' },\n  perMessageDeflate: true, // negotiate RFC 7692 compression\n});\n\nws.on('open', () => console.log('negotiated subprotocol:', ws.protocol));\n```\n\n### CommonJS\n\n```js\nconst { WebSocket } = require('@brashkie/ws');\n\nconst ws = new WebSocket('wss://example.com');\nws.on('open', () => ws.send('hello from CJS'));\nws.on('message', (data) => console.log(data.toString()));\n```\n\n---\n\n## API reference\n\n### `new WebSocket(url: string, options?: WebSocketOptions)`\n\nCreates the connection. Accepts `ws://` (TCP) and `wss://` (TLS). Starts the handshake immediately.\n\n`WebSocketOptions`:\n\n| Option | Type | Description |\n| --- | --- | --- |\n| `protocols` | `string \\| string[]` | Subprotocol(s) offered in `Sec-WebSocket-Protocol`. |\n| `headers` | `Record<string, string>` | Extra handshake headers (auth, cookies, …). |\n| `perMessageDeflate` | `boolean` | Negotiate `permessage-deflate` compression (RFC 7692). |\n| `maxPayload` | `number` | Max message size in bytes (default 100 MiB). Exceeding it closes with `1009`. |\n\nFor auto-reconnect, see `ReconnectingWebSocket(url, options?)`, which accepts the same options plus `maxRetries`, `minDelay`, `maxDelay` and `factor`.\n\n### Methods\n\n| Method | Description |\n| --- | --- |\n| `send(data: string \\| Buffer): void` | Sends a text frame (string) or binary frame (Buffer). |\n| `ping(data?: Buffer): void` | Sends a ping frame. |\n| `close(code = 1000, reason = ''): void` | Initiates the closing handshake. Supports application codes 3000–4999. |\n| `terminate(): void` | Closes immediately by destroying the socket (no closing handshake). |\n\n### Properties\n\n| Property | Type | Description |\n| --- | --- | --- |\n| `readyState` | `number` | Current connection state. |\n| `url` | `string` | The URL the connection was created with. |\n| `protocol` | `string` | Negotiated subprotocol (empty if none). |\n| `bufferedAmount` | `number` | Bytes queued in the socket but not yet sent (write backpressure). |\n\n### Statics\n\n`WebSocket.CONNECTING` `(0)` · `WebSocket.OPEN` `(1)` · `WebSocket.CLOSING` `(2)` · `WebSocket.CLOSED` `(3)`\n\n### Events\n\n| Event | Payload | When |\n| --- | --- | --- |\n| `open` | — | Handshake complete; ready to send. |\n| `message` | `(data: Buffer, isBinary: boolean)` | A full message arrived (fragments already reassembled). |\n| `ping` | `(data: Buffer)` | The server sent a ping (a pong is sent automatically). |\n| `pong` | `(data: Buffer)` | The server sent a pong. |\n| `close` | `(code: number, reason: string)` | The connection closed. `1006` means abnormal close. |\n| `error` | `(err: Error)` | Network, handshake, or protocol error. |\n\n### Exported types\n\n```ts\nimport type { WebSocketLike, WebSocketConstructor } from '@brashkie/ws';\n```\n\n`WebSocketLike` describes an instance's shape; `WebSocketConstructor` describes the class (including statics). Use them to accept any implementation in the family via injection:\n\n```ts\nimport type { WebSocketConstructor } from '@brashkie/ws';\n\nfunction createClient(WS: WebSocketConstructor, url: string) {\n  return new WS(url); // works with @brashkie/ws, @brashkie/ws-native or @brashkie/ws-web\n}\n```\n\n---\n\n## Comparison\n\n| | `@brashkie/ws` | `@brashkie/ws-native` *(planned)* | `@brashkie/ws-web` *(planned)* | `ws` (npm) |\n| --- | --- | --- | --- | --- |\n| Environment | Node.js | Node.js | Browser | Node.js |\n| Language | Pure TypeScript | Rust + TypeScript | TypeScript (wrapper) | JS (+ optional C++ addons) |\n| Runtime dependencies | **0** | **0** (own binary) | **0** (platform WebSocket) | 0 (optional addons) |\n| Masking / unmasking | JavaScript | Rust (SIMD, goal) | handled by the browser | JS, or `bufferutil` (C++) |\n| Installation | no build | prebuilt binaries | no build | no build |\n| Client | ✅ | ✅ | ✅ | ✅ |\n| Server | — (future) | — | — | ✅ |\n| `permessage-deflate` | planned | planned | handled by the browser | ✅ |\n| Minimum runtime | Node 18+ | Node 18+ | modern browsers | Node 10+ |\n| Ideal for | portability & simplicity | high throughput | browser / isomorphic apps | de-facto standard, client+server |\n\n`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.\n\n---\n\n## Performance\n\n`@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).\n\nFor **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.\n\n> 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.\n\n---\n\n## Compatibility\n\n- **Node.js 18+** (uses `node:net`, `node:tls`, `node:crypto`, and `Buffer`).\n- **ESM** (`import`) and **CommonJS** (`require`) from the same package.\n- **TypeScript** with bundled types (`.d.ts` and `.d.cts`).\n- **Browser:** not this package — see `@brashkie/ws-web` (planned).\n\n---\n\n## Roadmap\n\nDevelopment 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.\n\n### Phase 1 — `@brashkie/ws` (Node, pure TypeScript) · in progress\n\nA complete, portable WebSocket client.\n\n- [x] RFC 6455 client: handshake, 7/16/64-bit framing, masking\n- [x] Automatic ping/pong, fragment reassembly, close with code\n- [x] RSV-bit validation (rejects frames using non-negotiated extensions)\n- [x] `ws://` and `wss://`\n- [x] `url`, `bufferedAmount`, `terminate()`\n- [x] ESM + CJS + types, zero dependencies, vitest tests (87 tests)\n- [x] Subprotocols (`Sec-WebSocket-Protocol`)\n- [x] Custom handshake headers (auth, cookies)\n- [x] `permessage-deflate` (compression) via `node:zlib`\n- [x] Optional reconnection helper with backoff (`ReconnectingWebSocket`)\n- [x] Strict incremental UTF-8 validation on text frames (closes `1007`)\n- [x] Close-code validation, control-frame & fragmentation rules, `maxPayload` (`1002`/`1007`/`1009`)\n- [x] Memory-leak & backpressure verification (`npm run test:leak` / `test:backpressure`)\n- [ ] Autobahn test suite conformance *(harness in `autobahn/`; all required validations implemented — run `wstest` with Docker to confirm the score)*\n\n### Phase 2 — `@brashkie/ws-core` (isomorphic contract)\n\nThe shared, runtime-free contract that the whole family implements.\n\n- [ ] Extract `WebSocketLike` / `WebSocketConstructor` into a standalone package\n- [ ] Isomorphic types (data as `Uint8Array`; `ping()` optional, as the browser doesn't expose it)\n- [ ] Shared close codes and constants\n- [ ] No runtime code — types and tiny helpers only\n\n### Phase 3 — `@brashkie/ws-web` (Browser)\n\nA thin wrapper over the platform `WebSocket`, exposing the `ws-core` contract.\n\n- [ ] Adapt `addEventListener`/`onmessage` to the `.on(...)` event style\n- [ ] Normalize incoming data (`ArrayBuffer`/`Blob` → `Uint8Array`)\n- [ ] Graceful handling of browser limitations (no app-level `ping`, no custom headers)\n- [ ] Bundle for ESM + types\n\n### Phase 4 — `@brashkie/ws-native` (Node, Rust + TypeScript)\n\nA Rust-powered core for heavy workloads, same `ws-core` contract.\n\n- [ ] Native core with napi-rs implementing the contract\n- [ ] SIMD-accelerated masking/unmasking and native parsing\n- [ ] Prebuilt binaries per platform (linux/macos/windows · x64/arm64)\n- [ ] Automatic fallback to `@brashkie/ws` when no native binary is available\n- [ ] Reproducible benchmarks vs `@brashkie/ws` and vs `ws`\n\n> The roadmap is indicative and may change. Checked items reflect what's already available in the current version.\n\n---\n\n## Security\n\n- **Zero runtime dependencies.** The published package ships only `dist/`; it adds no attack surface and no `npm audit` alerts to your project.\n- 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.\n- Found a security issue? Open an issue in the repository.\n\n---\n\n## Contributing\n\n```bash\ngit clone https://github.com/Brashkie/ws.git\ncd ws\nnpm install\nnpm run build      # ESM + CJS + types\nnpm test           # vitest\nnpm run typecheck\nnpm run lint\n```\n\nPRs are welcome. For large changes, open an issue first to discuss the approach.\n\n---\n\n## License\n\nApache-2.0 © Brashkie (Hepein Oficial)\n","readmeFilename":"README.md"}