pinggy 0.3.5 → 0.3.7
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 +1 -1
- package/dist/chunk-65R2GMKQ.js +2101 -0
- package/dist/index.cjs +1798 -1349
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +24 -2
- package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
- package/package.json +2 -3
- package/.github/workflows/npm-publish-github-packages.yml +0 -34
- package/.github/workflows/publish-binaries.yml +0 -223
- package/Makefile +0 -4
- package/caxa_build.js +0 -24
- package/dist/chunk-T5ESYDJY.js +0 -121
- package/ent.plist +0 -14
- package/jest.config.js +0 -19
- package/src/_tests_/build_config.test.ts +0 -91
- package/src/cli/buildConfig.ts +0 -535
- package/src/cli/defaults.ts +0 -20
- package/src/cli/extendedOptions.ts +0 -153
- package/src/cli/help.ts +0 -43
- package/src/cli/options.ts +0 -50
- package/src/cli/starCli.ts +0 -229
- package/src/index.ts +0 -31
- package/src/logger.ts +0 -138
- package/src/main.ts +0 -87
- package/src/remote_management/handler.ts +0 -244
- package/src/remote_management/remoteManagement.ts +0 -226
- package/src/remote_management/remote_schema.ts +0 -176
- package/src/remote_management/websocket_handlers.ts +0 -180
- package/src/tui/blessed/TunnelTui.ts +0 -340
- package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
- package/src/tui/blessed/components/KeyBindings.ts +0 -236
- package/src/tui/blessed/components/Modals.ts +0 -302
- package/src/tui/blessed/components/UIComponents.ts +0 -306
- package/src/tui/blessed/components/index.ts +0 -4
- package/src/tui/blessed/config.ts +0 -53
- package/src/tui/blessed/headerFetcher.ts +0 -42
- package/src/tui/blessed/index.ts +0 -2
- package/src/tui/blessed/qrCodeGenerator.ts +0 -20
- package/src/tui/blessed/webDebuggerConnection.ts +0 -128
- package/src/tui/ink/asciArt.ts +0 -7
- package/src/tui/ink/hooks/useQrCodes.ts +0 -27
- package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
- package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
- package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
- package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
- package/src/tui/ink/index.tsx +0 -243
- package/src/tui/ink/layout/Borders.tsx +0 -15
- package/src/tui/ink/layout/Container.tsx +0 -15
- package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
- package/src/tui/ink/sections/KeyBindings.tsx +0 -58
- package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
- package/src/tui/ink/sections/StatsSection.tsx +0 -20
- package/src/tui/ink/sections/URLsSection.tsx +0 -53
- package/src/tui/ink/utils/utils.ts +0 -35
- package/src/tui/spinner/spinner.ts +0 -64
- package/src/tunnel_manager/TunnelManager.ts +0 -1212
- package/src/types.ts +0 -255
- package/src/utils/FileServer.ts +0 -112
- package/src/utils/detect_vc_redist_on_windows.ts +0 -111
- package/src/utils/getFreePort.ts +0 -41
- package/src/utils/htmlTemplates.ts +0 -146
- package/src/utils/parseArgs.ts +0 -79
- package/src/utils/printer.ts +0 -81
- package/src/utils/util.ts +0 -18
- package/src/workers/file_serve_worker.ts +0 -33
- package/tsconfig.json +0 -17
- package/tsup.config.ts +0 -12
package/src/cli/buildConfig.ts
DELETED
|
@@ -1,535 +0,0 @@
|
|
|
1
|
-
import { defaultOptions } from "./defaults.js";
|
|
2
|
-
import { parseExtendedOptions } from "./extendedOptions.js";
|
|
3
|
-
import { logger } from "../logger.js";
|
|
4
|
-
import { AdditionalForwarding, FinalConfig } from "../types.js";
|
|
5
|
-
import { ParsedValues } from "../utils/parseArgs.js";
|
|
6
|
-
import { cliOptions } from "./options.js";
|
|
7
|
-
import { getRandomId, isValidPort } from "../utils/util.js";
|
|
8
|
-
import { TunnelType } from "@pinggy/pinggy";
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import path from "path";
|
|
11
|
-
|
|
12
|
-
const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
|
13
|
-
|
|
14
|
-
// Define the set of valid keywords
|
|
15
|
-
const KEYWORDS = new Set([
|
|
16
|
-
TunnelType.Http,
|
|
17
|
-
TunnelType.Tcp,
|
|
18
|
-
TunnelType.Tls,
|
|
19
|
-
TunnelType.Udp,
|
|
20
|
-
TunnelType.TlsTcp,
|
|
21
|
-
'force',
|
|
22
|
-
'qr',
|
|
23
|
-
] as const);
|
|
24
|
-
|
|
25
|
-
type Keyword = typeof KEYWORDS extends Set<infer T> ? T : never;
|
|
26
|
-
|
|
27
|
-
function isKeyword(str: string): boolean {
|
|
28
|
-
return KEYWORDS.has(str.toLowerCase() as Keyword);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseUserAndDomain(str: string) {
|
|
32
|
-
let token: string | undefined;
|
|
33
|
-
let type: string | undefined;
|
|
34
|
-
let server: string | undefined;
|
|
35
|
-
let qrCode: boolean | undefined;
|
|
36
|
-
let forceFlag: boolean | undefined;
|
|
37
|
-
|
|
38
|
-
if (!str) return { token, type, server, qrCode, forceFlag } as const;
|
|
39
|
-
|
|
40
|
-
if (str.includes('@')) {
|
|
41
|
-
const [user, domain] = str.split('@', 2);
|
|
42
|
-
if (domainRegex.test(domain)) {
|
|
43
|
-
server = domain;
|
|
44
|
-
|
|
45
|
-
// parse user modifiers like token+type or just type
|
|
46
|
-
const parts = user.split('+');
|
|
47
|
-
|
|
48
|
-
if (parts.length === 0) {
|
|
49
|
-
return { token, type, server, qrCode, forceFlag } as const;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const firstPart = parts[0];
|
|
53
|
-
|
|
54
|
-
// If the first part is NOT a keyword, it's a token
|
|
55
|
-
// All subsequent parts must be keywords
|
|
56
|
-
if (!isKeyword(firstPart)) {
|
|
57
|
-
token = firstPart;
|
|
58
|
-
// Process remaining parts as keywords
|
|
59
|
-
for (let i = 1; i < parts.length; i++) {
|
|
60
|
-
const part = parts[i].toLowerCase();
|
|
61
|
-
if (!isKeyword(part)) {
|
|
62
|
-
throw new Error(`Invalid user format: unexpected token '${part}' when keywords are expected.`);
|
|
63
|
-
}
|
|
64
|
-
processKeyword(part);
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
// First part is a keyword, all parts must be keywords
|
|
68
|
-
for (const part of parts) {
|
|
69
|
-
const lowerPart = part.toLowerCase();
|
|
70
|
-
if (!isKeyword(lowerPart)) {
|
|
71
|
-
// Invalid: non-keyword when keywords are expected
|
|
72
|
-
throw new Error(`Invalid user format: unexpected token '${lowerPart}' when keywords are expected.`);
|
|
73
|
-
}
|
|
74
|
-
processKeyword(lowerPart);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function processKeyword(keyword: string) {
|
|
79
|
-
if ([TunnelType.Http, TunnelType.Tcp, TunnelType.Tls, TunnelType.Udp, TunnelType.TlsTcp].includes(keyword as TunnelType)) {
|
|
80
|
-
type = keyword;
|
|
81
|
-
} else if (keyword === 'force') {
|
|
82
|
-
forceFlag = true;
|
|
83
|
-
} else if (keyword === 'qr') {
|
|
84
|
-
qrCode = true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
} else if (domainRegex.test(str)) {
|
|
89
|
-
server = str;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return { token, type, server, qrCode, forceFlag } as const;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function parseUsers(positionalArgs: string[], explicitToken?: string) {
|
|
96
|
-
let token: string | undefined;
|
|
97
|
-
let server: string | undefined;
|
|
98
|
-
let type: string | undefined;
|
|
99
|
-
let forceFlag = false;
|
|
100
|
-
let qrCode = false;
|
|
101
|
-
let remaining: string[] = [...positionalArgs];
|
|
102
|
-
|
|
103
|
-
// Allow explicit token to carry user@domain
|
|
104
|
-
if (typeof explicitToken === 'string') {
|
|
105
|
-
const parsed = parseUserAndDomain(explicitToken);
|
|
106
|
-
if (parsed.server) server = parsed.server;
|
|
107
|
-
if (parsed.type) type = parsed.type;
|
|
108
|
-
if (parsed.token) token = parsed.token;
|
|
109
|
-
if (parsed.forceFlag) forceFlag = true;
|
|
110
|
-
if (parsed.qrCode) qrCode = true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (remaining.length > 0) {
|
|
114
|
-
const first = remaining[0];
|
|
115
|
-
const parsed = parseUserAndDomain(first);
|
|
116
|
-
if (parsed.server) {
|
|
117
|
-
server = parsed.server;
|
|
118
|
-
if (parsed.type) type = parsed.type;
|
|
119
|
-
if (parsed.token) token = parsed.token;
|
|
120
|
-
if (parsed.forceFlag) forceFlag = true;
|
|
121
|
-
if (parsed.qrCode) qrCode = true;
|
|
122
|
-
remaining = remaining.slice(1);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return { token, server, type, forceFlag, qrCode, remaining } as const;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function parseType(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>, inferredType?: string) {
|
|
130
|
-
const t = inferredType || values.type || finalConfig.tunnelType;
|
|
131
|
-
if (t === TunnelType.Http || t === TunnelType.Tcp || t === TunnelType.Tls || t === TunnelType.Udp || t === TunnelType.TlsTcp) {
|
|
132
|
-
finalConfig.tunnelType = [t];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function parseLocalPort(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>): Error | null {
|
|
137
|
-
if (typeof values.localport !== 'string') return null;
|
|
138
|
-
let lp = values.localport.trim();
|
|
139
|
-
|
|
140
|
-
let isHttps = false;
|
|
141
|
-
if (lp.startsWith('https://')) {
|
|
142
|
-
isHttps = true;
|
|
143
|
-
lp = lp.replace(/^https:\/\//, '');
|
|
144
|
-
} else if (lp.startsWith('http://')) {
|
|
145
|
-
lp = lp.replace(/^http:\/\//, '');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const parts = lp.split(':');
|
|
149
|
-
if (parts.length === 1) {
|
|
150
|
-
const port = parseInt(parts[0], 10);
|
|
151
|
-
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
152
|
-
finalConfig.forwarding = `localhost:${port}`;
|
|
153
|
-
} else {
|
|
154
|
-
return new Error('Invalid local port');
|
|
155
|
-
}
|
|
156
|
-
} else if (parts.length === 2) {
|
|
157
|
-
const host = parts[0] || 'localhost';
|
|
158
|
-
const port = parseInt(parts[1], 10);
|
|
159
|
-
if (!Number.isNaN(port) && isValidPort(port)) {
|
|
160
|
-
finalConfig.forwarding = `${host}:${port}`;
|
|
161
|
-
} else {
|
|
162
|
-
return new Error('Invalid local port. Please use -h option for help.');
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
return new Error('Invalid --localport format. Please use -h option for help.');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Remove IPv6 Brackets
|
|
172
|
-
// Example: From [::1] to ::1
|
|
173
|
-
function removeIPv6Brackets(ip: string): string {
|
|
174
|
-
if (ip.startsWith("[") && ip.endsWith("]")) {
|
|
175
|
-
return ip.slice(1, -1);
|
|
176
|
-
}
|
|
177
|
-
return ip;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function ipv6SafeSplitColon(s: string): string[] {
|
|
181
|
-
const result: string[] = [];
|
|
182
|
-
let buf = "";
|
|
183
|
-
const stack: string[] = [];
|
|
184
|
-
|
|
185
|
-
for (let i = 0; i < s.length; i++) {
|
|
186
|
-
const c = s[i];
|
|
187
|
-
|
|
188
|
-
if (c === "[") {
|
|
189
|
-
stack.push(c);
|
|
190
|
-
} else if (c === "]" && stack.length > 0) {
|
|
191
|
-
stack.pop();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (c === ":" && stack.length === 0) {
|
|
195
|
-
result.push(buf);
|
|
196
|
-
buf = "";
|
|
197
|
-
} else {
|
|
198
|
-
buf += c;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
result.push(buf);
|
|
203
|
-
return result;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const VALID_PROTOCOLS = ['http', 'tcp', 'udp', 'tls'] as const;
|
|
207
|
-
type ForwardingProtocol = typeof VALID_PROTOCOLS[number];
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
function parseDefaultForwarding(forwarding: string): AdditionalForwarding | Error {
|
|
211
|
-
const parts = ipv6SafeSplitColon(forwarding);
|
|
212
|
-
|
|
213
|
-
// Format: 5555:localhost:6666
|
|
214
|
-
if (parts.length === 3) {
|
|
215
|
-
const remotePort = parseInt(parts[0], 10);
|
|
216
|
-
const localDomain = removeIPv6Brackets(parts[1] || "localhost");
|
|
217
|
-
const localPort = parseInt(parts[2], 10);
|
|
218
|
-
return { remotePort, localDomain, localPort };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Format: domain.com:5555:localhost:6666
|
|
222
|
-
if (parts.length === 4) {
|
|
223
|
-
const remoteDomain = removeIPv6Brackets(parts[0]);
|
|
224
|
-
const remotePort = parseInt(parts[1], 10);
|
|
225
|
-
const localDomain = removeIPv6Brackets(parts[2] || "localhost");
|
|
226
|
-
const localPort = parseInt(parts[3], 10);
|
|
227
|
-
return { remoteDomain, remotePort, localDomain, localPort };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return new Error("forwarding address incorrect");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function parseAdditionalForwarding(
|
|
234
|
-
forwarding: string
|
|
235
|
-
): AdditionalForwarding | Error {
|
|
236
|
-
|
|
237
|
-
const toPort = (v?: string) => {
|
|
238
|
-
if (!v) return null;
|
|
239
|
-
const n = parseInt(v, 10);
|
|
240
|
-
return Number.isNaN(n) ? null : n;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const parsed = ipv6SafeSplitColon(forwarding);
|
|
244
|
-
if (parsed.length !== 4) {
|
|
245
|
-
return new Error(
|
|
246
|
-
"forwarding must be in format: [schema//]hostname[/port][@forwardingId]:<placeholder>:<forwardingAddress>:<forwardingPort>"
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// FIRST PART
|
|
251
|
-
// [schema//]hostname[/port][@forwardingId]
|
|
252
|
-
const firstPart = parsed[0];
|
|
253
|
-
|
|
254
|
-
// split optional @forwardingId (ignored for now)
|
|
255
|
-
const [hostPart] = firstPart.split("@");
|
|
256
|
-
|
|
257
|
-
let protocol: ForwardingProtocol = "http";
|
|
258
|
-
let remoteDomainRaw: string | undefined;
|
|
259
|
-
let remotePort: number | null = 0;
|
|
260
|
-
|
|
261
|
-
// CHECK IF PROTOCOL IS EXPLICIT
|
|
262
|
-
if (hostPart.includes("//")) {
|
|
263
|
-
// protocol is explicitly provided
|
|
264
|
-
const [schema, rest] = hostPart.split("//");
|
|
265
|
-
|
|
266
|
-
if (!schema || !VALID_PROTOCOLS.includes(schema as ForwardingProtocol)) {
|
|
267
|
-
return new Error(`invalid protocol: ${schema}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
protocol = schema as ForwardingProtocol;
|
|
271
|
-
|
|
272
|
-
const domainAndPort = rest.split("/");
|
|
273
|
-
if (domainAndPort.length > 2) {
|
|
274
|
-
return new Error("invalid forwarding address format");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
remoteDomainRaw = domainAndPort[0];
|
|
278
|
-
|
|
279
|
-
if (!remoteDomainRaw || !domainRegex.test(remoteDomainRaw)) {
|
|
280
|
-
return new Error("invalid remote domain");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const parsedRemotePort = toPort(domainAndPort[1]);
|
|
284
|
-
|
|
285
|
-
if (protocol === "http") {
|
|
286
|
-
// for HTTP always uses port 0
|
|
287
|
-
remotePort = 0;
|
|
288
|
-
} else {
|
|
289
|
-
// tcp / udp require remote port
|
|
290
|
-
if (parsedRemotePort === null || !isValidPort(parsedRemotePort)) {
|
|
291
|
-
return new Error(
|
|
292
|
-
`${protocol} forwarding requires port in format ${protocol}//domain/remotePort`
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
remotePort = parsedRemotePort;
|
|
296
|
-
}
|
|
297
|
-
} else {
|
|
298
|
-
// DEFAULT HTTP CASE
|
|
299
|
-
remoteDomainRaw = hostPart;
|
|
300
|
-
|
|
301
|
-
if (!domainRegex.test(remoteDomainRaw)) {
|
|
302
|
-
return new Error("invalid remote domain");
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// default http behavior
|
|
306
|
-
protocol = "http";
|
|
307
|
-
remotePort = 0;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// local target
|
|
311
|
-
const localDomain = removeIPv6Brackets(parsed[2] || "localhost");
|
|
312
|
-
const localPort = toPort(parsed[3]);
|
|
313
|
-
|
|
314
|
-
if (localPort === null || !isValidPort(localPort)) {
|
|
315
|
-
return new Error("forwarding address incorrect: invalid local port");
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return {
|
|
319
|
-
protocol,
|
|
320
|
-
remoteDomain: remoteDomainRaw,
|
|
321
|
-
remotePort,
|
|
322
|
-
localDomain,
|
|
323
|
-
localPort
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
function parseReverseTunnelAddr(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>): Error | null {
|
|
329
|
-
const reverseTunnel = values.R;
|
|
330
|
-
if ((!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) && !values.localport && !finalConfig.forwarding) {
|
|
331
|
-
return new Error("local port not specified. Please use '-h' option for help.");
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (!Array.isArray(reverseTunnel) || reverseTunnel.length === 0) {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
for (const forwarding of reverseTunnel) {
|
|
339
|
-
const slicedForwarding = ipv6SafeSplitColon(forwarding);
|
|
340
|
-
|
|
341
|
-
if (slicedForwarding.length === 3) {
|
|
342
|
-
const parsed = parseDefaultForwarding(forwarding);
|
|
343
|
-
if (parsed instanceof Error) return parsed;
|
|
344
|
-
|
|
345
|
-
finalConfig.forwarding = `${parsed.localDomain}:${parsed.localPort}`;
|
|
346
|
-
}
|
|
347
|
-
else if (slicedForwarding.length === 4) {
|
|
348
|
-
finalConfig.additionalForwarding ??= [];
|
|
349
|
-
|
|
350
|
-
const parsed = parseAdditionalForwarding(forwarding);
|
|
351
|
-
if (parsed instanceof Error) return parsed;
|
|
352
|
-
|
|
353
|
-
finalConfig.additionalForwarding.push(parsed);
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
return new Error(
|
|
357
|
-
"Incorrect command line arguments: reverse tunnel address incorrect. Please use '-h' option for help."
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return null;
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function parseLocalTunnelAddr(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>) {
|
|
367
|
-
if (!Array.isArray(values.L) || values.L.length === 0) return null;
|
|
368
|
-
const firstL = values.L[0] as string;
|
|
369
|
-
const parts = firstL.split(':');
|
|
370
|
-
if (parts.length === 3) {
|
|
371
|
-
const lp = parseInt(parts[0], 10);
|
|
372
|
-
if (!Number.isNaN(lp) && isValidPort(lp)) {
|
|
373
|
-
finalConfig.webDebugger = `localhost:${lp}`;
|
|
374
|
-
} else {
|
|
375
|
-
return new Error(`Invalid debugger port ${lp}`);
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
} else {
|
|
379
|
-
return new Error("Incorrect command line arguments: web debugger address incorrect. Please use '-h' option for help.");
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function parseDebugger(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>) {
|
|
385
|
-
let dbg = values.debugger;
|
|
386
|
-
if (typeof dbg !== 'string') return;
|
|
387
|
-
dbg = dbg.startsWith(':') ? dbg.slice(1) : dbg;
|
|
388
|
-
const d = parseInt(dbg, 10);
|
|
389
|
-
if (!Number.isNaN(d) && isValidPort(d)) {
|
|
390
|
-
finalConfig.webDebugger = `localhost:${d}`;
|
|
391
|
-
} else {
|
|
392
|
-
logger.error('Invalid debugger port:', dbg);
|
|
393
|
-
return new Error(`Invalid debugger port ${dbg}. Please use '-h' option for help.`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function parseToken(finalConfig: FinalConfig, explicitToken?: string) {
|
|
398
|
-
if (typeof explicitToken === 'string' && explicitToken) {
|
|
399
|
-
finalConfig.token = explicitToken;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
function parseArgs(finalConfig: FinalConfig, remainingPositionals: string[]) {
|
|
405
|
-
parseExtendedOptions(remainingPositionals, finalConfig);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function storeJson(config: FinalConfig, saveconf: string | null) {
|
|
409
|
-
if (saveconf) {
|
|
410
|
-
const path = saveconf;
|
|
411
|
-
try {
|
|
412
|
-
fs.writeFileSync(path, JSON.stringify(config, null, 2), { encoding: 'utf-8', flag: 'w' });
|
|
413
|
-
logger.info(`Configuration saved to ${path}`);
|
|
414
|
-
} catch (err) {
|
|
415
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
416
|
-
logger.error("Error loading configuration:", msg);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function loadJsonConfig(config: ParsedValues<typeof cliOptions>): FinalConfig | null {
|
|
423
|
-
const configpath = config["conf"];
|
|
424
|
-
if (typeof configpath === "string" && configpath.trim().length > 0) {
|
|
425
|
-
const filepath = path.resolve(configpath);
|
|
426
|
-
try {
|
|
427
|
-
const data = fs.readFileSync(filepath, { encoding: 'utf-8' });
|
|
428
|
-
const json = JSON.parse(data);
|
|
429
|
-
return json;
|
|
430
|
-
} catch (err) {
|
|
431
|
-
logger.error("Error loading configuration:", err);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
return null;
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function isSaveConfOption(values: ParsedValues<typeof cliOptions>): string | null {
|
|
440
|
-
const saveconf = values["saveconf"];
|
|
441
|
-
if (typeof saveconf === "string" && saveconf.trim().length > 0) {
|
|
442
|
-
return saveconf;
|
|
443
|
-
}
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function parseServe(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>): Error | null {
|
|
448
|
-
const sv = values.serve;
|
|
449
|
-
if (typeof sv !== 'string' || sv.trim().length === 0) return null;
|
|
450
|
-
finalConfig.serve = sv;
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function parseAutoReconnect(finalConfig: FinalConfig, values: ParsedValues<typeof cliOptions>): Error | null {
|
|
455
|
-
const autoReconnectValue = values.autoreconnect;
|
|
456
|
-
if (typeof autoReconnectValue === 'string') {
|
|
457
|
-
const trimmed = autoReconnectValue.trim().toLowerCase();
|
|
458
|
-
if (trimmed === 'true' || trimmed === '') {
|
|
459
|
-
finalConfig.autoReconnect = true;
|
|
460
|
-
} else if (trimmed === 'false') {
|
|
461
|
-
finalConfig.autoReconnect = false;
|
|
462
|
-
} else {
|
|
463
|
-
return new Error(`Invalid autoreconnect value: ${autoReconnectValue}. Use true or false.`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
export async function buildFinalConfig(values: ParsedValues<typeof cliOptions>, positionals: string[]): Promise<FinalConfig> {
|
|
471
|
-
let token: string | undefined;
|
|
472
|
-
let server: string | undefined;
|
|
473
|
-
let type: string | undefined;
|
|
474
|
-
let forceFlag = false;
|
|
475
|
-
let qrCode = false;
|
|
476
|
-
let finalConfig = new Object() as FinalConfig;
|
|
477
|
-
let saveconf = isSaveConfOption(values);
|
|
478
|
-
|
|
479
|
-
const configFromFile = loadJsonConfig(values);
|
|
480
|
-
|
|
481
|
-
const userParse = parseUsers(positionals, values.token);
|
|
482
|
-
token = userParse.token;
|
|
483
|
-
server = userParse.server;
|
|
484
|
-
type = userParse.type;
|
|
485
|
-
forceFlag = userParse.forceFlag;
|
|
486
|
-
qrCode = userParse.qrCode;
|
|
487
|
-
const remainingPositionals: string[] = userParse.remaining;
|
|
488
|
-
|
|
489
|
-
const initialTunnel = (type || values.type) as TunnelType;
|
|
490
|
-
finalConfig = {
|
|
491
|
-
...defaultOptions,
|
|
492
|
-
...(configFromFile || {}), // Apply loaded config on top of defaults
|
|
493
|
-
configid: getRandomId(),
|
|
494
|
-
token: token || (configFromFile?.token || (typeof values.token === 'string' ? values.token : '')),
|
|
495
|
-
serverAddress: server || (configFromFile?.serverAddress || defaultOptions.serverAddress),
|
|
496
|
-
tunnelType: initialTunnel ? [initialTunnel] : (configFromFile?.tunnelType || [TunnelType.Http]),
|
|
497
|
-
NoTUI: values.notui || (configFromFile?.NoTUI || false),
|
|
498
|
-
qrCode: qrCode || (configFromFile?.qrCode || false),
|
|
499
|
-
autoReconnect: configFromFile?.autoReconnect ? configFromFile.autoReconnect : defaultOptions.autoReconnect,
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
parseType(finalConfig, values, type);
|
|
504
|
-
|
|
505
|
-
// Apply token
|
|
506
|
-
parseToken(finalConfig, token || values.token);
|
|
507
|
-
|
|
508
|
-
const dbgErr = parseDebugger(finalConfig, values);
|
|
509
|
-
if (dbgErr instanceof Error) throw dbgErr;
|
|
510
|
-
|
|
511
|
-
const lpErr = parseLocalPort(finalConfig, values);
|
|
512
|
-
if (lpErr instanceof Error) throw lpErr;
|
|
513
|
-
|
|
514
|
-
const rErr = parseReverseTunnelAddr(finalConfig, values);
|
|
515
|
-
if (rErr instanceof Error) throw rErr;
|
|
516
|
-
|
|
517
|
-
const lErr = parseLocalTunnelAddr(finalConfig, values);
|
|
518
|
-
if (lErr instanceof Error) throw lErr;
|
|
519
|
-
|
|
520
|
-
const serveErr = parseServe(finalConfig, values);
|
|
521
|
-
if (serveErr instanceof Error) throw serveErr;
|
|
522
|
-
|
|
523
|
-
const autoReconnectErr = parseAutoReconnect(finalConfig, values);
|
|
524
|
-
if (autoReconnectErr instanceof Error) throw autoReconnectErr;
|
|
525
|
-
|
|
526
|
-
// Apply force flag if indicated via user
|
|
527
|
-
if (forceFlag) finalConfig.force = true;
|
|
528
|
-
|
|
529
|
-
// Parse positional extended options (like x:, w:, b:, k:, a:, u:, r:)
|
|
530
|
-
parseArgs(finalConfig, remainingPositionals);
|
|
531
|
-
|
|
532
|
-
storeJson(finalConfig, saveconf);
|
|
533
|
-
|
|
534
|
-
return finalConfig;
|
|
535
|
-
}
|
package/src/cli/defaults.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { PinggyOptions } from "@pinggy/pinggy";
|
|
2
|
-
|
|
3
|
-
// Default configuration for Tunnel
|
|
4
|
-
export const defaultOptions: Omit<PinggyOptions, 'token'> & { token: string | undefined } = {
|
|
5
|
-
token: undefined, // No default token
|
|
6
|
-
serverAddress: "a.pinggy.io",
|
|
7
|
-
forwarding: "localhost:8000",
|
|
8
|
-
webDebugger: "",
|
|
9
|
-
ipWhitelist: [],
|
|
10
|
-
basicAuth: [],
|
|
11
|
-
bearerTokenAuth: [],
|
|
12
|
-
headerModification: [],
|
|
13
|
-
force: false,
|
|
14
|
-
xForwardedFor: false,
|
|
15
|
-
httpsOnly: false,
|
|
16
|
-
originalRequestUrl: false,
|
|
17
|
-
allowPreflight: false,
|
|
18
|
-
reverseProxy: false,
|
|
19
|
-
autoReconnect: true,
|
|
20
|
-
};
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { PinggyOptions } from "@pinggy/pinggy";
|
|
2
|
-
import { isIP } from 'net';
|
|
3
|
-
import { logger } from "../logger.js";
|
|
4
|
-
import CLIPrinter from "../utils/printer.js";
|
|
5
|
-
|
|
6
|
-
export function parseExtendedOptions(options: string[] | undefined, config: PinggyOptions) {
|
|
7
|
-
if (!options) return;
|
|
8
|
-
|
|
9
|
-
for (const opt of options) {
|
|
10
|
-
const [key, value] = opt.replace(/^"|"$/g, "").split(/:(.+)/).filter(Boolean);
|
|
11
|
-
|
|
12
|
-
switch (key) {
|
|
13
|
-
case "x":
|
|
14
|
-
switch (value) {
|
|
15
|
-
case "https":
|
|
16
|
-
case "httpsonly":
|
|
17
|
-
config.httpsOnly = true;
|
|
18
|
-
break;
|
|
19
|
-
|
|
20
|
-
case "passpreflight":
|
|
21
|
-
case "allowpreflight":
|
|
22
|
-
config.allowPreflight = true;
|
|
23
|
-
break;
|
|
24
|
-
|
|
25
|
-
case "reverseproxy":
|
|
26
|
-
config.reverseProxy = false;
|
|
27
|
-
break;
|
|
28
|
-
|
|
29
|
-
case "xff":
|
|
30
|
-
config.xForwardedFor = true;
|
|
31
|
-
break;
|
|
32
|
-
|
|
33
|
-
case "fullurl":
|
|
34
|
-
case "fullrequesturl":
|
|
35
|
-
config.originalRequestUrl = true;
|
|
36
|
-
break;
|
|
37
|
-
|
|
38
|
-
default:
|
|
39
|
-
CLIPrinter.warn(`Unknown extended option "${key}"`);
|
|
40
|
-
logger.warn(`Warning: Unknown extended option "${key}"`);
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
break;
|
|
44
|
-
case "w":
|
|
45
|
-
// Whitelist IPs
|
|
46
|
-
if (value) {
|
|
47
|
-
const ips = value.split(",").map(ip => ip.trim()).filter(Boolean);
|
|
48
|
-
const invalidIps = ips.filter(ip => !(isValidIpV4Cidr(ip) || isValidIpV6Cidr(ip)));
|
|
49
|
-
|
|
50
|
-
if (invalidIps.length > 0) {
|
|
51
|
-
CLIPrinter.warn(`Invalid IP/CIDR(s) in whitelist: ${invalidIps.join(", ")}`);
|
|
52
|
-
logger.warn(`Warning: Invalid IP/CIDR(s) in whitelist: ${invalidIps.join(", ")}`);
|
|
53
|
-
}
|
|
54
|
-
if (!(invalidIps.length > 0)) {
|
|
55
|
-
config.ipWhitelist = ips;
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
CLIPrinter.warn(`Extended option "${opt}" for 'w' requires IP(s)`);
|
|
59
|
-
logger.warn(`Warning: Extended option "${opt}" for 'w' requires IP(s)`);
|
|
60
|
-
}
|
|
61
|
-
break;
|
|
62
|
-
case "k":
|
|
63
|
-
//bearer tokens
|
|
64
|
-
if (!config.bearerTokenAuth) config.bearerTokenAuth = [];
|
|
65
|
-
if (value) {
|
|
66
|
-
config.bearerTokenAuth.push(value);
|
|
67
|
-
} else {
|
|
68
|
-
CLIPrinter.warn(`Extended option "${opt}" for 'k' requires a value`);
|
|
69
|
-
logger.warn(`Warning: Extended option "${opt}" for 'k' requires a value`);
|
|
70
|
-
}
|
|
71
|
-
break;
|
|
72
|
-
|
|
73
|
-
case "b":
|
|
74
|
-
// basicauth "username:password"
|
|
75
|
-
if (value && value.includes(":")) {
|
|
76
|
-
const [username, password] = value.split(/:(.+)/);
|
|
77
|
-
if (!config.basicAuth) config.basicAuth = [];
|
|
78
|
-
config.basicAuth.push({ username, password });
|
|
79
|
-
} else {
|
|
80
|
-
CLIPrinter.warn(`Extended option "${opt}" for 'b' requires value in format username:password`);
|
|
81
|
-
logger.warn(`Warning: Extended option "${opt}" for 'b' requires value in format username:password`);
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
|
|
85
|
-
case "a":
|
|
86
|
-
// Add header
|
|
87
|
-
if (value && value.includes(":")) {
|
|
88
|
-
const [key, val] = value.split(/:(.+)/);
|
|
89
|
-
if (!config.headerModification) config.headerModification = [];
|
|
90
|
-
config.headerModification.push({ type: "add", key, value: [val] });
|
|
91
|
-
} else {
|
|
92
|
-
CLIPrinter.warn(`Extended option "${opt}" for 'a' requires key:value`);
|
|
93
|
-
logger.warn(`Warning: Extended option "${opt}" for 'a' requires key:value`);
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
case "u":
|
|
97
|
-
// Update header
|
|
98
|
-
if (value && value.includes(":")) {
|
|
99
|
-
const [key, val] = value.split(/:(.+)/);
|
|
100
|
-
if (!config.headerModification) config.headerModification = [];
|
|
101
|
-
config.headerModification.push({ type: "update", key, value: [val] });
|
|
102
|
-
} else {
|
|
103
|
-
CLIPrinter.warn(`Extended option "${opt}" for 'u' requires key:value`);
|
|
104
|
-
logger.warn(`Warning: Extended option "${opt}" for 'u' requires key:value`);
|
|
105
|
-
}
|
|
106
|
-
break;
|
|
107
|
-
case "r":
|
|
108
|
-
// Remove header
|
|
109
|
-
if (value) {
|
|
110
|
-
if (!config.headerModification) config.headerModification = [];
|
|
111
|
-
config.headerModification.push({ type: "remove", key: value });
|
|
112
|
-
} else {
|
|
113
|
-
CLIPrinter.warn(`Extended option "${opt}" for 'r' requires a key`);
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
default:
|
|
117
|
-
CLIPrinter.warn(`Unknown extended option "${key}"`);
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isValidIpV4Cidr(input: string): boolean {
|
|
124
|
-
// Check for CIDR notation
|
|
125
|
-
if (input.includes('/')) {
|
|
126
|
-
const [ip, mask] = input.split('/');
|
|
127
|
-
if (!ip || !mask) return false;
|
|
128
|
-
const isIp4 = isIP(ip) === 4;
|
|
129
|
-
const maskNum = parseInt(mask, 10);
|
|
130
|
-
const isMaskValid = !isNaN(maskNum) && maskNum >= 0 && maskNum <= 32;
|
|
131
|
-
return isIp4 && isMaskValid;
|
|
132
|
-
}
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
function isValidIpV6Cidr(input: string): boolean {
|
|
138
|
-
// Check for CIDR notation
|
|
139
|
-
if (input.includes('/')) {
|
|
140
|
-
const [rawIp, mask] = input.split('/');
|
|
141
|
-
if (!rawIp || !mask) return false;
|
|
142
|
-
|
|
143
|
-
// Strip zone index (e.g. %eth0) and surrounding brackets if present
|
|
144
|
-
const ip = rawIp.split('%')[0].replace(/^\[|\]$/g, '');
|
|
145
|
-
const isIp6 = isIP(ip) === 6;
|
|
146
|
-
const maskNum = parseInt(mask, 10);
|
|
147
|
-
const isMaskValid = !isNaN(maskNum) && maskNum >= 0 && maskNum <= 128;
|
|
148
|
-
return isIp6 && isMaskValid;
|
|
149
|
-
}
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|