tunli 0.0.20 → 0.0.22
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/client.js +2 -0
- package/package.json +1 -1
- package/src/commands/CommandConfig.js +2 -0
- package/src/commands/CommandHTTP.js +13 -5
- package/src/commands/Option/ProfileListOption.js +65 -0
- package/src/commands/Option/SelectConfigOption.js +1 -1
- package/src/commands/SubCommand/ProtocolCommand.js +21 -0
- package/src/config/ConfigAbstract.js +12 -1
- package/src/config/GlobalLocalShardConfigAbstract.js +10 -1
- package/src/lib/Proxy.js +42 -0
- package/src/net/http/TunnelRequest.js +52 -0
- package/src/tunnel-client/TunnelClient.js +6 -2
- package/src/utils/checkFunctions.js +12 -0
- package/types/index.d.ts +6 -0
package/client.js
CHANGED
|
@@ -8,6 +8,7 @@ import {createCommandRefresh} from "#commands/CommandRefresh";
|
|
|
8
8
|
import {argumentAliasResolver} from "#commands/helper/AliasResolver";
|
|
9
9
|
import {createCommandInvite} from "#commands/CommandInvite";
|
|
10
10
|
import {addExamples, addUsage} from "#commands/utils";
|
|
11
|
+
import {profileListOption} from "#commands/Option/ProfileListOption";
|
|
11
12
|
|
|
12
13
|
program
|
|
13
14
|
.name('tunli')
|
|
@@ -17,6 +18,7 @@ program
|
|
|
17
18
|
console.log(`tunli: ${packageJson.version}`)
|
|
18
19
|
process.exit()
|
|
19
20
|
})
|
|
21
|
+
.addOption(profileListOption())
|
|
20
22
|
|
|
21
23
|
program.addCommand(createCommandConfig(program))
|
|
22
24
|
program.addCommand(createCommandHTTP(program), {isDefault: true})
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {portCommand} from "#commands/SubCommand/PortCommand";
|
|
|
5
5
|
import {hostCommand} from "#commands/SubCommand/HostCommand";
|
|
6
6
|
import {selectConfigOption} from "#commands/Option/SelectConfigOption";
|
|
7
7
|
import {addExample, extendUsage} from "#commands/utils";
|
|
8
|
+
import {protocolCommand} from "#commands/SubCommand/ProtocolCommand";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
*
|
|
@@ -41,6 +42,7 @@ export const createCommandConfig = (program) => {
|
|
|
41
42
|
cmd.addCommand(allowDenyCidrCommand('denyCidr', cmd, configRef))
|
|
42
43
|
cmd.addCommand(portCommand(configRef))
|
|
43
44
|
cmd.addCommand(hostCommand(configRef))
|
|
45
|
+
cmd.addCommand(protocolCommand(configRef))
|
|
44
46
|
|
|
45
47
|
selectConfigOption(cmd, configRef, true)
|
|
46
48
|
.action(exec(configRef, program));
|
|
@@ -10,6 +10,7 @@ import {renewProxyUrlRegistration, requestNewProxyUrl} from "#lib/Flow/proxyUrl"
|
|
|
10
10
|
import {getCurrentIp} from "#lib/Flow/getCurrentIp";
|
|
11
11
|
import {arrayUnique} from "#src/utils/arrayFunctions";
|
|
12
12
|
import {initDashboard} from "#src/cli-app/Dashboard";
|
|
13
|
+
import {proxy} from "#lib/Proxy";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @callback httpCommandExec
|
|
@@ -61,6 +62,7 @@ const exec = (configRef, cmd, program) => {
|
|
|
61
62
|
|
|
62
63
|
options.port ??= port
|
|
63
64
|
options.host ??= host
|
|
65
|
+
options.protocol ??= protocol ?? 'http'
|
|
64
66
|
|
|
65
67
|
const save = options.save
|
|
66
68
|
delete options.save
|
|
@@ -90,12 +92,18 @@ const exec = (configRef, cmd, program) => {
|
|
|
90
92
|
path: undefined,
|
|
91
93
|
allowCidr: options.allowCidr ?? config.allowCidr,
|
|
92
94
|
denyCidr: options.denyCidr ?? config.denyCidr,
|
|
93
|
-
protocol: protocol
|
|
95
|
+
protocol: options.protocol
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
const useDashboard = process.env.DASHBOARD !== 'off'
|
|
99
|
+
|
|
96
100
|
const client = new TunnelClient(clientOptions)
|
|
97
|
-
const dashboard = initDashboard(client, clientOptions, config)
|
|
101
|
+
const dashboard = useDashboard ? initDashboard(client, clientOptions, config) : null
|
|
98
102
|
await client.init(dashboard)
|
|
103
|
+
|
|
104
|
+
if (!useDashboard) {
|
|
105
|
+
console.log(clientOptions)
|
|
106
|
+
}
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
/**
|
|
@@ -116,9 +124,9 @@ export const createCommandHTTP = (program) => {
|
|
|
116
124
|
|
|
117
125
|
cmd.option('--host <string>', 'setting hostname', bindArgs(checkHost, sharedArgument, false))
|
|
118
126
|
cmd.option('--port <string>', 'setting port', bindArgs(checkPort, sharedArgument, false))
|
|
119
|
-
cmd.option('--allow-cidr <string>', 'allow-cidr', validateArrayArguments(validateIpV4))
|
|
120
|
-
cmd.option('--deny-cidr <string>', 'deny-cidr', validateArrayArguments(validateIpV4))
|
|
121
|
-
cmd.option('--self', 'allow self only', false)
|
|
127
|
+
cmd.option('--allow, --allow-cidr <string>', 'allow-cidr', validateArrayArguments(validateIpV4))
|
|
128
|
+
cmd.option('--deny, --deny-cidr <string>', 'deny-cidr', validateArrayArguments(validateIpV4))
|
|
129
|
+
cmd.option('--allow-self, --self', 'allow self only', false)
|
|
122
130
|
cmd.option('--save [alias]', 'save current settings as alias/local')
|
|
123
131
|
cmd.action(exec(configRef, cmd, program))
|
|
124
132
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {Option} from "commander";
|
|
2
|
+
import {ConfigManager} from "#src/config/ConfigManager";
|
|
3
|
+
|
|
4
|
+
export const profileListOption = () => {
|
|
5
|
+
const option = new Option('-l, --list', 'profile list')
|
|
6
|
+
option.argParser(() => {
|
|
7
|
+
/**
|
|
8
|
+
* @param label
|
|
9
|
+
* @param {ConfigAbstract} config
|
|
10
|
+
* @return {string}
|
|
11
|
+
*/
|
|
12
|
+
const createOutput = (label, config) => {
|
|
13
|
+
const profiles = [...config.profiles]
|
|
14
|
+
const maxLabelLength = Math.max(...profiles.map(x => x.length))
|
|
15
|
+
const whitespace = ''.padEnd(2)
|
|
16
|
+
const rows = [`${label}`, '']
|
|
17
|
+
// const rows = [`${label}${profiles.shift()}`]
|
|
18
|
+
const overview = {}
|
|
19
|
+
const proxyURLs = []
|
|
20
|
+
for (const profile of profiles) {
|
|
21
|
+
|
|
22
|
+
const info = config.use(profile)
|
|
23
|
+
const host = info.host
|
|
24
|
+
const port = info.port
|
|
25
|
+
const protocol = info.protocol
|
|
26
|
+
let proxyURL = info.proxyURL
|
|
27
|
+
|
|
28
|
+
let targetUrl = new URL(`${protocol}://${host}:${port}`).toString()
|
|
29
|
+
if (proxyURL) {
|
|
30
|
+
proxyURL = proxyURL.substring(0, proxyURL.length - 1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
targetUrl = targetUrl.substring(0, targetUrl.length - 1)
|
|
34
|
+
proxyURLs.push(proxyURL.length)
|
|
35
|
+
overview[profile] = {
|
|
36
|
+
host,
|
|
37
|
+
port,
|
|
38
|
+
targetUrl,
|
|
39
|
+
proxyURL
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const maxProxyUrlLength = Math.max(...proxyURLs)
|
|
44
|
+
|
|
45
|
+
for (const profile of profiles) {
|
|
46
|
+
let {targetUrl, proxyURL} = overview[profile]
|
|
47
|
+
proxyURL = proxyURL.padEnd(maxProxyUrlLength)
|
|
48
|
+
rows.push(`${whitespace}${profile.padEnd(maxLabelLength)} ${proxyURL} -> ${targetUrl}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return rows.join("\n") + "\n"
|
|
52
|
+
}
|
|
53
|
+
const localConf = ConfigManager.loadLocalOnly()
|
|
54
|
+
const globalConf = ConfigManager.loadGlobalOnly()
|
|
55
|
+
|
|
56
|
+
if (localConf.exists()) {
|
|
57
|
+
console.log(createOutput('Alias local config: ', localConf))
|
|
58
|
+
}
|
|
59
|
+
if (globalConf.exists()) {
|
|
60
|
+
console.log(createOutput('Alias global config: ', globalConf))
|
|
61
|
+
}
|
|
62
|
+
process.exit()
|
|
63
|
+
})
|
|
64
|
+
return option
|
|
65
|
+
}
|
|
@@ -10,7 +10,7 @@ import {ConfigManager} from "#src/config/ConfigManager";
|
|
|
10
10
|
export const selectConfigOption = (command, configRef, strictMode = false) => {
|
|
11
11
|
|
|
12
12
|
command.option('--global', 'Use the global configuration file (default)')
|
|
13
|
-
.option('--workdir', 'Use the configuration file for the current working directory')
|
|
13
|
+
.option('--local, --workdir', 'Use the configuration file for the current working directory')
|
|
14
14
|
.option('-p --alias <string>', 'setting alias name', 'default')
|
|
15
15
|
|
|
16
16
|
if (configRef) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {Command} from "commander";
|
|
2
|
+
|
|
3
|
+
import {checkProtocol} from "#src/utils/checkFunctions";
|
|
4
|
+
import {deleteOption} from "#commands/Option/DeleteOption";
|
|
5
|
+
import {addGetDeleteValueAction} from "#commands/Action/addDelValuesAction";
|
|
6
|
+
|
|
7
|
+
export const protocolCommand = (configRef) => {
|
|
8
|
+
const cmd = new Command('protocol')
|
|
9
|
+
|
|
10
|
+
cmd.description('default protocol "http"')
|
|
11
|
+
.argument('[PROTOCOL]', 'füge eine kurze beschreibung ein', checkProtocol)
|
|
12
|
+
.hook('preAction', (thisCommand, actionCommand) => {
|
|
13
|
+
if (thisCommand.args.length && thisCommand.opts().del) {
|
|
14
|
+
actionCommand.error("error: wenn delete dann keine argumente");
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
.addOption(deleteOption())
|
|
18
|
+
.action(addGetDeleteValueAction('protocol', configRef))
|
|
19
|
+
|
|
20
|
+
return cmd
|
|
21
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {writeFileSync} from "fs"
|
|
1
|
+
import {existsSync, writeFileSync} from "fs"
|
|
2
2
|
import {dirname} from 'path'
|
|
3
3
|
import {ensureDirectoryExists} from "#src/core/FS/utils";
|
|
4
4
|
import {PropertyConfig} from "#src/config/PropertyConfig";
|
|
@@ -143,6 +143,17 @@ export class ConfigAbstract {
|
|
|
143
143
|
return this.#activeProfile
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @return {string[]}
|
|
148
|
+
*/
|
|
149
|
+
get profiles() {
|
|
150
|
+
return Object.keys(this.#profileData.profile)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
exists() {
|
|
154
|
+
return existsSync(this.#path)
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
/**
|
|
147
158
|
* Prepare the configuration by defining properties.
|
|
148
159
|
* @param {{[p: string]: PropertyConfig}} propertyConfig - The configuration properties.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {checkHost, checkIpV4Cidr, checkPort, checkUrl} from "#src/utils/checkFunctions";
|
|
1
|
+
import {checkHost, checkInArray, checkIpV4Cidr, checkPort, checkUrl} from "#src/utils/checkFunctions";
|
|
2
2
|
import {ConfigAbstract, VISIBILITY_PUBLIC} from "#src/config/ConfigAbstract";
|
|
3
3
|
import {property} from "#src/config/PropertyConfig";
|
|
4
4
|
import {ConfigManager} from "#src/config/ConfigManager";
|
|
@@ -58,6 +58,15 @@ export class GlobalLocalShardConfigAbstract extends ConfigAbstract {
|
|
|
58
58
|
return checkHost(val)
|
|
59
59
|
}
|
|
60
60
|
}),
|
|
61
|
+
protocol: property({
|
|
62
|
+
visibility: VISIBILITY_PUBLIC,
|
|
63
|
+
writeable: true,
|
|
64
|
+
type: String,
|
|
65
|
+
defaultValue: 'http',
|
|
66
|
+
validate(val) {
|
|
67
|
+
return checkInArray(val, ['http', 'https'])
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/**
|
package/src/lib/Proxy.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {ConfigManager} from "#src/config/ConfigManager";
|
|
2
|
+
import {TunnelClient} from "#src/tunnel-client/TunnelClient";
|
|
3
|
+
import {renewProxyUrlRegistration} from "#lib/Flow/proxyUrl";
|
|
4
|
+
|
|
5
|
+
const prepareOptions = async (options) => {
|
|
6
|
+
|
|
7
|
+
options ??= {}
|
|
8
|
+
const localConf = ConfigManager.loadLocalOnly()
|
|
9
|
+
const globalConf = ConfigManager.loadGlobalOnly()
|
|
10
|
+
|
|
11
|
+
const authToken = globalConf.authToken
|
|
12
|
+
|
|
13
|
+
let preparedOptions = {authToken, ...localConf.dump(), ...options}
|
|
14
|
+
|
|
15
|
+
preparedOptions.server = await renewProxyUrlRegistration(preparedOptions.proxyURL, preparedOptions.authToken)
|
|
16
|
+
preparedOptions.denyCidr ??= []
|
|
17
|
+
preparedOptions.allowCidr ??= []
|
|
18
|
+
|
|
19
|
+
console.log(preparedOptions)
|
|
20
|
+
|
|
21
|
+
return preparedOptions
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {tunliProxyOptions} options
|
|
27
|
+
*/
|
|
28
|
+
export const proxy = async (options) => {
|
|
29
|
+
|
|
30
|
+
options = await prepareOptions(options)
|
|
31
|
+
const client = new TunnelClient(options)
|
|
32
|
+
|
|
33
|
+
await client.init()
|
|
34
|
+
console.log('INIT')
|
|
35
|
+
return options
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tunli = {
|
|
39
|
+
proxy
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default tunli
|
|
@@ -109,11 +109,15 @@ export const forwardTunnelRequestToProxyTarget = (req, eventEmitter, options) =>
|
|
|
109
109
|
setupErrorHandlers(tunnelRequest, localReq)
|
|
110
110
|
|
|
111
111
|
const onLocalResponse = (localRes) => {
|
|
112
|
+
|
|
112
113
|
localReq.off('error', onLocalError)
|
|
114
|
+
|
|
113
115
|
if (isWebSocket && localRes.upgrade) {
|
|
114
116
|
return
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
if (!isWebSocket) rewriteResponse(localRes, options)
|
|
120
|
+
|
|
117
121
|
const tunnelResponse = new TunnelResponse({
|
|
118
122
|
responseId: req.requestId,
|
|
119
123
|
socket: req.tunnelSocket,
|
|
@@ -175,3 +179,51 @@ export const forwardTunnelRequestToProxyTarget = (req, eventEmitter, options) =>
|
|
|
175
179
|
localReq.on('upgrade', onUpgrade)
|
|
176
180
|
}
|
|
177
181
|
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {Response<IncomingMessage>} localRes
|
|
186
|
+
* @param {tunnelClientOptions} options
|
|
187
|
+
*/
|
|
188
|
+
const rewriteResponse = (localRes, options) => {
|
|
189
|
+
|
|
190
|
+
options.proxyURL ??= options.server
|
|
191
|
+
|
|
192
|
+
const hostSearch = options.origin ?? options.host
|
|
193
|
+
const {protocol: protocolReplace, hostname: hostReplace} = new URL(options.proxyURL)
|
|
194
|
+
const baseURL = new URL(`${options.protocol}://${hostSearch}:${options.port}`).toString()
|
|
195
|
+
|
|
196
|
+
if (localRes.statusCode === 301) {
|
|
197
|
+
delete localRes.headers.location
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let location = localRes.headers.location
|
|
201
|
+
let setCookie = localRes.headers['set-cookie']
|
|
202
|
+
|
|
203
|
+
if (!setCookie || !Array.isArray(setCookie)) {
|
|
204
|
+
setCookie = null
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (location) {
|
|
208
|
+
location = new URL(location, baseURL).toString()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (location && location.charAt(0) !== '/' && location.startsWith(baseURL)) {
|
|
212
|
+
localRes.headers.location = location.replace(baseURL, options.proxyURL)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (setCookie) {
|
|
216
|
+
localRes.headers['set-cookie'] = setCookie.map(cookie => {
|
|
217
|
+
return updateCookieDomain(cookie, hostReplace) // TODO MOVE THIS PART TO SERVER
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function extractDomainFromCookieString(cookie) {
|
|
223
|
+
const domainMatch = cookie.match(/Domain=([^;]+)/);
|
|
224
|
+
return domainMatch ? domainMatch[1] : null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function updateCookieDomain(cookie, newDomain) {
|
|
228
|
+
return cookie.replace(/Domain=[^;]+/, `Domain=${newDomain}`);
|
|
229
|
+
}
|
|
@@ -16,7 +16,7 @@ export class TunnelClient extends EventEmitter {
|
|
|
16
16
|
#latency = ref(0)
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* @param {tunnelClientOptions} options
|
|
19
|
+
* @param {...tunnelClientOptions} options
|
|
20
20
|
*/
|
|
21
21
|
constructor(options) {
|
|
22
22
|
super()
|
|
@@ -94,6 +94,10 @@ export class TunnelClient extends EventEmitter {
|
|
|
94
94
|
setInterval(ping, 5000)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param [dashboard]
|
|
99
|
+
* @return {Promise<TunnelClient>}
|
|
100
|
+
*/
|
|
97
101
|
async init(dashboard) {
|
|
98
102
|
|
|
99
103
|
const params = await this.#createParameters(dashboard)
|
|
@@ -108,7 +112,7 @@ export class TunnelClient extends EventEmitter {
|
|
|
108
112
|
const webSocketCapturePath = await securedHttpClient(options.authToken).get('/capture_path')
|
|
109
113
|
|
|
110
114
|
if (webSocketCapturePath.error) {
|
|
111
|
-
dashboard
|
|
115
|
+
dashboard?.destroy()
|
|
112
116
|
if (webSocketCapturePath.error?.message === 'Request failed with status code 401') {
|
|
113
117
|
console.error('missing authorization, check your registration and try again')
|
|
114
118
|
} else {
|
|
@@ -159,3 +159,15 @@ export const checkPort = (valueOrSharedArg, isArgument = false, value) => {
|
|
|
159
159
|
|
|
160
160
|
return value
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
export const checkProtocol = (value) => {
|
|
164
|
+
return checkInArray(value, ['http', 'https'])
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const checkInArray = (val, arr) => {
|
|
168
|
+
if (!arr.includes(val)) {
|
|
169
|
+
throw new InvalidArgumentError(`Value must be one of (${arr.join(', ')})`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return val
|
|
173
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ interface ConfigAbstract {
|
|
|
48
48
|
|
|
49
49
|
export interface AppConfig {
|
|
50
50
|
|
|
51
|
+
protocol: protocol
|
|
51
52
|
port: number
|
|
52
53
|
host: string
|
|
53
54
|
authToken: string
|
|
@@ -110,3 +111,8 @@ export type keypressEventDetails = {
|
|
|
110
111
|
export interface keypressEventListener {
|
|
111
112
|
(char: string, details: keypressEventDetails): void
|
|
112
113
|
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
export type tunliProxyOptions = {
|
|
117
|
+
|
|
118
|
+
}
|