nlcurl 0.1.0 → 0.3.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 +5 -13
- package/dist/cli/args.d.ts +37 -5
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +6 -17
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +25 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +24 -7
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +24 -12
- package/dist/cli/output.js.map +1 -1
- package/dist/cookies/jar.d.ts +45 -13
- package/dist/cookies/jar.d.ts.map +1 -1
- package/dist/cookies/jar.js +88 -29
- package/dist/cookies/jar.js.map +1 -1
- package/dist/cookies/parser.d.ts +25 -3
- package/dist/cookies/parser.d.ts.map +1 -1
- package/dist/cookies/parser.js +12 -7
- package/dist/cookies/parser.js.map +1 -1
- package/dist/core/client.d.ts +49 -33
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +65 -38
- package/dist/core/client.js.map +1 -1
- package/dist/core/errors.d.ts +94 -6
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +95 -6
- package/dist/core/errors.js.map +1 -1
- package/dist/core/request.d.ts +99 -32
- package/dist/core/request.d.ts.map +1 -1
- package/dist/core/request.js +0 -3
- package/dist/core/request.js.map +1 -1
- package/dist/core/response.d.ts +92 -8
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +92 -7
- package/dist/core/response.js.map +1 -1
- package/dist/core/session.d.ts +109 -14
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +143 -49
- package/dist/core/session.js.map +1 -1
- package/dist/fingerprints/akamai.d.ts +11 -11
- package/dist/fingerprints/akamai.d.ts.map +1 -1
- package/dist/fingerprints/akamai.js +10 -14
- package/dist/fingerprints/akamai.js.map +1 -1
- package/dist/fingerprints/database.d.ts +14 -15
- package/dist/fingerprints/database.d.ts.map +1 -1
- package/dist/fingerprints/database.js +14 -19
- package/dist/fingerprints/database.js.map +1 -1
- package/dist/fingerprints/extensions.d.ts +121 -27
- package/dist/fingerprints/extensions.d.ts.map +1 -1
- package/dist/fingerprints/extensions.js +132 -49
- package/dist/fingerprints/extensions.js.map +1 -1
- package/dist/fingerprints/ja3.d.ts +34 -18
- package/dist/fingerprints/ja3.d.ts.map +1 -1
- package/dist/fingerprints/ja3.js +34 -18
- package/dist/fingerprints/ja3.js.map +1 -1
- package/dist/fingerprints/profiles/chrome.d.ts +21 -10
- package/dist/fingerprints/profiles/chrome.d.ts.map +1 -1
- package/dist/fingerprints/profiles/chrome.js +25 -22
- package/dist/fingerprints/profiles/chrome.js.map +1 -1
- package/dist/fingerprints/profiles/edge.d.ts +10 -7
- package/dist/fingerprints/profiles/edge.d.ts.map +1 -1
- package/dist/fingerprints/profiles/edge.js +10 -10
- package/dist/fingerprints/profiles/edge.js.map +1 -1
- package/dist/fingerprints/profiles/firefox.d.ts +11 -3
- package/dist/fingerprints/profiles/firefox.d.ts.map +1 -1
- package/dist/fingerprints/profiles/firefox.js +15 -14
- package/dist/fingerprints/profiles/firefox.js.map +1 -1
- package/dist/fingerprints/profiles/safari.d.ts +14 -3
- package/dist/fingerprints/profiles/safari.d.ts.map +1 -1
- package/dist/fingerprints/profiles/safari.js +16 -13
- package/dist/fingerprints/profiles/safari.js.map +1 -1
- package/dist/fingerprints/profiles/tor.d.ts +8 -7
- package/dist/fingerprints/profiles/tor.d.ts.map +1 -1
- package/dist/fingerprints/profiles/tor.js +8 -14
- package/dist/fingerprints/profiles/tor.js.map +1 -1
- package/dist/fingerprints/types.d.ts +70 -47
- package/dist/fingerprints/types.d.ts.map +1 -1
- package/dist/fingerprints/types.js +0 -7
- package/dist/fingerprints/types.js.map +1 -1
- package/dist/http/h1/client.d.ts +30 -9
- package/dist/http/h1/client.d.ts.map +1 -1
- package/dist/http/h1/client.js +153 -20
- package/dist/http/h1/client.js.map +1 -1
- package/dist/http/h1/encoder.d.ts +9 -6
- package/dist/http/h1/encoder.d.ts.map +1 -1
- package/dist/http/h1/encoder.js +14 -13
- package/dist/http/h1/encoder.js.map +1 -1
- package/dist/http/h1/parser.d.ts +68 -14
- package/dist/http/h1/parser.d.ts.map +1 -1
- package/dist/http/h1/parser.js +92 -37
- package/dist/http/h1/parser.js.map +1 -1
- package/dist/http/h2/client.d.ts +87 -14
- package/dist/http/h2/client.d.ts.map +1 -1
- package/dist/http/h2/client.js +496 -74
- package/dist/http/h2/client.js.map +1 -1
- package/dist/http/h2/frames.d.ts +103 -6
- package/dist/http/h2/frames.d.ts.map +1 -1
- package/dist/http/h2/frames.js +96 -17
- package/dist/http/h2/frames.js.map +1 -1
- package/dist/http/h2/hpack.d.ts +40 -5
- package/dist/http/h2/hpack.d.ts.map +1 -1
- package/dist/http/h2/hpack.js +50 -36
- package/dist/http/h2/hpack.js.map +1 -1
- package/dist/http/negotiator.d.ts +36 -12
- package/dist/http/negotiator.d.ts.map +1 -1
- package/dist/http/negotiator.js +96 -24
- package/dist/http/negotiator.js.map +1 -1
- package/dist/http/pool.d.ts +66 -17
- package/dist/http/pool.d.ts.map +1 -1
- package/dist/http/pool.js +47 -20
- package/dist/http/pool.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -13
- package/dist/index.js.map +1 -1
- package/dist/middleware/interceptor.d.ts +40 -8
- package/dist/middleware/interceptor.d.ts.map +1 -1
- package/dist/middleware/interceptor.js +28 -6
- package/dist/middleware/interceptor.js.map +1 -1
- package/dist/middleware/rate-limiter.d.ts +18 -5
- package/dist/middleware/rate-limiter.d.ts.map +1 -1
- package/dist/middleware/rate-limiter.js +12 -7
- package/dist/middleware/rate-limiter.js.map +1 -1
- package/dist/middleware/retry.d.ts +17 -5
- package/dist/middleware/retry.d.ts.map +1 -1
- package/dist/middleware/retry.js +13 -11
- package/dist/middleware/retry.js.map +1 -1
- package/dist/proxy/http-proxy.d.ts +17 -9
- package/dist/proxy/http-proxy.d.ts.map +1 -1
- package/dist/proxy/http-proxy.js +9 -13
- package/dist/proxy/http-proxy.js.map +1 -1
- package/dist/proxy/socks.d.ts +20 -9
- package/dist/proxy/socks.d.ts.map +1 -1
- package/dist/proxy/socks.js +20 -31
- package/dist/proxy/socks.js.map +1 -1
- package/dist/tls/constants.d.ts +74 -4
- package/dist/tls/constants.d.ts.map +1 -1
- package/dist/tls/constants.js +75 -21
- package/dist/tls/constants.js.map +1 -1
- package/dist/tls/node-engine.d.ts +17 -16
- package/dist/tls/node-engine.d.ts.map +1 -1
- package/dist/tls/node-engine.js +20 -27
- package/dist/tls/node-engine.js.map +1 -1
- package/dist/tls/stealth/client-hello.d.ts +32 -16
- package/dist/tls/stealth/client-hello.d.ts.map +1 -1
- package/dist/tls/stealth/client-hello.js +13 -37
- package/dist/tls/stealth/client-hello.js.map +1 -1
- package/dist/tls/stealth/engine.d.ts +18 -10
- package/dist/tls/stealth/engine.d.ts.map +1 -1
- package/dist/tls/stealth/engine.js +55 -40
- package/dist/tls/stealth/engine.js.map +1 -1
- package/dist/tls/stealth/handshake.d.ts +31 -17
- package/dist/tls/stealth/handshake.d.ts.map +1 -1
- package/dist/tls/stealth/handshake.js +173 -74
- package/dist/tls/stealth/handshake.js.map +1 -1
- package/dist/tls/stealth/key-schedule.d.ts +90 -32
- package/dist/tls/stealth/key-schedule.d.ts.map +1 -1
- package/dist/tls/stealth/key-schedule.js +80 -46
- package/dist/tls/stealth/key-schedule.js.map +1 -1
- package/dist/tls/stealth/record-layer.d.ts +76 -25
- package/dist/tls/stealth/record-layer.d.ts.map +1 -1
- package/dist/tls/stealth/record-layer.js +66 -36
- package/dist/tls/stealth/record-layer.js.map +1 -1
- package/dist/tls/types.d.ts +33 -25
- package/dist/tls/types.d.ts.map +1 -1
- package/dist/tls/types.js +0 -4
- package/dist/tls/types.js.map +1 -1
- package/dist/utils/buffer-reader.d.ts +99 -7
- package/dist/utils/buffer-reader.d.ts.map +1 -1
- package/dist/utils/buffer-reader.js +99 -7
- package/dist/utils/buffer-reader.js.map +1 -1
- package/dist/utils/buffer-writer.d.ts +99 -10
- package/dist/utils/buffer-writer.d.ts.map +1 -1
- package/dist/utils/buffer-writer.js +101 -12
- package/dist/utils/buffer-writer.js.map +1 -1
- package/dist/utils/encoding.d.ts +33 -8
- package/dist/utils/encoding.d.ts.map +1 -1
- package/dist/utils/encoding.js +58 -13
- package/dist/utils/encoding.js.map +1 -1
- package/dist/utils/logger.d.ts +61 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +52 -4
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/url.d.ts +47 -7
- package/dist/utils/url.d.ts.map +1 -1
- package/dist/utils/url.js +47 -7
- package/dist/utils/url.js.map +1 -1
- package/dist/ws/client.d.ts +60 -15
- package/dist/ws/client.d.ts.map +1 -1
- package/dist/ws/client.js +38 -27
- package/dist/ws/client.js.map +1 -1
- package/dist/ws/frame.d.ts +43 -9
- package/dist/ws/frame.d.ts.map +1 -1
- package/dist/ws/frame.js +35 -19
- package/dist/ws/frame.js.map +1 -1
- package/package.json +4 -4
package/dist/utils/logger.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured logger with configurable verbosity.
|
|
3
|
-
*/
|
|
4
1
|
const LEVEL_ORDER = {
|
|
5
2
|
debug: 0,
|
|
6
3
|
info: 1,
|
|
@@ -8,26 +5,64 @@ const LEVEL_ORDER = {
|
|
|
8
5
|
error: 3,
|
|
9
6
|
silent: 4,
|
|
10
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Default {@link Logger} implementation that writes to `process.stderr`.
|
|
10
|
+
* Messages are prefixed with `[nlcurl:<level>]` and only emitted when the
|
|
11
|
+
* message severity meets or exceeds the configured `level`.
|
|
12
|
+
*/
|
|
11
13
|
export class ConsoleLogger {
|
|
12
14
|
level;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new ConsoleLogger.
|
|
17
|
+
*
|
|
18
|
+
* @param {LogLevel} [level='warn'] - Minimum severity level to emit.
|
|
19
|
+
*/
|
|
13
20
|
constructor(level = 'warn') {
|
|
14
21
|
this.level = LEVEL_ORDER[level];
|
|
15
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Emits a debug-level message to `stderr` — only written when the
|
|
25
|
+
* configured minimum level is `'debug'`.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} message - Primary log message.
|
|
28
|
+
* @param {...unknown} args - Additional values appended after the message.
|
|
29
|
+
*/
|
|
16
30
|
debug(message, ...args) {
|
|
17
31
|
if (this.level <= LEVEL_ORDER.debug) {
|
|
18
32
|
process.stderr.write(`[nlcurl:debug] ${message}${this.formatArgs(args)}\n`);
|
|
19
33
|
}
|
|
20
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Emits an info-level message to `stderr` — only written when the
|
|
37
|
+
* configured minimum level is `'debug'` or `'info'`.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} message - Primary log message.
|
|
40
|
+
* @param {...unknown} args - Additional values appended after the message.
|
|
41
|
+
*/
|
|
21
42
|
info(message, ...args) {
|
|
22
43
|
if (this.level <= LEVEL_ORDER.info) {
|
|
23
44
|
process.stderr.write(`[nlcurl:info] ${message}${this.formatArgs(args)}\n`);
|
|
24
45
|
}
|
|
25
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Emits a warn-level message to `stderr` — only written when the
|
|
49
|
+
* configured minimum level is `'debug'`, `'info'`, or `'warn'`.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} message - Primary log message.
|
|
52
|
+
* @param {...unknown} args - Additional values appended after the message.
|
|
53
|
+
*/
|
|
26
54
|
warn(message, ...args) {
|
|
27
55
|
if (this.level <= LEVEL_ORDER.warn) {
|
|
28
56
|
process.stderr.write(`[nlcurl:warn] ${message}${this.formatArgs(args)}\n`);
|
|
29
57
|
}
|
|
30
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Emits an error-level message to `stderr` — only written when the
|
|
61
|
+
* configured minimum level is not `'silent'`.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} message - Primary log message.
|
|
64
|
+
* @param {...unknown} args - Additional values appended after the message.
|
|
65
|
+
*/
|
|
31
66
|
error(message, ...args) {
|
|
32
67
|
if (this.level <= LEVEL_ORDER.error) {
|
|
33
68
|
process.stderr.write(`[nlcurl:error] ${message}${this.formatArgs(args)}\n`);
|
|
@@ -39,7 +74,10 @@ export class ConsoleLogger {
|
|
|
39
74
|
return ' ' + args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ');
|
|
40
75
|
}
|
|
41
76
|
}
|
|
42
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* A no-op {@link Logger} that discards all messages. Assign this via
|
|
79
|
+
* {@link setDefaultLogger} to silence the library entirely.
|
|
80
|
+
*/
|
|
43
81
|
export const SILENT_LOGGER = {
|
|
44
82
|
debug() { },
|
|
45
83
|
info() { },
|
|
@@ -47,9 +85,19 @@ export const SILENT_LOGGER = {
|
|
|
47
85
|
error() { },
|
|
48
86
|
};
|
|
49
87
|
let _default = new ConsoleLogger('warn');
|
|
88
|
+
/**
|
|
89
|
+
* Replaces the process-wide default logger used by all NLcURL internals.
|
|
90
|
+
*
|
|
91
|
+
* @param {Logger} logger - New logger instance to install.
|
|
92
|
+
*/
|
|
50
93
|
export function setDefaultLogger(logger) {
|
|
51
94
|
_default = logger;
|
|
52
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Returns the currently active process-wide logger.
|
|
98
|
+
*
|
|
99
|
+
* @returns {Logger} The active logger instance.
|
|
100
|
+
*/
|
|
53
101
|
export function getDefaultLogger() {
|
|
54
102
|
return _default;
|
|
55
103
|
}
|
package/dist/utils/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,GAA6B;IAC5C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;CACV,CAAC;AAcF;;;;GAIG;AACH,MAAM,OAAO,aAAa;IAChB,KAAK,CAAS;IAEtB;;;;OAIG;IACH,YAAY,QAAkB,MAAM;QAClC,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAEO,UAAU,CAAC,IAAe;QAChC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,OAAO,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1F,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,KAAK,KAAI,CAAC;IACV,IAAI,KAAI,CAAC;IACT,IAAI,KAAI,CAAC;IACT,KAAK,KAAI,CAAC;CACX,CAAC;AAEF,IAAI,QAAQ,GAAW,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,QAAQ,GAAG,MAAM,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/utils/url.d.ts
CHANGED
|
@@ -1,22 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Resolves `relative` against `base`. When `base` is `undefined` or the
|
|
3
|
+
* resolution fails, `relative` is returned as-is.
|
|
4
|
+
*
|
|
5
|
+
* @param {string | undefined} base - Base URL string.
|
|
6
|
+
* @param {string} relative - Relative or absolute URL to resolve.
|
|
7
|
+
* @returns {string} The resolved absolute URL, or `relative` if resolution fails.
|
|
4
8
|
*/
|
|
5
9
|
export declare function resolveURL(base: string | undefined, relative: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Appends `params` as query-string parameters to `url`. Existing parameters in
|
|
12
|
+
* the URL are preserved. `undefined` and `null` values are omitted.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} url - Base URL.
|
|
15
|
+
* @param {Record<string, string | number | boolean>} [params] - Key-value pairs to append.
|
|
16
|
+
* @returns {string} URL with appended query parameters.
|
|
17
|
+
*/
|
|
6
18
|
export declare function appendParams(url: string, params?: Record<string, string | number | boolean>): string;
|
|
19
|
+
/**
|
|
20
|
+
* Parses `raw` into a `URL` object.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} raw - Absolute URL string to parse.
|
|
23
|
+
* @returns {URL} Parsed URL.
|
|
24
|
+
* @throws {TypeError} If `raw` is not a valid absolute URL.
|
|
25
|
+
*/
|
|
7
26
|
export declare function parseURL(raw: string): URL;
|
|
8
27
|
/**
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
28
|
+
* Returns the origin of `url` in `scheme://hostname:port` form. The port is
|
|
29
|
+
* always included explicitly, defaulting to `443` for `https:` and `80` for
|
|
30
|
+
* `http:`.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} url - Absolute URL string.
|
|
33
|
+
* @returns {string} Origin string (e.g. `"https://example.com:443"`).
|
|
11
34
|
*/
|
|
12
35
|
export declare function originOf(url: string): string;
|
|
13
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Extracts the hostname from `url` for use as the TLS SNI server-name value.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} url - Absolute URL string.
|
|
40
|
+
* @returns {string} Hostname without port (e.g. `"example.com"`).
|
|
41
|
+
*/
|
|
14
42
|
export declare function sniHost(url: string): string;
|
|
15
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Extracts the host and port from `url`. The port defaults to `443` for
|
|
45
|
+
* `https:` and `80` for `http:` when not explicitly specified in the URL.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} url - Absolute URL string.
|
|
48
|
+
* @returns {{ host: string; port: number }} Hostname and numeric port.
|
|
49
|
+
*/
|
|
16
50
|
export declare function hostPort(url: string): {
|
|
17
51
|
host: string;
|
|
18
52
|
port: number;
|
|
19
53
|
};
|
|
20
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Returns the path and query string of `url` suitable for use as the
|
|
56
|
+
* request-target in an HTTP/1.1 request line.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} url - Absolute URL string.
|
|
59
|
+
* @returns {string} Path + query string (e.g. `"/search?q=hello"`).
|
|
60
|
+
*/
|
|
21
61
|
export declare function requestPath(url: string): string;
|
|
22
62
|
//# sourceMappingURL=url.d.ts.map
|
package/dist/utils/url.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO7E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GACjD,MAAM,CASR;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAEzC;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAOpE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C"}
|
package/dist/utils/url.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Resolves `relative` against `base`. When `base` is `undefined` or the
|
|
3
|
+
* resolution fails, `relative` is returned as-is.
|
|
4
|
+
*
|
|
5
|
+
* @param {string | undefined} base - Base URL string.
|
|
6
|
+
* @param {string} relative - Relative or absolute URL to resolve.
|
|
7
|
+
* @returns {string} The resolved absolute URL, or `relative` if resolution fails.
|
|
4
8
|
*/
|
|
5
9
|
export function resolveURL(base, relative) {
|
|
6
10
|
if (!base)
|
|
@@ -12,6 +16,14 @@ export function resolveURL(base, relative) {
|
|
|
12
16
|
return relative;
|
|
13
17
|
}
|
|
14
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Appends `params` as query-string parameters to `url`. Existing parameters in
|
|
21
|
+
* the URL are preserved. `undefined` and `null` values are omitted.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} url - Base URL.
|
|
24
|
+
* @param {Record<string, string | number | boolean>} [params] - Key-value pairs to append.
|
|
25
|
+
* @returns {string} URL with appended query parameters.
|
|
26
|
+
*/
|
|
15
27
|
export function appendParams(url, params) {
|
|
16
28
|
if (!params || Object.keys(params).length === 0)
|
|
17
29
|
return url;
|
|
@@ -23,23 +35,45 @@ export function appendParams(url, params) {
|
|
|
23
35
|
}
|
|
24
36
|
return parsed.toString();
|
|
25
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Parses `raw` into a `URL` object.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} raw - Absolute URL string to parse.
|
|
42
|
+
* @returns {URL} Parsed URL.
|
|
43
|
+
* @throws {TypeError} If `raw` is not a valid absolute URL.
|
|
44
|
+
*/
|
|
26
45
|
export function parseURL(raw) {
|
|
27
46
|
return new URL(raw);
|
|
28
47
|
}
|
|
29
48
|
/**
|
|
30
|
-
*
|
|
31
|
-
* `
|
|
49
|
+
* Returns the origin of `url` in `scheme://hostname:port` form. The port is
|
|
50
|
+
* always included explicitly, defaulting to `443` for `https:` and `80` for
|
|
51
|
+
* `http:`.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} url - Absolute URL string.
|
|
54
|
+
* @returns {string} Origin string (e.g. `"https://example.com:443"`).
|
|
32
55
|
*/
|
|
33
56
|
export function originOf(url) {
|
|
34
57
|
const u = new URL(url);
|
|
35
58
|
const port = u.port || (u.protocol === 'https:' ? '443' : '80');
|
|
36
59
|
return `${u.protocol}//${u.hostname}:${port}`;
|
|
37
60
|
}
|
|
38
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Extracts the hostname from `url` for use as the TLS SNI server-name value.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} url - Absolute URL string.
|
|
65
|
+
* @returns {string} Hostname without port (e.g. `"example.com"`).
|
|
66
|
+
*/
|
|
39
67
|
export function sniHost(url) {
|
|
40
68
|
return new URL(url).hostname;
|
|
41
69
|
}
|
|
42
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Extracts the host and port from `url`. The port defaults to `443` for
|
|
72
|
+
* `https:` and `80` for `http:` when not explicitly specified in the URL.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} url - Absolute URL string.
|
|
75
|
+
* @returns {{ host: string; port: number }} Hostname and numeric port.
|
|
76
|
+
*/
|
|
43
77
|
export function hostPort(url) {
|
|
44
78
|
const u = new URL(url);
|
|
45
79
|
const defaultPort = u.protocol === 'https:' ? 443 : 80;
|
|
@@ -48,7 +82,13 @@ export function hostPort(url) {
|
|
|
48
82
|
port: u.port ? parseInt(u.port, 10) : defaultPort,
|
|
49
83
|
};
|
|
50
84
|
}
|
|
51
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Returns the path and query string of `url` suitable for use as the
|
|
87
|
+
* request-target in an HTTP/1.1 request line.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} url - Absolute URL string.
|
|
90
|
+
* @returns {string} Path + query string (e.g. `"/search?q=hello"`).
|
|
91
|
+
*/
|
|
52
92
|
export function requestPath(url) {
|
|
53
93
|
const u = new URL(url);
|
|
54
94
|
return u.pathname + u.search;
|
package/dist/utils/url.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,IAAwB,EAAE,QAAgB;IACnE,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAC;IAC3B,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAW,EACX,MAAkD;IAElD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAE5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QACpD,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,WAAW,GAAG,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,QAAQ;QAChB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW;KAClD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;AAC/B,CAAC"}
|
package/dist/ws/client.d.ts
CHANGED
|
@@ -1,26 +1,40 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* Options for creating a {@link WebSocketClient} connection.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* @typedef {Object} WebSocketOptions
|
|
6
|
+
* @property {string} [impersonate] - Browser profile name for fingerprint impersonation.
|
|
7
|
+
* @property {boolean} [stealth] - Use the stealth TLS engine for byte-level fingerprinting.
|
|
8
|
+
* @property {Record<string, string>} [headers] - Additional HTTP upgrade request headers.
|
|
9
|
+
* @property {string[]} [protocols] - Sub-protocol names to negotiate.
|
|
10
|
+
* @property {boolean} [insecure] - Skip TLS certificate validation for `wss:` connections.
|
|
11
|
+
* @property {number} [timeout] - Connection timeout in milliseconds.
|
|
7
12
|
*/
|
|
8
|
-
import { EventEmitter } from 'node:events';
|
|
9
13
|
export interface WebSocketOptions {
|
|
10
|
-
/** Browser profile name to impersonate. */
|
|
11
14
|
impersonate?: string;
|
|
12
|
-
/** Use the stealth TLS engine. */
|
|
13
15
|
stealth?: boolean;
|
|
14
|
-
/** Extra headers for the upgrade request. */
|
|
15
16
|
headers?: Record<string, string>;
|
|
16
|
-
/** WebSocket sub-protocols. */
|
|
17
17
|
protocols?: string[];
|
|
18
|
-
/** Skip TLS certificate verification. */
|
|
19
18
|
insecure?: boolean;
|
|
20
|
-
/** Connection timeout in milliseconds. */
|
|
21
19
|
timeout?: number;
|
|
22
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* WebSocket connection state.
|
|
23
|
+
*
|
|
24
|
+
* @typedef {'connecting' | 'open' | 'closing' | 'closed'} WebSocketState
|
|
25
|
+
*/
|
|
23
26
|
export type WebSocketState = 'connecting' | 'open' | 'closing' | 'closed';
|
|
27
|
+
/**
|
|
28
|
+
* Typed event map for {@link WebSocketClient}.
|
|
29
|
+
*
|
|
30
|
+
* @typedef {Object} WebSocketEvents
|
|
31
|
+
* @property {[]} open - Emitted when the connection is established.
|
|
32
|
+
* @property {[data: string | Buffer, isBinary: boolean]} message - Emitted for each incoming message.
|
|
33
|
+
* @property {[code: number, reason: string]} close - Emitted when the connection closes.
|
|
34
|
+
* @property {[error: Error]} error - Emitted on connection or protocol errors.
|
|
35
|
+
* @property {[data: Buffer]} ping - Emitted when a PING frame is received.
|
|
36
|
+
* @property {[data: Buffer]} pong - Emitted when a PONG frame is received.
|
|
37
|
+
*/
|
|
24
38
|
export interface WebSocketEvents {
|
|
25
39
|
open: [];
|
|
26
40
|
message: [data: string | Buffer, isBinary: boolean];
|
|
@@ -29,6 +43,17 @@ export interface WebSocketEvents {
|
|
|
29
43
|
ping: [data: Buffer];
|
|
30
44
|
pong: [data: Buffer];
|
|
31
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* WebSocket client with optional browser fingerprint impersonation. Emits
|
|
48
|
+
* typed lifecycle events (`open`, `message`, `close`, `error`, `ping`, `pong`).
|
|
49
|
+
* The connection is initiated asynchronously in the constructor; listen for
|
|
50
|
+
* the `'open'` event before sending frames.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const ws = new WebSocketClient('wss://echo.example.com', { impersonate: 'chrome136' });
|
|
54
|
+
* ws.on('open', () => ws.sendText('hello'));
|
|
55
|
+
* ws.on('message', (data) => console.log(data));
|
|
56
|
+
*/
|
|
32
57
|
export declare class WebSocketClient extends EventEmitter {
|
|
33
58
|
state: WebSocketState;
|
|
34
59
|
protocol: string;
|
|
@@ -37,27 +62,47 @@ export declare class WebSocketClient extends EventEmitter {
|
|
|
37
62
|
private parser;
|
|
38
63
|
private fragments;
|
|
39
64
|
private fragmentOpcode;
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new WebSocketClient and begins connecting to `url`.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} url - WebSocket URL (`ws:` or `wss:`).
|
|
69
|
+
* @param {WebSocketOptions} [options={}] - Connection and impersonation options.
|
|
70
|
+
*/
|
|
40
71
|
constructor(url: string, options?: WebSocketOptions);
|
|
41
72
|
/**
|
|
42
|
-
*
|
|
73
|
+
* Sends a UTF-8 text message.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} data - Text to send.
|
|
76
|
+
* @throws {NLcURLError} If the WebSocket is not in the `'open'` state.
|
|
43
77
|
*/
|
|
44
78
|
sendText(data: string): void;
|
|
45
79
|
/**
|
|
46
|
-
*
|
|
80
|
+
* Sends a binary message.
|
|
81
|
+
*
|
|
82
|
+
* @param {Buffer} data - Binary data to send.
|
|
83
|
+
* @throws {NLcURLError} If the WebSocket is not in the `'open'` state.
|
|
47
84
|
*/
|
|
48
85
|
sendBinary(data: Buffer): void;
|
|
49
86
|
/**
|
|
50
|
-
*
|
|
87
|
+
* Sends a PING control frame.
|
|
88
|
+
*
|
|
89
|
+
* @param {Buffer} [data=Buffer.alloc(0)] - Optional ping payload (up to 125 bytes).
|
|
90
|
+
* @throws {NLcURLError} If the WebSocket is not in the `'open'` state.
|
|
51
91
|
*/
|
|
52
92
|
ping(data?: Buffer): void;
|
|
53
93
|
/**
|
|
54
|
-
*
|
|
94
|
+
* Initiates a graceful close handshake by sending a CLOSE frame with the
|
|
95
|
+
* given status code and reason. Does nothing if the connection is not open.
|
|
96
|
+
*
|
|
97
|
+
* @param {number} [code=1000] - WebSocket close status code.
|
|
98
|
+
* @param {string} [reason=''] - Human-readable close reason (UTF-8, max 123 bytes).
|
|
55
99
|
*/
|
|
56
100
|
close(code?: number, reason?: string): void;
|
|
57
101
|
private assertOpen;
|
|
58
102
|
private connect;
|
|
59
103
|
private performUpgrade;
|
|
60
104
|
private onData;
|
|
105
|
+
private drainBufferedFrames;
|
|
61
106
|
private handleFrame;
|
|
62
107
|
}
|
|
63
108
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/ws/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/ws/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/ws/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB3C;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,EAAE,CAAC;IACT,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACtB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IACxC,KAAK,EAAE,cAAc,CAAgB;IACrC,QAAQ,SAAM;IACrB,SAAgB,GAAG,EAAE,MAAM,CAAC;IAE5B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,cAAc,CAAuB;IAE7C;;;;;OAKG;gBACS,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB;IAUvD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM5B;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK9B;;;;;OAKG;IACH,IAAI,CAAC,IAAI,GAAE,MAAwB,GAAG,IAAI;IAK1C;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,SAAO,EAAE,MAAM,SAAK,GAAG,IAAI;IAYrC,OAAO,CAAC,UAAU;YAMJ,OAAO;IAkErB,OAAO,CAAC,cAAc;IAyGtB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,WAAW;CAwDpB"}
|
package/dist/ws/client.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WebSocket client with TLS fingerprint impersonation.
|
|
3
|
-
*
|
|
4
|
-
* Implements the WebSocket protocol (RFC 6455) over impersonated
|
|
5
|
-
* TLS connections. Supports text and binary messages, ping/pong,
|
|
6
|
-
* and graceful close.
|
|
7
|
-
*/
|
|
8
1
|
import { EventEmitter } from 'node:events';
|
|
9
2
|
import { NodeTLSEngine } from '../tls/node-engine.js';
|
|
10
3
|
import { StealthTLSEngine } from '../tls/stealth/engine.js';
|
|
11
4
|
import { getProfile } from '../fingerprints/database.js';
|
|
12
5
|
import { NLcURLError, ConnectionError } from '../core/errors.js';
|
|
13
6
|
import { encodeFrame, FrameParser, Opcode, generateWebSocketKey, computeAcceptKey, } from './frame.js';
|
|
7
|
+
/**
|
|
8
|
+
* WebSocket client with optional browser fingerprint impersonation. Emits
|
|
9
|
+
* typed lifecycle events (`open`, `message`, `close`, `error`, `ping`, `pong`).
|
|
10
|
+
* The connection is initiated asynchronously in the constructor; listen for
|
|
11
|
+
* the `'open'` event before sending frames.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const ws = new WebSocketClient('wss://echo.example.com', { impersonate: 'chrome136' });
|
|
15
|
+
* ws.on('open', () => ws.sendText('hello'));
|
|
16
|
+
* ws.on('message', (data) => console.log(data));
|
|
17
|
+
*/
|
|
14
18
|
export class WebSocketClient extends EventEmitter {
|
|
15
19
|
state = 'connecting';
|
|
16
20
|
protocol = '';
|
|
@@ -19,17 +23,25 @@ export class WebSocketClient extends EventEmitter {
|
|
|
19
23
|
parser = new FrameParser();
|
|
20
24
|
fragments = [];
|
|
21
25
|
fragmentOpcode = Opcode.TEXT;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new WebSocketClient and begins connecting to `url`.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} url - WebSocket URL (`ws:` or `wss:`).
|
|
30
|
+
* @param {WebSocketOptions} [options={}] - Connection and impersonation options.
|
|
31
|
+
*/
|
|
22
32
|
constructor(url, options = {}) {
|
|
23
33
|
super();
|
|
24
34
|
this.url = url;
|
|
25
|
-
// Start connection asynchronously
|
|
26
35
|
this.connect(url, options).catch((err) => {
|
|
27
36
|
this.state = 'closed';
|
|
28
37
|
this.emit('error', err);
|
|
29
38
|
});
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
|
-
*
|
|
41
|
+
* Sends a UTF-8 text message.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} data - Text to send.
|
|
44
|
+
* @throws {NLcURLError} If the WebSocket is not in the `'open'` state.
|
|
33
45
|
*/
|
|
34
46
|
sendText(data) {
|
|
35
47
|
this.assertOpen();
|
|
@@ -37,21 +49,31 @@ export class WebSocketClient extends EventEmitter {
|
|
|
37
49
|
this.socket.write(encodeFrame(Opcode.TEXT, payload));
|
|
38
50
|
}
|
|
39
51
|
/**
|
|
40
|
-
*
|
|
52
|
+
* Sends a binary message.
|
|
53
|
+
*
|
|
54
|
+
* @param {Buffer} data - Binary data to send.
|
|
55
|
+
* @throws {NLcURLError} If the WebSocket is not in the `'open'` state.
|
|
41
56
|
*/
|
|
42
57
|
sendBinary(data) {
|
|
43
58
|
this.assertOpen();
|
|
44
59
|
this.socket.write(encodeFrame(Opcode.BINARY, data));
|
|
45
60
|
}
|
|
46
61
|
/**
|
|
47
|
-
*
|
|
62
|
+
* Sends a PING control frame.
|
|
63
|
+
*
|
|
64
|
+
* @param {Buffer} [data=Buffer.alloc(0)] - Optional ping payload (up to 125 bytes).
|
|
65
|
+
* @throws {NLcURLError} If the WebSocket is not in the `'open'` state.
|
|
48
66
|
*/
|
|
49
67
|
ping(data = Buffer.alloc(0)) {
|
|
50
68
|
this.assertOpen();
|
|
51
69
|
this.socket.write(encodeFrame(Opcode.PING, data));
|
|
52
70
|
}
|
|
53
71
|
/**
|
|
54
|
-
*
|
|
72
|
+
* Initiates a graceful close handshake by sending a CLOSE frame with the
|
|
73
|
+
* given status code and reason. Does nothing if the connection is not open.
|
|
74
|
+
*
|
|
75
|
+
* @param {number} [code=1000] - WebSocket close status code.
|
|
76
|
+
* @param {string} [reason=''] - Human-readable close reason (UTF-8, max 123 bytes).
|
|
55
77
|
*/
|
|
56
78
|
close(code = 1000, reason = '') {
|
|
57
79
|
if (this.state !== 'open')
|
|
@@ -63,7 +85,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
63
85
|
reasonBuf.copy(payload, 2);
|
|
64
86
|
this.socket.write(encodeFrame(Opcode.CLOSE, payload));
|
|
65
87
|
}
|
|
66
|
-
// ---- Internal ----
|
|
67
88
|
assertOpen() {
|
|
68
89
|
if (this.state !== 'open') {
|
|
69
90
|
throw new NLcURLError('WebSocket is not open', 'ERR_WS_NOT_OPEN');
|
|
@@ -76,13 +97,11 @@ export class WebSocketClient extends EventEmitter {
|
|
|
76
97
|
? parseInt(parsed.port, 10)
|
|
77
98
|
: isSecure ? 443 : 80;
|
|
78
99
|
const host = parsed.hostname;
|
|
79
|
-
// Resolve profile
|
|
80
100
|
const profile = options.impersonate
|
|
81
101
|
? getProfile(options.impersonate) ?? undefined
|
|
82
102
|
: undefined;
|
|
83
103
|
let transport;
|
|
84
104
|
if (isSecure) {
|
|
85
|
-
// TLS connection with impersonation
|
|
86
105
|
const engine = options.stealth
|
|
87
106
|
? new StealthTLSEngine()
|
|
88
107
|
: new NodeTLSEngine();
|
|
@@ -98,7 +117,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
98
117
|
transport = tlsSocket;
|
|
99
118
|
}
|
|
100
119
|
else {
|
|
101
|
-
// Plain TCP (ws://)
|
|
102
120
|
const net = await import('node:net');
|
|
103
121
|
transport = await new Promise((resolve, reject) => {
|
|
104
122
|
const sock = net.createConnection({ host, port }, () => resolve(sock));
|
|
@@ -112,12 +130,9 @@ export class WebSocketClient extends EventEmitter {
|
|
|
112
130
|
});
|
|
113
131
|
}
|
|
114
132
|
this.socket = transport;
|
|
115
|
-
// Perform HTTP upgrade handshake
|
|
116
133
|
await this.performUpgrade(transport, parsed, options);
|
|
117
|
-
// Now in open state
|
|
118
134
|
this.state = 'open';
|
|
119
135
|
this.emit('open');
|
|
120
|
-
// Start reading frames
|
|
121
136
|
transport.on('data', (chunk) => this.onData(chunk));
|
|
122
137
|
transport.on('error', (err) => {
|
|
123
138
|
this.state = 'closed';
|
|
@@ -129,6 +144,7 @@ export class WebSocketClient extends EventEmitter {
|
|
|
129
144
|
this.emit('close', 1006, 'Connection lost');
|
|
130
145
|
}
|
|
131
146
|
});
|
|
147
|
+
this.drainBufferedFrames();
|
|
132
148
|
}
|
|
133
149
|
performUpgrade(socket, url, options) {
|
|
134
150
|
return new Promise((resolve, reject) => {
|
|
@@ -150,7 +166,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
150
166
|
}
|
|
151
167
|
request += `\r\n`;
|
|
152
168
|
socket.write(request);
|
|
153
|
-
// Read the upgrade response
|
|
154
169
|
let responseData = Buffer.alloc(0);
|
|
155
170
|
const expectedAccept = computeAcceptKey(key);
|
|
156
171
|
const onData = (chunk) => {
|
|
@@ -158,7 +173,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
158
173
|
const text = responseData.toString('utf8');
|
|
159
174
|
const headerEnd = text.indexOf('\r\n\r\n');
|
|
160
175
|
if (headerEnd === -1) {
|
|
161
|
-
// Limit response header size
|
|
162
176
|
if (responseData.length > 16384) {
|
|
163
177
|
cleanup();
|
|
164
178
|
reject(new NLcURLError('WebSocket upgrade response too large', 'ERR_WS_UPGRADE'));
|
|
@@ -175,7 +189,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
175
189
|
if (!statusMatch || statusMatch[1] !== '101') {
|
|
176
190
|
return reject(new NLcURLError(`WebSocket upgrade failed: ${statusLine}`, 'ERR_WS_UPGRADE'));
|
|
177
191
|
}
|
|
178
|
-
// Validate Sec-WebSocket-Accept
|
|
179
192
|
const headers = new Map();
|
|
180
193
|
for (const line of headerLines) {
|
|
181
194
|
const colonIdx = line.indexOf(':');
|
|
@@ -188,7 +201,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
188
201
|
return reject(new NLcURLError('Invalid Sec-WebSocket-Accept header', 'ERR_WS_UPGRADE'));
|
|
189
202
|
}
|
|
190
203
|
this.protocol = headers.get('sec-websocket-protocol') ?? '';
|
|
191
|
-
// Push any remaining data after headers into frame parser
|
|
192
204
|
const remaining = responseData.subarray(headerEnd + 4);
|
|
193
205
|
if (remaining.length > 0) {
|
|
194
206
|
this.parser.push(remaining);
|
|
@@ -209,6 +221,9 @@ export class WebSocketClient extends EventEmitter {
|
|
|
209
221
|
}
|
|
210
222
|
onData(chunk) {
|
|
211
223
|
this.parser.push(chunk);
|
|
224
|
+
this.drainBufferedFrames();
|
|
225
|
+
}
|
|
226
|
+
drainBufferedFrames() {
|
|
212
227
|
let frame;
|
|
213
228
|
while ((frame = this.parser.pull()) !== null) {
|
|
214
229
|
this.handleFrame(frame);
|
|
@@ -219,13 +234,11 @@ export class WebSocketClient extends EventEmitter {
|
|
|
219
234
|
case Opcode.TEXT:
|
|
220
235
|
case Opcode.BINARY:
|
|
221
236
|
if (frame.fin) {
|
|
222
|
-
// Complete message
|
|
223
237
|
const isBinary = frame.opcode === Opcode.BINARY;
|
|
224
238
|
const data = isBinary ? frame.payload : frame.payload.toString('utf8');
|
|
225
239
|
this.emit('message', data, isBinary);
|
|
226
240
|
}
|
|
227
241
|
else {
|
|
228
|
-
// Start of fragmented message
|
|
229
242
|
this.fragmentOpcode = frame.opcode;
|
|
230
243
|
this.fragments = [frame.payload];
|
|
231
244
|
}
|
|
@@ -248,7 +261,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
248
261
|
reason = frame.payload.subarray(2).toString('utf8');
|
|
249
262
|
}
|
|
250
263
|
if (this.state === 'open') {
|
|
251
|
-
// Server-initiated close, echo it back
|
|
252
264
|
this.state = 'closing';
|
|
253
265
|
this.socket.write(encodeFrame(Opcode.CLOSE, frame.payload));
|
|
254
266
|
}
|
|
@@ -259,7 +271,6 @@ export class WebSocketClient extends EventEmitter {
|
|
|
259
271
|
}
|
|
260
272
|
case Opcode.PING:
|
|
261
273
|
this.emit('ping', frame.payload);
|
|
262
|
-
// Auto-respond with pong
|
|
263
274
|
if (this.state === 'open') {
|
|
264
275
|
this.socket.write(encodeFrame(Opcode.PONG, frame.payload));
|
|
265
276
|
}
|