tunli 0.0.19

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 (63) hide show
  1. package/LICENSE.md +595 -0
  2. package/README.md +135 -0
  3. package/bin/tunli +11 -0
  4. package/client.js +31 -0
  5. package/package.json +51 -0
  6. package/src/cli-app/Dashboard.js +146 -0
  7. package/src/cli-app/Screen.js +135 -0
  8. package/src/cli-app/elements/ElementNode.js +97 -0
  9. package/src/cli-app/elements/Line.js +21 -0
  10. package/src/cli-app/elements/List/List.js +227 -0
  11. package/src/cli-app/elements/List/ListCell.js +83 -0
  12. package/src/cli-app/elements/List/ListColumn.js +52 -0
  13. package/src/cli-app/elements/List/ListRow.js +118 -0
  14. package/src/cli-app/elements/Row.js +38 -0
  15. package/src/cli-app/helper/utils.js +42 -0
  16. package/src/commands/Action/addDelValuesAction.js +56 -0
  17. package/src/commands/CommandAuth.js +32 -0
  18. package/src/commands/CommandClearAll.js +27 -0
  19. package/src/commands/CommandConfig.js +57 -0
  20. package/src/commands/CommandHTTP.js +131 -0
  21. package/src/commands/CommandInvite.js +38 -0
  22. package/src/commands/CommandRefresh.js +35 -0
  23. package/src/commands/CommandRegister.js +48 -0
  24. package/src/commands/Option/DeleteOption.js +6 -0
  25. package/src/commands/Option/SelectConfigOption.js +52 -0
  26. package/src/commands/SubCommand/AllowDenyCidrCommand.js +28 -0
  27. package/src/commands/SubCommand/HostCommand.js +22 -0
  28. package/src/commands/SubCommand/PortCommand.js +20 -0
  29. package/src/commands/helper/AliasResolver.js +13 -0
  30. package/src/commands/helper/BindArgs.js +53 -0
  31. package/src/commands/helper/SharedArg.js +32 -0
  32. package/src/commands/utils.js +96 -0
  33. package/src/config/ConfigAbstract.js +318 -0
  34. package/src/config/ConfigManager.js +70 -0
  35. package/src/config/GlobalConfig.js +14 -0
  36. package/src/config/GlobalLocalShardConfigAbstract.js +76 -0
  37. package/src/config/LocalConfig.js +7 -0
  38. package/src/config/PropertyConfig.js +122 -0
  39. package/src/config/SystemConfig.js +31 -0
  40. package/src/core/FS/utils.js +60 -0
  41. package/src/core/Ref.js +70 -0
  42. package/src/lib/Flow/getCurrentIp.js +18 -0
  43. package/src/lib/Flow/getLatestVersion.js +13 -0
  44. package/src/lib/Flow/proxyUrl.js +32 -0
  45. package/src/lib/Flow/validateAuthToken.js +19 -0
  46. package/src/lib/HttpClient.js +61 -0
  47. package/src/lib/defs.js +10 -0
  48. package/src/net/IPV4.js +139 -0
  49. package/src/net/http/IncomingMessage.js +92 -0
  50. package/src/net/http/ServerResponse.js +126 -0
  51. package/src/net/http/TunliRequest.js +1 -0
  52. package/src/net/http/TunliResponse.js +1 -0
  53. package/src/net/http/TunnelRequest.js +177 -0
  54. package/src/net/http/TunnelResponse.js +119 -0
  55. package/src/tunnel-client/TunnelClient.js +136 -0
  56. package/src/utils/arrayFunctions.js +45 -0
  57. package/src/utils/checkFunctions.js +161 -0
  58. package/src/utils/cliFunctions.js +62 -0
  59. package/src/utils/createRequest.js +12 -0
  60. package/src/utils/httpFunction.js +23 -0
  61. package/src/utils/npmFunctions.js +27 -0
  62. package/src/utils/stringFunctions.js +34 -0
  63. package/types/index.d.ts +112 -0
