wreq-js 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,260 +1,188 @@
1
1
  # wreq-js
2
2
 
3
- High-performance Node.js bindings for the Rust-based wreq HTTP client with native TLS and HTTP/2 browser impersonation.
3
+ High-performance HTTP client for Node.JS with real-browser TLS and HTTP/2 fingerprints, powered by Rust.
4
4
 
5
- Note: This is a personal fork of [will-work-for-meal/node-wreq](https://github.com/will-work-for-meal/node-wreq) (originally named node-wreq) with ongoing maintenance and faster dependency updates.
5
+ - ⚡️ A modern, actively maintained alternative to outdated browser-impersonating clients and legacy wrappers.
6
+ - When it comes to web scraping and automation, keeping up with the latest developments is NOT optional. Detection systems like Akamai and Cloudflare change every day using machine learning, old fingerprints are quickly detected.
7
+ - `wreq-js` builds upon the Rust-based [`wreq`](https://github.com/0x676e67/wreq) engine to deliver drop-in Node.js bindings that feel like `fetch()` but behave like a real browser.
6
8
 
7
- ## Features
8
-
9
- - Native performance (no process spawning)
10
- - TLS fingerprinting (JA3/JA4) aligned with real browsers
11
- - HTTP/2 fingerprinting: SETTINGS, PRIORITY, and header ordering
12
- - Multiple browser profiles (Chrome, Firefox, Safari, Edge, Opera, OkHttp)
13
- - WebSocket support
14
- - TypeScript definitions included
15
-
16
- ## How It Works
17
-
18
- The library provides Node.js bindings over [wreq](https://github.com/0x676e67/wreq), a Rust HTTP client that uses BoringSSL to replicate browser network behavior at the TLS and HTTP/2 layers.
9
+ > This is my maintained fork of [will-work-for-meal/node-wreq](https://github.com/will-work-for-meal/node-wreq), originally named `node-wreq`, with ongoing updates and dependency refreshes for compatibility and speed.
19
10
 
20
- ### Why It Works
21
-
22
- Traditional HTTP clients (axios, fetch, curl) have differences in:
23
- - **TLS handshake signatures** — Different cipher suites and extensions
24
- - **HTTP/2 frame ordering** — Different SETTINGS and PRIORITY patterns
25
- - **Header ordering** — Different sequence and values
26
-
27
- This library reproduces browser network behavior with high fidelity.
28
-
29
- ### Browser Profiles and wreq-util
11
+ ## Features
30
12
 
31
- - Browser profiles are generally tracked in the upstream `wreq-util` project; we depend on it for compatibility and support.
32
- - Profiles in this package are available in `src/generated-types.ts` and are automatically generated from the `wreq-util` codebase to improve update speed and reduce maintenance overhead.
33
- - Use `getProfiles()` to query the current set of supported profiles programmatically.
13
+ - **Native performance** no process spawning or browser overhead
14
+ - **Real browser TLS fingerprints** (JA3/JA4)
15
+ - **HTTP/2 impersonation** replicates SETTINGS, PRIORITY, and header ordering
16
+ - **Multiple browser profiles** — Chrome, Firefox, Safari, Edge, Opera, OkHttp
17
+ - **WebSocket support** with browser fingerprint consistency
18
+ - **Prebuilt native binaries** for macOS, Linux, and Windows
19
+ - **TypeScript-ready** with generated definitions
34
20
 
35
21
  ## Installation
36
22
 
37
23
  ```bash
38
- # From GitHub (this fork)
39
- # Latest master branch
40
24
  npm install wreq-js
25
+ # or
41
26
  yarn add wreq-js
42
27
  pnpm add wreq-js
43
28
  bun add wreq-js
44
-
45
- # From npm registry (original repo as node-wreq)
46
- npm install node-wreq
47
- yarn add node-wreq
48
- pnpm add node-wreq
49
- bun add node-wreq
50
29
  ```
51
30
 
52
- Pre-built native modules are included for major platforms:
53
- - macOS (Intel and Apple Silicon)
54
- - Linux (x64 and ARM64)
31
+ Prebuilt binaries are provided for:
32
+ - macOS (Intel & Apple Silicon)
33
+ - Linux (x64 & ARM64)
55
34
  - Windows (x64)
56
35
 
57
- Note on GitHub installs: if a matching prebuilt binary is not available for the referenced tag/commit, installation may build from source. Ensure a Rust toolchain and platform build prerequisites are installed.
36
+ > ⚠️ If a prebuilt binary for your platform or commit is unavailable, the package will build from source.
37
+ > Make sure a Rust toolchain and required build dependencies are installed.
38
+
39
+ ## Why It Exists
40
+
41
+ HTTP clients like `axios`, `fetch`, `got`, `curl` do not behave like browsers on the network layer.
42
+ They differ in:
43
+
44
+ - **TLS handshake** - unique cipher suite order and extension sets
45
+ - **HTTP/2 frames** - different SETTINGS and PRIORITY sequences
46
+ - **Header ordering** - deterministic but non-browser-compliant
58
47
 
59
- ## Usage
48
+ These subtle differences are enough for modern detection systems to identify automation.
49
+ `wreq-js` reproduces browser networking behavior using the `wreq` Rust engine underneath.
50
+ Your job is to write scripts, ours is to make them undetectable, yet effortless.
60
51
 
61
- ### Basic Request
52
+ ## Architecture Overview
53
+
54
+ `wreq-js` provides Node.js bindings over [`wreq`](https://github.com/0x676e67/wreq), a Rust HTTP client built on **BoringSSL** to emulate browser TLS and HTTP/2 stacks.
55
+ Browser profiles are defined in the upstream [`wreq-util`](https://github.com/0x676e67/wreq-util) project and automatically synchronized here for faster updates.
56
+
57
+ To query supported profiles:
62
58
 
63
59
  ```typescript
64
- import { request } from 'wreq-js';
60
+ import { getProfiles } from 'wreq-js';
61
+ console.log(getProfiles());
62
+ // ['chrome_142', 'firefox_139', 'edge_120', 'safari_18', ...]
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ ```typescript
68
+ import { fetch } from 'wreq-js';
65
69
 
66
- const response = await request({
67
- url: 'https://example.com/api',
68
- browser: 'chrome_137',
70
+ const response = await fetch('https://example.com/api', {
71
+ browser: 'chrome_142',
69
72
  });
70
73
 
71
- console.log(response.status); // 200
72
- console.log(response.body); // Response body
73
- console.log(response.headers); // Response headers
74
- console.log(response.cookies); // Cookies
74
+ console.log(await response.json());
75
75
  ```
76
76
 
77
- ### With Custom Headers
77
+ That’s it, you now have full browser impersonation, drop-in compatibility with the `fetch()` API.
78
+
79
+ ## Advanced Usage
80
+
81
+ ### Custom Headers
78
82
 
79
83
  ```typescript
80
- import { request } from 'wreq-js';
84
+ import { fetch, Headers } from 'wreq-js';
81
85
 
82
- const response = await request({
83
- url: 'https://api.example.com/data',
86
+ const response = await fetch('https://api.example.com/data', {
84
87
  browser: 'firefox_139',
85
- headers: {
86
- 'Authorization': 'Bearer token123',
88
+ headers: new Headers({
89
+ Authorization: 'Bearer token123',
87
90
  'Custom-Header': 'value',
88
- },
91
+ }),
89
92
  });
90
93
  ```
91
94
 
92
95
  ### POST Request
93
96
 
94
97
  ```typescript
95
- import { post } from 'wreq-js';
96
-
97
- const response = await post(
98
- 'https://api.example.com/submit',
99
- JSON.stringify({ foo: 'bar' }),
100
- {
101
- browser: 'chrome_137',
102
- headers: {
103
- 'Content-Type': 'application/json',
104
- },
105
- }
106
- );
98
+ const res = await fetch('https://api.example.com/submit', {
99
+ method: 'POST',
100
+ browser: 'chrome_142',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify({ foo: 'bar' }),
103
+ });
107
104
  ```
108
105
 
109
- ### Convenience Methods
106
+ ## Session & Cookie Isolation
107
+
108
+ Each `fetch()` call runs in **ephemeral mode** so that TLS caches, cookies, and session data never leak across requests.
109
+ To persist state, use `createSession()` or `withSession()`:
110
110
 
111
111
  ```typescript
112
- import { get, post } from 'wreq-js';
112
+ import { createSession, withSession } from 'wreq-js';
113
113
 
114
- // GET request
115
- const data = await get('https://api.example.com/users');
114
+ const session = await createSession({ browser: 'chrome_142' });
115
+ await session.fetch('https://example.com/login', { method: 'POST', body: '...' });
116
+ await session.fetch('https://example.com/dashboard');
117
+ await session.close();
116
118
 
117
- // POST request
118
- const result = await post(
119
- 'https://api.example.com/users',
120
- JSON.stringify({ name: 'John' })
121
- );
122
- ```
119
+ // Auto-disposing helper
120
+ await withSession(async (s) => {
121
+ await s.fetch('https://example.com/a');
122
+ await s.fetch('https://example.com/b');
123
+ });
123
124
 
124
- ### With Proxy
125
+ For finer control:
125
126
 
126
127
  ```typescript
127
- import { request } from 'wreq-js';
128
-
129
- const response = await request({
130
- url: 'https://example.com',
131
- browser: 'chrome_137',
132
- // proxy: 'http://proxy.example.com:8080',
133
- // proxy: 'http://username:password@proxy.example.com:8080',
134
- // proxy: 'socks5://proxy.example.com:1080',
128
+ await fetch('https://example.com', {
129
+ sessionId: 'user-42',
130
+ cookieMode: 'session',
135
131
  });
136
132
  ```
137
133
 
138
- ### WebSocket Connection
134
+ ## WebSocket Example
139
135
 
140
136
  ```typescript
141
137
  import { websocket } from 'wreq-js';
142
138
 
143
139
  const ws = await websocket({
144
140
  url: 'wss://echo.websocket.org',
145
- browser: 'chrome_137',
146
- onMessage: (data) => {
147
- console.log('Received:', data);
148
- },
149
- onClose: () => {
150
- console.log('Connection closed');
151
- },
152
- onError: (error) => {
153
- console.error('Error:', error);
154
- },
141
+ browser: 'chrome_142',
142
+ onMessage: (data) => console.log('Received:', data),
155
143
  });
156
144
 
157
- // Send text message
158
145
  await ws.send('Hello!');
159
-
160
- // Send binary message
161
- await ws.send(Buffer.from([1, 2, 3]));
162
-
163
- // Close connection
164
146
  await ws.close();
165
147
  ```
166
148
 
167
149
  ## API Reference
168
150
 
169
- ### `request(options:` [`RequestOptions`](#requestoptions)`): Promise<`[`Response`](#response)`>`
170
-
171
- Main function for making HTTP requests with browser impersonation.
172
-
173
- **Options:**
174
- <a name="requestoptions"></a>
151
+ The API is aiming to be `fetch`-compatible, with a few `wreq`-specific extensions.
152
+ See inline TypeScript definitions for complete typings.
175
153
 
176
154
  ```typescript
177
- interface RequestOptions {
178
- url: string; // Required: URL to request
179
- browser?: BrowserProfile; // Default: 'chrome_137'
180
- method?: HttpMethod; // Default: 'GET'
181
- headers?: Record<string, string>;
182
- body?: string;
183
- proxy?: string; // HTTP/HTTPS/SOCKS5 proxy URL
184
- timeout?: number; // Default: 30000ms
155
+ interface RequestInit {
156
+ method?: string;
157
+ headers?: HeadersInit;
158
+ body?: BodyInit | null;
159
+ signal?: AbortSignal | null;
160
+ redirect?: 'follow';
161
+ browser?: BrowserProfile;
162
+ proxy?: string;
163
+ timeout?: number;
164
+ cookieMode?: 'session' | 'ephemeral';
165
+ session?: Session;
166
+ sessionId?: string;
185
167
  }
186
168
  ```
187
169
 
188
- **Response:**
189
- <a name="response"></a>
190
-
191
- ```typescript
192
- interface Response {
193
- status: number;
194
- headers: Record<string, string>;
195
- body: string;
196
- cookies: Record<string, string>;
197
- url: string; // Final URL after redirects
198
- }
199
- ```
200
-
201
- ### `get(url: string, options?): Promise<`[`Response`](#response)`>`
202
-
203
- ### `post(url: string, body?: string, options?): Promise<`[`Response`](#response)`>`
204
-
205
- ### `websocket(options:` [`WebSocketOptions`](#websocketoptions)`): Promise<WebSocket>`
206
-
207
-
208
- **Options:**
209
- <a name="websocketoptions"></a>
210
-
211
- ```typescript
212
- interface WebSocketOptions {
213
- url: string; // Required: WebSocket URL (ws:// or wss://)
214
- browser?: BrowserProfile; // Default: 'chrome_137'
215
- headers?: Record<string, string>;
216
- proxy?: string; // HTTP/HTTPS/SOCKS5 proxy URL
217
- onMessage: (data: string | Buffer) => void; // Required: Message callback
218
- onClose?: () => void; // Optional: Close callback
219
- onError?: (error: string) => void; // Optional: Error callback
220
- }
221
- ```
222
-
223
- **WebSocket Methods:**
224
-
225
- ```typescript
226
- class WebSocket {
227
- send(data: string | Buffer): Promise<void>;
228
- close(): Promise<void>;
229
- }
230
- ```
231
-
232
- ### `getProfiles():` [`BrowserProfile[]`](#browser-profiles)
233
-
234
- Get list of available browser profiles.
235
-
236
- ```typescript
237
- import { getProfiles } from 'wreq-js';
238
-
239
- const profiles = getProfiles();
240
-
241
- console.log(profiles);
242
- // ['chrome_100', 'chrome_101', ..., 'chrome_137', 'edge_101', ..., 'safari_18', ...]
243
- ```
244
-
245
170
  ## Documentation
246
171
 
247
- - **[Architecture Guide](docs/ARCHITECTURE.md)** Technical details about TLS/HTTP2 fingerprinting, how browser impersonation works
248
- - **[Build Instructions](docs/BUILD.md)** Developer guide for building from source
249
- - **[Publishing Guide](docs/PUBLISHING.md)** How to publish the package
172
+ - **[Architecture Guide](docs/ARCHITECTURE.md)** - How fingerprinting and impersonation work
173
+ - **[Build Instructions](docs/BUILD.md)** - Build from source
174
+ - **[Publishing Guide](docs/PUBLISHING.md)** - Releasing new versions
250
175
 
251
176
  ## Contributing
252
177
 
253
178
  Please read the [Contributing Guide](CONTRIBUTING.md).
254
179
 
180
+ ## Origins
181
+ This project began as a fork of [will-work-for-meal/node-wreq](https://github.com/will-work-for-meal/node-wreq) but has since evolved into an independent implementation with extensive rewrites, new APIs, and active maintenance. It is not affiliated with the original project.
182
+
255
183
  ## Acknowledgments
256
184
 
257
- - [wreq](https://github.com/0x676e67/wreq) Rust HTTP client with browser impersonation
258
- - [wreq-util](https://github.com/0x676e67/wreq-util) Upstream utility project that tracks and ships browser fingerprint updates rapidly
259
- - [Neon](https://neon-bindings.com/) Rust ↔ Node.js bindings
260
- - Original Node.js wrapper: [will-work-for-meal/node-wreq](https://github.com/will-work-for-meal/node-wreq) (named node-wreq) clean, well-written baseline this fork builds on
185
+ - [wreq](https://github.com/0x676e67/wreq) - Rust HTTP client with browser impersonation
186
+ - [wreq-util](https://github.com/0x676e67/wreq-util) - Source of up-to-date browser profiles
187
+ - [Neon](https://neon-bindings.com/) - Rust ↔ Node.js bindings
188
+ - [will-work-for-meal/node-wreq](https://github.com/will-work-for-meal/node-wreq) - Original Node.js wrapper foundation
@@ -0,0 +1,7 @@
1
+ export interface LocalTestServer {
2
+ httpBaseUrl: string;
3
+ wsUrl: string;
4
+ close(): Promise<void>;
5
+ }
6
+ export declare function startLocalTestServer(): Promise<LocalTestServer>;
7
+ //# sourceMappingURL=local-test-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-test-server.d.ts","sourceRoot":"","sources":["../../../src/test/helpers/local-test-server.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,eAAe,CAAC,CAuMrE"}
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startLocalTestServer = startLocalTestServer;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_http_1 = require("node:http");
6
+ const promises_1 = require("node:timers/promises");
7
+ const WS_MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
8
+ async function startLocalTestServer() {
9
+ let baseUrl = "http://127.0.0.1";
10
+ const sockets = new Set();
11
+ const server = (0, node_http_1.createServer)(async (req, res) => {
12
+ try {
13
+ await routeHttpRequest(req, res, baseUrl);
14
+ }
15
+ catch (error) {
16
+ console.error("Local test server request error:", error);
17
+ if (!res.headersSent) {
18
+ res.statusCode = 500;
19
+ res.setHeader("Content-Type", "application/json");
20
+ }
21
+ res.end(JSON.stringify({ error: "internal server error" }));
22
+ }
23
+ });
24
+ server.on("connection", (socket) => {
25
+ sockets.add(socket);
26
+ socket.on("close", () => sockets.delete(socket));
27
+ });
28
+ server.on("upgrade", (req, socket, head) => {
29
+ handleWebSocketUpgrade(req, socket, head);
30
+ });
31
+ await new Promise((resolve, reject) => {
32
+ const onError = (error) => {
33
+ server.off("listening", onListening);
34
+ reject(error);
35
+ };
36
+ const onListening = () => {
37
+ server.off("error", onError);
38
+ resolve();
39
+ };
40
+ server.once("error", onError);
41
+ server.once("listening", onListening);
42
+ server.listen(0, "127.0.0.1");
43
+ });
44
+ const address = server.address();
45
+ if (!address) {
46
+ await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
47
+ throw new Error("Unable to determine local test server address");
48
+ }
49
+ baseUrl = `http://127.0.0.1:${address.port}`;
50
+ const wsUrl = `ws://127.0.0.1:${address.port}/ws`;
51
+ const close = async () => {
52
+ for (const socket of sockets) {
53
+ socket.destroy();
54
+ }
55
+ await new Promise((resolve, reject) => {
56
+ server.close((error) => (error ? reject(error) : resolve()));
57
+ });
58
+ };
59
+ return {
60
+ httpBaseUrl: baseUrl,
61
+ wsUrl,
62
+ close,
63
+ };
64
+ async function routeHttpRequest(req, res, resolvedBase) {
65
+ const url = new URL(req.url ?? "/", resolvedBase);
66
+ const path = url.pathname;
67
+ if (path === "/get") {
68
+ return json(res, createEchoPayload(req, url));
69
+ }
70
+ if (path === "/json") {
71
+ return json(res, {
72
+ message: "local test server",
73
+ status: "ok",
74
+ ts: Date.now(),
75
+ });
76
+ }
77
+ if (path === "/user-agent") {
78
+ return json(res, { "user-agent": req.headers["user-agent"] ?? "" });
79
+ }
80
+ if (path === "/cookies") {
81
+ return json(res, { cookies: parseCookies(req.headers.cookie) });
82
+ }
83
+ if (path.startsWith("/cookies/set")) {
84
+ const cookiesToSet = Array.from(url.searchParams.entries()).map(([key, value]) => `${key}=${value}; Path=/`);
85
+ const existingCookies = parseCookies(req.headers.cookie);
86
+ const newCookies = Object.fromEntries(url.searchParams.entries());
87
+ if (cookiesToSet.length > 0) {
88
+ res.setHeader("Set-Cookie", cookiesToSet);
89
+ }
90
+ return json(res, { cookies: { ...existingCookies, ...newCookies } });
91
+ }
92
+ const delayMatch = path.match(/^\/delay\/(\d+)/);
93
+ if (delayMatch) {
94
+ const seconds = Number(delayMatch[1]);
95
+ await (0, promises_1.setTimeout)(seconds * 1000);
96
+ return json(res, { delayed: seconds, ...createEchoPayload(req, url) });
97
+ }
98
+ res.statusCode = 404;
99
+ json(res, { error: "not found", path });
100
+ }
101
+ function createEchoPayload(req, url) {
102
+ const args = Object.fromEntries(url.searchParams.entries());
103
+ return {
104
+ args,
105
+ headers: canonicalizeHeaders(req),
106
+ method: req.method ?? "GET",
107
+ origin: req.socket.remoteAddress ?? "127.0.0.1",
108
+ url: url.toString(),
109
+ };
110
+ }
111
+ function canonicalizeHeaders(req) {
112
+ const headers = {};
113
+ for (const [name, value] of Object.entries(req.headers)) {
114
+ if (typeof value === "undefined")
115
+ continue;
116
+ const canonicalName = name
117
+ .split("-")
118
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
119
+ .join("-");
120
+ headers[canonicalName] = Array.isArray(value) ? value.join(", ") : value;
121
+ }
122
+ return headers;
123
+ }
124
+ function parseCookies(cookieHeader) {
125
+ if (!cookieHeader) {
126
+ return {};
127
+ }
128
+ return cookieHeader.split(";").reduce((acc, cookie) => {
129
+ const [key, ...rest] = cookie.trim().split("=");
130
+ if (!key) {
131
+ return acc;
132
+ }
133
+ acc[key] = rest.join("=");
134
+ return acc;
135
+ }, {});
136
+ }
137
+ function json(res, body) {
138
+ if (!res.hasHeader("Content-Type")) {
139
+ res.setHeader("Content-Type", "application/json");
140
+ }
141
+ res.end(JSON.stringify(body));
142
+ }
143
+ function handleWebSocketUpgrade(req, socket, head) {
144
+ try {
145
+ const url = new URL(req.url ?? "/", baseUrl);
146
+ if (url.pathname !== "/ws") {
147
+ socket.write("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
148
+ socket.destroy();
149
+ return;
150
+ }
151
+ const secKey = req.headers["sec-websocket-key"];
152
+ if (!secKey || Array.isArray(secKey)) {
153
+ socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
154
+ socket.destroy();
155
+ return;
156
+ }
157
+ const acceptKey = (0, node_crypto_1.createHash)("sha1")
158
+ .update(secKey + WS_MAGIC_STRING)
159
+ .digest("base64");
160
+ const responseHeaders = [
161
+ "HTTP/1.1 101 Switching Protocols",
162
+ "Upgrade: websocket",
163
+ "Connection: Upgrade",
164
+ `Sec-WebSocket-Accept: ${acceptKey}`,
165
+ ];
166
+ socket.write(`${responseHeaders.join("\r\n")}\r\n\r\n`);
167
+ if (head.length > 0) {
168
+ socket.unshift(head);
169
+ }
170
+ setupEchoWebSocket(socket);
171
+ }
172
+ catch (error) {
173
+ console.error("Local test server WebSocket upgrade error:", error);
174
+ socket.destroy();
175
+ }
176
+ }
177
+ }
178
+ function setupEchoWebSocket(socket) {
179
+ let buffer = Buffer.alloc(0);
180
+ let closed = false;
181
+ socket.on("data", (chunk) => {
182
+ buffer = Buffer.concat([buffer, chunk]);
183
+ parseFrames();
184
+ });
185
+ socket.on("close", () => {
186
+ closed = true;
187
+ });
188
+ socket.on("error", () => {
189
+ socket.destroy();
190
+ });
191
+ function parseFrames() {
192
+ while (buffer.length >= 2) {
193
+ const firstByte = buffer[0];
194
+ const secondByte = buffer[1];
195
+ const opcode = firstByte & 0x0f;
196
+ const isMasked = Boolean(secondByte & 0x80);
197
+ let offset = 2;
198
+ let payloadLength = secondByte & 0x7f;
199
+ if (payloadLength === 126) {
200
+ if (buffer.length < offset + 2)
201
+ return;
202
+ payloadLength = buffer.readUInt16BE(offset);
203
+ offset += 2;
204
+ }
205
+ else if (payloadLength === 127) {
206
+ if (buffer.length < offset + 8)
207
+ return;
208
+ const bigLength = buffer.readBigUInt64BE(offset);
209
+ payloadLength = Number(bigLength);
210
+ offset += 8;
211
+ }
212
+ const maskEnd = offset + (isMasked ? 4 : 0);
213
+ if (buffer.length < maskEnd)
214
+ return;
215
+ const maskingKey = isMasked ? buffer.subarray(offset, maskEnd) : undefined;
216
+ offset = maskEnd;
217
+ const frameEnd = offset + payloadLength;
218
+ if (buffer.length < frameEnd)
219
+ return;
220
+ const payload = buffer.subarray(offset, frameEnd);
221
+ buffer = buffer.subarray(frameEnd);
222
+ const data = isMasked && maskingKey ? unmask(payload, maskingKey) : payload;
223
+ handleFrame(opcode, data);
224
+ }
225
+ }
226
+ function handleFrame(opcode, data) {
227
+ if (closed) {
228
+ return;
229
+ }
230
+ switch (opcode) {
231
+ case 0x1: {
232
+ // Text frame: echo payload back
233
+ sendFrame(0x1, data);
234
+ break;
235
+ }
236
+ case 0x2: {
237
+ // Binary frame: echo back
238
+ sendFrame(0x2, data);
239
+ break;
240
+ }
241
+ case 0x8: {
242
+ // Close frame
243
+ sendFrame(0x8, data);
244
+ closed = true;
245
+ socket.end();
246
+ break;
247
+ }
248
+ case 0x9: {
249
+ // Ping
250
+ sendFrame(0xa, data);
251
+ break;
252
+ }
253
+ case 0xa: {
254
+ // Pong - ignore
255
+ break;
256
+ }
257
+ default: {
258
+ // Unsupported opcode: close connection
259
+ sendFrame(0x8, Buffer.alloc(0));
260
+ closed = true;
261
+ socket.end();
262
+ }
263
+ }
264
+ }
265
+ function sendFrame(opcode, data) {
266
+ const payloadLength = data.length;
267
+ let headerLength = 2;
268
+ if (payloadLength >= 126 && payloadLength < 65536) {
269
+ headerLength += 2;
270
+ }
271
+ else if (payloadLength >= 65536) {
272
+ headerLength += 8;
273
+ }
274
+ const frame = Buffer.alloc(headerLength + payloadLength);
275
+ frame[0] = 0x80 | (opcode & 0x0f);
276
+ let offset = 2;
277
+ if (payloadLength < 126) {
278
+ frame[1] = payloadLength;
279
+ }
280
+ else if (payloadLength < 65536) {
281
+ frame[1] = 126;
282
+ frame.writeUInt16BE(payloadLength, offset);
283
+ offset += 2;
284
+ }
285
+ else {
286
+ frame[1] = 127;
287
+ frame.writeBigUInt64BE(BigInt(payloadLength), offset);
288
+ offset += 8;
289
+ }
290
+ data.copy(frame, offset);
291
+ socket.write(frame);
292
+ }
293
+ function unmask(payload, maskingKey) {
294
+ const result = Buffer.alloc(payload.length);
295
+ for (let i = 0; i < payload.length; i++) {
296
+ const maskByte = maskingKey[i % 4];
297
+ result[i] = payload[i] ^ maskByte;
298
+ }
299
+ return result;
300
+ }
301
+ }
302
+ //# sourceMappingURL=local-test-server.js.map