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.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-65R2GMKQ.js +2101 -0
  3. package/dist/index.cjs +1798 -1349
  4. package/dist/index.d.cts +616 -0
  5. package/dist/index.d.ts +616 -0
  6. package/dist/index.js +24 -2
  7. package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
  8. package/package.json +2 -3
  9. package/.github/workflows/npm-publish-github-packages.yml +0 -34
  10. package/.github/workflows/publish-binaries.yml +0 -223
  11. package/Makefile +0 -4
  12. package/caxa_build.js +0 -24
  13. package/dist/chunk-T5ESYDJY.js +0 -121
  14. package/ent.plist +0 -14
  15. package/jest.config.js +0 -19
  16. package/src/_tests_/build_config.test.ts +0 -91
  17. package/src/cli/buildConfig.ts +0 -535
  18. package/src/cli/defaults.ts +0 -20
  19. package/src/cli/extendedOptions.ts +0 -153
  20. package/src/cli/help.ts +0 -43
  21. package/src/cli/options.ts +0 -50
  22. package/src/cli/starCli.ts +0 -229
  23. package/src/index.ts +0 -31
  24. package/src/logger.ts +0 -138
  25. package/src/main.ts +0 -87
  26. package/src/remote_management/handler.ts +0 -244
  27. package/src/remote_management/remoteManagement.ts +0 -226
  28. package/src/remote_management/remote_schema.ts +0 -176
  29. package/src/remote_management/websocket_handlers.ts +0 -180
  30. package/src/tui/blessed/TunnelTui.ts +0 -340
  31. package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
  32. package/src/tui/blessed/components/KeyBindings.ts +0 -236
  33. package/src/tui/blessed/components/Modals.ts +0 -302
  34. package/src/tui/blessed/components/UIComponents.ts +0 -306
  35. package/src/tui/blessed/components/index.ts +0 -4
  36. package/src/tui/blessed/config.ts +0 -53
  37. package/src/tui/blessed/headerFetcher.ts +0 -42
  38. package/src/tui/blessed/index.ts +0 -2
  39. package/src/tui/blessed/qrCodeGenerator.ts +0 -20
  40. package/src/tui/blessed/webDebuggerConnection.ts +0 -128
  41. package/src/tui/ink/asciArt.ts +0 -7
  42. package/src/tui/ink/hooks/useQrCodes.ts +0 -27
  43. package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
  44. package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
  45. package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
  46. package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
  47. package/src/tui/ink/index.tsx +0 -243
  48. package/src/tui/ink/layout/Borders.tsx +0 -15
  49. package/src/tui/ink/layout/Container.tsx +0 -15
  50. package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
  51. package/src/tui/ink/sections/KeyBindings.tsx +0 -58
  52. package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
  53. package/src/tui/ink/sections/StatsSection.tsx +0 -20
  54. package/src/tui/ink/sections/URLsSection.tsx +0 -53
  55. package/src/tui/ink/utils/utils.ts +0 -35
  56. package/src/tui/spinner/spinner.ts +0 -64
  57. package/src/tunnel_manager/TunnelManager.ts +0 -1212
  58. package/src/types.ts +0 -255
  59. package/src/utils/FileServer.ts +0 -112
  60. package/src/utils/detect_vc_redist_on_windows.ts +0 -111
  61. package/src/utils/getFreePort.ts +0 -41
  62. package/src/utils/htmlTemplates.ts +0 -146
  63. package/src/utils/parseArgs.ts +0 -79
  64. package/src/utils/printer.ts +0 -81
  65. package/src/utils/util.ts +0 -18
  66. package/src/workers/file_serve_worker.ts +0 -33
  67. package/tsconfig.json +0 -17
  68. package/tsup.config.ts +0 -12
@@ -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
- }
@@ -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
-