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 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tunli",
3
3
  "description": "Node.js application for creating HTTP tunnels to make local software projects accessible over the internet.",
4
- "version": "0.0.20",
4
+ "version": "0.0.22",
5
5
  "main": "bin/tunli",
6
6
  "bin": {
7
7
  "tunli": "bin/tunli"
@@ -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 ?? 'http'
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
  /**
@@ -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.destroy()
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
+ }