@fuman/net 0.0.1

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.
Files changed (59) hide show
  1. package/LICENSE +8 -0
  2. package/errors.cjs +14 -0
  3. package/errors.d.ts +6 -0
  4. package/errors.js +14 -0
  5. package/fake.cjs +42 -0
  6. package/fake.d.ts +16 -0
  7. package/fake.js +42 -0
  8. package/index.cjs +30 -0
  9. package/index.d.ts +7 -0
  10. package/index.js +30 -0
  11. package/ip/bundle.cjs +19 -0
  12. package/ip/bundle.d.ts +3 -0
  13. package/ip/bundle.js +19 -0
  14. package/ip/index.d.ts +8 -0
  15. package/ip/parse.cjs +34 -0
  16. package/ip/parse.d.ts +5 -0
  17. package/ip/parse.js +34 -0
  18. package/ip/types.d.ts +10 -0
  19. package/ip/v4.bench.d.ts +1 -0
  20. package/ip/v4.cjs +55 -0
  21. package/ip/v4.d.ts +4 -0
  22. package/ip/v4.js +55 -0
  23. package/ip/v6.bench.d.ts +1 -0
  24. package/ip/v6.cjs +217 -0
  25. package/ip/v6.d.ts +18 -0
  26. package/ip/v6.js +217 -0
  27. package/package.json +30 -0
  28. package/proxy/http/_protocol.cjs +31 -0
  29. package/proxy/http/_protocol.d.ts +3 -0
  30. package/proxy/http/_protocol.js +31 -0
  31. package/proxy/http/connect.cjs +34 -0
  32. package/proxy/http/connect.d.ts +4 -0
  33. package/proxy/http/connect.js +34 -0
  34. package/proxy/http/index.cjs +15 -0
  35. package/proxy/http/index.d.ts +5 -0
  36. package/proxy/http/index.js +15 -0
  37. package/proxy/http/types.cjs +10 -0
  38. package/proxy/http/types.d.ts +32 -0
  39. package/proxy/http/types.js +10 -0
  40. package/proxy/index.d.ts +2 -0
  41. package/proxy/socks/_protocol.cjs +111 -0
  42. package/proxy/socks/_protocol.d.ts +7 -0
  43. package/proxy/socks/_protocol.js +111 -0
  44. package/proxy/socks/connect.cjs +78 -0
  45. package/proxy/socks/connect.d.ts +4 -0
  46. package/proxy/socks/connect.js +78 -0
  47. package/proxy/socks/index.cjs +15 -0
  48. package/proxy/socks/index.d.ts +5 -0
  49. package/proxy/socks/index.js +15 -0
  50. package/proxy/socks/types.cjs +10 -0
  51. package/proxy/socks/types.d.ts +34 -0
  52. package/proxy/socks/types.js +10 -0
  53. package/reconnection.cjs +151 -0
  54. package/reconnection.d.ts +82 -0
  55. package/reconnection.js +151 -0
  56. package/types.d.ts +41 -0
  57. package/websocket.cjs +157 -0
  58. package/websocket.d.ts +44 -0
  59. package/websocket.js +157 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * An error has occurred while connecting to an HTTP(s) proxy