@@ -0,0 +1,70 @@
1
+ import EventEmitter from 'node:events'
2
+
3
+ /**
4
+ * A class representing a reference to a value.
5
+ * This class is useful for creating mutable references to values,
6
+ * allowing for controlled access and modification.
7
+ */
8
+ export class Ref extends EventEmitter {
9
+
10
+ /**
11
+ * @type {any}
12
+ * Private field to store the value.
13
+ */
14
+ #value
15
+
16
+ /**
17
+ * Constructor for the Ref class.
18
+ * If the provided value is an instance of Ref, it uses its value.
19
+ * Otherwise, it stores the provided value.
20
+ * @param {any} value - The value to store.
21
+ */
22
+ constructor(value) {
23
+ super()
24
+ if (value instanceof Ref) {
25
+ this.#value = value.#value
26
+ } else {
27
+ this.#value = { value }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Getter for the stored value.
33
+ * @returns {any} - The stored value.
34
+ */
35
+ get value() {
36
+ return this.#value.value
37
+ }
38
+
39
+ /**
40
+ * Setter for the stored value.
41
+ * @param {any} value - The new value.
42
+ */
43
+ set value(value) {
44
+ const oldValue = this.#value.value
45
+ this.#value.value = value
46
+ // Emit an event if the value has changed
47
+ if (oldValue !== value) {
48
+ this.emit('update', value, oldValue)
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * @template T
55
+ * Creates a new Ref instance.
56
+ * @param {T} [value] - The value to store.
57
+ * @returns {Ref} - A new Ref instance.
58
+ */
59
+ export const ref = (value) => {
60
+ return new Ref(value)
61
+ }
62
+
63
+ /**
64
+ * Checks if the value is an instance of Ref.
65
+ * @param {any} value - The value to check.
66
+ * @returns {boolean} - True if the value is an instance of Ref, otherwise false.
67
+ */
68
+ export const isRef = (value) => {
69
+ return value instanceof Ref
70
+ }
@@ -0,0 +1,18 @@
1
+ import {httpClient} from "#lib/HttpClient";
2
+ import {checkIpV4Cidr} from "#src/utils/checkFunctions";
3
+
4
+ export const getCurrentIp = async () => {
5
+ const {data, error} = await httpClient.get(`/ip`);
6
+
7
+ if (error) {
8
+ console.error(error)
9
+ process.exit(1)
10
+ }
11
+
12
+ try {
13
+ return checkIpV4Cidr(data)
14
+ } catch {
15
+ console.log(`invalid ip v4 address "${data}"`)
16
+ process.exit(1)
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ import {npmApiClient} from "#lib/HttpClient";
2
+
3
+ export const getLatestVersion = async () => {
4
+
5
+ await new Promise(resolve => setTimeout(resolve, 2000))
6
+ const {data, error} = await npmApiClient.get('/-/package/tunli/dist-tags')
7
+
8
+ if (error) {
9
+ return false
10
+ }
11
+
12
+ return data.latest
13
+ }
@@ -0,0 +1,32 @@
1
+ import {securedHttpClient} from "#lib/HttpClient";
2
+ import {SERVER_HOST} from "#lib/defs";
3
+
4
+ export const requestNewProxyUrl = async (token) => {
5
+
6
+ const {data, error} = await securedHttpClient(token).get('/create');
7
+
8
+ if (error) {
9
+ console.error('Auth failed as server response error, status: ', error);
10
+ process.exit(1)
11
+ }
12
+
13
+ return `https://${data}.${SERVER_HOST}`
14
+ }
15
+
16
+ export const renewProxyUrlRegistration = async (proxyUrl, token) => {
17
+
18
+ const subDomain = new URL(proxyUrl).hostname.split('.', 1)[0]
19
+
20
+ const {data, error} = await securedHttpClient(token).get(`/renew/${subDomain}`);
21
+
22
+ if (data === false) {
23
+ return false
24
+ }
25
+
26
+ if (!data) {
27
+ console.error('Renew failed, request a new URL', error);
28
+ process.exit(1)
29
+ }
30
+
31
+ return proxyUrl
32
+ }
@@ -0,0 +1,19 @@
1
+ import {Command} from "commander";
2
+ import {Ref} from "#src/core/Ref";
3
+
4
+
5
+ /**
6
+ * @param {Ref} configRef
7
+ * @param {Command} cmd
8
+ */
9
+ export const validateAuthToken = (cmd, configRef) => {
10
+ cmd.hook('preAction', (thisCommand, actionCommand) => {
11
+
12
+ /** @type {LocalConfig|GlobalConfig} */
13
+ const config = configRef.value
14
+
15
+ if (!config.authToken) {
16
+ actionCommand.error("error: Missing authToken. Please run register firstly");
17
+ }
18
+ })
19
+ }
@@ -0,0 +1,61 @@
1
+ import axios from "axios";
2
+ import {AUTH_SERVER_URL} from "#lib/defs";
3
+
4
+ const createClient = (options = {}) => {
5
+
6
+ const defaultOptions = {
7
+ baseURL: AUTH_SERVER_URL,
8
+ headers: {
9
+ 'user-agent': 'tunli/1.0'
10
+ }
11
+ }
12
+
13
+ const headers = {...defaultOptions.headers, ...options.headers ?? {}};
14
+ options = {...defaultOptions, ...options, headers};
15
+
16
+ const httpClient = axios.create(options)
17
+ httpClient.interceptors.response.use((response) => {
18
+ return {
19
+ data: response.data
20
+ }
21
+ },
22
+ (error) => {
23
+ if (error.code === 'ECONNREFUSED') {
24
+ console.error('Connection refused to', error.config.url);
25
+ }
26
+ const message = error.response?.data ? error.response?.data : null
27
+ return Promise.resolve({
28
+ error: message ?? {
29
+ code: error.code,
30
+ message: error.message
31
+ }
32
+ });
33
+ });
34
+
35
+ return httpClient
36
+ }
37
+
38
+
39
+ /**
40
+ * @type {AxiosInstance}
41
+ */
42
+ export const httpClient = createClient();
43
+
44
+ /**
45
+ * @type {AxiosInstance}
46
+ */
47
+ export const npmApiClient = createClient({
48
+ baseURL: 'https://registry.npmjs.org/'
49
+ });
50
+
51
+ /**
52
+ * @param {string} token - Der Authentifizierungs-Token.
53
+ * @returns {AxiosInstance} - Eine konfigurierte Axios-Instanz mit Authentifizierung.
54
+ */
55
+ export const securedHttpClient = (token) => {
56
+ return createClient({
57
+ headers: {
58
+ authorization: `Bearer ${token}`,
59
+ },
60
+ })
61
+ }
@@ -0,0 +1,10 @@
1
+ import {resolve} from "path";
2
+ import {homedir} from "os";
3
+
4
+ export const SERVER_HOST = 'tunli.app'
5
+ export const CONFIG_DIR_NAME = '.tunli'
6
+
7
+ export const AUTH_SERVER_URL = 'https://api.tunli.app'
8
+
9
+ export const GLOBAL_CONFIG_DIR = resolve(homedir(), CONFIG_DIR_NAME);
10
+ export const CONFIG_FILENAME = 'default.json'
@@ -0,0 +1,139 @@
1
+ export const ipV4 = (address) => {
2
+
3
+ if (address instanceof IPV4) {
4
+ return address
5
+ }
6
+ return new IPV4(address)
7
+ }
8
+
9
+ export class IPV4 {
10
+
11
+ #ipv4Address
12
+
13
+ /**
14
+ * @type {IPV4}
15
+ */
16
+ #subnet
17
+
18
+ /**
19
+ * @type {boolean}
20
+ */
21
+ #isValid
22
+
23
+ constructor(ipv4Address) {
24
+
25
+ this._ipv4Address = this.#ipv4Address = this.#prepare(ipv4Address)
26
+ this.#isValid = this.#ipv4Address !== null
27
+ }
28
+
29
+ /**
30
+ * @returns {boolean}
31
+ */
32
+ get isValid() {
33
+ return this.#isValid
34
+ }
35
+
36
+ /**
37
+ * @return {number|null}
38
+ */
39
+ get cidrSuffix() {
40
+ return this.#ipv4Address?.cidrSuffix ?? null
41
+ }
42
+
43
+ get subnetAddress() {
44
+ this.#subnet ??= this.#calculateSubnet()
45
+ return this.#subnet
46
+ }
47
+
48
+ #calculateSubnet() {
49
+
50
+ const mask = ipV4(~((1 << (32 - this.cidrSuffix)) - 1) >>> 0)
51
+ const octets = []
52
+
53
+ for (let i = 0; i < 4; i++) {
54
+ const maskPart = mask.#ipv4Address.octetsAsIntegers[i]
55
+ const ipPart = this.#ipv4Address.octetsAsIntegers[i]
56
+ octets.push(maskPart & ipPart)
57
+ }
58
+
59
+ return ipV4(octets.join('.'))
60
+ }
61
+
62
+ #prepare(ipv4Address) {
63
+
64
+ const typeOfAddress = typeof ipv4Address
65
+ let octets = []
66
+ let octetsAsIntegers = []
67
+ let octetsAsBinaryString = []
68
+ let ipv4AddressAsInt;
69
+ let ipv4AddressAsString;
70
+ let cidrSuffix = 32;
71
+
72
+ if (typeOfAddress === 'number') {
73
+ ipv4AddressAsInt = ipv4Address
74
+ const binaryRepresentation = ipv4Address.toString(2)
75
+ const chunkSize = 8
76
+ for (let i = binaryRepresentation.length; i > 0; i -= chunkSize) {
77
+ const octetAsBin = binaryRepresentation.substring(i - chunkSize, i).padStart(8, '0')
78
+ const octetAsInt = parseInt(octetAsBin, 2)
79
+
80
+ octetsAsBinaryString.unshift(octetAsBin);
81
+ octetsAsIntegers.unshift(octetAsInt);
82
+ octets.unshift(octetAsInt.toString());
83
+ }
84
+
85
+ ipv4AddressAsString = octets.join('.')
86
+ } else if (typeOfAddress === 'string') {
87
+
88
+ const parts = ipv4Address.split('/')
89
+ if (parts.length > 2) {
90
+ return null
91
+ } else if (parts.length === 2) {
92
+ cidrSuffix = parseInt(parts[1])
93
+ if (isNaN(cidrSuffix)) {
94
+ return null
95
+ }
96
+ }
97
+
98
+ octetsAsIntegers = parts[0].split('.').map(Number)
99
+ octets = octetsAsIntegers.map(x => x.toString())
100
+ ipv4AddressAsString = octets.join('.')
101
+ octetsAsBinaryString = octetsAsIntegers.map(x => x.toString(2).padStart(8, '0'))
102
+ } else {
103
+ return null
104
+ }
105
+
106
+ if (octetsAsIntegers.filter(x => x <= 255).length !== 4) {
107
+ return null
108
+ }
109
+
110
+ return {
111
+ asString: ipv4AddressAsString,
112
+ octets,
113
+ octetsAsIntegers,
114
+ octetsAsBinaryString,
115
+ asInt: ipv4AddressAsInt,
116
+ cidrSuffix
117
+ }
118
+ }
119
+
120
+ isInSubnet(subnet) {
121
+ subnet = ipV4(subnet)
122
+ const mask = ~((1 << (32 - subnet.cidrSuffix)) - 1) >>> 0;
123
+ return (this.toInteger() & mask) === (subnet.toInteger() & mask)
124
+ }
125
+
126
+ toString() {
127
+ return this.#ipv4Address?.asString ?? null
128
+ }
129
+
130
+ toInteger() {
131
+ if (!this.#ipv4Address) {
132
+ return null
133
+ }
134
+
135
+ this.#ipv4Address.asInt ??= parseInt(this.#ipv4Address.octetsAsBinaryString.join(''), 2)
136
+ return this.#ipv4Address.asInt
137
+ }
138
+ }
139
+
@@ -0,0 +1,92 @@
1
+ import {ServerResponse} from "#src/net/http/ServerResponse"
2
+ import {Socket} from "socket.io-client"
3
+ import {getRemoteAddress} from "#src/utils/httpFunction"
4
+
5
+ /**
6
+ * IncomingMessage class represents an HTTP request received by the server.
7
+ */
8
+ export class IncomingMessage {
9
+ /**
10
+ * @type {ServerResponse}
11
+ * @private
12
+ */
13
+ #res
14
+
15
+ /**
16
+ * @type {string}
17
+ * @private
18
+ */
19
+ #id
20
+
21
+ /**
22
+ * @type {Socket}
23
+ * @private
24
+ */
25
+ #socket
26
+
27
+ /**
28
+ * @type {string}
29
+ * @private
30
+ */
31
+ #remoteAddress
32
+
33
+ /**
34
+ * @type {string}
35
+ * @private
36
+ */
37
+ #url
38
+
39
+ /**
40
+ * Creates an instance of IncomingMessage.
41
+ * @param {SocketIoRawRequestObject} request - The raw request object.
42
+ * @param {string} requestId - The unique request identifier.
43
+ * @param {Socket} socket - The socket to communicate over.
44
+ */
45
+ constructor(request, requestId, socket) {
46
+ this.#url = request.path
47
+ this.#id = requestId
48
+ this.#socket = socket
49
+ this.#remoteAddress = getRemoteAddress(request)
50
+ }
51
+
52
+ /**
53
+ * Returns the URL path.
54
+ * @return {string}
55
+ */
56
+ get url() {
57
+ return this.#url
58
+ }
59
+
60
+ /**
61
+ * Returns the remote address.
62
+ * @return {string}
63
+ */
64
+ get remoteAddress() {
65
+ return this.#remoteAddress
66
+ }
67
+
68
+ /**
69
+ * Returns the unique request identifier.
70
+ * @return {string}
71
+ */
72
+ get id() {
73
+ return this.#id
74
+ }
75
+
76
+ /**
77
+ * Returns the socket associated with the request.
78
+ * @return {Socket}
79
+ */
80
+ get socket() {
81
+ return this.#socket
82
+ }
83
+
84
+ /**
85
+ * Returns the ServerResponse instance, creating it if necessary.
86
+ * @return {ServerResponse}
87
+ */
88
+ get res() {
89
+ this.#res ??= new ServerResponse(this)
90
+ return this.#res
91
+ }
92
+ }
@@ -0,0 +1,126 @@
1
+ import {IncomingMessage} from "#src/net/http/IncomingMessage"
2
+ import {TunnelResponse} from "#src/net/http/TunnelResponse"
3
+
4
+ /**
5
+ * ServerResponse class represents an HTTP response to be sent back to the client.
6
+ */
7
+ export class ServerResponse {
8
+
9
+ /**
10
+ * @type {IncomingMessage}
11
+ * @private
12
+ */
13
+ #req
14
+
15
+ /**
16
+ * @type {Object}
17
+ * @private
18
+ */
19
+ #headers = {}
20
+
21
+ /**
22
+ * @type {string}
23
+ * @private
24
+ */
25
+ #httpVersion = '1.1'
26
+
27
+ /**
28
+ * @type {TunnelResponse}
29
+ * @private
30
+ */
31
+ #res
32
+
33
+ /**
34
+ * @type {string}
35
+ * @private
36
+ */
37
+ #statusMessage = 'OK'
38
+
39
+ /**
40
+ * @type {number}
41
+ * @private
42
+ */
43
+ #statusCode = 200
44
+
45
+ /**
46
+ * Creates an instance of ServerResponse.
47
+ * @param {IncomingMessage} incomingMessage - The incoming message object.
48
+ */
49
+ constructor(incomingMessage) {
50
+ this.#req = incomingMessage
51
+ this.#res = new TunnelResponse({
52
+ responseId: incomingMessage.id,
53
+ socket: incomingMessage.socket,
54
+ })
55
+ }
56
+
57
+ /**
58
+ * Returns the associated IncomingMessage instance.
59
+ * @return {IncomingMessage}
60
+ */
61
+ get req() {
62
+ return this.#req
63
+ }
64
+
65
+ /**
66
+ * Sets the status code and message.
67
+ * @param {number} statusCode - The HTTP status code.
68
+ * @param {string} [statusMessage] - The HTTP status message.
69
+ * @return {ServerResponse}
70
+ */
71
+ status(statusCode, statusMessage) {
72
+ this.#statusMessage = statusMessage ?? {
73
+ 200: 'OK',
74
+ 201: 'Created',
75
+ 202: 'Accepted',
76
+ 204: 'No Content',
77
+ 301: 'Moved Permanently',
78
+ 302: 'Found',
79
+ 304: 'Not Modified',
80
+ 400: 'Bad Request',
81
+ 401: 'Unauthorized',
82
+ 403: 'Forbidden',
83
+ 404: 'Not Found',
84
+ 405: 'Method Not Allowed',
85
+ 500: 'Internal Server Error',
86
+ 501: 'Not Implemented',
87
+ 502: 'Bad Gateway',
88
+ 503: 'Service Unavailable',
89
+ 504: 'Gateway Timeout'
90
+ }[statusCode] ?? 'Unknown Status'
91
+ this.#statusCode = statusCode
92
+ return this
93
+ }
94
+
95
+ /**
96
+ * Sends the response with the provided body.
97
+ * @param {string|Buffer} body - The body of the response.
98
+ * @return {ServerResponse}
99
+ */
100
+ send(body) {
101
+ this.#res.writeHead(this.#statusCode, this.#statusMessage, this.#headers, this.#httpVersion)
102
+ this.#res.write(body)
103
+ this.#res.end()
104
+ return this
105
+ }
106
+
107
+ /**
108
+ * Sets the headers for the response.
109
+ * @param {Object} headers - The headers to set.
110
+ * @return {ServerResponse}
111
+ */
112
+ headers(headers) {
113
+ this.#headers = headers
114
+ return this
115
+ }
116
+
117
+ /**
118
+ * Sets the HTTP version for the response.
119
+ * @param {string} httpVersion - The HTTP version to set.
120
+ * @return {ServerResponse}
121
+ */
122
+ httpVersion(httpVersion) {
123
+ this.#httpVersion = httpVersion
124
+ return this
125
+ }
126
+ }
@@ -0,0 +1 @@
1
+ export class TunliRequest {}
@@ -0,0 +1 @@
1
+ export class TunliResponse {}