3
+ */
4
+ export declare class HttpProxyConnectionError extends Error {
5
+ readonly proxy: HttpProxySettings;
6
+ constructor(proxy: HttpProxySettings, message: string);
7
+ }
8
+ /**
9
+ * HTTP(s) proxy settings
10
+ */
11
+ export interface HttpProxySettings {
12
+ /**
13
+ * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`)
14
+ */
15
+ host: string;
16
+ /**
17
+ * Port of the proxy (e.g. `8888`)
18
+ */
19
+ port: number;
20
+ /**
21
+ * Proxy authorization username, if needed
22
+ */
23
+ user?: string;
24
+ /**
25
+ * Proxy authorization password, if needed
26
+ */
27
+ password?: string;
28
+ /**
29
+ * Proxy connection headers, if needed
30
+ */
31
+ headers?: Record<string, string>;
32
+ }
@@ -0,0 +1,10 @@
1
+ class HttpProxyConnectionError extends Error {
2
+ proxy;
3
+ constructor(proxy, message) {
4
+ super(`Error while connecting to ${proxy.host}:${proxy.port}: ${message}`);
5
+ this.proxy = proxy;
6
+ }
7
+ }
8
+ export {
9
+ HttpProxyConnectionError
10
+ };
@@ -0,0 +1,2 @@
1
+ export * from './http/index.js';
2
+ export * from './socks/index.js';
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const io = require("@fuman/io");
4
+ const utils = require("@fuman/utils");
5
+ const parse = require("../../ip/parse.cjs");
6
+ const v6 = require("../../ip/v6.cjs");
7
+ const SOCKS4_ERRORS = {
8
+ 91: "Request rejected or failed",
9
+ 92: "Request failed because client is not running identd",
10
+ 93: "Request failed because client's identd could not confirm the user ID in the request"
11
+ };
12
+ const SOCKS5_ERRORS = {
13
+ 1: "General failure",
14
+ 2: "Connection not allowed by ruleset",
15
+ 3: "Network unreachable",
16
+ 4: "Host unreachable",
17
+ 5: "Connection refused by destination host",
18
+ 6: "TTL expired",
19
+ 7: "Command not supported / protocol error",
20
+ 8: "Address type not supported"
21
+ };
22
+ function buildSocks4Connect(dest, username = "") {
23
+ let addr;
24
+ try {
25
+ addr = parse.parse(dest.address);
26
+ } catch {
27
+ }
28
+ let isHostname = false;
29
+ if (!addr) {
30
+ isHostname = true;
31
+ addr = { type: "ipv4", parts: utils.u8.allocWith([0, 0, 0, 42]) };
32
+ } else if (addr.type !== "ipv4") {
33
+ throw new TypeError("SOCKS4 only supports IPv4");
34
+ }
35
+ const hostnameLength = isHostname ? utils.utf8.encodedLength(dest.address) + 1 : 0;
36
+ const userIdLength = utils.utf8.encodedLength(username);
37
+ const buf = io.Bytes.alloc(8 + userIdLength + hostnameLength);
38
+ io.write.uint8(buf, 4);
39
+ io.write.uint8(buf, 1);
40
+ io.write.uint16be(buf, dest.port);
41
+ io.write.bytes(buf, addr.parts);
42
+ io.write.cUtf8String(buf, username);
43
+ if (isHostname) {
44
+ io.write.cUtf8String(buf, dest.address);
45
+ }
46
+ return buf.result();
47
+ }
48
+ function buildSocks5Greeting(authAvailable) {
49
+ const buf = utils.u8.alloc(authAvailable ? 4 : 3);
50
+ buf[0] = 5;
51
+ if (authAvailable) {
52
+ buf[1] = 2;
53
+ buf[2] = 0;
54
+ buf[3] = 2;
55
+ } else {
56
+ buf[1] = 1;
57
+ buf[2] = 0;
58
+ }
59
+ return buf;
60
+ }
61
+ function buildSocks5Auth(username, password) {
62
+ const usernameLen = utils.utf8.encodedLength(username);
63
+ const passwordLen = utils.utf8.encodedLength(password);
64
+ if (usernameLen > 255) {
65
+ throw new TypeError(`Too long username (${usernameLen} > 255)`);
66
+ }
67
+ if (passwordLen > 255) {
68
+ throw new TypeError(`Too long password (${passwordLen} > 255)`);
69
+ }
70
+ const buf = utils.u8.alloc(3 + usernameLen + passwordLen);
71
+ buf[0] = 1;
72
+ buf[1] = usernameLen;
73
+ utils.utf8.encoder.encodeInto(username, buf.subarray(2));
74
+ buf[2 + usernameLen] = passwordLen;
75
+ utils.utf8.encoder.encodeInto(password, buf.subarray(3 + usernameLen));
76
+ return buf;
77
+ }
78
+ function buildSocks5Connect(dest) {
79
+ let addr;
80
+ try {
81
+ addr = parse.parse(dest.address);
82
+ } catch {
83
+ }
84
+ const buf = io.Bytes.alloc(32);
85
+ io.write.uint8(buf, 5);
86
+ io.write.uint8(buf, 1);
87
+ io.write.uint8(buf, 0);
88
+ if (!addr) {
89
+ const addrLen = utils.utf8.encodedLength(dest.address);
90
+ if (addrLen > 255) {
91
+ throw new TypeError(`Too long hostname (${addrLen} > 255)`);
92
+ }
93
+ io.write.uint8(buf, 3);
94
+ io.write.uint8(buf, addrLen);
95
+ io.write.utf8String(buf, dest.address);
96
+ } else if (addr.type === "ipv6") {
97
+ io.write.uint8(buf, 4);
98
+ v6.writeV6(addr, buf);
99
+ } else {
100
+ io.write.uint8(buf, 1);
101
+ io.write.bytes(buf, addr.parts);
102
+ }
103
+ io.write.uint16be(buf, dest.port);
104
+ return buf.result();
105
+ }
106
+ exports.SOCKS4_ERRORS = SOCKS4_ERRORS;
107
+ exports.SOCKS5_ERRORS = SOCKS5_ERRORS;
108
+ exports.buildSocks4Connect = buildSocks4Connect;
109
+ exports.buildSocks5Auth = buildSocks5Auth;
110
+ exports.buildSocks5Connect = buildSocks5Connect;
111
+ exports.buildSocks5Greeting = buildSocks5Greeting;
@@ -0,0 +1,7 @@
1
+ import { TcpEndpoint } from '../../types.js';
2
+ export declare const SOCKS4_ERRORS: Record<number, string>;
3
+ export declare const SOCKS5_ERRORS: Record<number, string>;
4
+ export declare function buildSocks4Connect(dest: TcpEndpoint, username?: string): Uint8Array;
5
+ export declare function buildSocks5Greeting(authAvailable: boolean): Uint8Array;
6
+ export declare function buildSocks5Auth(username: string, password: string): Uint8Array;
7
+ export declare function buildSocks5Connect(dest: TcpEndpoint): Uint8Array;
@@ -0,0 +1,111 @@
1
+ import { Bytes, write } from "@fuman/io";
2
+ import { u8, utf8 } from "@fuman/utils";
3
+ import { parse } from "../../ip/parse.js";
4
+ import { writeV6 } from "../../ip/v6.js";
5
+ const SOCKS4_ERRORS = {
6
+ 91: "Request rejected or failed",
7
+ 92: "Request failed because client is not running identd",
8
+ 93: "Request failed because client's identd could not confirm the user ID in the request"
9
+ };
10
+ const SOCKS5_ERRORS = {
11
+ 1: "General failure",
12
+ 2: "Connection not allowed by ruleset",
13
+ 3: "Network unreachable",
14
+ 4: "Host unreachable",
15
+ 5: "Connection refused by destination host",
16
+ 6: "TTL expired",
17
+ 7: "Command not supported / protocol error",
18
+ 8: "Address type not supported"
19
+ };
20
+ function buildSocks4Connect(dest, username = "") {
21
+ let addr;
22
+ try {
23
+ addr = parse(dest.address);
24
+ } catch {
25
+ }
26
+ let isHostname = false;
27
+ if (!addr) {
28
+ isHostname = true;
29
+ addr = { type: "ipv4", parts: u8.allocWith([0, 0, 0, 42]) };
30
+ } else if (addr.type !== "ipv4") {
31
+ throw new TypeError("SOCKS4 only supports IPv4");
32
+ }
33
+ const hostnameLength = isHostname ? utf8.encodedLength(dest.address) + 1 : 0;
34
+ const userIdLength = utf8.encodedLength(username);
35
+ const buf = Bytes.alloc(8 + userIdLength + hostnameLength);
36
+ write.uint8(buf, 4);
37
+ write.uint8(buf, 1);
38
+ write.uint16be(buf, dest.port);
39
+ write.bytes(buf, addr.parts);
40
+ write.cUtf8String(buf, username);
41
+ if (isHostname) {
42
+ write.cUtf8String(buf, dest.address);
43
+ }
44
+ return buf.result();
45
+ }
46
+ function buildSocks5Greeting(authAvailable) {
47
+ const buf = u8.alloc(authAvailable ? 4 : 3);
48
+ buf[0] = 5;
49
+ if (authAvailable) {
50
+ buf[1] = 2;
51
+ buf[2] = 0;
52
+ buf[3] = 2;
53
+ } else {
54
+ buf[1] = 1;
55
+ buf[2] = 0;
56
+ }
57
+ return buf;
58
+ }
59
+ function buildSocks5Auth(username, password) {
60
+ const usernameLen = utf8.encodedLength(username);
61
+ const passwordLen = utf8.encodedLength(password);
62
+ if (usernameLen > 255) {
63
+ throw new TypeError(`Too long username (${usernameLen} > 255)`);
64
+ }
65
+ if (passwordLen > 255) {
66
+ throw new TypeError(`Too long password (${passwordLen} > 255)`);
67
+ }
68
+ const buf = u8.alloc(3 + usernameLen + passwordLen);
69
+ buf[0] = 1;
70
+ buf[1] = usernameLen;
71
+ utf8.encoder.encodeInto(username, buf.subarray(2));
72
+ buf[2 + usernameLen] = passwordLen;
73
+ utf8.encoder.encodeInto(password, buf.subarray(3 + usernameLen));
74
+ return buf;
75
+ }
76
+ function buildSocks5Connect(dest) {
77
+ let addr;
78
+ try {
79
+ addr = parse(dest.address);
80
+ } catch {
81
+ }
82
+ const buf = Bytes.alloc(32);
83
+ write.uint8(buf, 5);
84
+ write.uint8(buf, 1);
85
+ write.uint8(buf, 0);
86
+ if (!addr) {
87
+ const addrLen = utf8.encodedLength(dest.address);
88
+ if (addrLen > 255) {
89
+ throw new TypeError(`Too long hostname (${addrLen} > 255)`);
90
+ }
91
+ write.uint8(buf, 3);
92
+ write.uint8(buf, addrLen);
93
+ write.utf8String(buf, dest.address);
94
+ } else if (addr.type === "ipv6") {
95
+ write.uint8(buf, 4);
96
+ writeV6(addr, buf);
97
+ } else {
98
+ write.uint8(buf, 1);
99
+ write.bytes(buf, addr.parts);
100
+ }
101
+ write.uint16be(buf, dest.port);
102
+ return buf.result();
103
+ }
104
+ export {
105
+ SOCKS4_ERRORS,
106
+ SOCKS5_ERRORS,
107
+ buildSocks4Connect,
108
+ buildSocks5Auth,
109
+ buildSocks5Connect,
110
+ buildSocks5Greeting
111
+ };
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const io = require("@fuman/io");
4
+ const _protocol = require("./_protocol.cjs");
5
+ const types = require("./types.cjs");
6
+ async function connectV4(reader, writer, proxy, destination) {
7
+ await writer.write(_protocol.buildSocks4Connect(destination, proxy.user));
8
+ const response = await io.read.async.exactly(reader, 8);
9
+ if (response[0] !== 0) {
10
+ throw new types.SocksProxyConnectionError(proxy, `Unexpected response first byte: ${response[0]}`);
11
+ }
12
+ if (response[1] !== 90) {
13
+ const code = response[1];
14
+ throw new types.SocksProxyConnectionError(proxy, code in _protocol.SOCKS4_ERRORS ? _protocol.SOCKS4_ERRORS[code] : `Unknown error code: ${code}`);
15
+ }
16
+ }
17
+ async function connectV5(reader, writer, proxy, destination) {
18
+ await writer.write(_protocol.buildSocks5Greeting(proxy.user != null));
19
+ const greetingRes = await io.read.async.exactly(reader, 2);
20
+ if (greetingRes[0] !== 5) {
21
+ throw new types.SocksProxyConnectionError(proxy, `Unexpected response first byte: ${greetingRes[0]}`);
22
+ }
23
+ if (greetingRes[1] !== 0) {
24
+ if (greetingRes[1] === 2) {
25
+ if (proxy.user == null || proxy.password == null) {
26
+ throw new types.SocksProxyConnectionError(proxy, "Authentication is required, but not provided");
27
+ }
28
+ await writer.write(_protocol.buildSocks5Auth(proxy.user, proxy.password));
29
+ const authRes = await io.read.async.exactly(reader, 2);
30
+ if (authRes[0] !== 1) {
31
+ throw new types.SocksProxyConnectionError(proxy, `Invalid SOCKS auth version: ${authRes[0]}`);
32
+ }
33
+ if (authRes[1] !== 0) {
34
+ throw new types.SocksProxyConnectionError(proxy, `Unexpected SOCKS auth response, invalid auth? Code: ${authRes[1]}`);
35
+ }
36
+ } else {
37
+ throw new types.SocksProxyConnectionError(proxy, `Unsupported authentication method: ${greetingRes[1]}`);
38
+ }
39
+ }
40
+ await writer.write(_protocol.buildSocks5Connect(destination));
41
+ const response = await io.read.async.exactly(reader, 4);
42
+ if (response[0] !== 5) {
43
+ throw new types.SocksProxyConnectionError(proxy, `Unexpected response first byte: ${response[0]}`);
44
+ }
45
+ if (response[1] !== 0) {
46
+ const code = response[1];
47
+ throw new types.SocksProxyConnectionError(proxy, code in _protocol.SOCKS5_ERRORS ? _protocol.SOCKS5_ERRORS[code] : `Unknown error code: ${code}`);
48
+ }
49
+ switch (response[3]) {
50
+ case 0: {
51
+ await io.read.async.exactly(reader, 2);
52
+ break;
53
+ }
54
+ case 1: {
55
+ await io.read.async.exactly(reader, 6);
56
+ break;
57
+ }
58
+ case 4: {
59
+ await io.read.async.exactly(reader, 18);
60
+ break;
61
+ }
62
+ default:
63
+ throw new types.SocksProxyConnectionError(proxy, `Invalid BNDADDR type: ${response[3]}`);
64
+ }
65
+ }
66
+ async function performSocksHandshake(reader, writer, proxy, destination) {
67
+ if (proxy.version != null && proxy.version !== 4 && proxy.version !== 5) {
68
+ throw new types.SocksProxyConnectionError(
69
+ proxy,
70
+ `Invalid SOCKS version: ${proxy.version}`
71
+ );
72
+ }
73
+ if (proxy.version === 4) {
74
+ return connectV4(reader, writer, proxy, destination);
75
+ }
76
+ return connectV5(reader, writer, proxy, destination);
77
+ }
78
+ exports.performSocksHandshake = performSocksHandshake;
@@ -0,0 +1,4 @@
1
+ import { IReadable, IWritable } from '@fuman/io';
2
+ import { TcpEndpoint } from '../../types.js';
3
+ import { SocksProxySettings } from './types.js';
4
+ export declare function performSocksHandshake(reader: IReadable, writer: IWritable, proxy: SocksProxySettings, destination: TcpEndpoint): Promise<void>;
@@ -0,0 +1,78 @@
1
+ import { read } from "@fuman/io";
2
+ import { buildSocks4Connect, SOCKS4_ERRORS, buildSocks5Greeting, buildSocks5Auth, buildSocks5Connect, SOCKS5_ERRORS } from "./_protocol.js";
3
+ import { SocksProxyConnectionError } from "./types.js";
4
+ async function connectV4(reader, writer, proxy, destination) {
5
+ await writer.write(buildSocks4Connect(destination, proxy.user));
6
+ const response = await read.async.exactly(reader, 8);
7
+ if (response[0] !== 0) {
8
+ throw new SocksProxyConnectionError(proxy, `Unexpected response first byte: ${response[0]}`);
9
+ }
10
+ if (response[1] !== 90) {
11
+ const code = response[1];
12
+ throw new SocksProxyConnectionError(proxy, code in SOCKS4_ERRORS ? SOCKS4_ERRORS[code] : `Unknown error code: ${code}`);
13
+ }
14
+ }
15
+ async function connectV5(reader, writer, proxy, destination) {
16
+ await writer.write(buildSocks5Greeting(proxy.user != null));
17
+ const greetingRes = await read.async.exactly(reader, 2);
18
+ if (greetingRes[0] !== 5) {
19
+ throw new SocksProxyConnectionError(proxy, `Unexpected response first byte: ${greetingRes[0]}`);
20
+ }
21
+ if (greetingRes[1] !== 0) {
22
+ if (greetingRes[1] === 2) {
23
+ if (proxy.user == null || proxy.password == null) {
24
+ throw new SocksProxyConnectionError(proxy, "Authentication is required, but not provided");
25
+ }
26
+ await writer.write(buildSocks5Auth(proxy.user, proxy.password));
27
+ const authRes = await read.async.exactly(reader, 2);
28
+ if (authRes[0] !== 1) {
29
+ throw new SocksProxyConnectionError(proxy, `Invalid SOCKS auth version: ${authRes[0]}`);
30
+ }
31
+ if (authRes[1] !== 0) {
32
+ throw new SocksProxyConnectionError(proxy, `Unexpected SOCKS auth response, invalid auth? Code: ${authRes[1]}`);
33
+ }
34
+ } else {
35
+ throw new SocksProxyConnectionError(proxy, `Unsupported authentication method: ${greetingRes[1]}`);
36
+ }
37
+ }
38
+ await writer.write(buildSocks5Connect(destination));
39
+ const response = await read.async.exactly(reader, 4);
40
+ if (response[0] !== 5) {
41
+ throw new SocksProxyConnectionError(proxy, `Unexpected response first byte: ${response[0]}`);
42
+ }
43
+ if (response[1] !== 0) {
44
+ const code = response[1];
45
+ throw new SocksProxyConnectionError(proxy, code in SOCKS5_ERRORS ? SOCKS5_ERRORS[code] : `Unknown error code: ${code}`);
46
+ }
47
+ switch (response[3]) {
48
+ case 0: {
49
+ await read.async.exactly(reader, 2);
50
+ break;
51
+ }
52
+ case 1: {
53
+ await read.async.exactly(reader, 6);
54
+ break;
55
+ }
56
+ case 4: {
57
+ await read.async.exactly(reader, 18);
58
+ break;
59
+ }
60
+ default:
61
+ throw new SocksProxyConnectionError(proxy, `Invalid BNDADDR type: ${response[3]}`);
62
+ }
63
+ }
64
+ async function performSocksHandshake(reader, writer, proxy, destination) {
65
+ if (proxy.version != null && proxy.version !== 4 && proxy.version !== 5) {
66
+ throw new SocksProxyConnectionError(
67
+ proxy,
68
+ `Invalid SOCKS version: ${proxy.version}`
69
+ );
70
+ }
71
+ if (proxy.version === 4) {
72
+ return connectV4(reader, writer, proxy, destination);
73
+ }
74
+ return connectV5(reader, writer, proxy, destination);
75
+ }
76
+ export {
77
+ performSocksHandshake
78
+ };
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const connect = require("./connect.cjs");
4
+ function withSocksProxy(connect$1, proxy) {
5
+ return async (endpoint) => {
6
+ const conn = await connect$1({
7
+ address: proxy.host,
8
+ port: proxy.port
9
+ });
10
+ await connect.performSocksHandshake(conn, conn, proxy, endpoint);
11
+ return conn;
12
+ };
13
+ }
14
+ exports.performSocksHandshake = connect.performSocksHandshake;
15
+ exports.withSocksProxy = withSocksProxy;
@@ -0,0 +1,5 @@
1
+ import { ConnectFunction, ITcpConnection, TcpEndpoint } from '../../types.js';
2
+ import { SocksProxySettings } from './types.js';
3
+ export * from './connect.js';
4
+ export * from './types.js';
5
+ export declare function withSocksProxy<Connection extends ITcpConnection, Connect extends ConnectFunction<TcpEndpoint, Connection>>(connect: Connect, proxy: SocksProxySettings): Connect;
@@ -0,0 +1,15 @@
1
+ import { performSocksHandshake } from "./connect.js";
2
+ function withSocksProxy(connect, proxy) {
3
+ return async (endpoint) => {
4
+ const conn = await connect({
5
+ address: proxy.host,
6
+ port: proxy.port
7
+ });
8
+ await performSocksHandshake(conn, conn, proxy, endpoint);
9
+ return conn;
10
+ };
11
+ }
12
+ export {
13
+ performSocksHandshake,
14
+ withSocksProxy
15
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class SocksProxyConnectionError extends Error {
4
+ proxy;
5
+ constructor(proxy, message) {
6
+ super(`Error while connecting to ${proxy.host}:${proxy.port}: ${message}`);
7
+ this.proxy = proxy;
8
+ }
9
+ }
10
+ exports.SocksProxyConnectionError = SocksProxyConnectionError;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Settings for a SOCKS4/5 proxy
3
+ */
4
+ export interface SocksProxySettings {
5
+ /**
6
+ * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`)
7
+ */
8
+ host: string;
9
+ /**
10
+ * Port of the proxy (e.g. `8888`)
11
+ */
12
+ port: number;
13
+ /**
14
+ * Proxy authorization username, if needed
15
+ */
16
+ user?: string;
17
+ /**
18
+ * Proxy authorization password, if needed
19
+ */
20
+ password?: string;
21
+ /**
22
+ * Version of the SOCKS proxy (4 or 5)
23
+ *
24
+ * @default `5`
25
+ */
26
+ version?: 4 | 5;
27
+ }
28
+ /**
29
+ * An error has occurred while connecting to an SOCKS proxy
30
+ */
31
+ export declare class SocksProxyConnectionError extends Error {
32
+ readonly proxy: SocksProxySettings;
33
+ constructor(proxy: SocksProxySettings, message: string);
34
+ }
@@ -0,0 +1,10 @@
1
+ class SocksProxyConnectionError extends Error {
2
+ proxy;
3
+ constructor(proxy, message) {
4
+ super(`Error while connecting to ${proxy.host}:${proxy.port}: ${message}`);
5
+ this.proxy = proxy;
6
+ }
7
+ }
8
+ export {
9
+ SocksProxyConnectionError
10
+ };
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const utils = require("@fuman/utils");
4
+ const errors = require("./errors.cjs");
5
+ const defaultReconnectionStrategy = ({ previousWait }) => {
6
+ if (previousWait === null) return 0;
7
+ if (previousWait === 0) return 1e3;
8
+ if (previousWait >= 5e3) return 5e3;
9
+ return Math.min(5e3, previousWait + 1e3);
10
+ };
11
+ function defaultOnError(err) {
12
+ return err instanceof errors.ConnectionClosedError ? "reconnect" : "close";
13
+ }
14
+ class PersistentConnection {
15
+ constructor(params) {
16
+ this.params = params;
17
+ this.#strategy = params.strategy ?? defaultReconnectionStrategy;
18
+ this.#connect = params.connect;
19
+ this.#onError = params.onError ?? defaultOnError;
20
+ }
21
+ #state = {
22
+ previousWait: null,
23
+ lastError: null,
24
+ consequentFails: 0
25
+ };
26
+ #connect;
27
+ #lastAddress;
28
+ #connection;
29
+ #connecting = false;
30
+ #strategy;
31
+ #onError;
32
+ // boolean represents whether the timer is clean
33
+ // true - resolved because timer is up
34
+ // false - resolved because .close()/.reconnect() was called
35
+ #sleep;
36
+ #closed;
37
+ get isConnected() {
38
+ return this.#connection !== void 0;
39
+ }
40
+ get isConnecting() {
41
+ return this.#connection === void 0 && this.#connecting;
42
+ }
43
+ get isWaiting() {
44
+ return this.#connection === void 0 && this.#lastAddress !== void 0 && !this.#connecting;
45
+ }
46
+ get connection() {
47
+ return this.#connection ?? null;
48
+ }
49
+ get state() {
50
+ return this.#state;
51
+ }
52
+ #resetState() {
53
+ this.#state.previousWait = null;
54
+ this.#state.lastError = null;
55
+ this.#state.consequentFails = 0;
56
+ this.#connecting = false;
57
+ }
58
+ async #loop() {
59
+ while (true) {
60
+ try {
61
+ this.#connecting = true;
62
+ this.#connection = await this.#connect(this.#lastAddress);
63
+ if (this.#closed) {
64
+ this.#closed.resolve();
65
+ break;
66
+ }
67
+ this.#resetState();
68
+ await this.params.onOpen?.(this.#connection);
69
+ this.#connection.close();
70
+ this.#connection = void 0;
71
+ break;
72
+ } catch (err) {
73
+ const oldConnection = this.#connection;
74
+ this.#connection = void 0;
75
+ await this.params.onClose?.();
76
+ if (this.#closed) {
77
+ this.#closed.resolve();
78
+ break;
79
+ }
80
+ const action = await this.#onError(err, oldConnection ?? null, this.#state);
81
+ if (action === "close") {
82
+ break;
83
+ }
84
+ const wait = action === "reconnect-now" ? 0 : this.#strategy(this.#state);
85
+ if (wait === false) {
86
+ break;
87
+ }
88
+ this.params.onWait?.(wait);
89
+ if (wait !== 0) {
90
+ this.#sleep = new utils.Deferred();
91
+ const timer = utils.timers.setTimeout(this.#sleep.resolve, wait, true);
92
+ const sleepResult = await this.#sleep.promise;
93
+ this.#sleep = void 0;
94
+ if (!sleepResult) {
95
+ utils.timers.clearTimeout(timer);
96
+ if (this.#closed != null) {
97
+ this.#closed.resolve();
98
+ break;
99
+ } else {
100
+ continue;
101
+ }
102
+ }
103
+ }
104
+ this.#state.previousWait = wait;
105
+ this.#state.consequentFails = 1;
106
+ continue;
107
+ }
108
+ }
109
+ this.#lastAddress = void 0;
110
+ this.#resetState();
111
+ }
112
+ connect(address) {
113
+ if (this.#lastAddress !== void 0 && this.#lastAddress !== address) {
114
+ throw new Error("Connection is already open to another address");
115
+ }
116
+ this.#closed = void 0;
117
+ this.#lastAddress = address;
118
+ void this.#loop();
119
+ }
120
+ /**
121
+ * @param force Whether to close the existing connection if there is one
122
+ */
123
+ reconnect(force) {
124
+ if (this.#sleep) {
125
+ this.#sleep.resolve(false);
126
+ } else if (this.#connection && force) {
127
+ this.#connection.close();
128
+ }
129
+ }
130
+ async close() {
131
+ if (this.#closed) return this.#closed.promise;
132
+ if (this.#lastAddress == null) return;
133
+ this.#closed = new utils.Deferred();
134
+ if (this.#sleep) {
135
+ this.#sleep.resolve(false);
136
+ } else if (this.#connection) {
137
+ this.#connection.close();
138
+ } else if (this.#connecting) ;
139
+ return this.#closed.promise;
140
+ }
141
+ async changeTransport(connect) {
142
+ this.#connect = connect;
143
+ const addr = this.#lastAddress;
144
+ await this.close();
145
+ if (addr != null) {
146
+ this.connect(addr);
147
+ }
148
+ }
149
+ }
150
+ exports.PersistentConnection = PersistentConnection;
151
+ exports.defaultReconnectionStrategy = defaultReconnectionStrategy;